Web Replay: add infrastructure for memoizing nondeterministic DOM APIs
[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):
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
295     def __eq__(self, other):
296         return self.type_name() == other.type_name() and self.mode == other.mode
297
298     def __hash__(self):
299         return self._name.__hash__()
300
301     def has_flag(self, flagString):
302         return flagString in self._flags
303
304     def is_struct(self):
305         return self.has_flag("STRUCT")
306
307     def is_enum(self):
308         return self.has_flag("ENUM")
309
310     def is_enum_class(self):
311         return self.has_flag("ENUM_CLASS")
312
313     def declaration_kind(self):
314         if self.is_enum():
315             return "enum"
316         elif self.is_enum_class():
317             return "enum class"
318         elif self.is_struct():
319             return "struct"
320         else:
321             return "class"
322
323     def qualified_prefix(self):
324         components = []
325         if self.framework != Frameworks.Global:
326             components.append(self.framework.setting('namespace'))
327         if self.enclosing_class is not None:
328             components.append(self.enclosing_class)
329         components.append("")
330         return "::".join(components)
331
332     def type_name(self, qualified=False):
333         return "%s%s" % (self.qualified_prefix(), self._name) if qualified else self._name
334
335     def storage_type(self, qualified=False):
336         if self.mode == TypeModes.OWNED:
337             return "std::unique_ptr<%s>" % self.type_name(qualified)
338         elif self.mode == TypeModes.SHARED:
339             return "RefPtr<%s>" % self.type_name(qualified)
340         else:
341             return self.type_name(qualified)
342
343     def borrow_type(self, qualified=False):
344         if self.mode == TypeModes.SCALAR:
345             return self.type_name(qualified)
346         elif self.mode == TypeModes.SHARED:
347             return "PassRefPtr<%s>" % self.type_name(qualified)
348         else:
349             return "const %s&" % self.type_name(qualified)
350
351     def argument_type(self, qualified=False):
352         if self.mode == TypeModes.SHARED:
353             return "PassRefPtr<%s>" % self.type_name(qualified)
354         else:
355             return self.storage_type()
356
357
358 def check_for_required_properties(props, obj, what):
359     for prop in props:
360         if prop not in obj:
361             raise ParseException("When parsing %s, required property missing: %s" % (what, prop))
362
363
364 class VectorType(Type):
365     def __init__(self, element_type):
366         self._element_type = element_type
367         self.mode = TypeModes.VECTOR
368         self.framework = element_type.framework
369         self.enclosing_class = None
370
371     def has_flag(self):
372         return False
373
374     def is_struct(self):
375         return False
376
377     def is_enum(self):
378         return False
379
380     def is_enum_class(self):
381         return False
382
383     def qualified_prefix(self):
384         return ""
385
386     def type_name(self, qualified=False):
387         return "Vector<%s>" % self._element_type.type_name(qualified=qualified)
388
389     def argument_type(self, qualified=False):
390         return self.type_name(qualified=qualified) + "&"
391
392
393 class InputsModel:
394     def __init__(self, parsed_json):
395         self.inputs = []
396         self.types = []
397
398         # Types have associated frameworks and are in their namespace, but within the specification
399         # file types are in a flat namespace. Types with the same name are not allowed.
400         self.types_by_name = {}
401         self.inputs_by_name = {}
402
403         self.parse_toplevel(parsed_json)
404
405     def enum_types(self):
406         _enums = filter(lambda x: x.is_enum() or x.is_enum_class(), self.types)
407         return sorted(_enums, key=lambda _enum: _enum.type_name())
408
409     def get_type_for_member(self, member):
410         if member.has_flag("VECTOR"):
411             return VectorType(self.types_by_name.get(member.typeName))
412         else:
413             return self.types_by_name.get(member.typeName)
414
415     def parse_toplevel(self, json):
416         check_for_required_properties(['types', 'inputs'], json, 'toplevel')
417         if not isinstance(json['types'], dict):
418             raise ParseException("Malformed specification: types is not a dict of framework->type list")
419
420         if not isinstance(json['inputs'], list):
421             raise ParseException("Malformed specification: inputs is not an array")
422
423         for type_framework_name, type_list in json['types'].iteritems():
424             if not isinstance(type_list, list):
425                 raise ParseException("Malformed specification: type list for framework %s is not a list" % type_framework_name)
426
427             for _type in type_list:
428                 self.parse_type_with_framework_name(_type, type_framework_name)
429
430         for val in json['inputs']:
431             self.parse_input(val)
432
433     def parse_type_with_framework_name(self, json, framework_name):
434         check_for_required_properties(['name', 'mode'], json, 'type')
435         framework = Framework.fromString(framework_name)
436         if framework is not Frameworks.Global:
437             check_for_required_properties(['header'], json, 'non-global type')
438
439         type_name = json['name']
440         type_mode = TypeMode.fromString(json['mode'])
441         header = json.get('header')
442         enclosing_class = json.get('enclosing_class')
443         enum_values = json.get('values')
444         guarded_enum_values = json.get('guarded_values', {})
445         type_storage = json.get('storage')
446         type_flags = json.get('flags', [])
447         _type = Type(type_name, type_mode, framework, header, enclosing_class, enum_values, guarded_enum_values, type_storage, type_flags)
448         if _type.is_enum() or _type.is_enum_class():
449             check_for_required_properties(['values'], json, 'enum')
450             if not isinstance(json['values'], list) or len(_type.values) == 0:
451                 raise ParseException("Malformed specification: enum %s does not supply a list of values" % type_name)
452
453             if _type.is_enum() and "storage" not in json:
454                 raise ParseException("Could not parse enum %s: C-style enums must also specify their storage type so they can be forward declared." % type_name)
455
456         self.types.append(_type)
457
458     def parse_input(self, json):
459         check_for_required_properties(['name', 'description', 'queue', 'members'], json, 'input')
460         _input = Input(json['name'], json['description'], json['queue'], json.get('flags', []), json.get('guard'))
461         if isinstance(json['members'], list):
462             for member in json['members']:
463                 check_for_required_properties(['name', 'type'], member, 'member')
464                 _input.members.append(InputMember(member['name'], member['type'], member.get('flags', [])))
465
466         self.inputs.append(_input)
467
468     # Types cannot (yet) reference other types, so we can check references in one pass.
469     def resolve_types(self):
470         for _type in self.types:
471             self.typecheck_type(_type)
472
473         for _input in self.inputs:
474             self.typecheck_input(_input)
475
476     def typecheck_type(self, _type):
477         log.debug("typecheck type " + _type.type_name())
478
479         if _type.type_name() in self.types_by_name:
480             raise TypecheckException("Duplicate type with name: " + _type.type_name())
481
482         self.types_by_name[_type.type_name()] = _type
483
484     def typecheck_input(self, _input):
485         log.debug("typecheck input " + _input.name)
486
487         if _input.name in self.inputs_by_name:
488             raise TypecheckException("Duplicate input with name: " + _input.name)
489
490         seen_members = {}
491
492         for member in _input.members:
493             if member.memberName in seen_members:
494                 raise TypecheckException("Duplicate input member with name: " + member.memberName)
495
496             self.typecheck_input_member(member, _input)
497             seen_members[member.memberName] = member
498
499         self.inputs_by_name[_input.name] = _input
500
501     def typecheck_input_member(self, input_member, _input):
502         log.debug("typecheck member '%s' of '%s'" % (input_member.memberName, _input.name))
503
504         if not input_member.typeName in self.types_by_name:
505             raise TypecheckException("Unknown type '%s' referenced by member '%s' of input '%s'" % (input_member.typeName, input_member.memberName, _input.name))
506
507
508 # A writer that only updates file if it actually changed.
509 class IncrementalFileWriter:
510     def __init__(self, filepath, force_output):
511         self._filepath = filepath
512         self._output = ""
513         self.force_output = force_output
514
515     def write(self, text):
516         self._output += text
517
518     def close(self):
519         text_changed = True
520         self._output = self._output.rstrip() + "\n"
521
522         try:
523             read_file = open(self._filepath, "r")
524             old_text = read_file.read()
525             read_file.close()
526             text_changed = old_text != self._output
527         except:
528             # Ignore, just overwrite by default
529             pass
530
531         if text_changed or self.force_output:
532             out_file = open(self._filepath, "w")
533             out_file.write(self._output)
534             out_file.close()
535
536
537 def wrap_with_guard(contents, condition=None):
538     if condition is None:
539         return contents
540
541     return "\n".join([
542         "#if %s" % condition,
543         contents,
544         "#endif // %s" % condition
545     ])
546
547
548 class Generator:
549     def __init__(self, model, target_framework_name, input_filepath, output_prefix):
550         self._model = model
551         self.target_framework = Framework.fromString(target_framework_name)
552         self.traits_framework = Framework.fromString(self.setting('traitsFrameworkName'))
553         self._input_filepath = input_filepath
554         self._output_prefix = output_prefix
555
556     def setting(self, key, default=''):
557         return self.target_framework.setting(key, GLOBAL_CONFIG.get(key, default))
558
559     # This does not account for any filename mangling performed on behalf of the test harness.
560     def output_filename(self, extension=None):
561         components = []
562         if len(self._output_prefix) > 0:
563             components.extend([self._output_prefix, '-'])
564
565         components.extend([self.setting('prefix'), self.setting('baseFilename')])
566
567         if extension is not None:
568             components.extend(['.', extension])
569
570         return "".join(components)
571
572     def write_output_files(self, _dir, force=False):
573         header_file = IncrementalFileWriter(os.path.join(_dir, self.output_filename('h')), force)
574         implementation_file = IncrementalFileWriter(os.path.join(_dir, self.output_filename('cpp')), force)
575
576         header_file.write(self.generate_header())
577         implementation_file.write(self.generate_implementation())
578
579         header_file.close()
580         implementation_file.close()
581
582     def generate_header(self):
583         template_arguments = {
584             'licenseBlock': self.generate_license(),
585             'headerGuard': re.sub('[-./]', '_', self.output_filename() + ".h"),
586             'filename': self.output_filename(),
587             'guardCondition': self.setting('guardCondition'),
588             'traitsNamespace': self.traits_framework.setting('namespace'),
589             'inputsNamespace': self.target_framework.setting('namespace'),
590             'includes': self.generate_includes(defaults=self.setting('headerIncludes')),
591             'typeForwardDeclarations': self.generate_type_forward_declarations(),
592             'inputForwardDeclarations': "\n".join([wrap_with_guard("class %s;", _input.guard) % _input.name for _input in self._model.inputs]),
593             'inputClassDeclarations': "\n\n".join([self.generate_class_declaration(_input) for _input in self._model.inputs]),
594             'inputTraitDeclarations': "\n\n".join([self.generate_input_trait_declaration(_input) for _input in self._model.inputs]),
595             'enumTraitDeclarations': "\n\n".join([self.generate_enum_trait_declaration(_type) for _type in self._model.enum_types()]),
596             'forEachMacro': self.generate_for_each_macro(),
597         }
598
599         return Template(Templates.HeaderSkeleton).substitute(template_arguments)
600
601     def generate_implementation(self):
602         template_arguments = {
603             'licenseBlock': self.generate_license(),
604             'filename': self.output_filename(),
605             'guardCondition': self.setting('guardCondition'),
606             'traitsNamespace': self.traits_framework.setting('namespace'),
607             'inputsNamespace': self.target_framework.setting('namespace'),
608             'includes': self.generate_includes(defaults=self.setting('implIncludes'), includes_for_types=True),
609             'inputClassImplementations': "\n\n".join([self.generate_class_implementation(_input) for _input in self._model.inputs]),
610             'inputTraitImplementations': "\n\n".join([self.generate_input_trait_implementation(_input) for _input in self._model.inputs]),
611             'enumTraitImplementations': "\n\n".join([self.generate_enum_trait_implementation(_type) for _type in self._model.enum_types()]),
612         }
613
614         return Template(Templates.ImplementationSkeleton).substitute(template_arguments)
615
616     def generate_license(self):
617         return Template(Templates.CopyrightBlock).substitute(None, inputFilename=os.path.basename(self._input_filepath))
618
619     def generate_includes(self, defaults=[], includes_for_types=False):
620         lines = set()
621
622         for _type in self._model.types:
623             # Types in the "global" framework are implicitly declared and available in all namespaces.
624             if _type.framework is Frameworks.Global:
625                 continue
626             # For RefCounted types, we reverse when to include the header so that the destructor can be
627             # used in the header file.
628             include_for_destructor = _type.mode is TypeModes.SHARED
629             # Enums within classes cannot be forward declared, so we include
630             # headers with the relevant class declaration.
631             include_for_enclosing_class = _type.is_enum() and _type.enclosing_class is not None
632             # Include headers for types like URL and String which are copied, not owned or shared.
633             include_for_copyable_member = _type.mode is TypeModes.HEAVY_SCALAR
634             if (not includes_for_types) ^ (include_for_destructor or include_for_enclosing_class or include_for_copyable_member):
635                 continue
636
637             if self.target_framework != _type.framework:
638                 lines.add("#include <%s>" % _type.header)
639             else:
640                 lines.add("#include \"%s\"" % os.path.basename(_type.header))
641
642         for entry in defaults:
643             (allowed_framework_names, data) = entry
644             (framework_name, header_path) = data
645
646             if self.target_framework.name not in allowed_framework_names:
647                 continue
648             if self.target_framework.name != framework_name:
649                 lines.add("#include <%s>" % header_path)
650             else:
651                 lines.add("#include \"%s\"" % os.path.basename(header_path))
652
653         return "\n".join(sorted(list(lines)))
654
655     def generate_type_forward_declarations(self):
656         lines = []
657
658         decls_by_framework = {}
659         frameworks = [Framework.fromString(s) for s in FRAMEWORK_CONFIG_MAP.keys() if s != Frameworks.Global.name]
660         for framework in frameworks:
661             decls_by_framework[framework] = []
662
663         for _type in self._model.types:
664             if _type.framework not in frameworks:
665                 continue
666             if _type.enclosing_class is not None:
667                 continue
668             if _type.mode == TypeModes.HEAVY_SCALAR:
669                 continue
670             if _type.mode == TypeModes.SCALAR and not (_type.is_enum() or _type.is_enum_class()):
671                 continue
672             if _type.is_enum():
673                 declaration = "enum %s : %s;" % (_type.type_name(), _type.underlying_storage)
674             else:
675                 declaration = "%s %s;" % (_type.declaration_kind(), _type.type_name())
676             decls_by_framework[_type.framework].append(declaration)
677
678         # Declare all namespaces explicitly, even if it's the main namespace.
679         for framework in frameworks:
680             if len(decls_by_framework[framework]) == 0:
681                 continue
682
683             decls_by_framework[framework].sort()
684             lines.append("namespace %s {" % framework.setting('namespace'))
685             lines.extend(decls_by_framework[framework])
686             lines.append("}")
687             lines.append("")
688
689         return "\n".join(lines)
690
691     def generate_class_declaration(self, _input):
692         extra_declarations = []
693         if _input.queue == InputQueues.EVENT_LOOP:
694             extra_declarations.extend([
695                 "",
696                 "    // EventLoopInput API",
697                 "    virtual void dispatch(ReplayController&) override final;",
698             ])
699
700             if _input.setting('CREATE_FROM_PAGE'):
701                 extra_declarations.extend([
702                     "    static std::unique_ptr<%s> createFromPage(const Page&);" % _input.name
703                 ])
704
705         member_getters = [self.generate_input_member_getter(_member) for _member in _input.members]
706
707         member_declarations = [self.generate_input_member_declaration(_member) for _member in _input.members]
708         if len(member_declarations) > 0:
709             member_declarations.insert(0, "private:")
710
711         template_arguments = {
712             'inputConstructor': self.generate_input_constructor_declaration(_input),
713             'inputDestructor': self.generate_input_destructor_declaration(_input),
714             'inputName': _input.name,
715             'inputQueue': _input.setting('enumValue'),
716             'baseClass': _input.setting('baseClass') % _input.name,
717             'extraDeclarations': "\n".join(extra_declarations),
718             'memberGetters': "\n".join(member_getters),
719             'memberDeclarations': "\n".join(member_declarations),
720         }
721
722         return wrap_with_guard(Template(Templates.InputClassDeclaration).substitute(template_arguments), _input.guard)
723
724     def generate_input_constructor_declaration(self, _input):
725         formals_list = self.generate_constructor_formals_list(_input)
726         terms = []
727         if self.setting('exportMacro'):
728             terms.append(self.setting('exportMacro'))
729         terms.append("%s(%s)" % (_input.name, formals_list))
730         return "    %s;" % " ".join(terms)
731
732     def generate_input_destructor_declaration(self, _input):
733         return "    virtual ~%s();" % _input.name
734
735     def generate_input_member_getter(self, _member):
736         member_type = self._model.get_type_for_member(_member)
737         return "    %s %s() const { return %s; }" % (member_type.borrow_type(), _member.memberName, self.generate_member_borrow_expression(_member))
738
739     def generate_input_member_declaration(self, _member):
740         member_type = self._model.get_type_for_member(_member)
741         return "    %s m_%s;" % (member_type.storage_type(), _member.memberName)
742
743     def generate_input_member_tuples(self, _input):
744         return [(_member, self._model.get_type_for_member(_member)) for _member in _input.members]
745
746     def qualified_input_name(self, _input):
747         if self.target_framework == self.traits_framework:
748             return _input.name
749         else:
750             return "%s::%s" % (self.target_framework.setting('namespace'), _input.name)
751
752     def generate_input_trait_declaration(self, _input):
753         decl_type = ['struct']
754         if len(self.setting('exportMacro')) > 0:
755             decl_type.append(self.setting('exportMacro'))
756
757         template_arguments = {
758             'structOrClass': " ".join(decl_type),
759             'queueType': _input.queue.setting('enumValue'),
760             'qualifiedInputName': self.qualified_input_name(_input),
761         }
762
763         return wrap_with_guard(Template(Templates.InputTraitsDeclaration).substitute(template_arguments), _input.guard)
764
765     def generate_enum_trait_declaration(self, _type):
766         should_qualify_type = _type.framework != self.traits_framework
767         template = Templates.EnumTraitDeclaration if _type.is_enum() else Templates.EnumClassTraitDeclaration
768         template_arguments = {
769             'enumName': _type.type_name(qualified=should_qualify_type),
770         }
771         return Template(template).substitute(template_arguments)
772
773     def generate_for_each_macro(self):
774         macro_name = "%s_REPLAY_INPUT_NAMES_FOR_EACH" % self.setting('prefix').upper()
775         lines = []
776         lines.append("#define %s(macro) \\" % macro_name)
777         lines.extend(["    macro(%s) \\" % _input.name for _input in self._model.inputs])
778         lines.append("    \\")
779         lines.append("// end of %s" % macro_name)
780         return "\n".join(lines)
781
782     def generate_class_implementation(self, _input):
783         template_arguments = {
784             'inputName': _input.name,
785             'inputsNamespace': self.target_framework.setting('namespace'),
786             'initializerList': self.generate_constructor_initializer_list(_input),
787             'constructorFormalsList': self.generate_constructor_formals_list(_input),
788         }
789
790         return wrap_with_guard(Template(Templates.InputClassImplementation).substitute(template_arguments), _input.guard)
791
792     def generate_enum_trait_implementation(self, _type):
793         should_qualify_type = _type.framework != self.traits_framework
794         prefix_components = []
795         if should_qualify_type:
796             prefix_components.append(_type.framework.setting('namespace'))
797         if _type.is_enum_class():
798             prefix_components.append(_type.type_name())
799         if _type.enclosing_class is not None:
800             prefix_components.append(_type.enclosing_class)
801         prefix_components.append("")
802         enum_prefix = "::".join(prefix_components)
803         encodeLines = []
804
805         if _type.is_enum():
806             encode_template = Templates.EnumEncodeCase
807             decode_template = Templates.EnumDecodeCase
808             enum_trait_template = Templates.EnumTraitImplementation
809         else:
810             encode_template = Templates.EnumClassEncodeCase
811             decode_template = Templates.EnumClassDecodeCase
812             enum_trait_template = Templates.EnumClassTraitImplementation
813
814         # Generate body for encode.
815         for _value in _type.values:
816             template_arguments = {
817                 'enumStringValue': _value,
818                 'qualifiedEnumValue': "%s%s" % (enum_prefix, _value),
819             }
820             encodeLines.append(Template(encode_template).substitute(template_arguments))
821
822         for guard, guard_values in _type.guard_values_map.iteritems():
823             guardedLines = []
824             for guard_value in guard_values:
825                 template_arguments = {
826                     'enumStringValue': guard_value,
827                     'qualifiedEnumValue': "%s%s" % (enum_prefix, guard_value),
828                 }
829                 guardedLines.append(Template(encode_template).substitute(template_arguments))
830             encodeLines.append(wrap_with_guard("\n".join(guardedLines), guard))
831
832         # Generate body for decode.
833         decodeLines = []
834         for _value in _type.values:
835             template_arguments = {
836                 'enumStringValue': _value,
837                 'qualifiedEnumValue': "%s%s" % (enum_prefix, _value),
838                 'qualifiedEnumName': _type.type_name(qualified=should_qualify_type)
839             }
840             decodeLines.append(Template(decode_template).substitute(template_arguments))
841
842         for guard, guard_values in _type.guard_values_map.iteritems():
843             guardedLines = []
844             for guard_value in guard_values:
845                 template_arguments = {
846                     'enumStringValue': guard_value,
847                     'qualifiedEnumValue': "%s%s" % (enum_prefix, guard_value),
848                     'qualifiedEnumName': _type.type_name(qualified=should_qualify_type)
849                 }
850                 guardedLines.append(Template(decode_template).substitute(template_arguments))
851             decodeLines.append(wrap_with_guard("\n".join(guardedLines), guard))
852
853         template_arguments = {
854             'enumName': _type.type_name(qualified=should_qualify_type),
855             'encodeCases': "\n".join(encodeLines),
856             'decodeCases': "\n".join(decodeLines)
857         }
858
859         return Template(enum_trait_template).substitute(template_arguments)
860
861     def generate_input_trait_implementation(self, _input):
862         template_arguments = {
863             'inputsNamespace': self.target_framework.setting('namespace'),
864             'inputTypeImplementation': Template(self.setting('inputTypeTemplate')).substitute(None, inputName=_input.name),
865             'qualifiedInputName': self.qualified_input_name(_input),
866             'constructorArguments': self.generate_constructor_arguments_list(_input),
867             'constructorFormalsList': self.generate_constructor_formals_list(_input),
868             'encodeSteps': self.generate_input_encode_implementation(_input),
869             'decodeSteps': self.generate_input_decode_implementation(_input),
870         }
871         return wrap_with_guard(Template(Templates.InputTraitsImplementation).substitute(template_arguments), _input.guard)
872
873     def generate_input_encode_implementation(self, _input):
874         steps = []
875         for (_member, _type) in self.generate_input_member_tuples(_input):
876             should_qualify_type = _type.framework != self.traits_framework
877             put_method = "put<%s>" % _type.type_name(qualified=should_qualify_type)
878
879             steps.extend([
880                 "    encodedValue.%s(ASCIILiteral(\"%s\"), input.%s());" % (put_method, _member.memberName, _member.memberName)
881             ])
882
883         if len(steps) == 0:
884             steps.extend([
885                 "    UNUSED_PARAM(encodedValue);",
886                 "    UNUSED_PARAM(input);",
887             ])
888
889         return "\n".join(steps)
890
891     def generate_input_decode_implementation(self, _input):
892         steps = []
893         for (_member, _type) in self.generate_input_member_tuples(_input):
894             should_qualify_type = _type.framework != self.traits_framework
895             get_method = "get<%s>" % _type.type_name(qualified=should_qualify_type)
896
897             lines = [
898                 "    %s %s;" % (_type.storage_type(qualified=should_qualify_type), _member.memberName),
899                 "    if (!encodedValue.%s(ASCIILiteral(\"%s\"), %s))" % (get_method, _member.memberName, _member.memberName),
900                 "        return false;",
901                 ""
902             ]
903
904             steps.append("\n".join(lines))
905
906         if len(steps) == 0:
907             steps.extend([
908                 "    UNUSED_PARAM(encodedValue);",
909             ])
910
911         return "\n".join(steps)
912
913     def generate_constructor_initializer_list(self, _input):
914         initializers = []
915         initializers.append("    : %s()" % (_input.setting('baseClass') % _input.name))
916         for _member in _input.members:
917             initializers.append("    , m_%s(%s)" % (_member.memberName, self.generate_member_move_expression(_member)))
918
919         return "\n".join(initializers)
920
921     def generate_constructor_formals_list(self, _input):
922         member_tuples = self.generate_input_member_tuples(_input)
923         return ", ".join(["%s %s" % (_type.argument_type(), _member.memberName) for (_member, _type) in member_tuples])
924
925     def generate_member_borrow_expression(self, _member):
926         _type = self._model.get_type_for_member(_member)
927         expression = "m_%s" % _member.memberName
928         if _type.mode == TypeModes.OWNED:
929             expression = "*" + expression
930
931         return expression
932
933     def generate_member_move_expression(self, _member):
934         _type = self._model.get_type_for_member(_member)
935         if _type.mode == TypeModes.OWNED:
936             return "std::move(%s)" % _member.memberName
937         else:
938             return _member.memberName
939
940     def generate_constructor_arguments_list(self, _input):
941         return ", ".join([self.generate_member_move_expression(_member) for _member in _input.members])
942
943
944 def generate_from_specification(input_filepath=None, output_prefix="", output_dirpath=None, framework_name=None, force_output=False):
945     try:
946         with open(input_filepath, "r") as input_file:
947             parsed_json = json.load(input_file)
948     except ValueError as e:
949         raise Exception("Error parsing valid JSON in file: " + input_filepath)
950
951     if not framework_name in FRAMEWORK_CONFIG_MAP:
952         raise ParseException("Unknown or unsupported framework name supplied: " + framework_name)
953
954     model = InputsModel(parsed_json)
955     model.resolve_types()
956     generator = Generator(model, framework_name, input_filepath, output_prefix)
957
958     generator.write_output_files(output_dirpath, force_output)
959
960
961 if __name__ == '__main__':
962     allowed_framework_names = FRAMEWORK_CONFIG_MAP.keys()
963
964     cli_parser = optparse.OptionParser(usage="usage: %prog [options] <Inputs.json>")
965     cli_parser.add_option("-o", "--outputDir", help="Directory where generated files should be written.")
966     cli_parser.add_option("--framework", type="choice", choices=allowed_framework_names, help="The framework these inputs belong to.")  # JavaScriptCore, WebCore
967     cli_parser.add_option("--force", action="store_true", help="Force output of generated scripts, even if nothing changed.")
968     cli_parser.add_option("-v", "--debug", action="store_true", help="Log extra output for debugging the generator itself.")
969     cli_parser.add_option("-t", "--test", action="store_true", help="Enable test mode. Use unique output filenames created by prepending the input filename.")
970
971     options = None
972
973     arg_options, arg_values = cli_parser.parse_args()
974     if (len(arg_values) < 1):
975         raise ParseException("At least one plain argument expected")
976
977     if not arg_options.outputDir:
978         raise ParseException("Missing output directory")
979
980     if arg_options.debug:
981         log.setLevel(logging.DEBUG)
982
983     options = {
984         'input_filepath': arg_values[0],
985         'output_dirpath': arg_options.outputDir,
986         'output_prefix': os.path.basename(arg_values[0]) if arg_options.test else "",
987         'framework_name': arg_options.framework,
988         'force_output': arg_options.force
989     }
990
991     try:
992         generate_from_specification(**options)
993     except (ParseException, TypecheckException) as e:
994         if arg_options.test:
995             log.error(e.message)
996         else:
997             raise e  # Force the build to fail.