• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python
2# Copyright (c) 2013 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 grammar can be found at:
15#   http://heycam.github.io/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
25#
26# Disable check for line length and Member as Function due to how grammar rules
27# are defined with PLY
28#
29# pylint: disable=R0201
30# pylint: disable=C0301
31
32import os.path
33import sys
34import time
35
36from idl_lexer import IDLLexer
37from idl_node import IDLAttribute, IDLNode
38
39#
40# Try to load the ply module, if not, then assume it is in the third_party
41# directory.
42#
43try:
44  # Disable lint check which fails to find the ply module.
45  # pylint: disable=F0401
46  from ply import lex
47  from ply import yacc
48except ImportError:
49  module_path, module_name = os.path.split(__file__)
50  third_party = os.path.join(module_path, os.par, os.par, 'third_party')
51  sys.path.append(third_party)
52  # pylint: disable=F0401
53  from ply import lex
54  from ply import yacc
55
56#
57# ERROR_REMAP
58#
59# Maps the standard error formula into a more friendly error message.
60#
61ERROR_REMAP = {
62  'Unexpected ")" after "(".' : 'Empty argument list.',
63  'Unexpected ")" after ",".' : 'Missing argument.',
64  'Unexpected "}" after ",".' : 'Trailing comma in block.',
65  'Unexpected "}" after "{".' : 'Unexpected empty block.',
66  'Unexpected comment after "}".' : 'Unexpected trailing comment.',
67  'Unexpected "{" after keyword "enum".' : 'Enum missing name.',
68  'Unexpected "{" after keyword "struct".' : 'Struct missing name.',
69  'Unexpected "{" after keyword "interface".' : 'Interface missing name.',
70}
71
72
73def Boolean(val):
74  """Convert to strict boolean type."""
75  if val:
76    return True
77  return False
78
79
80def ListFromConcat(*items):
81  """Generate list by concatenating inputs"""
82  itemsout = []
83  for item in items:
84    if item is None:
85      continue
86    if type(item) is not type([]):
87      itemsout.append(item)
88    else:
89      itemsout.extend(item)
90
91  return itemsout
92
93def ExpandProduction(p):
94  if type(p) == list:
95    return '[' + ', '.join([ExpandProduction(x) for x in p]) + ']'
96  if type(p) == IDLNode:
97    return 'Node:' + str(p)
98  if type(p) == IDLAttribute:
99    return 'Attr:' + str(p)
100  if type(p) == str:
101    return 'str:' + p
102  return '%s:%s' % (p.__class__.__name__, str(p))
103
104# TokenTypeName
105#
106# Generate a string which has the type and value of the token.
107#
108def TokenTypeName(t):
109  if t.type == 'SYMBOL':
110    return 'symbol %s' % t.value
111  if t.type in ['HEX', 'INT', 'OCT', 'FLOAT']:
112    return 'value %s' % t.value
113  if t.type == 'string' :
114    return 'string "%s"' % t.value
115  if t.type == 'COMMENT' :
116    return 'comment'
117  if t.type == t.value:
118    return '"%s"' % t.value
119  if t.type == ',':
120    return 'Comma'
121  if t.type == 'identifier':
122    return 'identifier "%s"' % t.value
123  return 'keyword "%s"' % t.value
124
125
126#
127# IDL Parser
128#
129# The Parser inherits the from the Lexer to provide PLY with the tokenizing
130# definitions.  Parsing patterns are encoded as functions where p_<name> is
131# is called any time a patern matching the function documentation is found.
132# Paterns are expressed in the form of:
133# """ <new item> : <item> ....
134#                | <item> ...."""
135#
136# Where new item is the result of a match against one or more sets of items
137# separated by the "|".
138#
139# The function is called with an object 'p' where p[0] is the output object
140# and p[n] is the set of inputs for positive values of 'n'.  Len(p) can be
141# used to distinguish between multiple item sets in the pattern.
142#
143# For more details on parsing refer to the PLY documentation at
144#    http://www.dabeaz.com/ply/
145#
146# The parser is based on the WebIDL standard.  See:
147#    http://heycam.github.io/webidl/#idl-grammar
148#
149# The various productions are annotated so that the WHOLE number greater than
150# zero in the comment denotes the matching WebIDL grammar definition.
151#
152# Productions with a fractional component in the comment denote additions to
153# the WebIDL spec, such as comments.
154#
155
156
157class IDLParser(object):
158#
159# We force all input files to start with two comments.  The first comment is a
160# Copyright notice followed by a file comment and finally by file level
161# productions.
162#
163  # [0] Insert a TOP definition for Copyright and Comments
164  def p_Top(self, p):
165    """Top : COMMENT COMMENT Definitions"""
166    Copyright = self.BuildComment('Copyright', p, 1)
167    Filedoc = self.BuildComment('Comment', p, 2)
168    p[0] = ListFromConcat(Copyright, Filedoc, p[3])
169
170  # [0.1] Add support for Multiple COMMENTS
171  def p_Comments(self, p):
172    """Comments : CommentsRest"""
173    if len(p) > 1:
174      p[0] = p[1]
175
176  # [0.2] Produce a COMMENT and aggregate sibling comments
177  def p_CommentsRest(self, p):
178    """CommentsRest : COMMENT CommentsRest
179                    | """
180    if len(p) > 1:
181      p[0] = ListFromConcat(self.BuildComment('Comment', p, 1), p[2])
182
183
184#
185#The parser is based on the WebIDL standard.  See:
186# http://heycam.github.io/webidl/#idl-grammar
187#
188  # [1]
189  def p_Definitions(self, p):
190    """Definitions : ExtendedAttributeList Definition Definitions
191           | """
192    if len(p) > 1:
193      p[2].AddChildren(p[1])
194      p[0] = ListFromConcat(p[2], p[3])
195
196  # [2]
197  def p_Definition(self, p):
198    """Definition : CallbackOrInterface
199                  | Partial
200                  | Dictionary
201                  | Exception
202                  | Enum
203                  | Typedef
204                  | ImplementsStatement"""
205    p[0] = p[1]
206
207  # [2.1] Error recovery for definition
208  def p_DefinitionError(self, p):
209    """Definition : error ';'"""
210    p[0] = self.BuildError(p, 'Definition')
211
212  # [3]
213  def p_CallbackOrInterface(self, p):
214    """CallbackOrInterface : CALLBACK CallbackRestOrInterface
215                           | Interface"""
216    if len(p) > 2:
217      p[0] = p[2]
218    else:
219      p[0] = p[1]
220
221  # [4]
222  def p_CallbackRestOrInterface(self, p):
223    """CallbackRestOrInterface : CallbackRest
224                               | Interface"""
225    p[0] = p[1]
226
227  # [5]
228  def p_Interface(self, p):
229    """Interface : INTERFACE identifier Inheritance '{' InterfaceMembers '}' ';'"""
230    p[0] = self.BuildNamed('Interface', p, 2, ListFromConcat(p[3], p[5]))
231
232  # [6]
233  def p_Partial(self, p):
234    """Partial : PARTIAL PartialDefinition"""
235    p[2].AddChildren(self.BuildTrue('Partial'))
236    p[0] = p[2]
237
238  # [6.1] Error recovery for Partial
239  def p_PartialError(self, p):
240    """Partial : PARTIAL error"""
241    p[0] = self.BuildError(p, 'Partial')
242
243  # [7]
244  def p_PartialDefinition(self, p):
245    """PartialDefinition : PartialDictionary
246                         | PartialInterface"""
247    p[0] = p[1]
248
249  # [8]
250  def p_PartialInterface(self, p):
251    """PartialInterface : INTERFACE identifier '{' InterfaceMembers '}' ';'"""
252    p[0] = self.BuildNamed('Interface', p, 2, p[4])
253
254  # [9]
255  def p_InterfaceMembers(self, p):
256    """InterfaceMembers : ExtendedAttributeList InterfaceMember InterfaceMembers
257                        |"""
258    if len(p) > 1:
259      p[2].AddChildren(p[1])
260      p[0] = ListFromConcat(p[2], p[3])
261
262  # [10]
263  def p_InterfaceMember(self, p):
264    """InterfaceMember : Const
265                       | AttributeOrOperationOrIterator"""
266    p[0] = p[1]
267
268  # [11]
269  def p_Dictionary(self, p):
270    """Dictionary : DICTIONARY identifier Inheritance '{' DictionaryMembers '}' ';'"""
271    p[0] = self.BuildNamed('Dictionary', p, 2, ListFromConcat(p[3], p[5]))
272
273  # [11.1] Error recovery for regular Dictionary
274  def p_DictionaryError(self, p):
275    """Dictionary : DICTIONARY error ';'"""
276    p[0] = self.BuildError(p, 'Dictionary')
277
278  # [12]
279  def p_DictionaryMembers(self, p):
280    """DictionaryMembers : ExtendedAttributeList DictionaryMember DictionaryMembers
281                         |"""
282    if len(p) > 1:
283      p[2].AddChildren(p[1])
284      p[0] = ListFromConcat(p[2], p[3])
285
286  # [13]
287  def p_DictionaryMember(self, p):
288    """DictionaryMember : Type identifier Default ';'"""
289    p[0] = self.BuildNamed('Key', p, 2, ListFromConcat(p[1], p[3]))
290
291  # [14]
292  def p_PartialDictionary(self, p):
293    """PartialDictionary : DICTIONARY identifier '{' DictionaryMembers '}' ';'"""
294    partial = self.BuildTrue('Partial')
295    p[0] = self.BuildNamed('Dictionary', p, 2, ListFromConcat(p[4], partial))
296
297  # [14.1] Error recovery for Partial Dictionary
298  def p_PartialDictionaryError(self, p):
299    """PartialDictionary : DICTIONARY error ';'"""
300    p[0] = self.BuildError(p, 'PartialDictionary')
301
302  # [15]
303  def p_Default(self, p):
304    """Default : '=' DefaultValue
305               |"""
306    if len(p) > 1:
307      p[0] = self.BuildProduction('Default', p, 2, p[2])
308
309  # [16]
310  def p_DefaultValue(self, p):
311    """DefaultValue : ConstValue
312                    | string"""
313    if type(p[1]) == str:
314      p[0] = ListFromConcat(self.BuildAttribute('TYPE', 'DOMString'),
315                            self.BuildAttribute('NAME', p[1]))
316    else:
317      p[0] = p[1]
318
319  # [17]
320  def p_Exception(self, p):
321    """Exception : EXCEPTION identifier Inheritance '{' ExceptionMembers '}' ';'"""
322    p[0] = self.BuildNamed('Exception', p, 2, ListFromConcat(p[3], p[5]))
323
324  # [18]
325  def p_ExceptionMembers(self, p):
326    """ExceptionMembers : ExtendedAttributeList ExceptionMember ExceptionMembers
327                        |"""
328    if len(p) > 1:
329      p[2].AddChildren(p[1])
330      p[0] = ListFromConcat(p[2], p[3])
331
332  # [18.1] Error recovery for ExceptionMembers
333  def p_ExceptionMembersError(self, p):
334    """ExceptionMembers : error"""
335    p[0] = self.BuildError(p, 'ExceptionMembers')
336
337  # [19]
338  def p_Inheritance(self, p):
339    """Inheritance : ':' identifier
340                   |"""
341    if len(p) > 1:
342      p[0] = self.BuildNamed('Inherit', p, 2)
343
344  # [20]
345  def p_Enum(self, p):
346    """Enum : ENUM identifier '{' EnumValueList '}' ';'"""
347    p[0] = self.BuildNamed('Enum', p, 2, p[4])
348
349  # [20.1] Error recovery for Enums
350  def p_EnumError(self, p):
351    """Enum : ENUM error ';'"""
352    p[0] = self.BuildError(p, 'Enum')
353
354  # [21]
355  def p_EnumValueList(self, p):
356    """EnumValueList : ExtendedAttributeList string EnumValueListComma"""
357    enum = self.BuildNamed('EnumItem', p, 2, p[1])
358    p[0] = ListFromConcat(enum, p[3])
359
360  # [22]
361  def p_EnumValueListComma(self, p):
362    """EnumValueListComma : ',' EnumValueListString
363                          |"""
364    if len(p) > 1:
365      p[0] = p[2]
366
367  # [23]
368  def p_EnumValueListString(self, p):
369    """EnumValueListString : ExtendedAttributeList string EnumValueListComma
370                           |"""
371    if len(p) > 1:
372      enum = self.BuildNamed('EnumItem', p, 2, p[1])
373      p[0] = ListFromConcat(enum, p[3])
374
375  # [24]
376  def p_CallbackRest(self, p):
377    """CallbackRest : identifier '=' ReturnType '(' ArgumentList ')' ';'"""
378    arguments = self.BuildProduction('Arguments', p, 4, p[5])
379    p[0] = self.BuildNamed('Callback', p, 1, ListFromConcat(p[3], arguments))
380
381  # [25]
382  def p_Typedef(self, p):
383    """Typedef : TYPEDEF ExtendedAttributeListNoComments Type identifier ';'"""
384    p[0] = self.BuildNamed('Typedef', p, 4, ListFromConcat(p[2], p[3]))
385
386  # [25.1] Error recovery for Typedefs
387  def p_TypedefError(self, p):
388    """Typedef : TYPEDEF error ';'"""
389    p[0] = self.BuildError(p, 'Typedef')
390
391  # [26]
392  def p_ImplementsStatement(self, p):
393    """ImplementsStatement : identifier IMPLEMENTS identifier ';'"""
394    name = self.BuildAttribute('REFERENCE', p[3])
395    p[0] = self.BuildNamed('Implements', p, 1, name)
396
397  # [27]
398  def p_Const(self,  p):
399    """Const : CONST ConstType identifier '=' ConstValue ';'"""
400    value = self.BuildProduction('Value', p, 5, p[5])
401    p[0] = self.BuildNamed('Const', p, 3, ListFromConcat(p[2], value))
402
403  # [28]
404  def p_ConstValue(self, p):
405    """ConstValue : BooleanLiteral
406                  | FloatLiteral
407                  | integer
408                  | null"""
409    if type(p[1]) == str:
410      p[0] = ListFromConcat(self.BuildAttribute('TYPE', 'integer'),
411                            self.BuildAttribute('NAME', p[1]))
412    else:
413      p[0] = p[1]
414
415  # [28.1] Add definition for NULL
416  def p_null(self, p):
417    """null : NULL"""
418    p[0] = ListFromConcat(self.BuildAttribute('TYPE', 'NULL'),
419                          self.BuildAttribute('NAME', 'NULL'))
420
421  # [29]
422  def p_BooleanLiteral(self, p):
423    """BooleanLiteral : TRUE
424                      | FALSE"""
425    value = self.BuildAttribute('VALUE', Boolean(p[1] == 'true'))
426    p[0] = ListFromConcat(self.BuildAttribute('TYPE', 'boolean'), value)
427
428  # [30]
429  def p_FloatLiteral(self, p):
430    """FloatLiteral : float
431                    | '-' INFINITY
432                    | INFINITY
433                    | NAN """
434    if len(p) > 2:
435      val = '-Infinity'
436    else:
437      val = p[1]
438    p[0] = ListFromConcat(self.BuildAttribute('TYPE', 'float'),
439                          self.BuildAttribute('VALUE', val))
440
441  # [31] Removed unsupported: Serializer
442  def p_AttributeOrOperationOrIterator(self, p):
443    """AttributeOrOperationOrIterator : Stringifier
444                                      | StaticMember
445                                      | Attribute
446                                      | OperationOrIterator"""
447    p[0] = p[1]
448
449  # [32-37] NOT IMPLEMENTED (Serializer)
450
451  # [38]
452  def p_Stringifier(self, p):
453    """Stringifier : STRINGIFIER StringifierRest"""
454    p[0] = self.BuildProduction('Stringifier', p, 1, p[2])
455
456  # [39]
457  def p_StringifierRest(self, p):
458    """StringifierRest : AttributeRest
459                       | ReturnType OperationRest
460                       | ';'"""
461    if len(p) == 3:
462      p[2].AddChildren(p[1])
463      p[0] = p[2]
464    elif p[1] != ';':
465      p[0] = p[1]
466
467  # [40]
468  def p_StaticMember(self, p):
469    """StaticMember : STATIC StaticMemberRest"""
470    p[2].AddChildren(self.BuildTrue('STATIC'))
471    p[0] = p[2]
472
473  # [41]
474  def p_StaticMemberRest(self, p):
475    """StaticMemberRest : AttributeRest
476                        | ReturnType OperationRest"""
477    if len(p) == 2:
478      p[0] = p[1]
479    else:
480      p[2].AddChildren(p[1])
481      p[0] = p[2]
482
483  # [42]
484  def p_Attribute(self, p):
485    """Attribute : Inherit AttributeRest"""
486    p[2].AddChildren(ListFromConcat(p[1]))
487    p[0] = p[2]
488
489  # [43]
490  def p_AttributeRest(self, p):
491    """AttributeRest : ReadOnly ATTRIBUTE Type identifier ';'"""
492    p[0] = self.BuildNamed('Attribute', p, 4,
493                           ListFromConcat(p[1], p[3]))
494
495  # [44]
496  def p_Inherit(self, p):
497    """Inherit : INHERIT
498               |"""
499    if len(p) > 1:
500      p[0] = self.BuildTrue('INHERIT')
501
502  # [45]
503  def p_ReadOnly(self, p):
504    """ReadOnly : READONLY
505                |"""
506    if len(p) > 1:
507      p[0] = self.BuildTrue('READONLY')
508
509  # [46]
510  def p_OperationOrIterator(self, p):
511    """OperationOrIterator : ReturnType OperationOrIteratorRest
512                           | SpecialOperation"""
513    if len(p) == 3:
514      p[2].AddChildren(p[1])
515      p[0] = p[2]
516    else:
517      p[0] = p[1]
518
519  # [47]
520  def p_SpecialOperation(self, p):
521    """SpecialOperation : Special Specials ReturnType OperationRest"""
522    p[4].AddChildren(ListFromConcat(p[1], p[2], p[3]))
523    p[0] = p[4]
524
525  # [48]
526  def p_Specials(self, p):
527    """Specials : Special Specials
528                | """
529    if len(p) > 1:
530      p[0] = ListFromConcat(p[1], p[2])
531
532  # [49]
533  def p_Special(self, p):
534    """Special : GETTER
535               | SETTER
536               | CREATOR
537               | DELETER
538               | LEGACYCALLER"""
539    p[0] = self.BuildTrue(p[1].upper())
540
541  # [50] Removed unsupported: IteratorRest
542  def p_OperationOrIteratorRest(self, p):
543    """OperationOrIteratorRest : OperationRest"""
544    p[0] = p[1]
545
546  # [51-53] NOT IMPLEMENTED (IteratorRest)
547
548  # [54]
549  def p_OperationRest(self, p):
550    """OperationRest : OptionalIdentifier '(' ArgumentList ')' ';'"""
551    arguments = self.BuildProduction('Arguments', p, 2, p[3])
552    p[0] = self.BuildNamed('Operation', p, 1, arguments)
553
554  # [55]
555  def p_OptionalIdentifier(self, p):
556    """OptionalIdentifier : identifier
557                          |"""
558    if len(p) > 1:
559      p[0] = p[1]
560    else:
561      p[0] = '_unnamed_'
562
563  # [56]
564  def p_ArgumentList(self, p):
565    """ArgumentList : Argument Arguments
566                    |"""
567    if len(p) > 1:
568      p[0] = ListFromConcat(p[1], p[2])
569
570  # [56.1] ArgumentList error recovery
571  def p_ArgumentListError(self, p):
572    """ArgumentList : error """
573    p[0] = self.BuildError(p, 'ArgumentList')
574
575  # [57]
576  def p_Arguments(self, p):
577    """Arguments : ',' Argument Arguments
578                 |"""
579    if len(p) > 1:
580      p[0] = ListFromConcat(p[2], p[3])
581
582  # [58]
583  def p_Argument(self, p):
584    """Argument : ExtendedAttributeList OptionalOrRequiredArgument"""
585    p[2].AddChildren(p[1])
586    p[0] = p[2]
587
588  # [59]
589  def p_OptionalOrRequiredArgument(self, p):
590    """OptionalOrRequiredArgument : OPTIONAL Type ArgumentName Default
591                                  | Type Ellipsis ArgumentName"""
592    if len(p) > 4:
593      arg = self.BuildNamed('Argument', p, 3, ListFromConcat(p[2], p[4]))
594      arg.AddChildren(self.BuildTrue('OPTIONAL'))
595    else:
596      arg = self.BuildNamed('Argument', p, 3, ListFromConcat(p[1], p[2]))
597    p[0] = arg
598
599  # [60]
600  def p_ArgumentName(self, p):
601    """ArgumentName : ArgumentNameKeyword
602                    | identifier"""
603    p[0] = p[1]
604
605  # [61]
606  def p_Ellipsis(self, p):
607    """Ellipsis : ELLIPSIS
608                |"""
609    if len(p) > 1:
610      p[0] = self.BuildNamed('Argument', p, 1)
611      p[0].AddChildren(self.BuildTrue('ELLIPSIS'))
612
613  # [62]
614  def p_ExceptionMember(self, p):
615    """ExceptionMember : Const
616                       | ExceptionField"""
617    p[0] = p[1]
618
619  # [63]
620  def p_ExceptionField(self, p):
621    """ExceptionField : Type identifier ';'"""
622    p[0] = self.BuildNamed('ExceptionField', p, 2, p[1])
623
624  # [63.1] Error recovery for ExceptionMembers
625  def p_ExceptionFieldError(self, p):
626    """ExceptionField : error"""
627    p[0] = self.BuildError(p, 'ExceptionField')
628
629  # [64] No comment version for mid statement attributes.
630  def p_ExtendedAttributeListNoComments(self, p):
631    """ExtendedAttributeListNoComments : '[' ExtendedAttribute ExtendedAttributes ']'
632                                       | """
633    if len(p) > 2:
634      items = ListFromConcat(p[2], p[3])
635      p[0] = self.BuildProduction('ExtAttributes', p, 1, items)
636
637  # [64.1] Add optional comment field for start of statements.
638  def p_ExtendedAttributeList(self, p):
639    """ExtendedAttributeList : Comments '[' ExtendedAttribute ExtendedAttributes ']'
640                             | Comments """
641    if len(p) > 2:
642      items = ListFromConcat(p[3], p[4])
643      attribs = self.BuildProduction('ExtAttributes', p, 2, items)
644      p[0] = ListFromConcat(p[1], attribs)
645    else:
646      p[0] = p[1]
647
648  # [65]
649  def p_ExtendedAttributes(self, p):
650    """ExtendedAttributes : ',' ExtendedAttribute ExtendedAttributes
651                          |"""
652    if len(p) > 1:
653      p[0] = ListFromConcat(p[2], p[3])
654
655  # We only support:
656  #    [ identifier ]
657  #    [ identifier ( ArgumentList ) ]
658  #    [ identifier = identifier ]
659  #    [ identifier = ( IdentifierList ) ]
660  #    [ identifier = identifier ( ArgumentList ) ]
661  # [66] map directly to [91-93, 95]
662  # [67-69, 71] are unsupported
663  def p_ExtendedAttribute(self, p):
664    """ExtendedAttribute : ExtendedAttributeNoArgs
665                         | ExtendedAttributeArgList
666                         | ExtendedAttributeIdent
667                         | ExtendedAttributeIdentList
668                         | ExtendedAttributeNamedArgList"""
669    p[0] = p[1]
670
671  # [70]
672  def p_ArgumentNameKeyword(self, p):
673    """ArgumentNameKeyword : ATTRIBUTE
674                           | CALLBACK
675                           | CONST
676                           | CREATOR
677                           | DELETER
678                           | DICTIONARY
679                           | ENUM
680                           | EXCEPTION
681                           | GETTER
682                           | IMPLEMENTS
683                           | INHERIT
684                           | LEGACYCALLER
685                           | PARTIAL
686                           | SERIALIZER
687                           | SETTER
688                           | STATIC
689                           | STRINGIFIER
690                           | TYPEDEF
691                           | UNRESTRICTED"""
692    p[0] = p[1]
693
694  # [72]
695  def p_Type(self, p):
696    """Type : SingleType
697            | UnionType TypeSuffix"""
698    if len(p) == 2:
699      p[0] = self.BuildProduction('Type', p, 1, p[1])
700    else:
701      p[0] = self.BuildProduction('Type', p, 1, ListFromConcat(p[1], p[2]))
702
703  # [73]
704  def p_SingleType(self, p):
705    """SingleType : NonAnyType
706                  | ANY TypeSuffixStartingWithArray"""
707    if len(p) == 2:
708      p[0] = p[1]
709    else:
710      p[0] = ListFromConcat(self.BuildProduction('Any', p, 1), p[2])
711
712  # [74]
713  def p_UnionType(self, p):
714    """UnionType : '(' UnionMemberType OR UnionMemberType UnionMemberTypes ')'"""
715
716  # [75]
717  def p_UnionMemberType(self, p):
718    """UnionMemberType : NonAnyType
719                       | UnionType TypeSuffix
720                       | ANY '[' ']' TypeSuffix"""
721  # [76]
722  def p_UnionMemberTypes(self, p):
723    """UnionMemberTypes : OR UnionMemberType UnionMemberTypes
724                        |"""
725
726  # [77] Moved BYTESTRING, DOMSTRING, OBJECT, DATE, REGEXP to PrimitiveType
727  # Moving all built-in types into PrimitiveType makes it easier to
728  # differentiate between them and 'identifier', since p[1] would be a string in
729  # both cases.
730  def p_NonAnyType(self, p):
731    """NonAnyType : PrimitiveType TypeSuffix
732                  | identifier TypeSuffix
733                  | SEQUENCE '<' Type '>' Null"""
734    if len(p) == 3:
735      if type(p[1]) == str:
736        typeref = self.BuildNamed('Typeref', p, 1)
737      else:
738        typeref = p[1]
739      p[0] = ListFromConcat(typeref, p[2])
740
741    if len(p) == 6:
742      p[0] = self.BuildProduction('Sequence', p, 1, ListFromConcat(p[3], p[5]))
743
744
745  # [78]
746  def p_ConstType(self,  p):
747    """ConstType : PrimitiveType Null
748                 | identifier Null"""
749    if type(p[1]) == str:
750      p[0] = self.BuildNamed('Typeref', p, 1, p[2])
751    else:
752      p[1].AddChildren(p[2])
753      p[0] = p[1]
754
755
756  # [79] Added BYTESTRING, DOMSTRING, OBJECT, DATE, REGEXP
757  def p_PrimitiveType(self, p):
758    """PrimitiveType : UnsignedIntegerType
759                     | UnrestrictedFloatType
760                     | BOOLEAN
761                     | BYTE
762                     | OCTET
763                     | BYTESTRING
764                     | DOMSTRING
765                     | OBJECT
766                     | DATE
767                     | REGEXP"""
768    if type(p[1]) == str:
769      p[0] = self.BuildNamed('PrimitiveType', p, 1)
770    else:
771      p[0] = p[1]
772
773
774  # [80]
775  def p_UnrestrictedFloatType(self, p):
776    """UnrestrictedFloatType : UNRESTRICTED FloatType
777                             | FloatType"""
778    if len(p) == 2:
779      typeref = self.BuildNamed('PrimitiveType', p, 1)
780    else:
781      typeref = self.BuildNamed('PrimitiveType', p, 2)
782      typeref.AddChildren(self.BuildTrue('UNRESTRICTED'))
783    p[0] = typeref
784
785
786  # [81]
787  def p_FloatType(self, p):
788    """FloatType : FLOAT
789                 | DOUBLE"""
790    p[0] = p[1]
791
792  # [82]
793  def p_UnsignedIntegerType(self, p):
794    """UnsignedIntegerType : UNSIGNED IntegerType
795                           | IntegerType"""
796    if len(p) == 2:
797      p[0] = p[1]
798    else:
799      p[0] = 'unsigned ' + p[2]
800
801  # [83]
802  def p_IntegerType(self, p):
803    """IntegerType : SHORT
804                   | LONG OptionalLong"""
805    if len(p) == 2:
806      p[0] = p[1]
807    else:
808      p[0] = p[1] + p[2]
809
810  # [84]
811  def p_OptionalLong(self, p):
812    """OptionalLong : LONG
813                    | """
814    if len(p) > 1:
815      p[0] = ' ' + p[1]
816    else:
817      p[0] = ''
818
819
820  # [85] Add support for sized array
821  def p_TypeSuffix(self, p):
822    """TypeSuffix : '[' integer ']' TypeSuffix
823                  | '[' ']' TypeSuffix
824                  | '?' TypeSuffixStartingWithArray
825                  | """
826    if len(p) == 5:
827      p[0] = self.BuildNamed('Array', p, 2, p[4])
828
829    if len(p) == 4:
830      p[0] = self.BuildProduction('Array', p, 1, p[3])
831
832    if len(p) == 3:
833      p[0] = ListFromConcat(self.BuildTrue('NULLABLE'), p[2])
834
835
836  # [86]
837  def p_TypeSuffixStartingWithArray(self, p):
838    """TypeSuffixStartingWithArray : '[' ']' TypeSuffix
839                                   | """
840    if len(p) > 1:
841      p[0] = self.BuildProduction('Array', p, 0, p[3])
842
843  # [87]
844  def p_Null(self, p):
845    """Null : '?'
846            |"""
847    if len(p) > 1:
848      p[0] = self.BuildTrue('NULLABLE')
849
850  # [88]
851  def p_ReturnType(self, p):
852    """ReturnType : Type
853                  | VOID"""
854    if p[1] == 'void':
855      p[0] = self.BuildProduction('Type', p, 1)
856      p[0].AddChildren(self.BuildNamed('PrimitiveType', p, 1))
857    else:
858      p[0] = p[1]
859
860  # [89]
861  def p_IdentifierList(self, p):
862    """IdentifierList : identifier Identifiers"""
863    p[0] = ListFromConcat(p[1], p[2])
864
865  # [90]
866  def p_Identifiers(self, p):
867    """Identifiers : ',' identifier Identifiers
868                   |"""
869    if len(p) > 1:
870      p[0] = ListFromConcat(p[2], p[3])
871
872  # [91]
873  def p_ExtendedAttributeNoArgs(self, p):
874    """ExtendedAttributeNoArgs : identifier"""
875    p[0] = self.BuildNamed('ExtAttribute', p, 1)
876
877  # [92]
878  def p_ExtendedAttributeArgList(self, p):
879    """ExtendedAttributeArgList : identifier '(' ArgumentList ')'"""
880    arguments = self.BuildProduction('Arguments', p, 2, p[3])
881    p[0] = self.BuildNamed('ExtAttribute', p, 1, arguments)
882
883  # [93]
884  def p_ExtendedAttributeIdent(self, p):
885    """ExtendedAttributeIdent : identifier '=' identifier"""
886    value = self.BuildAttribute('VALUE', p[3])
887    p[0] = self.BuildNamed('ExtAttribute', p, 1, value)
888
889  # [94]
890  def p_ExtendedAttributeIdentList(self, p):
891    """ExtendedAttributeIdentList : identifier '=' '(' IdentifierList ')'"""
892    value = self.BuildAttribute('VALUE', p[4])
893    p[0] = self.BuildNamed('ExtAttribute', p, 1, value)
894
895  # [95]
896  def p_ExtendedAttributeNamedArgList(self, p):
897    """ExtendedAttributeNamedArgList : identifier '=' identifier '(' ArgumentList ')'"""
898    args = self.BuildProduction('Arguments', p, 4, p[5])
899    value = self.BuildNamed('Call', p, 3, args)
900    p[0] = self.BuildNamed('ExtAttribute', p, 1, value)
901
902  # [96] NOT IMPLEMENTED (ExtendedAttributeTypePair)
903
904#
905# Parser Errors
906#
907# p_error is called whenever the parser can not find a pattern match for
908# a set of items from the current state.  The p_error function defined here
909# is triggered logging an error, and parsing recovery happens as the
910# p_<type>_error functions defined above are called.  This allows the parser
911# to continue so as to capture more than one error per file.
912#
913  def p_error(self, t):
914    if t:
915      lineno = t.lineno
916      pos = t.lexpos
917      prev = self.yaccobj.symstack[-1]
918      if type(prev) == lex.LexToken:
919        msg = "Unexpected %s after %s." % (
920            TokenTypeName(t), TokenTypeName(prev))
921      else:
922        msg = "Unexpected %s." % (t.value)
923    else:
924      last = self.LastToken()
925      lineno = last.lineno
926      pos = last.lexpos
927      msg = "Unexpected end of file after %s." % TokenTypeName(last)
928      self.yaccobj.restart()
929
930    # Attempt to remap the error to a friendlier form
931    if msg in ERROR_REMAP:
932      msg = ERROR_REMAP[msg]
933
934    self._last_error_msg = msg
935    self._last_error_lineno = lineno
936    self._last_error_pos = pos
937
938  def Warn(self, node, msg):
939    sys.stdout.write(node.GetLogLine(msg))
940    self.parse_warnings += 1
941
942  def LastToken(self):
943    return self.lexer.last
944
945  def __init__(self, lexer, verbose=False, debug=False, mute_error=False):
946    self.lexer = lexer
947    self.tokens = lexer.KnownTokens()
948    self.yaccobj = yacc.yacc(module=self, tabmodule=None, debug=debug,
949                             optimize=0, write_tables=0)
950    self.parse_debug = debug
951    self.verbose = verbose
952    self.mute_error = mute_error
953    self._parse_errors = 0
954    self._parse_warnings = 0
955    self._last_error_msg = None
956    self._last_error_lineno = 0
957    self._last_error_pos = 0
958
959
960#
961# BuildProduction
962#
963# Production is the set of items sent to a grammar rule resulting in a new
964# item being returned.
965#
966# p - Is the Yacc production object containing the stack of items
967# index - Index into the production of the name for the item being produced.
968# cls - The type of item being producted
969# childlist - The children of the new item
970  def BuildProduction(self, cls, p, index, childlist=None):
971    try:
972      if not childlist:
973        childlist = []
974
975      filename = self.lexer.Lexer().filename
976      lineno = p.lineno(index)
977      pos = p.lexpos(index)
978      out = IDLNode(cls, filename, lineno, pos, childlist)
979      return out
980    except:
981      print 'Exception while parsing:'
982      for num, item in enumerate(p):
983        print '  [%d] %s' % (num, ExpandProduction(item))
984      if self.LastToken():
985        print 'Last token: %s' % str(self.LastToken())
986      raise
987
988  def BuildNamed(self, cls, p, index, childlist=None):
989    childlist = ListFromConcat(childlist)
990    childlist.append(self.BuildAttribute('NAME', p[index]))
991    return self.BuildProduction(cls, p, index, childlist)
992
993  def BuildComment(self, cls, p, index):
994    name = p[index]
995
996    # Remove comment markers
997    lines = []
998    if name[:2] == '//':
999      # For C++ style, remove any leading whitespace and the '//' marker from
1000      # each line.
1001      form = 'cc'
1002      for line in name.split('\n'):
1003        start = line.find('//')
1004        lines.append(line[start+2:])
1005    else:
1006      # For C style, remove ending '*/''
1007      form = 'c'
1008      for line in name[:-2].split('\n'):
1009        # Remove characters until start marker for this line '*' if found
1010        # otherwise it should be blank.
1011        offs = line.find('*')
1012        if offs >= 0:
1013          line = line[offs + 1:].rstrip()
1014        else:
1015          line = ''
1016        lines.append(line)
1017    name = '\n'.join(lines)
1018    childlist = [self.BuildAttribute('NAME', name),
1019                 self.BuildAttribute('FORM', form)]
1020    return self.BuildProduction(cls, p, index, childlist)
1021
1022#
1023# BuildError
1024#
1025# Build and Errror node as part of the recovery process.
1026#
1027#
1028  def BuildError(self, p, prod):
1029    self._parse_errors += 1
1030    name = self.BuildAttribute('NAME', self._last_error_msg)
1031    line = self.BuildAttribute('LINE', self._last_error_lineno)
1032    pos = self.BuildAttribute('POS', self._last_error_pos)
1033    prod = self.BuildAttribute('PROD', prod)
1034
1035    node = self.BuildProduction('Error', p, 1,
1036                                ListFromConcat(name, line, pos, prod))
1037    if not self.mute_error:
1038      node.Error(self._last_error_msg)
1039
1040    return node
1041
1042#
1043# BuildAttribute
1044#
1045# An ExtendedAttribute is a special production that results in a property
1046# which is applied to the adjacent item.  Attributes have no children and
1047# instead represent key/value pairs.
1048#
1049  def BuildAttribute(self, key, val):
1050    return IDLAttribute(key, val)
1051
1052  def BuildFalse(self, key):
1053    return IDLAttribute(key, Boolean(False))
1054
1055  def BuildTrue(self, key):
1056    return IDLAttribute(key, Boolean(True))
1057
1058  def GetErrors(self):
1059    # Access lexer errors, despite being private
1060    # pylint: disable=W0212
1061    return self._parse_errors + self.lexer._lex_errors
1062
1063#
1064# ParseData
1065#
1066# Attempts to parse the current data loaded in the lexer.
1067#
1068  def ParseText(self, filename, data):
1069    self._parse_errors = 0
1070    self._parse_warnings = 0
1071    self._last_error_msg = None
1072    self._last_error_lineno = 0
1073    self._last_error_pos = 0
1074
1075    try:
1076      self.lexer.Tokenize(data, filename)
1077      nodes = self.yaccobj.parse(lexer=self.lexer) or []
1078      name = self.BuildAttribute('NAME', filename)
1079      return IDLNode('File', filename, 0, 0, nodes + [name])
1080
1081    except lex.LexError as lexError:
1082      sys.stderr.write('Error in token: %s\n' % str(lexError))
1083    return None
1084
1085
1086
1087def ParseFile(parser, filename):
1088  """Parse a file and return a File type of node."""
1089  with open(filename) as fileobject:
1090    try:
1091      out = parser.ParseText(filename, fileobject.read())
1092      out.SetProperty('DATETIME', time.ctime(os.path.getmtime(filename)))
1093      out.SetProperty('ERRORS', parser.GetErrors())
1094      return out
1095
1096    except Exception as e:
1097      last = parser.LastToken()
1098      sys.stderr.write('%s(%d) : Internal parsing error\n\t%s.\n' % (
1099                       filename, last.lineno, str(e)))
1100
1101
1102def main(argv):
1103  nodes = []
1104  parser = IDLParser(IDLLexer())
1105  errors = 0
1106  for filename in argv:
1107    filenode = ParseFile(parser, filename)
1108    if (filenode):
1109      errors += filenode.GetProperty('ERRORS')
1110      nodes.append(filenode)
1111
1112  ast = IDLNode('AST', '__AST__', 0, 0, nodes)
1113
1114  print '\n'.join(ast.Tree(accept_props=['PROD']))
1115  if errors:
1116    print '\nFound %d errors.\n' % errors
1117
1118  return errors
1119
1120
1121if __name__ == '__main__':
1122  sys.exit(main(sys.argv[1:]))
1123