e6c82e21cde3d846e49b6aa38ed19e25c741be98
[WebKit-https.git] / Source / JavaScriptCore / replay / scripts / CodeGeneratorReplayInputs.py
1 #!/usr/bin/env python
2 # Copyright (c) 2014 Apple Inc. All rights reserved.
3 #
4 # Redistribution and use in source and binary forms, with or without
5 # modification, are permitted provided that the following conditions are
6 # met:
7 #
8 #     * Redistributions of source code must retain the above copyright
9 # notice, this list of conditions and the following disclaimer.
10 #     * Redistributions in binary form must reproduce the above
11 # copyright notice, this list of conditions and the following disclaimer
12 # in the documentation and/or other materials provided with the
13 # distribution.
14 #
15 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
27 import os.path
28 import re
29 import sys
30 import string
31 from string import Template
32 import optparse
33 import logging
34 from CodeGeneratorReplayInputsTemplates import Templates
35
36 try:
37     import json
38 except ImportError:
39     import simplejson as json
40
41 # Configuration values are first looked up in the framework configuration,
42 # and then in the global configuration if there is no framework-specific value.
43 GLOBAL_CONFIG = {
44     "baseFilename": "ReplayInputs",
45     "guardCondition": "ENABLE(WEB_REPLAY)",
46     "traitsFrameworkName": "JavaScriptCore",
47
48     # These are formatted as ([allowed_frameworks], (framework, header_path)).
49     # The generator can figure out how to format the includes.
50     "headerIncludes": [
51         (["WebCore"],
52             ("WebCore", "replay/EventLoopInput.h")
53         ),
54         (["JavaScriptCore", "WebCore"],
55             ("JavaScriptCore", "replay/EncodedValue.h")
56         ),
57         (["JavaScriptCore"],
58             ("JavaScriptCore", "replay/NondeterministicInput.h")
59         ),
60         (["WebCore"],
61             ("WTF", "wtf/text/WTFString.h")
62         ),
63
64         # Testing fixtures.
65         (["Test"],
66             ("WebCore", "platform/ExternalNamespaceHeaderIncludeDummy.h")
67         ),
68         (["Test"],
69             ("Test", "platform/InternalNamespaceHeaderIncludeDummy.h")
70         )
71     ],
72
73     "implIncludes": [
74         (["WebCore"],
75             ("WebCore", "replay/ReplayInputTypes.h")
76         ),
77         (["WebCore"],
78             ("WebCore", "replay/SerializationMethods.h")
79         ),
80         (["WebCore", "JavaScriptCore"],
81             ("JavaScriptCore", "inspector/InspectorValues.h")
82         ),
83         (["JavaScriptCore"],
84             ("WTF", "wtf/NeverDestroyed.h")
85         ),
86         (["JavaScriptCore"],
87             ("WTF", "wtf/text/AtomicString.h")
88         ),
89
90         # Testing fixtures.
91         (["Test"],
92             ("WebCore", "platform/ExternalNamespaceImplIncludeDummy.h")
93         ),
94         (["Test"],
95             ("Test", "platform/InternalNamespaceImplIncludeDummy.h")
96         )
97     ],
98 }
99
100 FRAMEWORK_CONFIG_MAP = {
101     "Global": {
102         "prefix": "",
103         "namespace": ""
104     },
105
106     "WTF": {
107         "prefix": "WTF",
108         "namespace": "WTF",
109     },
110     "JavaScriptCore": {
111         "prefix": "JS",
112         "namespace": "JSC",
113         "exportMacro": "JS_EXPORT_PRIVATE",
114         "inputTypeTemplate": Templates.InputTypeFromStaticLocal,
115     },
116     "WebCore": {
117         "prefix": "Web",
118         "namespace": "WebCore",
119         "inputTypeTemplate": Templates.InputTypeFromThreadLocal,
120     },
121     # Used for bindings tests.
122     "Test": {
123         "prefix": "Test",
124         "namespace": "Test",
125         "inputTypeTemplate": Templates.InputTypeFromStaticLocal,
126     }
127 }
128
129 # These settings are specific to an input queue.
130 QUEUE_CONFIG_MAP = {
131     "SCRIPT_MEMOIZED": {
132         "enumValue": "ScriptMemoizedData",
133         "baseClass": "NondeterministicInput<%s>",
134     },
135     "LOADER_MEMOIZED": {
136         "enumValue": "LoaderMemoizedData",
137         "baseClass": "NondeterministicInput<%s>",
138     },
139     "EVENT_LOOP": {
140         "enumValue": "EventLoopInput",
141         "baseClass": "EventLoopInput<%s>",
142     },
143 }
144
145 # Use a global logger, which normally only logs errors.
146 # It can be configured to log debug messages from the CLI.
147 logging.basicConfig(format='%(levelname)s: %(message)s', level=logging.ERROR)
148 log = logging.getLogger('global')
149
150
151 # Model classes, which transliterate JSON input.
152 class ParseException(Exception):
153     pass
154
155
156 class TypecheckException(Exception):
157     pass
158
159
160 class Framework:
161     def __init__(self, name):
162         self._settings = FRAMEWORK_CONFIG_MAP[name]
163         self.name = name
164
165     def setting(self, key, default=''):
166         return self._settings.get(key, default)
167
168     @staticmethod
169     def fromString(frameworkString):
170         if frameworkString == "Global":
171             return Frameworks.Global
172
173         if frameworkString == "WTF":
174             return Frameworks.WTF
175
176         if frameworkString == "JavaScriptCore":
177             return Frameworks.JavaScriptCore
178
179         if frameworkString == "WebCore":
180             return Frameworks.WebCore
181
182         if frameworkString == "Test":
183             return Frameworks.Test
184
185         raise ParseException("Unknown framework: " + frameworkString)
186
187
188 class Frameworks:
189     Global = Framework("Global")
190     WTF = Framework("WTF")
191     JavaScriptCore = Framework("JavaScriptCore")
192     WebCore = Framework("WebCore")
193     Test = Framework("Test")
194
195
196 class InputQueue:
197     def __init__(self, settings):
198         self._settings = settings
199
200     def setting(self, key, default=''):
201         return self._settings.get(key, default)
202
203     @staticmethod
204     def fromString(queueString):
205         if queueString == "SCRIPT_MEMOIZED":
206             return InputQueues.SCRIPT_MEMOIZED
207
208         if queueString == "LOADER_MEMOIZED":
209             return InputQueues.LOADER_MEMOIZED
210
211         if queueString == "EVENT_LOOP":
212             return InputQueues.EVENT_LOOP
213
214         raise ParseException("Unknown input queue: " + queueString)
215
216
217 class InputQueues:
218     SCRIPT_MEMOIZED = InputQueue(QUEUE_CONFIG_MAP["SCRIPT_MEMOIZED"])
219     LOADER_MEMOIZED = InputQueue(QUEUE_CONFIG_MAP["LOADER_MEMOIZED"])
220     EVENT_LOOP = InputQueue(QUEUE_CONFIG_MAP["EVENT_LOOP"])
221
222
223 class Input:
224     def __init__(self, name, description, queueString, flags, guard=None):
225         self.name = name
226         self.description = description
227         self.queue = InputQueue.fromString(queueString)
228         self._flags = flags
229         self.guard = guard
230         self.members = []  # names should be unique, but ordered.
231
232     def setting(self, key, default=''):
233         if key in self._flags:
234             return True
235
236         return self.queue.setting(key, default)
237
238
239 class InputMember:
240     def __init__(self, memberName, typeName, flags=[]):
241         self.memberName = memberName
242         self.typeName = typeName
243         self._flags = flags
244
245     def has_flag(self, key, default=''):
246         return key in self._flags
247
248
249 class TypeMode:
250     def __init__(self, name):
251         self._name = name
252
253     @staticmethod
254     def fromString(modeString):
255         modeString = modeString.upper()
256         if modeString == 'SCALAR':
257             return TypeModes.SCALAR
258         if modeString == 'HEAVY_SCALAR':
259             return TypeModes.HEAVY_SCALAR
260         if modeString == 'OWNED':
261             return TypeModes.OWNED
262         if modeString == 'SHARED':
263             return TypeModes.SHARED
264         if modeString == 'VECTOR':
265             return TypeModes.VECTOR
266
267         raise ParseException("Unknown type mode: " + modeString)
268
269
270 class TypeModes:
271     # Copy for assignment and for getter
272     SCALAR = TypeMode("SCALAR")
273     # Copy for assignment, pass by reference for getter
274     HEAVY_SCALAR = TypeMode("HEAVY_SCALAR")
275     # Move for assignment, pass by reference for getter
276     OWNED = TypeMode("OWNED")
277     # Copy a RefPtr for assignment and getter
278     SHARED = TypeMode("SHARED")
279     # Move operator for assignment, pass by reference for getter
280     VECTOR = TypeMode("VECTOR")
281
282
283 class Type:
284     def __init__(self, name, mode, framework, header, enclosing_class, values, guard_values_map, underlying_storage, flags, guard=None):
285         self._name = name
286         self.mode = mode
287         self.framework = framework
288         self.header = header
289         self.enclosing_class = enclosing_class
290         self.values = values
291         self.guard_values_map = guard_values_map
292         self.underlying_storage = underlying_storage
293         self._flags = flags
294         self.guard = guard
295
296     def __eq__(self, other):
297         return self.type_name() == other.type_name() and self.mode == other.mode
298
299     def __hash__(self):
300         return self._name.__hash__()
301
302     def has_flag(self, flagString):
303         return flagString in self._flags
304
305     def is_struct(self):
306         return self.has_flag("STRUCT")
307
308     def is_enum(self):
309         return self.has_flag("ENUM")
310
311     def is_enum_class(self):
312         return self.has_flag("ENUM_CLASS")
313
314     def declaration_kind(self):
315         if self.is_enum():
316             return "enum"
317         elif self.is_enum_class():
318             return "enum class"
319         elif self.is_struct():
320             return "struct"
321         else:
322             return "class"
323
324     def qualified_prefix(self):
325         components = []
326         if self.framework != Frameworks.Global:
327             components.append(self.framework.setting('namespace'))
328         if self.enclosing_class is not None:
329             components.append(self.enclosing_class)
330         components.append("")
331         return "::".join(components)
332
333     def type_name(self, qualified=False):
334         if qualified:
335             return "%s%s" % (self.qualified_prefix(), self._name)
336         elif self.enclosing_class is not None:
337             return "%s::%s" % (self.enclosing_class, self._name)
338         else:
339             return self._name
340
341     def storage_type(self, qualified=False):
342         if self.mode == TypeModes.OWNED:
343             return "std::unique_ptr<%s>" % self.type_name(qualified)
344         elif self.mode == TypeModes.SHARED:
345             return "RefPtr<%s>" % self.type_name(qualified)
346         else:
347             return self.type_name(qualified)
348
349     def borrow_type(self, qualified=False):
350         if self.mode == TypeModes.SCALAR:
351             return self.type_name(qualified)
352         elif self.mode == TypeModes.SHARED:
353             return "PassRefPtr<%s>" % self.type_name(qualified)
354         else:
355             return "const %s&" % self.type_name(qualified)
356
357     def argument_type(self, qualified=False):
358         if self.mode == TypeModes.SHARED:
359             return "PassRefPtr<%s>" % self.type_name(qualified)
360         else:
361             return self.storage_type()
362
363
364 def check_for_required_properties(props, obj, what):
365     for prop in props:
366         if prop not in obj:
367             raise ParseException("When parsing %s, required property missing: %s" % (what, prop))
368
369
370 class VectorType(Type):
371     def __init__(self, element_type):
372         self._element_type = element_type
373         self.mode = TypeModes.VECTOR
374         self.framework = element_type.framework
375         self.enclosing_class = None
376
377     def has_flag(self):
378         return False
379
380     def is_struct(self):
381         return False
382
383     def is_enum(self):
384         return False
385
386     def is_enum_class(self):
387         return False
388
389     def qualified_prefix(self):
390         return ""
391
392     def type_name(self, qualified=False):
393         return "Vector<%s>" % self._element_type.type_name(qualified=qualified)
394
395     def argument_type(self, qualified=False):
396         return self.type_name(qualified=qualified) + "&"
397
398
399 class InputsModel:
400     def __init__(self, parsed_json):
401         self.inputs = []
402         self.types = []
403
404         # Types have associated frameworks and are in their namespace, but within the specification
405         # file types are in a flat namespace. Types with the same name are not allowed.
406         self.types_by_name = {}
407         self.inputs_by_name = {}
408
409         self.parse_toplevel(parsed_json)
410
411     def enum_types(self):
412         _enums = filter(lambda x: x.is_enum() or x.is_enum_class(), self.types)
413         return sorted(_enums, key=lambda _enum: _enum.type_name())
414
415     def get_type_for_member(self, member):
416         if member.has_flag("VECTOR"):
417             return VectorType(self.types_by_name.get(member.typeName))
418         else:
419             return self.types_by_name.get(member.typeName)
420
421     def parse_toplevel(self, json):
422         check_for_required_properties(['types', 'inputs'], json, 'toplevel')
423         if not isinstance(json['types'], dict):
424             raise ParseException("Malformed specification: types is not a dict of framework->type list")
425
426         if not isinstance(json['inputs'], list):
427             raise ParseException("Malformed specification: inputs is not an array")
428
429         for type_framework_name, type_list in json['types'].iteritems():
430             if not isinstance(type_list, list):
431                 raise ParseException("Malformed specification: type list for framework %s is not a list" % type_framework_name)
432
433             for _type in type_list:
434                 self.parse_type_with_framework_name(_type, type_framework_name)
435
436         for val in json['inputs']:
437             self.parse_input(val)
438
439     def parse_type_with_framework_name(self, json, framework_name):
440         check_for_required_properties(['name', 'mode'], json, 'type')
441         framework = Framework.fromString(framework_name)
442         if framework is not Frameworks.Global:
443             check_for_required_properties(['header'], json, 'non-global type')
444
445         type_name = json['name']
446         type_mode = TypeMode.fromString(json['mode'])
447         header = json.get('header')
448         enclosing_class = json.get('enclosing_class')
449         enum_values = json.get('values')
450         guarded_enum_values = json.get('guarded_values', {})
451         type_storage = json.get('storage')
452         type_flags = json.get('flags', [])
453         guard = json.get('guard', None)
454         _type = Type(type_name, type_mode, framework, header, enclosing_class, enum_values, guarded_enum_values, type_storage, type_flags, guard)
455         if _type.is_enum() or _type.is_enum_class():
456             check_for_required_properties(['values'], json, 'enum')
457             if not isinstance(json['values'], list) or len(_type.values) == 0:
458                 raise ParseException("Malformed specification: enum %s does not supply a list of values" % type_name)
459
460             if _type.is_enum() and "storage" not in json:
461                 raise ParseException("Could not parse enum %s: C-style enums must also specify their storage type so they can be forward declared." % type_name)
462
463         self.types.append(_type)
464
465     def parse_input(self, json):
466         check_for_required_properties(['name', 'description', 'queue', 'members'], json, 'input')
467         _input = Input(json['name'], json['description'], json['queue'], json.get('flags', []), json.get('guard'))
468         if isinstance(json['members'], list):
469             for member in json['members']:
470                 check_for_required_properties(['name', 'type'], member, 'member')
471                 _input.members.append(InputMember(member['name'], member['type'], member.get('flags', [])))
472
473         self.inputs.append(_input)
474
475     # Types cannot (yet) reference other types, so we can check references in one pass.
476     def resolve_types(self):
477         for _type in self.types:
478             self.typecheck_type(_type)
479
480         for _input in self.inputs:
481             self.typecheck_input(_input)
482
483     def typecheck_type(self, _type):
484         log.debug("typecheck type " + _type.type_name())
485
486         if _type.type_name() in self.types_by_name:
487             raise TypecheckException("Duplicate type with name: " + _type.type_name())
488
489         self.types_by_name[_type.type_name()] = _type
490
491     def typecheck_input(self, _input):
492         log.debug("typecheck input " + _input.name)
493
494         if _input.name in self.inputs_by_name:
495             raise TypecheckException("Duplicate input with name: " + _input.name)
496
497         seen_members = {}
498
499         for member in _input.members:
500             if member.memberName in seen_members:
501                 raise TypecheckException("Duplicate input member with name: " + member.memberName)
502
503             self.typecheck_input_member(member, _input)
504             seen_members[member.memberName] = member
505
506         self.inputs_by_name[_input.name] = _input
507
508     def typecheck_input_member(self, input_member, _input):
509         log.debug("typecheck member '%s' of '%s'" % (input_member.memberName, _input.name))
510
511         if not input_member.typeName in self.types_by_name:
512             raise TypecheckException("Unknown type '%s' referenced by member '%s' of input '%s'" % (input_member.typeName, input_member.memberName, _input.name))
513
514
515 # A writer that only updates file if it actually changed.
516 class IncrementalFileWriter:
517     def __init__(self, filepath, force_output):
518         self._filepath = filepath
519         self._output = ""
520         self.force_output = force_output
521
522     def write(self, text):
523         self._output += text
524
525     def close(self):
526         text_changed = True
527         self._output = self._output.rstrip() + "\n"
528
529         try:
530             read_file = open(self._filepath, "r")
531             old_text = read_file.read()
532             read_file.close()
533             text_changed = old_text != self._output
534         except:
535             # Ignore, just overwrite by default
536             pass
537
538         if text_changed or self.force_output:
539             out_file = open(self._filepath, "w")
540             out_file.write(self._output)
541             out_file.close()
542
543
544 def wrap_with_guard(contents, condition=None):
545     if condition is None:
546         return contents
547
548     return "\n".join([
549         "#if %s" % condition,
550         contents,
551         "#endif // %s" % condition
552     ])
553
554
555 class Generator:
556     def __init__(self, model, target_framework_name, input_filepath, output_prefix):
557         self._model = model
558         self.target_framework = Framework.fromString(target_framework_name)
559         self.traits_framework = Framework.fromString(self.setting('traitsFrameworkName'))
560         self._input_filepath = input_filepath
561         self._output_prefix = output_prefix
562
563     def setting(self, key, default=''):
564         return self.target_framework.setting(key, GLOBAL_CONFIG.get(key, default))
565
566     # This does not account for any filename mangling performed on behalf of the test harness.
567     def output_filename(self, extension=None):
568         components = []
569         if len(self._output_prefix) > 0:
570             components.extend([self._output_prefix, '-'])
571
572         components.extend([self.setting('prefix'), self.setting('baseFilename')])
573
574         if extension is not None:
575             components.extend(['.', extension])
576
577         return "".join(components)
578
579     def write_output_files(self, _dir, force=False):
580         header_file = IncrementalFileWriter(os.path.join(_dir, self.output_filename('h')), force)
581         implementation_file = IncrementalFileWriter(os.path.join(_dir, self.output_filename('cpp')), force)
582
583         header_file.write(self.generate_header())
584         implementation_file.write(self.generate_implementation())
585
586         header_file.close()
587         implementation_file.close()
588
589     def generate_header(self):
590         template_arguments = {
591             'licenseBlock': self.generate_license(),
592             'headerGuard': re.sub('[-./]', '_', self.output_filename() + ".h"),
593             'filename': self.output_filename(),
594             'guardCondition': self.setting('guardCondition'),
595             'traitsNamespace': self.traits_framework.setting('namespace'),
596             'inputsNamespace': self.target_framework.setting('namespace'),
597             'includes': self.generate_includes(defaults=self.setting('headerIncludes')),
598             'typeForwardDeclarations': self.generate_type_forward_declarations(),
599             'inputForwardDeclarations': "\n".join([wrap_with_guard("class %s;", _input.guard) % _input.name for _input in self._model.inputs]),
600             'inputClassDeclarations': "\n\n".join([self.generate_class_declaration(_input) for _input in self._model.inputs]),
601             'inputTraitDeclarations': "\n\n".join([self.generate_input_trait_declaration(_input) for _input in self._model.inputs]),
602             'enumTraitDeclarations': "\n\n".join([wrap_with_guard(self.generate_enum_trait_declaration(_type), _type.guard) for _type in self._model.enum_types()]),
603             'forEachMacro': self.generate_for_each_macro(),
604         }
605
606         return Template(Templates.HeaderSkeleton).substitute(template_arguments)
607
608     def generate_implementation(self):
609         template_arguments = {
610             'licenseBlock': self.generate_license(),
611             'filename': self.output_filename(),
612             'guardCondition': self.setting('guardCondition'),
613             'traitsNamespace': self.traits_framework.setting('namespace'),
614             'inputsNamespace': self.target_framework.setting('namespace'),
615             'includes': self.generate_includes(defaults=self.setting('implIncludes'), includes_for_types=True),
616             'inputClassImplementations': "\n\n".join([self.generate_class_implementation(_input) for _input in self._model.inputs]),
617             'inputTraitImplementations': "\n\n".join([self.generate_input_trait_implementation(_input) for _input in self._model.inputs]),
618             'enumTraitImplementations': "\n\n".join([wrap_with_guard(self.generate_enum_trait_implementation(_type), _type.guard) for _type in self._model.enum_types()]),
619         }
620
621         return Template(Templates.ImplementationSkeleton).substitute(template_arguments)
622
623     def generate_license(self):
624         return Template(Templates.CopyrightBlock).substitute(None, inputFilename=os.path.basename(self._input_filepath))
625
626     def generate_includes(self, defaults=[], includes_for_types=False):
627         lines = set()
628
629         for _type in self._model.types:
630             # Types in the "global" framework are implicitly declared and available in all namespaces.
631             if _type.framework is Frameworks.Global:
632                 continue
633             # For RefCounted types, we reverse when to include the header so that the destructor can be
634             # used in the header file.
635             include_for_destructor = _type.mode is TypeModes.SHARED
636             # Enums within classes cannot be forward declared, so we include
637             # headers with the relevant class declaration.
638             include_for_enclosing_class = _type.is_enum() and _type.enclosing_class is not None
639             # Include headers for types like URL and String which are copied, not owned or shared.
640             include_for_copyable_member = _type.mode is TypeModes.HEAVY_SCALAR
641             if (not includes_for_types) ^ (include_for_destructor or include_for_enclosing_class or include_for_copyable_member):
642                 continue
643
644             if self.target_framework != _type.framework:
645                 lines.add("#include <%s>" % _type.header)
646             else:
647                 lines.add("#include \"%s\"" % os.path.basename(_type.header))
648
649         for entry in defaults:
650             (allowed_framework_names, data) = entry
651             (framework_name, header_path) = data
652
653             if self.target_framework.name not in allowed_framework_names:
654                 continue
655             if self.target_framework.name != framework_name:
656                 lines.add("#include <%s>" % header_path)
657             else:
658                 lines.add("#include \"%s\"" % os.path.basename(header_path))
659
660         return "\n".join(sorted(list(lines)))
661
662     def generate_type_forward_declarations(self):
663         lines = []
664
665         decls_by_framework = {}
666         frameworks = [Framework.fromString(s) for s in FRAMEWORK_CONFIG_MAP.keys() if s != Frameworks.Global.name]
667         for framework in frameworks:
668             decls_by_framework[framework] = []
669
670         for _type in self._model.types:
671             if _type.framework not in frameworks:
672                 continue
673             if _type.enclosing_class is not None:
674                 continue
675             if _type.mode == TypeModes.HEAVY_SCALAR:
676                 continue
677             if _type.mode == TypeModes.SCALAR and not (_type.is_enum() or _type.is_enum_class()):
678                 continue
679             if _type.is_enum():
680                 declaration = "enum %s : %s;" % (_type.type_name(), _type.underlying_storage)
681             else:
682                 declaration = "%s %s;" % (_type.declaration_kind(), _type.type_name())
683             decls_by_framework[_type.framework].append(declaration)
684
685         # Declare all namespaces explicitly, even if it's the main namespace.
686         for framework in frameworks:
687             if len(decls_by_framework[framework]) == 0:
688                 continue
689
690             decls_by_framework[framework].sort()
691             lines.append("namespace %s {" % framework.setting('namespace'))
692             lines.extend(decls_by_framework[framework])
693             lines.append("}")
694             lines.append("")
695
696         return "\n".join(lines)
697
698     def generate_class_declaration(self, _input):
699         extra_declarations = []
700         if _input.queue == InputQueues.EVENT_LOOP:
701             extra_declarations.extend([
702                 "",
703                 "    // EventLoopInput API",
704                 "    virtual void dispatch(ReplayController&) override final;",
705             ])
706
707             if _input.setting('CREATE_FROM_PAGE'):
708                 extra_declarations.extend([
709                     "    static std::unique_ptr<%s> createFromPage(const Page&);" % _input.name
710                 ])
711
712         member_getters = [self.generate_input_member_getter(_member) for _member in _input.members]
713
714         member_declarations = [self.generate_input_member_declaration(_member) for _member in _input.members]
715         if len(member_declarations) > 0:
716             member_declarations.insert(0, "private:")
717
718         template_arguments = {
719             'inputConstructor': self.generate_input_constructor_declaration(_input),
720             'inputDestructor': self.generate_input_destructor_declaration(_input),
721             'inputName': _input.name,
722             'inputQueue': _input.setting('enumValue'),
723             'baseClass': _input.setting('baseClass') % _input.name,
724             'extraDeclarations': "\n".join(extra_declarations),
725             'memberGetters': "\n".join(member_getters),
726             'memberDeclarations': "\n".join(member_declarations),
727         }
728
729         return wrap_with_guard(Template(Templates.InputClassDeclaration).substitute(template_arguments), _input.guard)
730
731     def generate_input_constructor_declaration(self, _input):
732         formals_list = self.generate_constructor_formals_list(_input)
733         terms = []
734         if self.setting('exportMacro'):
735             terms.append(self.setting('exportMacro'))
736         terms.append("%s(%s)" % (_input.name, formals_list))
737         return "    %s;" % " ".join(terms)
738
739     def generate_input_destructor_declaration(self, _input):
740         return "    virtual ~%s();" % _input.name
741
742     def generate_input_member_getter(self, _member):
743         member_type = self._model.get_type_for_member(_member)
744         return "    %s %s() const { return %s; }" % (member_type.borrow_type(), _member.memberName, self.generate_member_borrow_expression(_member))
745
746     def generate_input_member_declaration(self, _member):
747         member_type = self._model.get_type_for_member(_member)
748         return "    %s m_%s;" % (member_type.storage_type(), _member.memberName)
749
750     def generate_input_member_tuples(self, _input):
751         return [(_member, self._model.get_type_for_member(_member)) for _member in _input.members]
752
753     def qualified_input_name(self, _input):
754         if self.target_framework == self.traits_framework:
755             return _input.name
756         else:
757             return "%s::%s" % (self.target_framework.setting('namespace'), _input.name)
758
759     def generate_input_trait_declaration(self, _input):
760         decl_type = ['struct']
761         if len(self.setting('exportMacro')) > 0:
762             decl_type.append(self.setting('exportMacro'))
763
764         template_arguments = {
765             'structOrClass': " ".join(decl_type),
766             'queueType': _input.queue.setting('enumValue'),
767             'qualifiedInputName': self.qualified_input_name(_input),
768         }
769
770         return wrap_with_guard(Template(Templates.InputTraitsDeclaration).substitute(template_arguments), _input.guard)
771
772     def generate_enum_trait_declaration(self, _type):
773         should_qualify_type = _type.framework != self.traits_framework
774         template = Templates.EnumTraitDeclaration if _type.is_enum() else Templates.EnumClassTraitDeclaration
775         template_arguments = {
776             'enumName': _type.type_name(qualified=should_qualify_type),
777         }
778         return Template(template).substitute(template_arguments)
779
780     def generate_for_each_macro(self):
781         macro_name = "%s_REPLAY_INPUT_NAMES_FOR_EACH" % self.setting('prefix').upper()
782         lines = []
783         lines.append("#define %s(macro) \\" % macro_name)
784         lines.extend(["    macro(%s) \\" % _input.name for _input in self._model.inputs])
785         lines.append("    \\")
786         lines.append("// end of %s" % macro_name)
787         return "\n".join(lines)
788
789     def generate_class_implementation(self, _input):
790         template_arguments = {
791             'inputName': _input.name,
792             'inputsNamespace': self.target_framework.setting('namespace'),
793             'initializerList': self.generate_constructor_initializer_list(_input),
794             'constructorFormalsList': self.generate_constructor_formals_list(_input),
795         }
796
797         return wrap_with_guard(Template(Templates.InputClassImplementation).substitute(template_arguments), _input.guard)
798
799     def generate_enum_trait_implementation(self, _type):
800         should_qualify_type = _type.framework != self.traits_framework
801         prefix_components = []
802         if should_qualify_type:
803             prefix_components.append(_type.framework.setting('namespace'))
804         if _type.is_enum_class():
805             prefix_components.append(_type.type_name())
806         if _type.enclosing_class is not None:
807             prefix_components.append(_type.enclosing_class)
808         prefix_components.append("")
809         enum_prefix = "::".join(prefix_components)
810         encodeLines = []
811
812         if _type.is_enum():
813             encode_template = Templates.EnumEncodeCase
814             decode_template = Templates.EnumDecodeCase
815             enum_trait_template = Templates.EnumTraitImplementation
816         else:
817             encode_template = Templates.EnumClassEncodeCase
818             decode_template = Templates.EnumClassDecodeCase
819             enum_trait_template = Templates.EnumClassTraitImplementation
820
821         # Generate body for encode.
822         for _value in _type.values:
823             template_arguments = {
824                 'enumStringValue': _value,
825                 'qualifiedEnumValue': "%s%s" % (enum_prefix, _value),
826             }
827             encodeLines.append(Template(encode_template).substitute(template_arguments))
828
829         for guard, guard_values in _type.guard_values_map.iteritems():
830             guardedLines = []
831             for guard_value in guard_values:
832                 template_arguments = {
833                     'enumStringValue': guard_value,
834                     'qualifiedEnumValue': "%s%s" % (enum_prefix, guard_value),
835                 }
836                 guardedLines.append(Template(encode_template).substitute(template_arguments))
837             encodeLines.append(wrap_with_guard("\n".join(guardedLines), guard))
838
839         # Generate body for decode.
840         decodeLines = []
841         for _value in _type.values:
842             template_arguments = {
843                 'enumStringValue': _value,
844                 'qualifiedEnumValue': "%s%s" % (enum_prefix, _value),
845                 'qualifiedEnumName': _type.type_name(qualified=should_qualify_type)
846             }
847             decodeLines.append(Template(decode_template).substitute(template_arguments))
848
849         for guard, guard_values in _type.guard_values_map.iteritems():
850             guardedLines = []
851             for guard_value in guard_values:
852                 template_arguments = {
853                     'enumStringValue': guard_value,
854                     'qualifiedEnumValue': "%s%s" % (enum_prefix, guard_value),
855                     'qualifiedEnumName': _type.type_name(qualified=should_qualify_type)
856                 }
857                 guardedLines.append(Template(decode_template).substitute(template_arguments))
858             decodeLines.append(wrap_with_guard("\n".join(guardedLines), guard))
859
860         template_arguments = {
861             'enumName': _type.type_name(qualified=should_qualify_type),
862             'encodeCases': "\n".join(encodeLines),
863             'decodeCases': "\n".join(decodeLines)
864         }
865
866         return Template(enum_trait_template).substitute(template_arguments)
867
868     def generate_input_trait_implementation(self, _input):
869         template_arguments = {
870             'inputsNamespace': self.target_framework.setting('namespace'),
871             'inputTypeImplementation': Template(self.setting('inputTypeTemplate')).substitute(None, inputName=_input.name),
872             'qualifiedInputName': self.qualified_input_name(_input),
873             'constructorArguments': self.generate_constructor_arguments_list(_input),
874             'constructorFormalsList': self.generate_constructor_formals_list(_input),
875             'encodeSteps': self.generate_input_encode_implementation(_input),
876             'decodeSteps': self.generate_input_decode_implementation(_input),
877         }
878         return wrap_with_guard(Template(Templates.InputTraitsImplementation).substitute(template_arguments), _input.guard)
879
880     def generate_input_encode_implementation(self, _input):
881         steps = []
882         for (_member, _type) in self.generate_input_member_tuples(_input):
883             should_qualify_type = _type.framework != self.traits_framework
884             put_method = "put<%s>" % _type.type_name(qualified=should_qualify_type)
885
886             steps.extend([
887                 "    encodedValue.%s(ASCIILiteral(\"%s\"), input.%s());" % (put_method, _member.memberName, _member.memberName)
888             ])
889
890         if len(steps) == 0:
891             steps.extend([
892                 "    UNUSED_PARAM(encodedValue);",
893                 "    UNUSED_PARAM(input);",
894             ])
895
896         return "\n".join(steps)
897
898     def generate_input_decode_implementation(self, _input):
899         steps = []
900         for (_member, _type) in self.generate_input_member_tuples(_input):
901             should_qualify_type = _type.framework != self.traits_framework
902             get_method = "get<%s>" % _type.type_name(qualified=should_qualify_type)
903
904             lines = [
905                 "    %s %s;" % (_type.storage_type(qualified=should_qualify_type), _member.memberName),
906                 "    if (!encodedValue.%s(ASCIILiteral(\"%s\"), %s))" % (get_method, _member.memberName, _member.memberName),
907                 "        return false;",
908                 ""
909             ]
910
911             steps.append("\n".join(lines))
912
913         if len(steps) == 0:
914             steps.extend([
915                 "    UNUSED_PARAM(encodedValue);",
916             ])
917
918         return "\n".join(steps)
919
920     def generate_constructor_initializer_list(self, _input):
921         initializers = []
922         initializers.append("    : %s()" % (_input.setting('baseClass') % _input.name))
923         for _member in _input.members:
924             initializers.append("    , m_%s(%s)" % (_member.memberName, self.generate_member_move_expression(_member)))
925
926         return "\n".join(initializers)
927
928     def generate_constructor_formals_list(self, _input):
929         member_tuples = self.generate_input_member_tuples(_input)
930         return ", ".join(["%s %s" % (_type.argument_type(), _member.memberName) for (_member, _type) in member_tuples])
931
932     def generate_member_borrow_expression(self, _member):
933         _type = self._model.get_type_for_member(_member)
934         expression = "m_%s" % _member.memberName
935         if _type.mode == TypeModes.OWNED:
936             expression = "*" + expression
937
938         return expression
939
940     def generate_member_move_expression(self, _member):
941         _type = self._model.get_type_for_member(_member)
942         if _type.mode == TypeModes.OWNED:
943             return "std::move(%s)" % _member.memberName
944         else:
945             return _member.memberName
946
947     def generate_constructor_arguments_list(self, _input):
948         return ", ".join([self.generate_member_move_expression(_member) for _member in _input.members])
949
950
951 def generate_from_specification(input_filepath=None, output_prefix="", output_dirpath=None, framework_name=None, force_output=False):
952     try:
953         with open(input_filepath, "r") as input_file:
954             parsed_json = json.load(input_file)
955     except ValueError as e:
956         raise Exception("Error parsing valid JSON in file: " + input_filepath)
957
958     if not framework_name in FRAMEWORK_CONFIG_MAP:
959         raise ParseException("Unknown or unsupported framework name supplied: " + framework_name)
960
961     model = InputsModel(parsed_json)
962     model.resolve_types()
963     generator = Generator(model, framework_name, input_filepath, output_prefix)
964
965     generator.write_output_files(output_dirpath, force_output)
966
967
968 if __name__ == '__main__':
969     allowed_framework_names = FRAMEWORK_CONFIG_MAP.keys()
970
971     cli_parser = optparse.OptionParser(usage="usage: %prog [options] <Inputs.json>")
972     cli_parser.add_option("-o", "--outputDir", help="Directory where generated files should be written.")
973     cli_parser.add_option("--framework", type="choice", choices=allowed_framework_names, help="The framework these inputs belong to.")  # JavaScriptCore, WebCore
974     cli_parser.add_option("--force", action="store_true", help="Force output of generated scripts, even if nothing changed.")
975     cli_parser.add_option("-v", "--debug", action="store_true", help="Log extra output for debugging the generator itself.")
976     cli_parser.add_option("-t", "--test", action="store_true", help="Enable test mode. Use unique output filenames created by prepending the input filename.")
977
978     options = None
979
980     arg_options, arg_values = cli_parser.parse_args()
981     if (len(arg_values) < 1):
982         raise ParseException("At least one plain argument expected")
983
984     if not arg_options.outputDir:
985         raise ParseException("Missing output directory")
986
987     if arg_options.debug:
988         log.setLevel(logging.DEBUG)
989
990     options = {
991         'input_filepath': arg_values[0],
992         'output_dirpath': arg_options.outputDir,
993         'output_prefix': os.path.basename(arg_values[0]) if arg_options.test else "",
994         'framework_name': arg_options.framework,
995         'force_output': arg_options.force
996     }
997
998     try:
999         generate_from_specification(**options)
1000     except (ParseException, TypecheckException) as e:
1001         if arg_options.test:
1002             log.error(e.message)
1003         else:
1004             raise e  # Force the build to fail.