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# Scoped name is a name with an optional scope.
272#
273# Used for types and namespace names. eg. foo_bar.hello_world, or
274# foo_bar.hello_world.SomeType.
275#
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)
280
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)
286
287#
288# Type reference
289#
290#
291  def p_typeref(self, p):
292    """typeref : scoped_name"""
293    p[0] = p[1]
294    if self.parse_debug: DumpReduction('typeref', p)
295
296
297#
298# Comments
299#
300# Comments are optional list of C style comment objects.  Comments are returned
301# as a list or None.
302#
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)
312
313
314#
315# Namespace
316#
317# A namespace provides a named scope to an enclosed top_list.
318#
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)
323
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]
328
329
330#
331# Dictionary
332#
333# A dictionary is a named list of optional and required members.
334#
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]))
338
339  def p_dictionary_errorA(self, p):
340    """dictionary_block : modifiers DICTIONARY error ';'"""
341    p[0] = []
342
343  def p_dictionary_errorB(self, p):
344    """dictionary_block : modifiers DICTIONARY error '{' struct_list '}' ';'"""
345    p[0] = []
346
347#
348# Callback
349#
350# A callback is essentially a single function declaration (outside of an
351# Interface).
352#
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)
357
358
359#
360# Inline
361#
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
367#
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)
377
378# Extended Attributes
379#
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:
383#
384#  Name
385#  Name=HEX | INT | OCT | FLOAT
386#  Name="STRING"
387#  Name=Function(arg ...)
388#  TODO(noelallen) -Not currently supported:
389#  ** Name(arg ...) ...
390#  ** Name=Scope::Value
391#
392# Extended Attributes are returned as a list or None.
393
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)
402
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)
421
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])
426
427  def p_values(self, p):
428    """values : value values_cont"""
429    p[0] = ListFromConcat(p[1], p[2])
430
431  def p_symbols(self, p):
432    """symbols : SYMBOL symbols_cont"""
433    p[0] = ListFromConcat(p[1], p[2])
434
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])
439
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])
444
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)
450
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)
455
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])
460
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]
466
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)
471
472
473#
474# Describe
475#
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
479#
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)
485
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] = []
490
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])
500
501#
502# Constant Values (integer, value)
503#
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.
507#
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)
516
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)
521
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)
529
530#
531# Expression
532#
533# A simple arithmetic expression.
534#
535  precedence = (
536    ('left','|','&','^'),
537    ('left','LSHIFT','RSHIFT'),
538    ('left','+','-'),
539    ('left','*','/'),
540    ('right','UMINUS','~'),
541    )
542
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)
555
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)
561
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)
566
567  def p_expression_symbol(self, p):
568    """expression : SYMBOL"""
569    p[0] = p[1]
570    if self.parse_debug: DumpReduction('expression_symbol', p)
571
572  def p_expression_integer(self, p):
573    """expression : integer"""
574    p[0] = p[1]
575    if self.parse_debug: DumpReduction('expression_integer', p)
576
577#
578# Array List
579#
580# Defined a list of array sizes (if any).
581#
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)
598
599
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))
611
612
613#
614# Union
615#
616# A union allows multiple choices of types for a parameter or member.
617#
618
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)
624
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]
632
633#
634# Parameter List
635#
636# A parameter list is a collection of arguments which are passed to a
637# function.
638#
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)
648
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)
655
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)
662
663  def p_optional(self, p):
664    """optional : OPTIONAL
665                | """
666    if len(p) == 2:
667      p[0] = self.BuildAttribute('OPTIONAL', True)
668
669
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)
676
677  def p_param_error(self, p):
678    """param_cont : error param_cont"""
679    p[0] = p[2]
680
681
682#
683# Typedef
684#
685# A typedef creates a new referencable type.  The typedef can specify an array
686# definition as well as a function declaration.
687#
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)
694
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)
701
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)
708
709#
710# Enumeration
711#
712# An enumeration is a set of named integer constants.  An enumeration
713# is valid type which can be referenced in other definitions.
714#
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)
719
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] = []
724
725  def p_enum_errorB(self, p):
726    """enum_block : modifiers ENUM error ';'"""
727    p[0] = []
728
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)
740
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)
746
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)
751
752
753#
754# Label
755#
756# A label is a special kind of enumeration which allows us to go from a
757# set of labels
758#
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)
763
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)
770
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)
776
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)
781
782
783#
784# Members
785#
786# A member attribute or function of a struct or interface.
787#
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)
794
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)
801
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)
808
809  def p_static(self, p):
810    """static : STATIC
811              | """
812    if len(p) == 2:
813      p[0] = self.BuildAttribute('STATIC', True)
814
815  def p_questionmark(self, p):
816    """questionmark : '?'
817                    | """
818    if len(p) == 2:
819      p[0] = self.BuildAttribute('OPTIONAL', True)
820
821#
822# Interface
823#
824# An interface is a named collection of functions.
825#
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)
830
831  def p_interface_error(self, p):
832    """interface_block : modifiers INTERFACE error '{' interface_list '}' ';'"""
833    p[0] = []
834
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)
841
842
843#
844# Struct
845#
846# A struct is a named collection of members which in turn reference other
847# types.  The struct is a referencable type.
848#
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)
854
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] = []
859
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])
865
866
867#
868# Parser Errors
869#
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.
875#
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()
893
894    # Attempt to remap the error to a friendlier form
895    if msg in ERROR_REMAP:
896      msg = ERROR_REMAP[msg]
897
898    # Log the error
899    ErrOut.LogLine(filename, lineno, pos, msg)
900
901  def Warn(self, node, msg):
902    WarnOut.LogLine(node.filename, node.lineno, node.pos, msg)
903    self.parse_warnings += 1
904
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)
909
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
915
916#
917# Tokenizer
918#
919# The token function returns the next token provided by IDLLexer for matching
920# against the leaf paterns.
921#
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
929
930#
931# BuildProduction
932#
933# Production is the set of items sent to a grammar rule resulting in a new
934# item being returned.
935#
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
949
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)
954
955  def BuildComment(self, cls, p, index):
956    name = p[index]
957
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)
980
981    childlist = [self.BuildAttribute('NAME', name),
982                 self.BuildAttribute('FORM', form)]
983    return self.BuildProduction(cls, p, index, childlist)
984
985#
986# BuildAttribute
987#
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.
991#
992  def BuildAttribute(self, key, val):
993    return IDLAttribute(key, val)
994
995
996#
997# ParseData
998#
999# Attempts to parse the current data loaded in the lexer.
1000#
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)
1007
1008    except lex.LexError as le:
1009      ErrOut.Log(str(le))
1010      return []
1011
1012#
1013# ParseFile
1014#
1015# Loads a new file into the lexer and attemps to parse it.
1016#
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)
1024
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
1032
1033    except Exception as e:
1034      ErrOut.LogLine(filename, self.last.lineno, self.last.lexpos,
1035                     'Internal parsing error - %s.' % str(e))
1036      raise
1037
1038
1039
1040#
1041# Flatten Tree
1042#
1043# Flattens the tree of IDLNodes for use in testing.
1044#
1045def 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))
1053
1054  if add_self:
1055    out = [str(node)] + out
1056  return out
1057
1058
1059def TestErrors(filename, filenode):
1060  nodelist = filenode.GetChildren()
1061
1062  lexer = IDLLexer()
1063  data = open(filename).read()
1064  lexer.SetData(filename, data)
1065
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))
1081
1082  errors = 0
1083
1084  #
1085  # Check for expected successes
1086  #
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
1096
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
1103
1104  #
1105  # Check for expected errors
1106  #
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
1117
1118  for i in range(fail_cnt):
1119    line, comment = fail_comments[i]
1120    err = err_list[i].strip()
1121
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
1126
1127  # Clear the error list for the next run
1128  err_list = []
1129  return errors
1130
1131
1132def 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)
1137
1138  filenode = parser.ParseFile(filename)
1139
1140  # Renable output
1141  ErrOut.SetConsole(True)
1142  ErrOut.SetCapture(False)
1143
1144  # Compare captured errors
1145  return TestErrors(filename, filenode)
1146
1147
1148def 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
1160
1161  if total_errs:
1162    ErrOut.Log("Failed parsing test.")
1163  else:
1164    InfoOut.Log("Passed parsing test.")
1165  return total_errs
1166
1167
1168def 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 = []
1173
1174  for filename in filenames:
1175    if filter and filename not in filter: continue
1176    testnames.append(filename)
1177
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
1182
1183  InfoOut.SetConsole(False)
1184  ast = ParseFiles(testnames)
1185  InfoOut.SetConsole(True)
1186
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
1193
1194
1195
1196def 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(' ')
1203
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
1210
1211  for child in node.GetChildren():
1212    err_cnt += FindVersionError(releases, child)
1213  return err_cnt
1214
1215
1216def 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 = []
1221
1222  for filename in filenames:
1223    if filter and filename not in filter: continue
1224    testnames.append(filename)
1225
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
1230
1231  ast = ParseFiles(testnames)
1232  errs = FindVersionError(ast.releases, ast)
1233  errs += ast.errors
1234
1235  if errs:
1236    ErrOut.Log("Failed version test.")
1237  else:
1238    InfoOut.Log("Passed version test.")
1239  return errs
1240
1241
1242default_dirs = ['.', 'trusted', 'dev', 'private']
1243def ParseFiles(filenames):
1244  parser = IDLParser()
1245  filenodes = []
1246
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))
1257
1258  if not filenames:
1259    ErrOut.Log('No sources provided.')
1260
1261  for filename in filenames:
1262    filenode = parser.ParseFile(filename)
1263    filenodes.append(filenode)
1264
1265  ast = IDLAst(filenodes)
1266  if GetOption('dump_tree'): ast.Dump(0)
1267
1268  Lint(ast)
1269  return ast
1270
1271
1272def Main(args):
1273  filenames = ParseOptions(args)
1274
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
1284
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
1292
1293
1294if __name__ == '__main__':
1295  sys.exit(Main(sys.argv[1:]))
1296
1297