blob: a105c10b0d41f6c0514b4a739c39f49b97072a18 [file] [log] [blame]
#!/usr/bin/python
# Copyright (c) 2012, 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.
"""This module generates Elemental APIs from the IDL database."""
import emitter
import idlnode
import logging
import multiemitter
import os
import re
import shutil
from generator_java import *
from systembaseelemental import *
from systemgwt import *
from systemgwtjso import *
from templateloader import TemplateLoader
_logger = logging.getLogger('elementalgenerator')
def MergeNodes(node, other):
node.operations.extend(other.operations)
for attribute in other.attributes:
if not node.has_attribute(attribute):
node.attributes.append(attribute)
node.constants.extend(other.constants)
class ElementalGenerator(object):
"""Utilities to generate Elemental APIs and corresponding JavaScript."""
def __init__(self, auxiliary_dir, template_dir, base_package):
"""Constructor for the DartGenerator.
Args:
auxiliary_dir -- location of auxiliary handwritten classes
template_dir -- location of template files
base_package -- the base package name for the generated code.
"""
self._auxiliary_dir = auxiliary_dir
self._template_dir = template_dir
self._base_package = base_package
self._auxiliary_files = {}
self._dart_templates_re = re.compile(r'[\w.:]+<([\w\.<>:]+)>')
self._emitters = None # set later
def _StripModules(self, type_name):
return type_name.split('::')[-1]
def _IsCompoundType(self, database, type_name):
if IsPrimitiveType(type_name):
return True
striped_type_name = self._StripModules(type_name)
if database.HasInterface(striped_type_name):
return True
dart_template_match = self._dart_templates_re.match(type_name)
if dart_template_match:
# Dart templates
parent_type_name = type_name[0 : dart_template_match.start(1) - 1]
sub_type_name = dart_template_match.group(1)
return (self._IsCompoundType(database, parent_type_name) and
self._IsCompoundType(database, sub_type_name))
return False
def _IsDartType(self, type_name):
return '.' in type_name
def LoadAuxiliary(self):
def Visitor(_, dirname, names):
for name in names:
if name.endswith('.dart'):
name = name[0:-5] # strip off ".dart"
self._auxiliary_files[name] = os.path.join(dirname, name)
os.path.walk(self._auxiliary_dir, Visitor, None)
def RenameTypes(self, database, conversion_table, rename_javascript_binding_names):
"""Renames interfaces using the given conversion table.
References through all interfaces will be renamed as well.
Args:
database: the database to apply the renames to.
conversion_table: maps old names to new names.
"""
if conversion_table is None:
conversion_table = {}
# Rename interfaces:
for old_name, new_name in conversion_table.items():
if database.HasInterface(old_name):
_logger.info('renaming interface %s to %s' % (old_name, new_name))
interface = database.GetInterface(old_name)
database.DeleteInterface(old_name)
if not database.HasInterface(new_name):
interface.id = new_name
database.AddInterface(interface)
else:
new_interface = database.GetInterface(new_name)
MergeNodes(new_interface, interface)
if rename_javascript_binding_names:
interface.javascript_binding_name = new_name
interface.doc_js_name = new_name
for member in (interface.operations + interface.constants
+ interface.attributes):
member.doc_js_interface_name = new_name
# Fix references:
for interface in database.GetInterfaces():
for idl_type in interface.all(idlnode.IDLType):
type_name = self._StripModules(idl_type.id)
if type_name in conversion_table:
idl_type.id = conversion_table[type_name]
def FilterMembersWithUnidentifiedTypes(self, database):
"""Removes unidentified types.
Removes constants, attributes, operations and parents with unidentified
types.
"""
for interface in database.GetInterfaces():
def IsIdentified(idl_node):
node_name = idl_node.id if idl_node.id else 'parent'
for idl_type in idl_node.all(idlnode.IDLType):
type_name = idl_type.id
if (type_name is not None and
self._IsCompoundType(database, type_name)):
continue
_logger.warn('removing %s in %s which has unidentified type %s' %
(node_name, interface.id, type_name))
return False
return True
interface.constants = filter(IsIdentified, interface.constants)
interface.attributes = filter(IsIdentified, interface.attributes)
interface.operations = filter(IsIdentified, interface.operations)
interface.parents = filter(IsIdentified, interface.parents)
def FilterInterfaces(self, database,
and_annotations=[],
or_annotations=[],
exclude_displaced=[],
exclude_suppressed=[]):
"""Filters a database to remove interfaces and members that are missing
annotations.
The FremontCut IDLs use annotations to specify implementation
status in various platforms. For example, if a member is annotated
with @WebKit, this means that the member is supported by WebKit.
Args:
database -- the database to filter
all_annotations -- a list of annotation names a member has to
have or it will be filtered.
or_annotations -- if a member has one of these annotations, it
won't be filtered even if it is missing some of the
all_annotations.
exclude_displaced -- if a member has this annotation and it
is marked as displaced it will always be filtered.
exclude_suppressed -- if a member has this annotation and it
is marked as suppressed it will always be filtered.
"""
# Filter interfaces and members whose annotations don't match.
for interface in database.GetInterfaces():
def HasAnnotations(idl_node):
"""Utility for determining if an IDLNode has all
the required annotations"""
for a in exclude_displaced:
if (a in idl_node.annotations
and 'via' in idl_node.annotations[a]):
return False
for a in exclude_suppressed:
if (a in idl_node.annotations
and 'suppressed' in idl_node.annotations[a]):
return False
for a in or_annotations:
if a in idl_node.annotations:
return True
if and_annotations == []:
return False
for a in and_annotations:
if a not in idl_node.annotations:
return False
return True
if HasAnnotations(interface):
interface.constants = filter(HasAnnotations, interface.constants)
interface.attributes = filter(HasAnnotations, interface.attributes)
interface.operations = filter(HasAnnotations, interface.operations)
interface.parents = filter(HasAnnotations, interface.parents)
else:
database.DeleteInterface(interface.id)
self.FilterMembersWithUnidentifiedTypes(database)
def Generate(self, database, output_dir,
module_source_preference=[], source_filter=None,
super_database=None, common_prefix=None, super_map={},
html_map={}, lib_dir=None, systems=[]):
"""Generates Dart and JS files for the loaded interfaces.
Args:
database -- database containing interfaces to generate code for.
output_dir -- directory to write generated files to.
module_source_preference -- priority order list of source annotations to
use when choosing a module name, if none specified uses the module name
from the database.
source_filter -- if specified, only outputs interfaces that have one of
these source annotation and rewrites the names of superclasses not
marked with this source to use the common prefix.
super_database -- database containing super interfaces that the generated
interfaces should extend.
common_prefix -- prefix for the common library, if any.
lib_file_path -- filename for generated .lib file, None if not required.
lib_template -- template file in this directory for generated lib file.
"""
self._emitters = multiemitter.MultiEmitter()
self._database = database
self._output_dir = output_dir
self._FixEventTargets()
self._ComputeInheritanceClosure()
self._systems = []
# TODO(jmesserly): only create these if needed
if ('gwtjso' in systems):
jso_system = ElementalJsoSystem(
TemplateLoader(self._template_dir, ['dom/jso', 'dom', '']),
self._database, self._emitters, self._output_dir)
self._systems.append(jso_system)
if ('gwt' in systems):
interface_system = ElementalInterfacesSystem(
TemplateLoader(self._template_dir, ['dom/interface', 'dom', '']),
self._database, self._emitters, self._output_dir)
self._systems.append(interface_system)
# if 'gwt' in systems:
# elemental_system = ElementalSystem(
# TemplateLoader(self._template_dir, ['dom/elemental', 'dom', '']),
# self._database, self._emitters, self._output_dir)
# elemental_system._interface_system = interface_system
# self._systems.append(elemental_system)
# Collect interfaces
interfaces = []
for interface in database.GetInterfaces():
if not MatchSourceFilter(source_filter, interface):
# Skip this interface since it's not present in the required source
_logger.info('Omitting interface - %s' % interface.id)
continue
interfaces.append(interface)
# TODO(sra): Use this list of exception names to generate information to
# tell Frog which exceptions can be passed from JS to Dart code.
exceptions = self._CollectExceptions(interfaces)
mixins = self._ComputeMixins(self._PreOrderInterfaces(interfaces))
for system in self._systems:
# give outputters a chance to see the mixin list before starting
system.ProcessMixins(mixins)
# copy all mixin methods from every interface to this base interface
self.PopulateMixinBase(self._database.GetInterface('ElementalMixinBase'), mixins)
# Render all interfaces into Dart and save them in files.
for interface in self._PreOrderInterfaces(interfaces):
super_interface = None
super_name = interface.id
if super_name in super_map:
super_name = super_map[super_name]
if (super_database is not None and
super_database.HasInterface(super_name)):
super_interface = super_name
interface_name = interface.id
auxiliary_file = self._auxiliary_files.get(interface_name)
if auxiliary_file is not None:
_logger.info('Skipping %s because %s exists' % (
interface_name, auxiliary_file))
continue
info = RecognizeCallback(interface)
if info:
for system in self._systems:
system.ProcessCallback(interface, info)
else:
if 'Callback' in interface.ext_attrs:
_logger.info('Malformed callback: %s' % interface.id)
self._ProcessInterface(interface, super_interface,
source_filter, common_prefix)
for system in self._systems:
system.Finish()
def PopulateMixinBase(self, mixinbase, mixins):
"""Copy all mixin attributes and operations to mixin base class"""
for mixin_name in mixins:
if self._database.HasInterface(mixin_name):
mixin = self._database.GetInterface(mixin_name)
mixinbase.attributes.extend(mixin.attributes)
mixinbase.operations.extend(mixin.operations)
for extattr in mixin.ext_attrs.keys():
mixinbase.ext_attrs[extattr] = mixin.ext_attrs[extattr]
# compute all interfaces which are in disjoint type hierarchies
# that is, cannot be SingleImplJSO without hoisting
def _ComputeMixins(self, interfaces):
implementors = {}
mixins = {}
parents = {}
# first compute the set of all inherited super-interfaces of every interface
for interface in interfaces:
if interface.parents:
# the first parent interface is the superclass
parent = interface.parents[0]
# if we haven't processed this one before
if not interface.id in parents:
# compute a list of all of the direct superclass this interface
parents[interface.id] = []
parents[interface.id].append(parent)
if parent.type.id in parents:
# inherit all the super-interfaces
parents[interface.id].extend(parents[parent.type.id])
implemented_by = None
for interface in interfaces:
# now, examining secondary interfaces
for secondary in interface.parents[1:]:
if secondary.type.id in implementors:
implemented_by = implementors[secondary.type.id]
# if the interface is implemented by someone else who is not one of my parents, it is not SingleJsoImpl
if not implemented_by in parents[interface.id]:
mixins[secondary.type.id]=implemented_by
print "Mixin detected %s, previously implemented by %s, but also implemented by %s" % (secondary.type.id, implemented_by, interface.id)
# add all parents of the mixin as well
superparents = []
superiface = secondary.type.id
if self._database.HasInterface(superiface):
self.getParents(self._database.GetInterface(superiface), superparents)
for parent in superparents:
mixins[parent.id]=implemented_by
print "Super Mixin detected %s, previously implemented by %s, but also implemented by %s" % (parent.id, implemented_by, interface.id)
else:
implementors[secondary.type.id] = interface.id
# manual patch for outliers not picked up by this logic
mixins['ElementTimeControl']=1
mixins['ElementTraversal']=1
return mixins.keys()
def _PreOrderInterfaces(self, interfaces):
"""Returns the interfaces in pre-order, i.e. parents first."""
seen = set()
ordered = []
def visit(interface):
if interface.id in seen:
return
seen.add(interface.id)
for parent in interface.parents:
if IsDartCollectionType(parent.type.id):
continue
if self._database.HasInterface(parent.type.id):
parent_interface = self._database.GetInterface(parent.type.id)
visit(parent_interface)
ordered.append(interface)
for interface in interfaces:
visit(interface)
return ordered
def _ProcessInterface(self, interface, super_interface_name,
source_filter,
common_prefix):
"""."""
_logger.info('Generating %s' % interface.id)
generators = [system.InterfaceGenerator(interface,
common_prefix,
super_interface_name,
source_filter)
for system in self._systems]
generators = filter(None, generators)
mixinbase = self._database.GetInterface("ElementalMixinBase")
parentops = []
parentattrs = []
directParents = []
mixinOps = []
# compute the immediate parents of each interface (not including secondary interfaces)
self.getParents(interface, directParents)
# if not the mixin base, add its parents
if interface.id != 'ElementalMixinBase':
self.getParents(mixinbase, directParents)
# add the mixin base class itself as the parent of everything
directParents.insert(0, mixinbase)
# for each parent interface
for pint in directParents:
for op in pint.operations:
# compute unique method signatures for each operation :
op_name = op.ext_attrs.get('DartName', op.id)
sig = "%s %s(" % (op.type.id, op_name)
for arg in op.arguments:
sig += arg.type.id
parentops.append(sig)
for attr in pint.attributes:
# compute attributes
if attr.is_fc_getter:
parentattrs.append("getter_" + DartDomNameOfAttribute(attr))
if attr.is_fc_setter:
parentattrs.append("setter_" + DartDomNameOfAttribute(attr))
for generator in generators:
generator.StartInterface()
for const in sorted(interface.constants, ConstantOutputOrder):
for generator in generators:
generator.AddConstant(const)
attributes = [attr for attr in interface.attributes]
for (getter, setter) in _PairUpAttributes(attributes):
for generator in generators:
# detect if attribute is inherited (as opposed to just redeclared)
inheritedGetter = ("getter_" + DartDomNameOfAttribute(getter)) in parentattrs
inheritedSetter = setter and ("setter_" + DartDomNameOfAttribute(setter)) in parentattrs
generator.AddAttribute(getter, setter, inheritedGetter, inheritedSetter)
# The implementation should define an indexer if the interface directly
# extends List.
element_type = MaybeListElementType(interface)
if element_type:
for generator in generators:
generator.AddIndexer(element_type)
# Generate operations
alreadyGenerated = []
for operation in interface.operations:
op_name = operation.ext_attrs.get('DartName', operation.id)
sig = "%s %s(" % (operation.type.id, op_name)
for arg in operation.arguments:
sig += arg.type.id
if sig in alreadyGenerated:
continue
alreadyGenerated.append(sig)
# hacks, should be able to compute this from IDL database
if operation.id == 'toString':
# implemented on JSO.toString()
continue
operations = []
operations.append(operation)
info = AnalyzeOperation(interface, operations)
for generator in generators:
# don't override stuff hoisted to mixin base in implementors
inherited = sig in parentops
if info.IsStatic():
generator.AddStaticOperation(info, inherited)
else:
generator.AddOperation(info, inherited)
# With multiple inheritance, attributes and operations of non-first
# interfaces need to be added. Sometimes the attribute or operation is
# defined in the current interface as well as a parent. In that case we
# avoid making a duplicate definition and pray that the signatures match.
for parent_interface in self._TransitiveSecondaryParents(interface):
if isinstance(parent_interface, str): # IsDartCollectionType(parent_interface)
continue
attributes = [attr for attr in parent_interface.attributes
if not FindMatchingAttribute(interface, attr)]
for (getter, setter) in _PairUpAttributes(attributes):
for generator in generators:
generator.AddSecondaryAttribute(parent_interface, getter, setter)
# Group overloaded operations by id
operationsById = {}
for operation in parent_interface.operations:
if operation.id not in operationsById:
operationsById[operation.id] = []
operationsById[operation.id].append(operation)
# Generate operations
for id in sorted(operationsById.keys()):
if not any(op.id == id for op in interface.operations):
operations = operationsById[id]
info = AnalyzeOperation(interface, operations)
for generator in generators:
generator.AddSecondaryOperation(parent_interface, info)
for generator in generators:
generator.FinishInterface()
return
def getParents(self, interface, results):
if interface.parents:
pid = interface.parents[0].type.id
if self._database.HasInterface(pid):
pint = self._database.GetInterface(interface.parents[0].type.id)
results.append(pint)
self.getParents(pint, results)
def _TransitiveSecondaryParents(self, interface):
"""Returns a list of all non-primary parents.
The list contains the interface objects for interfaces defined in the
database, and the name for undefined interfaces.
"""
def walk(parents):
for parent in parents:
if IsDartCollectionType(parent.type.id):
result.append(parent.type.id)
continue
if self._database.HasInterface(parent.type.id):
parent_interface = self._database.GetInterface(parent.type.id)
result.append(parent_interface)
walk(parent_interface.parents)
result = []
walk(interface.parents[1:])
return result;
def _CollectExceptions(self, interfaces):
"""Returns the names of all exception classes raised."""
exceptions = set()
for interface in interfaces:
for attribute in interface.attributes:
if attribute.get_raises:
exceptions.add(attribute.get_raises.id)
if attribute.set_raises:
exceptions.add(attribute.set_raises.id)
for operation in interface.operations:
if operation.raises:
exceptions.add(operation.raises.id)
return exceptions
def Flush(self):
"""Write out all pending files."""
_logger.info('Flush...')
self._emitters.Flush()
def _FixEventTargets(self):
for interface in self._database.GetInterfaces():
# Create fake EventTarget parent interface for interfaces that have
# 'EventTarget' extended attribute.
if 'EventTarget' in interface.ext_attrs:
ast = [('Annotation', [('Id', 'WebKit')]),
('InterfaceType', ('ScopedName', 'EventTarget'))]
interface.parents.append(idlnode.IDLParentInterface(ast))
def _ComputeInheritanceClosure(self):
def Collect(interface, seen, collected):
name = interface.id
if '<' in name:
# TODO(sra): Handle parameterized types.
return
if not name in seen:
seen.add(name)
collected.append(name)
for parent in interface.parents:
# TODO(sra): Handle parameterized types.
if not '<' in parent.type.id:
if self._database.HasInterface(parent.type.id):
Collect(self._database.GetInterface(parent.type.id),
seen, collected)
self._inheritance_closure = {}
for interface in self._database.GetInterfaces():
seen = set()
collected = []
Collect(interface, seen, collected)
self._inheritance_closure[interface.id] = collected
def _AllImplementedInterfaces(self, interface):
"""Returns a list of the names of all interfaces implemented by 'interface'.
List includes the name of 'interface'.
"""
return self._inheritance_closure[interface.id]
def _PairUpAttributes(attributes):
"""Returns a list of (getter, setter) pairs sorted by name.
One element of the pair may be None.
"""
names = sorted(set(attr.id for attr in attributes))
getters = {}
setters = {}
for attr in attributes:
if attr.is_fc_getter:
getters[attr.id] = attr
elif attr.is_fc_setter and 'Replaceable' not in attr.ext_attrs:
setters[attr.id] = attr
return [(getters.get(id), setters.get(id)) for id in names]
# ------------------------------------------------------------------------------
class DummyImplementationSystem(SystemElemental):
"""Generates a dummy implementation for use by the editor analysis.
All the code comes from hand-written library files.
"""
def __init__(self, templates, database, emitters, output_dir):
super(DummyImplementationSystem, self).__init__(
templates, database, emitters, output_dir)
factory_providers_file = os.path.join(self._output_dir, 'src', 'dummy',
'RegularFactoryProviders.dart')
self._factory_providers_emitter = self._emitters.FileEmitter(
factory_providers_file)
self._impl_file_paths = [factory_providers_file]
def InterfaceGenerator(self,
interface,
common_prefix,
super_interface_name,
source_filter):
return DummyInterfaceGenerator(self, interface)
def ProcessCallback(self, interface, info):
pass
def GenerateLibraries(self, lib_dir):
# Library generated for implementation.
self._GenerateLibFile(
'dom_dummy.darttemplate',
os.path.join(lib_dir, 'dom_dummy.dart'),
(self._interface_system._dart_interface_file_paths +
self._interface_system._dart_callback_file_paths +
self._impl_file_paths))
# ------------------------------------------------------------------------------
class DummyInterfaceGenerator(object):
"""Generates dummy implementation."""
def __init__(self, system, interface):
self._system = system
self._interface = interface
def StartInterface(self):
# There is no implementation to match the interface, but there might be a
# factory constructor for the Dart interface.
constructor_info = AnalyzeConstructor(self._interface)
if constructor_info:
dart_interface_name = self._interface.id
self._EmitFactoryProvider(dart_interface_name, constructor_info)
def _EmitFactoryProvider(self, interface_name, constructor_info):
factory_provider = '_' + interface_name + 'FactoryProvider'
self._system._factory_providers_emitter.Emit(
self._system._templates.Load('factoryprovider.darttemplate'),
FACTORYPROVIDER=factory_provider,
CONSTRUCTOR=interface_name,
PARAMETERS=constructor_info.ParametersImplementationDeclaration())
def FinishInterface(self):
pass
def AddConstant(self, constant):
pass
def AddAttribute(self, getter, setter, inheritedGetter, inheritedSetter):
pass
def AddSecondaryAttribute(self, interface, getter, setter):
pass
def AddSecondaryOperation(self, interface, info):
pass
def AddIndexer(self, element_type):
pass
def AddTypedArrayConstructors(self, element_type):
pass
def AddOperation(self, info, inherited):
pass
def AddStaticOperation(self, info, inherited):
pass
def AddEventAttributes(self, event_attrs):
pass