blob: 0dc2ce18bd074d52b078a3398f585e84ee1343b0 [file] [log] [blame]
#!/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 = []
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 '):]
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))
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 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 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)