idl_c_proto.py revision 5821806d5e7f356e8fa4b058a389a808ea183019
1#!/usr/bin/env python 2# Copyright (c) 2012 The Chromium Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6""" Generator for C style prototypes and definitions """ 7 8import glob 9import os 10import sys 11 12from idl_log import ErrOut, InfoOut, WarnOut 13from idl_node import IDLNode 14from idl_ast import IDLAst 15from idl_option import GetOption, Option, ParseOptions 16from idl_parser import ParseFiles 17 18Option('cgen_debug', 'Debug generate.') 19 20class CGenError(Exception): 21 def __init__(self, msg): 22 self.value = msg 23 24 def __str__(self): 25 return repr(self.value) 26 27 28def CommentLines(lines, tabs=0): 29 # Generate a C style comment block by prepending the block with '<tab>/*' 30 # and adding a '<tab> *' per line. 31 tab = ' ' * tabs 32 33 out = '%s/*' % tab + ('\n%s *' % tab).join(lines) 34 35 # Add a terminating ' */' unless the last line is blank which would mean it 36 # already has ' *' 37 if not lines[-1]: 38 out += '/\n' 39 else: 40 out += ' */\n' 41 return out 42 43def Comment(node, prefix=None, tabs=0): 44 # Generate a comment block from the provided Comment node. 45 comment = node.GetName() 46 lines = comment.split('\n') 47 48 # If an option prefix is provided, then prepend that to the comment 49 # for this node. 50 if prefix: 51 prefix_lines = prefix.split('\n') 52 # If both the prefix and comment start with a blank line ('*') remove 53 # the extra one. 54 if prefix_lines[0] == '*' and lines[0] == '*': 55 lines = prefix_lines + lines[1:] 56 else: 57 lines = prefix_lines + lines; 58 return CommentLines(lines, tabs) 59 60def GetNodeComments(node, tabs=0): 61 # Generate a comment block joining all comment nodes which are children of 62 # the provided node. 63 comment_txt = '' 64 for doc in node.GetListOf('Comment'): 65 comment_txt += Comment(doc, tabs=tabs) 66 return comment_txt 67 68 69class CGen(object): 70 # TypeMap 71 # 72 # TypeMap modifies how an object is stored or passed, for example pointers 73 # are passed as 'const' if they are 'in' parameters, and structures are 74 # preceeded by the keyword 'struct' as well as using a pointer. 75 # 76 TypeMap = { 77 'Array': { 78 'in': 'const %s', 79 'inout': '%s', 80 'out': '%s*', 81 'store': '%s', 82 'return': '%s' 83 }, 84 'Callspec': { 85 'in': '%s', 86 'inout': '%s', 87 'out': '%s', 88 'store': '%s', 89 'return': '%s' 90 }, 91 'Enum': { 92 'in': '%s', 93 'inout': '%s*', 94 'out': '%s*', 95 'store': '%s', 96 'return': '%s' 97 }, 98 'Interface': { 99 'in': 'const %s*', 100 'inout': '%s*', 101 'out': '%s**', 102 'return': '%s*', 103 'store': '%s*' 104 }, 105 'Struct': { 106 'in': 'const %s*', 107 'inout': '%s*', 108 'out': '%s*', 109 'return': ' %s*', 110 'store': '%s' 111 }, 112 'blob_t': { 113 'in': 'const %s', 114 'inout': '%s', 115 'out': '%s', 116 'return': '%s', 117 'store': '%s' 118 }, 119 'mem_t': { 120 'in': 'const %s', 121 'inout': '%s', 122 'out': '%s', 123 'return': '%s', 124 'store': '%s' 125 }, 126 'str_t': { 127 'in': 'const %s', 128 'inout': '%s', 129 'out': '%s', 130 'return': 'const %s', 131 'store': '%s' 132 }, 133 'cstr_t': { 134 'in': '%s', 135 'inout': '%s*', 136 'out': '%s*', 137 'return': '%s', 138 'store': '%s' 139 }, 140 'TypeValue': { 141 'in': '%s', 142 'inout': '%s*', 143 'out': '%s*', 144 'return': '%s', 145 'store': '%s' 146 }, 147 } 148 149 150 # 151 # RemapName 152 # 153 # A diction array of PPAPI types that are converted to language specific 154 # types before being returned by by the C generator 155 # 156 RemapName = { 157 'blob_t': 'void**', 158 'float_t': 'float', 159 'double_t': 'double', 160 'handle_t': 'int', 161 'mem_t': 'void*', 162 'str_t': 'char*', 163 'cstr_t': 'const char*', 164 'interface_t' : 'const void*' 165 } 166 167 def __init__(self): 168 self.dbg_depth = 0 169 170 # 171 # Debug Logging functions 172 # 173 def Log(self, txt): 174 if not GetOption('cgen_debug'): return 175 tabs = ' ' * self.dbg_depth 176 print '%s%s' % (tabs, txt) 177 178 def LogEnter(self, txt): 179 if txt: self.Log(txt) 180 self.dbg_depth += 1 181 182 def LogExit(self, txt): 183 self.dbg_depth -= 1 184 if txt: self.Log(txt) 185 186 187 def GetDefine(self, name, value): 188 out = '#define %s %s' % (name, value) 189 if len(out) > 80: 190 out = '#define %s \\\n %s' % (name, value) 191 return '%s\n' % out 192 193 # 194 # Interface strings 195 # 196 def GetMacroHelper(self, node): 197 macro = node.GetProperty('macro') 198 if macro: return macro 199 name = node.GetName() 200 name = name.upper() 201 return "%s_INTERFACE" % name 202 203 def GetInterfaceMacro(self, node, version = None): 204 name = self.GetMacroHelper(node) 205 if version is None: 206 return name 207 return '%s_%s' % (name, str(version).replace('.', '_')) 208 209 def GetInterfaceString(self, node, version = None): 210 # If an interface name is specified, use that 211 name = node.GetProperty('iname') 212 if not name: 213 # Otherwise, the interface name is the object's name 214 # With '_Dev' replaced by '(Dev)' if it's a Dev interface. 215 name = node.GetName() 216 if name.endswith('_Dev'): 217 name = '%s(Dev)' % name[:-4] 218 if version is None: 219 return name 220 return "%s;%s" % (name, version) 221 222 223 # 224 # Return the array specification of the object. 225 # 226 def GetArraySpec(self, node): 227 assert(node.cls == 'Array') 228 fixed = node.GetProperty('FIXED') 229 if fixed: 230 return '[%s]' % fixed 231 else: 232 return '[]' 233 234 # 235 # GetTypeName 236 # 237 # For any valid 'typed' object such as Member or Typedef 238 # the typenode object contains the typename 239 # 240 # For a given node return the type name by passing mode. 241 # 242 def GetTypeName(self, node, release, prefix=''): 243 self.LogEnter('GetTypeName of %s rel=%s' % (node, release)) 244 245 # For Members, Params, and Typedefs get the type it refers to otherwise 246 # the node in question is it's own type (struct, union etc...) 247 if node.IsA('Member', 'Param', 'Typedef'): 248 typeref = node.GetType(release) 249 else: 250 typeref = node 251 252 if typeref is None: 253 node.Error('No type at release %s.' % release) 254 raise CGenError('No type for %s' % node) 255 256 # If the type is a (BuiltIn) Type then return it's name 257 # remapping as needed 258 if typeref.IsA('Type'): 259 name = CGen.RemapName.get(typeref.GetName(), None) 260 if name is None: name = typeref.GetName() 261 name = '%s%s' % (prefix, name) 262 263 # For Interfaces, use the name + version 264 elif typeref.IsA('Interface'): 265 rel = typeref.first_release[release] 266 name = 'struct %s%s' % (prefix, self.GetStructName(typeref, rel, True)) 267 268 # For structures, preceed with 'struct' or 'union' as appropriate 269 elif typeref.IsA('Struct'): 270 if typeref.GetProperty('union'): 271 name = 'union %s%s' % (prefix, typeref.GetName()) 272 else: 273 name = 'struct %s%s' % (prefix, typeref.GetName()) 274 275 # If it's an enum, or typedef then return the Enum's name 276 elif typeref.IsA('Enum', 'Typedef'): 277 # The enum may have skipped having a typedef, we need prefix with 'enum'. 278 if typeref.GetProperty('notypedef'): 279 name = 'enum %s%s' % (prefix, typeref.GetName()) 280 else: 281 name = '%s%s' % (prefix, typeref.GetName()) 282 283 else: 284 raise RuntimeError('Getting name of non-type %s.' % node) 285 self.LogExit('GetTypeName %s is %s' % (node, name)) 286 return name 287 288 289 # 290 # GetRootType 291 # 292 # For a given node return basic type of that object. This is 293 # either a 'Type', 'Callspec', or 'Array' 294 # 295 def GetRootTypeMode(self, node, release, mode): 296 self.LogEnter('GetRootType of %s' % node) 297 # If it has an array spec, then treat it as an array regardless of type 298 if node.GetOneOf('Array'): 299 rootType = 'Array' 300 # Or if it has a callspec, treat it as a function 301 elif node.GetOneOf('Callspec'): 302 rootType, mode = self.GetRootTypeMode(node.GetType(release), release, 303 'return') 304 305 # If it's a plain typedef, try that object's root type 306 elif node.IsA('Member', 'Param', 'Typedef'): 307 rootType, mode = self.GetRootTypeMode(node.GetType(release), 308 release, mode) 309 310 # If it's an Enum, then it's normal passing rules 311 elif node.IsA('Enum'): 312 rootType = node.cls 313 314 # If it's an Interface or Struct, we may be passing by value 315 elif node.IsA('Interface', 'Struct'): 316 if mode == 'return': 317 if node.GetProperty('returnByValue'): 318 rootType = 'TypeValue' 319 else: 320 rootType = node.cls 321 else: 322 if node.GetProperty('passByValue'): 323 rootType = 'TypeValue' 324 else: 325 rootType = node.cls 326 327 # If it's an Basic Type, check if it's a special type 328 elif node.IsA('Type'): 329 if node.GetName() in CGen.TypeMap: 330 rootType = node.GetName() 331 else: 332 rootType = 'TypeValue' 333 else: 334 raise RuntimeError('Getting root type of non-type %s.' % node) 335 self.LogExit('RootType is "%s"' % rootType) 336 return rootType, mode 337 338 339 def GetTypeByMode(self, node, release, mode): 340 self.LogEnter('GetTypeByMode of %s mode=%s release=%s' % 341 (node, mode, release)) 342 name = self.GetTypeName(node, release) 343 ntype, mode = self.GetRootTypeMode(node, release, mode) 344 out = CGen.TypeMap[ntype][mode] % name 345 self.LogExit('GetTypeByMode %s = %s' % (node, out)) 346 return out 347 348 349 # Get the passing mode of the object (in, out, inout). 350 def GetParamMode(self, node): 351 self.Log('GetParamMode for %s' % node) 352 if node.GetProperty('in'): return 'in' 353 if node.GetProperty('out'): return 'out' 354 if node.GetProperty('inout'): return 'inout' 355 return 'return' 356 357 # 358 # GetComponents 359 # 360 # Returns the signature components of an object as a tuple of 361 # (rtype, name, arrays, callspec) where: 362 # rtype - The store or return type of the object. 363 # name - The name of the object. 364 # arrays - A list of array dimensions as [] or [<fixed_num>]. 365 # args - None if not a function, otherwise a list of parameters. 366 # 367 def GetComponents(self, node, release, mode): 368 self.LogEnter('GetComponents mode %s for %s %s' % (mode, node, release)) 369 370 # Generate passing type by modifying root type 371 rtype = self.GetTypeByMode(node, release, mode) 372 if node.IsA('Enum', 'Interface', 'Struct'): 373 rname = node.GetName() 374 else: 375 rname = node.GetType(release).GetName() 376 377 if rname in CGen.RemapName: 378 rname = CGen.RemapName[rname] 379 if '%' in rtype: 380 rtype = rtype % rname 381 name = node.GetName() 382 arrayspec = [self.GetArraySpec(array) for array in node.GetListOf('Array')] 383 callnode = node.GetOneOf('Callspec') 384 if callnode: 385 callspec = [] 386 for param in callnode.GetListOf('Param'): 387 mode = self.GetParamMode(param) 388 ptype, pname, parray, pspec = self.GetComponents(param, release, mode) 389 callspec.append((ptype, pname, parray, pspec)) 390 else: 391 callspec = None 392 393 self.LogExit('GetComponents: %s, %s, %s, %s' % 394 (rtype, name, arrayspec, callspec)) 395 return (rtype, name, arrayspec, callspec) 396 397 398 def Compose(self, rtype, name, arrayspec, callspec, prefix, func_as_ptr, 399 ptr_prefix, include_name): 400 self.LogEnter('Compose: %s %s' % (rtype, name)) 401 arrayspec = ''.join(arrayspec) 402 if not include_name: 403 name = prefix + arrayspec 404 else: 405 name = prefix + name + arrayspec 406 if callspec is None: 407 out = '%s %s' % (rtype, name) 408 else: 409 params = [] 410 for ptype, pname, parray, pspec in callspec: 411 params.append(self.Compose(ptype, pname, parray, pspec, '', True, 412 ptr_prefix='', include_name=True)) 413 if func_as_ptr: 414 name = '(%s*%s)' % (ptr_prefix, name) 415 out = '%s %s(%s)' % (rtype, name, ', '.join(params)) 416 self.LogExit('Exit Compose: %s' % out) 417 return out 418 419 # 420 # GetSignature 421 # 422 # Returns the 'C' style signature of the object 423 # prefix - A prefix for the object's name 424 # func_as_ptr - Formats a function as a function pointer 425 # ptr_prefix - A prefix that goes before the "*" for a function pointer 426 # include_name - If true, include member name in the signature. 427 # If false, leave it out. In any case, prefix and ptr_prefix 428 # are always included. 429 # 430 def GetSignature(self, node, release, mode, prefix='', func_as_ptr=True, 431 ptr_prefix='', include_name=True): 432 self.LogEnter('GetSignature %s %s as func=%s' % 433 (node, mode, func_as_ptr)) 434 rtype, name, arrayspec, callspec = self.GetComponents(node, release, mode) 435 out = self.Compose(rtype, name, arrayspec, callspec, prefix, 436 func_as_ptr, ptr_prefix, include_name) 437 self.LogExit('Exit GetSignature: %s' % out) 438 return out 439 440 # Define a Typedef. 441 def DefineTypedef(self, node, releases, prefix='', comment=False): 442 __pychecker__ = 'unusednames=comment' 443 build_list = node.GetUniqueReleases(releases) 444 445 # TODO(noelallen) : Bug 157017 finish multiversion support 446 if len(build_list) != 1: 447 node.Error('Can not support multiple versions of node.') 448 assert len(build_list) == 1 449 450 out = 'typedef %s;\n' % self.GetSignature(node, build_list[0], 'return', 451 prefix, True) 452 self.Log('DefineTypedef: %s' % out) 453 return out 454 455 # Define an Enum. 456 def DefineEnum(self, node, releases, prefix='', comment=False): 457 __pychecker__ = 'unusednames=comment,releases' 458 self.LogEnter('DefineEnum %s' % node) 459 name = '%s%s' % (prefix, node.GetName()) 460 notypedef = node.GetProperty('notypedef') 461 unnamed = node.GetProperty('unnamed') 462 463 if unnamed: 464 out = 'enum {' 465 elif notypedef: 466 out = 'enum %s {' % name 467 else: 468 out = 'typedef enum {' 469 enumlist = [] 470 for child in node.GetListOf('EnumItem'): 471 value = child.GetProperty('VALUE') 472 comment_txt = GetNodeComments(child, tabs=1) 473 if value: 474 item_txt = '%s%s = %s' % (prefix, child.GetName(), value) 475 else: 476 item_txt = '%s%s' % (prefix, child.GetName()) 477 enumlist.append('%s %s' % (comment_txt, item_txt)) 478 self.LogExit('Exit DefineEnum') 479 480 if unnamed or notypedef: 481 out = '%s\n%s\n};\n' % (out, ',\n'.join(enumlist)) 482 else: 483 out = '%s\n%s\n} %s;\n' % (out, ',\n'.join(enumlist), name) 484 return out 485 486 def DefineMember(self, node, releases, prefix='', comment=False): 487 __pychecker__ = 'unusednames=prefix,comment' 488 release = releases[0] 489 self.LogEnter('DefineMember %s' % node) 490 out = '%s;' % self.GetSignature(node, release, 'store', '', True) 491 self.LogExit('Exit DefineMember') 492 return out 493 494 def GetStructName(self, node, release, include_version=False): 495 suffix = '' 496 if include_version: 497 ver_num = node.GetVersion(release) 498 suffix = ('_%s' % ver_num).replace('.', '_') 499 return node.GetName() + suffix 500 501 def DefineStructInternals(self, node, release, 502 include_version=False, comment=True): 503 out = '' 504 if node.GetProperty('union'): 505 out += 'union %s {\n' % ( 506 self.GetStructName(node, release, include_version)) 507 else: 508 out += 'struct %s {\n' % ( 509 self.GetStructName(node, release, include_version)) 510 511 # Generate Member Functions 512 members = [] 513 for child in node.GetListOf('Member'): 514 member = self.Define(child, [release], tabs=1, comment=comment) 515 if not member: 516 continue 517 members.append(member) 518 out += '%s\n};\n' % '\n'.join(members) 519 return out 520 521 522 def DefineStruct(self, node, releases, prefix='', comment=False): 523 __pychecker__ = 'unusednames=comment,prefix' 524 self.LogEnter('DefineStruct %s' % node) 525 out = '' 526 build_list = node.GetUniqueReleases(releases) 527 528 # TODO(noelallen) : Bug 157017 finish multiversion support 529 if node.IsA('Struct'): 530 if len(build_list) != 1: 531 node.Error('Can not support multiple versions of node.') 532 assert len(build_list) == 1 533 534 535 if node.IsA('Interface'): 536 # Build the most recent one versioned, with comments 537 out = self.DefineStructInternals(node, build_list[-1], 538 include_version=True, comment=True) 539 540 # Define an unversioned typedef for the most recent version 541 out += '\ntypedef struct %s %s;\n' % ( 542 self.GetStructName(node, build_list[-1], include_version=True), 543 self.GetStructName(node, build_list[-1], include_version=False)) 544 else: 545 # Build the most recent one versioned, with comments 546 out = self.DefineStructInternals(node, build_list[-1], 547 include_version=False, comment=True) 548 549 550 # Build the rest without comments and with the version number appended 551 for rel in build_list[0:-1]: 552 out += '\n' + self.DefineStructInternals(node, rel, 553 include_version=True, 554 comment=False) 555 556 self.LogExit('Exit DefineStruct') 557 return out 558 559 560 # 561 # Copyright and Comment 562 # 563 # Generate a comment or copyright block 564 # 565 def Copyright(self, node, tabs=0): 566 lines = node.GetName().split('\n') 567 return CommentLines(lines, tabs) 568 569 570 # Define a top level object. 571 def Define(self, node, releases, tabs=0, prefix='', comment=False): 572 # If this request does not match unique release, or if the release is not 573 # available (possibly deprecated) then skip. 574 unique = node.GetUniqueReleases(releases) 575 if not unique or not node.InReleases(releases): 576 return '' 577 578 self.LogEnter('Define %s tab=%d prefix="%s"' % (node,tabs,prefix)) 579 declmap = dict({ 580 'Enum': CGen.DefineEnum, 581 'Function': CGen.DefineMember, 582 'Interface': CGen.DefineStruct, 583 'Member': CGen.DefineMember, 584 'Struct': CGen.DefineStruct, 585 'Typedef': CGen.DefineTypedef 586 }) 587 588 out = '' 589 func = declmap.get(node.cls, None) 590 if not func: 591 ErrOut.Log('Failed to define %s named %s' % (node.cls, node.GetName())) 592 define_txt = func(self, node, releases, prefix=prefix, comment=comment) 593 594 comment_txt = GetNodeComments(node, tabs=0) 595 if comment_txt and comment: 596 out += comment_txt 597 out += define_txt 598 599 tab = ' ' * tabs 600 lines = [] 601 for line in out.split('\n'): 602 # Add indentation 603 line = tab + line 604 if len(line) > 80: 605 left = line.rfind('(') + 1 606 args = line[left:].split(',') 607 line_max = 0 608 for arg in args: 609 if len(arg) > line_max: line_max = len(arg) 610 611 if left + line_max >= 80: 612 space = '%s ' % tab 613 args = (',\n%s' % space).join([arg.strip() for arg in args]) 614 lines.append('%s\n%s%s' % (line[:left], space, args)) 615 else: 616 space = ' ' * (left - 1) 617 args = (',\n%s' % space).join(args) 618 lines.append('%s%s' % (line[:left], args)) 619 else: 620 lines.append(line.rstrip()) 621 self.LogExit('Exit Define') 622 return '\n'.join(lines) 623 624# Clean a string representing an object definition and return then string 625# as a single space delimited set of tokens. 626def CleanString(instr): 627 instr = instr.strip() 628 instr = instr.split() 629 return ' '.join(instr) 630 631 632# Test a file, by comparing all it's objects, with their comments. 633def TestFile(filenode): 634 cgen = CGen() 635 636 errors = 0 637 for node in filenode.GetChildren()[2:]: 638 instr = node.GetOneOf('Comment') 639 if not instr: continue 640 instr.Dump() 641 instr = CleanString(instr.GetName()) 642 643 outstr = cgen.Define(node, releases=['M14']) 644 if GetOption('verbose'): 645 print outstr + '\n' 646 outstr = CleanString(outstr) 647 648 if instr != outstr: 649 ErrOut.Log('Failed match of\n>>%s<<\n>>%s<<\nto:' % (instr, outstr)) 650 node.Dump(1, comments=True) 651 errors += 1 652 return errors 653 654 655# Build and resolve the AST and compare each file individual. 656def TestFiles(filenames): 657 if not filenames: 658 idldir = os.path.split(sys.argv[0])[0] 659 idldir = os.path.join(idldir, 'test_cgen', '*.idl') 660 filenames = glob.glob(idldir) 661 662 filenames = sorted(filenames) 663 ast = ParseFiles(filenames) 664 665 total_errs = 0 666 for filenode in ast.GetListOf('File'): 667 errs = TestFile(filenode) 668 if errs: 669 ErrOut.Log('%s test failed with %d error(s).' % 670 (filenode.GetName(), errs)) 671 total_errs += errs 672 673 if total_errs: 674 ErrOut.Log('Failed generator test.') 675 else: 676 InfoOut.Log('Passed generator test.') 677 return total_errs 678 679def Main(args): 680 filenames = ParseOptions(args) 681 if GetOption('test'): 682 return TestFiles(filenames) 683 ast = ParseFiles(filenames) 684 cgen = CGen() 685 for f in ast.GetListOf('File'): 686 if f.GetProperty('ERRORS') > 0: 687 print 'Skipping %s' % f.GetName() 688 continue 689 for node in f.GetChildren()[2:]: 690 print cgen.Define(node, comment=True, prefix='tst_') 691 692 693if __name__ == '__main__': 694 sys.exit(Main(sys.argv[1:])) 695 696