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