3 # Copyright (c) 2014 Apple Inc. All rights reserved.
4 # Copyright (c) 2014 University of Washington. All rights reserved.
6 # Redistribution and use in source and binary forms, with or without
7 # modification, are permitted provided that the following conditions
9 # 1. Redistributions of source code must retain the above copyright
10 # notice, this list of conditions and the following disclaimer.
11 # 2. Redistributions in binary form must reproduce the above copyright
12 # notice, this list of conditions and the following disclaimer in the
13 # documentation and/or other materials provided with the distribution.
15 # THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
16 # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
17 # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
19 # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
20 # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
21 # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
22 # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23 # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
24 # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
25 # THE POSSIBILITY OF SUCH DAMAGE.
30 log
= logging
.getLogger('global')
34 return str[:1].upper() + str[1:]
37 def find_duplicates(l
):
38 return [key
for key
, count
in collections
.Counter(l
).items() if count
> 1]
41 _FRAMEWORK_CONFIG_MAP
= {
45 "export_macro": "JS_EXPORT_PRIVATE"
49 # Used for code generator tests.
55 class ParseException(Exception):
59 class TypecheckException(Exception):
64 def __init__(self
, name
):
65 self
._settings
= _FRAMEWORK_CONFIG_MAP
[name
]
68 def setting(self
, key
, default
=''):
69 return self
._settings
.get(key
, default
)
72 def fromString(frameworkString
):
73 if frameworkString
== "Global":
74 return Frameworks
.Global
76 if frameworkString
== "JavaScriptCore":
77 return Frameworks
.JavaScriptCore
79 if frameworkString
== "WebInspector":
80 return Frameworks
.WebInspector
82 if frameworkString
== "Test":
83 return Frameworks
.Test
85 raise ParseException("Unknown framework: %s" % frameworkString
)
89 Global
= Framework("Global")
90 JavaScriptCore
= Framework("JavaScriptCore")
91 WebInspector
= Framework("WebInspector")
92 Test
= Framework("Test")
96 def __init__(self
, type_kind
, referenced_type_name
, enum_values
, array_items
):
97 self
.type_kind
= type_kind
98 self
.referenced_type_name
= referenced_type_name
99 self
.enum_values
= enum_values
100 if array_items
is None:
101 self
.array_type_ref
= None
103 self
.array_type_ref
= TypeReference(array_items
.get('type'), array_items
.get('$ref'), array_items
.get('enum'), array_items
.get('items'))
105 if type_kind
is not None and referenced_type_name
is not None:
106 raise ParseException("Type reference cannot have both 'type' and '$ref' keys.")
108 if type_kind
== "array" and array_items
is None:
109 raise ParseException("Type reference with type 'array' must have key 'items' to define array element type.")
111 if enum_values
is not None and len(enum_values
) == 0:
112 raise ParseException("Type reference with enum values must have at least one enum value.")
114 def referenced_name(self
):
115 if self
.referenced_type_name
is not None:
116 return self
.referenced_type_name
118 return self
.type_kind
# integer, string, number, boolean, enum, object, array
125 def __eq__(self
, other
):
126 return self
.qualified_name() == other
.qualified_name()
129 return self
.qualified_name().__hash
__()
134 # These methods should be overridden by subclasses.
138 def type_domain(self
):
141 def qualified_name(self
):
144 # This is used to resolve nested types after instances are created.
145 def resolve_type_references(self
, protocol
):
149 class PrimitiveType(Type
):
150 def __init__(self
, name
):
154 return 'PrimitiveType[%s]' % self
.qualified_name()
156 def type_domain(self
):
159 def qualified_name(self
):
160 return self
.raw_name()
163 class AliasedType(Type
):
164 def __init__(self
, name
, domain
, aliased_type_ref
):
166 self
._domain
= domain
167 self
._aliased
_type
_ref
= aliased_type_ref
168 self
.aliased_type
= None
171 if self
.aliased_type
is not None:
172 return 'AliasedType[%s -> %r]' % (self
.qualified_name(), self
.aliased_type
)
174 return 'AliasedType[%s -> (unresolved)]' % self
.qualified_name()
177 return self
.aliased_type
.is_enum()
179 def type_domain(self
):
182 def qualified_name(self
):
183 return ".".join([self
.type_domain().domain_name
, self
.raw_name()])
185 def resolve_type_references(self
, protocol
):
186 if self
.aliased_type
is not None:
189 self
.aliased_type
= protocol
.lookup_type_reference(self
._aliased
_type
_ref
, self
.type_domain())
190 log
.debug("< Resolved type reference for aliased type in %s: %s" % (self
.qualified_name(), self
.aliased_type
.qualified_name()))
193 class EnumType(Type
):
194 def __init__(self
, name
, domain
, values
, primitive_type_ref
, is_anonymous
=False):
196 self
._domain
= domain
197 self
._values
= values
198 self
._primitive
_type
_ref
= primitive_type_ref
199 self
.primitive_type
= None
200 self
.is_anonymous
= is_anonymous
203 return 'EnumType[value_type=%s; values=%s]' % (self
.qualified_name(), ', '.join(map(str, self
.enum_values())))
208 def type_domain(self
):
211 def enum_values(self
):
214 def qualified_name(self
):
215 return ".".join([self
.type_domain().domain_name
, self
.raw_name()])
217 def resolve_type_references(self
, protocol
):
218 if self
.primitive_type
is not None:
221 self
.primitive_type
= protocol
.lookup_type_reference(self
._primitive
_type
_ref
, Domains
.GLOBAL
)
222 log
.debug("< Resolved type reference for enum type in %s: %s" % (self
.qualified_name(), self
.primitive_type
.qualified_name()))
223 log
.debug("<< enum values: %s" % self
.enum_values())
226 class ArrayType(Type
):
227 def __init__(self
, name
, element_type_ref
, domain
):
229 self
._domain
= domain
230 self
._element
_type
_ref
= element_type_ref
231 self
.element_type
= None
234 if self
.element_type
is not None:
235 return 'ArrayType[element_type=%r]' % self
.element_type
237 return 'ArrayType[element_type=(unresolved)]'
239 def type_domain(self
):
242 def qualified_name(self
):
243 return ".".join(["array", self
.element_type
.qualified_name()])
245 def resolve_type_references(self
, protocol
):
246 if self
.element_type
is not None:
249 self
.element_type
= protocol
.lookup_type_reference(self
._element
_type
_ref
, self
.type_domain())
250 log
.debug("< Resolved type reference for element type in %s: %s" % (self
.qualified_name(), self
.element_type
.qualified_name()))
253 class ObjectType(Type
):
254 def __init__(self
, name
, domain
, members
):
256 self
._domain
= domain
257 self
.members
= members
260 return 'ObjectType[%s]' % self
.qualified_name()
262 def type_domain(self
):
265 def qualified_name(self
):
266 return ".".join([self
.type_domain().domain_name
, self
.raw_name()])
269 def check_for_required_properties(props
, obj
, what
):
272 raise ParseException("When parsing %s, required property missing: %s" % (what
, prop
))
276 def __init__(self
, framework_name
):
278 self
.types_by_name
= {}
279 self
.framework
= Framework
.fromString(framework_name
)
281 def parse_specification(self
, json
, isSupplemental
):
282 log
.debug("parse toplevel")
284 if isinstance(json
, dict) and 'domains' in json
:
285 json
= json
['domains']
286 if not isinstance(json
, list):
290 self
.parse_domain(domain
, isSupplemental
)
292 def parse_domain(self
, json
, isSupplemental
):
293 check_for_required_properties(['domain'], json
, "domain")
294 log
.debug("parse domain " + json
['domain'])
301 if not isinstance(json
['types'], list):
302 raise ParseException("Malformed domain specification: types is not an array")
303 types
.extend([self
.parse_type_declaration(declaration
) for declaration
in json
['types']])
305 if 'commands' in json
:
306 if not isinstance(json
['commands'], list):
307 raise ParseException("Malformed domain specification: commands is not an array")
308 commands
.extend([self
.parse_command(command
) for command
in json
['commands']])
311 if not isinstance(json
['events'], list):
312 raise ParseException("Malformed domain specification: events is not an array")
313 events
.extend([self
.parse_event(event
) for event
in json
['events']])
315 if 'availability' in json
:
316 if not commands
and not events
:
317 raise ParseException("Malformed domain specification: availability should only be included if there are commands or events.")
318 allowed_activation_strings
= set(['web'])
319 if json
['availability'] not in allowed_activation_strings
:
320 raise ParseException('Malformed domain specification: availability is an unsupported string. Was: "%s", Allowed values: %s' % (json
['availability'], ', '.join(allowed_activation_strings
)))
322 self
.domains
.append(Domain(json
['domain'], json
.get('description', ''), json
.get('featureGuard'), json
.get('availability'), isSupplemental
, types
, commands
, events
))
324 def parse_type_declaration(self
, json
):
325 check_for_required_properties(['id', 'type'], json
, "type")
326 log
.debug("parse type %s" % json
['id'])
330 if 'properties' in json
:
331 if not isinstance(json
['properties'], list):
332 raise ParseException("Malformed type specification: properties is not an array")
333 type_members
.extend([self
.parse_type_member(member
) for member
in json
['properties']])
335 duplicate_names
= find_duplicates([member
.member_name
for member
in type_members
])
336 if len(duplicate_names
) > 0:
337 raise ParseException("Malformed domain specification: type declaration for %s has duplicate member names" % json
['id'])
339 type_ref
= TypeReference(json
['type'], json
.get('$ref'), json
.get('enum'), json
.get('items'))
340 return TypeDeclaration(json
['id'], type_ref
, json
.get("description", ""), type_members
)
342 def parse_type_member(self
, json
):
343 check_for_required_properties(['name'], json
, "type member")
344 log
.debug("parse type member %s" % json
['name'])
346 type_ref
= TypeReference(json
.get('type'), json
.get('$ref'), json
.get('enum'), json
.get('items'))
347 return TypeMember(json
['name'], type_ref
, json
.get('optional', False), json
.get('description', ""))
349 def parse_command(self
, json
):
350 check_for_required_properties(['name'], json
, "command")
351 log
.debug("parse command %s" % json
['name'])
354 return_parameters
= []
356 if 'parameters' in json
:
357 if not isinstance(json
['parameters'], list):
358 raise ParseException("Malformed command specification: parameters is not an array")
359 call_parameters
.extend([self
.parse_call_or_return_parameter(parameter
) for parameter
in json
['parameters']])
361 duplicate_names
= find_duplicates([param
.parameter_name
for param
in call_parameters
])
362 if len(duplicate_names
) > 0:
363 raise ParseException("Malformed domain specification: call parameter list for command %s has duplicate parameter names" % json
['name'])
365 if 'returns' in json
:
366 if not isinstance(json
['returns'], list):
367 raise ParseException("Malformed command specification: returns is not an array")
368 return_parameters
.extend([self
.parse_call_or_return_parameter(parameter
) for parameter
in json
['returns']])
370 duplicate_names
= find_duplicates([param
.parameter_name
for param
in return_parameters
])
371 if len(duplicate_names
) > 0:
372 raise ParseException("Malformed domain specification: return parameter list for command %s has duplicate parameter names" % json
['name'])
374 return Command(json
['name'], call_parameters
, return_parameters
, json
.get('description', ""), json
.get('async', False))
376 def parse_event(self
, json
):
377 check_for_required_properties(['name'], json
, "event")
378 log
.debug("parse event %s" % json
['name'])
380 event_parameters
= []
382 if 'parameters' in json
:
383 if not isinstance(json
['parameters'], list):
384 raise ParseException("Malformed event specification: parameters is not an array")
385 event_parameters
.extend([self
.parse_call_or_return_parameter(parameter
) for parameter
in json
['parameters']])
387 duplicate_names
= find_duplicates([param
.parameter_name
for param
in event_parameters
])
388 if len(duplicate_names
) > 0:
389 raise ParseException("Malformed domain specification: parameter list for event %s has duplicate parameter names" % json
['name'])
391 return Event(json
['name'], event_parameters
, json
.get('description', ""))
393 def parse_call_or_return_parameter(self
, json
):
394 check_for_required_properties(['name'], json
, "parameter")
395 log
.debug("parse parameter %s" % json
['name'])
397 type_ref
= TypeReference(json
.get('type'), json
.get('$ref'), json
.get('enum'), json
.get('items'))
398 return Parameter(json
['name'], type_ref
, json
.get('optional', False), json
.get('description', ""))
400 def resolve_types(self
):
401 qualified_declared_type_names
= set(['boolean', 'string', 'integer', 'number', 'enum', 'array', 'object', 'any'])
403 self
.types_by_name
['string'] = PrimitiveType('string')
404 for _primitive_type
in ['boolean', 'integer', 'number']:
405 self
.types_by_name
[_primitive_type
] = PrimitiveType(_primitive_type
)
406 for _object_type
in ['any', 'object']:
407 self
.types_by_name
[_object_type
] = PrimitiveType(_object_type
)
409 # Gather qualified type names from type declarations in each domain.
410 for domain
in self
.domains
:
411 for declaration
in domain
.type_declarations
:
412 # Basic sanity checking.
413 if declaration
.type_ref
.referenced_type_name
is not None:
414 raise TypecheckException("Type declarations must name a base type, not a type reference.")
416 # Find duplicate qualified type names.
417 qualified_type_name
= ".".join([domain
.domain_name
, declaration
.type_name
])
418 if qualified_type_name
in qualified_declared_type_names
:
419 raise TypecheckException("Duplicate type declaration: %s" % qualified_type_name
)
421 qualified_declared_type_names
.add(qualified_type_name
)
425 kind
= declaration
.type_ref
.type_kind
426 if declaration
.type_ref
.enum_values
is not None:
427 primitive_type_ref
= TypeReference(declaration
.type_ref
.type_kind
, None, None, None)
428 type_instance
= EnumType(declaration
.type_name
, domain
, declaration
.type_ref
.enum_values
, primitive_type_ref
)
429 elif kind
== "array":
430 type_instance
= ArrayType(declaration
.type_name
, declaration
.type_ref
.array_type_ref
, domain
)
431 elif kind
== "object":
432 type_instance
= ObjectType(declaration
.type_name
, domain
, declaration
.type_members
)
434 type_instance
= AliasedType(declaration
.type_name
, domain
, declaration
.type_ref
)
436 log
.debug("< Created fresh type %r for declaration %s" % (type_instance
, qualified_type_name
))
437 self
.types_by_name
[qualified_type_name
] = type_instance
439 # Resolve all type references recursively.
440 for domain
in self
.domains
:
441 domain
.resolve_type_references(self
)
443 def lookup_type_for_declaration(self
, declaration
, domain
):
444 # This will only match a type defined in the same domain, where prefixes aren't required.
445 qualified_name
= ".".join([domain
.domain_name
, declaration
.type_name
])
446 if qualified_name
in self
.types_by_name
:
447 found_type
= self
.types_by_name
[qualified_name
]
448 found_type
.resolve_type_references(self
)
451 raise TypecheckException("Lookup failed for type declaration: %s (referenced from domain: %s)" % (declaration
.type_name
, domain
.domain_name
))
453 def lookup_type_reference(self
, type_ref
, domain
):
454 # If reference is to an anonymous array type, create a fresh instance.
455 if type_ref
.type_kind
== "array":
456 type_instance
= ArrayType(None, type_ref
.array_type_ref
, domain
)
457 type_instance
.resolve_type_references(self
)
458 log
.debug("< Created fresh type instance for anonymous array type: %s" % type_instance
.qualified_name())
461 # If reference is to an anonymous enum type, create a fresh instance.
462 if type_ref
.enum_values
is not None:
463 # We need to create a type reference without enum values as the enum's nested type.
464 primitive_type_ref
= TypeReference(type_ref
.type_kind
, None, None, None)
465 type_instance
= EnumType("(anonymous)", domain
, type_ref
.enum_values
, primitive_type_ref
, True)
466 type_instance
.resolve_type_references(self
)
467 log
.debug("< Created fresh type instance for anonymous enum type: %s" % type_instance
.qualified_name())
470 # This will match when referencing a type defined in the same domain, where prefixes aren't required.
471 qualified_name
= ".".join([domain
.domain_name
, type_ref
.referenced_name()])
472 if qualified_name
in self
.types_by_name
:
473 found_type
= self
.types_by_name
[qualified_name
]
474 found_type
.resolve_type_references(self
)
475 log
.debug("< Lookup succeeded for unqualified type: %s" % found_type
.qualified_name())
478 # This will match primitive types and fully-qualified types from a different domain.
479 if type_ref
.referenced_name() in self
.types_by_name
:
480 found_type
= self
.types_by_name
[type_ref
.referenced_name()]
481 found_type
.resolve_type_references(self
)
482 log
.debug("< Lookup succeeded for primitive or qualified type: %s" % found_type
.qualified_name())
485 raise TypecheckException("Lookup failed for type reference: %s (referenced from domain: %s)" % (type_ref
.referenced_name(), domain
.domain_name
))
489 def __init__(self
, domain_name
, description
, feature_guard
, availability
, isSupplemental
, type_declarations
, commands
, events
):
490 self
.domain_name
= domain_name
491 self
.description
= description
492 self
.feature_guard
= feature_guard
493 self
.availability
= availability
494 self
.is_supplemental
= isSupplemental
495 self
.type_declarations
= type_declarations
496 self
.commands
= commands
499 def resolve_type_references(self
, protocol
):
500 log
.debug("> Resolving type declarations for domain: %s" % self
.domain_name
)
501 for declaration
in self
.type_declarations
:
502 declaration
.resolve_type_references(protocol
, self
)
504 log
.debug("> Resolving types in commands for domain: %s" % self
.domain_name
)
505 for command
in self
.commands
:
506 command
.resolve_type_references(protocol
, self
)
508 log
.debug("> Resolving types in events for domain: %s" % self
.domain_name
)
509 for event
in self
.events
:
510 event
.resolve_type_references(protocol
, self
)
514 GLOBAL
= Domain("", "The global domain, in which primitive types are implicitly declared.", None, None, True, [], [], [])
517 class TypeDeclaration
:
518 def __init__(self
, type_name
, type_ref
, description
, type_members
):
519 self
.type_name
= type_name
520 self
.type_ref
= type_ref
521 self
.description
= description
522 self
.type_members
= type_members
524 if self
.type_name
!= ucfirst(self
.type_name
):
525 raise ParseException("Types must begin with an uppercase character.")
527 def resolve_type_references(self
, protocol
, domain
):
528 log
.debug(">> Resolving type references for type declaration: %s" % self
.type_name
)
529 self
.type = protocol
.lookup_type_for_declaration(self
, domain
)
530 for member
in self
.type_members
:
531 member
.resolve_type_references(protocol
, domain
)
535 def __init__(self
, member_name
, type_ref
, is_optional
, description
):
536 self
.member_name
= member_name
537 self
.type_ref
= type_ref
538 self
.is_optional
= is_optional
539 self
.description
= description
541 if self
.is_optional
not in [True, False]:
542 raise ParseException("The 'optional' flag for a type member must be a boolean literal.")
544 def resolve_type_references(self
, protocol
, domain
):
545 log
.debug(">>> Resolving type references for type member: %s" % self
.member_name
)
546 self
.type = protocol
.lookup_type_reference(self
.type_ref
, domain
)
550 def __init__(self
, parameter_name
, type_ref
, is_optional
, description
):
551 self
.parameter_name
= parameter_name
552 self
.type_ref
= type_ref
553 self
.is_optional
= is_optional
554 self
.description
= description
556 if self
.is_optional
not in [True, False]:
557 raise ParseException("The 'optional' flag for a parameter must be a boolean literal.")
559 def resolve_type_references(self
, protocol
, domain
):
560 log
.debug(">>> Resolving type references for parameter: %s" % self
.parameter_name
)
561 self
.type = protocol
.lookup_type_reference(self
.type_ref
, domain
)
565 def __init__(self
, command_name
, call_parameters
, return_parameters
, description
, is_async
):
566 self
.command_name
= command_name
567 self
.call_parameters
= call_parameters
568 self
.return_parameters
= return_parameters
569 self
.description
= description
570 self
.is_async
= is_async
572 def resolve_type_references(self
, protocol
, domain
):
573 log
.debug(">> Resolving type references for call parameters in command: %s" % self
.command_name
)
574 for parameter
in self
.call_parameters
:
575 parameter
.resolve_type_references(protocol
, domain
)
577 log
.debug(">> Resolving type references for return parameters in command: %s" % self
.command_name
)
578 for parameter
in self
.return_parameters
:
579 parameter
.resolve_type_references(protocol
, domain
)
583 def __init__(self
, event_name
, event_parameters
, description
):
584 self
.event_name
= event_name
585 self
.event_parameters
= event_parameters
586 self
.description
= description
588 def resolve_type_references(self
, protocol
, domain
):
589 log
.debug(">> Resolving type references for parameters in event: %s" % self
.event_name
)
590 for parameter
in self
.event_parameters
:
591 parameter
.resolve_type_references(protocol
, domain
)