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 """ Parser for PPAPI IDL """ 7 8 # 9 # IDL Parser 10 # 11 # The parser is uses the PLY yacc library to build a set of parsing rules based 12 # on WebIDL. 13 # 14 # WebIDL, and WebIDL regular expressions can be found at: 15 # http://dev.w3.org/2006/webapi/WebIDL/ 16 # PLY can be found at: 17 # http://www.dabeaz.com/ply/ 18 # 19 # The parser generates a tree by recursively matching sets of items against 20 # defined patterns. When a match is made, that set of items is reduced 21 # to a new item. The new item can provide a match for parent patterns. 22 # In this way an AST is built (reduced) depth first. 23 24 25 import getopt 26 import glob 27 import os.path 28 import re 29 import sys 30 import time 31 32 from idl_ast import IDLAst 33 from idl_log import ErrOut, InfoOut, WarnOut 34 from idl_lexer import IDLLexer 35 from idl_node import IDLAttribute, IDLFile, IDLNode 36 from idl_option import GetOption, Option, ParseOptions 37 from idl_lint import Lint 38 39 from ply import lex 40 from ply import yacc 41 42 Option('build_debug', 'Debug tree building.') 43 Option('parse_debug', 'Debug parse reduction steps.') 44 Option('token_debug', 'Debug token generation.') 45 Option('dump_tree', 'Dump the tree.') 46 Option('srcroot', 'Working directory.', default=os.path.join('..', 'api')) 47 Option('include_private', 'Include private IDL directory in default API paths.') 48 49 # 50 # ERROR_REMAP 51 # 52 # Maps the standard error formula into a more friendly error message. 53 # 54 ERROR_REMAP = { 55 'Unexpected ")" after "(".' : 'Empty argument list.', 56 'Unexpected ")" after ",".' : 'Missing argument.', 57 'Unexpected "}" after ",".' : 'Trailing comma in block.', 58 'Unexpected "}" after "{".' : 'Unexpected empty block.', 59 'Unexpected comment after "}".' : 'Unexpected trailing comment.', 60 'Unexpected "{" after keyword "enum".' : 'Enum missing name.', 61 'Unexpected "{" after keyword "struct".' : 'Struct missing name.', 62 'Unexpected "{" after keyword "interface".' : 'Interface missing name.', 63 } 64 65 # DumpReduction 66 # 67 # Prints out the set of items which matched a particular pattern and the 68 # new item or set it was reduced to. 69 def DumpReduction(cls, p): 70 if p[0] is None: 71 InfoOut.Log("OBJ: %s(%d) - None\n" % (cls, len(p))) 72 InfoOut.Log(" [%s]\n" % [str(x) for x in p[1:]]) 73 else: 74 out = "" 75 for index in range(len(p) - 1): 76 out += " >%s< " % str(p[index + 1]) 77 InfoOut.Log("OBJ: %s(%d) - %s : %s\n" % (cls, len(p), str(p[0]), out)) 78 79 80 # CopyToList 81 # 82 # Takes an input item, list, or None, and returns a new list of that set. 83 def CopyToList(item): 84 # If the item is 'Empty' make it an empty list 85 if not item: item = [] 86 87 # If the item is not a list 88 if type(item) is not type([]): item = [item] 89 90 # Make a copy we can modify 91 return list(item) 92 93 94 95 # ListFromConcat 96 # 97 # Generate a new List by joining of two sets of inputs which can be an 98 # individual item, a list of items, or None. 99 def ListFromConcat(*items): 100 itemsout = [] 101 for item in items: 102 itemlist = CopyToList(item) 103 itemsout.extend(itemlist) 104 105 return itemsout 106 107 108 # TokenTypeName 109 # 110 # Generate a string which has the type and value of the token. 111 def TokenTypeName(t): 112 if t.type == 'SYMBOL': return 'symbol %s' % t.value 113 if t.type in ['HEX', 'INT', 'OCT', 'FLOAT']: 114 return 'value %s' % t.value 115 if t.type == 'STRING' : return 'string "%s"' % t.value 116 if t.type == 'COMMENT' : return 'comment' 117 if t.type == t.value: return '"%s"' % t.value 118 return 'keyword "%s"' % t.value 119 120 121 # 122 # IDL Parser 123 # 124 # The Parser inherits the from the Lexer to provide PLY with the tokenizing 125 # definitions. Parsing patterns are encoded as function where p_<name> is 126 # is called any time a patern matching the function documentation is found. 127 # Paterns are expressed in the form of: 128 # """ <new item> : <item> .... 129 # | <item> ....""" 130 # 131 # Where new item is the result of a match against one or more sets of items 132 # separated by the "|". 133 # 134 # The function is called with an object 'p' where p[0] is the output object 135 # and p[n] is the set of inputs for positive values of 'n'. Len(p) can be 136 # used to distinguish between multiple item sets in the pattern. 137 # 138 # For more details on parsing refer to the PLY documentation at 139 # http://www.dabeaz.com/ply/ 140 # 141 # 142 # The parser uses the following conventions: 143 # a <type>_block defines a block of <type> definitions in the form of: 144 # [comment] [ext_attr_block] <type> <name> '{' <type>_list '}' ';' 145 # A block is reduced by returning an object of <type> with a name of <name> 146 # which in turn has <type>_list as children. 147 # 148 # A [comment] is a optional C style comment block enclosed in /* ... */ which 149 # is appended to the adjacent node as a child. 150 # 151 # A [ext_attr_block] is an optional list of Extended Attributes which is 152 # appended to the adjacent node as a child. 153 # 154 # a <type>_list defines a list of <type> items which will be passed as a 155 # list of children to the parent pattern. A list is in the form of: 156 # [comment] [ext_attr_block] <...DEF...> ';' <type>_list | (empty) 157 # or 158 # [comment] [ext_attr_block] <...DEF...> <type>_cont 159 # 160 # In the first form, the list is reduced recursively, where the right side 161 # <type>_list is first reduced then joined with pattern currently being 162 # matched. The list is terminated with the (empty) pattern is matched. 163 # 164 # In the second form the list is reduced recursively, where the right side 165 # <type>_cont is first reduced then joined with the pattern currently being 166 # matched. The type_<cont> is in the form of: 167 # ',' <type>_list | (empty) 168 # The <type>_cont form is used to consume the ',' which only occurs when 169 # there is more than one object in the list. The <type>_cont also provides 170 # the terminating (empty) definition. 171 # 172 173 174 class IDLParser(IDLLexer): 175 # TOP 176 # 177 # This pattern defines the top of the parse tree. The parse tree is in the 178 # the form of: 179 # 180 # top 181 # *modifiers 182 # *comments 183 # *ext_attr_block 184 # ext_attr_list 185 # attr_arg_list 186 # *integer, value 187 # *param_list 188 # *typeref 189 # 190 # top_list 191 # describe_block 192 # describe_list 193 # enum_block 194 # enum_item 195 # interface_block 196 # member 197 # label_block 198 # label_item 199 # struct_block 200 # member 201 # typedef_decl 202 # typedef_data 203 # typedef_func 204 # 205 # (* sub matches found at multiple levels and are not truly children of top) 206 # 207 # We force all input files to start with two comments. The first comment is a 208 # Copyright notice followed by a set of file wide Extended Attributes, followed 209 # by the file comment and finally by file level patterns. 210 # 211 # Find the Copyright, File comment, and optional file wide attributes. We 212 # use a match with COMMENT instead of comments to force the token to be 213 # present. The extended attributes and the top_list become siblings which 214 # in turn are children of the file object created from the results of top. 215 def p_top(self, p): 216 """top : COMMENT COMMENT ext_attr_block top_list""" 217 218 Copyright = self.BuildComment('Copyright', p, 1) 219 Filedoc = self.BuildComment('Comment', p, 2) 220 221 p[0] = ListFromConcat(Copyright, Filedoc, p[3], p[4]) 222 if self.parse_debug: DumpReduction('top', p) 223 224 def p_top_short(self, p): 225 """top : COMMENT ext_attr_block top_list""" 226 Copyright = self.BuildComment('Copyright', p, 1) 227 Filedoc = IDLNode('Comment', self.lexobj.filename, p.lineno(2)-1, 228 p.lexpos(2)-1, [self.BuildAttribute('NAME', ''), 229 self.BuildAttribute('FORM', 'cc')]) 230 p[0] = ListFromConcat(Copyright, Filedoc, p[2], p[3]) 231 if self.parse_debug: DumpReduction('top', p) 232 233 # Build a list of top level items. 234 def p_top_list(self, p): 235 """top_list : callback_decl top_list 236 | describe_block top_list 237 | dictionary_block top_list 238 | enum_block top_list 239 | inline top_list 240 | interface_block top_list 241 | label_block top_list 242 | namespace top_list 243 | struct_block top_list 244 | typedef_decl top_list 245 | bad_decl top_list 246 | """ 247 if len(p) > 2: 248 p[0] = ListFromConcat(p[1], p[2]) 249 if self.parse_debug: DumpReduction('top_list', p) 250 251 # Recover from error and continue parsing at the next top match. 252 def p_top_error(self, p): 253 """top_list : error top_list""" 254 p[0] = p[2] 255 256 # Recover from error and continue parsing at the next top match. 257 def p_bad_decl(self, p): 258 """bad_decl : modifiers SYMBOL error '}' ';'""" 259 p[0] = [] 260 261 # 262 # Modifier List 263 # 264 # 265 def p_modifiers(self, p): 266 """modifiers : comments ext_attr_block""" 267 p[0] = ListFromConcat(p[1], p[2]) 268 if self.parse_debug: DumpReduction('modifiers', p) 269 270 # 271 # Scoped name is a name with an optional scope. 272 # 273 # Used for types and namespace names. eg. foo_bar.hello_world, or 274 # foo_bar.hello_world.SomeType. 275 # 276 def p_scoped_name(self, p): 277 """scoped_name : SYMBOL scoped_name_rest""" 278 p[0] = ''.join(p[1:]) 279 if self.parse_debug: DumpReduction('scoped_name', p) 280 281 def p_scoped_name_rest(self, p): 282 """scoped_name_rest : '.' scoped_name 283 |""" 284 p[0] = ''.join(p[1:]) 285 if self.parse_debug: DumpReduction('scoped_name_rest', p) 286 287 # 288 # Type reference 289 # 290 # 291 def p_typeref(self, p): 292 """typeref : scoped_name""" 293 p[0] = p[1] 294 if self.parse_debug: DumpReduction('typeref', p) 295 296 297 # 298 # Comments 299 # 300 # Comments are optional list of C style comment objects. Comments are returned 301 # as a list or None. 302 # 303 def p_comments(self, p): 304 """comments : COMMENT comments 305 | """ 306 if len(p) > 1: 307 child = self.BuildComment('Comment', p, 1) 308 p[0] = ListFromConcat(child, p[2]) 309 if self.parse_debug: DumpReduction('comments', p) 310 else: 311 if self.parse_debug: DumpReduction('no comments', p) 312 313 314 # 315 # Namespace 316 # 317 # A namespace provides a named scope to an enclosed top_list. 318 # 319 def p_namespace(self, p): 320 """namespace : modifiers NAMESPACE namespace_name '{' top_list '}' ';'""" 321 children = ListFromConcat(p[1], p[5]) 322 p[0] = self.BuildNamed('Namespace', p, 3, children) 323 324 # We allow namespace names of the form foo.bar.baz. 325 def p_namespace_name(self, p): 326 """namespace_name : scoped_name""" 327 p[0] = p[1] 328 329 330 # 331 # Dictionary 332 # 333 # A dictionary is a named list of optional and required members. 334 # 335 def p_dictionary_block(self, p): 336 """dictionary_block : modifiers DICTIONARY SYMBOL '{' struct_list '}' ';'""" 337 p[0] = self.BuildNamed('Dictionary', p, 3, ListFromConcat(p[1], p[5])) 338 339 def p_dictionary_errorA(self, p): 340 """dictionary_block : modifiers DICTIONARY error ';'""" 341 p[0] = [] 342 343 def p_dictionary_errorB(self, p): 344 """dictionary_block : modifiers DICTIONARY error '{' struct_list '}' ';'""" 345 p[0] = [] 346 347 # 348 # Callback 349 # 350 # A callback is essentially a single function declaration (outside of an 351 # Interface). 352 # 353 def p_callback_decl(self, p): 354 """callback_decl : modifiers CALLBACK SYMBOL '=' SYMBOL param_list ';'""" 355 children = ListFromConcat(p[1], p[6]) 356 p[0] = self.BuildNamed('Callback', p, 3, children) 357 358 359 # 360 # Inline 361 # 362 # Inline blocks define option code to be emitted based on language tag, 363 # in the form of: 364 # #inline <LANGUAGE> 365 # <CODE> 366 # #endinl 367 # 368 def p_inline(self, p): 369 """inline : modifiers INLINE""" 370 words = p[2].split() 371 name = self.BuildAttribute('NAME', words[1]) 372 lines = p[2].split('\n') 373 value = self.BuildAttribute('VALUE', '\n'.join(lines[1:-1]) + '\n') 374 children = ListFromConcat(name, value, p[1]) 375 p[0] = self.BuildProduction('Inline', p, 2, children) 376 if self.parse_debug: DumpReduction('inline', p) 377 378 # Extended Attributes 379 # 380 # Extended Attributes denote properties which will be applied to a node in the 381 # AST. A list of extended attributes are denoted by a brackets '[' ... ']' 382 # enclosing a comma separated list of extended attributes in the form of: 383 # 384 # Name 385 # Name=HEX | INT | OCT | FLOAT 386 # Name="STRING" 387 # Name=Function(arg ...) 388 # TODO(noelallen) -Not currently supported: 389 # ** Name(arg ...) ... 390 # ** Name=Scope::Value 391 # 392 # Extended Attributes are returned as a list or None. 393 394 def p_ext_attr_block(self, p): 395 """ext_attr_block : '[' ext_attr_list ']' 396 | """ 397 if len(p) > 1: 398 p[0] = p[2] 399 if self.parse_debug: DumpReduction('ext_attr_block', p) 400 else: 401 if self.parse_debug: DumpReduction('no ext_attr_block', p) 402 403 def p_ext_attr_list(self, p): 404 """ext_attr_list : SYMBOL '=' SYMBOL ext_attr_cont 405 | SYMBOL '=' value ext_attr_cont 406 | SYMBOL '=' SYMBOL param_list ext_attr_cont 407 | SYMBOL ext_attr_cont""" 408 # If there are 4 tokens plus a return slot, this must be in the form 409 # SYMBOL = SYMBOL|value ext_attr_cont 410 if len(p) == 5: 411 p[0] = ListFromConcat(self.BuildAttribute(p[1], p[3]), p[4]) 412 # If there are 5 tokens plus a return slot, this must be in the form 413 # SYMBOL = SYMBOL (param_list) ext_attr_cont 414 elif len(p) == 6: 415 member = self.BuildNamed('Member', p, 3, [p[4]]) 416 p[0] = ListFromConcat(self.BuildAttribute(p[1], member), p[5]) 417 # Otherwise, this must be: SYMBOL ext_attr_cont 418 else: 419 p[0] = ListFromConcat(self.BuildAttribute(p[1], 'True'), p[2]) 420 if self.parse_debug: DumpReduction('ext_attribute_list', p) 421 422 def p_ext_attr_list_values(self, p): 423 """ext_attr_list : SYMBOL '=' '(' values ')' ext_attr_cont 424 | SYMBOL '=' '(' symbols ')' ext_attr_cont""" 425 p[0] = ListFromConcat(self.BuildAttribute(p[1], p[4]), p[6]) 426 427 def p_values(self, p): 428 """values : value values_cont""" 429 p[0] = ListFromConcat(p[1], p[2]) 430 431 def p_symbols(self, p): 432 """symbols : SYMBOL symbols_cont""" 433 p[0] = ListFromConcat(p[1], p[2]) 434 435 def p_symbols_cont(self, p): 436 """symbols_cont : ',' SYMBOL symbols_cont 437 | """ 438 if len(p) > 1: p[0] = ListFromConcat(p[2], p[3]) 439 440 def p_values_cont(self, p): 441 """values_cont : ',' value values_cont 442 | """ 443 if len(p) > 1: p[0] = ListFromConcat(p[2], p[3]) 444 445 def p_ext_attr_cont(self, p): 446 """ext_attr_cont : ',' ext_attr_list 447 |""" 448 if len(p) > 1: p[0] = p[2] 449 if self.parse_debug: DumpReduction('ext_attribute_cont', p) 450 451 def p_ext_attr_func(self, p): 452 """ext_attr_list : SYMBOL '(' attr_arg_list ')' ext_attr_cont""" 453 p[0] = ListFromConcat(self.BuildAttribute(p[1] + '()', p[3]), p[5]) 454 if self.parse_debug: DumpReduction('attr_arg_func', p) 455 456 def p_ext_attr_arg_list(self, p): 457 """attr_arg_list : SYMBOL attr_arg_cont 458 | value attr_arg_cont""" 459 p[0] = ListFromConcat(p[1], p[2]) 460 461 def p_attr_arg_cont(self, p): 462 """attr_arg_cont : ',' attr_arg_list 463 | """ 464 if self.parse_debug: DumpReduction('attr_arg_cont', p) 465 if len(p) > 1: p[0] = p[2] 466 467 def p_attr_arg_error(self, p): 468 """attr_arg_cont : error attr_arg_cont""" 469 p[0] = p[2] 470 if self.parse_debug: DumpReduction('attr_arg_error', p) 471 472 473 # 474 # Describe 475 # 476 # A describe block is defined at the top level. It provides a mechanism for 477 # attributing a group of ext_attr to a describe_list. Members of the 478 # describe list are language specific 'Type' declarations 479 # 480 def p_describe_block(self, p): 481 """describe_block : modifiers DESCRIBE '{' describe_list '}' ';'""" 482 children = ListFromConcat(p[1], p[4]) 483 p[0] = self.BuildProduction('Describe', p, 2, children) 484 if self.parse_debug: DumpReduction('describe_block', p) 485 486 # Recover from describe error and continue parsing at the next top match. 487 def p_describe_error(self, p): 488 """describe_list : error describe_list""" 489 p[0] = [] 490 491 def p_describe_list(self, p): 492 """describe_list : modifiers SYMBOL ';' describe_list 493 | modifiers ENUM ';' describe_list 494 | modifiers STRUCT ';' describe_list 495 | modifiers TYPEDEF ';' describe_list 496 | """ 497 if len(p) > 1: 498 Type = self.BuildNamed('Type', p, 2, p[1]) 499 p[0] = ListFromConcat(Type, p[4]) 500 501 # 502 # Constant Values (integer, value) 503 # 504 # Constant values can be found at various levels. A Constant value is returns 505 # as the string value after validated against a FLOAT, HEX, INT, OCT or 506 # STRING pattern as appropriate. 507 # 508 def p_value(self, p): 509 """value : FLOAT 510 | HEX 511 | INT 512 | OCT 513 | STRING""" 514 p[0] = p[1] 515 if self.parse_debug: DumpReduction('value', p) 516 517 def p_value_lshift(self, p): 518 """value : integer LSHIFT INT""" 519 p[0] = "%s << %s" % (p[1], p[3]) 520 if self.parse_debug: DumpReduction('value', p) 521 522 # Integers are numbers which may not be floats used in cases like array sizes. 523 def p_integer(self, p): 524 """integer : HEX 525 | INT 526 | OCT""" 527 p[0] = p[1] 528 if self.parse_debug: DumpReduction('integer', p) 529 530 # 531 # Expression 532 # 533 # A simple arithmetic expression. 534 # 535 precedence = ( 536 ('left','|','&','^'), 537 ('left','LSHIFT','RSHIFT'), 538 ('left','+','-'), 539 ('left','*','/'), 540 ('right','UMINUS','~'), 541 ) 542 543 def p_expression_binop(self, p): 544 """expression : expression LSHIFT expression 545 | expression RSHIFT expression 546 | expression '|' expression 547 | expression '&' expression 548 | expression '^' expression 549 | expression '+' expression 550 | expression '-' expression 551 | expression '*' expression 552 | expression '/' expression""" 553 p[0] = "%s %s %s" % (str(p[1]), str(p[2]), str(p[3])) 554 if self.parse_debug: DumpReduction('expression_binop', p) 555 556 def p_expression_unop(self, p): 557 """expression : '-' expression %prec UMINUS 558 | '~' expression %prec '~'""" 559 p[0] = "%s%s" % (str(p[1]), str(p[2])) 560 if self.parse_debug: DumpReduction('expression_unop', p) 561 562 def p_expression_term(self, p): 563 """expression : '(' expression ')'""" 564 p[0] = "%s%s%s" % (str(p[1]), str(p[2]), str(p[3])) 565 if self.parse_debug: DumpReduction('expression_term', p) 566 567 def p_expression_symbol(self, p): 568 """expression : SYMBOL""" 569 p[0] = p[1] 570 if self.parse_debug: DumpReduction('expression_symbol', p) 571 572 def p_expression_integer(self, p): 573 """expression : integer""" 574 p[0] = p[1] 575 if self.parse_debug: DumpReduction('expression_integer', p) 576 577 # 578 # Array List 579 # 580 # Defined a list of array sizes (if any). 581 # 582 def p_arrays(self, p): 583 """arrays : '[' ']' arrays 584 | '[' integer ']' arrays 585 | """ 586 # If there are 3 tokens plus a return slot it is an unsized array 587 if len(p) == 4: 588 array = self.BuildProduction('Array', p, 1) 589 p[0] = ListFromConcat(array, p[3]) 590 # If there are 4 tokens plus a return slot it is a fixed array 591 elif len(p) == 5: 592 count = self.BuildAttribute('FIXED', p[2]) 593 array = self.BuildProduction('Array', p, 2, [count]) 594 p[0] = ListFromConcat(array, p[4]) 595 # If there is only a return slot, do not fill it for this terminator. 596 elif len(p) == 1: return 597 if self.parse_debug: DumpReduction('arrays', p) 598 599 600 # An identifier is a legal value for a parameter or attribute name. Lots of 601 # existing IDL files use "callback" as a parameter/attribute name, so we allow 602 # a SYMBOL or the CALLBACK keyword. 603 def p_identifier(self, p): 604 """identifier : SYMBOL 605 | CALLBACK""" 606 p[0] = p[1] 607 # Save the line number of the underlying token (otherwise it gets 608 # discarded), since we use it in the productions with an identifier in 609 # them. 610 p.set_lineno(0, p.lineno(1)) 611 612 613 # 614 # Union 615 # 616 # A union allows multiple choices of types for a parameter or member. 617 # 618 619 def p_union_option(self, p): 620 """union_option : modifiers SYMBOL arrays""" 621 typeref = self.BuildAttribute('TYPEREF', p[2]) 622 children = ListFromConcat(p[1], typeref, p[3]) 623 p[0] = self.BuildProduction('Option', p, 2, children) 624 625 def p_union_list(self, p): 626 """union_list : union_option OR union_list 627 | union_option""" 628 if len(p) > 2: 629 p[0] = ListFromConcat(p[1], p[3]) 630 else: 631 p[0] = p[1] 632 633 # 634 # Parameter List 635 # 636 # A parameter list is a collection of arguments which are passed to a 637 # function. 638 # 639 def p_param_list(self, p): 640 """param_list : '(' param_item param_cont ')' 641 | '(' ')' """ 642 if len(p) > 3: 643 args = ListFromConcat(p[2], p[3]) 644 else: 645 args = [] 646 p[0] = self.BuildProduction('Callspec', p, 1, args) 647 if self.parse_debug: DumpReduction('param_list', p) 648 649 def p_param_item(self, p): 650 """param_item : modifiers optional typeref arrays identifier""" 651 typeref = self.BuildAttribute('TYPEREF', p[3]) 652 children = ListFromConcat(p[1], p[2], typeref, p[4]) 653 p[0] = self.BuildNamed('Param', p, 5, children) 654 if self.parse_debug: DumpReduction('param_item', p) 655 656 def p_param_item_union(self, p): 657 """param_item : modifiers optional '(' union_list ')' identifier""" 658 union = self.BuildAttribute('Union', True) 659 children = ListFromConcat(p[1], p[2], p[4], union) 660 p[0] = self.BuildNamed('Param', p, 6, children) 661 if self.parse_debug: DumpReduction('param_item', p) 662 663 def p_optional(self, p): 664 """optional : OPTIONAL 665 | """ 666 if len(p) == 2: 667 p[0] = self.BuildAttribute('OPTIONAL', True) 668 669 670 def p_param_cont(self, p): 671 """param_cont : ',' param_item param_cont 672 | """ 673 if len(p) > 1: 674 p[0] = ListFromConcat(p[2], p[3]) 675 if self.parse_debug: DumpReduction('param_cont', p) 676 677 def p_param_error(self, p): 678 """param_cont : error param_cont""" 679 p[0] = p[2] 680 681 682 # 683 # Typedef 684 # 685 # A typedef creates a new referencable type. The typedef can specify an array 686 # definition as well as a function declaration. 687 # 688 def p_typedef_data(self, p): 689 """typedef_decl : modifiers TYPEDEF SYMBOL SYMBOL ';' """ 690 typeref = self.BuildAttribute('TYPEREF', p[3]) 691 children = ListFromConcat(p[1], typeref) 692 p[0] = self.BuildNamed('Typedef', p, 4, children) 693 if self.parse_debug: DumpReduction('typedef_data', p) 694 695 def p_typedef_array(self, p): 696 """typedef_decl : modifiers TYPEDEF SYMBOL arrays SYMBOL ';' """ 697 typeref = self.BuildAttribute('TYPEREF', p[3]) 698 children = ListFromConcat(p[1], typeref, p[4]) 699 p[0] = self.BuildNamed('Typedef', p, 5, children) 700 if self.parse_debug: DumpReduction('typedef_array', p) 701 702 def p_typedef_func(self, p): 703 """typedef_decl : modifiers TYPEDEF SYMBOL SYMBOL param_list ';' """ 704 typeref = self.BuildAttribute('TYPEREF', p[3]) 705 children = ListFromConcat(p[1], typeref, p[5]) 706 p[0] = self.BuildNamed('Typedef', p, 4, children) 707 if self.parse_debug: DumpReduction('typedef_func', p) 708 709 # 710 # Enumeration 711 # 712 # An enumeration is a set of named integer constants. An enumeration 713 # is valid type which can be referenced in other definitions. 714 # 715 def p_enum_block(self, p): 716 """enum_block : modifiers ENUM SYMBOL '{' enum_list '}' ';'""" 717 p[0] = self.BuildNamed('Enum', p, 3, ListFromConcat(p[1], p[5])) 718 if self.parse_debug: DumpReduction('enum_block', p) 719 720 # Recover from enum error and continue parsing at the next top match. 721 def p_enum_errorA(self, p): 722 """enum_block : modifiers ENUM error '{' enum_list '}' ';'""" 723 p[0] = [] 724 725 def p_enum_errorB(self, p): 726 """enum_block : modifiers ENUM error ';'""" 727 p[0] = [] 728 729 def p_enum_list(self, p): 730 """enum_list : modifiers SYMBOL '=' expression enum_cont 731 | modifiers SYMBOL enum_cont""" 732 if len(p) > 4: 733 val = self.BuildAttribute('VALUE', p[4]) 734 enum = self.BuildNamed('EnumItem', p, 2, ListFromConcat(val, p[1])) 735 p[0] = ListFromConcat(enum, p[5]) 736 else: 737 enum = self.BuildNamed('EnumItem', p, 2, p[1]) 738 p[0] = ListFromConcat(enum, p[3]) 739 if self.parse_debug: DumpReduction('enum_list', p) 740 741 def p_enum_cont(self, p): 742 """enum_cont : ',' enum_list 743 |""" 744 if len(p) > 1: p[0] = p[2] 745 if self.parse_debug: DumpReduction('enum_cont', p) 746 747 def p_enum_cont_error(self, p): 748 """enum_cont : error enum_cont""" 749 p[0] = p[2] 750 if self.parse_debug: DumpReduction('enum_error', p) 751 752 753 # 754 # Label 755 # 756 # A label is a special kind of enumeration which allows us to go from a 757 # set of labels 758 # 759 def p_label_block(self, p): 760 """label_block : modifiers LABEL SYMBOL '{' label_list '}' ';'""" 761 p[0] = self.BuildNamed('Label', p, 3, ListFromConcat(p[1], p[5])) 762 if self.parse_debug: DumpReduction('label_block', p) 763 764 def p_label_list(self, p): 765 """label_list : modifiers SYMBOL '=' FLOAT label_cont""" 766 val = self.BuildAttribute('VALUE', p[4]) 767 label = self.BuildNamed('LabelItem', p, 2, ListFromConcat(val, p[1])) 768 p[0] = ListFromConcat(label, p[5]) 769 if self.parse_debug: DumpReduction('label_list', p) 770 771 def p_label_cont(self, p): 772 """label_cont : ',' label_list 773 |""" 774 if len(p) > 1: p[0] = p[2] 775 if self.parse_debug: DumpReduction('label_cont', p) 776 777 def p_label_cont_error(self, p): 778 """label_cont : error label_cont""" 779 p[0] = p[2] 780 if self.parse_debug: DumpReduction('label_error', p) 781 782 783 # 784 # Members 785 # 786 # A member attribute or function of a struct or interface. 787 # 788 def p_member_attribute(self, p): 789 """member_attribute : modifiers typeref arrays questionmark identifier""" 790 typeref = self.BuildAttribute('TYPEREF', p[2]) 791 children = ListFromConcat(p[1], typeref, p[3], p[4]) 792 p[0] = self.BuildNamed('Member', p, 5, children) 793 if self.parse_debug: DumpReduction('attribute', p) 794 795 def p_member_attribute_union(self, p): 796 """member_attribute : modifiers '(' union_list ')' questionmark identifier""" 797 union = self.BuildAttribute('Union', True) 798 children = ListFromConcat(p[1], p[3], p[5], union) 799 p[0] = self.BuildNamed('Member', p, 6, children) 800 if self.parse_debug: DumpReduction('attribute', p) 801 802 def p_member_function(self, p): 803 """member_function : modifiers static typeref arrays SYMBOL param_list""" 804 typeref = self.BuildAttribute('TYPEREF', p[3]) 805 children = ListFromConcat(p[1], p[2], typeref, p[4], p[6]) 806 p[0] = self.BuildNamed('Member', p, 5, children) 807 if self.parse_debug: DumpReduction('function', p) 808 809 def p_static(self, p): 810 """static : STATIC 811 | """ 812 if len(p) == 2: 813 p[0] = self.BuildAttribute('STATIC', True) 814 815 def p_questionmark(self, p): 816 """questionmark : '?' 817 | """ 818 if len(p) == 2: 819 p[0] = self.BuildAttribute('OPTIONAL', True) 820 821 # 822 # Interface 823 # 824 # An interface is a named collection of functions. 825 # 826 def p_interface_block(self, p): 827 """interface_block : modifiers INTERFACE SYMBOL '{' interface_list '}' ';'""" 828 p[0] = self.BuildNamed('Interface', p, 3, ListFromConcat(p[1], p[5])) 829 if self.parse_debug: DumpReduction('interface_block', p) 830 831 def p_interface_error(self, p): 832 """interface_block : modifiers INTERFACE error '{' interface_list '}' ';'""" 833 p[0] = [] 834 835 def p_interface_list(self, p): 836 """interface_list : member_function ';' interface_list 837 | """ 838 if len(p) > 1 : 839 p[0] = ListFromConcat(p[1], p[3]) 840 if self.parse_debug: DumpReduction('interface_list', p) 841 842 843 # 844 # Struct 845 # 846 # A struct is a named collection of members which in turn reference other 847 # types. The struct is a referencable type. 848 # 849 def p_struct_block(self, p): 850 """struct_block : modifiers STRUCT SYMBOL '{' struct_list '}' ';'""" 851 children = ListFromConcat(p[1], p[5]) 852 p[0] = self.BuildNamed('Struct', p, 3, children) 853 if self.parse_debug: DumpReduction('struct_block', p) 854 855 # Recover from struct error and continue parsing at the next top match. 856 def p_struct_error(self, p): 857 """enum_block : modifiers STRUCT error '{' struct_list '}' ';'""" 858 p[0] = [] 859 860 def p_struct_list(self, p): 861 """struct_list : member_attribute ';' struct_list 862 | member_function ';' struct_list 863 |""" 864 if len(p) > 1: p[0] = ListFromConcat(p[1], p[3]) 865 866 867 # 868 # Parser Errors 869 # 870 # p_error is called whenever the parser can not find a pattern match for 871 # a set of items from the current state. The p_error function defined here 872 # is triggered logging an error, and parsing recover happens as the 873 # p_<type>_error functions defined above are called. This allows the parser 874 # to continue so as to capture more than one error per file. 875 # 876 def p_error(self, t): 877 filename = self.lexobj.filename 878 self.parse_errors += 1 879 if t: 880 lineno = t.lineno 881 pos = t.lexpos 882 prev = self.yaccobj.symstack[-1] 883 if type(prev) == lex.LexToken: 884 msg = "Unexpected %s after %s." % ( 885 TokenTypeName(t), TokenTypeName(prev)) 886 else: 887 msg = "Unexpected %s." % (t.value) 888 else: 889 lineno = self.last.lineno 890 pos = self.last.lexpos 891 msg = "Unexpected end of file after %s." % TokenTypeName(self.last) 892 self.yaccobj.restart() 893 894 # Attempt to remap the error to a friendlier form 895 if msg in ERROR_REMAP: 896 msg = ERROR_REMAP[msg] 897 898 # Log the error 899 ErrOut.LogLine(filename, lineno, pos, msg) 900 901 def Warn(self, node, msg): 902 WarnOut.LogLine(node.filename, node.lineno, node.pos, msg) 903 self.parse_warnings += 1 904 905 def __init__(self): 906 IDLLexer.__init__(self) 907 self.yaccobj = yacc.yacc(module=self, tabmodule=None, debug=False, 908 optimize=0, write_tables=0) 909 910 self.build_debug = GetOption('build_debug') 911 self.parse_debug = GetOption('parse_debug') 912 self.token_debug = GetOption('token_debug') 913 self.verbose = GetOption('verbose') 914 self.parse_errors = 0 915 916 # 917 # Tokenizer 918 # 919 # The token function returns the next token provided by IDLLexer for matching 920 # against the leaf paterns. 921 # 922 def token(self): 923 tok = self.lexobj.token() 924 if tok: 925 self.last = tok 926 if self.token_debug: 927 InfoOut.Log("TOKEN %s(%s)" % (tok.type, tok.value)) 928 return tok 929 930 # 931 # BuildProduction 932 # 933 # Production is the set of items sent to a grammar rule resulting in a new 934 # item being returned. 935 # 936 # p - Is the Yacc production object containing the stack of items 937 # index - Index into the production of the name for the item being produced. 938 # cls - The type of item being producted 939 # childlist - The children of the new item 940 def BuildProduction(self, cls, p, index, childlist=None): 941 if not childlist: childlist = [] 942 filename = self.lexobj.filename 943 lineno = p.lineno(index) 944 pos = p.lexpos(index) 945 out = IDLNode(cls, filename, lineno, pos, childlist) 946 if self.build_debug: 947 InfoOut.Log("Building %s" % out) 948 return out 949 950 def BuildNamed(self, cls, p, index, childlist=None): 951 if not childlist: childlist = [] 952 childlist.append(self.BuildAttribute('NAME', p[index])) 953 return self.BuildProduction(cls, p, index, childlist) 954 955 def BuildComment(self, cls, p, index): 956 name = p[index] 957 958 # Remove comment markers 959 lines = [] 960 if name[:2] == '//': 961 # For C++ style, remove any leading whitespace and the '//' marker from 962 # each line. 963 form = 'cc' 964 for line in name.split('\n'): 965 start = line.find('//') 966 lines.append(line[start+2:]) 967 else: 968 # For C style, remove ending '*/'' 969 form = 'c' 970 for line in name[:-2].split('\n'): 971 # Remove characters until start marker for this line '*' if found 972 # otherwise it should be blank. 973 offs = line.find('*') 974 if offs >= 0: 975 line = line[offs + 1:].rstrip() 976 else: 977 line = '' 978 lines.append(line) 979 name = '\n'.join(lines) 980 981 childlist = [self.BuildAttribute('NAME', name), 982 self.BuildAttribute('FORM', form)] 983 return self.BuildProduction(cls, p, index, childlist) 984 985 # 986 # BuildAttribute 987 # 988 # An ExtendedAttribute is a special production that results in a property 989 # which is applied to the adjacent item. Attributes have no children and 990 # instead represent key/value pairs. 991 # 992 def BuildAttribute(self, key, val): 993 return IDLAttribute(key, val) 994 995 996 # 997 # ParseData 998 # 999 # Attempts to parse the current data loaded in the lexer. 1000 # 1001 def ParseData(self, data, filename='<Internal>'): 1002 self.SetData(filename, data) 1003 try: 1004 self.parse_errors = 0 1005 self.parse_warnings = 0 1006 return self.yaccobj.parse(lexer=self) 1007 1008 except lex.LexError as le: 1009 ErrOut.Log(str(le)) 1010 return [] 1011 1012 # 1013 # ParseFile 1014 # 1015 # Loads a new file into the lexer and attemps to parse it. 1016 # 1017 def ParseFile(self, filename): 1018 date = time.ctime(os.path.getmtime(filename)) 1019 data = open(filename).read() 1020 if self.verbose: 1021 InfoOut.Log("Parsing %s" % filename) 1022 try: 1023 out = self.ParseData(data, filename) 1024 1025 # If we have a src root specified, remove it from the path 1026 srcroot = GetOption('srcroot') 1027 if srcroot and filename.find(srcroot) == 0: 1028 filename = filename[len(srcroot) + 1:] 1029 filenode = IDLFile(filename, out, self.parse_errors + self.lex_errors) 1030 filenode.SetProperty('DATETIME', date) 1031 return filenode 1032 1033 except Exception as e: 1034 ErrOut.LogLine(filename, self.last.lineno, self.last.lexpos, 1035 'Internal parsing error - %s.' % str(e)) 1036 raise 1037 1038 1039 1040 # 1041 # Flatten Tree 1042 # 1043 # Flattens the tree of IDLNodes for use in testing. 1044 # 1045 def FlattenTree(node): 1046 add_self = False 1047 out = [] 1048 for child in node.GetChildren(): 1049 if child.IsA('Comment'): 1050 add_self = True 1051 else: 1052 out.extend(FlattenTree(child)) 1053 1054 if add_self: 1055 out = [str(node)] + out 1056 return out 1057 1058 1059 def TestErrors(filename, filenode): 1060 nodelist = filenode.GetChildren() 1061 1062 lexer = IDLLexer() 1063 data = open(filename).read() 1064 lexer.SetData(filename, data) 1065 1066 pass_comments = [] 1067 fail_comments = [] 1068 while True: 1069 tok = lexer.lexobj.token() 1070 if tok == None: break 1071 if tok.type == 'COMMENT': 1072 args = tok.value[3:-3].split() 1073 if args[0] == 'OK': 1074 pass_comments.append((tok.lineno, ' '.join(args[1:]))) 1075 else: 1076 if args[0] == 'FAIL': 1077 fail_comments.append((tok.lineno, ' '.join(args[1:]))) 1078 obj_list = [] 1079 for node in nodelist: 1080 obj_list.extend(FlattenTree(node)) 1081 1082 errors = 0 1083 1084 # 1085 # Check for expected successes 1086 # 1087 obj_cnt = len(obj_list) 1088 pass_cnt = len(pass_comments) 1089 if obj_cnt != pass_cnt: 1090 InfoOut.Log("Mismatched pass (%d) vs. nodes built (%d)." 1091 % (pass_cnt, obj_cnt)) 1092 InfoOut.Log("PASS: %s" % [x[1] for x in pass_comments]) 1093 InfoOut.Log("OBJS: %s" % obj_list) 1094 errors += 1 1095 if pass_cnt > obj_cnt: pass_cnt = obj_cnt 1096 1097 for i in range(pass_cnt): 1098 line, comment = pass_comments[i] 1099 if obj_list[i] != comment: 1100 ErrOut.LogLine(filename, line, None, "OBJ %s : EXPECTED %s\n" % 1101 (obj_list[i], comment)) 1102 errors += 1 1103 1104 # 1105 # Check for expected errors 1106 # 1107 err_list = ErrOut.DrainLog() 1108 err_cnt = len(err_list) 1109 fail_cnt = len(fail_comments) 1110 if err_cnt != fail_cnt: 1111 InfoOut.Log("Mismatched fail (%d) vs. errors seen (%d)." 1112 % (fail_cnt, err_cnt)) 1113 InfoOut.Log("FAIL: %s" % [x[1] for x in fail_comments]) 1114 InfoOut.Log("ERRS: %s" % err_list) 1115 errors += 1 1116 if fail_cnt > err_cnt: fail_cnt = err_cnt 1117 1118 for i in range(fail_cnt): 1119 line, comment = fail_comments[i] 1120 err = err_list[i].strip() 1121 1122 if err_list[i] != comment: 1123 ErrOut.Log("%s(%d) Error\n\tERROR : %s\n\tEXPECT: %s" % ( 1124 filename, line, err_list[i], comment)) 1125 errors += 1 1126 1127 # Clear the error list for the next run 1128 err_list = [] 1129 return errors 1130 1131 1132 def TestFile(parser, filename): 1133 # Capture errors instead of reporting them so we can compare them 1134 # with the expected errors. 1135 ErrOut.SetConsole(False) 1136 ErrOut.SetCapture(True) 1137 1138 filenode = parser.ParseFile(filename) 1139 1140 # Renable output 1141 ErrOut.SetConsole(True) 1142 ErrOut.SetCapture(False) 1143 1144 # Compare captured errors 1145 return TestErrors(filename, filenode) 1146 1147 1148 def TestErrorFiles(filter): 1149 idldir = os.path.split(sys.argv[0])[0] 1150 idldir = os.path.join(idldir, 'test_parser', '*.idl') 1151 filenames = glob.glob(idldir) 1152 parser = IDLParser() 1153 total_errs = 0 1154 for filename in filenames: 1155 if filter and filename not in filter: continue 1156 errs = TestFile(parser, filename) 1157 if errs: 1158 ErrOut.Log("%s test failed with %d error(s)." % (filename, errs)) 1159 total_errs += errs 1160 1161 if total_errs: 1162 ErrOut.Log("Failed parsing test.") 1163 else: 1164 InfoOut.Log("Passed parsing test.") 1165 return total_errs 1166 1167 1168 def TestNamespaceFiles(filter): 1169 idldir = os.path.split(sys.argv[0])[0] 1170 idldir = os.path.join(idldir, 'test_namespace', '*.idl') 1171 filenames = glob.glob(idldir) 1172 testnames = [] 1173 1174 for filename in filenames: 1175 if filter and filename not in filter: continue 1176 testnames.append(filename) 1177 1178 # If we have no files to test, then skip this test 1179 if not testnames: 1180 InfoOut.Log('No files to test for namespace.') 1181 return 0 1182 1183 InfoOut.SetConsole(False) 1184 ast = ParseFiles(testnames) 1185 InfoOut.SetConsole(True) 1186 1187 errs = ast.GetProperty('ERRORS') 1188 if errs: 1189 ErrOut.Log("Failed namespace test.") 1190 else: 1191 InfoOut.Log("Passed namespace test.") 1192 return errs 1193 1194 1195 1196 def FindVersionError(releases, node): 1197 err_cnt = 0 1198 if node.IsA('Interface', 'Struct'): 1199 comment_list = [] 1200 comment = node.GetOneOf('Comment') 1201 if comment and comment.GetName()[:4] == 'REL:': 1202 comment_list = comment.GetName()[5:].strip().split(' ') 1203 1204 first_list = [node.first_release[rel] for rel in releases] 1205 first_list = sorted(set(first_list)) 1206 if first_list != comment_list: 1207 node.Error("Mismatch in releases: %s vs %s." % ( 1208 comment_list, first_list)) 1209 err_cnt += 1 1210 1211 for child in node.GetChildren(): 1212 err_cnt += FindVersionError(releases, child) 1213 return err_cnt 1214 1215 1216 def TestVersionFiles(filter): 1217 idldir = os.path.split(sys.argv[0])[0] 1218 idldir = os.path.join(idldir, 'test_version', '*.idl') 1219 filenames = glob.glob(idldir) 1220 testnames = [] 1221 1222 for filename in filenames: 1223 if filter and filename not in filter: continue 1224 testnames.append(filename) 1225 1226 # If we have no files to test, then skip this test 1227 if not testnames: 1228 InfoOut.Log('No files to test for version.') 1229 return 0 1230 1231 ast = ParseFiles(testnames) 1232 errs = FindVersionError(ast.releases, ast) 1233 errs += ast.errors 1234 1235 if errs: 1236 ErrOut.Log("Failed version test.") 1237 else: 1238 InfoOut.Log("Passed version test.") 1239 return errs 1240 1241 1242 default_dirs = ['.', 'trusted', 'dev', 'private'] 1243 def ParseFiles(filenames): 1244 parser = IDLParser() 1245 filenodes = [] 1246 1247 if not filenames: 1248 filenames = [] 1249 srcroot = GetOption('srcroot') 1250 dirs = default_dirs 1251 if GetOption('include_private'): 1252 dirs += ['private'] 1253 for dirname in dirs: 1254 srcdir = os.path.join(srcroot, dirname, '*.idl') 1255 srcdir = os.path.normpath(srcdir) 1256 filenames += sorted(glob.glob(srcdir)) 1257 1258 if not filenames: 1259 ErrOut.Log('No sources provided.') 1260 1261 for filename in filenames: 1262 filenode = parser.ParseFile(filename) 1263 filenodes.append(filenode) 1264 1265 ast = IDLAst(filenodes) 1266 if GetOption('dump_tree'): ast.Dump(0) 1267 1268 Lint(ast) 1269 return ast 1270 1271 1272 def Main(args): 1273 filenames = ParseOptions(args) 1274 1275 # If testing... 1276 if GetOption('test'): 1277 errs = TestErrorFiles(filenames) 1278 errs = TestNamespaceFiles(filenames) 1279 errs = TestVersionFiles(filenames) 1280 if errs: 1281 ErrOut.Log("Parser failed with %d errors." % errs) 1282 return -1 1283 return 0 1284 1285 # Otherwise, build the AST 1286 ast = ParseFiles(filenames) 1287 errs = ast.GetProperty('ERRORS') 1288 if errs: 1289 ErrOut.Log('Found %d error(s).' % errs); 1290 InfoOut.Log("%d files processed." % len(filenames)) 1291 return errs 1292 1293 1294 if __name__ == '__main__': 1295 sys.exit(Main(sys.argv[1:])) 1296 1297