• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python2
2# Copyright 2017 The Dawn Authors
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#     http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16import json, os, sys
17from collections import namedtuple
18
19from generator_lib import Generator, run_generator, FileRender
20
21############################################################
22# OBJECT MODEL
23############################################################
24
25class Name:
26    def __init__(self, name, native=False):
27        self.native = native
28        if native:
29            self.chunks = [name]
30        else:
31            self.chunks = name.split(' ')
32
33    def CamelChunk(self, chunk):
34        return chunk[0].upper() + chunk[1:]
35
36    def canonical_case(self):
37        return (' '.join(self.chunks)).lower()
38
39    def concatcase(self):
40        return ''.join(self.chunks)
41
42    def camelCase(self):
43        return self.chunks[0] + ''.join([self.CamelChunk(chunk) for chunk in self.chunks[1:]])
44
45    def CamelCase(self):
46        return ''.join([self.CamelChunk(chunk) for chunk in self.chunks])
47
48    def SNAKE_CASE(self):
49        return '_'.join([chunk.upper() for chunk in self.chunks])
50
51    def snake_case(self):
52        return '_'.join(self.chunks)
53
54def concat_names(*names):
55    return ' '.join([name.canonical_case() for name in names])
56
57class Type:
58    def __init__(self, name, json_data, native=False):
59        self.json_data = json_data
60        self.dict_name = name
61        self.name = Name(name, native=native)
62        self.category = json_data['category']
63
64EnumValue = namedtuple('EnumValue', ['name', 'value'])
65class EnumType(Type):
66    def __init__(self, name, json_data):
67        Type.__init__(self, name, json_data)
68        self.values = [EnumValue(Name(m['name']), m['value']) for m in self.json_data['values']]
69
70        # Assert that all values are unique in enums
71        all_values = set()
72        for value in self.values:
73            if value.value in all_values:
74                raise Exception("Duplicate value {} in enum {}".format(value.value, name))
75            all_values.add(value.value)
76
77BitmaskValue = namedtuple('BitmaskValue', ['name', 'value'])
78class BitmaskType(Type):
79    def __init__(self, name, json_data):
80        Type.__init__(self, name, json_data)
81        self.values = [BitmaskValue(Name(m['name']), m['value']) for m in self.json_data['values']]
82        self.full_mask = 0
83        for value in self.values:
84            self.full_mask = self.full_mask | value.value
85
86class NativeType(Type):
87    def __init__(self, name, json_data):
88        Type.__init__(self, name, json_data, native=True)
89
90class NativelyDefined(Type):
91    def __init__(self, name, json_data):
92        Type.__init__(self, name, json_data)
93
94# Methods and structures are both "records", so record members correspond to
95# method arguments or structure members.
96class RecordMember:
97    def __init__(self, name, typ, annotation, optional=False,
98                 is_return_value=False, default_value=None,
99                 skip_serialize=False):
100        self.name = name
101        self.type = typ
102        self.annotation = annotation
103        self.length = None
104        self.optional = optional
105        self.is_return_value = is_return_value
106        self.handle_type = None
107        self.default_value = default_value
108        self.skip_serialize = skip_serialize
109
110    def set_handle_type(self, handle_type):
111        assert self.type.dict_name == "ObjectHandle"
112        self.handle_type = handle_type
113
114Method = namedtuple('Method', ['name', 'return_type', 'arguments'])
115class ObjectType(Type):
116    def __init__(self, name, json_data):
117        Type.__init__(self, name, json_data)
118        self.methods = []
119        self.native_methods = []
120        self.built_type = None
121
122class Record:
123    def __init__(self, name):
124        self.name = Name(name)
125        self.members = []
126        self.has_dawn_object = False
127
128    def update_metadata(self):
129        def has_dawn_object(member):
130            if isinstance(member.type, ObjectType):
131                return True
132            elif isinstance(member.type, StructureType):
133                return member.type.has_dawn_object
134            else:
135                return False
136
137        self.has_dawn_object = any(has_dawn_object(member) for member in self.members)
138
139class StructureType(Record, Type):
140    def __init__(self, name, json_data):
141        Record.__init__(self, name)
142        Type.__init__(self, name, json_data)
143        self.extensible = json_data.get("extensible", False)
144
145class Command(Record):
146    def __init__(self, name, members=None):
147        Record.__init__(self, name)
148        self.members = members or []
149        self.derived_object = None
150        self.derived_method = None
151
152def linked_record_members(json_data, types):
153    members = []
154    members_by_name = {}
155    for m in json_data:
156        member = RecordMember(Name(m['name']), types[m['type']],
157                              m.get('annotation', 'value'),
158                              optional=m.get('optional', False),
159                              is_return_value=m.get('is_return_value', False),
160                              default_value=m.get('default', None),
161                              skip_serialize=m.get('skip_serialize', False))
162        handle_type = m.get('handle_type')
163        if handle_type:
164            member.set_handle_type(types[handle_type])
165        members.append(member)
166        members_by_name[member.name.canonical_case()] = member
167
168    for (member, m) in zip(members, json_data):
169        if member.annotation != 'value':
170            if not 'length' in m:
171                if member.type.category != 'object':
172                    member.length = "constant"
173                    member.constant_length = 1
174                else:
175                    assert(False)
176            elif m['length'] == 'strlen':
177                member.length = 'strlen'
178            else:
179                member.length = members_by_name[m['length']]
180
181    return members
182
183############################################################
184# PARSE
185############################################################
186
187def is_native_method(method):
188    return method.return_type.category == "natively defined" or \
189        any([arg.type.category == "natively defined" for arg in method.arguments])
190
191def link_object(obj, types):
192    def make_method(json_data):
193        arguments = linked_record_members(json_data.get('args', []), types)
194        return Method(Name(json_data['name']), types[json_data.get('returns', 'void')], arguments)
195
196    methods = [make_method(m) for m in obj.json_data.get('methods', [])]
197    obj.methods = [method for method in methods if not is_native_method(method)]
198    obj.native_methods = [method for method in methods if is_native_method(method)]
199
200def link_structure(struct, types):
201    struct.members = linked_record_members(struct.json_data['members'], types)
202
203# Sort structures so that if struct A has struct B as a member, then B is listed before A
204# This is a form of topological sort where we try to keep the order reasonably similar to the
205# original order (though th sort isn't technically stable).
206# It works by computing for each struct type what is the depth of its DAG of dependents, then
207# resorting based on that depth using Python's stable sort. This makes a toposort because if
208# A depends on B then its depth will be bigger than B's. It is also nice because all nodes
209# with the same depth are kept in the input order.
210def topo_sort_structure(structs):
211    for struct in structs:
212        struct.visited = False
213        struct.subdag_depth = 0
214
215    def compute_depth(struct):
216        if struct.visited:
217            return struct.subdag_depth
218
219        max_dependent_depth = 0
220        for member in struct.members:
221            if member.type.category == 'structure':
222                max_dependent_depth = max(max_dependent_depth, compute_depth(member.type) + 1)
223
224        struct.subdag_depth = max_dependent_depth
225        struct.visited = True
226        return struct.subdag_depth
227
228    for struct in structs:
229        compute_depth(struct)
230
231    result = sorted(structs, key=lambda struct: struct.subdag_depth)
232
233    for struct in structs:
234        del struct.visited
235        del struct.subdag_depth
236
237    return result
238
239def parse_json(json):
240    category_to_parser = {
241        'bitmask': BitmaskType,
242        'enum': EnumType,
243        'native': NativeType,
244        'natively defined': NativelyDefined,
245        'object': ObjectType,
246        'structure': StructureType,
247    }
248
249    types = {}
250
251    by_category = {}
252    for name in category_to_parser.keys():
253        by_category[name] = []
254
255    for (name, json_data) in json.items():
256        if name[0] == '_':
257            continue
258        category = json_data['category']
259        parsed = category_to_parser[category](name, json_data)
260        by_category[category].append(parsed)
261        types[name] = parsed
262
263    for obj in by_category['object']:
264        link_object(obj, types)
265
266    for struct in by_category['structure']:
267        link_structure(struct, types)
268
269    for category in by_category.keys():
270        by_category[category] = sorted(by_category[category], key=lambda typ: typ.name.canonical_case())
271
272    by_category['structure'] = topo_sort_structure(by_category['structure'])
273
274    for struct in by_category['structure']:
275        struct.update_metadata()
276
277    return {
278        'types': types,
279        'by_category': by_category
280    }
281
282############################################################
283# WIRE STUFF
284############################################################
285
286# Create wire commands from api methods
287def compute_wire_params(api_params, wire_json):
288    wire_params = api_params.copy()
289    types = wire_params['types']
290
291    commands = []
292    return_commands = []
293
294    # Generate commands from object methods
295    for api_object in wire_params['by_category']['object']:
296        for method in api_object.methods:
297            command_name = concat_names(api_object.name, method.name)
298            command_suffix = Name(command_name).CamelCase()
299
300            # Only object return values or void are supported. Other methods must be handwritten.
301            if method.return_type.category != 'object' and method.return_type.name.canonical_case() != 'void':
302                assert(command_suffix in wire_json['special items']['client_handwritten_commands'])
303                continue
304
305            if command_suffix in wire_json['special items']['client_side_commands']:
306                continue
307
308            # Create object method commands by prepending "self"
309            members = [RecordMember(Name('self'), types[api_object.dict_name], 'value')]
310            members += method.arguments
311
312            # Client->Server commands that return an object return the result object handle
313            if method.return_type.category == 'object':
314                result = RecordMember(Name('result'), types['ObjectHandle'], 'value', is_return_value=True)
315                result.set_handle_type(method.return_type)
316                members.append(result)
317
318            command = Command(command_name, members)
319            command.derived_object = api_object
320            command.derived_method = method
321            commands.append(command)
322
323    for (name, json_data) in wire_json['commands'].items():
324        commands.append(Command(name, linked_record_members(json_data, types)))
325
326    for (name, json_data) in wire_json['return commands'].items():
327        return_commands.append(Command(name, linked_record_members(json_data, types)))
328
329    wire_params['cmd_records'] = {
330        'command': commands,
331        'return command': return_commands
332    }
333
334    for commands in wire_params['cmd_records'].values():
335        for command in commands:
336            command.update_metadata()
337        commands.sort(key=lambda c: c.name.canonical_case())
338
339    wire_params.update(wire_json.get('special items', {}))
340
341    return wire_params
342
343#############################################################
344# Generator
345#############################################################
346
347def as_varName(*names):
348    return names[0].camelCase() + ''.join([name.CamelCase() for name in names[1:]])
349
350def as_cType(name):
351    if name.native:
352        return name.concatcase()
353    else:
354        return 'Dawn' + name.CamelCase()
355
356def as_cppType(name):
357    if name.native:
358        return name.concatcase()
359    else:
360        return name.CamelCase()
361
362def convert_cType_to_cppType(typ, annotation, arg, indent=0):
363    if typ.category == 'native':
364        return arg
365    if annotation == 'value':
366        if typ.category == 'object':
367            return '{}::Acquire({})'.format(as_cppType(typ.name), arg)
368        elif typ.category == 'structure':
369            converted_members = [
370                convert_cType_to_cppType(
371                    member.type, member.annotation,
372                    '{}.{}'.format(arg, as_varName(member.name)),
373                    indent + 1)
374                for member in typ.members]
375
376            converted_members = [(' ' * 4) + m for m in converted_members ]
377            converted_members = ',\n'.join(converted_members)
378
379            return as_cppType(typ.name) + ' {\n' + converted_members + '\n}'
380        else:
381            return 'static_cast<{}>({})'.format(as_cppType(typ.name), arg)
382    else:
383        return 'reinterpret_cast<{} {}>({})'.format(as_cppType(typ.name), annotation, arg)
384
385def decorate(name, typ, arg):
386    if arg.annotation == 'value':
387        return typ + ' ' + name
388    elif arg.annotation == '*':
389        return typ + ' * ' + name
390    elif arg.annotation == 'const*':
391        return typ + ' const * ' + name
392    elif arg.annotation == 'const*const*':
393        return 'const ' + typ + '* const * ' + name
394    else:
395        assert(False)
396
397def annotated(typ, arg):
398    name = as_varName(arg.name)
399    return decorate(name, typ, arg)
400
401def as_cEnum(type_name, value_name):
402    assert(not type_name.native and not value_name.native)
403    return 'DAWN' + '_' + type_name.SNAKE_CASE() + '_' + value_name.SNAKE_CASE()
404
405def as_cppEnum(value_name):
406    assert(not value_name.native)
407    if value_name.concatcase()[0].isdigit():
408        return "e" + value_name.CamelCase()
409    return value_name.CamelCase()
410
411def as_cMethod(type_name, method_name):
412    assert(not type_name.native and not method_name.native)
413    return 'dawn' + type_name.CamelCase() + method_name.CamelCase()
414
415def as_MethodSuffix(type_name, method_name):
416    assert(not type_name.native and not method_name.native)
417    return type_name.CamelCase() + method_name.CamelCase()
418
419def as_cProc(type_name, method_name):
420    assert(not type_name.native and not method_name.native)
421    return 'Dawn' + 'Proc' + type_name.CamelCase() + method_name.CamelCase()
422
423def as_frontendType(typ):
424    if typ.category == 'object':
425        return typ.name.CamelCase() + 'Base*'
426    elif typ.category in ['bitmask', 'enum']:
427        return 'dawn::' + typ.name.CamelCase()
428    elif typ.category == 'structure':
429        return as_cppType(typ.name)
430    else:
431        return as_cType(typ.name)
432
433def cpp_native_methods(types, typ):
434    return typ.methods + typ.native_methods
435
436def c_native_methods(types, typ):
437    return cpp_native_methods(types, typ) + [
438        Method(Name('reference'), types['void'], []),
439        Method(Name('release'), types['void'], []),
440    ]
441
442class MultiGeneratorFromDawnJSON(Generator):
443    def get_description(self):
444        return 'Generates code for various target from Dawn.json.'
445
446    def add_commandline_arguments(self, parser):
447        allowed_targets = ['dawn_headers', 'libdawn', 'mock_dawn', 'dawn_wire', "dawn_native_utils"]
448
449        parser.add_argument('--dawn-json', required=True, type=str, help ='The DAWN JSON definition to use.')
450        parser.add_argument('--wire-json', default=None, type=str, help='The DAWN WIRE JSON definition to use.')
451        parser.add_argument('--targets', required=True, type=str, help='Comma-separated subset of targets to output. Available targets: ' + ', '.join(allowed_targets))
452
453    def get_file_renders(self, args):
454        with open(args.dawn_json) as f:
455            loaded_json = json.loads(f.read())
456        api_params = parse_json(loaded_json)
457
458        targets = args.targets.split(',')
459
460        wire_json = None
461        if args.wire_json:
462            with open(args.wire_json) as f:
463                wire_json = json.loads(f.read())
464
465        base_params = {
466            'Name': lambda name: Name(name),
467
468            'as_annotated_cType': lambda arg: annotated(as_cType(arg.type.name), arg),
469            'as_annotated_cppType': lambda arg: annotated(as_cppType(arg.type.name), arg),
470            'as_cEnum': as_cEnum,
471            'as_cppEnum': as_cppEnum,
472            'as_cMethod': as_cMethod,
473            'as_MethodSuffix': as_MethodSuffix,
474            'as_cProc': as_cProc,
475            'as_cType': as_cType,
476            'as_cppType': as_cppType,
477            'convert_cType_to_cppType': convert_cType_to_cppType,
478            'as_varName': as_varName,
479            'decorate': decorate,
480        }
481
482        renders = []
483
484        c_params = {'native_methods': lambda typ: c_native_methods(api_params['types'], typ)}
485        cpp_params = {'native_methods': lambda typ: cpp_native_methods(api_params['types'], typ)}
486
487        if 'dawn_headers' in targets:
488            renders.append(FileRender('api.h', 'dawn/dawn.h', [base_params, api_params, c_params]))
489            renders.append(FileRender('apicpp.h', 'dawn/dawncpp.h', [base_params, api_params, cpp_params]))
490
491        if 'libdawn' in targets:
492            additional_params = {'native_methods': lambda typ: cpp_native_methods(api_params['types'], typ)}
493            renders.append(FileRender('api.c', 'dawn/dawn.c', [base_params, api_params, c_params]))
494            renders.append(FileRender('apicpp.cpp', 'dawn/dawncpp.cpp', [base_params, api_params, cpp_params]))
495
496        if 'mock_dawn' in targets:
497            renders.append(FileRender('mock_api.h', 'mock/mock_dawn.h', [base_params, api_params, c_params]))
498            renders.append(FileRender('mock_api.cpp', 'mock/mock_dawn.cpp', [base_params, api_params, c_params]))
499
500        if 'dawn_native_utils' in targets:
501            frontend_params = [
502                base_params,
503                api_params,
504                c_params,
505                {
506                    'as_frontendType': lambda typ: as_frontendType(typ), # TODO as_frontendType and friends take a Type and not a Name :(
507                    'as_annotated_frontendType': lambda arg: annotated(as_frontendType(arg.type), arg)
508                }
509            ]
510
511            renders.append(FileRender('dawn_native/ValidationUtils.h', 'dawn_native/ValidationUtils_autogen.h', frontend_params))
512            renders.append(FileRender('dawn_native/ValidationUtils.cpp', 'dawn_native/ValidationUtils_autogen.cpp', frontend_params))
513            renders.append(FileRender('dawn_native/api_structs.h', 'dawn_native/dawn_structs_autogen.h', frontend_params))
514            renders.append(FileRender('dawn_native/api_structs.cpp', 'dawn_native/dawn_structs_autogen.cpp', frontend_params))
515            renders.append(FileRender('dawn_native/ProcTable.cpp', 'dawn_native/ProcTable.cpp', frontend_params))
516
517        if 'dawn_wire' in targets:
518            additional_params = compute_wire_params(api_params, wire_json)
519
520            wire_params = [
521                base_params,
522                api_params,
523                c_params,
524                {
525                    'as_wireType': lambda typ: typ.name.CamelCase() + '*' if typ.category == 'object' else as_cppType(typ.name)
526                },
527                additional_params
528            ]
529            renders.append(FileRender('dawn_wire/WireCmd.h', 'dawn_wire/WireCmd_autogen.h', wire_params))
530            renders.append(FileRender('dawn_wire/WireCmd.cpp', 'dawn_wire/WireCmd_autogen.cpp', wire_params))
531            renders.append(FileRender('dawn_wire/client/ApiObjects.h', 'dawn_wire/client/ApiObjects_autogen.h', wire_params))
532            renders.append(FileRender('dawn_wire/client/ApiProcs.cpp', 'dawn_wire/client/ApiProcs_autogen.cpp', wire_params))
533            renders.append(FileRender('dawn_wire/client/ApiProcs.h', 'dawn_wire/client/ApiProcs_autogen.h', wire_params))
534            renders.append(FileRender('dawn_wire/client/ClientBase.h', 'dawn_wire/client/ClientBase_autogen.h', wire_params))
535            renders.append(FileRender('dawn_wire/client/ClientHandlers.cpp', 'dawn_wire/client/ClientHandlers_autogen.cpp', wire_params))
536            renders.append(FileRender('dawn_wire/client/ClientPrototypes.inc', 'dawn_wire/client/ClientPrototypes_autogen.inc', wire_params))
537            renders.append(FileRender('dawn_wire/server/ServerBase.h', 'dawn_wire/server/ServerBase_autogen.h', wire_params))
538            renders.append(FileRender('dawn_wire/server/ServerDoers.cpp', 'dawn_wire/server/ServerDoers_autogen.cpp', wire_params))
539            renders.append(FileRender('dawn_wire/server/ServerHandlers.cpp', 'dawn_wire/server/ServerHandlers_autogen.cpp', wire_params))
540            renders.append(FileRender('dawn_wire/server/ServerPrototypes.inc', 'dawn_wire/server/ServerPrototypes_autogen.inc', wire_params))
541
542        return renders
543
544    def get_dependencies(self, args):
545        deps = [os.path.abspath(args.dawn_json)]
546        if args.wire_json != None:
547            deps += [os.path.abspath(args.wire_json)]
548        return deps
549
550if __name__ == '__main__':
551    sys.exit(run_generator(MultiGeneratorFromDawnJSON()))
552