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# Comments 272# 273# Comments are optional list of C style comment objects. Comments are returned 274# as a list or None. 275# 276 def p_comments(self, p): 277 """comments : COMMENT comments 278 | """ 279 if len(p) > 1: 280 child = self.BuildComment('Comment', p, 1) 281 p[0] = ListFromConcat(child, p[2]) 282 if self.parse_debug: DumpReduction('comments', p) 283 else: 284 if self.parse_debug: DumpReduction('no comments', p) 285 286 287# 288# Namespace 289# 290# A namespace provides a named scope to an enclosed top_list. 291# 292 def p_namespace(self, p): 293 """namespace : modifiers NAMESPACE namespace_name '{' top_list '}' ';'""" 294 children = ListFromConcat(p[1], p[5]) 295 p[0] = self.BuildNamed('Namespace', p, 3, children) 296 297 # We allow namespace names of the form foo.bar.baz. 298 def p_namespace_name(self, p): 299 """namespace_name : SYMBOL 300 | SYMBOL '.' namespace_name""" 301 p[0] = "".join(p[1:]) 302 303 304# 305# Dictionary 306# 307# A dictionary is a named list of optional and required members. 308# 309 def p_dictionary_block(self, p): 310 """dictionary_block : modifiers DICTIONARY SYMBOL '{' struct_list '}' ';'""" 311 p[0] = self.BuildNamed('Dictionary', p, 3, ListFromConcat(p[1], p[5])) 312 313 def p_dictionary_errorA(self, p): 314 """dictionary_block : modifiers DICTIONARY error ';'""" 315 p[0] = [] 316 317 def p_dictionary_errorB(self, p): 318 """dictionary_block : modifiers DICTIONARY error '{' struct_list '}' ';'""" 319 p[0] = [] 320 321# 322# Callback 323# 324# A callback is essentially a single function declaration (outside of an 325# Interface). 326# 327 def p_callback_decl(self, p): 328 """callback_decl : modifiers CALLBACK SYMBOL '=' SYMBOL param_list ';'""" 329 children = ListFromConcat(p[1], p[6]) 330 p[0] = self.BuildNamed('Callback', p, 3, children) 331 332 333# 334# Inline 335# 336# Inline blocks define option code to be emitted based on language tag, 337# in the form of: 338# #inline <LANGUAGE> 339# <CODE> 340# #endinl 341# 342 def p_inline(self, p): 343 """inline : modifiers INLINE""" 344 words = p[2].split() 345 name = self.BuildAttribute('NAME', words[1]) 346 lines = p[2].split('\n') 347 value = self.BuildAttribute('VALUE', '\n'.join(lines[1:-1]) + '\n') 348 children = ListFromConcat(name, value, p[1]) 349 p[0] = self.BuildProduction('Inline', p, 2, children) 350 if self.parse_debug: DumpReduction('inline', p) 351 352# Extended Attributes 353# 354# Extended Attributes denote properties which will be applied to a node in the 355# AST. A list of extended attributes are denoted by a brackets '[' ... ']' 356# enclosing a comma separated list of extended attributes in the form of: 357# 358# Name 359# Name=HEX | INT | OCT | FLOAT 360# Name="STRING" 361# Name=Function(arg ...) 362# TODO(noelallen) -Not currently supported: 363# ** Name(arg ...) ... 364# ** Name=Scope::Value 365# 366# Extended Attributes are returned as a list or None. 367 368 def p_ext_attr_block(self, p): 369 """ext_attr_block : '[' ext_attr_list ']' 370 | """ 371 if len(p) > 1: 372 p[0] = p[2] 373 if self.parse_debug: DumpReduction('ext_attr_block', p) 374 else: 375 if self.parse_debug: DumpReduction('no ext_attr_block', p) 376 377 def p_ext_attr_list(self, p): 378 """ext_attr_list : SYMBOL '=' SYMBOL ext_attr_cont 379 | SYMBOL '=' value ext_attr_cont 380 | SYMBOL '=' SYMBOL param_list ext_attr_cont 381 | SYMBOL ext_attr_cont""" 382 # If there are 4 tokens plus a return slot, this must be in the form 383 # SYMBOL = SYMBOL|value ext_attr_cont 384 if len(p) == 5: 385 p[0] = ListFromConcat(self.BuildAttribute(p[1], p[3]), p[4]) 386 # If there are 5 tokens plus a return slot, this must be in the form 387 # SYMBOL = SYMBOL (param_list) ext_attr_cont 388 elif len(p) == 6: 389 member = self.BuildNamed('Member', p, 3, [p[4]]) 390 p[0] = ListFromConcat(self.BuildAttribute(p[1], member), p[5]) 391 # Otherwise, this must be: SYMBOL ext_attr_cont 392 else: 393 p[0] = ListFromConcat(self.BuildAttribute(p[1], 'True'), p[2]) 394 if self.parse_debug: DumpReduction('ext_attribute_list', p) 395 396 def p_ext_attr_list_values(self, p): 397 """ext_attr_list : SYMBOL '=' '(' values ')' ext_attr_cont 398 | SYMBOL '=' '(' symbols ')' ext_attr_cont""" 399 p[0] = ListFromConcat(self.BuildAttribute(p[1], p[4]), p[6]) 400 401 def p_values(self, p): 402 """values : value values_cont""" 403 p[0] = ListFromConcat(p[1], p[2]) 404 405 def p_symbols(self, p): 406 """symbols : SYMBOL symbols_cont""" 407 p[0] = ListFromConcat(p[1], p[2]) 408 409 def p_symbols_cont(self, p): 410 """symbols_cont : ',' SYMBOL symbols_cont 411 | """ 412 if len(p) > 1: p[0] = ListFromConcat(p[2], p[3]) 413 414 def p_values_cont(self, p): 415 """values_cont : ',' value values_cont 416 | """ 417 if len(p) > 1: p[0] = ListFromConcat(p[2], p[3]) 418 419 def p_ext_attr_cont(self, p): 420 """ext_attr_cont : ',' ext_attr_list 421 |""" 422 if len(p) > 1: p[0] = p[2] 423 if self.parse_debug: DumpReduction('ext_attribute_cont', p) 424 425 def p_ext_attr_func(self, p): 426 """ext_attr_list : SYMBOL '(' attr_arg_list ')' ext_attr_cont""" 427 p[0] = ListFromConcat(self.BuildAttribute(p[1] + '()', p[3]), p[5]) 428 if self.parse_debug: DumpReduction('attr_arg_func', p) 429 430 def p_ext_attr_arg_list(self, p): 431 """attr_arg_list : SYMBOL attr_arg_cont 432 | value attr_arg_cont""" 433 p[0] = ListFromConcat(p[1], p[2]) 434 435 def p_attr_arg_cont(self, p): 436 """attr_arg_cont : ',' attr_arg_list 437 | """ 438 if self.parse_debug: DumpReduction('attr_arg_cont', p) 439 if len(p) > 1: p[0] = p[2] 440 441 def p_attr_arg_error(self, p): 442 """attr_arg_cont : error attr_arg_cont""" 443 p[0] = p[2] 444 if self.parse_debug: DumpReduction('attr_arg_error', p) 445 446 447# 448# Describe 449# 450# A describe block is defined at the top level. It provides a mechanism for 451# attributing a group of ext_attr to a describe_list. Members of the 452# describe list are language specific 'Type' declarations 453# 454 def p_describe_block(self, p): 455 """describe_block : modifiers DESCRIBE '{' describe_list '}' ';'""" 456 children = ListFromConcat(p[1], p[4]) 457 p[0] = self.BuildProduction('Describe', p, 2, children) 458 if self.parse_debug: DumpReduction('describe_block', p) 459 460 # Recover from describe error and continue parsing at the next top match. 461 def p_describe_error(self, p): 462 """describe_list : error describe_list""" 463 p[0] = [] 464 465 def p_describe_list(self, p): 466 """describe_list : modifiers SYMBOL ';' describe_list 467 | modifiers ENUM ';' describe_list 468 | modifiers STRUCT ';' describe_list 469 | modifiers TYPEDEF ';' describe_list 470 | """ 471 if len(p) > 1: 472 Type = self.BuildNamed('Type', p, 2, p[1]) 473 p[0] = ListFromConcat(Type, p[4]) 474 475# 476# Constant Values (integer, value) 477# 478# Constant values can be found at various levels. A Constant value is returns 479# as the string value after validated against a FLOAT, HEX, INT, OCT or 480# STRING pattern as appropriate. 481# 482 def p_value(self, p): 483 """value : FLOAT 484 | HEX 485 | INT 486 | OCT 487 | STRING""" 488 p[0] = p[1] 489 if self.parse_debug: DumpReduction('value', p) 490 491 def p_value_lshift(self, p): 492 """value : integer LSHIFT INT""" 493 p[0] = "%s << %s" % (p[1], p[3]) 494 if self.parse_debug: DumpReduction('value', p) 495 496# Integers are numbers which may not be floats used in cases like array sizes. 497 def p_integer(self, p): 498 """integer : HEX 499 | INT 500 | OCT""" 501 p[0] = p[1] 502 if self.parse_debug: DumpReduction('integer', p) 503 504# 505# Expression 506# 507# A simple arithmetic expression. 508# 509 precedence = ( 510 ('left','|','&','^'), 511 ('left','LSHIFT','RSHIFT'), 512 ('left','+','-'), 513 ('left','*','/'), 514 ('right','UMINUS','~'), 515 ) 516 517 def p_expression_binop(self, p): 518 """expression : expression LSHIFT expression 519 | expression RSHIFT expression 520 | expression '|' expression 521 | expression '&' expression 522 | expression '^' expression 523 | expression '+' expression 524 | expression '-' expression 525 | expression '*' expression 526 | expression '/' expression""" 527 p[0] = "%s %s %s" % (str(p[1]), str(p[2]), str(p[3])) 528 if self.parse_debug: DumpReduction('expression_binop', p) 529 530 def p_expression_unop(self, p): 531 """expression : '-' expression %prec UMINUS 532 | '~' expression %prec '~'""" 533 p[0] = "%s%s" % (str(p[1]), str(p[2])) 534 if self.parse_debug: DumpReduction('expression_unop', p) 535 536 def p_expression_term(self, p): 537 "expression : '(' expression ')'" 538 p[0] = "%s%s%s" % (str(p[1]), str(p[2]), str(p[3])) 539 if self.parse_debug: DumpReduction('expression_term', p) 540 541 def p_expression_symbol(self, p): 542 "expression : SYMBOL" 543 p[0] = p[1] 544 if self.parse_debug: DumpReduction('expression_symbol', p) 545 546 def p_expression_integer(self, p): 547 "expression : integer" 548 p[0] = p[1] 549 if self.parse_debug: DumpReduction('expression_integer', p) 550 551# 552# Array List 553# 554# Defined a list of array sizes (if any). 555# 556 def p_arrays(self, p): 557 """arrays : '[' ']' arrays 558 | '[' integer ']' arrays 559 | """ 560 # If there are 3 tokens plus a return slot it is an unsized array 561 if len(p) == 4: 562 array = self.BuildProduction('Array', p, 1) 563 p[0] = ListFromConcat(array, p[3]) 564 # If there are 4 tokens plus a return slot it is a fixed array 565 elif len(p) == 5: 566 count = self.BuildAttribute('FIXED', p[2]) 567 array = self.BuildProduction('Array', p, 2, [count]) 568 p[0] = ListFromConcat(array, p[4]) 569 # If there is only a return slot, do not fill it for this terminator. 570 elif len(p) == 1: return 571 if self.parse_debug: DumpReduction('arrays', p) 572 573 574# An identifier is a legal value for a parameter or attribute name. Lots of 575# existing IDL files use "callback" as a parameter/attribute name, so we allow 576# a SYMBOL or the CALLBACK keyword. 577 def p_identifier(self, p): 578 """identifier : SYMBOL 579 | CALLBACK""" 580 p[0] = p[1] 581 # Save the line number of the underlying token (otherwise it gets 582 # discarded), since we use it in the productions with an identifier in 583 # them. 584 p.set_lineno(0, p.lineno(1)) 585 586# 587# Parameter List 588# 589# A parameter list is a collection of arguments which are passed to a 590# function. 591# 592 def p_param_list(self, p): 593 """param_list : '(' param_item param_cont ')' 594 | '(' ')' """ 595 if len(p) > 3: 596 args = ListFromConcat(p[2], p[3]) 597 else: 598 args = [] 599 p[0] = self.BuildProduction('Callspec', p, 1, args) 600 if self.parse_debug: DumpReduction('param_list', p) 601 602 def p_param_item(self, p): 603 """param_item : modifiers optional SYMBOL arrays identifier""" 604 typeref = self.BuildAttribute('TYPEREF', p[3]) 605 children = ListFromConcat(p[1], p[2], typeref, p[4]) 606 p[0] = self.BuildNamed('Param', p, 5, children) 607 if self.parse_debug: DumpReduction('param_item', p) 608 609 def p_optional(self, p): 610 """optional : OPTIONAL 611 | """ 612 if len(p) == 2: 613 p[0] = self.BuildAttribute('OPTIONAL', True) 614 615 616 def p_param_cont(self, p): 617 """param_cont : ',' param_item param_cont 618 | """ 619 if len(p) > 1: 620 p[0] = ListFromConcat(p[2], p[3]) 621 if self.parse_debug: DumpReduction('param_cont', p) 622 623 def p_param_error(self, p): 624 """param_cont : error param_cont""" 625 p[0] = p[2] 626 627 628# 629# Typedef 630# 631# A typedef creates a new referencable type. The typedef can specify an array 632# definition as well as a function declaration. 633# 634 def p_typedef_data(self, p): 635 """typedef_decl : modifiers TYPEDEF SYMBOL SYMBOL ';' """ 636 typeref = self.BuildAttribute('TYPEREF', p[3]) 637 children = ListFromConcat(p[1], typeref) 638 p[0] = self.BuildNamed('Typedef', p, 4, children) 639 if self.parse_debug: DumpReduction('typedef_data', p) 640 641 def p_typedef_array(self, p): 642 """typedef_decl : modifiers TYPEDEF SYMBOL arrays SYMBOL ';' """ 643 typeref = self.BuildAttribute('TYPEREF', p[3]) 644 children = ListFromConcat(p[1], typeref, p[4]) 645 p[0] = self.BuildNamed('Typedef', p, 5, children) 646 if self.parse_debug: DumpReduction('typedef_array', p) 647 648 def p_typedef_func(self, p): 649 """typedef_decl : modifiers TYPEDEF SYMBOL SYMBOL param_list ';' """ 650 typeref = self.BuildAttribute('TYPEREF', p[3]) 651 children = ListFromConcat(p[1], typeref, p[5]) 652 p[0] = self.BuildNamed('Typedef', p, 4, children) 653 if self.parse_debug: DumpReduction('typedef_func', p) 654 655# 656# Enumeration 657# 658# An enumeration is a set of named integer constants. An enumeration 659# is valid type which can be referenced in other definitions. 660# 661 def p_enum_block(self, p): 662 """enum_block : modifiers ENUM SYMBOL '{' enum_list '}' ';'""" 663 p[0] = self.BuildNamed('Enum', p, 3, ListFromConcat(p[1], p[5])) 664 if self.parse_debug: DumpReduction('enum_block', p) 665 666 # Recover from enum error and continue parsing at the next top match. 667 def p_enum_errorA(self, p): 668 """enum_block : modifiers ENUM error '{' enum_list '}' ';'""" 669 p[0] = [] 670 671 def p_enum_errorB(self, p): 672 """enum_block : modifiers ENUM error ';'""" 673 p[0] = [] 674 675 def p_enum_list(self, p): 676 """enum_list : modifiers SYMBOL '=' expression enum_cont 677 | modifiers SYMBOL enum_cont""" 678 if len(p) > 4: 679 val = self.BuildAttribute('VALUE', p[4]) 680 enum = self.BuildNamed('EnumItem', p, 2, ListFromConcat(val, p[1])) 681 p[0] = ListFromConcat(enum, p[5]) 682 else: 683 enum = self.BuildNamed('EnumItem', p, 2, p[1]) 684 p[0] = ListFromConcat(enum, p[3]) 685 if self.parse_debug: DumpReduction('enum_list', p) 686 687 def p_enum_cont(self, p): 688 """enum_cont : ',' enum_list 689 |""" 690 if len(p) > 1: p[0] = p[2] 691 if self.parse_debug: DumpReduction('enum_cont', p) 692 693 def p_enum_cont_error(self, p): 694 """enum_cont : error enum_cont""" 695 p[0] = p[2] 696 if self.parse_debug: DumpReduction('enum_error', p) 697 698 699# 700# Label 701# 702# A label is a special kind of enumeration which allows us to go from a 703# set of labels 704# 705 def p_label_block(self, p): 706 """label_block : modifiers LABEL SYMBOL '{' label_list '}' ';'""" 707 p[0] = self.BuildNamed('Label', p, 3, ListFromConcat(p[1], p[5])) 708 if self.parse_debug: DumpReduction('label_block', p) 709 710 def p_label_list(self, p): 711 """label_list : modifiers SYMBOL '=' FLOAT label_cont""" 712 val = self.BuildAttribute('VALUE', p[4]) 713 label = self.BuildNamed('LabelItem', p, 2, ListFromConcat(val, p[1])) 714 p[0] = ListFromConcat(label, p[5]) 715 if self.parse_debug: DumpReduction('label_list', p) 716 717 def p_label_cont(self, p): 718 """label_cont : ',' label_list 719 |""" 720 if len(p) > 1: p[0] = p[2] 721 if self.parse_debug: DumpReduction('label_cont', p) 722 723 def p_label_cont_error(self, p): 724 """label_cont : error label_cont""" 725 p[0] = p[2] 726 if self.parse_debug: DumpReduction('label_error', p) 727 728 729# 730# Members 731# 732# A member attribute or function of a struct or interface. 733# 734 def p_member_attribute(self, p): 735 """member_attribute : modifiers SYMBOL arrays questionmark identifier""" 736 typeref = self.BuildAttribute('TYPEREF', p[2]) 737 children = ListFromConcat(p[1], typeref, p[3], p[4]) 738 p[0] = self.BuildNamed('Member', p, 5, children) 739 if self.parse_debug: DumpReduction('attribute', p) 740 741 def p_member_function(self, p): 742 """member_function : modifiers static SYMBOL arrays SYMBOL param_list""" 743 typeref = self.BuildAttribute('TYPEREF', p[3]) 744 children = ListFromConcat(p[1], p[2], typeref, p[4], p[6]) 745 p[0] = self.BuildNamed('Member', p, 5, children) 746 if self.parse_debug: DumpReduction('function', p) 747 748 def p_static(self, p): 749 """static : STATIC 750 | """ 751 if len(p) == 2: 752 p[0] = self.BuildAttribute('STATIC', True) 753 754 def p_questionmark(self, p): 755 """questionmark : '?' 756 | """ 757 if len(p) == 2: 758 p[0] = self.BuildAttribute('OPTIONAL', True) 759 760# 761# Interface 762# 763# An interface is a named collection of functions. 764# 765 def p_interface_block(self, p): 766 """interface_block : modifiers INTERFACE SYMBOL '{' interface_list '}' ';'""" 767 p[0] = self.BuildNamed('Interface', p, 3, ListFromConcat(p[1], p[5])) 768 if self.parse_debug: DumpReduction('interface_block', p) 769 770 def p_interface_error(self, p): 771 """interface_block : modifiers INTERFACE error '{' interface_list '}' ';'""" 772 p[0] = [] 773 774 def p_interface_list(self, p): 775 """interface_list : member_function ';' interface_list 776 | """ 777 if len(p) > 1 : 778 p[0] = ListFromConcat(p[1], p[3]) 779 if self.parse_debug: DumpReduction('interface_list', p) 780 781 782# 783# Struct 784# 785# A struct is a named collection of members which in turn reference other 786# types. The struct is a referencable type. 787# 788 def p_struct_block(self, p): 789 """struct_block : modifiers STRUCT SYMBOL '{' struct_list '}' ';'""" 790 children = ListFromConcat(p[1], p[5]) 791 p[0] = self.BuildNamed('Struct', p, 3, children) 792 if self.parse_debug: DumpReduction('struct_block', p) 793 794 # Recover from struct error and continue parsing at the next top match. 795 def p_struct_error(self, p): 796 """enum_block : modifiers STRUCT error '{' struct_list '}' ';'""" 797 p[0] = [] 798 799 def p_struct_list(self, p): 800 """struct_list : member_attribute ';' struct_list 801 | member_function ';' struct_list 802 |""" 803 if len(p) > 1: p[0] = ListFromConcat(p[1], p[3]) 804 805 806# 807# Parser Errors 808# 809# p_error is called whenever the parser can not find a pattern match for 810# a set of items from the current state. The p_error function defined here 811# is triggered logging an error, and parsing recover happens as the 812# p_<type>_error functions defined above are called. This allows the parser 813# to continue so as to capture more than one error per file. 814# 815 def p_error(self, t): 816 filename = self.lexobj.filename 817 self.parse_errors += 1 818 if t: 819 lineno = t.lineno 820 pos = t.lexpos 821 prev = self.yaccobj.symstack[-1] 822 if type(prev) == lex.LexToken: 823 msg = "Unexpected %s after %s." % ( 824 TokenTypeName(t), TokenTypeName(prev)) 825 else: 826 msg = "Unexpected %s." % (t.value) 827 else: 828 lineno = self.last.lineno 829 pos = self.last.lexpos 830 msg = "Unexpected end of file after %s." % TokenTypeName(self.last) 831 self.yaccobj.restart() 832 833 # Attempt to remap the error to a friendlier form 834 if msg in ERROR_REMAP: 835 msg = ERROR_REMAP[msg] 836 837 # Log the error 838 ErrOut.LogLine(filename, lineno, pos, msg) 839 840 def Warn(self, node, msg): 841 WarnOut.LogLine(node.filename, node.lineno, node.pos, msg) 842 self.parse_warnings += 1 843 844 def __init__(self): 845 IDLLexer.__init__(self) 846 self.yaccobj = yacc.yacc(module=self, tabmodule=None, debug=False, 847 optimize=0, write_tables=0) 848 849 self.build_debug = GetOption('build_debug') 850 self.parse_debug = GetOption('parse_debug') 851 self.token_debug = GetOption('token_debug') 852 self.verbose = GetOption('verbose') 853 self.parse_errors = 0 854 855# 856# Tokenizer 857# 858# The token function returns the next token provided by IDLLexer for matching 859# against the leaf paterns. 860# 861 def token(self): 862 tok = self.lexobj.token() 863 if tok: 864 self.last = tok 865 if self.token_debug: 866 InfoOut.Log("TOKEN %s(%s)" % (tok.type, tok.value)) 867 return tok 868 869# 870# BuildProduction 871# 872# Production is the set of items sent to a grammar rule resulting in a new 873# item being returned. 874# 875# p - Is the Yacc production object containing the stack of items 876# index - Index into the production of the name for the item being produced. 877# cls - The type of item being producted 878# childlist - The children of the new item 879 def BuildProduction(self, cls, p, index, childlist=None): 880 if not childlist: childlist = [] 881 filename = self.lexobj.filename 882 lineno = p.lineno(index) 883 pos = p.lexpos(index) 884 out = IDLNode(cls, filename, lineno, pos, childlist) 885 if self.build_debug: 886 InfoOut.Log("Building %s" % out) 887 return out 888 889 def BuildNamed(self, cls, p, index, childlist=None): 890 if not childlist: childlist = [] 891 childlist.append(self.BuildAttribute('NAME', p[index])) 892 return self.BuildProduction(cls, p, index, childlist) 893 894 def BuildComment(self, cls, p, index): 895 name = p[index] 896 897 # Remove comment markers 898 lines = [] 899 if name[:2] == '//': 900 # For C++ style, remove any leading whitespace and the '//' marker from 901 # each line. 902 form = 'cc' 903 for line in name.split('\n'): 904 start = line.find('//') 905 lines.append(line[start+2:]) 906 else: 907 # For C style, remove ending '*/'' 908 form = 'c' 909 for line in name[:-2].split('\n'): 910 # Remove characters until start marker for this line '*' if found 911 # otherwise it should be blank. 912 offs = line.find('*') 913 if offs >= 0: 914 line = line[offs + 1:].rstrip() 915 else: 916 line = '' 917 lines.append(line) 918 name = '\n'.join(lines) 919 920 childlist = [self.BuildAttribute('NAME', name), 921 self.BuildAttribute('FORM', form)] 922 return self.BuildProduction(cls, p, index, childlist) 923 924# 925# BuildAttribute 926# 927# An ExtendedAttribute is a special production that results in a property 928# which is applied to the adjacent item. Attributes have no children and 929# instead represent key/value pairs. 930# 931 def BuildAttribute(self, key, val): 932 return IDLAttribute(key, val) 933 934 935# 936# ParseData 937# 938# Attempts to parse the current data loaded in the lexer. 939# 940 def ParseData(self, data, filename='<Internal>'): 941 self.SetData(filename, data) 942 try: 943 self.parse_errors = 0 944 self.parse_warnings = 0 945 return self.yaccobj.parse(lexer=self) 946 947 except lex.LexError as le: 948 ErrOut.Log(str(le)) 949 return [] 950 951# 952# ParseFile 953# 954# Loads a new file into the lexer and attemps to parse it. 955# 956 def ParseFile(self, filename): 957 date = time.ctime(os.path.getmtime(filename)) 958 data = open(filename).read() 959 if self.verbose: 960 InfoOut.Log("Parsing %s" % filename) 961 try: 962 out = self.ParseData(data, filename) 963 964 # If we have a src root specified, remove it from the path 965 srcroot = GetOption('srcroot') 966 if srcroot and filename.find(srcroot) == 0: 967 filename = filename[len(srcroot) + 1:] 968 filenode = IDLFile(filename, out, self.parse_errors + self.lex_errors) 969 filenode.SetProperty('DATETIME', date) 970 return filenode 971 972 except Exception as e: 973 ErrOut.LogLine(filename, self.last.lineno, self.last.lexpos, 974 'Internal parsing error - %s.' % str(e)) 975 raise 976 977 978 979# 980# Flatten Tree 981# 982# Flattens the tree of IDLNodes for use in testing. 983# 984def FlattenTree(node): 985 add_self = False 986 out = [] 987 for child in node.GetChildren(): 988 if child.IsA('Comment'): 989 add_self = True 990 else: 991 out.extend(FlattenTree(child)) 992 993 if add_self: 994 out = [str(node)] + out 995 return out 996 997 998def TestErrors(filename, filenode): 999 nodelist = filenode.GetChildren() 1000 1001 lexer = IDLLexer() 1002 data = open(filename).read() 1003 lexer.SetData(filename, data) 1004 1005 pass_comments = [] 1006 fail_comments = [] 1007 while True: 1008 tok = lexer.lexobj.token() 1009 if tok == None: break 1010 if tok.type == 'COMMENT': 1011 args = tok.value[3:-3].split() 1012 if args[0] == 'OK': 1013 pass_comments.append((tok.lineno, ' '.join(args[1:]))) 1014 else: 1015 if args[0] == 'FAIL': 1016 fail_comments.append((tok.lineno, ' '.join(args[1:]))) 1017 obj_list = [] 1018 for node in nodelist: 1019 obj_list.extend(FlattenTree(node)) 1020 1021 errors = 0 1022 1023 # 1024 # Check for expected successes 1025 # 1026 obj_cnt = len(obj_list) 1027 pass_cnt = len(pass_comments) 1028 if obj_cnt != pass_cnt: 1029 InfoOut.Log("Mismatched pass (%d) vs. nodes built (%d)." 1030 % (pass_cnt, obj_cnt)) 1031 InfoOut.Log("PASS: %s" % [x[1] for x in pass_comments]) 1032 InfoOut.Log("OBJS: %s" % obj_list) 1033 errors += 1 1034 if pass_cnt > obj_cnt: pass_cnt = obj_cnt 1035 1036 for i in range(pass_cnt): 1037 line, comment = pass_comments[i] 1038 if obj_list[i] != comment: 1039 ErrOut.LogLine(filename, line, None, "OBJ %s : EXPECTED %s\n" % 1040 (obj_list[i], comment)) 1041 errors += 1 1042 1043 # 1044 # Check for expected errors 1045 # 1046 err_list = ErrOut.DrainLog() 1047 err_cnt = len(err_list) 1048 fail_cnt = len(fail_comments) 1049 if err_cnt != fail_cnt: 1050 InfoOut.Log("Mismatched fail (%d) vs. errors seen (%d)." 1051 % (fail_cnt, err_cnt)) 1052 InfoOut.Log("FAIL: %s" % [x[1] for x in fail_comments]) 1053 InfoOut.Log("ERRS: %s" % err_list) 1054 errors += 1 1055 if fail_cnt > err_cnt: fail_cnt = err_cnt 1056 1057 for i in range(fail_cnt): 1058 line, comment = fail_comments[i] 1059 err = err_list[i].strip() 1060 1061 if err_list[i] != comment: 1062 ErrOut.Log("%s(%d) Error\n\tERROR : %s\n\tEXPECT: %s" % ( 1063 filename, line, err_list[i], comment)) 1064 errors += 1 1065 1066 # Clear the error list for the next run 1067 err_list = [] 1068 return errors 1069 1070 1071def TestFile(parser, filename): 1072 # Capture errors instead of reporting them so we can compare them 1073 # with the expected errors. 1074 ErrOut.SetConsole(False) 1075 ErrOut.SetCapture(True) 1076 1077 filenode = parser.ParseFile(filename) 1078 1079 # Renable output 1080 ErrOut.SetConsole(True) 1081 ErrOut.SetCapture(False) 1082 1083 # Compare captured errors 1084 return TestErrors(filename, filenode) 1085 1086 1087def TestErrorFiles(filter): 1088 idldir = os.path.split(sys.argv[0])[0] 1089 idldir = os.path.join(idldir, 'test_parser', '*.idl') 1090 filenames = glob.glob(idldir) 1091 parser = IDLParser() 1092 total_errs = 0 1093 for filename in filenames: 1094 if filter and filename not in filter: continue 1095 errs = TestFile(parser, filename) 1096 if errs: 1097 ErrOut.Log("%s test failed with %d error(s)." % (filename, errs)) 1098 total_errs += errs 1099 1100 if total_errs: 1101 ErrOut.Log("Failed parsing test.") 1102 else: 1103 InfoOut.Log("Passed parsing test.") 1104 return total_errs 1105 1106 1107def TestNamespaceFiles(filter): 1108 idldir = os.path.split(sys.argv[0])[0] 1109 idldir = os.path.join(idldir, 'test_namespace', '*.idl') 1110 filenames = glob.glob(idldir) 1111 testnames = [] 1112 1113 for filename in filenames: 1114 if filter and filename not in filter: continue 1115 testnames.append(filename) 1116 1117 # If we have no files to test, then skip this test 1118 if not testnames: 1119 InfoOut.Log('No files to test for namespace.') 1120 return 0 1121 1122 InfoOut.SetConsole(False) 1123 ast = ParseFiles(testnames) 1124 InfoOut.SetConsole(True) 1125 1126 errs = ast.GetProperty('ERRORS') 1127 if errs: 1128 ErrOut.Log("Failed namespace test.") 1129 else: 1130 InfoOut.Log("Passed namespace test.") 1131 return errs 1132 1133 1134 1135def FindVersionError(releases, node): 1136 err_cnt = 0 1137 if node.IsA('Interface', 'Struct'): 1138 comment_list = [] 1139 comment = node.GetOneOf('Comment') 1140 if comment and comment.GetName()[:4] == 'REL:': 1141 comment_list = comment.GetName()[5:].strip().split(' ') 1142 1143 first_list = [node.first_release[rel] for rel in releases] 1144 first_list = sorted(set(first_list)) 1145 if first_list != comment_list: 1146 node.Error("Mismatch in releases: %s vs %s." % ( 1147 comment_list, first_list)) 1148 err_cnt += 1 1149 1150 for child in node.GetChildren(): 1151 err_cnt += FindVersionError(releases, child) 1152 return err_cnt 1153 1154 1155def TestVersionFiles(filter): 1156 idldir = os.path.split(sys.argv[0])[0] 1157 idldir = os.path.join(idldir, 'test_version', '*.idl') 1158 filenames = glob.glob(idldir) 1159 testnames = [] 1160 1161 for filename in filenames: 1162 if filter and filename not in filter: continue 1163 testnames.append(filename) 1164 1165 # If we have no files to test, then skip this test 1166 if not testnames: 1167 InfoOut.Log('No files to test for version.') 1168 return 0 1169 1170 ast = ParseFiles(testnames) 1171 errs = FindVersionError(ast.releases, ast) 1172 errs += ast.errors 1173 1174 if errs: 1175 ErrOut.Log("Failed version test.") 1176 else: 1177 InfoOut.Log("Passed version test.") 1178 return errs 1179 1180 1181default_dirs = ['.', 'trusted', 'dev', 'private', 'extensions', 1182 'extensions/dev'] 1183def ParseFiles(filenames): 1184 parser = IDLParser() 1185 filenodes = [] 1186 1187 if not filenames: 1188 filenames = [] 1189 srcroot = GetOption('srcroot') 1190 dirs = default_dirs 1191 if GetOption('include_private'): 1192 dirs += ['private'] 1193 for dirname in dirs: 1194 srcdir = os.path.join(srcroot, dirname, '*.idl') 1195 srcdir = os.path.normpath(srcdir) 1196 filenames += sorted(glob.glob(srcdir)) 1197 1198 if not filenames: 1199 ErrOut.Log('No sources provided.') 1200 1201 for filename in filenames: 1202 filenode = parser.ParseFile(filename) 1203 filenodes.append(filenode) 1204 1205 ast = IDLAst(filenodes) 1206 if GetOption('dump_tree'): ast.Dump(0) 1207 1208 Lint(ast) 1209 return ast 1210 1211 1212def Main(args): 1213 filenames = ParseOptions(args) 1214 1215 # If testing... 1216 if GetOption('test'): 1217 errs = TestErrorFiles(filenames) 1218 errs = TestNamespaceFiles(filenames) 1219 errs = TestVersionFiles(filenames) 1220 if errs: 1221 ErrOut.Log("Parser failed with %d errors." % errs) 1222 return -1 1223 return 0 1224 1225 # Otherwise, build the AST 1226 ast = ParseFiles(filenames) 1227 errs = ast.GetProperty('ERRORS') 1228 if errs: 1229 ErrOut.Log('Found %d error(s).' % errs); 1230 InfoOut.Log("%d files processed." % len(filenames)) 1231 return errs 1232 1233 1234if __name__ == '__main__': 1235 sys.exit(Main(sys.argv[1:])) 1236 1237