OLD | NEW |
| (Empty) |
1 # Copyright (C) 2013 Google Inc. All rights reserved. | |
2 # | |
3 # Redistribution and use in source and binary forms, with or without | |
4 # modification, are permitted provided that the following conditions are | |
5 # met: | |
6 # | |
7 # * Redistributions of source code must retain the above copyright | |
8 # notice, this list of conditions and the following disclaimer. | |
9 # * Redistributions in binary form must reproduce the above | |
10 # copyright notice, this list of conditions and the following disclaimer | |
11 # in the documentation and/or other materials provided with the | |
12 # distribution. | |
13 # * Neither the name of Google Inc. nor the names of its | |
14 # contributors may be used to endorse or promote products derived from | |
15 # this software without specific prior written permission. | |
16 # | |
17 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
18 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
19 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
20 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
21 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
22 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
23 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
24 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
25 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
26 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
27 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
28 | |
29 """Parser for Blink IDL. | |
30 | |
31 The parser uses the PLY (Python Lex-Yacc) library to build a set of parsing | |
32 rules which understand the Blink dialect of Web IDL. | |
33 It derives from a standard Web IDL parser, overriding rules where Blink IDL | |
34 differs syntactically or semantically from the base parser, or where the base | |
35 parser diverges from the Web IDL standard. | |
36 | |
37 Web IDL: | |
38 http://www.w3.org/TR/WebIDL/ | |
39 Web IDL Grammar: | |
40 http://www.w3.org/TR/WebIDL/#idl-grammar | |
41 PLY: | |
42 http://www.dabeaz.com/ply/ | |
43 """ | |
44 | |
45 # Disable check for line length and Member as Function due to how grammar rules | |
46 # are defined with PLY | |
47 # | |
48 # pylint: disable=R0201 | |
49 # pylint: disable=C0301 | |
50 # | |
51 # Disable attribute validation, as lint can't import parent class to check | |
52 # pylint: disable=E1101 | |
53 | |
54 import os.path | |
55 import sys | |
56 | |
57 # PLY is in Chromium src/third_party/ply | |
58 module_path, module_name = os.path.split(__file__) | |
59 third_party = os.path.join(module_path, os.pardir, os.pardir, os.pardir, os.pard
ir, os.pardir) | |
60 # Insert at front to override system libraries, and after path[0] == script dir | |
61 sys.path.insert(1, third_party) | |
62 from ply import yacc | |
63 | |
64 # Base parser is in Chromium src/tools/idl_parser | |
65 tools_dir = os.path.join(module_path, os.pardir, os.pardir, os.pardir, os.pardir
, os.pardir, os.pardir, 'tools') | |
66 sys.path.append(tools_dir) | |
67 from idl_parser.idl_parser import IDLParser, ListFromConcat | |
68 from idl_parser.idl_parser import ParseFile as parse_file | |
69 | |
70 from blink_idl_lexer import BlinkIDLLexer | |
71 | |
72 | |
73 # Explicitly set starting symbol to rule defined only in base parser. | |
74 # BEWARE that the starting symbol should NOT be defined in both the base parser | |
75 # and the derived one, as otherwise which is used depends on which line number | |
76 # is lower, which is fragile. Instead, either use one in base parser or | |
77 # create a new symbol, so that this is unambiguous. | |
78 # FIXME: unfortunately, this doesn't work in PLY 3.4, so need to duplicate the | |
79 # rule below. | |
80 STARTING_SYMBOL = 'Definitions' | |
81 | |
82 # We ignore comments (and hence don't need 'Top') but base parser preserves them | |
83 # FIXME: Upstream: comments should be removed in base parser | |
84 REMOVED_RULES = ['Top', # [0] | |
85 'Comments', # [0.1] | |
86 'CommentsRest', # [0.2] | |
87 ] | |
88 | |
89 | |
90 class BlinkIDLParser(IDLParser): | |
91 # [1] | |
92 # FIXME: Need to duplicate rule for starting symbol here, with line number | |
93 # *lower* than in the base parser (idl_parser.py). | |
94 # This is a bug in PLY: it determines starting symbol by lowest line number. | |
95 # This can be overridden by the 'start' parameter, but as of PLY 3.4 this | |
96 # doesn't work correctly. | |
97 def p_Definitions(self, p): | |
98 """Definitions : ExtendedAttributeList Definition Definitions | |
99 | """ | |
100 if len(p) > 1: | |
101 p[2].AddChildren(p[1]) | |
102 p[0] = ListFromConcat(p[2], p[3]) | |
103 | |
104 # Below are grammar rules used by yacc, given by functions named p_<RULE>. | |
105 # * The docstring is the production rule in BNF (grammar). | |
106 # * The body is the yacc action (semantics). | |
107 # | |
108 # The PLY framework builds the actual low-level parser by introspecting this | |
109 # parser object, selecting all attributes named p_<RULE> as grammar rules. | |
110 # It extracts the docstrings and uses them as the production rules, building | |
111 # the table of a LALR parser, and uses the body of the functions as actions. | |
112 # | |
113 # Reference: | |
114 # http://www.dabeaz.com/ply/ply.html#ply_nn23 | |
115 # | |
116 # Review of yacc: | |
117 # Yacc parses a token stream, internally producing a Concrete Syntax Tree | |
118 # (CST), where each node corresponds to a production rule in the grammar. | |
119 # At each node, it runs an action, which is usually "produce a node in the | |
120 # Abstract Syntax Tree (AST)" or "ignore this node" (for nodes in the CST | |
121 # that aren't included in the AST, since only needed for parsing). | |
122 # | |
123 # The rules use pseudo-variables; in PLY syntax: | |
124 # p[0] is the left side: assign return value to p[0] instead of returning, | |
125 # p[1] ... p[n] are the right side: the values can be accessed, and they | |
126 # can be modified. | |
127 # (In yacc these are $$ and $1 ... $n.) | |
128 # | |
129 # The rules can look cryptic at first, but there are a few standard | |
130 # transforms from the CST to AST. With these in mind, the actions should | |
131 # be reasonably legible. | |
132 # | |
133 # * Ignore production | |
134 # Discard this branch. Primarily used when one alternative is empty. | |
135 # | |
136 # Sample code: | |
137 # if len(p) > 1: | |
138 # p[0] = ... | |
139 # # Note no assignment if len(p) == 1 | |
140 # | |
141 # * Eliminate singleton production | |
142 # Discard this node in the CST, pass the next level down up the tree. | |
143 # Used to ignore productions only necessary for parsing, but not needed | |
144 # in the AST. | |
145 # | |
146 # Sample code: | |
147 # p[0] = p[1] | |
148 # | |
149 # * Build node | |
150 # The key type of rule. In this parser, produces object of class IDLNode. | |
151 # There are several helper functions: | |
152 # * BuildProduction: actually builds an IDLNode, based on a production. | |
153 # * BuildAttribute: builds an IDLAttribute, which is a temporary | |
154 # object to hold a name-value pair, which is then | |
155 # set as a Property of the IDLNode when the IDLNode | |
156 # is built. | |
157 # * BuildNamed: Same as BuildProduction, and sets the 'NAME' property. | |
158 # * BuildTrue: BuildAttribute with value True, for flags. | |
159 # See base idl_parser.py for definitions and more examples of use. | |
160 # | |
161 # Sample code: | |
162 # # Build node of type NodeType, with value p[1], and children. | |
163 # p[0] = self.BuildProduction('NodeType', p, 1, children) | |
164 # | |
165 # # Build named node of type NodeType, with name and value p[1]. | |
166 # # (children optional) | |
167 # p[0] = self.BuildNamed('NodeType', p, 1) | |
168 # | |
169 # # Make a list | |
170 # # Used if one node has several children. | |
171 # children = ListFromConcat(p[2], p[3]) | |
172 # p[0] = self.BuildProduction('NodeType', p, 1, children) | |
173 # | |
174 # # Also used to collapse the right-associative tree | |
175 # # produced by parsing a list back into a single list. | |
176 # """Foos : Foo Foos | |
177 # |""" | |
178 # if len(p) > 1: | |
179 # p[0] = ListFromConcat(p[1], p[2]) | |
180 # | |
181 # # Add children. | |
182 # # Primarily used to add attributes, produced via BuildTrue. | |
183 # # p_StaticAttribute | |
184 # """StaticAttribute : STATIC Attribute""" | |
185 # p[2].AddChildren(self.BuildTrue('STATIC')) | |
186 # p[0] = p[2] | |
187 # | |
188 # Numbering scheme for the rules is: | |
189 # [1] for Web IDL spec (or additions in base parser) | |
190 # These should all be upstreamed to the base parser. | |
191 # [b1] for Blink IDL changes (overrides Web IDL) | |
192 # [b1.1] for Blink IDL additions, auxiliary rules for [b1] | |
193 # Numbers are as per Candidate Recommendation 19 April 2012: | |
194 # http://www.w3.org/TR/2012/CR-WebIDL-20120419/ | |
195 | |
196 # [3] Override action, since we distinguish callbacks | |
197 # FIXME: Upstream | |
198 def p_CallbackOrInterface(self, p): | |
199 """CallbackOrInterface : CALLBACK CallbackRestOrInterface | |
200 | Interface""" | |
201 if len(p) > 2: | |
202 p[2].AddChildren(self.BuildTrue('CALLBACK')) | |
203 p[0] = p[2] | |
204 else: | |
205 p[0] = p[1] | |
206 | |
207 # [b27] Add strings, more 'Literal' productions | |
208 # 'Literal's needed because integers and strings are both internally strings | |
209 def p_ConstValue(self, p): | |
210 """ConstValue : BooleanLiteral | |
211 | FloatLiteral | |
212 | IntegerLiteral | |
213 | StringLiteral | |
214 | null""" | |
215 # Standard is (no 'string', fewer 'Literal's): | |
216 # ConstValue : BooleanLiteral | |
217 # | FloatLiteral | |
218 # | integer | |
219 # | NULL | |
220 p[0] = p[1] | |
221 | |
222 # [b27.1] | |
223 def p_IntegerLiteral(self, p): | |
224 """IntegerLiteral : integer""" | |
225 p[0] = ListFromConcat(self.BuildAttribute('TYPE', 'integer'), | |
226 self.BuildAttribute('NAME', p[1])) | |
227 | |
228 # [b27.2] | |
229 def p_StringLiteral(self, p): | |
230 """StringLiteral : string""" | |
231 p[0] = ListFromConcat(self.BuildAttribute('TYPE', 'DOMString'), | |
232 self.BuildAttribute('NAME', p[1])) | |
233 | |
234 # [b30] Add StaticAttribute | |
235 def p_AttributeOrOperation(self, p): | |
236 """AttributeOrOperation : STRINGIFIER StringifierAttributeOrOperation | |
237 | Attribute | |
238 | StaticAttribute | |
239 | Operation""" | |
240 # Standard is (no StaticAttribute): | |
241 # AttributeOrOperation : STRINGIFIER StringifierAttributeOrOperation | |
242 # | Attribute | |
243 # | Operation | |
244 if len(p) > 2: | |
245 # FIXME: Clearer to add stringifier property here, as: | |
246 # p[2].AddChildren(self.BuildTrue('STRINGIFIER')) | |
247 # Fix when actually implementing stringifiers. | |
248 p[0] = p[2] | |
249 else: | |
250 p[0] = p[1] | |
251 | |
252 # [b30.1] | |
253 def p_StaticAttribute(self, p): | |
254 """StaticAttribute : STATIC Attribute""" | |
255 p[2].AddChildren(self.BuildTrue('STATIC')) | |
256 p[0] = p[2] | |
257 | |
258 # [b47] | |
259 def p_ExceptionMember(self, p): | |
260 """ExceptionMember : Const | |
261 | ExceptionField | |
262 | Attribute | |
263 | ExceptionOperation""" | |
264 # Standard is (no Attribute, no ExceptionOperation): | |
265 # ExceptionMember : Const | |
266 # | ExceptionField | |
267 # FIXME: In DOMException.idl, Attributes should be changed to | |
268 # ExceptionFields, and Attribute removed from this rule. | |
269 p[0] = p[1] | |
270 | |
271 # [b47.1] | |
272 def p_ExceptionOperation(self, p): | |
273 """ExceptionOperation : Type identifier '(' ')' ';'""" | |
274 # Needed to handle one case in DOMException.idl: | |
275 # // Override in a Mozilla compatible format | |
276 # [NotEnumerable] DOMString toString(); | |
277 # Limited form of Operation to prevent others from being added. | |
278 # FIXME: Should be a stringifier instead. | |
279 p[0] = self.BuildNamed('ExceptionOperation', p, 2, p[1]) | |
280 | |
281 # Extended attributes | |
282 # [b49] Override base parser: remove comment field, since comments stripped | |
283 # FIXME: Upstream | |
284 def p_ExtendedAttributeList(self, p): | |
285 """ExtendedAttributeList : '[' ExtendedAttribute ExtendedAttributes ']' | |
286 | '[' ']' | |
287 | """ | |
288 if len(p) > 3: | |
289 items = ListFromConcat(p[2], p[3]) | |
290 p[0] = self.BuildProduction('ExtAttributes', p, 1, items) | |
291 | |
292 # [b50] Allow optional trailing comma | |
293 # Blink-only, marked as WONTFIX in Web IDL spec: | |
294 # https://www.w3.org/Bugs/Public/show_bug.cgi?id=22156 | |
295 def p_ExtendedAttributes(self, p): | |
296 """ExtendedAttributes : ',' ExtendedAttribute ExtendedAttributes | |
297 | ',' | |
298 |""" | |
299 if len(p) > 3: | |
300 p[0] = ListFromConcat(p[2], p[3]) | |
301 | |
302 # [b51] Add ExtendedAttributeIdentAndOrIdent | |
303 def p_ExtendedAttribute(self, p): | |
304 """ExtendedAttribute : ExtendedAttributeNoArgs | |
305 | ExtendedAttributeArgList | |
306 | ExtendedAttributeIdent | |
307 | ExtendedAttributeIdentList | |
308 | ExtendedAttributeStringLiteralList | |
309 | ExtendedAttributeNamedArgList""" | |
310 p[0] = p[1] | |
311 | |
312 # [59] | |
313 # FIXME: Upstream UnionType | |
314 def p_UnionType(self, p): | |
315 """UnionType : '(' UnionMemberType OR UnionMemberType UnionMemberTypes '
)'""" | |
316 members = ListFromConcat(p[2], p[4], p[5]) | |
317 p[0] = self.BuildProduction('UnionType', p, 1, members) | |
318 | |
319 # [60] | |
320 def p_UnionMemberType(self, p): | |
321 """UnionMemberType : NonAnyType | |
322 | UnionType TypeSuffix | |
323 | ANY '[' ']' TypeSuffix""" | |
324 if len(p) == 2: | |
325 p[0] = self.BuildProduction('Type', p, 1, p[1]) | |
326 elif len(p) == 3: | |
327 p[0] = self.BuildProduction('Type', p, 1, ListFromConcat(p[1], p[2])
) | |
328 else: | |
329 any_node = ListFromConcat(self.BuildProduction('Any', p, 1), p[4]) | |
330 p[0] = self.BuildProduction('Type', p, 1, any_node) | |
331 | |
332 # [61] | |
333 def p_UnionMemberTypes(self, p): | |
334 """UnionMemberTypes : OR UnionMemberType UnionMemberTypes | |
335 |""" | |
336 if len(p) > 2: | |
337 p[0] = ListFromConcat(p[2], p[3]) | |
338 | |
339 # [70] Override base parser to remove non-standard sized array | |
340 # FIXME: Upstream | |
341 def p_TypeSuffix(self, p): | |
342 """TypeSuffix : '[' ']' TypeSuffix | |
343 | '?' TypeSuffixStartingWithArray | |
344 |""" | |
345 if len(p) == 4: | |
346 p[0] = self.BuildProduction('Array', p, 1, p[3]) | |
347 elif len(p) == 3: | |
348 p[0] = ListFromConcat(self.BuildTrue('NULLABLE'), p[2]) | |
349 | |
350 # [b76.1] Add support for compound Extended Attribute values (A&B and A|B) | |
351 def p_ExtendedAttributeIdentList(self, p): | |
352 """ExtendedAttributeIdentList : identifier '=' identifier '&' IdentAndLi
st | |
353 | identifier '=' identifier '|' IdentOrLis
t""" | |
354 value = self.BuildAttribute('VALUE', p[3] + p[4] + p[5]) | |
355 p[0] = self.BuildNamed('ExtAttribute', p, 1, value) | |
356 | |
357 # [b76.2] A&B&C | |
358 def p_IdentAndList(self, p): | |
359 """IdentAndList : identifier '&' IdentAndList | |
360 | identifier""" | |
361 if len(p) > 3: | |
362 p[0] = p[1] + p[2] + p[3] | |
363 else: | |
364 p[0] = p[1] | |
365 | |
366 # [b76.3] A|B|C | |
367 def p_IdentOrList(self, p): | |
368 """IdentOrList : identifier '|' IdentOrList | |
369 | identifier""" | |
370 if len(p) > 3: | |
371 p[0] = p[1] + p[2] + p[3] | |
372 else: | |
373 p[0] = p[1] | |
374 | |
375 # Blink extension: Add support for compound Extended Attribute values over s
tring literals ("A"|"B") | |
376 def p_ExtendedAttributeStringLiteralList(self, p): | |
377 """ExtendedAttributeStringLiteralList : identifier '=' StringLiteralOrLi
st""" | |
378 value = self.BuildAttribute('VALUE', p[3]) | |
379 p[0] = self.BuildNamed('ExtAttribute', p, 1, value) | |
380 | |
381 # Blink extension: one or more string literals. The values aren't propagated
as literals, | |
382 # but their by their value only. | |
383 def p_StringLiteralOrList(self, p): | |
384 """StringLiteralOrList : StringLiteral '|' StringLiteralOrList | |
385 | StringLiteral""" | |
386 def unwrap_string(ls): | |
387 """Reach in and grab the string literal's "NAME".""" | |
388 return ls[1].value | |
389 | |
390 if len(p) > 3: | |
391 p[0] = unwrap_string(p[1]) + p[2] + p[3] | |
392 else: | |
393 p[0] = unwrap_string(p[1]) | |
394 | |
395 def __dir__(self): | |
396 # Remove REMOVED_RULES from listing so yacc doesn't parse them | |
397 # FIXME: Upstream | |
398 keys = set(self.__dict__.keys() + dir(self.__class__)) | |
399 for rule in REMOVED_RULES: | |
400 production_name = 'p_' + rule | |
401 if production_name in keys: | |
402 keys.remove(production_name) | |
403 return list(keys) | |
404 | |
405 def __init__(self, lexer=None, verbose=False, debug=False, mute_error=False,
outputdir=''): | |
406 lexer = lexer or BlinkIDLLexer() | |
407 self.lexer = lexer | |
408 self.tokens = lexer.KnownTokens() | |
409 # Using SLR (instead of LALR) generates the table faster, | |
410 # but produces the same output. This is ok b/c Web IDL (and Blink IDL) | |
411 # is an SLR grammar (as is often the case for simple LL(1) grammars). | |
412 self.yaccobj = yacc.yacc(module=self, start=STARTING_SYMBOL, method='SLR
', debug=debug, outputdir=outputdir) | |
413 self.parse_debug = debug | |
414 self.verbose = verbose | |
415 self.mute_error = mute_error | |
416 self._parse_errors = 0 | |
417 self._parse_warnings = 0 | |
418 self._last_error_msg = None | |
419 self._last_error_lineno = 0 | |
420 self._last_error_pos = 0 | |
421 | |
422 | |
423 # If run by itself, attempt to build the parser | |
424 if __name__ == '__main__': | |
425 parser = BlinkIDLParser() | |
OLD | NEW |