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
27import os.path
28import re
29import sys
30import string
31from string import Template
32import optparse
33import logging
34from CodeGeneratorReplayInputsTemplates import Templates
35
36try:
37    import json
38except 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.
43GLOBAL_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
100FRAMEWORK_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.
130QUEUE_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.
147logging.basicConfig(format='%(levelname)s: %(message)s', level=logging.ERROR)
148log = logging.getLogger('global')
149
150
151# Model classes, which transliterate JSON input.
152class ParseException(Exception):
153    pass
154
155
156class TypecheckException(Exception):
157    pass
158
159
160class 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
188class Frameworks:
189    Global = Framework("Global")
190    WTF = Framework("WTF")
191    JavaScriptCore = Framework("JavaScriptCore")
192    WebCore = Framework("WebCore")
193    Test = Framework("Test")
194
195
196class 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
217class 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
223class 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
239class 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
249class 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
270class 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
283class 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
364def 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
370class 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
399class 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.
516class 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
544def 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
555class 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 "WTF::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
951def 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
968if __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.
1005