blob: 67a256506db04facc58f984ca6dcd9e0061510ea [file] [log] [blame]
# Copyright (C) 2013 Google Inc. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# pylint: disable=relative-import
"""Blink IDL Intermediate Representation (IR) classes.
Classes are primarily constructors, which build an IdlDefinitions object
(and various contained objects) from an AST (produced by blink_idl_parser).
IR stores typedefs and they are resolved by the code generator.
Typedef resolution uses some auxiliary classes and OOP techniques to make this
a generic call. See TypedefResolver class in code_generator_v8.py.
Class hierarchy (mostly containment, '<' for inheritance):
IdlDefinitions
IdlCallbackFunction < TypedObject
IdlEnum :: FIXME: remove, just use a dict for enums
IdlInterface
IdlAttribute < TypedObject
IdlConstant < TypedObject
IdlLiteral
IdlOperation < TypedObject
IdlArgument < TypedObject
IdlSerializer
IdlStringifier
IdlIterable < IdlIterableOrMaplikeOrSetlike
IdlMaplike < IdlIterableOrMaplikeOrSetlike
IdlSetlike < IdlIterableOrMaplikeOrSetlike
IdlException < IdlInterface
(same contents as IdlInterface)
TypedObject :: Object with one or more attributes that is a type.
IdlArgument is 'picklable', as it is stored in interfaces_info.
Design doc: http://www.chromium.org/developers/design-documents/idl-compiler
"""
import abc
from idl_types import IdlArrayType
from idl_types import IdlFrozenArrayType
from idl_types import IdlNullableType
from idl_types import IdlPromiseType
from idl_types import IdlRecordType
from idl_types import IdlSequenceType
from idl_types import IdlType
from idl_types import IdlUnionType
SPECIAL_KEYWORD_LIST = ['LEGACYCALLER', 'GETTER', 'SETTER', 'DELETER']
################################################################################
# TypedObject
################################################################################
class TypedObject(object):
"""Object with a type, such as an Attribute or Operation (return value).
The type can be an actual type, or can be a typedef, which must be resolved
by the TypedefResolver before passing data to the code generator.
"""
__metaclass__ = abc.ABCMeta
idl_type_attributes = ('idl_type',)
################################################################################
# Definitions (main container class)
################################################################################
class IdlDefinitions(object):
def __init__(self, node):
"""Args: node: AST root node, class == 'File'"""
self.callback_functions = {}
self.dictionaries = {}
self.enumerations = {}
self.implements = []
self.interfaces = {}
self.typedefs = {}
node_class = node.GetClass()
if node_class != 'File':
raise ValueError('Unrecognized node class: %s' % node_class)
children = node.GetChildren()
for child in children:
child_class = child.GetClass()
if child_class == 'Interface':
interface = IdlInterface(child)
self.interfaces[interface.name] = interface
elif child_class == 'Exception':
exception = IdlException(child)
# For simplicity, treat exceptions as interfaces
self.interfaces[exception.name] = exception
elif child_class == 'Typedef':
typedef = IdlTypedef(child)
self.typedefs[typedef.name] = typedef
elif child_class == 'Enum':
enumeration = IdlEnum(child)
self.enumerations[enumeration.name] = enumeration
elif child_class == 'Callback':
callback_function = IdlCallbackFunction(child)
self.callback_functions[callback_function.name] = callback_function
elif child_class == 'Implements':
self.implements.append(IdlImplement(child))
elif child_class == 'Dictionary':
dictionary = IdlDictionary(child)
self.dictionaries[dictionary.name] = dictionary
else:
raise ValueError('Unrecognized node class: %s' % child_class)
def accept(self, visitor):
visitor.visit_definitions(self)
for interface in self.interfaces.values():
interface.accept(visitor)
for callback_function in self.callback_functions.values():
callback_function.accept(visitor)
for dictionary in self.dictionaries.values():
dictionary.accept(visitor)
for enumeration in self.enumerations.values():
enumeration.accept(visitor)
for implement in self.implements:
implement.accept(visitor)
for typedef in self.typedefs.values():
typedef.accept(visitor)
def update(self, other):
"""Update with additional IdlDefinitions."""
for interface_name, new_interface in other.interfaces.items():
if not new_interface.is_partial:
# Add as new interface
self.interfaces[interface_name] = new_interface
else:
# Merge partial to existing interface
try:
self.interfaces[interface_name].merge(new_interface)
except KeyError:
raise Exception('Tried to merge partial interface for {0}, '
'but no existing interface by that name'
.format(interface_name))
# Merge callbacks and enumerations
self.enumerations.update(other.enumerations)
self.callback_functions.update(other.callback_functions)
################################################################################
# Callback Functions
################################################################################
class IdlCallbackFunction(TypedObject):
def __init__(self, node):
children = node.GetChildren()
num_children = len(children)
if num_children < 2 or num_children > 3:
raise ValueError('Expected 2 or 3 children, got %s' % num_children)
type_node = children[0]
arguments_node = children[1]
if num_children == 3:
ext_attributes_node = children[2]
self.extended_attributes = (
ext_attributes_node_to_extended_attributes(ext_attributes_node))
else:
self.extended_attributes = {}
arguments_node_class = arguments_node.GetClass()
if arguments_node_class != 'Arguments':
raise ValueError('Expected Arguments node, got %s' % arguments_node_class)
self.name = node.GetName()
self.idl_type = type_node_to_type(type_node)
self.arguments = arguments_node_to_arguments(arguments_node)
def accept(self, visitor):
visitor.visit_callback_function(self)
for argument in self.arguments:
argument.accept(visitor)
################################################################################
# Dictionary
################################################################################
class IdlDictionary(object):
def __init__(self, node):
self.extended_attributes = {}
self.is_partial = bool(node.GetProperty('Partial'))
self.name = node.GetName()
self.members = []
self.parent = None
for child in node.GetChildren():
child_class = child.GetClass()
if child_class == 'Inherit':
self.parent = child.GetName()
elif child_class == 'Key':
self.members.append(IdlDictionaryMember(child))
elif child_class == 'ExtAttributes':
self.extended_attributes = (
ext_attributes_node_to_extended_attributes(child))
else:
raise ValueError('Unrecognized node class: %s' % child_class)
def accept(self, visitor):
visitor.visit_dictionary(self)
for member in self.members:
member.accept(visitor)
class IdlDictionaryMember(TypedObject):
def __init__(self, node):
self.default_value = None
self.extended_attributes = {}
self.idl_type = None
self.is_required = bool(node.GetProperty('REQUIRED'))
self.name = node.GetName()
for child in node.GetChildren():
child_class = child.GetClass()
if child_class == 'Type':
self.idl_type = type_node_to_type(child)
elif child_class == 'Default':
self.default_value = default_node_to_idl_literal(child)
elif child_class == 'ExtAttributes':
self.extended_attributes = (
ext_attributes_node_to_extended_attributes(child))
else:
raise ValueError('Unrecognized node class: %s' % child_class)
def accept(self, visitor):
visitor.visit_dictionary_member(self)
################################################################################
# Enumerations
################################################################################
class IdlEnum(object):
def __init__(self, node):
self.name = node.GetName()
self.values = []
for child in node.GetChildren():
self.values.append(child.GetName())
def accept(self, visitor):
visitor.visit_enumeration(self)
################################################################################
# Typedefs
################################################################################
class IdlTypedef(object):
idl_type_attributes = ('idl_type',)
def __init__(self, node):
self.name = node.GetName()
self.idl_type = typedef_node_to_type(node)
def accept(self, visitor):
visitor.visit_typedef(self)
################################################################################
# Interfaces and Exceptions
################################################################################
class IdlInterface(object):
def __init__(self, node=None):
self.attributes = []
self.constants = []
self.constructors = []
self.custom_constructors = []
self.extended_attributes = {}
self.operations = []
self.parent = None
self.serializer = None
self.stringifier = None
self.iterable = None
self.has_indexed_elements = False
self.maplike = None
self.setlike = None
self.original_interface = None
self.partial_interfaces = []
if not node: # Early exit for IdlException.__init__
return
self.is_callback = bool(node.GetProperty('CALLBACK'))
self.is_exception = False
# FIXME: uppercase 'Partial' => 'PARTIAL' in base IDL parser
self.is_partial = bool(node.GetProperty('Partial'))
self.name = node.GetName()
self.idl_type = IdlType(self.name)
has_indexed_property_getter = False
has_integer_typed_length = False
children = node.GetChildren()
for child in children:
child_class = child.GetClass()
if child_class == 'Attribute':
attr = IdlAttribute(child)
if attr.idl_type.is_integer_type and attr.name == 'length':
has_integer_typed_length = True
self.attributes.append(attr)
elif child_class == 'Const':
self.constants.append(IdlConstant(child))
elif child_class == 'ExtAttributes':
extended_attributes = ext_attributes_node_to_extended_attributes(child)
self.constructors, self.custom_constructors = (
extended_attributes_to_constructors(extended_attributes))
clear_constructor_attributes(extended_attributes)
self.extended_attributes = extended_attributes
elif child_class == 'Operation':
op = IdlOperation(child)
if 'getter' in op.specials and str(op.arguments[0].idl_type) == 'unsigned long':
has_indexed_property_getter = True
self.operations.append(op)
elif child_class == 'Inherit':
self.parent = child.GetName()
elif child_class == 'Serializer':
self.serializer = IdlSerializer(child)
self.process_serializer()
elif child_class == 'Stringifier':
self.stringifier = IdlStringifier(child)
self.process_stringifier()
elif child_class == 'Iterable':
self.iterable = IdlIterable(child)
elif child_class == 'Maplike':
self.maplike = IdlMaplike(child)
elif child_class == 'Setlike':
self.setlike = IdlSetlike(child)
else:
raise ValueError('Unrecognized node class: %s' % child_class)
if len(list(filter(None, [self.iterable, self.maplike, self.setlike]))) > 1:
raise ValueError('Interface can only have one of iterable<>, maplike<> and setlike<>.')
if has_integer_typed_length and has_indexed_property_getter:
self.has_indexed_elements = True
else:
if self.iterable is not None and self.iterable.key_type is None:
raise ValueError('Value iterators (iterable<V>) must be accompanied by an indexed '
'property getter and an integer-typed length attribute.')
def accept(self, visitor):
visitor.visit_interface(self)
for attribute in self.attributes:
attribute.accept(visitor)
for constant in self.constants:
constant.accept(visitor)
for constructor in self.constructors:
constructor.accept(visitor)
for custom_constructor in self.custom_constructors:
custom_constructor.accept(visitor)
for operation in self.operations:
operation.accept(visitor)
if self.iterable:
self.iterable.accept(visitor)
elif self.maplike:
self.maplike.accept(visitor)
elif self.setlike:
self.setlike.accept(visitor)
def process_serializer(self):
"""Add the serializer's named operation child, if it has one, as a regular
operation of this interface."""
if self.serializer.operation:
self.operations.append(self.serializer.operation)
def process_stringifier(self):
"""Add the stringifier's attribute or named operation child, if it has
one, as a regular attribute/operation of this interface."""
if self.stringifier.attribute:
self.attributes.append(self.stringifier.attribute)
elif self.stringifier.operation:
self.operations.append(self.stringifier.operation)
def merge(self, other):
"""Merge in another interface's members (e.g., partial interface)"""
self.attributes.extend(other.attributes)
self.constants.extend(other.constants)
self.operations.extend(other.operations)
if self.serializer is None:
self.serializer = other.serializer
if self.stringifier is None:
self.stringifier = other.stringifier
class IdlException(IdlInterface):
# Properly exceptions and interfaces are distinct, and thus should inherit a
# common base class (say, "IdlExceptionOrInterface").
# However, there is only one exception (DOMException), and new exceptions
# are not expected. Thus it is easier to implement exceptions as a
# restricted subclass of interfaces.
# http://www.w3.org/TR/WebIDL/#idl-exceptions
def __init__(self, node):
# Exceptions are similar to Interfaces, but simpler
IdlInterface.__init__(self)
self.is_callback = False
self.is_exception = True
self.is_partial = False
self.name = node.GetName()
self.idl_type = IdlType(self.name)
children = node.GetChildren()
for child in children:
child_class = child.GetClass()
if child_class == 'Attribute':
attribute = IdlAttribute(child)
self.attributes.append(attribute)
elif child_class == 'Const':
self.constants.append(IdlConstant(child))
elif child_class == 'ExtAttributes':
extended_attributes = ext_attributes_node_to_extended_attributes(child)
self.constructors, self.custom_constructors = (
extended_attributes_to_constructors(extended_attributes))
clear_constructor_attributes(extended_attributes)
self.extended_attributes = extended_attributes
elif child_class == 'ExceptionOperation':
self.operations.append(IdlOperation.from_exception_operation_node(child))
else:
raise ValueError('Unrecognized node class: %s' % child_class)
################################################################################
# Attributes
################################################################################
class IdlAttribute(TypedObject):
def __init__(self, node=None):
self.is_read_only = bool(node.GetProperty('READONLY')) if node else False
self.is_static = bool(node.GetProperty('STATIC')) if node else False
self.name = node.GetName() if node else None
self.idl_type = None
self.extended_attributes = {}
if node:
children = node.GetChildren()
for child in children:
child_class = child.GetClass()
if child_class == 'Type':
self.idl_type = type_node_to_type(child)
elif child_class == 'ExtAttributes':
self.extended_attributes = ext_attributes_node_to_extended_attributes(child)
else:
raise ValueError('Unrecognized node class: %s' % child_class)
def accept(self, visitor):
visitor.visit_attribute(self)
def __lt__(self, other):
return id(self) < id(other)
################################################################################
# Constants
################################################################################
class IdlConstant(TypedObject):
def __init__(self, node):
children = node.GetChildren()
num_children = len(children)
if num_children < 2 or num_children > 3:
raise ValueError('Expected 2 or 3 children, got %s' % num_children)
type_node = children[0]
value_node = children[1]
value_node_class = value_node.GetClass()
if value_node_class != 'Value':
raise ValueError('Expected Value node, got %s' % value_node_class)
self.name = node.GetName()
# ConstType is more limited than Type, so subtree is smaller and
# we don't use the full type_node_to_type function.
self.idl_type = type_node_inner_to_type(type_node)
# FIXME: This code is unnecessarily complicated due to the rather
# inconsistent way the upstream IDL parser outputs default values.
# http://crbug.com/374178
if value_node.GetProperty('TYPE') == 'float':
self.value = value_node.GetProperty('VALUE')
else:
self.value = value_node.GetName()
if num_children == 3:
ext_attributes_node = children[2]
self.extended_attributes = ext_attributes_node_to_extended_attributes(ext_attributes_node)
else:
self.extended_attributes = {}
def accept(self, visitor):
visitor.visit_constant(self)
################################################################################
# Literals
################################################################################
class IdlLiteral(object):
def __init__(self, idl_type, value):
self.idl_type = idl_type
self.value = value
self.is_null = False
def __str__(self):
if self.idl_type == 'DOMString':
if self.value:
return '"%s"' % self.value
else:
return '""'
if self.idl_type == 'integer':
return '%d' % self.value
if self.idl_type == 'float':
return '%g' % self.value
if self.idl_type == 'boolean':
return 'true' if self.value else 'false'
raise ValueError('Unsupported literal type: %s' % self.idl_type)
class IdlLiteralNull(IdlLiteral):
def __init__(self):
self.idl_type = 'NULL'
self.value = None
self.is_null = True
def __str__(self):
return 'NULL'
def default_node_to_idl_literal(node):
# FIXME: This code is unnecessarily complicated due to the rather
# inconsistent way the upstream IDL parser outputs default values.
# http://crbug.com/374178
idl_type = node.GetProperty('TYPE')
if idl_type == 'DOMString':
value = node.GetProperty('NAME')
if '"' in value or '\\' in value:
raise ValueError('Unsupported string value: %r' % value)
return IdlLiteral(idl_type, value)
if idl_type == 'integer':
return IdlLiteral(idl_type, int(node.GetProperty('NAME'), base=0))
if idl_type == 'float':
return IdlLiteral(idl_type, float(node.GetProperty('VALUE')))
if idl_type in ['boolean', 'sequence']:
return IdlLiteral(idl_type, node.GetProperty('VALUE'))
if idl_type == 'NULL':
return IdlLiteralNull()
raise ValueError('Unrecognized default value type: %s' % idl_type)
################################################################################
# Operations
################################################################################
class IdlOperation(TypedObject):
def __init__(self, node=None):
self.arguments = []
self.extended_attributes = {}
self.specials = []
self.is_constructor = False
self.idl_type = None
self.is_static = False
if not node:
return
self.name = node.GetName() # FIXME: should just be: or ''
# FIXME: AST should use None internally
if self.name == '_unnamed_':
self.name = ''
self.is_static = bool(node.GetProperty('STATIC'))
property_dictionary = node.GetProperties()
for special_keyword in SPECIAL_KEYWORD_LIST:
if special_keyword in property_dictionary:
self.specials.append(special_keyword.lower())
children = node.GetChildren()
for child in children:
child_class = child.GetClass()
if child_class == 'Arguments':
self.arguments = arguments_node_to_arguments(child)
elif child_class == 'Type':
self.idl_type = type_node_to_type(child)
elif child_class == 'ExtAttributes':
self.extended_attributes = ext_attributes_node_to_extended_attributes(child)
else:
raise ValueError('Unrecognized node class: %s' % child_class)
@classmethod
def from_exception_operation_node(cls, node):
# Needed to handle one case in DOMException.idl:
# // Override in a Mozilla compatible format
# [NotEnumerable] DOMString toString();
# FIXME: can we remove this? replace with a stringifier?
operation = cls()
operation.name = node.GetName()
children = node.GetChildren()
if len(children) < 1 or len(children) > 2:
raise ValueError('ExceptionOperation node with %s children, expected 1 or 2' % len(children))
type_node = children[0]
operation.idl_type = type_node_to_type(type_node)
if len(children) > 1:
ext_attributes_node = children[1]
operation.extended_attributes = ext_attributes_node_to_extended_attributes(ext_attributes_node)
return operation
@classmethod
def constructor_from_arguments_node(cls, name, arguments_node):
constructor = cls()
constructor.name = name
constructor.arguments = arguments_node_to_arguments(arguments_node)
constructor.is_constructor = True
return constructor
def accept(self, visitor):
visitor.visit_operation(self)
for argument in self.arguments:
argument.accept(visitor)
################################################################################
# Arguments
################################################################################
class IdlArgument(TypedObject):
def __init__(self, node=None):
self.extended_attributes = {}
self.idl_type = None
self.is_optional = False # syntax: (optional T)
self.is_variadic = False # syntax: (T...)
self.default_value = None
if not node:
return
self.is_optional = node.GetProperty('OPTIONAL')
self.name = node.GetName()
children = node.GetChildren()
for child in children:
child_class = child.GetClass()
if child_class == 'Type':
self.idl_type = type_node_to_type(child)
elif child_class == 'ExtAttributes':
self.extended_attributes = ext_attributes_node_to_extended_attributes(child)
elif child_class == 'Argument':
child_name = child.GetName()
if child_name != '...':
raise ValueError('Unrecognized Argument node; expected "...", got "%s"' % child_name)
self.is_variadic = bool(child.GetProperty('ELLIPSIS'))
elif child_class == 'Default':
self.default_value = default_node_to_idl_literal(child)
else:
raise ValueError('Unrecognized node class: %s' % child_class)
def accept(self, visitor):
visitor.visit_argument(self)
def arguments_node_to_arguments(node):
# [Constructor] and [CustomConstructor] without arguments (the bare form)
# have None instead of an arguments node, but have the same meaning as using
# an empty argument list, [Constructor()], so special-case this.
# http://www.w3.org/TR/WebIDL/#Constructor
if node is None:
return []
return [IdlArgument(argument_node) for argument_node in node.GetChildren()]
################################################################################
# Serializers
################################################################################
class IdlSerializer(object):
def __init__(self, node):
self.attribute_name = node.GetProperty('ATTRIBUTE')
self.attribute_names = None
self.operation = None
self.extended_attributes = {}
self.is_attribute = False
self.is_getter = False
self.is_inherit = False
self.is_list = False
self.is_map = False
for child in node.GetChildren():
child_class = child.GetClass()
if child_class == 'Operation':
self.operation = IdlOperation(child)
elif child_class == 'List':
self.is_list = True
self.is_getter = bool(child.GetProperty('GETTER'))
self.attributes = child.GetProperty('ATTRIBUTES')
elif child_class == 'Map':
self.is_map = True
self.is_attribute = bool(child.GetProperty('ATTRIBUTE'))
self.is_getter = bool(child.GetProperty('GETTER'))
self.is_inherit = bool(child.GetProperty('INHERIT'))
self.attributes = child.GetProperty('ATTRIBUTES')
elif child_class == 'ExtAttributes':
self.extended_attributes = ext_attributes_node_to_extended_attributes(child)
else:
raise ValueError('Unrecognized node class: %s' % child_class)
################################################################################
# Stringifiers
################################################################################
class IdlStringifier(object):
def __init__(self, node):
self.attribute = None
self.operation = None
self.extended_attributes = {}
for child in node.GetChildren():
child_class = child.GetClass()
if child_class == 'Attribute':
self.attribute = IdlAttribute(child)
elif child_class == 'Operation':
operation = IdlOperation(child)
if operation.name:
self.operation = operation
elif child_class == 'ExtAttributes':
self.extended_attributes = ext_attributes_node_to_extended_attributes(child)
else:
raise ValueError('Unrecognized node class: %s' % child_class)
# Copy the stringifier's extended attributes (such as [Unforgable]) onto
# the underlying attribute or operation, if there is one.
if self.attribute or self.operation:
(self.attribute or self.operation).extended_attributes.update(
self.extended_attributes)
################################################################################
# Iterable, Maplike, Setlike
################################################################################
class IdlIterableOrMaplikeOrSetlike(TypedObject):
def __init__(self, node):
self.extended_attributes = {}
self.type_children = []
for child in node.GetChildren():
child_class = child.GetClass()
if child_class == 'ExtAttributes':
self.extended_attributes = ext_attributes_node_to_extended_attributes(child)
elif child_class == 'Type':
self.type_children.append(child)
else:
raise ValueError('Unrecognized node class: %s' % child_class)
class IdlIterable(IdlIterableOrMaplikeOrSetlike):
idl_type_attributes = ('key_type', 'value_type')
def __init__(self, node):
super(IdlIterable, self).__init__(node)
if len(self.type_children) == 1:
self.key_type = None
self.value_type = type_node_to_type(self.type_children[0])
elif len(self.type_children) == 2:
self.key_type = type_node_to_type(self.type_children[0])
self.value_type = type_node_to_type(self.type_children[1])
else:
raise ValueError('Unexpected number of type children: %d' % len(self.type_children))
del self.type_children
def accept(self, visitor):
visitor.visit_iterable(self)
class IdlMaplike(IdlIterableOrMaplikeOrSetlike):
idl_type_attributes = ('key_type', 'value_type')
def __init__(self, node):
super(IdlMaplike, self).__init__(node)
self.is_read_only = bool(node.GetProperty('READONLY'))
if len(self.type_children) == 2:
self.key_type = type_node_to_type(self.type_children[0])
self.value_type = type_node_to_type(self.type_children[1])
else:
raise ValueError('Unexpected number of children: %d' % len(self.type_children))
del self.type_children
def accept(self, visitor):
visitor.visit_maplike(self)
class IdlSetlike(IdlIterableOrMaplikeOrSetlike):
idl_type_attributes = ('value_type',)
def __init__(self, node):
super(IdlSetlike, self).__init__(node)
self.is_read_only = bool(node.GetProperty('READONLY'))
if len(self.type_children) == 1:
self.value_type = type_node_to_type(self.type_children[0])
else:
raise ValueError('Unexpected number of children: %d' % len(self.type_children))
del self.type_children
def accept(self, visitor):
visitor.visit_setlike(self)
################################################################################
# Implement statements
################################################################################
class IdlImplement(object):
def __init__(self, node):
self.left_interface = node.GetName()
self.right_interface = node.GetProperty('REFERENCE')
def accept(self, visitor):
visitor.visit_implement(self)
################################################################################
# Extended attributes
################################################################################
class Exposure:
"""An Exposure holds one Exposed or RuntimeEnabled condition.
Each exposure has two properties: exposed and runtime_enabled.
Exposure(e, r) corresponds to [Exposed(e r)]. Exposure(e) corresponds to
[Exposed=e].
"""
def __init__(self, exposed, runtime_enabled=None):
self.exposed = exposed
self.runtime_enabled = runtime_enabled
def ext_attributes_node_to_extended_attributes(node):
"""
Returns:
Dictionary of {ExtAttributeName: ExtAttributeValue}.
Value is usually a string, with these exceptions:
Constructors: value is a list of Arguments nodes, corresponding to
possible signatures of the constructor.
CustomConstructors: value is a list of Arguments nodes, corresponding to
possible signatures of the custom constructor.
NamedConstructor: value is a Call node, corresponding to the single
signature of the named constructor.
SetWrapperReferenceTo: value is an Arguments node.
"""
# Primarily just make a dictionary from the children.
# The only complexity is handling various types of constructors:
# Constructors and Custom Constructors can have duplicate entries due to
# overloading, and thus are stored in temporary lists.
# However, Named Constructors cannot be overloaded, and thus do not have
# a list.
# FIXME: move Constructor logic into separate function, instead of modifying
# extended attributes in-place.
constructors = []
custom_constructors = []
extended_attributes = {}
def child_node(extended_attribute_node):
children = extended_attribute_node.GetChildren()
if not children:
return None
if len(children) > 1:
raise ValueError('ExtAttributes node with %s children, expected at most 1' % len(children))
return children[0]
extended_attribute_node_list = node.GetChildren()
for extended_attribute_node in extended_attribute_node_list:
name = extended_attribute_node.GetName()
child = child_node(extended_attribute_node)
child_class = child and child.GetClass()
if name == 'Constructor':
if child_class and child_class != 'Arguments':
raise ValueError('Constructor only supports Arguments as child, but has child of class: %s' % child_class)
constructors.append(child)
elif name == 'CustomConstructor':
if child_class and child_class != 'Arguments':
raise ValueError('[CustomConstructor] only supports Arguments as child, but has child of class: %s' % child_class)
custom_constructors.append(child)
elif name == 'NamedConstructor':
if child_class and child_class != 'Call':
raise ValueError('[NamedConstructor] only supports Call as child, but has child of class: %s' % child_class)
extended_attributes[name] = child
elif name == 'SetWrapperReferenceTo':
if not child:
raise ValueError('[SetWrapperReferenceTo] requires a child, but has none.')
children = child.GetChildren()
if len(children) != 1:
raise ValueError('[SetWrapperReferenceTo] supports only one child.')
if child_class != 'Arguments':
raise ValueError('[SetWrapperReferenceTo] only supports Arguments as child, but has child of class: %s' % child_class)
extended_attributes[name] = IdlArgument(children[0])
elif name == 'Exposed':
if child_class and child_class != 'Arguments':
raise ValueError('[Exposed] only supports Arguments as child, but has child of class: %s' % child_class)
exposures = []
if child_class == 'Arguments':
exposures = [Exposure(exposed=str(arg.idl_type),
runtime_enabled=arg.name)
for arg in arguments_node_to_arguments(child)]
else:
value = extended_attribute_node.GetProperty('VALUE')
if type(value) is str:
exposures = [Exposure(exposed=value)]
else:
exposures = [Exposure(exposed=v) for v in value]
extended_attributes[name] = exposures
elif child:
raise ValueError('ExtAttributes node with unexpected children: %s' % name)
else:
value = extended_attribute_node.GetProperty('VALUE')
extended_attributes[name] = value
# Store constructors and custom constructors in special list attributes,
# which are deleted later. Note plural in key.
if constructors:
extended_attributes['Constructors'] = constructors
if custom_constructors:
extended_attributes['CustomConstructors'] = custom_constructors
return extended_attributes
def extended_attributes_to_constructors(extended_attributes):
"""Returns constructors and custom_constructors (lists of IdlOperations).
Auxiliary function for IdlInterface.__init__.
"""
constructor_list = extended_attributes.get('Constructors', [])
constructors = [
IdlOperation.constructor_from_arguments_node('Constructor', arguments_node)
for arguments_node in constructor_list]
custom_constructor_list = extended_attributes.get('CustomConstructors', [])
custom_constructors = [
IdlOperation.constructor_from_arguments_node('CustomConstructor', arguments_node)
for arguments_node in custom_constructor_list]
if 'NamedConstructor' in extended_attributes:
# FIXME: support overloaded named constructors, and make homogeneous
name = 'NamedConstructor'
call_node = extended_attributes['NamedConstructor']
extended_attributes['NamedConstructor'] = call_node.GetName()
children = call_node.GetChildren()
if len(children) != 1:
raise ValueError('NamedConstructor node expects 1 child, got %s.' % len(children))
arguments_node = children[0]
named_constructor = IdlOperation.constructor_from_arguments_node('NamedConstructor', arguments_node)
# FIXME: should return named_constructor separately; appended for Perl
constructors.append(named_constructor)
return constructors, custom_constructors
def clear_constructor_attributes(extended_attributes):
# Deletes Constructor*s* (plural), sets Constructor (singular)
if 'Constructors' in extended_attributes:
del extended_attributes['Constructors']
extended_attributes['Constructor'] = None
if 'CustomConstructors' in extended_attributes:
del extended_attributes['CustomConstructors']
extended_attributes['CustomConstructor'] = None
################################################################################
# Types
################################################################################
def type_node_to_type(node):
children = node.GetChildren()
if len(children) < 1 or len(children) > 2:
raise ValueError('Type node expects 1 or 2 children (type + optional array []), got %s (multi-dimensional arrays are not supported).' % len(children))
base_type = type_node_inner_to_type(children[0])
if node.GetProperty('NULLABLE'):
base_type = IdlNullableType(base_type)
if len(children) == 2:
array_node = children[1]
array_node_class = array_node.GetClass()
if array_node_class != 'Array':
raise ValueError('Expected Array node as TypeSuffix, got %s node.' % array_node_class)
array_type = IdlArrayType(base_type)
if array_node.GetProperty('NULLABLE'):
return IdlNullableType(array_type)
return array_type
return base_type
def type_node_inner_to_type(node):
node_class = node.GetClass()
# Note Type*r*ef, not Typedef, meaning the type is an identifier, thus
# either a typedef shorthand (but not a Typedef declaration itself) or an
# interface type. We do not distinguish these, and just use the type name.
if node_class in ['PrimitiveType', 'StringType', 'Typeref']:
# unrestricted syntax: unrestricted double | unrestricted float
is_unrestricted = bool(node.GetProperty('UNRESTRICTED'))
return IdlType(node.GetName(), is_unrestricted=is_unrestricted)
elif node_class == 'Any':
return IdlType('any')
elif node_class in ['Sequence', 'FrozenArray']:
return sequence_node_to_type(node)
elif node_class == 'UnionType':
return union_type_node_to_idl_union_type(node)
elif node_class == 'Promise':
return promise_node_to_type(node)
elif node_class == 'Record':
return record_node_to_type(node)
raise ValueError('Unrecognized node class: %s' % node_class)
def record_node_to_type(node):
children = node.GetChildren()
if len(children) != 2:
raise ValueError('record<K,V> node expects exactly 2 children, got %d' % (len(children)))
key_child = children[0]
value_child = children[1]
if key_child.GetClass() != 'StringType':
raise ValueError('Keys in record<K,V> nodes must be string types.')
if value_child.GetClass() != 'Type':
raise ValueError('Unrecognized node class for record<K,V> value: %s' % value_child.GetClass())
return IdlRecordType(IdlType(key_child.GetName()), type_node_to_type(value_child))
def sequence_node_to_type(node):
children = node.GetChildren()
class_name = node.GetClass()
if len(children) != 1:
raise ValueError('%s node expects exactly 1 child, got %s' % (class_name, len(children)))
sequence_child = children[0]
sequence_child_class = sequence_child.GetClass()
if sequence_child_class != 'Type':
raise ValueError('Unrecognized node class: %s' % sequence_child_class)
element_type = type_node_to_type(sequence_child)
if class_name == 'Sequence':
sequence_type = IdlSequenceType(element_type)
elif class_name == 'FrozenArray':
sequence_type = IdlFrozenArrayType(element_type)
else:
raise ValueError('Unexpected node: %s' % class_name)
if node.GetProperty('NULLABLE'):
return IdlNullableType(sequence_type)
return sequence_type
def typedef_node_to_type(node):
children = node.GetChildren()
if len(children) != 1:
raise ValueError('Typedef node with %s children, expected 1' % len(children))
child = children[0]
child_class = child.GetClass()
if child_class != 'Type':
raise ValueError('Unrecognized node class: %s' % child_class)
return type_node_to_type(child)
def union_type_node_to_idl_union_type(node):
member_types = [type_node_to_type(member_type_node)
for member_type_node in node.GetChildren()]
return IdlUnionType(member_types)
def promise_node_to_type(node):
children = node.GetChildren()
class_name = node.GetClass()
if len(children) != 1:
raise ValueError('%s node expects exactly 1 child, got %s' % (class_name, len(children)))
promise_child = children[0]
promise_child_class = promise_child.GetClass()
if promise_child_class != 'Type':
raise ValueError('Unrecognized node class: %s' % sequence_child_class)
element_type = type_node_to_type(promise_child)
if class_name == 'Promise':
promise_type = IdlPromiseType(element_type)
else:
raise ValueError('Unexpected node: %s' % class_name)
return promise_type
################################################################################
# Visitor
################################################################################
class Visitor(object):
"""Abstract visitor class for IDL definitions traverse."""
def visit_definitions(self, definitions):
pass
def visit_typed_object(self, typed_object):
pass
def visit_callback_function(self, callback_function):
self.visit_typed_object(callback_function)
def visit_dictionary(self, dictionary):
pass
def visit_dictionary_member(self, member):
self.visit_typed_object(member)
def visit_enumeration(self, enumeration):
pass
def visit_implement(self, implement):
pass
def visit_interface(self, interface):
pass
def visit_typedef(self, typedef):
self.visit_typed_object(typedef)
def visit_attribute(self, attribute):
self.visit_typed_object(attribute)
def visit_constant(self, constant):
self.visit_typed_object(constant)
def visit_operation(self, operation):
self.visit_typed_object(operation)
def visit_argument(self, argument):
self.visit_typed_object(argument)
def visit_iterable(self, iterable):
self.visit_typed_object(iterable)
def visit_maplike(self, maplike):
self.visit_typed_object(maplike)
def visit_setlike(self, setlike):
self.visit_typed_object(setlike)