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