| #!/usr/bin/env python |
| # |
| # Copyright (c) 2014 Apple Inc. All rights reserved. |
| # Copyright (c) 2014 University of Washington. All rights reserved. |
| # |
| # Redistribution and use in source and binary forms, with or without |
| # modification, are permitted provided that the following conditions |
| # are met: |
| # 1. Redistributions of source code must retain the above copyright |
| # notice, this list of conditions and the following disclaimer. |
| # 2. 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. |
| # |
| # THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS 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 APPLE INC. OR ITS 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. |
| |
| import logging |
| import collections |
| |
| log = logging.getLogger('global') |
| |
| |
| def ucfirst(str): |
| return str[:1].upper() + str[1:] |
| |
| |
| def find_duplicates(l): |
| return [key for key, count in list(collections.Counter(l).items()) if count > 1] |
| |
| |
| def validate_target_types(debuggable_types, target_types): |
| for target_type in target_types: |
| required_debuggable_types = set() |
| if target_type == 'javascript': |
| if not 'javascript' in debuggable_types: |
| return False |
| elif target_type == 'page' or target_type == 'worker': |
| if not 'page' in debuggable_types: |
| return False |
| elif target_type == 'service-worker': |
| if not 'service-worker' in debuggable_types: |
| return False |
| elif target_type == 'web-page': |
| if not 'web-page' in debuggable_types: |
| return False |
| return True |
| |
| |
| _FRAMEWORK_CONFIG_MAP = { |
| "Global": { |
| }, |
| "JavaScriptCore": { |
| "cpp_protocol_group": "Inspector", |
| "export_macro": "JS_EXPORT_PRIVATE", |
| "alternate_dispatchers": True, |
| }, |
| "WebKit": { |
| "cpp_protocol_group": "Automation", |
| "objc_protocol_group": "WD", |
| "objc_prefix": "WD", |
| }, |
| "WebInspector": { |
| "objc_protocol_group": "RWI", |
| "objc_prefix": "RWI", |
| }, |
| "WebInspectorUI": { |
| }, |
| # Used for code generator tests. |
| "Test": { |
| "alternate_dispatchers": True, |
| "cpp_protocol_group": "Test", |
| "objc_protocol_group": "Test", |
| "objc_prefix": "Test", |
| } |
| } |
| |
| _ALLOWED_DEBUGGABLE_TYPE_STRINGS = frozenset(['javascript', 'page', 'service-worker', 'web-page']) |
| _ALLOWED_TARGET_TYPE_STRINGS = frozenset(['javascript', 'page', 'service-worker', 'web-page', 'worker']) |
| |
| |
| class ParseException(Exception): |
| pass |
| |
| |
| class TypecheckException(Exception): |
| pass |
| |
| |
| class Framework: |
| def __init__(self, name): |
| self._settings = _FRAMEWORK_CONFIG_MAP[name] |
| self.name = name |
| |
| def setting(self, key, default=''): |
| return self._settings.get(key, default) |
| |
| @staticmethod |
| def fromString(frameworkString): |
| if frameworkString == "Global": |
| return Frameworks.Global |
| |
| if frameworkString == "JavaScriptCore": |
| return Frameworks.JavaScriptCore |
| |
| if frameworkString == "WebKit": |
| return Frameworks.WebKit |
| |
| if frameworkString == "WebInspector": |
| return Frameworks.WebInspector |
| |
| if frameworkString == "WebInspectorUI": |
| return Frameworks.WebInspectorUI |
| |
| if frameworkString == "Test": |
| return Frameworks.Test |
| |
| raise ParseException("Unknown framework: %s" % frameworkString) |
| |
| |
| class Frameworks: |
| Global = Framework("Global") |
| JavaScriptCore = Framework("JavaScriptCore") |
| WebKit = Framework("WebKit") |
| WebInspector = Framework("WebInspector") |
| WebInspectorUI = Framework("WebInspectorUI") |
| Test = Framework("Test") |
| |
| |
| class Platform: |
| def __init__(self, name): |
| self.name = name |
| |
| @staticmethod |
| def fromString(platformString): |
| platformString = platformString.lower() |
| if platformString == "ios": |
| return Platforms.iOS |
| |
| if platformString == "macos": |
| return Platforms.macOS |
| |
| if platformString == "all": |
| return Platforms.All |
| |
| if platformString == "generic" or not platformString: |
| return Platforms.Generic |
| |
| raise ParseException("Unknown platform: %s" % platformString) |
| |
| |
| class Platforms: |
| All = Platform("all") |
| Generic = Platform("generic") |
| iOS = Platform("ios") |
| macOS = Platform("macos") |
| |
| # Allow iteration over all platforms. See <http://stackoverflow.com/questions/5434400/>. |
| class __metaclass__(type): |
| def __iter__(self): |
| for attr in dir(Platforms): |
| if not attr.startswith("__"): |
| yield getattr(Platforms, attr) |
| |
| class TypeReference: |
| def __init__(self, type_kind, referenced_type_name, enum_values, array_items): |
| self.type_kind = type_kind |
| self.referenced_type_name = referenced_type_name |
| self.enum_values = enum_values |
| if array_items is None: |
| self.array_type_ref = None |
| else: |
| self.array_type_ref = TypeReference(array_items.get('type'), array_items.get('$ref'), array_items.get('enum'), array_items.get('items')) |
| |
| if type_kind is not None and referenced_type_name is not None: |
| raise ParseException("Type reference cannot have both 'type' and '$ref' keys.") |
| |
| all_primitive_types = ["integer", "number", "string", "boolean", "enum", "object", "array", "any"] |
| if type_kind is not None and type_kind not in all_primitive_types: |
| raise ParseException("Type reference '%s' is not a primitive type. Allowed values: %s" % (type_kind, ', '.join(all_primitive_types))) |
| |
| if type_kind == "array" and array_items is None: |
| raise ParseException("Type reference with type 'array' must have key 'items' to define array element type.") |
| |
| if enum_values is not None and len(enum_values) == 0: |
| raise ParseException("Type reference with enum values must have at least one enum value.") |
| |
| def referenced_name(self): |
| if self.referenced_type_name is not None: |
| return self.referenced_type_name |
| else: |
| return self.type_kind # one of all_primitive_types |
| |
| |
| class Type: |
| def __init__(self): |
| pass |
| |
| def __eq__(self, other): |
| return self.qualified_name() == other.qualified_name() |
| |
| def __hash__(self): |
| return self.qualified_name().__hash__() |
| |
| def raw_name(self): |
| return self._name |
| |
| # These methods should be overridden by subclasses. |
| def is_enum(self): |
| return False |
| |
| def type_domain(self): |
| pass |
| |
| def qualified_name(self): |
| pass |
| |
| # This is used to resolve nested types after instances are created. |
| def resolve_type_references(self, protocol): |
| pass |
| |
| |
| class PrimitiveType(Type): |
| def __init__(self, name): |
| self._name = name |
| |
| def __repr__(self): |
| return 'PrimitiveType[%s]' % self.qualified_name() |
| |
| def type_domain(self): |
| return None |
| |
| def qualified_name(self): |
| return self.raw_name() |
| |
| |
| class AliasedType(Type): |
| def __init__(self, declaration, domain, aliased_type_ref): |
| self._name = declaration.type_name |
| self._declaration = declaration |
| self._domain = domain |
| self._aliased_type_ref = aliased_type_ref |
| self.aliased_type = None |
| |
| def __repr__(self): |
| if self.aliased_type is not None: |
| return 'AliasedType[%s -> %r]' % (self.qualified_name(), self.aliased_type) |
| else: |
| return 'AliasedType[%s -> (unresolved)]' % self.qualified_name() |
| |
| def is_enum(self): |
| return self.aliased_type.is_enum() |
| |
| def type_domain(self): |
| return self._domain |
| |
| def qualified_name(self): |
| return ".".join([self.type_domain().domain_name, self.raw_name()]) |
| |
| def resolve_type_references(self, protocol): |
| if self.aliased_type is not None: |
| return |
| |
| self.aliased_type = protocol.lookup_type_reference(self._aliased_type_ref, self.type_domain()) |
| log.debug("< Resolved type reference for aliased type in %s: %s" % (self.qualified_name(), self.aliased_type.qualified_name())) |
| |
| |
| class EnumType(Type): |
| def __init__(self, declaration, domain, values, primitive_type_ref, is_anonymous=False): |
| self._name = "(anonymous)" if declaration is None else declaration.type_name |
| self._declaration = declaration |
| self._domain = domain |
| self._values = values |
| self._primitive_type_ref = primitive_type_ref |
| self.primitive_type = None |
| self.is_anonymous = is_anonymous |
| |
| def __repr__(self): |
| return 'EnumType[primitive_type=%s; enum_values=%s]' % (self.qualified_name(), ', '.join(map(str, self.enum_values()))) |
| |
| def is_enum(self): |
| return True |
| |
| def enum_values(self): |
| return self._values |
| |
| def type_domain(self): |
| return self._domain |
| |
| def declaration(self): |
| return self._declaration |
| |
| def qualified_name(self): |
| return ".".join([self.type_domain().domain_name, self.raw_name()]) |
| |
| def resolve_type_references(self, protocol): |
| if self.primitive_type is not None: |
| return |
| |
| self.primitive_type = protocol.lookup_type_reference(self._primitive_type_ref, Domains.GLOBAL) |
| log.debug("< Resolved type reference for enum type in %s: %s" % (self.qualified_name(), self.primitive_type.qualified_name())) |
| log.debug("<< enum values: %s" % self.enum_values()) |
| |
| |
| class ArrayType(Type): |
| def __init__(self, declaration, element_type_ref, domain): |
| self._name = None if declaration is None else declaration.type_name |
| self._declaration = declaration |
| self._domain = domain |
| self._element_type_ref = element_type_ref |
| self.element_type = None |
| |
| def __repr__(self): |
| if self.element_type is not None: |
| return 'ArrayType[element_type=%r]' % self.element_type |
| else: |
| return 'ArrayType[element_type=(unresolved)]' |
| |
| def declaration(self): |
| return self._declaration |
| |
| def type_domain(self): |
| return self._domain |
| |
| def qualified_name(self): |
| return ".".join(["array", self.element_type.qualified_name()]) |
| |
| def resolve_type_references(self, protocol): |
| if self.element_type is not None: |
| return |
| |
| self.element_type = protocol.lookup_type_reference(self._element_type_ref, self.type_domain()) |
| log.debug("< Resolved type reference for element type in %s: %s" % (self.qualified_name(), self.element_type.qualified_name())) |
| |
| |
| class ObjectType(Type): |
| def __init__(self, declaration, domain): |
| self._name = declaration.type_name |
| self._declaration = declaration |
| self._domain = domain |
| self.members = declaration.type_members |
| |
| def __repr__(self): |
| return 'ObjectType[%s]' % self.qualified_name() |
| |
| def declaration(self): |
| return self._declaration |
| |
| def type_domain(self): |
| return self._domain |
| |
| def qualified_name(self): |
| return ".".join([self.type_domain().domain_name, self.raw_name()]) |
| |
| |
| def check_for_required_properties(props, obj, what): |
| for prop in props: |
| if prop not in obj: |
| raise ParseException("When parsing %s, required property missing: %s" % (what, prop)) |
| |
| |
| class Protocol: |
| def __init__(self, framework_name): |
| self.domains = [] |
| self.types_by_name = {} |
| self.framework = Framework.fromString(framework_name) |
| |
| def parse_specification(self, json, isSupplemental): |
| log.debug("parse toplevel") |
| |
| if isinstance(json, dict) and 'domains' in json: |
| json = json['domains'] |
| if not isinstance(json, list): |
| json = [json] |
| |
| for domain in json: |
| self.parse_domain(domain, isSupplemental) |
| |
| def parse_domain(self, json, isSupplemental): |
| check_for_required_properties(['domain'], json, "domain") |
| log.debug("parse domain " + json['domain']) |
| |
| debuggable_types = None |
| target_types = None |
| version = None |
| types = [] |
| commands = [] |
| events = [] |
| |
| if 'debuggableTypes' in json: |
| if not isinstance(json['debuggableTypes'], list): |
| raise ParseException("Malformed domain specification: debuggableTypes for domain %s is not an array" % json['domain']) |
| |
| for debuggable_types in json['debuggableTypes']: |
| if debuggable_types not in _ALLOWED_DEBUGGABLE_TYPE_STRINGS: |
| raise ParseException('Malformed domain specification: debuggableTypes for domain %s is an unsupported string. Was: "%s", Allowed values: %s' % (json['domain'], json['debuggableTypes'], ', '.join(_ALLOWED_DEBUGGABLE_TYPE_STRINGS))) |
| |
| debuggable_types = json.get('debuggableTypes') |
| |
| if 'targetTypes' in json: |
| if not isinstance(json['targetTypes'], list): |
| raise ParseException("Malformed domain specification: targetTypes for domain %s is not an array" % json['domain']) |
| |
| for target_types in json['targetTypes']: |
| if target_types not in _ALLOWED_TARGET_TYPE_STRINGS: |
| raise ParseException('Malformed domain specification: targetTypes for domain %s is an unsupported string. Was: "%s", Allowed values: %s' % (json['domain'], json['targetTypes'], ', '.join(_ALLOWED_TARGET_TYPE_STRINGS))) |
| |
| target_types = json.get('targetTypes') |
| |
| if debuggable_types and not validate_target_types(debuggable_types, target_types): |
| raise ParseException('Malformed domain specification: domain %s has an item in targetTypes "%s" that is not supported by any value in debuggableTypes "%s".' % (json['domain'], target_types, debuggable_types)) |
| |
| if 'version' in json: |
| if not isinstance(json['version'], int): |
| raise ParseException("Malformed domain specification: version is not a number or string") |
| version = json['version'] |
| |
| if 'types' in json: |
| if not isinstance(json['types'], list): |
| raise ParseException("Malformed domain specification: types is not an array") |
| types.extend([self.parse_type_declaration(declaration) for declaration in json['types']]) |
| |
| if 'commands' in json: |
| if not isinstance(json['commands'], list): |
| raise ParseException("Malformed domain specification: commands is not an array") |
| commands.extend([self.parse_command(command, debuggable_types) for command in json['commands']]) |
| |
| if 'events' in json: |
| if not isinstance(json['events'], list): |
| raise ParseException("Malformed domain specification: events is not an array") |
| events.extend([self.parse_event(event, debuggable_types) for event in json['events']]) |
| |
| self.domains.append(Domain(json['domain'], json.get('description', ''), json.get('featureGuard'), debuggable_types, target_types, isSupplemental, version, types, commands, events)) |
| |
| def parse_type_declaration(self, json): |
| check_for_required_properties(['id', 'type'], json, "type") |
| log.debug("parse type %s" % json['id']) |
| |
| type_members = [] |
| |
| if 'properties' in json: |
| if not isinstance(json['properties'], list): |
| raise ParseException("Malformed type specification: properties is not an array") |
| type_members.extend([self.parse_type_member(member) for member in json['properties']]) |
| |
| duplicate_names = find_duplicates([member.member_name for member in type_members]) |
| if len(duplicate_names) > 0: |
| raise ParseException("Malformed domain specification: type declaration for %s has duplicate member names" % json['id']) |
| |
| type_ref = TypeReference(json['type'], json.get('$ref'), json.get('enum'), json.get('items')) |
| platform = Platform.fromString(json.get('platform', 'generic')) |
| return TypeDeclaration(json['id'], type_ref, json.get("description", ""), platform, type_members) |
| |
| def parse_type_member(self, json): |
| check_for_required_properties(['name'], json, "type member") |
| log.debug("parse type member %s" % json['name']) |
| |
| type_ref = TypeReference(json.get('type'), json.get('$ref'), json.get('enum'), json.get('items')) |
| return TypeMember(json['name'], type_ref, json.get('optional', False), json.get('description', "")) |
| |
| def parse_command(self, json, debuggable_types): |
| check_for_required_properties(['name'], json, "command") |
| log.debug("parse command %s" % json['name']) |
| |
| target_types = None |
| call_parameters = [] |
| return_parameters = [] |
| |
| if 'targetTypes' in json: |
| if not isinstance(json['targetTypes'], list): |
| raise ParseException("Malformed domain specification: targetTypes list for command %s is not an array" % json['name']) |
| target_types = json['targetTypes'] |
| |
| for target_type in target_types: |
| if target_type not in _ALLOWED_TARGET_TYPE_STRINGS: |
| raise ParseException('Malformed domain specification: targetTypes list for command %s is an unsupported string. Was: "%s", Allowed values: %s' % (json['name'], json['targetTypes'], ', '.join(_ALLOWED_TARGET_TYPE_STRINGS))) |
| |
| duplicate_types = find_duplicates(target_types) |
| if len(duplicate_types) > 0: |
| raise ParseException("Malformed domain specification: targetTypes list for command %s has duplicate items" % json['name']) |
| |
| if debuggable_types and not validate_target_types(debuggable_types, target_types): |
| raise ParseException('Malformed domain specification: command %s has an item in targetTypes "%s" that is not supported by any value in debuggableTypes "%s".' % (json['name'], target_types, debuggable_types)) |
| |
| if 'parameters' in json: |
| if not isinstance(json['parameters'], list): |
| raise ParseException("Malformed command specification: parameters is not an array") |
| call_parameters.extend([self.parse_call_or_return_parameter(parameter) for parameter in json['parameters']]) |
| |
| duplicate_names = find_duplicates([param.parameter_name for param in call_parameters]) |
| if len(duplicate_names) > 0: |
| raise ParseException("Malformed domain specification: call parameter list for command %s has duplicate parameter names" % json['name']) |
| |
| if 'returns' in json: |
| if not isinstance(json['returns'], list): |
| raise ParseException("Malformed command specification: returns is not an array") |
| return_parameters.extend([self.parse_call_or_return_parameter(parameter) for parameter in json['returns']]) |
| |
| duplicate_names = find_duplicates([param.parameter_name for param in return_parameters]) |
| if len(duplicate_names) > 0: |
| raise ParseException("Malformed domain specification: return parameter list for command %s has duplicate parameter names" % json['name']) |
| |
| platform = Platform.fromString(json.get('platform', 'generic')) |
| return Command(json['name'], target_types, call_parameters, return_parameters, json.get('description', ""), platform, json.get('async', False)) |
| |
| def parse_event(self, json, debuggable_types): |
| check_for_required_properties(['name'], json, "event") |
| log.debug("parse event %s" % json['name']) |
| |
| target_types = None |
| event_parameters = [] |
| |
| if 'targetTypes' in json: |
| if not isinstance(json['targetTypes'], list): |
| raise ParseException("Malformed domain specification: targetTypes for event %s is not an array" % json['name']) |
| target_types = json['targetTypes'] |
| |
| for target_type in target_types: |
| if target_type not in _ALLOWED_TARGET_TYPE_STRINGS: |
| raise ParseException('Malformed domain specification: targetTypes for event %s is an unsupported string. Was: "%s", Allowed values: %s' % (json['name'], json['targetTypes'], ', '.join(_ALLOWED_TARGET_TYPE_STRINGS))) |
| |
| duplicate_types = find_duplicates(target_types) |
| if len(duplicate_types) > 0: |
| raise ParseException("Malformed domain specification: targetTypes list for event %s has duplicate items" % json['name']) |
| |
| if debuggable_types and not validate_target_types(debuggable_types, target_types): |
| raise ParseException('Malformed domain specification: event %s has an item in targetTypes "%s" that is not supported by any value in debuggableTypes "%s".' % (json['name'], target_types, debuggable_types)) |
| |
| if 'parameters' in json: |
| if not isinstance(json['parameters'], list): |
| raise ParseException("Malformed event specification: parameters is not an array") |
| event_parameters.extend([self.parse_call_or_return_parameter(parameter) for parameter in json['parameters']]) |
| |
| duplicate_names = find_duplicates([param.parameter_name for param in event_parameters]) |
| if len(duplicate_names) > 0: |
| raise ParseException("Malformed domain specification: parameter list for event %s has duplicate parameter names" % json['name']) |
| |
| platform = Platform.fromString(json.get('platform', 'generic')) |
| return Event(json['name'], target_types, event_parameters, json.get('description', ""), platform) |
| |
| def parse_call_or_return_parameter(self, json): |
| check_for_required_properties(['name'], json, "parameter") |
| log.debug("parse parameter %s" % json['name']) |
| |
| type_ref = TypeReference(json.get('type'), json.get('$ref'), json.get('enum'), json.get('items')) |
| return Parameter(json['name'], type_ref, json.get('optional', False), json.get('description', "")) |
| |
| def resolve_types(self): |
| qualified_declared_type_names = set(['boolean', 'string', 'integer', 'number', 'enum', 'array', 'object', 'any']) |
| |
| self.types_by_name['string'] = PrimitiveType('string') |
| for _primitive_type in ['boolean', 'integer', 'number']: |
| self.types_by_name[_primitive_type] = PrimitiveType(_primitive_type) |
| for _object_type in ['any', 'object']: |
| self.types_by_name[_object_type] = PrimitiveType(_object_type) |
| |
| # Gather qualified type names from type declarations in each domain. |
| for domain in self.domains: |
| for declaration in domain.all_type_declarations(): |
| # Basic sanity checking. |
| if declaration.type_ref.referenced_type_name is not None: |
| raise TypecheckException("Type declarations must name a base type, not a type reference.") |
| |
| # Find duplicate qualified type names. |
| qualified_type_name = ".".join([domain.domain_name, declaration.type_name]) |
| if qualified_type_name in qualified_declared_type_names: |
| raise TypecheckException("Duplicate type declaration: %s" % qualified_type_name) |
| |
| qualified_declared_type_names.add(qualified_type_name) |
| |
| type_instance = None |
| |
| kind = declaration.type_ref.type_kind |
| if declaration.type_ref.enum_values is not None: |
| primitive_type_ref = TypeReference(declaration.type_ref.type_kind, None, None, None) |
| type_instance = EnumType(declaration, domain, declaration.type_ref.enum_values, primitive_type_ref) |
| elif kind == "array": |
| type_instance = ArrayType(declaration, declaration.type_ref.array_type_ref, domain) |
| elif kind == "object": |
| type_instance = ObjectType(declaration, domain) |
| else: |
| type_instance = AliasedType(declaration, domain, declaration.type_ref) |
| |
| log.debug("< Created fresh type %r for declaration %s" % (type_instance, qualified_type_name)) |
| self.types_by_name[qualified_type_name] = type_instance |
| |
| # Resolve all type references recursively. |
| for domain in self.domains: |
| domain.resolve_type_references(self) |
| |
| def lookup_type_for_declaration(self, declaration, domain): |
| # This will only match a type defined in the same domain, where prefixes aren't required. |
| qualified_name = ".".join([domain.domain_name, declaration.type_name]) |
| if qualified_name in self.types_by_name: |
| found_type = self.types_by_name[qualified_name] |
| found_type.resolve_type_references(self) |
| return found_type |
| |
| raise TypecheckException("Lookup failed for type declaration: %s (referenced from domain: %s)" % (declaration.type_name, domain.domain_name)) |
| |
| def lookup_type_reference(self, type_ref, domain): |
| # If reference is to an anonymous array type, create a fresh instance. |
| if type_ref.type_kind == "array": |
| type_instance = ArrayType(None, type_ref.array_type_ref, domain) |
| type_instance.resolve_type_references(self) |
| log.debug("< Created fresh type instance for anonymous array type: %s" % type_instance.qualified_name()) |
| return type_instance |
| |
| # If reference is to an anonymous enum type, create a fresh instance. |
| if type_ref.enum_values is not None: |
| # We need to create a type reference without enum values as the enum's nested type. |
| primitive_type_ref = TypeReference(type_ref.type_kind, None, None, None) |
| type_instance = EnumType(None, domain, type_ref.enum_values, primitive_type_ref, True) |
| type_instance.resolve_type_references(self) |
| log.debug("< Created fresh type instance for anonymous enum type: %s" % type_instance.qualified_name()) |
| return type_instance |
| |
| # This will match when referencing a type defined in the same domain, where prefixes aren't required. |
| qualified_name = ".".join([domain.domain_name, type_ref.referenced_name()]) |
| if qualified_name in self.types_by_name: |
| found_type = self.types_by_name[qualified_name] |
| found_type.resolve_type_references(self) |
| log.debug("< Lookup succeeded for unqualified type: %s" % found_type.qualified_name()) |
| return found_type |
| |
| # This will match primitive types and fully-qualified types from a different domain. |
| if type_ref.referenced_name() in self.types_by_name: |
| found_type = self.types_by_name[type_ref.referenced_name()] |
| found_type.resolve_type_references(self) |
| log.debug("< Lookup succeeded for primitive or qualified type: %s" % found_type.qualified_name()) |
| return found_type |
| |
| raise TypecheckException("Lookup failed for type reference: %s (referenced from domain: %s)" % (type_ref.referenced_name(), domain.domain_name)) |
| |
| |
| class Domain: |
| def __init__(self, domain_name, description, feature_guard, debuggable_types, target_types, isSupplemental, version, type_declarations, commands, events): |
| self.domain_name = domain_name |
| self.description = description |
| self.feature_guard = feature_guard |
| self.debuggable_types = debuggable_types |
| self.target_types = target_types |
| self.is_supplemental = isSupplemental |
| self._version = version |
| self._type_declarations = type_declarations |
| self._commands = commands |
| self._events = events |
| |
| def version(self): |
| return self._version |
| |
| def all_type_declarations(self): |
| return self._type_declarations |
| |
| def all_commands(self): |
| return self._commands |
| |
| def all_events(self): |
| return self._events |
| |
| def resolve_type_references(self, protocol): |
| log.debug("> Resolving type declarations for domain: %s" % self.domain_name) |
| for declaration in self._type_declarations: |
| declaration.resolve_type_references(protocol, self) |
| |
| log.debug("> Resolving types in commands for domain: %s" % self.domain_name) |
| for command in self._commands: |
| command.resolve_type_references(protocol, self) |
| |
| log.debug("> Resolving types in events for domain: %s" % self.domain_name) |
| for event in self._events: |
| event.resolve_type_references(protocol, self) |
| |
| |
| class Domains: |
| GLOBAL = Domain("", "The global domain, in which primitive types are implicitly declared.", None, None, None, False, None, [], [], []) |
| |
| |
| class TypeDeclaration: |
| def __init__(self, type_name, type_ref, description, platform, type_members): |
| self.type_name = type_name |
| self.type_ref = type_ref |
| self.description = description |
| self.platform = platform |
| self.type_members = type_members |
| |
| if self.type_name != ucfirst(self.type_name): |
| raise ParseException("Types must begin with an uppercase character.") |
| |
| def resolve_type_references(self, protocol, domain): |
| log.debug(">> Resolving type references for type declaration: %s" % self.type_name) |
| self.type = protocol.lookup_type_for_declaration(self, domain) |
| for member in self.type_members: |
| member.resolve_type_references(protocol, domain) |
| |
| |
| class TypeMember: |
| def __init__(self, member_name, type_ref, is_optional, description): |
| self.member_name = member_name |
| self.type_ref = type_ref |
| self.is_optional = is_optional |
| self.description = description |
| |
| if not isinstance(self.is_optional, bool): |
| raise ParseException("The 'optional' flag for a type member must be a boolean literal.") |
| |
| def resolve_type_references(self, protocol, domain): |
| log.debug(">>> Resolving type references for type member: %s" % self.member_name) |
| self.type = protocol.lookup_type_reference(self.type_ref, domain) |
| |
| |
| class Parameter: |
| def __init__(self, parameter_name, type_ref, is_optional, description): |
| self.parameter_name = parameter_name |
| self.type_ref = type_ref |
| self.is_optional = is_optional |
| self.description = description |
| |
| if not isinstance(self.is_optional, bool): |
| raise ParseException("The 'optional' flag for a parameter must be a boolean literal.") |
| |
| def resolve_type_references(self, protocol, domain): |
| log.debug(">>> Resolving type references for parameter: %s" % self.parameter_name) |
| self.type = protocol.lookup_type_reference(self.type_ref, domain) |
| |
| |
| class Command: |
| def __init__(self, command_name, target_types, call_parameters, return_parameters, description, platform, is_async): |
| self.command_name = command_name |
| self.target_types = target_types |
| self.call_parameters = call_parameters |
| self.return_parameters = return_parameters |
| self.description = description |
| self.platform = platform |
| self.is_async = is_async |
| |
| def resolve_type_references(self, protocol, domain): |
| log.debug(">> Resolving type references for call parameters in command: %s" % self.command_name) |
| for parameter in self.call_parameters: |
| parameter.resolve_type_references(protocol, domain) |
| |
| log.debug(">> Resolving type references for return parameters in command: %s" % self.command_name) |
| for parameter in self.return_parameters: |
| parameter.resolve_type_references(protocol, domain) |
| |
| |
| class Event: |
| def __init__(self, event_name, target_types, event_parameters, description, platform): |
| self.event_name = event_name |
| self.target_types = target_types |
| self.event_parameters = event_parameters |
| self.description = description |
| self.platform = platform |
| |
| def resolve_type_references(self, protocol, domain): |
| log.debug(">> Resolving type references for parameters in event: %s" % self.event_name) |
| for parameter in self.event_parameters: |
| parameter.resolve_type_references(protocol, domain) |