idl_c_proto.py revision eb525c5499e34cc9c4b825d6d9e75bb07cc06ace
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 if not typeref.LastRelease(release): 280 first = node.first_release[release] 281 ver = '_' + node.GetVersion(first).replace('.','_') 282 else: 283 ver = '' 284 # The enum may have skipped having a typedef, we need prefix with 'enum'. 285 if typeref.GetProperty('notypedef'): 286 name = 'enum %s%s%s' % (prefix, typeref.GetName(), ver) 287 else: 288 name = '%s%s%s' % (prefix, typeref.GetName(), ver) 289 290 else: 291 raise RuntimeError('Getting name of non-type %s.' % node) 292 self.LogExit('GetTypeName %s is %s' % (node, name)) 293 return name 294 295 296 # 297 # GetRootType 298 # 299 # For a given node return basic type of that object. This is 300 # either a 'Type', 'Callspec', or 'Array' 301 # 302 def GetRootTypeMode(self, node, release, mode): 303 self.LogEnter('GetRootType of %s' % node) 304 # If it has an array spec, then treat it as an array regardless of type 305 if node.GetOneOf('Array'): 306 rootType = 'Array' 307 # Or if it has a callspec, treat it as a function 308 elif node.GetOneOf('Callspec'): 309 rootType, mode = self.GetRootTypeMode(node.GetType(release), release, 310 'return') 311 312 # If it's a plain typedef, try that object's root type 313 elif node.IsA('Member', 'Param', 'Typedef'): 314 rootType, mode = self.GetRootTypeMode(node.GetType(release), 315 release, mode) 316 317 # If it's an Enum, then it's normal passing rules 318 elif node.IsA('Enum'): 319 rootType = node.cls 320 321 # If it's an Interface or Struct, we may be passing by value 322 elif node.IsA('Interface', 'Struct'): 323 if mode == 'return': 324 if node.GetProperty('returnByValue'): 325 rootType = 'TypeValue' 326 else: 327 rootType = node.cls 328 else: 329 if node.GetProperty('passByValue'): 330 rootType = 'TypeValue' 331 else: 332 rootType = node.cls 333 334 # If it's an Basic Type, check if it's a special type 335 elif node.IsA('Type'): 336 if node.GetName() in CGen.TypeMap: 337 rootType = node.GetName() 338 else: 339 rootType = 'TypeValue' 340 else: 341 raise RuntimeError('Getting root type of non-type %s.' % node) 342 self.LogExit('RootType is "%s"' % rootType) 343 return rootType, mode 344 345 346 def GetTypeByMode(self, node, release, mode): 347 self.LogEnter('GetTypeByMode of %s mode=%s release=%s' % 348 (node, mode, release)) 349 name = self.GetTypeName(node, release) 350 ntype, mode = self.GetRootTypeMode(node, release, mode) 351 out = CGen.TypeMap[ntype][mode] % name 352 self.LogExit('GetTypeByMode %s = %s' % (node, out)) 353 return out 354 355 356 # Get the passing mode of the object (in, out, inout). 357 def GetParamMode(self, node): 358 self.Log('GetParamMode for %s' % node) 359 if node.GetProperty('in'): return 'in' 360 if node.GetProperty('out'): return 'out' 361 if node.GetProperty('inout'): return 'inout' 362 return 'return' 363 364 # 365 # GetComponents 366 # 367 # Returns the signature components of an object as a tuple of 368 # (rtype, name, arrays, callspec) where: 369 # rtype - The store or return type of the object. 370 # name - The name of the object. 371 # arrays - A list of array dimensions as [] or [<fixed_num>]. 372 # args - None if not a function, otherwise a list of parameters. 373 # 374 def GetComponents(self, node, release, mode): 375 self.LogEnter('GetComponents mode %s for %s %s' % (mode, node, release)) 376 377 # Generate passing type by modifying root type 378 rtype = self.GetTypeByMode(node, release, mode) 379 if node.IsA('Enum', 'Interface', 'Struct'): 380 rname = node.GetName() 381 else: 382 rname = node.GetType(release).GetName() 383 384 if rname in CGen.RemapName: 385 rname = CGen.RemapName[rname] 386 if '%' in rtype: 387 rtype = rtype % rname 388 name = node.GetName() 389 arrayspec = [self.GetArraySpec(array) for array in node.GetListOf('Array')] 390 callnode = node.GetOneOf('Callspec') 391 if callnode: 392 callspec = [] 393 for param in callnode.GetListOf('Param'): 394 if not param.IsRelease(release): 395 continue 396 mode = self.GetParamMode(param) 397 ptype, pname, parray, pspec = self.GetComponents(param, release, mode) 398 callspec.append((ptype, pname, parray, pspec)) 399 else: 400 callspec = None 401 402 self.LogExit('GetComponents: %s, %s, %s, %s' % 403 (rtype, name, arrayspec, callspec)) 404 return (rtype, name, arrayspec, callspec) 405 406 407 def Compose(self, rtype, name, arrayspec, callspec, prefix, func_as_ptr, 408 include_name, unsized_as_ptr): 409 self.LogEnter('Compose: %s %s' % (rtype, name)) 410 arrayspec = ''.join(arrayspec) 411 412 # Switch unsized array to a ptr. NOTE: Only last element can be unsized. 413 if unsized_as_ptr and arrayspec[-2:] == '[]': 414 prefix += '*' 415 arrayspec=arrayspec[:-2] 416 417 if not include_name: 418 name = prefix + arrayspec 419 else: 420 name = prefix + name + arrayspec 421 if callspec is None: 422 out = '%s %s' % (rtype, name) 423 else: 424 params = [] 425 for ptype, pname, parray, pspec in callspec: 426 params.append(self.Compose(ptype, pname, parray, pspec, '', True, 427 include_name=True, 428 unsized_as_ptr=unsized_as_ptr)) 429 if func_as_ptr: 430 name = '(*%s)' % name 431 if not params: 432 params = ['void'] 433 out = '%s %s(%s)' % (rtype, name, ', '.join(params)) 434 self.LogExit('Exit Compose: %s' % out) 435 return out 436 437 # 438 # GetSignature 439 # 440 # Returns the 'C' style signature of the object 441 # prefix - A prefix for the object's name 442 # func_as_ptr - Formats a function as a function pointer 443 # include_name - If true, include member name in the signature. 444 # If false, leave it out. In any case, prefix is always 445 # included. 446 # include_version - if True, include version in the member name 447 # 448 def GetSignature(self, node, release, mode, prefix='', func_as_ptr=True, 449 include_name=True, include_version=False): 450 self.LogEnter('GetSignature %s %s as func=%s' % 451 (node, mode, func_as_ptr)) 452 rtype, name, arrayspec, callspec = self.GetComponents(node, release, mode) 453 if include_version: 454 name = self.GetStructName(node, release, True) 455 456 # If not a callspec (such as a struct) use a ptr instead of [] 457 unsized_as_ptr = not callspec 458 459 out = self.Compose(rtype, name, arrayspec, callspec, prefix, 460 func_as_ptr, include_name, unsized_as_ptr) 461 462 self.LogExit('Exit GetSignature: %s' % out) 463 return out 464 465 # Define a Typedef. 466 def DefineTypedef(self, node, releases, prefix='', comment=False): 467 __pychecker__ = 'unusednames=comment' 468 build_list = node.GetUniqueReleases(releases) 469 470 out = 'typedef %s;\n' % self.GetSignature(node, build_list[-1], 'return', 471 prefix, True, 472 include_version=False) 473 # Version mangle any other versions 474 for index, rel in enumerate(build_list[:-1]): 475 out += '\n' 476 out += 'typedef %s;\n' % self.GetSignature(node, rel, 'return', 477 prefix, True, 478 include_version=True) 479 self.Log('DefineTypedef: %s' % out) 480 return out 481 482 # Define an Enum. 483 def DefineEnum(self, node, releases, prefix='', comment=False): 484 __pychecker__ = 'unusednames=comment,releases' 485 self.LogEnter('DefineEnum %s' % node) 486 name = '%s%s' % (prefix, node.GetName()) 487 notypedef = node.GetProperty('notypedef') 488 unnamed = node.GetProperty('unnamed') 489 490 if unnamed: 491 out = 'enum {' 492 elif notypedef: 493 out = 'enum %s {' % name 494 else: 495 out = 'typedef enum {' 496 enumlist = [] 497 for child in node.GetListOf('EnumItem'): 498 value = child.GetProperty('VALUE') 499 comment_txt = GetNodeComments(child, tabs=1) 500 if value: 501 item_txt = '%s%s = %s' % (prefix, child.GetName(), value) 502 else: 503 item_txt = '%s%s' % (prefix, child.GetName()) 504 enumlist.append('%s %s' % (comment_txt, item_txt)) 505 self.LogExit('Exit DefineEnum') 506 507 if unnamed or notypedef: 508 out = '%s\n%s\n};\n' % (out, ',\n'.join(enumlist)) 509 else: 510 out = '%s\n%s\n} %s;\n' % (out, ',\n'.join(enumlist), name) 511 return out 512 513 def DefineMember(self, node, releases, prefix='', comment=False): 514 __pychecker__ = 'unusednames=prefix,comment' 515 release = releases[0] 516 self.LogEnter('DefineMember %s' % node) 517 if node.GetProperty('ref'): 518 out = '%s;' % self.GetSignature(node, release, 'ref', '', True) 519 else: 520 out = '%s;' % self.GetSignature(node, release, 'store', '', True) 521 self.LogExit('Exit DefineMember') 522 return out 523 524 def GetStructName(self, node, release, include_version=False): 525 suffix = '' 526 if include_version: 527 ver_num = node.GetVersion(release) 528 suffix = ('_%s' % ver_num).replace('.', '_') 529 return node.GetName() + suffix 530 531 def DefineStructInternals(self, node, release, 532 include_version=False, comment=True): 533 out = '' 534 if node.GetProperty('union'): 535 out += 'union %s {\n' % ( 536 self.GetStructName(node, release, include_version)) 537 else: 538 out += 'struct %s {\n' % ( 539 self.GetStructName(node, release, include_version)) 540 541 # Generate Member Functions 542 members = [] 543 for child in node.GetListOf('Member'): 544 member = self.Define(child, [release], tabs=1, comment=comment) 545 if not member: 546 continue 547 members.append(member) 548 out += '%s\n};\n' % '\n'.join(members) 549 return out 550 551 552 def DefineStruct(self, node, releases, prefix='', comment=False): 553 __pychecker__ = 'unusednames=comment,prefix' 554 self.LogEnter('DefineStruct %s' % node) 555 out = '' 556 build_list = node.GetUniqueReleases(releases) 557 558 # TODO(noelallen) : Bug 157017 finish multiversion support 559 if node.IsA('Struct'): 560 if len(build_list) != 1: 561 node.Error('Can not support multiple versions of node.') 562 assert len(build_list) == 1 563 564 565 if node.IsA('Interface'): 566 # Build the most recent one versioned, with comments 567 out = self.DefineStructInternals(node, build_list[-1], 568 include_version=True, comment=True) 569 570 # Define an unversioned typedef for the most recent version 571 out += '\ntypedef struct %s %s;\n' % ( 572 self.GetStructName(node, build_list[-1], include_version=True), 573 self.GetStructName(node, build_list[-1], include_version=False)) 574 else: 575 # Build the most recent one versioned, with comments 576 out = self.DefineStructInternals(node, build_list[-1], 577 include_version=False, comment=True) 578 579 580 # Build the rest without comments and with the version number appended 581 for rel in build_list[0:-1]: 582 out += '\n' + self.DefineStructInternals(node, rel, 583 include_version=True, 584 comment=False) 585 586 self.LogExit('Exit DefineStruct') 587 return out 588 589 590 # 591 # Copyright and Comment 592 # 593 # Generate a comment or copyright block 594 # 595 def Copyright(self, node, cpp_style=False): 596 lines = node.GetName().split('\n') 597 if cpp_style: 598 return '//' + '\n//'.join(filter(lambda f: f != '', lines)) + '\n' 599 return CommentLines(lines) 600 601 602 def Indent(self, data, tabs=0): 603 """Handles indentation and 80-column line wrapping.""" 604 tab = ' ' * tabs 605 lines = [] 606 for line in data.split('\n'): 607 # Add indentation 608 line = tab + line 609 if len(line) <= 80: 610 lines.append(line.rstrip()) 611 else: 612 left = line.rfind('(') + 1 613 args = line[left:].split(',') 614 orig_args = args 615 orig_left = left 616 # Try to split on '(arg1)' or '(arg1, arg2)', not '()' 617 while args[0][0] == ')': 618 left = line.rfind('(', 0, left - 1) + 1 619 if left == 0: # No more parens, take the original option 620 args = orig_args 621 left = orig_left 622 break 623 args = line[left:].split(',') 624 625 line_max = 0 626 for arg in args: 627 if len(arg) > line_max: line_max = len(arg) 628 629 if left + line_max >= 80: 630 indent = '%s ' % tab 631 args = (',\n%s' % indent).join([arg.strip() for arg in args]) 632 lines.append('%s\n%s%s' % (line[:left], indent, args)) 633 else: 634 indent = ' ' * (left - 1) 635 args = (',\n%s' % indent).join(args) 636 lines.append('%s%s' % (line[:left], args)) 637 return '\n'.join(lines) 638 639 640 # Define a top level object. 641 def Define(self, node, releases, tabs=0, prefix='', comment=False): 642 # If this request does not match unique release, or if the release is not 643 # available (possibly deprecated) then skip. 644 unique = node.GetUniqueReleases(releases) 645 if not unique or not node.InReleases(releases): 646 return '' 647 648 self.LogEnter('Define %s tab=%d prefix="%s"' % (node,tabs,prefix)) 649 declmap = dict({ 650 'Enum': CGen.DefineEnum, 651 'Function': CGen.DefineMember, 652 'Interface': CGen.DefineStruct, 653 'Member': CGen.DefineMember, 654 'Struct': CGen.DefineStruct, 655 'Typedef': CGen.DefineTypedef 656 }) 657 658 out = '' 659 func = declmap.get(node.cls, None) 660 if not func: 661 ErrOut.Log('Failed to define %s named %s' % (node.cls, node.GetName())) 662 define_txt = func(self, node, releases, prefix=prefix, comment=comment) 663 664 comment_txt = GetNodeComments(node, tabs=0) 665 if comment_txt and comment: 666 out += comment_txt 667 out += define_txt 668 669 indented_out = self.Indent(out, tabs) 670 self.LogExit('Exit Define') 671 return indented_out 672 673 674# Clean a string representing an object definition and return then string 675# as a single space delimited set of tokens. 676def CleanString(instr): 677 instr = instr.strip() 678 instr = instr.split() 679 return ' '.join(instr) 680 681 682# Test a file, by comparing all it's objects, with their comments. 683def TestFile(filenode): 684 cgen = CGen() 685 686 errors = 0 687 for node in filenode.GetChildren()[2:]: 688 instr = node.GetOneOf('Comment') 689 if not instr: continue 690 instr.Dump() 691 instr = CleanString(instr.GetName()) 692 693 outstr = cgen.Define(node, releases=['M14']) 694 if GetOption('verbose'): 695 print outstr + '\n' 696 outstr = CleanString(outstr) 697 698 if instr != outstr: 699 ErrOut.Log('Failed match of\n>>%s<<\nto:\n>>%s<<\nFor:\n' % 700 (instr, outstr)) 701 node.Dump(1, comments=True) 702 errors += 1 703 return errors 704 705 706# Build and resolve the AST and compare each file individual. 707def TestFiles(filenames): 708 if not filenames: 709 idldir = os.path.split(sys.argv[0])[0] 710 idldir = os.path.join(idldir, 'test_cgen', '*.idl') 711 filenames = glob.glob(idldir) 712 713 filenames = sorted(filenames) 714 ast = ParseFiles(filenames) 715 716 total_errs = 0 717 for filenode in ast.GetListOf('File'): 718 errs = TestFile(filenode) 719 if errs: 720 ErrOut.Log('%s test failed with %d error(s).' % 721 (filenode.GetName(), errs)) 722 total_errs += errs 723 724 if total_errs: 725 ErrOut.Log('Failed generator test.') 726 else: 727 InfoOut.Log('Passed generator test.') 728 return total_errs 729 730def main(args): 731 filenames = ParseOptions(args) 732 if GetOption('test'): 733 return TestFiles(filenames) 734 ast = ParseFiles(filenames) 735 cgen = CGen() 736 for f in ast.GetListOf('File'): 737 if f.GetProperty('ERRORS') > 0: 738 print 'Skipping %s' % f.GetName() 739 continue 740 for node in f.GetChildren()[2:]: 741 print cgen.Define(node, comment=True, prefix='tst_') 742 743 744if __name__ == '__main__': 745 sys.exit(main(sys.argv[1:])) 746 747