Index: Source/bindings/scripts/idl_definitions.py |
diff --git a/Source/bindings/scripts/idl_definitions.py b/Source/bindings/scripts/idl_definitions.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..84fe36ce739f95b8cdb802ca8fa16c82ad75d291 |
--- /dev/null |
+++ b/Source/bindings/scripts/idl_definitions.py |
@@ -0,0 +1,452 @@ |
+# 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. |
+ |
+"""Blink IDL Intermediate Representation (IR) classes. |
+ |
+Also JSON export, using legacy Perl terms and format, to ensure that both |
+parsers produce the same output. |
+FIXME: remove BaseIdl, JSON export (json_serializable and to_json), and Perl |
+compatibility functions and hacks once Perl compiler gone. |
+""" |
+ |
+# Disable attribute hiding check (else JSONEncoder default raises an error) |
+# pylint: disable=E0202 |
+# pylint doesn't understand ABCs. |
+# pylint: disable=W0232, E0203, W0201 |
+ |
+import abc |
+import json |
+import os.path |
+import re |
+ |
+ |
+# Base classes |
+ |
+ |
+class BaseIdl: |
+ """Abstract base class, used for JSON serialization.""" |
+ __metaclass__ = abc.ABCMeta |
+ |
+ @abc.abstractmethod |
+ def json_serializable(self): |
+ """Returns a JSON serializable form of the object. |
+ |
+ This should be a dictionary, with keys scoped names of the form |
+ Class::key, where the scope is the class name. |
+ This is so we produce identical output to the Perl code, which uses |
+ the Perl module JSON.pm, which uses this format. |
+ """ |
+ pass |
+ |
+ |
+class TypedObject: |
+ """Object with a Type, such as an Attribute or Operation (return value).""" |
haraken
2013/07/22 01:50:23
As mentioned in the previous comment, let's add "t
Nils Barth (inactive)
2013/07/22 06:32:01
Done.
|
+ __metaclass__ = abc.ABCMeta |
+ data_type = None |
haraken
2013/07/22 01:50:23
Shall we add "extended_attributes = None" ?
Nils Barth (inactive)
2013/07/22 06:32:01
You’re right, that’s better. Done.
|
+ extended_attributes = {} |
+ |
+ def apply_typedefs(self, typedefs): |
+ """Applies Typedefs to object itself (e.g., return type of function). |
haraken
2013/07/22 01:50:23
function => operation
Nils Barth (inactive)
2013/07/22 06:32:01
(>.<) Done. (Fixed throughout.)
|
+ |
+ Override for instance if calling on arguments of function.""" |
haraken
2013/07/22 01:50:23
Nit: I'd remove this comment.
Nils Barth (inactive)
2013/07/22 06:32:01
Done.
|
+ new_extended_attributes = {} |
+ # Convert string representation to and from an IdlType object |
+ # to handle parsing |
+ data_type_object = IdlType.from_string(self.data_type) |
+ base_type = data_type_object.base_type |
+ if base_type in typedefs: |
+ replacement_type = typedefs[base_type] |
+ data_type_object.base_type = replacement_type.data_type |
+ new_extended_attributes = replacement_type.extended_attributes |
haraken
2013/07/22 01:50:23
Nit: new_extended_attributes => additional_extende
Nils Barth (inactive)
2013/07/22 06:32:01
Good point, that’s clearer. Done.
|
+ self.data_type = str(data_type_object) |
+ self.extended_attributes.update(new_extended_attributes) |
+ |
+ |
+# IDL classes |
+ |
+ |
+class IdlDefinitions(BaseIdl): |
+ def __init__(self, callback_functions=None, enumerations=None, exceptions=None, file_name=None, interfaces=None, typedefs=None): |
+ self.callback_functions = callback_functions or {} |
+ self.enumerations = enumerations or {} |
+ self.exceptions = exceptions or {} |
+ if file_name: |
+ self.file_name = os.path.abspath(file_name) |
+ self.interfaces = interfaces or {} |
+ # Typedefs are not exposed by bindings; apply to the parsed definitions, |
+ # replacing typedefs with the actual type. |
+ # http://www.w3.org/TR/WebIDL/#idl-typedefs |
+ if typedefs: |
+ self.apply_typedefs(typedefs) |
+ |
+ def apply_typedefs(self, typedefs): |
+ for callback_function in self.callback_functions.itervalues(): |
+ callback_function.apply_typedefs(typedefs) |
+ for exception in self.exceptions.itervalues(): |
+ exception.apply_typedefs(typedefs) |
+ for interface in self.interfaces.itervalues(): |
+ interface.apply_typedefs(typedefs) |
+ |
+ def json_serializable(self): |
+ return { |
+ 'idlDocument::callbackFunctions': self.callback_functions.values(), |
+ 'idlDocument::enumerations': self.enumerations.values(), |
+ 'idlDocument::fileName': self.file_name, |
+ # Perl treats exceptions as a kind of interface |
+ 'idlDocument::interfaces': sorted(self.exceptions.values() + self.interfaces.values()), |
+ } |
+ |
+ def to_json(self, debug=False): |
+ """Returns a JSON string representing the Definitions. |
+ |
+ The JSON output should be identical with the output of the Perl parser, |
+ specifically the function serializeJSON in deprecated_idl_serializer.pm, |
+ which takes a Perl object created by deprecated_idl_parser.pm. |
+ """ |
+ # Sort so order consistent, allowing comparison of output |
+ if debug: |
+ # indent turns on pretty-printing for legibility |
+ return json.dumps(self, cls=IdlEncoder, sort_keys=True, indent=4) |
+ # Use compact separators so output identical to Perl |
+ return json.dumps(self, cls=IdlEncoder, sort_keys=True, separators=(',', ':')) |
+ |
+ |
+class IdlCallbackFunction(BaseIdl, TypedObject): |
+ def __init__(self, name=None, data_type=None, arguments=None): |
+ self.data_type = data_type |
+ self.name = name |
+ self.arguments = arguments or [] |
+ |
+ def apply_typedefs(self, typedefs): |
+ TypedObject.apply_typedefs(self, typedefs) |
+ for argument in self.arguments: |
+ argument.apply_typedefs(typedefs) |
+ raise ValueError('Typedefs in callback functions are untested!') |
+ |
+ def json_serializable(self): |
+ return { |
+ 'callbackFunction::name': self.name, |
+ 'callbackFunction::type': self.data_type, |
+ 'callbackFunction::parameters': self.arguments, |
+ } |
+ |
+ |
+class IdlEnum(BaseIdl): |
+ def __init__(self, name=None, values=None): |
+ self.name = name |
+ self.values = values or [] |
+ |
+ def json_serializable(self): |
+ return { |
+ 'domEnum::name': self.name, |
+ 'domEnum::values': self.values, |
+ } |
+ |
+ |
+class IdlInterface(BaseIdl): |
+ def __init__(self, attributes=None, constants=None, constructors=None, custom_constructors=None, extended_attributes=None, functions=None, is_callback=False, is_partial=False, name=None, parent=None): |
+ self.attributes = attributes or [] |
+ self.constants = constants or [] |
+ self.constructors = constructors or [] |
+ self.custom_constructors = custom_constructors or [] |
+ self.extended_attributes = extended_attributes or {} |
+ self.functions = functions or [] |
+ self.is_callback = is_callback |
+ self.is_partial = is_partial |
+ self.name = name |
+ self.parent = parent |
+ |
+ def apply_typedefs(self, typedefs): |
+ for attribute in self.attributes: |
+ attribute.apply_typedefs(typedefs) |
+ for constant in self.constants: |
+ constant.apply_typedefs(typedefs) |
+ for constructor in self.constructors: |
+ constructor.apply_typedefs(typedefs) |
+ for custom_constructor in self.custom_constructors: |
+ custom_constructor.apply_typedefs(typedefs) |
+ for function in self.functions: |
+ function.apply_typedefs(typedefs) |
+ |
+ def json_serializable(self): |
+ return { |
+ 'domInterface::attributes': self.attributes, |
+ 'domInterface::constants': self.constants, |
+ 'domInterface::constructors': self.constructors, |
+ 'domInterface::customConstructors': self.custom_constructors, |
+ 'domInterface::extendedAttributes': none_to_value_is_missing(self.extended_attributes), |
+ 'domInterface::functions': self.functions, |
+ 'domInterface::isException': None, |
+ 'domInterface::isCallback': boolean_to_perl(false_to_none(self.is_callback)), |
+ 'domInterface::isPartial': false_to_none(self.is_partial), |
+ 'domInterface::name': self.name, |
+ 'domInterface::parent': self.parent, |
+ } |
+ |
+ |
+class IdlException(BaseIdl): |
+ def __init__(self, name=None, constants=None, functions=None, attributes=None, extended_attributes=None): |
+ self.attributes = attributes or [] |
+ self.constants = constants or [] |
+ self.extended_attributes = extended_attributes or {} |
+ self.functions = functions or [] |
+ self.name = name |
+ |
+ def apply_typedefs(self, typedefs): |
+ for constant in self.constants: |
+ constant.apply_typedefs(typedefs) |
+ for attribute in self.attributes: |
+ attribute.apply_typedefs(typedefs) |
+ for function in self.functions: |
+ function.apply_typedefs(typedefs) |
+ |
+ def json_serializable(self): |
+ return { |
+ # Perl code treats Exceptions as a kind of Interface |
+ 'domInterface::name': self.name, |
+ 'domInterface::attributes': self.attributes, |
+ 'domInterface::constants': self.constants, |
+ 'domInterface::extendedAttributes': none_to_value_is_missing(self.extended_attributes), |
+ 'domInterface::functions': self.functions, |
+ # These values don't vary for exceptions |
+ 'domInterface::constructors': [], |
+ 'domInterface::customConstructors': [], |
+ 'domInterface::isException': 1, |
+ 'domInterface::isCallback': None, |
+ 'domInterface::isPartial': None, |
+ 'domInterface::parent': None, |
+ } |
+ |
+ |
+class IdlAttribute(BaseIdl, TypedObject): |
+ def __init__(self, data_type=None, extended_attributes=None, getter_exceptions=None, is_nullable=False, is_static=False, is_read_only=False, name=None, setter_exceptions=None): |
+ self.data_type = data_type |
+ self.extended_attributes = extended_attributes or {} |
+ self.getter_exceptions = getter_exceptions or [] |
+ self.is_nullable = is_nullable |
+ self.is_static = is_static |
+ self.is_read_only = is_read_only |
+ self.name = name |
+ self.setter_exceptions = setter_exceptions or [] |
+ |
+ def json_serializable(self): |
+ return { |
+ 'domAttribute::extendedAttributes': none_to_value_is_missing(self.extended_attributes), |
+ 'domAttribute::getterExceptions': self.getter_exceptions, |
+ 'domAttribute::isNullable': boolean_to_perl_quoted(false_to_none(self.is_nullable)), |
+ 'domAttribute::isReadOnly': boolean_to_perl(false_to_none(self.is_read_only)), |
+ 'domAttribute::isStatic': boolean_to_perl(false_to_none(self.is_static)), |
+ 'domAttribute::name': self.name, |
+ 'domAttribute::setterExceptions': self.setter_exceptions, |
+ 'domAttribute::type': self.data_type, |
+ } |
+ |
+ |
+class IdlConstant(BaseIdl, TypedObject): |
+ def __init__(self, name=None, data_type=None, value=None, extended_attributes=None): |
+ self.data_type = data_type |
+ self.extended_attributes = extended_attributes or {} |
+ self.name = name |
+ self.value = value |
+ |
+ def json_serializable(self): |
+ return { |
+ 'domConstant::extendedAttributes': none_to_value_is_missing(self.extended_attributes), |
+ 'domConstant::name': self.name, |
+ 'domConstant::type': self.data_type, |
+ 'domConstant::value': self.value, |
+ } |
+ |
+ |
+class IdlOperation(BaseIdl, TypedObject): |
+ def __init__(self, is_static=False, name=None, data_type=None, extended_attributes=None, specials=None, arguments=None, overloaded_index=None): |
+ self.is_static = is_static |
+ self.name = name or '' |
+ self.data_type = data_type |
+ self.extended_attributes = extended_attributes or {} |
+ self.specials = specials or [] |
+ self.arguments = arguments or [] |
+ self.overloaded_index = overloaded_index |
+ |
+ def apply_typedefs(self, typedefs): |
+ TypedObject.apply_typedefs(self, typedefs) |
+ for argument in self.arguments: |
+ argument.apply_typedefs(typedefs) |
+ |
+ def json_serializable(self): |
+ return { |
+ 'domFunction::extendedAttributes': none_to_value_is_missing(self.extended_attributes), |
+ 'domFunction::isStatic': boolean_to_perl(false_to_none(self.is_static)), |
+ 'domFunction::name': self.name, |
+ 'domFunction::overloadedIndex': self.overloaded_index, |
+ 'domFunction::parameters': self.arguments, |
+ 'domFunction::specials': self.specials, |
+ 'domFunction::type': self.data_type, |
+ } |
+ |
+ |
+class IdlArgument(BaseIdl, TypedObject): |
+ def __init__(self, name=None, data_type=None, extended_attributes=None, is_optional=False, is_nullable=None, is_variadic=False): |
+ self.data_type = data_type |
+ self.extended_attributes = extended_attributes or {} |
+ # FIXME: boolean values are inconsistent. |
+ # The below hack is so that generated JSON is identical to |
+ # Perl-generated JSON, where the exact values depend on the code path. |
+ # False and None (Perl: 0 and undef) are semantically interchangeable, |
+ # but yield different JSON. |
+ # Once Perl removed, have all default to False. |
+ if is_optional is None: |
+ is_optional = False |
+ if is_variadic is None: |
+ is_variadic = False |
+ self.is_nullable = is_nullable # (T?) |
+ self.is_optional = is_optional # (optional T) |
+ self.is_variadic = is_variadic # (T...) |
+ self.name = name |
+ |
+ def json_serializable(self): |
+ return { |
+ 'domParameter::extendedAttributes': none_to_value_is_missing(self.extended_attributes), |
+ 'domParameter::isNullable': boolean_to_perl_quoted(self.is_nullable), |
+ 'domParameter::isOptional': boolean_to_perl(self.is_optional), |
+ 'domParameter::isVariadic': boolean_to_perl(self.is_variadic), |
+ 'domParameter::name': self.name, |
+ 'domParameter::type': self.data_type, |
+ } |
+ |
+# Type classes |
+ |
+ |
+class IdlType: |
+ # FIXME: replace Type strings with these objects, |
+ # so don't need to parse everywhere types are used. |
+ # Types are stored internally as strings, not objects, |
+ # e.g., as 'sequence<Foo>' or 'Foo[]', |
+ # hence need to parse the string whenever a type is used. |
+ # FIXME: incorporate Nullable, Variadic, etc. |
+ # FIXME: properly should nest types |
+ # Formally types are nested, e.g., short?[] vs. short[]?, |
+ # but in practice these complex types aren't used and can treat |
+ # as orthogonal properties. |
+ def __init__(self, base_type, is_array=False, is_sequence=False): |
+ if is_array and is_sequence: |
+ raise ValueError('Array of Sequences are not allowed.') |
+ self.base_type = base_type |
+ self.is_array = is_array |
+ self.is_sequence = is_sequence |
+ |
+ def __str__(self): |
+ type_string = self.base_type |
+ if self.is_array: |
+ return type_string + '[]' |
+ if self.is_sequence: |
+ return 'sequence<%s>' % type_string |
+ return type_string |
+ |
+ @classmethod |
+ def from_string(cls, type_string): |
+ sequence_re = r'^sequence<([^>]*)>$' |
+ if type_string.endswith('[]'): |
+ type_string = type_string[:-2] |
+ sequence_match = re.match(sequence_re, type_string) |
+ if sequence_match: |
+ raise ValueError('Array of Sequences are not allowed.') |
+ return cls(type_string, is_array=True) |
+ sequence_match = re.match(sequence_re, type_string) |
+ if sequence_match: |
+ base_type = sequence_match.group(1) |
+ return cls(base_type, is_sequence=True) |
+ return cls(type_string) |
+ |
+ |
+class IdlTypedef: |
+ # Internal to IDL parsing: typedefs are all translated during IdlObject |
+ # construction, and the typedefs themselves not stored in the object. |
+ def __init__(self, extended_attributes=None, data_type=None): |
+ self.extended_attributes = extended_attributes or {} |
+ self.data_type = data_type |
+ |
+ |
+class IdlUnionType(BaseIdl): |
+ def __init__(self, union_member_types=None): |
+ self.union_member_types = union_member_types or [] |
+ |
+ def json_serializable(self): |
+ return { |
+ 'UnionType::unionMemberTypes': self.union_member_types, |
+ } |
+ |
+ |
+# Perl JSON compatibility functions |
+ |
+def none_to_value_is_missing(extended_attributes): |
+ # Perl IDL Parser uses 'VALUE_IS_MISSING' for null values in |
+ # extended attributes, so add this as a filter when exporting to JSON. |
+ new_extended_attributes = {} |
+ for key, value in extended_attributes.iteritems(): |
+ if value is None: |
+ new_extended_attributes[key] = 'VALUE_IS_MISSING' |
+ else: |
+ new_extended_attributes[key] = value |
+ return new_extended_attributes |
+ |
+ |
+def boolean_to_perl(value): |
+ # Perl stores booleans as 1, 0, or undefined (JSON null); |
+ # convert to this format. |
+ if value is None: |
+ return None |
+ return int(value) |
+ |
+ |
+def boolean_to_perl_quoted(value): |
+ # Bug-for-bug compatibility with Perl. |
+ # The value of isNullable is quoted ('1', '0', or undefined), rather than |
+ # an integer, so add quotes. |
+ if value is None: |
+ return None |
+ return str(int(value)) |
+ |
+ |
+def false_to_none(value): |
+ # The Perl parser generally uses undefined (Python None) rather than False |
+ # for boolean flags, because the value is simply left undefined, rather than |
+ # explicitly set to False. |
+ if value is False: |
+ return None |
+ return value |
+ |
+ |
+# JSON export |
+ |
+ |
+class IdlEncoder(json.JSONEncoder): |
+ def default(self, obj): |
+ if isinstance(obj, BaseIdl): |
+ return obj.json_serializable() |
+ return json.JSONEncoder.default(self, obj) |