• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python
2# Copyright (c) 2012 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6""" Parser for PPAPI IDL """
7
8#
9# IDL Parser
10#
11# The parser is uses the PLY yacc library to build a set of parsing rules based
12# on WebIDL.
13#
14# WebIDL, and WebIDL regular expressions can be found at:
15#   http://dev.w3.org/2006/webapi/WebIDL/
16# PLY can be found at:
17#   http://www.dabeaz.com/ply/
18#
19# The parser generates a tree by recursively matching sets of items against
20# defined patterns.  When a match is made, that set of items is reduced
21# to a new item.   The new item can provide a match for parent patterns.
22# In this way an AST is built (reduced) depth first.
23
24
25import getopt
26import glob
27import os.path
28import re
29import sys
30import time
31
32from idl_ast import IDLAst
33from idl_log import ErrOut, InfoOut, WarnOut
34from idl_lexer import IDLLexer
35from idl_node import IDLAttribute, IDLFile, IDLNode
36from idl_option import GetOption, Option, ParseOptions
37from idl_lint import Lint
38
39from ply import lex
40from ply import yacc
41
42Option('build_debug', 'Debug tree building.')
43Option('parse_debug', 'Debug parse reduction steps.')
44Option('token_debug', 'Debug token generation.')
45Option('dump_tree', 'Dump the tree.')
46Option('srcroot', 'Working directory.', default=os.path.join('..', 'api'))
47Option('include_private', 'Include private IDL directory in default API paths.')
48
49#
50# ERROR_REMAP
51#
52# Maps the standard error formula into a more friendly error message.
53#
54ERROR_REMAP = {
55  'Unexpected ")" after "(".' : 'Empty argument list.',
56  'Unexpected ")" after ",".' : 'Missing argument.',
57  'Unexpected "}" after ",".' : 'Trailing comma in block.',
58  'Unexpected "}" after "{".' : 'Unexpected empty block.',
59  'Unexpected comment after "}".' : 'Unexpected trailing comment.',
60  'Unexpected "{" after keyword "enum".' : 'Enum missing name.',
61  'Unexpected "{" after keyword "struct".' : 'Struct missing name.',
62  'Unexpected "{" after keyword "interface".' : 'Interface missing name.',
63}
64
65# DumpReduction
66#
67# Prints out the set of items which matched a particular pattern and the
68# new item or set it was reduced to.
69def DumpReduction(cls, p):
70  if p[0] is None:
71    InfoOut.Log("OBJ: %s(%d) - None\n" % (cls, len(p)))
72    InfoOut.Log("  [%s]\n" % [str(x) for x in p[1:]])
73  else:
74    out = ""
75    for index in range(len(p) - 1):
76      out += " >%s< " % str(p[index + 1])
77    InfoOut.Log("OBJ: %s(%d) - %s : %s\n"  % (cls, len(p), str(p[0]), out))
78
79
80# CopyToList
81#
82# Takes an input item, list, or None, and returns a new list of that set.
83def CopyToList(item):
84  # If the item is 'Empty' make it an empty list
85  if not item: item = []
86
87  # If the item is not a list
88  if type(item) is not type([]): item = [item]
89
90  # Make a copy we can modify
91  return list(item)
92
93
94
95# ListFromConcat
96#
97# Generate a new List by joining of two sets of inputs which can be an
98# individual item, a list of items, or None.
99def ListFromConcat(*items):
100  itemsout = []
101  for item in items:
102    itemlist = CopyToList(item)
103    itemsout.extend(itemlist)
104
105  return itemsout
106
107
108# TokenTypeName
109#
110# Generate a string which has the type and value of the token.
111def TokenTypeName(t):
112  if t.type == 'SYMBOL':  return 'symbol %s' % t.value
113  if t.type in ['HEX', 'INT', 'OCT', 'FLOAT']:
114    return 'value %s' % t.value
115  if t.type == 'STRING' : return 'string "%s"' % t.value
116  if t.type == 'COMMENT' : return 'comment'
117  if t.type == t.value: return '"%s"' % t.value
118  return 'keyword "%s"' % t.value
119
120
121#
122# IDL Parser
123#
124# The Parser inherits the from the Lexer to provide PLY with the tokenizing
125# definitions.  Parsing patterns are encoded as function where p_<name> is
126# is called any time a patern matching the function documentation is found.
127# Paterns are expressed in the form of:
128# """ <new item> : <item> ....
129#                | <item> ...."""
130#
131# Where new item is the result of a match against one or more sets of items
132# separated by the "|".
133#
134# The function is called with an object 'p' where p[0] is the output object
135# and p[n] is the set of inputs for positive values of 'n'.  Len(p) can be
136# used to distinguish between multiple item sets in the pattern.
137#
138# For more details on parsing refer to the PLY documentation at
139#    http://www.dabeaz.com/ply/
140#
141#
142# The parser uses the following conventions:
143#   a <type>_block defines a block of <type> definitions in the form of:
144#       [comment] [ext_attr_block] <type> <name> '{' <type>_list '}' ';'
145#   A block is reduced by returning an object of <type> with a name of <name>
146#   which in turn has <type>_list as children.
147#
148#   A [comment] is a optional C style comment block enclosed in /* ... */ which
149#   is appended to the adjacent node as a child.
150#
151#   A [ext_attr_block] is an optional list of Extended Attributes which is
152#   appended to the adjacent node as a child.
153#
154#   a <type>_list defines a list of <type> items which will be passed as a
155#   list of children to the parent pattern.  A list is in the form of:
156#       [comment] [ext_attr_block] <...DEF...> ';' <type>_list | (empty)
157# or
158#       [comment] [ext_attr_block] <...DEF...> <type>_cont
159#
160#   In the first form, the list is reduced recursively, where the right side
161#   <type>_list is first reduced then joined with pattern currently being
162#   matched.  The list is terminated with the (empty) pattern is matched.
163#
164#   In the second form the list is reduced recursively, where the right side
165#   <type>_cont is first reduced then joined with the pattern currently being
166#   matched.  The type_<cont> is in the form of:
167#       ',' <type>_list | (empty)
168#   The <type>_cont form is used to consume the ',' which only occurs when
169#   there is more than one object in the list.  The <type>_cont also provides
170#   the terminating (empty) definition.
171#
172
173
174class IDLParser(IDLLexer):
175# TOP
176#
177# This pattern defines the top of the parse tree.  The parse tree is in the
178# the form of:
179#
180# top
181#   *modifiers
182#     *comments
183#     *ext_attr_block
184#       ext_attr_list
185#          attr_arg_list
186#   *integer, value
187#   *param_list
188#   *typeref
189#
190#   top_list
191#     describe_block
192#       describe_list
193#     enum_block
194#       enum_item
195#     interface_block
196#       member
197#     label_block
198#       label_item
199#     struct_block
200#       member
201#     typedef_decl
202#       typedef_data
203#       typedef_func
204#
205# (* sub matches found at multiple levels and are not truly children of top)
206#
207# We force all input files to start with two comments.  The first comment is a
208# Copyright notice followed by a set of file wide Extended Attributes, followed
209# by the file comment and finally by file level patterns.
210#
211  # Find the Copyright, File comment, and optional file wide attributes.  We
212  # use a match with COMMENT instead of comments to force the token to be
213  # present.  The extended attributes and the top_list become siblings which
214  # in turn are children of the file object created from the results of top.
215  def p_top(self, p):
216    """top : COMMENT COMMENT ext_attr_block top_list"""
217
218    Copyright = self.BuildComment('Copyright', p, 1)
219    Filedoc = self.BuildComment('Comment', p, 2)
220
221    p[0] = ListFromConcat(Copyright, Filedoc, p[3], p[4])
222    if self.parse_debug: DumpReduction('top', p)
223
224  def p_top_short(self, p):
225    """top : COMMENT ext_attr_block top_list"""
226    Copyright = self.BuildComment('Copyright', p, 1)
227    Filedoc = IDLNode('Comment', self.lexobj.filename, p.lineno(2)-1,
228        p.lexpos(2)-1, [self.BuildAttribute('NAME', ''),
229          self.BuildAttribute('FORM', 'cc')])
230    p[0] = ListFromConcat(Copyright, Filedoc, p[2], p[3])
231    if self.parse_debug: DumpReduction('top', p)
232
233  # Build a list of top level items.
234  def p_top_list(self, p):
235    """top_list : callback_decl top_list
236                | describe_block top_list
237                | dictionary_block top_list
238                | enum_block top_list
239                | inline top_list
240                | interface_block top_list
241                | label_block top_list
242                | namespace top_list
243                | struct_block top_list
244                | typedef_decl top_list
245                | bad_decl top_list
246                | """
247    if len(p) > 2:
248      p[0] = ListFromConcat(p[1], p[2])
249    if self.parse_debug: DumpReduction('top_list', p)
250
251  # Recover from error and continue parsing at the next top match.
252  def p_top_error(self, p):
253    """top_list : error top_list"""
254    p[0] = p[2]
255
256  # Recover from error and continue parsing at the next top match.
257  def p_bad_decl(self, p):
258    """bad_decl : modifiers SYMBOL error '}' ';'"""
259    p[0] = []
260
261#
262# Modifier List
263#
264#
265  def p_modifiers(self, p):
266    """modifiers : comments ext_attr_block"""
267    p[0] = ListFromConcat(p[1], p[2])
268    if self.parse_debug: DumpReduction('modifiers', p)
269
270#
271# Comments
272#
273# Comments are optional list of C style comment objects.  Comments are returned
274# as a list or None.
275#
276  def p_comments(self, p):
277    """comments : COMMENT comments
278                | """
279    if len(p) > 1:
280      child = self.BuildComment('Comment', p, 1)
281      p[0] = ListFromConcat(child, p[2])
282      if self.parse_debug: DumpReduction('comments', p)
283    else:
284      if self.parse_debug: DumpReduction('no comments', p)
285
286
287#
288# Namespace
289#
290# A namespace provides a named scope to an enclosed top_list.
291#
292  def p_namespace(self, p):
293    """namespace : modifiers NAMESPACE namespace_name '{' top_list '}' ';'"""
294    children = ListFromConcat(p[1], p[5])
295    p[0] = self.BuildNamed('Namespace', p, 3, children)
296
297  # We allow namespace names of the form foo.bar.baz.
298  def p_namespace_name(self, p):
299    """namespace_name : SYMBOL
300                      | SYMBOL '.' namespace_name"""
301    p[0] = "".join(p[1:])
302
303
304#
305# Dictionary
306#
307# A dictionary is a named list of optional and required members.
308#
309  def p_dictionary_block(self, p):
310    """dictionary_block : modifiers DICTIONARY SYMBOL '{' struct_list '}' ';'"""
311    p[0] = self.BuildNamed('Dictionary', p, 3, ListFromConcat(p[1], p[5]))
312
313  def p_dictionary_errorA(self, p):
314    """dictionary_block : modifiers DICTIONARY error ';'"""
315    p[0] = []
316
317  def p_dictionary_errorB(self, p):
318    """dictionary_block : modifiers DICTIONARY error '{' struct_list '}' ';'"""
319    p[0] = []
320
321#
322# Callback
323#
324# A callback is essentially a single function declaration (outside of an
325# Interface).
326#
327  def p_callback_decl(self, p):
328    """callback_decl : modifiers CALLBACK SYMBOL '=' SYMBOL param_list ';'"""
329    children = ListFromConcat(p[1], p[6])
330    p[0] = self.BuildNamed('Callback', p, 3, children)
331
332
333#
334# Inline
335#
336# Inline blocks define option code to be emitted based on language tag,
337# in the form of:
338# #inline <LANGUAGE>
339# <CODE>
340# #endinl
341#
342  def p_inline(self, p):
343    """inline : modifiers INLINE"""
344    words = p[2].split()
345    name = self.BuildAttribute('NAME', words[1])
346    lines = p[2].split('\n')
347    value = self.BuildAttribute('VALUE', '\n'.join(lines[1:-1]) + '\n')
348    children = ListFromConcat(name, value, p[1])
349    p[0] = self.BuildProduction('Inline', p, 2, children)
350    if self.parse_debug: DumpReduction('inline', p)
351
352# Extended Attributes
353#
354# Extended Attributes denote properties which will be applied to a node in the
355# AST.  A list of extended attributes are denoted by a brackets '[' ... ']'
356# enclosing a comma separated list of extended attributes in the form of:
357#
358#  Name
359#  Name=HEX | INT | OCT | FLOAT
360#  Name="STRING"
361#  Name=Function(arg ...)
362#  TODO(noelallen) -Not currently supported:
363#  ** Name(arg ...) ...
364#  ** Name=Scope::Value
365#
366# Extended Attributes are returned as a list or None.
367
368  def p_ext_attr_block(self, p):
369    """ext_attr_block : '[' ext_attr_list ']'
370                  | """
371    if len(p) > 1:
372      p[0] = p[2]
373      if self.parse_debug: DumpReduction('ext_attr_block', p)
374    else:
375      if self.parse_debug: DumpReduction('no ext_attr_block', p)
376
377  def p_ext_attr_list(self, p):
378    """ext_attr_list : SYMBOL '=' SYMBOL ext_attr_cont
379                     | SYMBOL '=' value ext_attr_cont
380                     | SYMBOL '=' SYMBOL param_list ext_attr_cont
381                     | SYMBOL ext_attr_cont"""
382    # If there are 4 tokens plus a return slot, this must be in the form
383    # SYMBOL = SYMBOL|value ext_attr_cont
384    if len(p) == 5:
385      p[0] = ListFromConcat(self.BuildAttribute(p[1], p[3]), p[4])
386    # If there are 5 tokens plus a return slot, this must be in the form
387    # SYMBOL = SYMBOL (param_list) ext_attr_cont
388    elif len(p) == 6:
389      member = self.BuildNamed('Member', p, 3, [p[4]])
390      p[0] = ListFromConcat(self.BuildAttribute(p[1], member), p[5])
391    # Otherwise, this must be: SYMBOL ext_attr_cont
392    else:
393      p[0] = ListFromConcat(self.BuildAttribute(p[1], 'True'), p[2])
394    if self.parse_debug: DumpReduction('ext_attribute_list', p)
395
396  def p_ext_attr_list_values(self, p):
397    """ext_attr_list : SYMBOL '=' '(' values ')' ext_attr_cont
398                     | SYMBOL '=' '(' symbols ')' ext_attr_cont"""
399    p[0] = ListFromConcat(self.BuildAttribute(p[1], p[4]), p[6])
400
401  def p_values(self, p):
402    """values : value values_cont"""
403    p[0] = ListFromConcat(p[1], p[2])
404
405  def p_symbols(self, p):
406    """symbols : SYMBOL symbols_cont"""
407    p[0] = ListFromConcat(p[1], p[2])
408
409  def p_symbols_cont(self, p):
410    """symbols_cont : ',' SYMBOL symbols_cont
411                    | """
412    if len(p) > 1: p[0] = ListFromConcat(p[2], p[3])
413
414  def p_values_cont(self, p):
415    """values_cont : ',' value values_cont
416                   | """
417    if len(p) > 1: p[0] = ListFromConcat(p[2], p[3])
418
419  def p_ext_attr_cont(self, p):
420    """ext_attr_cont : ',' ext_attr_list
421                     |"""
422    if len(p) > 1: p[0] = p[2]
423    if self.parse_debug: DumpReduction('ext_attribute_cont', p)
424
425  def p_ext_attr_func(self, p):
426    """ext_attr_list : SYMBOL '(' attr_arg_list ')' ext_attr_cont"""
427    p[0] = ListFromConcat(self.BuildAttribute(p[1] + '()', p[3]), p[5])
428    if self.parse_debug: DumpReduction('attr_arg_func', p)
429
430  def p_ext_attr_arg_list(self, p):
431    """attr_arg_list : SYMBOL attr_arg_cont
432                     | value attr_arg_cont"""
433    p[0] = ListFromConcat(p[1], p[2])
434
435  def p_attr_arg_cont(self, p):
436    """attr_arg_cont : ',' attr_arg_list
437                     | """
438    if self.parse_debug: DumpReduction('attr_arg_cont', p)
439    if len(p) > 1: p[0] = p[2]
440
441  def p_attr_arg_error(self, p):
442    """attr_arg_cont : error attr_arg_cont"""
443    p[0] = p[2]
444    if self.parse_debug: DumpReduction('attr_arg_error', p)
445
446
447#
448# Describe
449#
450# A describe block is defined at the top level.  It provides a mechanism for
451# attributing a group of ext_attr to a describe_list.  Members of the
452# describe list are language specific 'Type' declarations
453#
454  def p_describe_block(self, p):
455    """describe_block : modifiers DESCRIBE '{' describe_list '}' ';'"""
456    children = ListFromConcat(p[1], p[4])
457    p[0] = self.BuildProduction('Describe', p, 2, children)
458    if self.parse_debug: DumpReduction('describe_block', p)
459
460  # Recover from describe error and continue parsing at the next top match.
461  def p_describe_error(self, p):
462    """describe_list : error describe_list"""
463    p[0] = []
464
465  def p_describe_list(self, p):
466    """describe_list : modifiers SYMBOL ';' describe_list
467                     | modifiers ENUM ';' describe_list
468                     | modifiers STRUCT ';' describe_list
469                     | modifiers TYPEDEF ';' describe_list
470                     | """
471    if len(p) > 1:
472      Type = self.BuildNamed('Type', p, 2, p[1])
473      p[0] = ListFromConcat(Type, p[4])
474
475#
476# Constant Values (integer, value)
477#
478# Constant values can be found at various levels.  A Constant value is returns
479# as the string value after validated against a FLOAT, HEX, INT, OCT or
480# STRING pattern as appropriate.
481#
482  def p_value(self, p):
483    """value : FLOAT
484             | HEX
485             | INT
486             | OCT
487             | STRING"""
488    p[0] = p[1]
489    if self.parse_debug: DumpReduction('value', p)
490
491  def p_value_lshift(self, p):
492    """value : integer LSHIFT INT"""
493    p[0] = "%s << %s" % (p[1], p[3])
494    if self.parse_debug: DumpReduction('value', p)
495
496# Integers are numbers which may not be floats used in cases like array sizes.
497  def p_integer(self, p):
498    """integer : HEX
499               | INT
500               | OCT"""
501    p[0] = p[1]
502    if self.parse_debug: DumpReduction('integer', p)
503
504#
505# Expression
506#
507# A simple arithmetic expression.
508#
509  precedence = (
510    ('left','|','&','^'),
511    ('left','LSHIFT','RSHIFT'),
512    ('left','+','-'),
513    ('left','*','/'),
514    ('right','UMINUS','~'),
515    )
516
517  def p_expression_binop(self, p):
518    """expression : expression LSHIFT expression
519                  | expression RSHIFT expression
520                  | expression '|' expression
521                  | expression '&' expression
522                  | expression '^' expression
523                  | expression '+' expression
524                  | expression '-' expression
525                  | expression '*' expression
526                  | expression '/' expression"""
527    p[0] = "%s %s %s" % (str(p[1]), str(p[2]), str(p[3]))
528    if self.parse_debug: DumpReduction('expression_binop', p)
529
530  def p_expression_unop(self, p):
531    """expression : '-' expression %prec UMINUS
532                  | '~' expression %prec '~'"""
533    p[0] = "%s%s" % (str(p[1]), str(p[2]))
534    if self.parse_debug: DumpReduction('expression_unop', p)
535
536  def p_expression_term(self, p):
537    "expression : '(' expression ')'"
538    p[0] = "%s%s%s" % (str(p[1]), str(p[2]), str(p[3]))
539    if self.parse_debug: DumpReduction('expression_term', p)
540
541  def p_expression_symbol(self, p):
542    "expression : SYMBOL"
543    p[0] = p[1]
544    if self.parse_debug: DumpReduction('expression_symbol', p)
545
546  def p_expression_integer(self, p):
547    "expression : integer"
548    p[0] = p[1]
549    if self.parse_debug: DumpReduction('expression_integer', p)
550
551#
552# Array List
553#
554# Defined a list of array sizes (if any).
555#
556  def p_arrays(self, p):
557    """arrays : '[' ']' arrays
558              | '[' integer ']' arrays
559              | """
560    # If there are 3 tokens plus a return slot it is an unsized array
561    if len(p) == 4:
562      array = self.BuildProduction('Array', p, 1)
563      p[0] = ListFromConcat(array, p[3])
564    # If there are 4 tokens plus a return slot it is a fixed array
565    elif len(p) == 5:
566      count = self.BuildAttribute('FIXED', p[2])
567      array = self.BuildProduction('Array', p, 2, [count])
568      p[0] = ListFromConcat(array, p[4])
569    # If there is only a return slot, do not fill it for this terminator.
570    elif len(p) == 1: return
571    if self.parse_debug: DumpReduction('arrays', p)
572
573
574# An identifier is a legal value for a parameter or attribute name. Lots of
575# existing IDL files use "callback" as a parameter/attribute name, so we allow
576# a SYMBOL or the CALLBACK keyword.
577  def p_identifier(self, p):
578    """identifier : SYMBOL
579                  | CALLBACK"""
580    p[0] = p[1]
581    # Save the line number of the underlying token (otherwise it gets
582    # discarded), since we use it in the productions with an identifier in
583    # them.
584    p.set_lineno(0, p.lineno(1))
585
586#
587# Parameter List
588#
589# A parameter list is a collection of arguments which are passed to a
590# function.
591#
592  def p_param_list(self, p):
593    """param_list : '(' param_item param_cont ')'
594                  | '(' ')' """
595    if len(p) > 3:
596      args = ListFromConcat(p[2], p[3])
597    else:
598      args = []
599    p[0] = self.BuildProduction('Callspec', p, 1, args)
600    if self.parse_debug: DumpReduction('param_list', p)
601
602  def p_param_item(self, p):
603    """param_item : modifiers optional SYMBOL arrays identifier"""
604    typeref = self.BuildAttribute('TYPEREF', p[3])
605    children = ListFromConcat(p[1], p[2], typeref, p[4])
606    p[0] = self.BuildNamed('Param', p, 5, children)
607    if self.parse_debug: DumpReduction('param_item', p)
608
609  def p_optional(self, p):
610    """optional : OPTIONAL
611                | """
612    if len(p) == 2:
613      p[0] = self.BuildAttribute('OPTIONAL', True)
614
615
616  def p_param_cont(self, p):
617    """param_cont : ',' param_item param_cont
618                  | """
619    if len(p) > 1:
620      p[0] = ListFromConcat(p[2], p[3])
621      if self.parse_debug: DumpReduction('param_cont', p)
622
623  def p_param_error(self, p):
624    """param_cont : error param_cont"""
625    p[0] = p[2]
626
627
628#
629# Typedef
630#
631# A typedef creates a new referencable type.  The typedef can specify an array
632# definition as well as a function declaration.
633#
634  def p_typedef_data(self, p):
635    """typedef_decl : modifiers TYPEDEF SYMBOL SYMBOL ';' """
636    typeref = self.BuildAttribute('TYPEREF', p[3])
637    children = ListFromConcat(p[1], typeref)
638    p[0] = self.BuildNamed('Typedef', p, 4, children)
639    if self.parse_debug: DumpReduction('typedef_data', p)
640
641  def p_typedef_array(self, p):
642    """typedef_decl : modifiers TYPEDEF SYMBOL arrays SYMBOL ';' """
643    typeref = self.BuildAttribute('TYPEREF', p[3])
644    children = ListFromConcat(p[1], typeref, p[4])
645    p[0] = self.BuildNamed('Typedef', p, 5, children)
646    if self.parse_debug: DumpReduction('typedef_array', p)
647
648  def p_typedef_func(self, p):
649    """typedef_decl : modifiers TYPEDEF SYMBOL SYMBOL param_list ';' """
650    typeref = self.BuildAttribute('TYPEREF', p[3])
651    children = ListFromConcat(p[1], typeref, p[5])
652    p[0] = self.BuildNamed('Typedef', p, 4, children)
653    if self.parse_debug: DumpReduction('typedef_func', p)
654
655#
656# Enumeration
657#
658# An enumeration is a set of named integer constants.  An enumeration
659# is valid type which can be referenced in other definitions.
660#
661  def p_enum_block(self, p):
662    """enum_block : modifiers ENUM SYMBOL '{' enum_list '}' ';'"""
663    p[0] = self.BuildNamed('Enum', p, 3, ListFromConcat(p[1], p[5]))
664    if self.parse_debug: DumpReduction('enum_block', p)
665
666  # Recover from enum error and continue parsing at the next top match.
667  def p_enum_errorA(self, p):
668    """enum_block : modifiers ENUM error '{' enum_list '}' ';'"""
669    p[0] = []
670
671  def p_enum_errorB(self, p):
672    """enum_block : modifiers ENUM error ';'"""
673    p[0] = []
674
675  def p_enum_list(self, p):
676    """enum_list : modifiers SYMBOL '=' expression enum_cont
677                 | modifiers SYMBOL enum_cont"""
678    if len(p) > 4:
679      val  = self.BuildAttribute('VALUE', p[4])
680      enum = self.BuildNamed('EnumItem', p, 2, ListFromConcat(val, p[1]))
681      p[0] = ListFromConcat(enum, p[5])
682    else:
683      enum = self.BuildNamed('EnumItem', p, 2, p[1])
684      p[0] = ListFromConcat(enum, p[3])
685    if self.parse_debug: DumpReduction('enum_list', p)
686
687  def p_enum_cont(self, p):
688    """enum_cont : ',' enum_list
689                 |"""
690    if len(p) > 1: p[0] = p[2]
691    if self.parse_debug: DumpReduction('enum_cont', p)
692
693  def p_enum_cont_error(self, p):
694    """enum_cont : error enum_cont"""
695    p[0] = p[2]
696    if self.parse_debug: DumpReduction('enum_error', p)
697
698
699#
700# Label
701#
702# A label is a special kind of enumeration which allows us to go from a
703# set of labels
704#
705  def p_label_block(self, p):
706    """label_block : modifiers LABEL SYMBOL '{' label_list '}' ';'"""
707    p[0] = self.BuildNamed('Label', p, 3, ListFromConcat(p[1], p[5]))
708    if self.parse_debug: DumpReduction('label_block', p)
709
710  def p_label_list(self, p):
711    """label_list : modifiers SYMBOL '=' FLOAT label_cont"""
712    val  = self.BuildAttribute('VALUE', p[4])
713    label = self.BuildNamed('LabelItem', p, 2, ListFromConcat(val, p[1]))
714    p[0] = ListFromConcat(label, p[5])
715    if self.parse_debug: DumpReduction('label_list', p)
716
717  def p_label_cont(self, p):
718    """label_cont : ',' label_list
719                 |"""
720    if len(p) > 1: p[0] = p[2]
721    if self.parse_debug: DumpReduction('label_cont', p)
722
723  def p_label_cont_error(self, p):
724    """label_cont : error label_cont"""
725    p[0] = p[2]
726    if self.parse_debug: DumpReduction('label_error', p)
727
728
729#
730# Members
731#
732# A member attribute or function of a struct or interface.
733#
734  def p_member_attribute(self, p):
735    """member_attribute : modifiers SYMBOL arrays questionmark identifier"""
736    typeref = self.BuildAttribute('TYPEREF', p[2])
737    children = ListFromConcat(p[1], typeref, p[3], p[4])
738    p[0] = self.BuildNamed('Member', p, 5, children)
739    if self.parse_debug: DumpReduction('attribute', p)
740
741  def p_member_function(self, p):
742    """member_function : modifiers static SYMBOL arrays SYMBOL param_list"""
743    typeref = self.BuildAttribute('TYPEREF', p[3])
744    children = ListFromConcat(p[1], p[2], typeref, p[4], p[6])
745    p[0] = self.BuildNamed('Member', p, 5, children)
746    if self.parse_debug: DumpReduction('function', p)
747
748  def p_static(self, p):
749    """static : STATIC
750              | """
751    if len(p) == 2:
752      p[0] = self.BuildAttribute('STATIC', True)
753
754  def p_questionmark(self, p):
755    """questionmark : '?'
756                    | """
757    if len(p) == 2:
758      p[0] = self.BuildAttribute('OPTIONAL', True)
759
760#
761# Interface
762#
763# An interface is a named collection of functions.
764#
765  def p_interface_block(self, p):
766    """interface_block : modifiers INTERFACE SYMBOL '{' interface_list '}' ';'"""
767    p[0] = self.BuildNamed('Interface', p, 3, ListFromConcat(p[1], p[5]))
768    if self.parse_debug: DumpReduction('interface_block', p)
769
770  def p_interface_error(self, p):
771    """interface_block : modifiers INTERFACE error '{' interface_list '}' ';'"""
772    p[0] = []
773
774  def p_interface_list(self, p):
775    """interface_list : member_function ';' interface_list
776                      | """
777    if len(p) > 1 :
778      p[0] = ListFromConcat(p[1], p[3])
779      if self.parse_debug: DumpReduction('interface_list', p)
780
781
782#
783# Struct
784#
785# A struct is a named collection of members which in turn reference other
786# types.  The struct is a referencable type.
787#
788  def p_struct_block(self, p):
789    """struct_block : modifiers STRUCT SYMBOL '{' struct_list '}' ';'"""
790    children = ListFromConcat(p[1], p[5])
791    p[0] = self.BuildNamed('Struct', p, 3, children)
792    if self.parse_debug: DumpReduction('struct_block', p)
793
794  # Recover from struct error and continue parsing at the next top match.
795  def p_struct_error(self, p):
796    """enum_block : modifiers STRUCT error '{' struct_list '}' ';'"""
797    p[0] = []
798
799  def p_struct_list(self, p):
800    """struct_list : member_attribute ';' struct_list
801                   | member_function ';' struct_list
802                   |"""
803    if len(p) > 1: p[0] = ListFromConcat(p[1], p[3])
804
805
806#
807# Parser Errors
808#
809# p_error is called whenever the parser can not find a pattern match for
810# a set of items from the current state.  The p_error function defined here
811# is triggered logging an error, and parsing recover happens as the
812# p_<type>_error functions defined above are called.  This allows the parser
813# to continue so as to capture more than one error per file.
814#
815  def p_error(self, t):
816    filename = self.lexobj.filename
817    self.parse_errors += 1
818    if t:
819      lineno = t.lineno
820      pos = t.lexpos
821      prev = self.yaccobj.symstack[-1]
822      if type(prev) == lex.LexToken:
823        msg = "Unexpected %s after %s." % (
824            TokenTypeName(t), TokenTypeName(prev))
825      else:
826        msg = "Unexpected %s." % (t.value)
827    else:
828      lineno = self.last.lineno
829      pos = self.last.lexpos
830      msg = "Unexpected end of file after %s." % TokenTypeName(self.last)
831      self.yaccobj.restart()
832
833    # Attempt to remap the error to a friendlier form
834    if msg in ERROR_REMAP:
835      msg = ERROR_REMAP[msg]
836
837    # Log the error
838    ErrOut.LogLine(filename, lineno, pos, msg)
839
840  def Warn(self, node, msg):
841    WarnOut.LogLine(node.filename, node.lineno, node.pos, msg)
842    self.parse_warnings += 1
843
844  def __init__(self):
845    IDLLexer.__init__(self)
846    self.yaccobj = yacc.yacc(module=self, tabmodule=None, debug=False,
847                             optimize=0, write_tables=0)
848
849    self.build_debug = GetOption('build_debug')
850    self.parse_debug = GetOption('parse_debug')
851    self.token_debug = GetOption('token_debug')
852    self.verbose = GetOption('verbose')
853    self.parse_errors = 0
854
855#
856# Tokenizer
857#
858# The token function returns the next token provided by IDLLexer for matching
859# against the leaf paterns.
860#
861  def token(self):
862    tok = self.lexobj.token()
863    if tok:
864      self.last = tok
865      if self.token_debug:
866        InfoOut.Log("TOKEN %s(%s)" % (tok.type, tok.value))
867    return tok
868
869#
870# BuildProduction
871#
872# Production is the set of items sent to a grammar rule resulting in a new
873# item being returned.
874#
875# p - Is the Yacc production object containing the stack of items
876# index - Index into the production of the name for the item being produced.
877# cls - The type of item being producted
878# childlist - The children of the new item
879  def BuildProduction(self, cls, p, index, childlist=None):
880    if not childlist: childlist = []
881    filename = self.lexobj.filename
882    lineno = p.lineno(index)
883    pos = p.lexpos(index)
884    out = IDLNode(cls, filename, lineno, pos, childlist)
885    if self.build_debug:
886      InfoOut.Log("Building %s" % out)
887    return out
888
889  def BuildNamed(self, cls, p, index, childlist=None):
890    if not childlist: childlist = []
891    childlist.append(self.BuildAttribute('NAME', p[index]))
892    return self.BuildProduction(cls, p, index, childlist)
893
894  def BuildComment(self, cls, p, index):
895    name = p[index]
896
897    # Remove comment markers
898    lines = []
899    if name[:2] == '//':
900      # For C++ style, remove any leading whitespace and the '//' marker from
901      # each line.
902      form = 'cc'
903      for line in name.split('\n'):
904        start = line.find('//')
905        lines.append(line[start+2:])
906    else:
907      # For C style, remove ending '*/''
908      form = 'c'
909      for line in name[:-2].split('\n'):
910        # Remove characters until start marker for this line '*' if found
911        # otherwise it should be blank.
912        offs = line.find('*')
913        if offs >= 0:
914          line = line[offs + 1:].rstrip()
915        else:
916          line = ''
917        lines.append(line)
918    name = '\n'.join(lines)
919
920    childlist = [self.BuildAttribute('NAME', name),
921                 self.BuildAttribute('FORM', form)]
922    return self.BuildProduction(cls, p, index, childlist)
923
924#
925# BuildAttribute
926#
927# An ExtendedAttribute is a special production that results in a property
928# which is applied to the adjacent item.  Attributes have no children and
929# instead represent key/value pairs.
930#
931  def BuildAttribute(self, key, val):
932    return IDLAttribute(key, val)
933
934
935#
936# ParseData
937#
938# Attempts to parse the current data loaded in the lexer.
939#
940  def ParseData(self, data, filename='<Internal>'):
941    self.SetData(filename, data)
942    try:
943      self.parse_errors = 0
944      self.parse_warnings = 0
945      return self.yaccobj.parse(lexer=self)
946
947    except lex.LexError as le:
948      ErrOut.Log(str(le))
949      return []
950
951#
952# ParseFile
953#
954# Loads a new file into the lexer and attemps to parse it.
955#
956  def ParseFile(self, filename):
957    date = time.ctime(os.path.getmtime(filename))
958    data = open(filename).read()
959    if self.verbose:
960      InfoOut.Log("Parsing %s" % filename)
961    try:
962      out = self.ParseData(data, filename)
963
964      # If we have a src root specified, remove it from the path
965      srcroot = GetOption('srcroot')
966      if srcroot and filename.find(srcroot) == 0:
967        filename = filename[len(srcroot) + 1:]
968      filenode = IDLFile(filename, out, self.parse_errors + self.lex_errors)
969      filenode.SetProperty('DATETIME', date)
970      return filenode
971
972    except Exception as e:
973      ErrOut.LogLine(filename, self.last.lineno, self.last.lexpos,
974                     'Internal parsing error - %s.' % str(e))
975      raise
976
977
978
979#
980# Flatten Tree
981#
982# Flattens the tree of IDLNodes for use in testing.
983#
984def FlattenTree(node):
985  add_self = False
986  out = []
987  for child in node.GetChildren():
988    if child.IsA('Comment'):
989      add_self = True
990    else:
991      out.extend(FlattenTree(child))
992
993  if add_self:
994    out = [str(node)] + out
995  return out
996
997
998def TestErrors(filename, filenode):
999  nodelist = filenode.GetChildren()
1000
1001  lexer = IDLLexer()
1002  data = open(filename).read()
1003  lexer.SetData(filename, data)
1004
1005  pass_comments = []
1006  fail_comments = []
1007  while True:
1008    tok = lexer.lexobj.token()
1009    if tok == None: break
1010    if tok.type == 'COMMENT':
1011      args = tok.value[3:-3].split()
1012      if args[0] == 'OK':
1013        pass_comments.append((tok.lineno, ' '.join(args[1:])))
1014      else:
1015        if args[0] == 'FAIL':
1016          fail_comments.append((tok.lineno, ' '.join(args[1:])))
1017  obj_list = []
1018  for node in nodelist:
1019    obj_list.extend(FlattenTree(node))
1020
1021  errors = 0
1022
1023  #
1024  # Check for expected successes
1025  #
1026  obj_cnt = len(obj_list)
1027  pass_cnt = len(pass_comments)
1028  if obj_cnt != pass_cnt:
1029    InfoOut.Log("Mismatched pass (%d) vs. nodes built (%d)."
1030        % (pass_cnt, obj_cnt))
1031    InfoOut.Log("PASS: %s" % [x[1] for x in pass_comments])
1032    InfoOut.Log("OBJS: %s" % obj_list)
1033    errors += 1
1034    if pass_cnt > obj_cnt: pass_cnt = obj_cnt
1035
1036  for i in range(pass_cnt):
1037    line, comment = pass_comments[i]
1038    if obj_list[i] != comment:
1039      ErrOut.LogLine(filename, line, None, "OBJ %s : EXPECTED %s\n" %
1040                     (obj_list[i], comment))
1041      errors += 1
1042
1043  #
1044  # Check for expected errors
1045  #
1046  err_list = ErrOut.DrainLog()
1047  err_cnt = len(err_list)
1048  fail_cnt = len(fail_comments)
1049  if err_cnt != fail_cnt:
1050    InfoOut.Log("Mismatched fail (%d) vs. errors seen (%d)."
1051        % (fail_cnt, err_cnt))
1052    InfoOut.Log("FAIL: %s" % [x[1] for x in fail_comments])
1053    InfoOut.Log("ERRS: %s" % err_list)
1054    errors += 1
1055    if fail_cnt > err_cnt:  fail_cnt = err_cnt
1056
1057  for i in range(fail_cnt):
1058    line, comment = fail_comments[i]
1059    err = err_list[i].strip()
1060
1061    if err_list[i] != comment:
1062      ErrOut.Log("%s(%d) Error\n\tERROR : %s\n\tEXPECT: %s" % (
1063        filename, line, err_list[i], comment))
1064      errors += 1
1065
1066  # Clear the error list for the next run
1067  err_list = []
1068  return errors
1069
1070
1071def TestFile(parser, filename):
1072  # Capture errors instead of reporting them so we can compare them
1073  # with the expected errors.
1074  ErrOut.SetConsole(False)
1075  ErrOut.SetCapture(True)
1076
1077  filenode = parser.ParseFile(filename)
1078
1079  # Renable output
1080  ErrOut.SetConsole(True)
1081  ErrOut.SetCapture(False)
1082
1083  # Compare captured errors
1084  return TestErrors(filename, filenode)
1085
1086
1087def TestErrorFiles(filter):
1088  idldir = os.path.split(sys.argv[0])[0]
1089  idldir = os.path.join(idldir, 'test_parser', '*.idl')
1090  filenames = glob.glob(idldir)
1091  parser = IDLParser()
1092  total_errs = 0
1093  for filename in filenames:
1094    if filter and filename not in filter: continue
1095    errs = TestFile(parser, filename)
1096    if errs:
1097      ErrOut.Log("%s test failed with %d error(s)." % (filename, errs))
1098      total_errs += errs
1099
1100  if total_errs:
1101    ErrOut.Log("Failed parsing test.")
1102  else:
1103    InfoOut.Log("Passed parsing test.")
1104  return total_errs
1105
1106
1107def TestNamespaceFiles(filter):
1108  idldir = os.path.split(sys.argv[0])[0]
1109  idldir = os.path.join(idldir, 'test_namespace', '*.idl')
1110  filenames = glob.glob(idldir)
1111  testnames = []
1112
1113  for filename in filenames:
1114    if filter and filename not in filter: continue
1115    testnames.append(filename)
1116
1117  # If we have no files to test, then skip this test
1118  if not testnames:
1119    InfoOut.Log('No files to test for namespace.')
1120    return 0
1121
1122  InfoOut.SetConsole(False)
1123  ast = ParseFiles(testnames)
1124  InfoOut.SetConsole(True)
1125
1126  errs = ast.GetProperty('ERRORS')
1127  if errs:
1128    ErrOut.Log("Failed namespace test.")
1129  else:
1130    InfoOut.Log("Passed namespace test.")
1131  return errs
1132
1133
1134
1135def FindVersionError(releases, node):
1136  err_cnt = 0
1137  if node.IsA('Interface', 'Struct'):
1138    comment_list = []
1139    comment = node.GetOneOf('Comment')
1140    if comment and comment.GetName()[:4] == 'REL:':
1141      comment_list = comment.GetName()[5:].strip().split(' ')
1142
1143    first_list = [node.first_release[rel] for rel in releases]
1144    first_list = sorted(set(first_list))
1145    if first_list != comment_list:
1146      node.Error("Mismatch in releases: %s vs %s." % (
1147          comment_list, first_list))
1148      err_cnt += 1
1149
1150  for child in node.GetChildren():
1151    err_cnt += FindVersionError(releases, child)
1152  return err_cnt
1153
1154
1155def TestVersionFiles(filter):
1156  idldir = os.path.split(sys.argv[0])[0]
1157  idldir = os.path.join(idldir, 'test_version', '*.idl')
1158  filenames = glob.glob(idldir)
1159  testnames = []
1160
1161  for filename in filenames:
1162    if filter and filename not in filter: continue
1163    testnames.append(filename)
1164
1165  # If we have no files to test, then skip this test
1166  if not testnames:
1167    InfoOut.Log('No files to test for version.')
1168    return 0
1169
1170  ast = ParseFiles(testnames)
1171  errs = FindVersionError(ast.releases, ast)
1172  errs += ast.errors
1173
1174  if errs:
1175    ErrOut.Log("Failed version test.")
1176  else:
1177    InfoOut.Log("Passed version test.")
1178  return errs
1179
1180
1181default_dirs = ['.', 'trusted', 'dev', 'private', 'extensions',
1182                'extensions/dev']
1183def ParseFiles(filenames):
1184  parser = IDLParser()
1185  filenodes = []
1186
1187  if not filenames:
1188    filenames = []
1189    srcroot = GetOption('srcroot')
1190    dirs = default_dirs
1191    if GetOption('include_private'):
1192      dirs += ['private']
1193    for dirname in dirs:
1194      srcdir = os.path.join(srcroot, dirname, '*.idl')
1195      srcdir = os.path.normpath(srcdir)
1196      filenames += sorted(glob.glob(srcdir))
1197
1198  if not filenames:
1199    ErrOut.Log('No sources provided.')
1200
1201  for filename in filenames:
1202    filenode = parser.ParseFile(filename)
1203    filenodes.append(filenode)
1204
1205  ast = IDLAst(filenodes)
1206  if GetOption('dump_tree'): ast.Dump(0)
1207
1208  Lint(ast)
1209  return ast
1210
1211
1212def Main(args):
1213  filenames = ParseOptions(args)
1214
1215  # If testing...
1216  if GetOption('test'):
1217    errs = TestErrorFiles(filenames)
1218    errs = TestNamespaceFiles(filenames)
1219    errs = TestVersionFiles(filenames)
1220    if errs:
1221      ErrOut.Log("Parser failed with %d errors." % errs)
1222      return  -1
1223    return 0
1224
1225  # Otherwise, build the AST
1226  ast = ParseFiles(filenames)
1227  errs = ast.GetProperty('ERRORS')
1228  if errs:
1229    ErrOut.Log('Found %d error(s).' % errs);
1230  InfoOut.Log("%d files processed." % len(filenames))
1231  return errs
1232
1233
1234if __name__ == '__main__':
1235  sys.exit(Main(sys.argv[1:]))
1236
1237