Fix the no password save issue for ajax login
[chromium-blink-merge.git] / ppapi / generators / idl_parser.py
blob95b0ddb7087fb0d3c1ab68932d1dfe12ea9f6ef7
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
38 from idl_visitor import IDLVisitor
40 from ply import lex
41 from ply import yacc
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.')
51 # ERROR_REMAP
53 # Maps the standard error formula into a more friendly error message.
55 ERROR_REMAP = {
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.',
66 # DumpReduction
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):
71 if p[0] is None:
72 InfoOut.Log("OBJ: %s(%d) - None\n" % (cls, len(p)))
73 InfoOut.Log(" [%s]\n" % [str(x) for x in p[1:]])
74 else:
75 out = ""
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))
81 # CopyToList
83 # Takes an input item, list, or None, and returns a new list of that set.
84 def CopyToList(item):
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
92 return list(item)
96 # ListFromConcat
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):
101 itemsout = []
102 for item in items:
103 itemlist = CopyToList(item)
104 itemsout.extend(itemlist)
106 return itemsout
109 # TokenTypeName
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
123 # IDL Parser
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> ....
130 # | <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)
158 # or
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):
176 # TOP
178 # This pattern defines the top of the parse tree. The parse tree is in the
179 # the form of:
181 # top
182 # *modifiers
183 # *comments
184 # *ext_attr_block
185 # ext_attr_list
186 # attr_arg_list
187 # *integer, value
188 # *param_list
189 # *typeref
191 # top_list
192 # describe_block
193 # describe_list
194 # enum_block
195 # enum_item
196 # interface_block
197 # member
198 # label_block
199 # label_item
200 # struct_block
201 # member
202 # typedef_decl
203 # typedef_data
204 # typedef_func
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.
216 def p_top(self, p):
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
240 | inline top_list
241 | interface_block top_list
242 | label_block top_list
243 | namespace top_list
244 | struct_block top_list
245 | typedef_decl top_list
246 | bad_decl top_list
247 | """
248 if len(p) > 2:
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"""
255 p[0] = p[2]
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 '}' ';'"""
260 p[0] = []
263 # Modifier List
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)
272 # Comments
274 # Comments are optional list of C style comment objects. Comments are returned
275 # as a list or None.
277 def p_comments(self, p):
278 """comments : COMMENT comments
279 | """
280 if len(p) > 1:
281 child = self.BuildComment('Comment', p, 1)
282 p[0] = ListFromConcat(child, p[2])
283 if self.parse_debug: DumpReduction('comments', p)
284 else:
285 if self.parse_debug: DumpReduction('no comments', p)
289 # Namespace
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:])
306 # Dictionary
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]))
315 # Callback
317 # A callback is essentially a single function declaration (outside of an
318 # Interface).
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)
327 # Inline
329 # Inline blocks define option code to be emitted based on language tag,
330 # in the form of:
331 # #inline <LANGUAGE>
332 # <CODE>
333 # #endinl
335 def p_inline(self, p):
336 """inline : modifiers INLINE"""
337 words = p[2].split()
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:
351 # Name
352 # Name=HEX | INT | OCT | FLOAT
353 # Name="STRING"
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 ']'
363 | """
364 if len(p) > 1:
365 p[0] = p[2]
366 if self.parse_debug: DumpReduction('ext_attr_block', p)
367 else:
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
377 if len(p) == 5:
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
381 elif len(p) == 6:
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
385 else:
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
404 | """
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
409 | """
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
414 |"""
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
430 | """
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"""
436 p[0] = p[2]
437 if self.parse_debug: DumpReduction('attr_arg_error', p)
441 # Describe
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"""
456 p[0] = []
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
463 | """
464 if len(p) > 1:
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):
476 """value : FLOAT
477 | HEX
478 | INT
479 | OCT
480 | STRING"""
481 p[0] = p[1]
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):
491 """integer : HEX
492 | INT
493 | OCT"""
494 p[0] = p[1]
495 if self.parse_debug: DumpReduction('integer', p)
498 # Expression
500 # A simple arithmetic expression.
502 precedence = (
503 ('left','|','&','^'),
504 ('left','LSHIFT','RSHIFT'),
505 ('left','+','-'),
506 ('left','*','/'),
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"
536 p[0] = p[1]
537 if self.parse_debug: DumpReduction('expression_symbol', p)
539 def p_expression_integer(self, p):
540 "expression : integer"
541 p[0] = p[1]
542 if self.parse_debug: DumpReduction('expression_integer', p)
545 # Array List
547 # Defined a list of array sizes (if any).
549 def p_arrays(self, p):
550 """arrays : '[' ']' arrays
551 | '[' integer ']' arrays
552 | """
553 # If there are 3 tokens plus a return slot it is an unsized array
554 if len(p) == 4:
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
558 elif len(p) == 5:
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
572 | CALLBACK"""
573 p[0] = p[1]
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
576 # them.
577 p.set_lineno(0, p.lineno(1))
580 # Parameter List
582 # A parameter list is a collection of arguments which are passed to a
583 # function.
585 def p_param_list(self, p):
586 """param_list : '(' param_item param_cont ')'
587 | '(' ')' """
588 if len(p) > 3:
589 args = ListFromConcat(p[2], p[3])
590 else:
591 args = []
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
604 | """
605 if len(p) == 2:
606 p[0] = self.BuildAttribute('OPTIONAL', True)
609 def p_param_cont(self, p):
610 """param_cont : ',' param_item param_cont
611 | """
612 if len(p) > 1:
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"""
618 p[0] = p[2]
622 # Typedef
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)
649 # Enumeration
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 '}' ';'"""
662 p[0] = []
664 def p_enum_errorB(self, p):
665 """enum_block : modifiers ENUM error ';'"""
666 p[0] = []
668 def p_enum_list(self, p):
669 """enum_list : modifiers SYMBOL '=' expression enum_cont
670 | modifiers SYMBOL enum_cont"""
671 if len(p) > 4:
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])
675 else:
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
682 |"""
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"""
688 p[0] = p[2]
689 if self.parse_debug: DumpReduction('enum_error', p)
693 # Label
695 # A label is a special kind of enumeration which allows us to go from a
696 # set of labels
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
712 |"""
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"""
718 p[0] = p[2]
719 if self.parse_debug: DumpReduction('label_error', p)
723 # Members
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):
742 """static : STATIC
743 | """
744 if len(p) == 2:
745 p[0] = self.BuildAttribute('STATIC', True)
747 def p_questionmark(self, p):
748 """questionmark : '?'
749 | """
750 if len(p) == 2:
751 p[0] = self.BuildAttribute('OPTIONAL', True)
754 # Interface
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 '}' ';'"""
765 p[0] = []
767 def p_interface_list(self, p):
768 """interface_list : member_function ';' interface_list
769 | """
770 if len(p) > 1 :
771 p[0] = ListFromConcat(p[1], p[3])
772 if self.parse_debug: DumpReduction('interface_list', p)
776 # Struct
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 '}' ';'"""
790 p[0] = []
792 def p_struct_list(self, p):
793 """struct_list : member_attribute ';' struct_list
794 | member_function ';' struct_list
795 |"""
796 if len(p) > 1: p[0] = ListFromConcat(p[1], p[3])
800 # Parser Errors
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
811 if t:
812 lineno = t.lineno
813 pos = t.lexpos
814 prev = self.yaccobj.symstack[-1]
815 if type(prev) == lex.LexToken:
816 msg = "Unexpected %s after %s." % (
817 TokenTypeName(t), TokenTypeName(prev))
818 else:
819 msg = "Unexpected %s." % (t.value)
820 else:
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]
830 # Log the error
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
837 def __init__(self):
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
849 # Tokenizer
851 # The token function returns the next token provided by IDLLexer for matching
852 # against the leaf paterns.
854 def token(self):
855 tok = self.lexobj.token()
856 if tok:
857 self.last = tok
858 if self.token_debug:
859 InfoOut.Log("TOKEN %s(%s)" % (tok.type, tok.value))
860 return tok
863 # BuildProduction
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)
878 if self.build_debug:
879 InfoOut.Log("Building %s" % out)
880 return 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):
888 name = p[index]
890 # Remove comment markers
891 lines = []
892 if name[:2] == '//':
893 # For C++ style, remove any leading whitespace and the '//' marker from
894 # each line.
895 form = 'cc'
896 for line in name.split('\n'):
897 start = line.find('//')
898 lines.append(line[start+2:])
899 else:
900 # For C style, remove ending '*/''
901 form = 'c'
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('*')
906 if offs >= 0:
907 line = line[offs + 1:].rstrip()
908 else:
909 line = ''
910 lines.append(line)
911 name = '\n'.join(lines)
913 childlist = [self.BuildAttribute('NAME', name),
914 self.BuildAttribute('FORM', form)]
915 return self.BuildProduction(cls, p, index, childlist)
918 # BuildAttribute
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)
929 # ParseData
931 # Attempts to parse the current data loaded in the lexer.
933 def ParseData(self, data, filename='<Internal>'):
934 self.SetData(filename, data)
935 try:
936 self.parse_errors = 0
937 self.parse_warnings = 0
938 return self.yaccobj.parse(lexer=self)
940 except lex.LexError as le:
941 ErrOut.Log(str(le))
942 return []
945 # ParseFile
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()
952 if self.verbose:
953 InfoOut.Log("Parsing %s" % filename)
954 try:
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)
963 return filenode
965 except Exception as e:
966 ErrOut.LogLine(filename, self.last.lineno, self.last.lexpos,
967 'Internal parsing error - %s.' % str(e))
968 raise
973 # Flatten Tree
975 # Flattens the tree of IDLNodes for use in testing.
977 def FlattenTree(node):
978 add_self = False
979 out = []
980 for child in node.children:
981 if child.IsA('Comment'):
982 add_self = True
983 else:
984 out.extend(FlattenTree(child))
986 if add_self:
987 out = [str(node)] + out
988 return out
991 def TestErrors(filename, filenode):
992 nodelist = filenode.GetChildren()
994 lexer = IDLLexer()
995 data = open(filename).read()
996 lexer.SetData(filename, data)
998 pass_comments = []
999 fail_comments = []
1000 while True:
1001 tok = lexer.lexobj.token()
1002 if tok == None: break
1003 if tok.type == 'COMMENT':
1004 args = tok.value[3:-3].split()
1005 if args[0] == 'OK':
1006 pass_comments.append((tok.lineno, ' '.join(args[1:])))
1007 else:
1008 if args[0] == 'FAIL':
1009 fail_comments.append((tok.lineno, ' '.join(args[1:])))
1010 obj_list = []
1011 for node in nodelist:
1012 obj_list.extend(FlattenTree(node))
1014 errors = 0
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)
1026 errors += 1
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))
1034 errors += 1
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)
1047 errors += 1
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))
1057 errors += 1
1059 # Clear the error list for the next run
1060 err_list = []
1061 return errors
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)
1072 # Renable output
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()
1085 total_errs = 0
1086 for filename in filenames:
1087 if filter and filename not in filter: continue
1088 errs = TestFile(parser, filename)
1089 if errs:
1090 ErrOut.Log("%s test failed with %d error(s)." % (filename, errs))
1091 total_errs += errs
1093 if total_errs:
1094 ErrOut.Log("Failed parsing test.")
1095 else:
1096 InfoOut.Log("Passed parsing test.")
1097 return total_errs
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)
1104 testnames = []
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
1111 if not testnames:
1112 InfoOut.Log('No files to test for namespace.')
1113 return 0
1115 InfoOut.SetConsole(False)
1116 ast = ParseFiles(testnames)
1117 InfoOut.SetConsole(True)
1119 errs = ast.GetProperty('ERRORS')
1120 if errs:
1121 ErrOut.Log("Failed namespace test.")
1122 else:
1123 InfoOut.Log("Passed namespace test.")
1124 return errs
1128 def FindVersionError(releases, node):
1129 err_cnt = 0
1130 if node.IsA('Interface', 'Struct'):
1131 comment_list = []
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))
1141 err_cnt += 1
1143 for child in node.GetChildren():
1144 err_cnt += FindVersionError(releases, child)
1145 return err_cnt
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)
1152 testnames = []
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
1159 if not testnames:
1160 InfoOut.Log('No files to test for version.')
1161 return 0
1163 ast = ParseFiles(testnames)
1164 errs = FindVersionError(ast.releases, ast)
1166 if errs:
1167 ErrOut.Log("Failed version test.")
1168 else:
1169 InfoOut.Log("Passed version test.")
1170 return errs
1173 default_dirs = ['.', 'trusted', 'dev', 'private', 'extensions',
1174 'extensions/dev']
1175 def ParseFiles(filenames):
1176 parser = IDLParser()
1177 filenodes = []
1179 if not filenames:
1180 filenames = []
1181 srcroot = GetOption('srcroot')
1182 dirs = default_dirs
1183 if GetOption('include_private'):
1184 dirs += ['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))
1190 if not filenames:
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)
1200 Lint(ast)
1201 return ast
1204 def Main(args):
1205 filenames = ParseOptions(args)
1207 # If testing...
1208 if GetOption('test'):
1209 errs = TestErrorFiles(filenames)
1210 errs = TestNamespaceFiles(filenames)
1211 errs = TestVersionFiles(filenames)
1212 if errs:
1213 ErrOut.Log("Parser failed with %d errors." % errs)
1214 return -1
1215 return 0
1217 # Otherwise, build the AST
1218 ast = ParseFiles(filenames)
1219 errs = ast.GetProperty('ERRORS')
1220 if errs:
1221 ErrOut.Log('Found %d error(s).' % errs);
1222 InfoOut.Log("%d files processed." % len(filenames))
1223 return errs
1226 if __name__ == '__main__':
1227 sys.exit(Main(sys.argv[1:]))