Index: client/dom/scripts/databasebuilder.py |
=================================================================== |
--- client/dom/scripts/databasebuilder.py (revision 5796) |
+++ client/dom/scripts/databasebuilder.py (working copy) |
@@ -1,615 +0,0 @@ |
-#!/usr/bin/python |
-# Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file |
-# for details. All rights reserved. Use of this source code is governed by a |
-# BSD-style license that can be found in the LICENSE file. |
- |
-import copy |
-import database |
-import idlparser |
-import logging |
-import os |
-import os.path |
- |
-from idlnode import * |
- |
-_logger = logging.getLogger('databasebuilder') |
- |
-# Used in source annotations to specify the parent interface declaring |
-# a displaced declaration. The 'via' attribute specifies the parent interface |
-# which implements a displaced declaration. |
-_VIA_ANNOTATION_ATTR_NAME = 'via' |
- |
-# Used in source annotations to specify the module that the interface was |
-# imported from. |
-_MODULE_ANNOTATION_ATTR_NAME = 'module' |
- |
- |
-class DatabaseBuilderOptions(object): |
- """Used in specifying options when importing new interfaces""" |
- |
- def __init__(self, |
- idl_syntax=idlparser.WEBIDL_SYNTAX, |
- idl_defines=[], |
- source=None, source_attributes={}, |
- type_rename_map={}, |
- rename_operation_arguments_on_merge=False, |
- add_new_interfaces=True, |
- obsolete_old_declarations=False): |
- """Constructor. |
- Args: |
- idl_syntax -- the syntax of the IDL file that is imported. |
- idl_defines -- list of definitions for the idl gcc pre-processor |
- source -- the origin of the IDL file, used for annotating the |
- database. |
- source_attributes -- this map of attributes is used as |
- annotation attributes. |
- rename_operation_arguments_on_merge -- if True, will rename |
- operation arguments when merging using the new name rather |
- than the old. |
- add_new_interfaces -- when False, if an interface is a new |
- addition, it will be ignored. |
- obsolete_old_declarations -- when True, if a declaration |
- from a certain source is not re-declared, it will be removed. |
- """ |
- self.source = source |
- self.source_attributes = source_attributes |
- self.idl_syntax = idl_syntax |
- self.idl_defines = idl_defines |
- self.type_rename_map = type_rename_map |
- self.rename_operation_arguments_on_merge = \ |
- rename_operation_arguments_on_merge |
- self.add_new_interfaces = add_new_interfaces |
- self.obsolete_old_declarations = obsolete_old_declarations |
- |
- |
-class DatabaseBuilder(object): |
- def __init__(self, database): |
- """DatabaseBuilder is used for importing and merging interfaces into |
- the Database""" |
- self._database = database |
- self._imported_interfaces = [] |
- self._impl_stmts = [] |
- self._same_signatures = {} |
- |
- def _load_idl_file(self, file_name, import_options): |
- """Loads an IDL file intor memory""" |
- idl_parser = idlparser.IDLParser(import_options.idl_syntax) |
- |
- try: |
- f = open(file_name, 'r') |
- content = f.read() |
- f.close() |
- |
- idl_ast = idl_parser.parse(content, |
- defines=import_options.idl_defines) |
- return IDLFile(idl_ast, file_name) |
- except SyntaxError, e: |
- raise RuntimeError('Failed to load file %s: %s' % (file_name, e)) |
- |
- def _resolve_type_defs(self, idl_file): |
- type_def_map = {} |
- # build map |
- for type_def in idl_file.all(IDLTypeDef): |
- if type_def.type.id != type_def.id: # sanity check |
- type_def_map[type_def.id] = type_def.type.id |
- # use the map |
- for type_node in idl_file.all(IDLType): |
- while type_node.id in type_def_map: |
- type_node.id = type_def_map[type_node.id] |
- |
- def _strip_ext_attributes(self, idl_file): |
- """Strips unuseful extended attributes.""" |
- for ext_attrs in idl_file.all(IDLExtAttrs): |
- # TODO: Decide which attributes are uninteresting. |
- pass |
- |
- def _split_declarations(self, interface, optional_argument_whitelist): |
- """Splits read-write attributes and operations with optional |
- arguments into multiple declarations""" |
- |
- # split attributes into setters and getters |
- new_attributes = [] |
- for attribute in interface.attributes: |
- if attribute.is_fc_getter or attribute.is_fc_setter: |
- new_attributes.append(attribute) |
- continue |
- getter_attr = copy.deepcopy(attribute) |
- getter_attr.is_fc_getter = True |
- new_attributes.append(getter_attr) |
- if not attribute.is_read_only: |
- setter_attr = copy.deepcopy(attribute) |
- setter_attr.is_fc_setter = True |
- new_attributes.append(setter_attr) |
- interface.attributes = new_attributes |
- |
- # Remove optional annotations from legacy optional arguments. |
- for op in interface.operations: |
- for i in range(0, len(op.arguments)): |
- argument = op.arguments[i] |
- |
- in_optional_whitelist = (interface.id, op.id, argument.id) in optional_argument_whitelist |
- if in_optional_whitelist or set(['Optional', 'Callback']).issubset(argument.ext_attrs.keys()): |
- argument.is_optional = True |
- argument.ext_attrs['RequiredCppParameter'] = None |
- continue |
- |
- if argument.is_optional: |
- if 'Optional' in argument.ext_attrs: |
- optional_value = argument.ext_attrs['Optional'] |
- if optional_value: |
- argument.is_optional = False |
- del argument.ext_attrs['Optional'] |
- |
- # split operations with optional args into multiple operations |
- new_ops = [] |
- for op in interface.operations: |
- for i in range(0, len(op.arguments)): |
- if op.arguments[i].is_optional: |
- op.arguments[i].is_optional = False |
- new_op = copy.deepcopy(op) |
- new_op.arguments = new_op.arguments[:i] |
- new_ops.append(new_op) |
- new_ops.append(op) |
- interface.operations = new_ops |
- |
- def _rename_types(self, idl_file, import_options): |
- """Rename interface and type names with names provided in the |
- options. Also clears scopes from scoped names""" |
- |
- def rename(name): |
- name_parts = name.split('::') |
- name = name_parts[-1] |
- if name in import_options.type_rename_map: |
- name = import_options.type_rename_map[name] |
- return name |
- |
- def rename_node(idl_node): |
- idl_node.id = rename(idl_node.id) |
- |
- def rename_ext_attrs(ext_attrs_node): |
- for type_valued_attribute_name in ['Supplemental']: |
- if type_valued_attribute_name in ext_attrs_node: |
- value = ext_attrs_node[type_valued_attribute_name] |
- if isinstance(value, str): |
- ext_attrs_node[type_valued_attribute_name] = rename(value) |
- |
- map(rename_node, idl_file.all(IDLInterface)) |
- map(rename_node, idl_file.all(IDLType)) |
- map(rename_ext_attrs, idl_file.all(IDLExtAttrs)) |
- |
- def _annotate(self, interface, module_name, import_options): |
- """Adds @ annotations based on the source and source_attributes |
- members of import_options.""" |
- |
- source = import_options.source |
- if not source: |
- return |
- |
- def add_source_annotation(idl_node): |
- annotation = IDLAnnotation( |
- copy.deepcopy(import_options.source_attributes)) |
- idl_node.annotations[source] = annotation |
- if ((isinstance(idl_node, IDLInterface) or |
- isinstance(idl_node, IDLMember)) and |
- idl_node.is_fc_suppressed): |
- annotation['suppressed'] = None |
- |
- add_source_annotation(interface) |
- interface.annotations[source][_MODULE_ANNOTATION_ATTR_NAME] = module_name |
- |
- map(add_source_annotation, interface.parents) |
- map(add_source_annotation, interface.constants) |
- map(add_source_annotation, interface.attributes) |
- map(add_source_annotation, interface.operations) |
- |
- def _sign(self, node): |
- """Computes a unique signature for the node, for merging purposed, by |
- concatenating types and names in the declaration.""" |
- if isinstance(node, IDLType): |
- res = node.id |
- if res.startswith('unsigned '): |
- res = res[len('unsigned '):] |
- if res in self._same_signatures: |
- res = self._same_signatures[res] |
- return res |
- |
- res = [] |
- if isinstance(node, IDLInterface): |
- res = ['interface', node.id] |
- elif isinstance(node, IDLParentInterface): |
- res = ['parent', self._sign(node.type)] |
- elif isinstance(node, IDLOperation): |
- res = ['op'] |
- for special in node.specials: |
- res.append(special) |
- if node.id is not None: |
- res.append(node.id) |
- for arg in node.arguments: |
- res.append(self._sign(arg.type)) |
- res.append(self._sign(node.type)) |
- elif isinstance(node, IDLAttribute): |
- res = [] |
- if node.is_fc_getter: |
- res.append('getter') |
- elif node.is_fc_setter: |
- res.append('setter') |
- res.append(node.id) |
- res.append(self._sign(node.type)) |
- elif isinstance(node, IDLConstant): |
- res = [] |
- res.append('const') |
- res.append(node.id) |
- res.append(node.value) |
- res.append(self._sign(node.type)) |
- else: |
- raise TypeError("Can't sign input of type %s" % type(node)) |
- return ':'.join(res) |
- |
- def _build_signatures_map(self, idl_node_list): |
- """Creates a hash table mapping signatures to idl_nodes for the |
- given list of nodes""" |
- res = {} |
- for idl_node in idl_node_list: |
- sig = self._sign(idl_node) |
- if sig is None: |
- continue |
- if sig in res: |
- raise RuntimeError('Warning: Multiple members have the same ' |
- 'signature: "%s"' % sig) |
- res[sig] = idl_node |
- return res |
- |
- def _get_parent_interfaces(self, interface): |
- """Return a list of all the parent interfaces of a given interface""" |
- res = [] |
- |
- def recurse(current_interface): |
- if current_interface in res: |
- return |
- res.append(current_interface) |
- for parent in current_interface.parents: |
- parent_name = parent.type.id |
- if self._database.HasInterface(parent_name): |
- recurse(self._database.GetInterface(parent_name)) |
- |
- recurse(interface) |
- return res[1:] |
- |
- def _merge_ext_attrs(self, old_attrs, new_attrs): |
- """Merges two sets of extended attributes. |
- |
- Returns: True if old_attrs has changed. |
- """ |
- changed = False |
- for (name, value) in new_attrs.items(): |
- if name in old_attrs and old_attrs[name] == value: |
- pass # Identical |
- else: |
- old_attrs[name] = value |
- changed = True |
- return changed |
- |
- def _merge_nodes(self, old_list, new_list, import_options): |
- """Merges two lists of nodes. Annotates nodes with the source of each |
- node. |
- |
- Returns: |
- True if the old_list has changed. |
- |
- Args: |
- old_list -- the list to merge into. |
- new_list -- list containing more nodes. |
- import_options -- controls how merging is done. |
- """ |
- changed = False |
- |
- source = import_options.source |
- |
- old_signatures_map = self._build_signatures_map(old_list) |
- new_signatures_map = self._build_signatures_map(new_list) |
- |
- # Merge new items |
- for (sig, new_node) in new_signatures_map.items(): |
- if sig not in old_signatures_map: |
- # New node: |
- old_list.append(new_node) |
- changed = True |
- else: |
- # Merge old and new nodes: |
- old_node = old_signatures_map[sig] |
- if (source not in old_node.annotations |
- and source in new_node.annotations): |
- old_node.annotations[source] = new_node.annotations[source] |
- changed = True |
- # Maybe rename arguments: |
- if isinstance(old_node, IDLOperation): |
- for i in range(0, len(old_node.arguments)): |
- old_arg_name = old_node.arguments[i].id |
- new_arg_name = new_node.arguments[i].id |
- if (old_arg_name != new_arg_name |
- and (old_arg_name == 'arg' |
- or old_arg_name.endswith('Arg') |
- or import_options.rename_operation_arguments_on_merge)): |
- old_node.arguments[i].id = new_arg_name |
- changed = True |
- # Maybe merge annotations: |
- if (isinstance(old_node, IDLAttribute) or |
- isinstance(old_node, IDLOperation)): |
- if self._merge_ext_attrs(old_node.ext_attrs, new_node.ext_attrs): |
- changed = True |
- |
- # Remove annotations on obsolete items from the same source |
- if import_options.obsolete_old_declarations: |
- for (sig, old_node) in old_signatures_map.items(): |
- if (source in old_node.annotations |
- and sig not in new_signatures_map): |
- _logger.warn('%s not available in %s anymore' % |
- (sig, source)) |
- del old_node.annotations[source] |
- changed = True |
- |
- return changed |
- |
- def _merge_interfaces(self, old_interface, new_interface, import_options): |
- """Merges the new_interface into the old_interface, annotating the |
- interface with the sources of each change.""" |
- |
- changed = False |
- |
- source = import_options.source |
- if (source and source not in old_interface.annotations and |
- source in new_interface.annotations and |
- not new_interface.is_supplemental): |
- old_interface.annotations[source] = new_interface.annotations[source] |
- changed = True |
- |
- def merge_list(what): |
- old_list = old_interface.__dict__[what] |
- new_list = new_interface.__dict__[what] |
- |
- if what != 'parents' and old_interface.id != new_interface.id: |
- for node in new_list: |
- node.ext_attrs['ImplementedBy'] = new_interface.id |
- |
- changed = self._merge_nodes(old_list, new_list, import_options) |
- |
- # Delete list items with zero remaining annotations. |
- if changed and import_options.obsolete_old_declarations: |
- |
- def has_annotations(idl_node): |
- return len(idl_node.annotations) |
- |
- old_interface.__dict__[what] = filter(has_annotations, old_list) |
- |
- return changed |
- |
- # Smartly merge various declarations: |
- if merge_list('parents'): |
- changed = True |
- if merge_list('constants'): |
- changed = True |
- if merge_list('attributes'): |
- changed = True |
- if merge_list('operations'): |
- changed = True |
- |
- if self._merge_ext_attrs(old_interface.ext_attrs, new_interface.ext_attrs): |
- changed = True |
- |
- _logger.info('merged interface %s (changed=%s, supplemental=%s)' % |
- (old_interface.id, changed, new_interface.is_supplemental)) |
- |
- return changed |
- |
- def _merge_impl_stmt(self, impl_stmt, import_options): |
- """Applies "X implements Y" statemetns on the proper places in the |
- database""" |
- implementor_name = impl_stmt.implementor.id |
- implemented_name = impl_stmt.implemented.id |
- _logger.info('merging impl stmt %s implements %s' % |
- (implementor_name, implemented_name)) |
- |
- if implementor_name == implemented_name: |
- # After renaming, this might happen (e.g. Window impls |
- # AbstractView, but AbstractView was renamed to Window). |
- return |
- |
- source = import_options.source |
- if self._database.HasInterface(implementor_name): |
- interface = self._database.GetInterface(implementor_name) |
- if interface.parents is None: |
- interface.parents = [] |
- for parent in interface.parents: |
- if parent.type.id == implemented_name: |
- if source and source not in parent.annotations: |
- parent.annotations[source] = IDLAnnotation( |
- import_options.source_attributes) |
- return |
- # not found, so add new one |
- parent = IDLParentInterface(None) |
- parent.type = IDLType(implemented_name) |
- if source: |
- parent.annotations[source] = IDLAnnotation( |
- import_options.source_attributes) |
- interface.parents.append(parent) |
- |
- def set_same_signatures(self, signatures_table): |
- """Customize signature calculations by providing a custom table |
- indicating which types are equivalent""" |
- self._same_signatures = signatures_table |
- |
- def merge_imported_interfaces(self, optional_argument_whitelist): |
- """Merges all imported interfaces and loads them into the DB.""" |
- |
- # Step 1: Pre process imported interfaces |
- for interface, module_name, import_options in self._imported_interfaces: |
- self._split_declarations(interface, optional_argument_whitelist) |
- self._annotate(interface, module_name, import_options) |
- |
- # Step 2: Add all new interfaces and merge overlapping ones |
- for interface, module_name, import_options in self._imported_interfaces: |
- if not interface.is_supplemental: |
- if self._database.HasInterface(interface.id): |
- old_interface = self._database.GetInterface(interface.id) |
- self._merge_interfaces(old_interface, interface, import_options) |
- else: |
- if import_options.add_new_interfaces: |
- self._database.AddInterface(interface) |
- |
- # Step 3: Merge in supplemental interfaces |
- for interface, module_name, import_options in self._imported_interfaces: |
- if interface.is_supplemental: |
- target_name = interface.ext_attrs['Supplemental'] |
- if target_name: |
- # [Supplemental=DOMWindow] - merge into DOMWindow. |
- target = target_name |
- else: |
- # [Supplemental] - merge into existing inteface with same name. |
- target = interface.id |
- if self._database.HasInterface(target): |
- old_interface = self._database.GetInterface(target) |
- self._merge_interfaces(old_interface, interface, import_options) |
- else: |
- raise Exception("Supplemental target '%s' not found", target) |
- |
- # Step 4: Resolve 'implements' statements |
- for impl_stmt, import_options in self._impl_stmts: |
- self._merge_impl_stmt(impl_stmt, import_options) |
- |
- self._impl_stmts = [] |
- self._imported_interfaces = [] |
- |
- def import_idl_file(self, file_path, |
- import_options=DatabaseBuilderOptions()): |
- """Parses, loads into memory and cleans up and IDL file""" |
- idl_file = self._load_idl_file(file_path, import_options) |
- |
- self._strip_ext_attributes(idl_file) |
- self._resolve_type_defs(idl_file) |
- self._rename_types(idl_file, import_options) |
- |
- def enabled(idl_node): |
- return self._is_node_enabled(idl_node, import_options.idl_defines) |
- |
- for module in idl_file.modules: |
- for interface in module.interfaces: |
- if not self._is_node_enabled(interface, import_options.idl_defines): |
- _logger.info('skipping interface %s/%s (source=%s file=%s)' |
- % (module.id, interface.id, import_options.source, |
- file_path)) |
- continue |
- |
- _logger.info('importing interface %s/%s (source=%s file=%s)' |
- % (module.id, interface.id, import_options.source, |
- file_path)) |
- interface.attributes = filter(enabled, interface.attributes) |
- interface.operations = filter(enabled, interface.operations) |
- self._imported_interfaces.append((interface, module.id, import_options)) |
- |
- for implStmt in module.implementsStatements: |
- self._impl_stmts.append((implStmt, import_options)) |
- |
- def _is_node_enabled(self, node, idl_defines): |
- if not 'Conditional' in node.ext_attrs: |
- return True |
- |
- def enabled(condition): |
- return 'ENABLE_%s' % condition in idl_defines |
- |
- conditional = node.ext_attrs['Conditional'] |
- if conditional.find('&') != -1: |
- for condition in conditional.split('&'): |
- if not enabled(condition): |
- return False |
- return True |
- |
- for condition in conditional.split('|'): |
- if enabled(condition): |
- return True |
- return False |
- |
- def import_idl_directory(self, directory_path, |
- import_options=DatabaseBuilderOptions()): |
- """Parses, loads into memory and cleans up all IDL files in a given |
- directory""" |
- if not os.path.exists(directory_path): |
- raise RuntimeError('directory not found: %s' % directory_path) |
- |
- def visitor(arg, dir_name, names): |
- module = dir_name[len(directory_path) + 1:] |
- for name in names: |
- file_name = os.path.join(dir_name, name) |
- (interface, ext) = os.path.splitext(file_name) |
- if ext == '.idl' and not name.startswith('._'): |
- self.import_idl_file(file_name, import_options) |
- os.path.walk(directory_path, visitor, None) |
- |
- def fix_displacements(self, source): |
- """E.g. In W3C, something is declared on HTMLDocument but in WebKit |
- its on Document, so we need to mark that something in HTMLDocument |
- with @WebKit(via=Document). The 'via' attribute specifies the |
- parent interface that has the declaration.""" |
- |
- for interface in self._database.GetInterfaces(): |
- changed = False |
- |
- _logger.info('fixing displacements in %s' % interface.id) |
- |
- for parent_interface in self._get_parent_interfaces(interface): |
- _logger.info('scanning parent %s of %s' % |
- (parent_interface.id, interface.id)) |
- |
- def fix_nodes(local_list, parent_list): |
- changed = False |
- parent_signatures_map = self._build_signatures_map( |
- parent_list) |
- for idl_node in local_list: |
- sig = self._sign(idl_node) |
- if sig in parent_signatures_map: |
- parent_member = parent_signatures_map[sig] |
- if (source in parent_member.annotations |
- and source not in idl_node.annotations |
- and _VIA_ANNOTATION_ATTR_NAME |
- not in parent_member.annotations[source]): |
- idl_node.annotations[source] = IDLAnnotation( |
- {_VIA_ANNOTATION_ATTR_NAME: parent_interface.id}) |
- changed = True |
- return changed |
- |
- changed = fix_nodes(interface.constants, |
- parent_interface.constants) or changed |
- changed = fix_nodes(interface.attributes, |
- parent_interface.attributes) or changed |
- changed = fix_nodes(interface.operations, |
- parent_interface.operations) or changed |
- if changed: |
- _logger.info('fixed displaced declarations in %s' % |
- interface.id) |
- |
- def normalize_annotations(self, sources): |
- """Makes the IDLs less verbose by removing annotation attributes |
- that are identical to the ones defined at the interface level. |
- |
- Args: |
- sources -- list of source names to normalize.""" |
- for interface in self._database.GetInterfaces(): |
- _logger.debug('normalizing annotations for %s' % interface.id) |
- for source in sources: |
- if (source not in interface.annotations or |
- not interface.annotations[source]): |
- continue |
- top_level_annotation = interface.annotations[source] |
- |
- def normalize(idl_node): |
- if (source in idl_node.annotations |
- and idl_node.annotations[source]): |
- annotation = idl_node.annotations[source] |
- for name, value in annotation.items(): |
- if (name in top_level_annotation |
- and value == top_level_annotation[name]): |
- del annotation[name] |
- |
- map(normalize, interface.parents) |
- map(normalize, interface.constants) |
- map(normalize, interface.attributes) |
- map(normalize, interface.operations) |