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
38 from idl_visitor
import IDLVisitor
43 Option('build_debug', 'Debug tree building.')
44 Option('parse_debug', 'Debug parse reduction steps.')
45 Option('token_debug', 'Debug token generation.')
46 Option('dump_tree', 'Dump the tree.')
47 Option('srcroot', 'Working directory.', default
=os
.path
.join('..', 'api'))
48 Option('include_private', 'Include private IDL directory in default API paths.')
53 # Maps the standard error formula into a more friendly error message.
56 'Unexpected ")" after "(".' : 'Empty argument list.',
57 'Unexpected ")" after ",".' : 'Missing argument.',
58 'Unexpected "}" after ",".' : 'Trailing comma in block.',
59 'Unexpected "}" after "{".' : 'Unexpected empty block.',
60 'Unexpected comment after "}".' : 'Unexpected trailing comment.',
61 'Unexpected "{" after keyword "enum".' : 'Enum missing name.',
62 'Unexpected "{" after keyword "struct".' : 'Struct missing name.',
63 'Unexpected "{" after keyword "interface".' : 'Interface missing name.',
68 # Prints out the set of items which matched a particular pattern and the
69 # new item or set it was reduced to.
70 def DumpReduction(cls
, p
):
72 InfoOut
.Log("OBJ: %s(%d) - None\n" % (cls
, len(p
)))
73 InfoOut
.Log(" [%s]\n" % [str(x
) for x
in p
[1:]])
76 for index
in range(len(p
) - 1):
77 out
+= " >%s< " % str(p
[index
+ 1])
78 InfoOut
.Log("OBJ: %s(%d) - %s : %s\n" % (cls
, len(p
), str(p
[0]), out
))
83 # Takes an input item, list, or None, and returns a new list of that set.
85 # If the item is 'Empty' make it an empty list
86 if not item
: item
= []
88 # If the item is not a list
89 if type(item
) is not type([]): item
= [item
]
91 # Make a copy we can modify
98 # Generate a new List by joining of two sets of inputs which can be an
99 # individual item, a list of items, or None.
100 def ListFromConcat(*items
):
103 itemlist
= CopyToList(item
)
104 itemsout
.extend(itemlist
)
111 # Generate a string which has the type and value of the token.
112 def TokenTypeName(t
):
113 if t
.type == 'SYMBOL': return 'symbol %s' % t
.value
114 if t
.type in ['HEX', 'INT', 'OCT', 'FLOAT']:
115 return 'value %s' % t
.value
116 if t
.type == 'STRING' : return 'string "%s"' % t
.value
117 if t
.type == 'COMMENT' : return 'comment'
118 if t
.type == t
.value
: return '"%s"' % t
.value
119 return 'keyword "%s"' % t
.value
125 # The Parser inherits the from the Lexer to provide PLY with the tokenizing
126 # definitions. Parsing patterns are encoded as function where p_<name> is
127 # is called any time a patern matching the function documentation is found.
128 # Paterns are expressed in the form of:
129 # """ <new item> : <item> ....
132 # Where new item is the result of a match against one or more sets of items
133 # separated by the "|".
135 # The function is called with an object 'p' where p[0] is the output object
136 # and p[n] is the set of inputs for positive values of 'n'. Len(p) can be
137 # used to distinguish between multiple item sets in the pattern.
139 # For more details on parsing refer to the PLY documentation at
140 # http://www.dabeaz.com/ply/
143 # The parser uses the following conventions:
144 # a <type>_block defines a block of <type> definitions in the form of:
145 # [comment] [ext_attr_block] <type> <name> '{' <type>_list '}' ';'
146 # A block is reduced by returning an object of <type> with a name of <name>
147 # which in turn has <type>_list as children.
149 # A [comment] is a optional C style comment block enclosed in /* ... */ which
150 # is appended to the adjacent node as a child.
152 # A [ext_attr_block] is an optional list of Extended Attributes which is
153 # appended to the adjacent node as a child.
155 # a <type>_list defines a list of <type> items which will be passed as a
156 # list of children to the parent pattern. A list is in the form of:
157 # [comment] [ext_attr_block] <...DEF...> ';' <type>_list | (empty)
159 # [comment] [ext_attr_block] <...DEF...> <type>_cont
161 # In the first form, the list is reduced recursively, where the right side
162 # <type>_list is first reduced then joined with pattern currently being
163 # matched. The list is terminated with the (empty) pattern is matched.
165 # In the second form the list is reduced recursively, where the right side
166 # <type>_cont is first reduced then joined with the pattern currently being
167 # matched. The type_<cont> is in the form of:
168 # ',' <type>_list | (empty)
169 # The <type>_cont form is used to consume the ',' which only occurs when
170 # there is more than one object in the list. The <type>_cont also provides
171 # the terminating (empty) definition.
175 class IDLParser(IDLLexer
):
178 # This pattern defines the top of the parse tree. The parse tree is in the
206 # (* sub matches found at multiple levels and are not truly children of top)
208 # We force all input files to start with two comments. The first comment is a
209 # Copyright notice followed by a set of file wide Extended Attributes, followed
210 # by the file comment and finally by file level patterns.
212 # Find the Copyright, File comment, and optional file wide attributes. We
213 # use a match with COMMENT instead of comments to force the token to be
214 # present. The extended attributes and the top_list become siblings which
215 # in turn are children of the file object created from the results of top.
217 """top : COMMENT COMMENT ext_attr_block top_list"""
219 Copyright
= self
.BuildComment('Copyright', p
, 1)
220 Filedoc
= self
.BuildComment('Comment', p
, 2)
222 p
[0] = ListFromConcat(Copyright
, Filedoc
, p
[3], p
[4])
223 if self
.parse_debug
: DumpReduction('top', p
)
225 def p_top_short(self
, p
):
226 """top : COMMENT ext_attr_block top_list"""
227 Copyright
= self
.BuildComment('Copyright', p
, 1)
228 Filedoc
= IDLNode('Comment', self
.lexobj
.filename
, p
.lineno(2)-1,
229 p
.lexpos(2)-1, [self
.BuildAttribute('NAME', ''),
230 self
.BuildAttribute('FORM', 'cc')])
231 p
[0] = ListFromConcat(Copyright
, Filedoc
, p
[2], p
[3])
232 if self
.parse_debug
: DumpReduction('top', p
)
234 # Build a list of top level items.
235 def p_top_list(self
, p
):
236 """top_list : callback_decl top_list
237 | describe_block top_list
238 | dictionary_block top_list
239 | enum_block top_list
241 | interface_block top_list
242 | label_block top_list
244 | struct_block top_list
245 | typedef_decl top_list
249 p
[0] = ListFromConcat(p
[1], p
[2])
250 if self
.parse_debug
: DumpReduction('top_list', p
)
252 # Recover from error and continue parsing at the next top match.
253 def p_top_error(self
, p
):
254 """top_list : error top_list"""
257 # Recover from error and continue parsing at the next top match.
258 def p_bad_decl(self
, p
):
259 """bad_decl : modifiers SYMBOL error '}' ';'"""
266 def p_modifiers(self
, p
):
267 """modifiers : comments ext_attr_block"""
268 p
[0] = ListFromConcat(p
[1], p
[2])
269 if self
.parse_debug
: DumpReduction('modifiers', p
)
274 # Comments are optional list of C style comment objects. Comments are returned
277 def p_comments(self
, p
):
278 """comments : COMMENT comments
281 child
= self
.BuildComment('Comment', p
, 1)
282 p
[0] = ListFromConcat(child
, p
[2])
283 if self
.parse_debug
: DumpReduction('comments', p
)
285 if self
.parse_debug
: DumpReduction('no comments', p
)
291 # A namespace provides a named scope to an enclosed top_list.
293 def p_namespace(self
, p
):
294 """namespace : modifiers NAMESPACE namespace_name '{' top_list '}' ';'"""
295 children
= ListFromConcat(p
[1], p
[5])
296 p
[0] = self
.BuildNamed('Namespace', p
, 3, children
)
298 # We allow namespace names of the form foo.bar.baz.
299 def p_namespace_name(self
, p
):
300 """namespace_name : SYMBOL
301 | SYMBOL '.' namespace_name"""
302 p
[0] = "".join(p
[1:])
308 # A dictionary is a named list of optional and required members.
310 def p_dictionary_block(self
, p
):
311 """dictionary_block : modifiers DICTIONARY SYMBOL '{' struct_list '}' ';'"""
312 p
[0] = self
.BuildNamed('Dictionary', p
, 3, ListFromConcat(p
[1], p
[5]))
317 # A callback is essentially a single function declaration (outside of an
320 def p_callback_decl(self
, p
):
321 """callback_decl : modifiers CALLBACK SYMBOL '=' SYMBOL param_list ';'"""
322 children
= ListFromConcat(p
[1], p
[6])
323 p
[0] = self
.BuildNamed('Callback', p
, 3, children
)
329 # Inline blocks define option code to be emitted based on language tag,
335 def p_inline(self
, p
):
336 """inline : modifiers INLINE"""
338 name
= self
.BuildAttribute('NAME', words
[1])
339 lines
= p
[2].split('\n')
340 value
= self
.BuildAttribute('VALUE', '\n'.join(lines
[1:-1]) + '\n')
341 children
= ListFromConcat(name
, value
, p
[1])
342 p
[0] = self
.BuildProduction('Inline', p
, 2, children
)
343 if self
.parse_debug
: DumpReduction('inline', p
)
345 # Extended Attributes
347 # Extended Attributes denote properties which will be applied to a node in the
348 # AST. A list of extended attributes are denoted by a brackets '[' ... ']'
349 # enclosing a comma separated list of extended attributes in the form of:
352 # Name=HEX | INT | OCT | FLOAT
354 # Name=Function(arg ...)
355 # TODO(noelallen) -Not currently supported:
356 # ** Name(arg ...) ...
357 # ** Name=Scope::Value
359 # Extended Attributes are returned as a list or None.
361 def p_ext_attr_block(self
, p
):
362 """ext_attr_block : '[' ext_attr_list ']'
366 if self
.parse_debug
: DumpReduction('ext_attr_block', p
)
368 if self
.parse_debug
: DumpReduction('no ext_attr_block', p
)
370 def p_ext_attr_list(self
, p
):
371 """ext_attr_list : SYMBOL '=' SYMBOL ext_attr_cont
372 | SYMBOL '=' value ext_attr_cont
373 | SYMBOL '=' SYMBOL param_list ext_attr_cont
374 | SYMBOL ext_attr_cont"""
375 # If there are 4 tokens plus a return slot, this must be in the form
376 # SYMBOL = SYMBOL|value ext_attr_cont
378 p
[0] = ListFromConcat(self
.BuildAttribute(p
[1], p
[3]), p
[4])
379 # If there are 5 tokens plus a return slot, this must be in the form
380 # SYMBOL = SYMBOL (param_list) ext_attr_cont
382 member
= self
.BuildNamed('Member', p
, 3, [p
[4]])
383 p
[0] = ListFromConcat(self
.BuildAttribute(p
[1], member
), p
[5])
384 # Otherwise, this must be: SYMBOL ext_attr_cont
386 p
[0] = ListFromConcat(self
.BuildAttribute(p
[1], 'True'), p
[2])
387 if self
.parse_debug
: DumpReduction('ext_attribute_list', p
)
389 def p_ext_attr_list_values(self
, p
):
390 """ext_attr_list : SYMBOL '=' '(' values ')' ext_attr_cont
391 | SYMBOL '=' '(' symbols ')' ext_attr_cont"""
392 p
[0] = ListFromConcat(self
.BuildAttribute(p
[1], p
[4]), p
[6])
394 def p_values(self
, p
):
395 """values : value values_cont"""
396 p
[0] = ListFromConcat(p
[1], p
[2])
398 def p_symbols(self
, p
):
399 """symbols : SYMBOL symbols_cont"""
400 p
[0] = ListFromConcat(p
[1], p
[2])
402 def p_symbols_cont(self
, p
):
403 """symbols_cont : ',' SYMBOL symbols_cont
405 if len(p
) > 1: p
[0] = ListFromConcat(p
[2], p
[3])
407 def p_values_cont(self
, p
):
408 """values_cont : ',' value values_cont
410 if len(p
) > 1: p
[0] = ListFromConcat(p
[2], p
[3])
412 def p_ext_attr_cont(self
, p
):
413 """ext_attr_cont : ',' ext_attr_list
415 if len(p
) > 1: p
[0] = p
[2]
416 if self
.parse_debug
: DumpReduction('ext_attribute_cont', p
)
418 def p_ext_attr_func(self
, p
):
419 """ext_attr_list : SYMBOL '(' attr_arg_list ')' ext_attr_cont"""
420 p
[0] = ListFromConcat(self
.BuildAttribute(p
[1] + '()', p
[3]), p
[5])
421 if self
.parse_debug
: DumpReduction('attr_arg_func', p
)
423 def p_ext_attr_arg_list(self
, p
):
424 """attr_arg_list : SYMBOL attr_arg_cont
425 | value attr_arg_cont"""
426 p
[0] = ListFromConcat(p
[1], p
[2])
428 def p_attr_arg_cont(self
, p
):
429 """attr_arg_cont : ',' attr_arg_list
431 if self
.parse_debug
: DumpReduction('attr_arg_cont', p
)
432 if len(p
) > 1: p
[0] = p
[2]
434 def p_attr_arg_error(self
, p
):
435 """attr_arg_cont : error attr_arg_cont"""
437 if self
.parse_debug
: DumpReduction('attr_arg_error', p
)
443 # A describe block is defined at the top level. It provides a mechanism for
444 # attributing a group of ext_attr to a describe_list. Members of the
445 # describe list are language specific 'Type' declarations
447 def p_describe_block(self
, p
):
448 """describe_block : modifiers DESCRIBE '{' describe_list '}' ';'"""
449 children
= ListFromConcat(p
[1], p
[4])
450 p
[0] = self
.BuildProduction('Describe', p
, 2, children
)
451 if self
.parse_debug
: DumpReduction('describe_block', p
)
453 # Recover from describe error and continue parsing at the next top match.
454 def p_describe_error(self
, p
):
455 """describe_list : error describe_list"""
458 def p_describe_list(self
, p
):
459 """describe_list : modifiers SYMBOL ';' describe_list
460 | modifiers ENUM ';' describe_list
461 | modifiers STRUCT ';' describe_list
462 | modifiers TYPEDEF ';' describe_list
465 Type
= self
.BuildNamed('Type', p
, 2, p
[1])
466 p
[0] = ListFromConcat(Type
, p
[4])
469 # Constant Values (integer, value)
471 # Constant values can be found at various levels. A Constant value is returns
472 # as the string value after validated against a FLOAT, HEX, INT, OCT or
473 # STRING pattern as appropriate.
475 def p_value(self
, p
):
482 if self
.parse_debug
: DumpReduction('value', p
)
484 def p_value_lshift(self
, p
):
485 """value : integer LSHIFT INT"""
486 p
[0] = "%s << %s" % (p
[1], p
[3])
487 if self
.parse_debug
: DumpReduction('value', p
)
489 # Integers are numbers which may not be floats used in cases like array sizes.
490 def p_integer(self
, p
):
495 if self
.parse_debug
: DumpReduction('integer', p
)
500 # A simple arithmetic expression.
503 ('left','|','&','^'),
504 ('left','LSHIFT','RSHIFT'),
507 ('right','UMINUS','~'),
510 def p_expression_binop(self
, p
):
511 """expression : expression LSHIFT expression
512 | expression RSHIFT expression
513 | expression '|' expression
514 | expression '&' expression
515 | expression '^' expression
516 | expression '+' expression
517 | expression '-' expression
518 | expression '*' expression
519 | expression '/' expression"""
520 p
[0] = "%s %s %s" % (str(p
[1]), str(p
[2]), str(p
[3]))
521 if self
.parse_debug
: DumpReduction('expression_binop', p
)
523 def p_expression_unop(self
, p
):
524 """expression : '-' expression %prec UMINUS
525 | '~' expression %prec '~'"""
526 p
[0] = "%s%s" % (str(p
[1]), str(p
[2]))
527 if self
.parse_debug
: DumpReduction('expression_unop', p
)
529 def p_expression_term(self
, p
):
530 "expression : '(' expression ')'"
531 p
[0] = "%s%s%s" % (str(p
[1]), str(p
[2]), str(p
[3]))
532 if self
.parse_debug
: DumpReduction('expression_term', p
)
534 def p_expression_symbol(self
, p
):
535 "expression : SYMBOL"
537 if self
.parse_debug
: DumpReduction('expression_symbol', p
)
539 def p_expression_integer(self
, p
):
540 "expression : integer"
542 if self
.parse_debug
: DumpReduction('expression_integer', p
)
547 # Defined a list of array sizes (if any).
549 def p_arrays(self
, p
):
550 """arrays : '[' ']' arrays
551 | '[' integer ']' arrays
553 # If there are 3 tokens plus a return slot it is an unsized array
555 array
= self
.BuildProduction('Array', p
, 1)
556 p
[0] = ListFromConcat(array
, p
[3])
557 # If there are 4 tokens plus a return slot it is a fixed array
559 count
= self
.BuildAttribute('FIXED', p
[2])
560 array
= self
.BuildProduction('Array', p
, 2, [count
])
561 p
[0] = ListFromConcat(array
, p
[4])
562 # If there is only a return slot, do not fill it for this terminator.
563 elif len(p
) == 1: return
564 if self
.parse_debug
: DumpReduction('arrays', p
)
567 # An identifier is a legal value for a parameter or attribute name. Lots of
568 # existing IDL files use "callback" as a parameter/attribute name, so we allow
569 # a SYMBOL or the CALLBACK keyword.
570 def p_identifier(self
, p
):
571 """identifier : SYMBOL
574 # Save the line number of the underlying token (otherwise it gets
575 # discarded), since we use it in the productions with an identifier in
577 p
.set_lineno(0, p
.lineno(1))
582 # A parameter list is a collection of arguments which are passed to a
585 def p_param_list(self
, p
):
586 """param_list : '(' param_item param_cont ')'
589 args
= ListFromConcat(p
[2], p
[3])
592 p
[0] = self
.BuildProduction('Callspec', p
, 1, args
)
593 if self
.parse_debug
: DumpReduction('param_list', p
)
595 def p_param_item(self
, p
):
596 """param_item : modifiers optional SYMBOL arrays identifier"""
597 typeref
= self
.BuildAttribute('TYPEREF', p
[3])
598 children
= ListFromConcat(p
[1], p
[2], typeref
, p
[4])
599 p
[0] = self
.BuildNamed('Param', p
, 5, children
)
600 if self
.parse_debug
: DumpReduction('param_item', p
)
602 def p_optional(self
, p
):
603 """optional : OPTIONAL
606 p
[0] = self
.BuildAttribute('OPTIONAL', True)
609 def p_param_cont(self
, p
):
610 """param_cont : ',' param_item param_cont
613 p
[0] = ListFromConcat(p
[2], p
[3])
614 if self
.parse_debug
: DumpReduction('param_cont', p
)
616 def p_param_error(self
, p
):
617 """param_cont : error param_cont"""
624 # A typedef creates a new referencable type. The typedef can specify an array
625 # definition as well as a function declaration.
627 def p_typedef_data(self
, p
):
628 """typedef_decl : modifiers TYPEDEF SYMBOL SYMBOL ';' """
629 typeref
= self
.BuildAttribute('TYPEREF', p
[3])
630 children
= ListFromConcat(p
[1], typeref
)
631 p
[0] = self
.BuildNamed('Typedef', p
, 4, children
)
632 if self
.parse_debug
: DumpReduction('typedef_data', p
)
634 def p_typedef_array(self
, p
):
635 """typedef_decl : modifiers TYPEDEF SYMBOL arrays SYMBOL ';' """
636 typeref
= self
.BuildAttribute('TYPEREF', p
[3])
637 children
= ListFromConcat(p
[1], typeref
, p
[4])
638 p
[0] = self
.BuildNamed('Typedef', p
, 5, children
)
639 if self
.parse_debug
: DumpReduction('typedef_array', p
)
641 def p_typedef_func(self
, p
):
642 """typedef_decl : modifiers TYPEDEF SYMBOL SYMBOL param_list ';' """
643 typeref
= self
.BuildAttribute('TYPEREF', p
[3])
644 children
= ListFromConcat(p
[1], typeref
, p
[5])
645 p
[0] = self
.BuildNamed('Typedef', p
, 4, children
)
646 if self
.parse_debug
: DumpReduction('typedef_func', p
)
651 # An enumeration is a set of named integer constants. An enumeration
652 # is valid type which can be referenced in other definitions.
654 def p_enum_block(self
, p
):
655 """enum_block : modifiers ENUM SYMBOL '{' enum_list '}' ';'"""
656 p
[0] = self
.BuildNamed('Enum', p
, 3, ListFromConcat(p
[1], p
[5]))
657 if self
.parse_debug
: DumpReduction('enum_block', p
)
659 # Recover from enum error and continue parsing at the next top match.
660 def p_enum_errorA(self
, p
):
661 """enum_block : modifiers ENUM error '{' enum_list '}' ';'"""
664 def p_enum_errorB(self
, p
):
665 """enum_block : modifiers ENUM error ';'"""
668 def p_enum_list(self
, p
):
669 """enum_list : modifiers SYMBOL '=' expression enum_cont
670 | modifiers SYMBOL enum_cont"""
672 val
= self
.BuildAttribute('VALUE', p
[4])
673 enum
= self
.BuildNamed('EnumItem', p
, 2, ListFromConcat(val
, p
[1]))
674 p
[0] = ListFromConcat(enum
, p
[5])
676 enum
= self
.BuildNamed('EnumItem', p
, 2, p
[1])
677 p
[0] = ListFromConcat(enum
, p
[3])
678 if self
.parse_debug
: DumpReduction('enum_list', p
)
680 def p_enum_cont(self
, p
):
681 """enum_cont : ',' enum_list
683 if len(p
) > 1: p
[0] = p
[2]
684 if self
.parse_debug
: DumpReduction('enum_cont', p
)
686 def p_enum_cont_error(self
, p
):
687 """enum_cont : error enum_cont"""
689 if self
.parse_debug
: DumpReduction('enum_error', p
)
695 # A label is a special kind of enumeration which allows us to go from a
698 def p_label_block(self
, p
):
699 """label_block : modifiers LABEL SYMBOL '{' label_list '}' ';'"""
700 p
[0] = self
.BuildNamed('Label', p
, 3, ListFromConcat(p
[1], p
[5]))
701 if self
.parse_debug
: DumpReduction('label_block', p
)
703 def p_label_list(self
, p
):
704 """label_list : modifiers SYMBOL '=' FLOAT label_cont"""
705 val
= self
.BuildAttribute('VALUE', p
[4])
706 label
= self
.BuildNamed('LabelItem', p
, 2, ListFromConcat(val
, p
[1]))
707 p
[0] = ListFromConcat(label
, p
[5])
708 if self
.parse_debug
: DumpReduction('label_list', p
)
710 def p_label_cont(self
, p
):
711 """label_cont : ',' label_list
713 if len(p
) > 1: p
[0] = p
[2]
714 if self
.parse_debug
: DumpReduction('label_cont', p
)
716 def p_label_cont_error(self
, p
):
717 """label_cont : error label_cont"""
719 if self
.parse_debug
: DumpReduction('label_error', p
)
725 # A member attribute or function of a struct or interface.
727 def p_member_attribute(self
, p
):
728 """member_attribute : modifiers SYMBOL arrays questionmark identifier"""
729 typeref
= self
.BuildAttribute('TYPEREF', p
[2])
730 children
= ListFromConcat(p
[1], typeref
, p
[3], p
[4])
731 p
[0] = self
.BuildNamed('Member', p
, 5, children
)
732 if self
.parse_debug
: DumpReduction('attribute', p
)
734 def p_member_function(self
, p
):
735 """member_function : modifiers static SYMBOL SYMBOL param_list"""
736 typeref
= self
.BuildAttribute('TYPEREF', p
[3])
737 children
= ListFromConcat(p
[1], p
[2], typeref
, p
[5])
738 p
[0] = self
.BuildNamed('Member', p
, 4, children
)
739 if self
.parse_debug
: DumpReduction('function', p
)
741 def p_static(self
, p
):
745 p
[0] = self
.BuildAttribute('STATIC', True)
747 def p_questionmark(self
, p
):
748 """questionmark : '?'
751 p
[0] = self
.BuildAttribute('OPTIONAL', True)
756 # An interface is a named collection of functions.
758 def p_interface_block(self
, p
):
759 """interface_block : modifiers INTERFACE SYMBOL '{' interface_list '}' ';'"""
760 p
[0] = self
.BuildNamed('Interface', p
, 3, ListFromConcat(p
[1], p
[5]))
761 if self
.parse_debug
: DumpReduction('interface_block', p
)
763 def p_interface_error(self
, p
):
764 """interface_block : modifiers INTERFACE error '{' interface_list '}' ';'"""
767 def p_interface_list(self
, p
):
768 """interface_list : member_function ';' interface_list
771 p
[0] = ListFromConcat(p
[1], p
[3])
772 if self
.parse_debug
: DumpReduction('interface_list', p
)
778 # A struct is a named collection of members which in turn reference other
779 # types. The struct is a referencable type.
781 def p_struct_block(self
, p
):
782 """struct_block : modifiers STRUCT SYMBOL '{' struct_list '}' ';'"""
783 children
= ListFromConcat(p
[1], p
[5])
784 p
[0] = self
.BuildNamed('Struct', p
, 3, children
)
785 if self
.parse_debug
: DumpReduction('struct_block', p
)
787 # Recover from struct error and continue parsing at the next top match.
788 def p_struct_error(self
, p
):
789 """enum_block : modifiers STRUCT error '{' struct_list '}' ';'"""
792 def p_struct_list(self
, p
):
793 """struct_list : member_attribute ';' struct_list
794 | member_function ';' struct_list
796 if len(p
) > 1: p
[0] = ListFromConcat(p
[1], p
[3])
802 # p_error is called whenever the parser can not find a pattern match for
803 # a set of items from the current state. The p_error function defined here
804 # is triggered logging an error, and parsing recover happens as the
805 # p_<type>_error functions defined above are called. This allows the parser
806 # to continue so as to capture more than one error per file.
808 def p_error(self
, t
):
809 filename
= self
.lexobj
.filename
810 self
.parse_errors
+= 1
814 prev
= self
.yaccobj
.symstack
[-1]
815 if type(prev
) == lex
.LexToken
:
816 msg
= "Unexpected %s after %s." % (
817 TokenTypeName(t
), TokenTypeName(prev
))
819 msg
= "Unexpected %s." % (t
.value
)
821 lineno
= self
.last
.lineno
822 pos
= self
.last
.lexpos
823 msg
= "Unexpected end of file after %s." % TokenTypeName(self
.last
)
824 self
.yaccobj
.restart()
826 # Attempt to remap the error to a friendlier form
827 if msg
in ERROR_REMAP
:
828 msg
= ERROR_REMAP
[msg
]
831 ErrOut
.LogLine(filename
, lineno
, pos
, msg
)
833 def Warn(self
, node
, msg
):
834 WarnOut
.LogLine(node
.filename
, node
.lineno
, node
.pos
, msg
)
835 self
.parse_warnings
+= 1
838 IDLLexer
.__init
__(self
)
839 self
.yaccobj
= yacc
.yacc(module
=self
, tabmodule
=None, debug
=False,
840 optimize
=0, write_tables
=0)
842 self
.build_debug
= GetOption('build_debug')
843 self
.parse_debug
= GetOption('parse_debug')
844 self
.token_debug
= GetOption('token_debug')
845 self
.verbose
= GetOption('verbose')
846 self
.parse_errors
= 0
851 # The token function returns the next token provided by IDLLexer for matching
852 # against the leaf paterns.
855 tok
= self
.lexobj
.token()
859 InfoOut
.Log("TOKEN %s(%s)" % (tok
.type, tok
.value
))
865 # Production is the set of items sent to a grammar rule resulting in a new
866 # item being returned.
868 # p - Is the Yacc production object containing the stack of items
869 # index - Index into the production of the name for the item being produced.
870 # cls - The type of item being producted
871 # childlist - The children of the new item
872 def BuildProduction(self
, cls
, p
, index
, childlist
=None):
873 if not childlist
: childlist
= []
874 filename
= self
.lexobj
.filename
875 lineno
= p
.lineno(index
)
876 pos
= p
.lexpos(index
)
877 out
= IDLNode(cls
, filename
, lineno
, pos
, childlist
)
879 InfoOut
.Log("Building %s" % out
)
882 def BuildNamed(self
, cls
, p
, index
, childlist
=None):
883 if not childlist
: childlist
= []
884 childlist
.append(self
.BuildAttribute('NAME', p
[index
]))
885 return self
.BuildProduction(cls
, p
, index
, childlist
)
887 def BuildComment(self
, cls
, p
, index
):
890 # Remove comment markers
893 # For C++ style, remove any leading whitespace and the '//' marker from
896 for line
in name
.split('\n'):
897 start
= line
.find('//')
898 lines
.append(line
[start
+2:])
900 # For C style, remove ending '*/''
902 for line
in name
[:-2].split('\n'):
903 # Remove characters until start marker for this line '*' if found
904 # otherwise it should be blank.
905 offs
= line
.find('*')
907 line
= line
[offs
+ 1:].rstrip()
911 name
= '\n'.join(lines
)
913 childlist
= [self
.BuildAttribute('NAME', name
),
914 self
.BuildAttribute('FORM', form
)]
915 return self
.BuildProduction(cls
, p
, index
, childlist
)
920 # An ExtendedAttribute is a special production that results in a property
921 # which is applied to the adjacent item. Attributes have no children and
922 # instead represent key/value pairs.
924 def BuildAttribute(self
, key
, val
):
925 return IDLAttribute(key
, val
)
931 # Attempts to parse the current data loaded in the lexer.
933 def ParseData(self
, data
, filename
='<Internal>'):
934 self
.SetData(filename
, data
)
936 self
.parse_errors
= 0
937 self
.parse_warnings
= 0
938 return self
.yaccobj
.parse(lexer
=self
)
940 except lex
.LexError
as le
:
947 # Loads a new file into the lexer and attemps to parse it.
949 def ParseFile(self
, filename
):
950 date
= time
.ctime(os
.path
.getmtime(filename
))
951 data
= open(filename
).read()
953 InfoOut
.Log("Parsing %s" % filename
)
955 out
= self
.ParseData(data
, filename
)
957 # If we have a src root specified, remove it from the path
958 srcroot
= GetOption('srcroot')
959 if srcroot
and filename
.find(srcroot
) == 0:
960 filename
= filename
[len(srcroot
) + 1:]
961 filenode
= IDLFile(filename
, out
, self
.parse_errors
+ self
.lex_errors
)
962 filenode
.SetProperty('DATETIME', date
)
965 except Exception as e
:
966 ErrOut
.LogLine(filename
, self
.last
.lineno
, self
.last
.lexpos
,
967 'Internal parsing error - %s.' % str(e
))
975 # Flattens the tree of IDLNodes for use in testing.
977 def FlattenTree(node
):
980 for child
in node
.children
:
981 if child
.IsA('Comment'):
984 out
.extend(FlattenTree(child
))
987 out
= [str(node
)] + out
991 def TestErrors(filename
, filenode
):
992 nodelist
= filenode
.GetChildren()
995 data
= open(filename
).read()
996 lexer
.SetData(filename
, data
)
1001 tok
= lexer
.lexobj
.token()
1002 if tok
== None: break
1003 if tok
.type == 'COMMENT':
1004 args
= tok
.value
[3:-3].split()
1006 pass_comments
.append((tok
.lineno
, ' '.join(args
[1:])))
1008 if args
[0] == 'FAIL':
1009 fail_comments
.append((tok
.lineno
, ' '.join(args
[1:])))
1011 for node
in nodelist
:
1012 obj_list
.extend(FlattenTree(node
))
1017 # Check for expected successes
1019 obj_cnt
= len(obj_list
)
1020 pass_cnt
= len(pass_comments
)
1021 if obj_cnt
!= pass_cnt
:
1022 InfoOut
.Log("Mismatched pass (%d) vs. nodes built (%d)."
1023 % (pass_cnt
, obj_cnt
))
1024 InfoOut
.Log("PASS: %s" % [x
[1] for x
in pass_comments
])
1025 InfoOut
.Log("OBJS: %s" % obj_list
)
1027 if pass_cnt
> obj_cnt
: pass_cnt
= obj_cnt
1029 for i
in range(pass_cnt
):
1030 line
, comment
= pass_comments
[i
]
1031 if obj_list
[i
] != comment
:
1032 ErrOut
.LogLine(filename
, line
, None, "OBJ %s : EXPECTED %s\n" %
1033 (obj_list
[i
], comment
))
1037 # Check for expected errors
1039 err_list
= ErrOut
.DrainLog()
1040 err_cnt
= len(err_list
)
1041 fail_cnt
= len(fail_comments
)
1042 if err_cnt
!= fail_cnt
:
1043 InfoOut
.Log("Mismatched fail (%d) vs. errors seen (%d)."
1044 % (fail_cnt
, err_cnt
))
1045 InfoOut
.Log("FAIL: %s" % [x
[1] for x
in fail_comments
])
1046 InfoOut
.Log("ERRS: %s" % err_list
)
1048 if fail_cnt
> err_cnt
: fail_cnt
= err_cnt
1050 for i
in range(fail_cnt
):
1051 line
, comment
= fail_comments
[i
]
1052 err
= err_list
[i
].strip()
1054 if err_list
[i
] != comment
:
1055 ErrOut
.Log("%s(%d) Error\n\tERROR : %s\n\tEXPECT: %s" % (
1056 filename
, line
, err_list
[i
], comment
))
1059 # Clear the error list for the next run
1064 def TestFile(parser
, filename
):
1065 # Capture errors instead of reporting them so we can compare them
1066 # with the expected errors.
1067 ErrOut
.SetConsole(False)
1068 ErrOut
.SetCapture(True)
1070 filenode
= parser
.ParseFile(filename
)
1073 ErrOut
.SetConsole(True)
1074 ErrOut
.SetCapture(False)
1076 # Compare captured errors
1077 return TestErrors(filename
, filenode
)
1080 def TestErrorFiles(filter):
1081 idldir
= os
.path
.split(sys
.argv
[0])[0]
1082 idldir
= os
.path
.join(idldir
, 'test_parser', '*.idl')
1083 filenames
= glob
.glob(idldir
)
1084 parser
= IDLParser()
1086 for filename
in filenames
:
1087 if filter and filename
not in filter: continue
1088 errs
= TestFile(parser
, filename
)
1090 ErrOut
.Log("%s test failed with %d error(s)." % (filename
, errs
))
1094 ErrOut
.Log("Failed parsing test.")
1096 InfoOut
.Log("Passed parsing test.")
1100 def TestNamespaceFiles(filter):
1101 idldir
= os
.path
.split(sys
.argv
[0])[0]
1102 idldir
= os
.path
.join(idldir
, 'test_namespace', '*.idl')
1103 filenames
= glob
.glob(idldir
)
1106 for filename
in filenames
:
1107 if filter and filename
not in filter: continue
1108 testnames
.append(filename
)
1110 # If we have no files to test, then skip this test
1112 InfoOut
.Log('No files to test for namespace.')
1115 InfoOut
.SetConsole(False)
1116 ast
= ParseFiles(testnames
)
1117 InfoOut
.SetConsole(True)
1119 errs
= ast
.GetProperty('ERRORS')
1121 ErrOut
.Log("Failed namespace test.")
1123 InfoOut
.Log("Passed namespace test.")
1128 def FindVersionError(releases
, node
):
1130 if node
.IsA('Interface', 'Struct'):
1132 comment
= node
.GetOneOf('Comment')
1133 if comment
and comment
.GetName()[:4] == 'REL:':
1134 comment_list
= comment
.GetName()[5:].strip().split(' ')
1136 first_list
= [node
.first_release
[rel
] for rel
in releases
]
1137 first_list
= sorted(set(first_list
))
1138 if first_list
!= comment_list
:
1139 node
.Error("Mismatch in releases: %s vs %s." % (
1140 comment_list
, first_list
))
1143 for child
in node
.GetChildren():
1144 err_cnt
+= FindVersionError(releases
, child
)
1148 def TestVersionFiles(filter):
1149 idldir
= os
.path
.split(sys
.argv
[0])[0]
1150 idldir
= os
.path
.join(idldir
, 'test_version', '*.idl')
1151 filenames
= glob
.glob(idldir
)
1154 for filename
in filenames
:
1155 if filter and filename
not in filter: continue
1156 testnames
.append(filename
)
1158 # If we have no files to test, then skip this test
1160 InfoOut
.Log('No files to test for version.')
1163 ast
= ParseFiles(testnames
)
1164 errs
= FindVersionError(ast
.releases
, ast
)
1167 ErrOut
.Log("Failed version test.")
1169 InfoOut
.Log("Passed version test.")
1173 default_dirs
= ['.', 'trusted', 'dev', 'private', 'extensions',
1175 def ParseFiles(filenames
):
1176 parser
= IDLParser()
1181 srcroot
= GetOption('srcroot')
1183 if GetOption('include_private'):
1185 for dirname
in dirs
:
1186 srcdir
= os
.path
.join(srcroot
, dirname
, '*.idl')
1187 srcdir
= os
.path
.normpath(srcdir
)
1188 filenames
+= sorted(glob
.glob(srcdir
))
1191 ErrOut
.Log('No sources provided.')
1193 for filename
in filenames
:
1194 filenode
= parser
.ParseFile(filename
)
1195 filenodes
.append(filenode
)
1197 ast
= IDLAst(filenodes
)
1198 if GetOption('dump_tree'): ast
.Dump(0)
1205 filenames
= ParseOptions(args
)
1208 if GetOption('test'):
1209 errs
= TestErrorFiles(filenames
)
1210 errs
= TestNamespaceFiles(filenames
)
1211 errs
= TestVersionFiles(filenames
)
1213 ErrOut
.Log("Parser failed with %d errors." % errs
)
1217 # Otherwise, build the AST
1218 ast
= ParseFiles(filenames
)
1219 errs
= ast
.GetProperty('ERRORS')
1221 ErrOut
.Log('Found %d error(s).' % errs
);
1222 InfoOut
.Log("%d files processed." % len(filenames
))
1226 if __name__
== '__main__':
1227 sys
.exit(Main(sys
.argv
[1:]))