1 # Copyright 2014 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
5 """Generates a syntax tree from a Mojo IDL file."""
11 def _GetDirAbove(dirname):
12 """Returns the directory "above" this file containing |dirname| (which must
13 also be "above" this file)."""
14 path = os.path.abspath(__file__)
16 path, tail = os.path.split(path)
22 imp.find_module("ply")
24 sys.path.append(os.path.join(_GetDirAbove("mojo"), "third_party"))
28 from ..error import Error
30 from .lexer import Lexer
33 _MAX_ORDINAL_VALUE = 0xffffffff
34 _MAX_ARRAY_SIZE = 0xffffffff
37 class ParseError(Error):
38 """Class for errors from the parser."""
40 def __init__(self, filename, message, lineno=None, snippet=None):
41 Error.__init__(self, filename, message, lineno=lineno,
42 addenda=([snippet] if snippet else None))
45 # We have methods which look like they could be functions:
46 # pylint: disable=R0201
49 def __init__(self, lexer, source, filename):
50 self.tokens = lexer.tokens
52 self.filename = filename
56 # In general, we name functions after the left-hand-side of the rule(s) that
57 # they handle. E.g., |p_foo_bar| for a rule |foo_bar : ...|.
59 # There may be multiple functions handling rules for the same left-hand-side;
60 # then we name the functions |p_foo_bar_N| (for left-hand-side |foo_bar|),
61 # where N is a number (numbered starting from 1). Note that using multiple
62 # functions is actually more efficient than having single functions handle
63 # multiple rules (and, e.g., distinguishing them by examining |len(p)|).
65 # It's also possible to have a function handling multiple rules with different
66 # left-hand-sides. We do not do this.
68 # See http://www.dabeaz.com/ply/ply.html#ply_nn25 for more details.
70 # TODO(vtl): Get rid of the braces in the module "statement". (Consider
71 # renaming "module" -> "package".) Then we'll be able to have a single rule
72 # for root (by making module "optional").
73 def p_root_1(self, p):
75 p[0] = ast.Mojom(None, ast.ImportList(), [])
77 def p_root_2(self, p):
78 """root : root module"""
79 if p[1].module is not None:
80 raise ParseError(self.filename,
81 "Multiple \"module\" statements not allowed:",
82 p[2].lineno, snippet=self._GetSnippet(p[2].lineno))
83 if p[1].import_list.items or p[1].definition_list:
86 "\"module\" statements must precede imports and definitions:",
87 p[2].lineno, snippet=self._GetSnippet(p[2].lineno))
91 def p_root_3(self, p):
92 """root : root import"""
93 if p[1].definition_list:
94 raise ParseError(self.filename,
95 "\"import\" statements must precede definitions:",
96 p[2].lineno, snippet=self._GetSnippet(p[2].lineno))
98 p[0].import_list.Append(p[2])
100 def p_root_4(self, p):
101 """root : root definition"""
103 p[0].definition_list.append(p[2])
105 def p_import(self, p):
106 """import : IMPORT STRING_LITERAL SEMI"""
107 # 'eval' the literal to strip the quotes.
108 # TODO(vtl): This eval is dubious. We should unquote/unescape ourselves.
109 p[0] = ast.Import(eval(p[2]), filename=self.filename, lineno=p.lineno(2))
111 def p_module(self, p):
112 """module : attribute_section MODULE identifier_wrapped SEMI"""
113 p[0] = ast.Module(p[3], p[1], filename=self.filename, lineno=p.lineno(2))
115 def p_definition(self, p):
116 """definition : struct
122 def p_attribute_section_1(self, p):
123 """attribute_section : """
126 def p_attribute_section_2(self, p):
127 """attribute_section : LBRACKET attribute_list RBRACKET"""
130 def p_attribute_list_1(self, p):
131 """attribute_list : """
132 p[0] = ast.AttributeList()
134 def p_attribute_list_2(self, p):
135 """attribute_list : nonempty_attribute_list"""
138 def p_nonempty_attribute_list_1(self, p):
139 """nonempty_attribute_list : attribute"""
140 p[0] = ast.AttributeList(p[1])
142 def p_nonempty_attribute_list_2(self, p):
143 """nonempty_attribute_list : nonempty_attribute_list COMMA attribute"""
147 def p_attribute(self, p):
148 """attribute : NAME EQUALS evaled_literal
149 | NAME EQUALS NAME"""
150 p[0] = ast.Attribute(p[1], p[3], filename=self.filename, lineno=p.lineno(1))
152 def p_evaled_literal(self, p):
153 """evaled_literal : literal"""
154 # 'eval' the literal to strip the quotes.
157 def p_struct(self, p):
158 """struct : attribute_section STRUCT NAME LBRACE struct_body RBRACE SEMI"""
159 p[0] = ast.Struct(p[3], p[1], p[5])
161 def p_struct_body_1(self, p):
163 p[0] = ast.StructBody()
165 def p_struct_body_2(self, p):
166 """struct_body : struct_body const
168 | struct_body struct_field"""
172 def p_struct_field(self, p):
173 """struct_field : typename NAME ordinal default SEMI"""
174 p[0] = ast.StructField(p[2], p[3], p[1], p[4])
176 def p_default_1(self, p):
180 def p_default_2(self, p):
181 """default : EQUALS constant"""
184 def p_interface(self, p):
185 """interface : attribute_section INTERFACE NAME LBRACE interface_body \
187 p[0] = ast.Interface(p[3], p[1], p[5])
189 def p_interface_body_1(self, p):
190 """interface_body : """
191 p[0] = ast.InterfaceBody()
193 def p_interface_body_2(self, p):
194 """interface_body : interface_body const
195 | interface_body enum
196 | interface_body method"""
200 def p_response_1(self, p):
204 def p_response_2(self, p):
205 """response : RESPONSE LPAREN parameter_list RPAREN"""
208 def p_method(self, p):
209 """method : NAME ordinal LPAREN parameter_list RPAREN response SEMI"""
210 p[0] = ast.Method(p[1], p[2], p[4], p[6])
212 def p_parameter_list_1(self, p):
213 """parameter_list : """
214 p[0] = ast.ParameterList()
216 def p_parameter_list_2(self, p):
217 """parameter_list : nonempty_parameter_list"""
220 def p_nonempty_parameter_list_1(self, p):
221 """nonempty_parameter_list : parameter"""
222 p[0] = ast.ParameterList(p[1])
224 def p_nonempty_parameter_list_2(self, p):
225 """nonempty_parameter_list : nonempty_parameter_list COMMA parameter"""
229 def p_parameter(self, p):
230 """parameter : typename NAME ordinal"""
231 p[0] = ast.Parameter(p[2], p[3], p[1],
232 filename=self.filename, lineno=p.lineno(2))
234 def p_typename(self, p):
235 """typename : nonnullable_typename QSTN
236 | nonnullable_typename"""
242 def p_nonnullable_typename(self, p):
243 """nonnullable_typename : basictypename
247 | interfacerequest"""
250 def p_basictypename(self, p):
251 """basictypename : identifier
255 def p_handletype(self, p):
256 """handletype : HANDLE
257 | HANDLE LANGLE NAME RANGLE"""
261 if p[3] not in ('data_pipe_consumer',
262 'data_pipe_producer',
265 # Note: We don't enable tracking of line numbers for everything, so we
266 # can't use |p.lineno(3)|.
267 raise ParseError(self.filename, "Invalid handle type %r:" % p[3],
269 snippet=self._GetSnippet(p.lineno(1)))
270 p[0] = "handle<" + p[3] + ">"
272 def p_array(self, p):
273 """array : ARRAY LANGLE typename RANGLE"""
276 def p_fixed_array(self, p):
277 """fixed_array : ARRAY LANGLE typename COMMA INT_CONST_DEC RANGLE"""
279 if value == 0 or value > _MAX_ARRAY_SIZE:
280 raise ParseError(self.filename, "Fixed array size %d invalid:" % value,
282 snippet=self._GetSnippet(p.lineno(5)))
283 p[0] = p[3] + "[" + p[5] + "]"
285 def p_associative_array(self, p):
286 """associative_array : MAP LANGLE identifier COMMA typename RANGLE"""
287 p[0] = p[5] + "{" + p[3] + "}"
289 def p_interfacerequest(self, p):
290 """interfacerequest : identifier AMP"""
293 def p_ordinal_1(self, p):
297 def p_ordinal_2(self, p):
298 """ordinal : ORDINAL"""
299 value = int(p[1][1:])
300 if value > _MAX_ORDINAL_VALUE:
301 raise ParseError(self.filename, "Ordinal value %d too large:" % value,
303 snippet=self._GetSnippet(p.lineno(1)))
304 p[0] = ast.Ordinal(value, filename=self.filename, lineno=p.lineno(1))
307 """enum : ENUM NAME LBRACE nonempty_enum_value_list RBRACE SEMI
308 | ENUM NAME LBRACE nonempty_enum_value_list COMMA RBRACE SEMI"""
309 p[0] = ast.Enum(p[2], p[4], filename=self.filename, lineno=p.lineno(1))
311 def p_nonempty_enum_value_list_1(self, p):
312 """nonempty_enum_value_list : enum_value"""
313 p[0] = ast.EnumValueList(p[1])
315 def p_nonempty_enum_value_list_2(self, p):
316 """nonempty_enum_value_list : nonempty_enum_value_list COMMA enum_value"""
320 def p_enum_value(self, p):
323 | NAME EQUALS identifier_wrapped"""
324 p[0] = ast.EnumValue(p[1], p[3] if len(p) == 4 else None,
325 filename=self.filename, lineno=p.lineno(1))
327 def p_const(self, p):
328 """const : CONST typename NAME EQUALS constant SEMI"""
329 p[0] = ast.Const(p[3], p[2], p[5])
331 def p_constant(self, p):
332 """constant : literal
333 | identifier_wrapped"""
336 def p_identifier_wrapped(self, p):
337 """identifier_wrapped : identifier"""
338 p[0] = ('IDENTIFIER', p[1])
340 # TODO(vtl): Make this produce a "wrapped" identifier (probably as an
341 # |ast.Identifier|, to be added) and get rid of identifier_wrapped.
342 def p_identifier(self, p):
344 | NAME DOT identifier"""
345 p[0] = ''.join(p[1:])
347 def p_literal(self, p):
360 p[0] = ''.join(p[1:])
362 def p_int_const(self, p):
363 """int_const : INT_CONST_DEC
367 def p_float(self, p):
368 """float : FLOAT_CONST
370 | MINUS FLOAT_CONST"""
371 p[0] = ''.join(p[1:])
373 def p_error(self, e):
376 # TODO(vtl): Can we figure out what's missing?
377 raise ParseError(self.filename, "Unexpected end of file")
379 raise ParseError(self.filename, "Unexpected %r:" % e.value, lineno=e.lineno,
380 snippet=self._GetSnippet(e.lineno))
382 def _GetSnippet(self, lineno):
383 return self.source.split('\n')[lineno - 1]
386 def Parse(source, filename):
387 lexer = Lexer(filename)
388 parser = Parser(lexer, source, filename)
390 lex.lex(object=lexer)
391 yacc.yacc(module=parser, debug=0, write_tables=0)
393 tree = yacc.parse(source)