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.
6 """ Parser for PPAPI IDL """
11 # The parser is uses the PLY yacc library to build a set of parsing rules based
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/
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.
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
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.')
52 # Maps the standard error formula into a more friendly error message.
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.',
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
):
71 InfoOut
.Log("OBJ: %s(%d) - None\n" % (cls
, len(p
)))
72 InfoOut
.Log(" [%s]\n" % [str(x
) for x
in p
[1:]])
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
))
82 # Takes an input item, list, or None, and returns a new list of that set.
84 # If the item is 'Empty' make it an empty list
85 if not item
: item
= []
87 # If the item is not a list
88 if type(item
) is not type([]): item
= [item
]
90 # Make a copy we can modify
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
):
102 itemlist
= CopyToList(item
)
103 itemsout
.extend(itemlist
)
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
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> ....
131 # Where new item is the result of a match against one or more sets of items
132 # separated by the "|".
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.
138 # For more details on parsing refer to the PLY documentation at
139 # http://www.dabeaz.com/ply/
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.
148 # A [comment] is a optional C style comment block enclosed in /* ... */ which
149 # is appended to the adjacent node as a child.
151 # A [ext_attr_block] is an optional list of Extended Attributes which is
152 # appended to the adjacent node as a child.
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)
158 # [comment] [ext_attr_block] <...DEF...> <type>_cont
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.
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.
174 class IDLParser(IDLLexer
):
177 # This pattern defines the top of the parse tree. The parse tree is in the
205 # (* sub matches found at multiple levels and are not truly children of top)
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.
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.
216 """top : COMMENT COMMENT ext_attr_block top_list"""
218 Copyright
= self
.BuildComment('Copyright', p
, 1)
219 Filedoc
= self
.BuildComment('Comment', p
, 2)
221 p
[0] = ListFromConcat(Copyright
, Filedoc
, p
[3], p
[4])
222 if self
.parse_debug
: DumpReduction('top', p
)
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
)
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
240 | interface_block top_list
241 | label_block top_list
243 | struct_block top_list
244 | typedef_decl top_list
248 p
[0] = ListFromConcat(p
[1], p
[2])
249 if self
.parse_debug
: DumpReduction('top_list', p
)
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"""
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 '}' ';'"""
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
)
271 # Scoped name is a name with an optional scope.
273 # Used for types and namespace names. eg. foo_bar.hello_world, or
274 # foo_bar.hello_world.SomeType.
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
)
281 def p_scoped_name_rest(self
, p
):
282 """scoped_name_rest : '.' scoped_name
284 p
[0] = ''.join(p
[1:])
285 if self
.parse_debug
: DumpReduction('scoped_name_rest', p
)
291 def p_typeref(self
, p
):
292 """typeref : scoped_name"""
294 if self
.parse_debug
: DumpReduction('typeref', p
)
300 # Comments are optional list of C style comment objects. Comments are returned
303 def p_comments(self
, p
):
304 """comments : COMMENT comments
307 child
= self
.BuildComment('Comment', p
, 1)
308 p
[0] = ListFromConcat(child
, p
[2])
309 if self
.parse_debug
: DumpReduction('comments', p
)
311 if self
.parse_debug
: DumpReduction('no comments', p
)
317 # A namespace provides a named scope to an enclosed top_list.
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
)
324 # We allow namespace names of the form foo.bar.baz.
325 def p_namespace_name(self
, p
):
326 """namespace_name : scoped_name"""
333 # A dictionary is a named list of optional and required members.
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]))
339 def p_dictionary_errorA(self
, p
):
340 """dictionary_block : modifiers DICTIONARY error ';'"""
343 def p_dictionary_errorB(self
, p
):
344 """dictionary_block : modifiers DICTIONARY error '{' struct_list '}' ';'"""
350 # A callback is essentially a single function declaration (outside of an
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
)
362 # Inline blocks define option code to be emitted based on language tag,
368 def p_inline(self
, p
):
369 """inline : modifiers INLINE"""
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
)
378 # Extended Attributes
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:
385 # Name=HEX | INT | OCT | FLOAT
387 # Name=Function(arg ...)
388 # TODO(bradnelson) -Not currently supported:
389 # ** Name(arg ...) ...
390 # ** Name=Scope::Value
392 # Extended Attributes are returned as a list or None.
394 def p_ext_attr_block(self
, p
):
395 """ext_attr_block : '[' ext_attr_list ']'
399 if self
.parse_debug
: DumpReduction('ext_attr_block', p
)
401 if self
.parse_debug
: DumpReduction('no ext_attr_block', p
)
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
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
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
419 p
[0] = ListFromConcat(self
.BuildAttribute(p
[1], 'True'), p
[2])
420 if self
.parse_debug
: DumpReduction('ext_attribute_list', p
)
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])
427 def p_values(self
, p
):
428 """values : value values_cont"""
429 p
[0] = ListFromConcat(p
[1], p
[2])
431 def p_symbols(self
, p
):
432 """symbols : SYMBOL symbols_cont"""
433 p
[0] = ListFromConcat(p
[1], p
[2])
435 def p_symbols_cont(self
, p
):
436 """symbols_cont : ',' SYMBOL symbols_cont
438 if len(p
) > 1: p
[0] = ListFromConcat(p
[2], p
[3])
440 def p_values_cont(self
, p
):
441 """values_cont : ',' value values_cont
443 if len(p
) > 1: p
[0] = ListFromConcat(p
[2], p
[3])
445 def p_ext_attr_cont(self
, p
):
446 """ext_attr_cont : ',' ext_attr_list
448 if len(p
) > 1: p
[0] = p
[2]
449 if self
.parse_debug
: DumpReduction('ext_attribute_cont', p
)
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
)
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])
461 def p_attr_arg_cont(self
, p
):
462 """attr_arg_cont : ',' attr_arg_list
464 if self
.parse_debug
: DumpReduction('attr_arg_cont', p
)
465 if len(p
) > 1: p
[0] = p
[2]
467 def p_attr_arg_error(self
, p
):
468 """attr_arg_cont : error attr_arg_cont"""
470 if self
.parse_debug
: DumpReduction('attr_arg_error', p
)
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
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
)
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"""
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
498 Type
= self
.BuildNamed('Type', p
, 2, p
[1])
499 p
[0] = ListFromConcat(Type
, p
[4])
502 # Constant Values (integer, value)
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.
508 def p_value(self
, p
):
515 if self
.parse_debug
: DumpReduction('value', p
)
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
)
522 # Integers are numbers which may not be floats used in cases like array sizes.
523 def p_integer(self
, p
):
528 if self
.parse_debug
: DumpReduction('integer', p
)
533 # A simple arithmetic expression.
536 ('left','|','&','^'),
537 ('left','LSHIFT','RSHIFT'),
540 ('right','UMINUS','~'),
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
)
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
)
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
)
567 def p_expression_symbol(self
, p
):
568 """expression : SYMBOL"""
570 if self
.parse_debug
: DumpReduction('expression_symbol', p
)
572 def p_expression_integer(self
, p
):
573 """expression : integer"""
575 if self
.parse_debug
: DumpReduction('expression_integer', p
)
580 # Defined a list of array sizes (if any).
582 def p_arrays(self
, p
):
583 """arrays : '[' ']' arrays
584 | '[' integer ']' arrays
586 # If there are 3 tokens plus a return slot it is an unsized array
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
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
)
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
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
610 p
.set_lineno(0, p
.lineno(1))
616 # A union allows multiple choices of types for a parameter or member.
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
)
625 def p_union_list(self
, p
):
626 """union_list : union_option OR union_list
629 p
[0] = ListFromConcat(p
[1], p
[3])
636 # A parameter list is a collection of arguments which are passed to a
639 def p_param_list(self
, p
):
640 """param_list : '(' param_item param_cont ')'
643 args
= ListFromConcat(p
[2], p
[3])
646 p
[0] = self
.BuildProduction('Callspec', p
, 1, args
)
647 if self
.parse_debug
: DumpReduction('param_list', p
)
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
)
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
)
663 def p_optional(self
, p
):
664 """optional : OPTIONAL
667 p
[0] = self
.BuildAttribute('OPTIONAL', True)
670 def p_param_cont(self
, p
):
671 """param_cont : ',' param_item param_cont
674 p
[0] = ListFromConcat(p
[2], p
[3])
675 if self
.parse_debug
: DumpReduction('param_cont', p
)
677 def p_param_error(self
, p
):
678 """param_cont : error param_cont"""
685 # A typedef creates a new referencable type. The typedef can specify an array
686 # definition as well as a function declaration.
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
)
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
)
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
)
712 # An enumeration is a set of named integer constants. An enumeration
713 # is valid type which can be referenced in other definitions.
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
)
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 '}' ';'"""
725 def p_enum_errorB(self
, p
):
726 """enum_block : modifiers ENUM error ';'"""
729 def p_enum_list(self
, p
):
730 """enum_list : modifiers SYMBOL '=' expression enum_cont
731 | modifiers SYMBOL enum_cont"""
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])
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
)
741 def p_enum_cont(self
, p
):
742 """enum_cont : ',' enum_list
744 if len(p
) > 1: p
[0] = p
[2]
745 if self
.parse_debug
: DumpReduction('enum_cont', p
)
747 def p_enum_cont_error(self
, p
):
748 """enum_cont : error enum_cont"""
750 if self
.parse_debug
: DumpReduction('enum_error', p
)
756 # A label is a special kind of enumeration which allows us to go from a
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
)
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
)
771 def p_label_cont(self
, p
):
772 """label_cont : ',' label_list
774 if len(p
) > 1: p
[0] = p
[2]
775 if self
.parse_debug
: DumpReduction('label_cont', p
)
777 def p_label_cont_error(self
, p
):
778 """label_cont : error label_cont"""
780 if self
.parse_debug
: DumpReduction('label_error', p
)
786 # A member attribute or function of a struct or interface.
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
)
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
)
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
)
809 def p_static(self
, p
):
813 p
[0] = self
.BuildAttribute('STATIC', True)
815 def p_questionmark(self
, p
):
816 """questionmark : '?'
819 p
[0] = self
.BuildAttribute('OPTIONAL', True)
824 # An interface is a named collection of functions.
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
)
831 def p_interface_error(self
, p
):
832 """interface_block : modifiers INTERFACE error '{' interface_list '}' ';'"""
835 def p_interface_list(self
, p
):
836 """interface_list : member_function ';' interface_list
839 p
[0] = ListFromConcat(p
[1], p
[3])
840 if self
.parse_debug
: DumpReduction('interface_list', p
)
846 # A struct is a named collection of members which in turn reference other
847 # types. The struct is a referencable type.
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
)
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 '}' ';'"""
860 def p_struct_list(self
, p
):
861 """struct_list : member_attribute ';' struct_list
862 | member_function ';' struct_list
864 if len(p
) > 1: p
[0] = ListFromConcat(p
[1], p
[3])
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.
876 def p_error(self
, t
):
877 filename
= self
.lexobj
.filename
878 self
.parse_errors
+= 1
882 prev
= self
.yaccobj
.symstack
[-1]
883 if type(prev
) == lex
.LexToken
:
884 msg
= "Unexpected %s after %s." % (
885 TokenTypeName(t
), TokenTypeName(prev
))
887 msg
= "Unexpected %s." % (t
.value
)
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()
894 # Attempt to remap the error to a friendlier form
895 if msg
in ERROR_REMAP
:
896 msg
= ERROR_REMAP
[msg
]
899 ErrOut
.LogLine(filename
, lineno
, pos
, msg
)
901 def Warn(self
, node
, msg
):
902 WarnOut
.LogLine(node
.filename
, node
.lineno
, node
.pos
, msg
)
903 self
.parse_warnings
+= 1
906 IDLLexer
.__init
__(self
)
907 self
.yaccobj
= yacc
.yacc(module
=self
, tabmodule
=None, debug
=False,
908 optimize
=0, write_tables
=0)
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
919 # The token function returns the next token provided by IDLLexer for matching
920 # against the leaf paterns.
923 tok
= self
.lexobj
.token()
927 InfoOut
.Log("TOKEN %s(%s)" % (tok
.type, tok
.value
))
933 # Production is the set of items sent to a grammar rule resulting in a new
934 # item being returned.
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
)
947 InfoOut
.Log("Building %s" % out
)
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
)
955 def BuildComment(self
, cls
, p
, index
):
958 # Remove comment markers
961 # For C++ style, remove any leading whitespace and the '//' marker from
964 for line
in name
.split('\n'):
965 start
= line
.find('//')
966 lines
.append(line
[start
+2:])
968 # For C style, remove ending '*/''
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('*')
975 line
= line
[offs
+ 1:].rstrip()
979 name
= '\n'.join(lines
)
981 childlist
= [self
.BuildAttribute('NAME', name
),
982 self
.BuildAttribute('FORM', form
)]
983 return self
.BuildProduction(cls
, p
, index
, childlist
)
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.
992 def BuildAttribute(self
, key
, val
):
993 return IDLAttribute(key
, val
)
999 # Attempts to parse the current data loaded in the lexer.
1001 def ParseData(self
, data
, filename
='<Internal>'):
1002 self
.SetData(filename
, data
)
1004 self
.parse_errors
= 0
1005 self
.parse_warnings
= 0
1006 return self
.yaccobj
.parse(lexer
=self
)
1008 except lex
.LexError
as le
:
1015 # Loads a new file into the lexer and attemps to parse it.
1017 def ParseFile(self
, filename
):
1018 date
= time
.ctime(os
.path
.getmtime(filename
))
1019 data
= open(filename
).read()
1021 InfoOut
.Log("Parsing %s" % filename
)
1023 out
= self
.ParseData(data
, filename
)
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
)
1033 except Exception as e
:
1034 ErrOut
.LogLine(filename
, self
.last
.lineno
, self
.last
.lexpos
,
1035 'Internal parsing error - %s.' % str(e
))
1043 # Flattens the tree of IDLNodes for use in testing.
1045 def FlattenTree(node
):
1048 for child
in node
.GetChildren():
1049 if child
.IsA('Comment'):
1052 out
.extend(FlattenTree(child
))
1055 out
= [str(node
)] + out
1059 def TestErrors(filename
, filenode
):
1060 nodelist
= filenode
.GetChildren()
1063 data
= open(filename
).read()
1064 lexer
.SetData(filename
, data
)
1069 tok
= lexer
.lexobj
.token()
1070 if tok
== None: break
1071 if tok
.type == 'COMMENT':
1072 args
= tok
.value
[3:-3].split()
1074 pass_comments
.append((tok
.lineno
, ' '.join(args
[1:])))
1076 if args
[0] == 'FAIL':
1077 fail_comments
.append((tok
.lineno
, ' '.join(args
[1:])))
1079 for node
in nodelist
:
1080 obj_list
.extend(FlattenTree(node
))
1085 # Check for expected successes
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
)
1095 if pass_cnt
> obj_cnt
: pass_cnt
= obj_cnt
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
))
1105 # Check for expected errors
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
)
1116 if fail_cnt
> err_cnt
: fail_cnt
= err_cnt
1118 for i
in range(fail_cnt
):
1119 line
, comment
= fail_comments
[i
]
1120 err
= err_list
[i
].strip()
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
))
1127 # Clear the error list for the next run
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)
1138 filenode
= parser
.ParseFile(filename
)
1141 ErrOut
.SetConsole(True)
1142 ErrOut
.SetCapture(False)
1144 # Compare captured errors
1145 return TestErrors(filename
, filenode
)
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()
1154 for filename
in filenames
:
1155 if filter and filename
not in filter: continue
1156 errs
= TestFile(parser
, filename
)
1158 ErrOut
.Log("%s test failed with %d error(s)." % (filename
, errs
))
1162 ErrOut
.Log("Failed parsing test.")
1164 InfoOut
.Log("Passed parsing test.")
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
)
1174 for filename
in filenames
:
1175 if filter and filename
not in filter: continue
1176 testnames
.append(filename
)
1178 # If we have no files to test, then skip this test
1180 InfoOut
.Log('No files to test for namespace.')
1183 InfoOut
.SetConsole(False)
1184 ast
= ParseFiles(testnames
)
1185 InfoOut
.SetConsole(True)
1187 errs
= ast
.GetProperty('ERRORS')
1189 ErrOut
.Log("Failed namespace test.")
1191 InfoOut
.Log("Passed namespace test.")
1196 def FindVersionError(releases
, node
):
1198 if node
.IsA('Interface', 'Struct'):
1200 comment
= node
.GetOneOf('Comment')
1201 if comment
and comment
.GetName()[:4] == 'REL:':
1202 comment_list
= comment
.GetName()[5:].strip().split(' ')
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
))
1211 for child
in node
.GetChildren():
1212 err_cnt
+= FindVersionError(releases
, child
)
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
)
1222 for filename
in filenames
:
1223 if filter and filename
not in filter: continue
1224 testnames
.append(filename
)
1226 # If we have no files to test, then skip this test
1228 InfoOut
.Log('No files to test for version.')
1231 ast
= ParseFiles(testnames
)
1232 errs
= FindVersionError(ast
.releases
, ast
)
1236 ErrOut
.Log("Failed version test.")
1238 InfoOut
.Log("Passed version test.")
1242 default_dirs
= ['.', 'trusted', 'dev', 'private']
1243 def ParseFiles(filenames
):
1244 parser
= IDLParser()
1249 srcroot
= GetOption('srcroot')
1251 if GetOption('include_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
))
1259 ErrOut
.Log('No sources provided.')
1261 for filename
in filenames
:
1262 filenode
= parser
.ParseFile(filename
)
1263 filenodes
.append(filenode
)
1265 ast
= IDLAst(filenodes
)
1266 if GetOption('dump_tree'): ast
.Dump(0)
1273 filenames
= ParseOptions(args
)
1276 if GetOption('test'):
1277 errs
= TestErrorFiles(filenames
)
1278 errs
= TestNamespaceFiles(filenames
)
1279 errs
= TestVersionFiles(filenames
)
1281 ErrOut
.Log("Parser failed with %d errors." % errs
)
1285 # Otherwise, build the AST
1286 ast
= ParseFiles(filenames
)
1287 errs
= ast
.GetProperty('ERRORS')
1289 ErrOut
.Log('Found %d error(s).' % errs
);
1290 InfoOut
.Log("%d files processed." % len(filenames
))
1294 if __name__
== '__main__':
1295 sys
.exit(Main(sys
.argv
[1:]))