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 25import getopt 26import glob 27import os.path 28import re 29import sys 30import time 31 32from idl_ast import IDLAst 33from idl_log import ErrOut, InfoOut, WarnOut 34from idl_lexer import IDLLexer 35from idl_node import IDLAttribute, IDLFile, IDLNode 36from idl_option import GetOption, Option, ParseOptions 37from idl_lint import Lint 38 39from ply import lex 40from ply import yacc 41 42Option('build_debug', 'Debug tree building.') 43Option('parse_debug', 'Debug parse reduction steps.') 44Option('token_debug', 'Debug token generation.') 45Option('dump_tree', 'Dump the tree.') 46Option('srcroot', 'Working directory.', default=os.path.join('..', 'api')) 47Option('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# 54ERROR_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. 69def 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. 83def 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. 99def 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. 111def 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 174class 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# 1045def 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 1059def 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 1132def 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 1148def 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 1168def 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 1196def 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 1216def 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 1242default_dirs = ['.', 'trusted', 'dev', 'private'] 1243def 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 1272def 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 1294if __name__ == '__main__': 1295 sys.exit(Main(sys.argv[1:])) 1296 1297