cd65108e16c2eab79ee09c7e26d5a332184cf13b
[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 or _type.mode is TypeModes.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.SCALAR or _type.mode == TypeModes.HEAVY_SCALAR:
669                 continue
670             if _type.is_enum():
671                 declaration = "enum %s : %s;" % (_type.type_name(), _type.underlying_storage)
672             else:
673                 declaration = "%s %s;" % (_type.declaration_kind(), _type.type_name())
674             decls_by_framework[_type.framework].append(declaration)
675
676         # Declare all namespaces explicitly, even if it's the main namespace.
677         for framework in frameworks:
678             if len(decls_by_framework[framework]) == 0:
679                 continue
680
681             decls_by_framework[framework].sort()
682             lines.append("namespace %s {" % framework.setting('namespace'))
683             lines.extend(decls_by_framework[framework])
684             lines.append("}")
685             lines.append("")
686
687         return "\n".join(lines)
688
689     def generate_class_declaration(self, _input):
690         extra_declarations = []
691         if _input.queue == InputQueues.EVENT_LOOP:
692             extra_declarations.extend([
693                 "",
694                 "    // EventLoopInput API",
695                 "    virtual void dispatch(ReplayController&) override final;",
696             ])
697
698             if _input.setting('CREATE_FROM_PAGE'):
699                 extra_declarations.extend([
700                     "    static std::unique_ptr<%s> createFromPage(const Page&);" % _input.name
701                 ])
702
703         member_getters = [self.generate_input_member_getter(_member) for _member in _input.members]
704
705         member_declarations = [self.generate_input_member_declaration(_member) for _member in _input.members]
706         if len(member_declarations) > 0:
707             member_declarations.insert(0, "private:")
708
709         template_arguments = {
710             'inputConstructor': self.generate_input_constructor_declaration(_input),
711             'inputDestructor': self.generate_input_destructor_declaration(_input),
712             'inputName': _input.name,
713             'inputQueue': _input.setting('enumValue'),
714             'baseClass': _input.setting('baseClass') % _input.name,
715             'extraDeclarations': "\n".join(extra_declarations),
716             'memberGetters': "\n".join(member_getters),
717             'memberDeclarations': "\n".join(member_declarations),
718         }
719
720         return wrap_with_guard(Template(Templates.InputClassDeclaration).substitute(template_arguments), _input.guard)
721
722     def generate_input_constructor_declaration(self, _input):
723         formals_list = self.generate_constructor_formals_list(_input)
724         terms = []
725         if self.setting('exportMacro'):
726             terms.append(self.setting('exportMacro'))
727         terms.append("%s(%s)" % (_input.name, formals_list))
728         return "    %s;" % " ".join(terms)
729
730     def generate_input_destructor_declaration(self, _input):
731         return "    virtual ~%s();" % _input.name
732
733     def generate_input_member_getter(self, _member):
734         member_type = self._model.get_type_for_member(_member)
735         return "    %s %s() const { return %s; }" % (member_type.borrow_type(), _member.memberName, self.generate_member_borrow_expression(_member))
736
737     def generate_input_member_declaration(self, _member):
738         member_type = self._model.get_type_for_member(_member)
739         return "    %s m_%s;" % (member_type.storage_type(), _member.memberName)
740
741     def generate_input_member_tuples(self, _input):
742         return [(_member, self._model.get_type_for_member(_member)) for _member in _input.members]
743
744     def qualified_input_name(self, _input):
745         if self.target_framework == self.traits_framework:
746             return _input.name
747         else:
748             return "%s::%s" % (self.target_framework.setting('namespace'), _input.name)
749
750     def generate_input_trait_declaration(self, _input):
751         decl_type = ['struct']
752         if len(self.setting('exportMacro')) > 0:
753             decl_type.append(self.setting('exportMacro'))
754
755         template_arguments = {
756             'structOrClass': " ".join(decl_type),
757             'queueType': _input.queue.setting('enumValue'),
758             'qualifiedInputName': self.qualified_input_name(_input),
759         }
760
761         return wrap_with_guard(Template(Templates.InputTraitsDeclaration).substitute(template_arguments), _input.guard)
762
763     def generate_enum_trait_declaration(self, _type):
764         should_qualify_type = _type.framework != self.traits_framework
765         template = Templates.EnumTraitDeclaration if _type.is_enum() else Templates.EnumClassTraitDeclaration
766         template_arguments = {
767             'enumName': _type.type_name(qualified=should_qualify_type),
768         }
769         return Template(template).substitute(template_arguments)
770
771     def generate_for_each_macro(self):
772         macro_name = "%s_REPLAY_INPUT_NAMES_FOR_EACH" % self.setting('prefix').upper()
773         lines = []
774         lines.append("#define %s(macro) \\" % macro_name)
775         lines.extend(["    macro(%s) \\" % _input.name for _input in self._model.inputs])
776         lines.append("    \\")
777         lines.append("// end of %s" % macro_name)
778         return "\n".join(lines)
779
780     def generate_class_implementation(self, _input):
781         template_arguments = {
782             'inputName': _input.name,
783             'inputsNamespace': self.target_framework.setting('namespace'),
784             'initializerList': self.generate_constructor_initializer_list(_input),
785             'constructorFormalsList': self.generate_constructor_formals_list(_input),
786         }
787
788         return wrap_with_guard(Template(Templates.InputClassImplementation).substitute(template_arguments), _input.guard)
789
790     def generate_enum_trait_implementation(self, _type):
791         should_qualify_type = _type.framework != self.traits_framework
792         prefix_components = []
793         if should_qualify_type:
794             prefix_components.append(_type.framework.setting('namespace'))
795         if _type.is_enum_class():
796             prefix_components.append(_type.type_name())
797         if _type.enclosing_class is not None:
798             prefix_components.append(_type.enclosing_class)
799         prefix_components.append("")
800         enum_prefix = "::".join(prefix_components)
801         encodeLines = []
802
803         if _type.is_enum():
804             encode_template = Templates.EnumEncodeCase
805             decode_template = Templates.EnumDecodeCase
806             enum_trait_template = Templates.EnumTraitImplementation
807         else:
808             encode_template = Templates.EnumClassEncodeCase
809             decode_template = Templates.EnumClassDecodeCase
810             enum_trait_template = Templates.EnumClassTraitImplementation
811
812         # Generate body for encode.
813         for _value in _type.values:
814             template_arguments = {
815                 'enumStringValue': _value,
816                 'qualifiedEnumValue': "%s%s" % (enum_prefix, _value),
817             }
818             encodeLines.append(Template(encode_template).substitute(template_arguments))
819
820         for guard, guard_values in _type.guard_values_map.iteritems():
821             guardedLines = []
822             for guard_value in guard_values:
823                 template_arguments = {
824                     'enumStringValue': guard_value,
825                     'qualifiedEnumValue': "%s%s" % (enum_prefix, guard_value),
826                 }
827                 guardedLines.append(Template(encode_template).substitute(template_arguments))
828             encodeLines.append(wrap_with_guard("\n".join(guardedLines), guard))
829
830         # Generate body for decode.
831         decodeLines = []
832         for _value in _type.values:
833             template_arguments = {
834                 'enumStringValue': _value,
835                 'qualifiedEnumValue': "%s%s" % (enum_prefix, _value),
836                 'qualifiedEnumName': _type.type_name(qualified=should_qualify_type)
837             }
838             decodeLines.append(Template(decode_template).substitute(template_arguments))
839
840         for guard, guard_values in _type.guard_values_map.iteritems():
841             guardedLines = []
842             for guard_value in guard_values:
843                 template_arguments = {
844                     'enumStringValue': guard_value,
845                     'qualifiedEnumValue': "%s%s" % (enum_prefix, guard_value),
846                     'qualifiedEnumName': _type.type_name(qualified=should_qualify_type)
847                 }
848                 guardedLines.append(Template(decode_template).substitute(template_arguments))
849             decodeLines.append(wrap_with_guard("\n".join(guardedLines), guard))
850
851         template_arguments = {
852             'enumName': _type.type_name(qualified=should_qualify_type),
853             'encodeCases': "\n".join(encodeLines),
854             'decodeCases': "\n".join(decodeLines)
855         }
856
857         return Template(enum_trait_template).substitute(template_arguments)
858
859     def generate_input_trait_implementation(self, _input):
860         template_arguments = {
861             'inputsNamespace': self.target_framework.setting('namespace'),
862             'inputTypeImplementation': Template(self.setting('inputTypeTemplate')).substitute(None, inputName=_input.name),
863             'qualifiedInputName': self.qualified_input_name(_input),
864             'constructorArguments': self.generate_constructor_arguments_list(_input),
865             'constructorFormalsList': self.generate_constructor_formals_list(_input),
866             'encodeSteps': self.generate_input_encode_implementation(_input),
867             'decodeSteps': self.generate_input_decode_implementation(_input),
868         }
869         return wrap_with_guard(Template(Templates.InputTraitsImplementation).substitute(template_arguments), _input.guard)
870
871     def generate_input_encode_implementation(self, _input):
872         steps = []
873         for (_member, _type) in self.generate_input_member_tuples(_input):
874             should_qualify_type = _type.framework != self.traits_framework
875             put_method = "put<%s>" % _type.type_name(qualified=should_qualify_type)
876
877             steps.extend([
878                 "    encodedValue.%s(ASCIILiteral(\"%s\"), input.%s());" % (put_method, _member.memberName, _member.memberName)
879             ])
880
881         if len(steps) == 0:
882             steps.extend([
883                 "    UNUSED_PARAM(encodedValue);",
884                 "    UNUSED_PARAM(input);",
885             ])
886
887         return "\n".join(steps)
888
889     def generate_input_decode_implementation(self, _input):
890         steps = []
891         for (_member, _type) in self.generate_input_member_tuples(_input):
892             should_qualify_type = _type.framework != self.traits_framework
893             get_method = "get<%s>" % _type.type_name(qualified=should_qualify_type)
894
895             lines = [
896                 "    %s %s;" % (_type.storage_type(qualified=should_qualify_type), _member.memberName),
897                 "    if (!encodedValue.%s(ASCIILiteral(\"%s\"), %s))" % (get_method, _member.memberName, _member.memberName),
898                 "        return false;",
899                 ""
900             ]
901
902             steps.append("\n".join(lines))
903
904         if len(steps) == 0:
905             steps.extend([
906                 "    UNUSED_PARAM(encodedValue);",
907             ])
908
909         return "\n".join(steps)
910
911     def generate_constructor_initializer_list(self, _input):
912         initializers = []
913         initializers.append("    : %s()" % (_input.setting('baseClass') % _input.name))
914         for _member in _input.members:
915             initializers.append("    , m_%s(%s)" % (_member.memberName, self.generate_member_move_expression(_member)))
916
917         return "\n".join(initializers)
918
919     def generate_constructor_formals_list(self, _input):
920         member_tuples = self.generate_input_member_tuples(_input)
921         return ", ".join(["%s %s" % (_type.argument_type(), _member.memberName) for (_member, _type) in member_tuples])
922
923     def generate_member_borrow_expression(self, _member):
924         _type = self._model.get_type_for_member(_member)
925         expression = "m_%s" % _member.memberName
926         if _type.mode == TypeModes.OWNED:
927             expression = "*" + expression
928
929         return expression
930
931     def generate_member_move_expression(self, _member):
932         _type = self._model.get_type_for_member(_member)
933         if _type.mode == TypeModes.OWNED:
934             return "std::move(%s)" % _member.memberName
935         else:
936             return _member.memberName
937
938     def generate_constructor_arguments_list(self, _input):
939         return ", ".join([self.generate_member_move_expression(_member) for _member in _input.members])
940
941
942 def generate_from_specification(input_filepath=None, output_prefix="", output_dirpath=None, framework_name=None, force_output=False):
943     try:
944         with open(input_filepath, "r") as input_file:
945             parsed_json = json.load(input_file)
946     except ValueError as e:
947         raise Exception("Error parsing valid JSON in file: " + input_filepath)
948
949     if not framework_name in FRAMEWORK_CONFIG_MAP:
950         raise ParseException("Unknown or unsupported framework name supplied: " + framework_name)
951
952     model = InputsModel(parsed_json)
953     model.resolve_types()
954     generator = Generator(model, framework_name, input_filepath, output_prefix)
955
956     generator.write_output_files(output_dirpath, force_output)
957
958
959 if __name__ == '__main__':
960     allowed_framework_names = FRAMEWORK_CONFIG_MAP.keys()
961
962     cli_parser = optparse.OptionParser(usage="usage: %prog [options] <Inputs.json>")
963     cli_parser.add_option("-o", "--outputDir", help="Directory where generated files should be written.")
964     cli_parser.add_option("--framework", type="choice", choices=allowed_framework_names, help="The framework these inputs belong to.")  # JavaScriptCore, WebCore
965     cli_parser.add_option("--force", action="store_true", help="Force output of generated scripts, even if nothing changed.")
966     cli_parser.add_option("-v", "--debug", action="store_true", help="Log extra output for debugging the generator itself.")
967     cli_parser.add_option("-t", "--test", action="store_true", help="Enable test mode. Use unique output filenames created by prepending the input filename.")
968
969     options = None
970
971     arg_options, arg_values = cli_parser.parse_args()
972     if (len(arg_values) < 1):
973         raise ParseException("At least one plain argument expected")
974
975     if not arg_options.outputDir:
976         raise ParseException("Missing output directory")
977
978     if arg_options.debug:
979         log.setLevel(logging.DEBUG)
980
981     options = {
982         'input_filepath': arg_values[0],
983         'output_dirpath': arg_options.outputDir,
984         'output_prefix': os.path.basename(arg_values[0]) if arg_options.test else "",
985         'framework_name': arg_options.framework,
986         'force_output': arg_options.force
987     }
988
989     try:
990         generate_from_specification(**options)
991     except (ParseException, TypecheckException) as e:
992         if arg_options.test:
993             log.error(e.message)
994         else:
995             raise e  # Force the build to fail.