OLD | NEW |
---|---|
(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 | |
OLD | NEW |