]> git.saurik.com Git - apple/javascriptcore.git/blob - inspector/scripts/codegen/models.py
JavaScriptCore-7601.1.46.3.tar.gz
[apple/javascriptcore.git] / inspector / scripts / codegen / models.py
1 #!/usr/bin/env python
2 #
3 # Copyright (c) 2014 Apple Inc. All rights reserved.
4 # Copyright (c) 2014 University of Washington. All rights reserved.
5 #
6 # Redistribution and use in source and binary forms, with or without
7 # modification, are permitted provided that the following conditions
8 # are met:
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.
14 #
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.
26
27 import logging
28 import collections
29
30 log = logging.getLogger('global')
31
32
33 def ucfirst(str):
34 return str[:1].upper() + str[1:]
35
36
37 def find_duplicates(l):
38 return [key for key, count in collections.Counter(l).items() if count > 1]
39
40
41 _FRAMEWORK_CONFIG_MAP = {
42 "Global": {
43 },
44 "JavaScriptCore": {
45 "export_macro": "JS_EXPORT_PRIVATE"
46 },
47 "WebInspector": {
48 },
49 # Used for code generator tests.
50 "Test": {
51 }
52 }
53
54
55 class ParseException(Exception):
56 pass
57
58
59 class TypecheckException(Exception):
60 pass
61
62
63 class Framework:
64 def __init__(self, name):
65 self._settings = _FRAMEWORK_CONFIG_MAP[name]
66 self.name = name
67
68 def setting(self, key, default=''):
69 return self._settings.get(key, default)
70
71 @staticmethod
72 def fromString(frameworkString):
73 if frameworkString == "Global":
74 return Frameworks.Global
75
76 if frameworkString == "JavaScriptCore":
77 return Frameworks.JavaScriptCore
78
79 if frameworkString == "WebInspector":
80 return Frameworks.WebInspector
81
82 if frameworkString == "Test":
83 return Frameworks.Test
84
85 raise ParseException("Unknown framework: %s" % frameworkString)
86
87
88 class Frameworks:
89 Global = Framework("Global")
90 JavaScriptCore = Framework("JavaScriptCore")
91 WebInspector = Framework("WebInspector")
92 Test = Framework("Test")
93
94
95 class TypeReference:
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
102 else:
103 self.array_type_ref = TypeReference(array_items.get('type'), array_items.get('$ref'), array_items.get('enum'), array_items.get('items'))
104
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.")
107
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.")
110
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.")
113
114 def referenced_name(self):
115 if self.referenced_type_name is not None:
116 return self.referenced_type_name
117 else:
118 return self.type_kind # integer, string, number, boolean, enum, object, array
119
120
121 class Type:
122 def __init__(self):
123 pass
124
125 def __eq__(self, other):
126 return self.qualified_name() == other.qualified_name()
127
128 def __hash__(self):
129 return self.qualified_name().__hash__()
130
131 def raw_name(self):
132 return self._name
133
134 # These methods should be overridden by subclasses.
135 def is_enum(self):
136 return False
137
138 def type_domain(self):
139 pass
140
141 def qualified_name(self):
142 pass
143
144 # This is used to resolve nested types after instances are created.
145 def resolve_type_references(self, protocol):
146 pass
147
148
149 class PrimitiveType(Type):
150 def __init__(self, name):
151 self._name = name
152
153 def __repr__(self):
154 return 'PrimitiveType[%s]' % self.qualified_name()
155
156 def type_domain(self):
157 return None
158
159 def qualified_name(self):
160 return self.raw_name()
161
162
163 class AliasedType(Type):
164 def __init__(self, name, domain, aliased_type_ref):
165 self._name = name
166 self._domain = domain
167 self._aliased_type_ref = aliased_type_ref
168 self.aliased_type = None
169
170 def __repr__(self):
171 if self.aliased_type is not None:
172 return 'AliasedType[%s -> %r]' % (self.qualified_name(), self.aliased_type)
173 else:
174 return 'AliasedType[%s -> (unresolved)]' % self.qualified_name()
175
176 def is_enum(self):
177 return self.aliased_type.is_enum()
178
179 def type_domain(self):
180 return self._domain
181
182 def qualified_name(self):
183 return ".".join([self.type_domain().domain_name, self.raw_name()])
184
185 def resolve_type_references(self, protocol):
186 if self.aliased_type is not None:
187 return
188
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()))
191
192
193 class EnumType(Type):
194 def __init__(self, name, domain, values, primitive_type_ref, is_anonymous=False):
195 self._name = name
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
201
202 def __repr__(self):
203 return 'EnumType[value_type=%s; values=%s]' % (self.qualified_name(), ', '.join(map(str, self.enum_values())))
204
205 def is_enum(self):
206 return True
207
208 def type_domain(self):
209 return self._domain
210
211 def enum_values(self):
212 return self._values
213
214 def qualified_name(self):
215 return ".".join([self.type_domain().domain_name, self.raw_name()])
216
217 def resolve_type_references(self, protocol):
218 if self.primitive_type is not None:
219 return
220
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())
224
225
226 class ArrayType(Type):
227 def __init__(self, name, element_type_ref, domain):
228 self._name = name
229 self._domain = domain
230 self._element_type_ref = element_type_ref
231 self.element_type = None
232
233 def __repr__(self):
234 if self.element_type is not None:
235 return 'ArrayType[element_type=%r]' % self.element_type
236 else:
237 return 'ArrayType[element_type=(unresolved)]'
238
239 def type_domain(self):
240 return self._domain
241
242 def qualified_name(self):
243 return ".".join(["array", self.element_type.qualified_name()])
244
245 def resolve_type_references(self, protocol):
246 if self.element_type is not None:
247 return
248
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()))
251
252
253 class ObjectType(Type):
254 def __init__(self, name, domain, members):
255 self._name = name
256 self._domain = domain
257 self.members = members
258
259 def __repr__(self):
260 return 'ObjectType[%s]' % self.qualified_name()
261
262 def type_domain(self):
263 return self._domain
264
265 def qualified_name(self):
266 return ".".join([self.type_domain().domain_name, self.raw_name()])
267
268
269 def check_for_required_properties(props, obj, what):
270 for prop in props:
271 if prop not in obj:
272 raise ParseException("When parsing %s, required property missing: %s" % (what, prop))
273
274
275 class Protocol:
276 def __init__(self, framework_name):
277 self.domains = []
278 self.types_by_name = {}
279 self.framework = Framework.fromString(framework_name)
280
281 def parse_specification(self, json, isSupplemental):
282 log.debug("parse toplevel")
283
284 if isinstance(json, dict) and 'domains' in json:
285 json = json['domains']
286 if not isinstance(json, list):
287 json = [json]
288
289 for domain in json:
290 self.parse_domain(domain, isSupplemental)
291
292 def parse_domain(self, json, isSupplemental):
293 check_for_required_properties(['domain'], json, "domain")
294 log.debug("parse domain " + json['domain'])
295
296 types = []
297 commands = []
298 events = []
299
300 if 'types' in json:
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']])
304
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']])
309
310 if 'events' in json:
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']])
314
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)))
321
322 self.domains.append(Domain(json['domain'], json.get('description', ''), json.get('featureGuard'), json.get('availability'), isSupplemental, types, commands, events))
323
324 def parse_type_declaration(self, json):
325 check_for_required_properties(['id', 'type'], json, "type")
326 log.debug("parse type %s" % json['id'])
327
328 type_members = []
329
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']])
334
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'])
338
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)
341
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'])
345
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', ""))
348
349 def parse_command(self, json):
350 check_for_required_properties(['name'], json, "command")
351 log.debug("parse command %s" % json['name'])
352
353 call_parameters = []
354 return_parameters = []
355
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']])
360
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'])
364
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']])
369
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'])
373
374 return Command(json['name'], call_parameters, return_parameters, json.get('description', ""), json.get('async', False))
375
376 def parse_event(self, json):
377 check_for_required_properties(['name'], json, "event")
378 log.debug("parse event %s" % json['name'])
379
380 event_parameters = []
381
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']])
386
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'])
390
391 return Event(json['name'], event_parameters, json.get('description', ""))
392
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'])
396
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', ""))
399
400 def resolve_types(self):
401 qualified_declared_type_names = set(['boolean', 'string', 'integer', 'number', 'enum', 'array', 'object', 'any'])
402
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)
408
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.")
415
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)
420
421 qualified_declared_type_names.add(qualified_type_name)
422
423 type_instance = None
424
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)
433 else:
434 type_instance = AliasedType(declaration.type_name, domain, declaration.type_ref)
435
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
438
439 # Resolve all type references recursively.
440 for domain in self.domains:
441 domain.resolve_type_references(self)
442
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)
449 return found_type
450
451 raise TypecheckException("Lookup failed for type declaration: %s (referenced from domain: %s)" % (declaration.type_name, domain.domain_name))
452
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())
459 return type_instance
460
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())
468 return type_instance
469
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())
476 return found_type
477
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())
483 return found_type
484
485 raise TypecheckException("Lookup failed for type reference: %s (referenced from domain: %s)" % (type_ref.referenced_name(), domain.domain_name))
486
487
488 class Domain:
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
497 self.events = events
498
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)
503
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)
507
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)
511
512
513 class Domains:
514 GLOBAL = Domain("", "The global domain, in which primitive types are implicitly declared.", None, None, True, [], [], [])
515
516
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
523
524 if self.type_name != ucfirst(self.type_name):
525 raise ParseException("Types must begin with an uppercase character.")
526
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)
532
533
534 class TypeMember:
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
540
541 if self.is_optional not in [True, False]:
542 raise ParseException("The 'optional' flag for a type member must be a boolean literal.")
543
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)
547
548
549 class Parameter:
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
555
556 if self.is_optional not in [True, False]:
557 raise ParseException("The 'optional' flag for a parameter must be a boolean literal.")
558
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)
562
563
564 class Command:
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
571
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)
576
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)
580
581
582 class Event:
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
587
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)