Infobar material design refresh: bg color
[chromium-blink-merge.git] / ppapi / generators / idl_parser.py
blob823dcf5aeda2a57e1c5dc4f6c80dfd742ec3725e
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.
6 """ Parser for PPAPI IDL """
9 # IDL Parser
11 # The parser is uses the PLY yacc library to build a set of parsing rules based
12 # on WebIDL.
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.
25 import getopt
26 import glob
27 import os.path
28 import re
29 import sys
30 import time
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
39 from ply import lex
40 from ply import yacc
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.')
50 # ERROR_REMAP
52 # Maps the standard error formula into a more friendly error message.
54 ERROR_REMAP = {
55 'Unexpected ")" after "(".' : 'Empty argument list.',
56 'Unexpected ")" after ",".' : 'Missing argument.',
57 'Unexpected "}" after ",".' : 'Trailing comma in block.',
58 'Unexpected "}" after "{".' : 'Unexpected empty block.',
59 'Unexpected comment after "}".' : 'Unexpected trailing comment.',
60 'Unexpected "{" after keyword "enum".' : 'Enum missing name.',
61 'Unexpected "{" after keyword "struct".' : 'Struct missing name.',
62 'Unexpected "{" after keyword "interface".' : 'Interface missing name.',
65 # DumpReduction
67 # Prints out the set of items which matched a particular pattern and the
68 # new item or set it was reduced to.
69 def DumpReduction(cls, p):
70 if p[0] is None:
71 InfoOut.Log("OBJ: %s(%d) - None\n" % (cls, len(p)))
72 InfoOut.Log(" [%s]\n" % [str(x) for x in p[1:]])
73 else:
74 out = ""
75 for index in range(len(p) - 1):
76 out += " >%s< " % str(p[index + 1])
77 InfoOut.Log("OBJ: %s(%d) - %s : %s\n" % (cls, len(p), str(p[0]), out))
80 # CopyToList
82 # Takes an input item, list, or None, and returns a new list of that set.
83 def CopyToList(item):
84 # If the item is 'Empty' make it an empty list
85 if not item: item = []
87 # If the item is not a list
88 if type(item) is not type([]): item = [item]
90 # Make a copy we can modify
91 return list(item)
95 # ListFromConcat
97 # Generate a new List by joining of two sets of inputs which can be an
98 # individual item, a list of items, or None.
99 def ListFromConcat(*items):
100 itemsout = []
101 for item in items:
102 itemlist = CopyToList(item)
103 itemsout.extend(itemlist)
105 return itemsout
108 # TokenTypeName
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
122 # IDL Parser
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> ...."""
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)
157 # or
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):
175 # TOP
177 # This pattern defines the top of the parse tree. The parse tree is in the
178 # the form of:
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
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
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.
215 def p_top(self, p):
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
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)
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]
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] = []
262 # Modifier List
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
283 |"""
284 p[0] = ''.join(p[1:])
285 if self.parse_debug: DumpReduction('scoped_name_rest', p)
288 # Type reference
291 def p_typeref(self, p):
292 """typeref : scoped_name"""
293 p[0] = p[1]
294 if self.parse_debug: DumpReduction('typeref', p)
298 # Comments
300 # Comments are optional list of C style comment objects. Comments are returned
301 # as a list or None.
303 def p_comments(self, p):
304 """comments : COMMENT comments
305 | """
306 if len(p) > 1:
307 child = self.BuildComment('Comment', p, 1)
308 p[0] = ListFromConcat(child, p[2])
309 if self.parse_debug: DumpReduction('comments', p)
310 else:
311 if self.parse_debug: DumpReduction('no comments', p)
315 # Namespace
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"""
327 p[0] = p[1]
331 # Dictionary
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 ';'"""
341 p[0] = []
343 def p_dictionary_errorB(self, p):
344 """dictionary_block : modifiers DICTIONARY error '{' struct_list '}' ';'"""
345 p[0] = []
348 # Callback
350 # A callback is essentially a single function declaration (outside of an
351 # Interface).
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)
360 # Inline
362 # Inline blocks define option code to be emitted based on language tag,
363 # in the form of:
364 # #inline <LANGUAGE>
365 # <CODE>
366 # #endinl
368 def p_inline(self, p):
369 """inline : modifiers INLINE"""
370 words = p[2].split()
371 name = self.BuildAttribute('NAME', words[1])
372 lines = p[2].split('\n')
373 value = self.BuildAttribute('VALUE', '\n'.join(lines[1:-1]) + '\n')
374 children = ListFromConcat(name, value, p[1])
375 p[0] = self.BuildProduction('Inline', p, 2, children)
376 if self.parse_debug: DumpReduction('inline', p)
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:
384 # Name
385 # Name=HEX | INT | OCT | FLOAT
386 # Name="STRING"
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 ']'
396 | """
397 if len(p) > 1:
398 p[0] = p[2]
399 if self.parse_debug: DumpReduction('ext_attr_block', p)
400 else:
401 if self.parse_debug: DumpReduction('no ext_attr_block', p)
403 def p_ext_attr_list(self, p):
404 """ext_attr_list : SYMBOL '=' SYMBOL ext_attr_cont
405 | SYMBOL '=' value ext_attr_cont
406 | SYMBOL '=' SYMBOL param_list ext_attr_cont
407 | SYMBOL ext_attr_cont"""
408 # If there are 4 tokens plus a return slot, this must be in the form
409 # SYMBOL = SYMBOL|value ext_attr_cont
410 if len(p) == 5:
411 p[0] = ListFromConcat(self.BuildAttribute(p[1], p[3]), p[4])
412 # If there are 5 tokens plus a return slot, this must be in the form
413 # SYMBOL = SYMBOL (param_list) ext_attr_cont
414 elif len(p) == 6:
415 member = self.BuildNamed('Member', p, 3, [p[4]])
416 p[0] = ListFromConcat(self.BuildAttribute(p[1], member), p[5])
417 # Otherwise, this must be: SYMBOL ext_attr_cont
418 else:
419 p[0] = ListFromConcat(self.BuildAttribute(p[1], 'True'), p[2])
420 if self.parse_debug: DumpReduction('ext_attribute_list', p)
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
437 | """
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
442 | """
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
447 |"""
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
463 | """
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"""
469 p[0] = p[2]
470 if self.parse_debug: DumpReduction('attr_arg_error', p)
474 # Describe
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"""
489 p[0] = []
491 def p_describe_list(self, p):
492 """describe_list : modifiers SYMBOL ';' describe_list
493 | modifiers ENUM ';' describe_list
494 | modifiers STRUCT ';' describe_list
495 | modifiers TYPEDEF ';' describe_list
496 | """
497 if len(p) > 1:
498 Type = self.BuildNamed('Type', p, 2, p[1])
499 p[0] = ListFromConcat(Type, p[4])
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):
509 """value : FLOAT
510 | HEX
511 | INT
512 | OCT
513 | STRING"""
514 p[0] = p[1]
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):
524 """integer : HEX
525 | INT
526 | OCT"""
527 p[0] = p[1]
528 if self.parse_debug: DumpReduction('integer', p)
531 # Expression
533 # A simple arithmetic expression.
535 precedence = (
536 ('left','|','&','^'),
537 ('left','LSHIFT','RSHIFT'),
538 ('left','+','-'),
539 ('left','*','/'),
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"""
569 p[0] = p[1]
570 if self.parse_debug: DumpReduction('expression_symbol', p)
572 def p_expression_integer(self, p):
573 """expression : integer"""
574 p[0] = p[1]
575 if self.parse_debug: DumpReduction('expression_integer', p)
578 # Array List
580 # Defined a list of array sizes (if any).
582 def p_arrays(self, p):
583 """arrays : '[' ']' arrays
584 | '[' integer ']' arrays
585 | """
586 # If there are 3 tokens plus a return slot it is an unsized array
587 if len(p) == 4:
588 array = self.BuildProduction('Array', p, 1)
589 p[0] = ListFromConcat(array, p[3])
590 # If there are 4 tokens plus a return slot it is a fixed array
591 elif len(p) == 5:
592 count = self.BuildAttribute('FIXED', p[2])
593 array = self.BuildProduction('Array', p, 2, [count])
594 p[0] = ListFromConcat(array, p[4])
595 # If there is only a return slot, do not fill it for this terminator.
596 elif len(p) == 1: return
597 if self.parse_debug: DumpReduction('arrays', p)
600 # An identifier is a legal value for a parameter or attribute name. Lots of
601 # existing IDL files use "callback" as a parameter/attribute name, so we allow
602 # a SYMBOL or the CALLBACK keyword.
603 def p_identifier(self, p):
604 """identifier : SYMBOL
605 | CALLBACK"""
606 p[0] = p[1]
607 # Save the line number of the underlying token (otherwise it gets
608 # discarded), since we use it in the productions with an identifier in
609 # them.
610 p.set_lineno(0, p.lineno(1))
614 # Union
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
627 | union_option"""
628 if len(p) > 2:
629 p[0] = ListFromConcat(p[1], p[3])
630 else:
631 p[0] = p[1]
634 # Parameter List
636 # A parameter list is a collection of arguments which are passed to a
637 # function.
639 def p_param_list(self, p):
640 """param_list : '(' param_item param_cont ')'
641 | '(' ')' """
642 if len(p) > 3:
643 args = ListFromConcat(p[2], p[3])
644 else:
645 args = []
646 p[0] = self.BuildProduction('Callspec', p, 1, args)
647 if self.parse_debug: DumpReduction('param_list', p)
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
665 | """
666 if len(p) == 2:
667 p[0] = self.BuildAttribute('OPTIONAL', True)
670 def p_param_cont(self, p):
671 """param_cont : ',' param_item param_cont
672 | """
673 if len(p) > 1:
674 p[0] = ListFromConcat(p[2], p[3])
675 if self.parse_debug: DumpReduction('param_cont', p)
677 def p_param_error(self, p):
678 """param_cont : error param_cont"""
679 p[0] = p[2]
683 # Typedef
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)
710 # Enumeration
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 '}' ';'"""
723 p[0] = []
725 def p_enum_errorB(self, p):
726 """enum_block : modifiers ENUM error ';'"""
727 p[0] = []
729 def p_enum_list(self, p):
730 """enum_list : modifiers SYMBOL '=' expression enum_cont
731 | modifiers SYMBOL enum_cont"""
732 if len(p) > 4:
733 val = self.BuildAttribute('VALUE', p[4])
734 enum = self.BuildNamed('EnumItem', p, 2, ListFromConcat(val, p[1]))
735 p[0] = ListFromConcat(enum, p[5])
736 else:
737 enum = self.BuildNamed('EnumItem', p, 2, p[1])
738 p[0] = ListFromConcat(enum, p[3])
739 if self.parse_debug: DumpReduction('enum_list', p)
741 def p_enum_cont(self, p):
742 """enum_cont : ',' enum_list
743 |"""
744 if len(p) > 1: p[0] = p[2]
745 if self.parse_debug: DumpReduction('enum_cont', p)
747 def p_enum_cont_error(self, p):
748 """enum_cont : error enum_cont"""
749 p[0] = p[2]
750 if self.parse_debug: DumpReduction('enum_error', p)
754 # Label
756 # A label is a special kind of enumeration which allows us to go from a
757 # set of labels
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
773 |"""
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"""
779 p[0] = p[2]
780 if self.parse_debug: DumpReduction('label_error', p)
784 # Members
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):
810 """static : STATIC
811 | """
812 if len(p) == 2:
813 p[0] = self.BuildAttribute('STATIC', True)
815 def p_questionmark(self, p):
816 """questionmark : '?'
817 | """
818 if len(p) == 2:
819 p[0] = self.BuildAttribute('OPTIONAL', True)
822 # Interface
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 '}' ';'"""
833 p[0] = []
835 def p_interface_list(self, p):
836 """interface_list : member_function ';' interface_list
837 | """
838 if len(p) > 1 :
839 p[0] = ListFromConcat(p[1], p[3])
840 if self.parse_debug: DumpReduction('interface_list', p)
844 # Struct
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 '}' ';'"""
858 p[0] = []
860 def p_struct_list(self, p):
861 """struct_list : member_attribute ';' struct_list
862 | member_function ';' struct_list
863 |"""
864 if len(p) > 1: p[0] = ListFromConcat(p[1], p[3])
868 # Parser Errors
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
879 if t:
880 lineno = t.lineno
881 pos = t.lexpos
882 prev = self.yaccobj.symstack[-1]
883 if type(prev) == lex.LexToken:
884 msg = "Unexpected %s after %s." % (
885 TokenTypeName(t), TokenTypeName(prev))
886 else:
887 msg = "Unexpected %s." % (t.value)
888 else:
889 lineno = self.last.lineno
890 pos = self.last.lexpos
891 msg = "Unexpected end of file after %s." % TokenTypeName(self.last)
892 self.yaccobj.restart()
894 # Attempt to remap the error to a friendlier form
895 if msg in ERROR_REMAP:
896 msg = ERROR_REMAP[msg]
898 # Log the error
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
905 def __init__(self):
906 IDLLexer.__init__(self)
907 self.yaccobj = yacc.yacc(module=self, tabmodule=None, debug=False,
908 optimize=0, write_tables=0)
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
917 # Tokenizer
919 # The token function returns the next token provided by IDLLexer for matching
920 # against the leaf paterns.
922 def token(self):
923 tok = self.lexobj.token()
924 if tok:
925 self.last = tok
926 if self.token_debug:
927 InfoOut.Log("TOKEN %s(%s)" % (tok.type, tok.value))
928 return tok
931 # BuildProduction
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)
946 if self.build_debug:
947 InfoOut.Log("Building %s" % out)
948 return 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):
956 name = p[index]
958 # Remove comment markers
959 lines = []
960 if name[:2] == '//':
961 # For C++ style, remove any leading whitespace and the '//' marker from
962 # each line.
963 form = 'cc'
964 for line in name.split('\n'):
965 start = line.find('//')
966 lines.append(line[start+2:])
967 else:
968 # For C style, remove ending '*/''
969 form = 'c'
970 for line in name[:-2].split('\n'):
971 # Remove characters until start marker for this line '*' if found
972 # otherwise it should be blank.
973 offs = line.find('*')
974 if offs >= 0:
975 line = line[offs + 1:].rstrip()
976 else:
977 line = ''
978 lines.append(line)
979 name = '\n'.join(lines)
981 childlist = [self.BuildAttribute('NAME', name),
982 self.BuildAttribute('FORM', form)]
983 return self.BuildProduction(cls, p, index, childlist)
986 # BuildAttribute
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)
997 # ParseData
999 # Attempts to parse the current data loaded in the lexer.
1001 def ParseData(self, data, filename='<Internal>'):
1002 self.SetData(filename, data)
1003 try:
1004 self.parse_errors = 0
1005 self.parse_warnings = 0
1006 return self.yaccobj.parse(lexer=self)
1008 except lex.LexError as le:
1009 ErrOut.Log(str(le))
1010 return []
1013 # ParseFile
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()
1020 if self.verbose:
1021 InfoOut.Log("Parsing %s" % filename)
1022 try:
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)
1031 return filenode
1033 except Exception as e:
1034 ErrOut.LogLine(filename, self.last.lineno, self.last.lexpos,
1035 'Internal parsing error - %s.' % str(e))
1036 raise
1041 # Flatten Tree
1043 # Flattens the tree of IDLNodes for use in testing.
1045 def FlattenTree(node):
1046 add_self = False
1047 out = []
1048 for child in node.GetChildren():
1049 if child.IsA('Comment'):
1050 add_self = True
1051 else:
1052 out.extend(FlattenTree(child))
1054 if add_self:
1055 out = [str(node)] + out
1056 return out
1059 def TestErrors(filename, filenode):
1060 nodelist = filenode.GetChildren()
1062 lexer = IDLLexer()
1063 data = open(filename).read()
1064 lexer.SetData(filename, data)
1066 pass_comments = []
1067 fail_comments = []
1068 while True:
1069 tok = lexer.lexobj.token()
1070 if tok == None: break
1071 if tok.type == 'COMMENT':
1072 args = tok.value[3:-3].split()
1073 if args[0] == 'OK':
1074 pass_comments.append((tok.lineno, ' '.join(args[1:])))
1075 else:
1076 if args[0] == 'FAIL':
1077 fail_comments.append((tok.lineno, ' '.join(args[1:])))
1078 obj_list = []
1079 for node in nodelist:
1080 obj_list.extend(FlattenTree(node))
1082 errors = 0
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)
1094 errors += 1
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))
1102 errors += 1
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)
1115 errors += 1
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))
1125 errors += 1
1127 # Clear the error list for the next run
1128 err_list = []
1129 return errors
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)
1140 # Renable output
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()
1153 total_errs = 0
1154 for filename in filenames:
1155 if filter and filename not in filter: continue
1156 errs = TestFile(parser, filename)
1157 if errs:
1158 ErrOut.Log("%s test failed with %d error(s)." % (filename, errs))
1159 total_errs += errs
1161 if total_errs:
1162 ErrOut.Log("Failed parsing test.")
1163 else:
1164 InfoOut.Log("Passed parsing test.")
1165 return total_errs
1168 def TestNamespaceFiles(filter):
1169 idldir = os.path.split(sys.argv[0])[0]
1170 idldir = os.path.join(idldir, 'test_namespace', '*.idl')
1171 filenames = glob.glob(idldir)
1172 testnames = []
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
1179 if not testnames:
1180 InfoOut.Log('No files to test for namespace.')
1181 return 0
1183 InfoOut.SetConsole(False)
1184 ast = ParseFiles(testnames)
1185 InfoOut.SetConsole(True)
1187 errs = ast.GetProperty('ERRORS')
1188 if errs:
1189 ErrOut.Log("Failed namespace test.")
1190 else:
1191 InfoOut.Log("Passed namespace test.")
1192 return errs
1196 def FindVersionError(releases, node):
1197 err_cnt = 0
1198 if node.IsA('Interface', 'Struct'):
1199 comment_list = []
1200 comment = node.GetOneOf('Comment')
1201 if comment and comment.GetName()[:4] == 'REL:':
1202 comment_list = comment.GetName()[5:].strip().split(' ')
1204 first_list = [node.first_release[rel] for rel in releases]
1205 first_list = sorted(set(first_list))
1206 if first_list != comment_list:
1207 node.Error("Mismatch in releases: %s vs %s." % (
1208 comment_list, first_list))
1209 err_cnt += 1
1211 for child in node.GetChildren():
1212 err_cnt += FindVersionError(releases, child)
1213 return err_cnt
1216 def TestVersionFiles(filter):
1217 idldir = os.path.split(sys.argv[0])[0]
1218 idldir = os.path.join(idldir, 'test_version', '*.idl')
1219 filenames = glob.glob(idldir)
1220 testnames = []
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
1227 if not testnames:
1228 InfoOut.Log('No files to test for version.')
1229 return 0
1231 ast = ParseFiles(testnames)
1232 errs = FindVersionError(ast.releases, ast)
1233 errs += ast.errors
1235 if errs:
1236 ErrOut.Log("Failed version test.")
1237 else:
1238 InfoOut.Log("Passed version test.")
1239 return errs
1242 default_dirs = ['.', 'trusted', 'dev', 'private']
1243 def ParseFiles(filenames):
1244 parser = IDLParser()
1245 filenodes = []
1247 if not filenames:
1248 filenames = []
1249 srcroot = GetOption('srcroot')
1250 dirs = default_dirs
1251 if GetOption('include_private'):
1252 dirs += ['private']
1253 for dirname in dirs:
1254 srcdir = os.path.join(srcroot, dirname, '*.idl')
1255 srcdir = os.path.normpath(srcdir)
1256 filenames += sorted(glob.glob(srcdir))
1258 if not filenames:
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)
1268 Lint(ast)
1269 return ast
1272 def Main(args):
1273 filenames = ParseOptions(args)
1275 # If testing...
1276 if GetOption('test'):
1277 errs = TestErrorFiles(filenames)
1278 errs = TestNamespaceFiles(filenames)
1279 errs = TestVersionFiles(filenames)
1280 if errs:
1281 ErrOut.Log("Parser failed with %d errors." % errs)
1282 return -1
1283 return 0
1285 # Otherwise, build the AST
1286 ast = ParseFiles(filenames)
1287 errs = ast.GetProperty('ERRORS')
1288 if errs:
1289 ErrOut.Log('Found %d error(s).' % errs);
1290 InfoOut.Log("%d files processed." % len(filenames))
1291 return errs
1294 if __name__ == '__main__':
1295 sys.exit(Main(sys.argv[1:]))