Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(203)

Side by Side Diff: tools/idl_parser/idl_parser.py

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

Powered by Google App Engine
This is Rietveld 408576698