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.
4
5"""Generates a syntax tree from a Mojo IDL file."""
6
7import imp
8import os.path
9import sys
10
11def _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__)
15  while True:
16    path, tail = os.path.split(path)
17    assert tail
18    if tail == dirname:
19      return path
20
21try:
22  imp.find_module("ply")
23except ImportError:
24  sys.path.append(os.path.join(_GetDirAbove("mojo"), "third_party"))
25from ply import lex
26from ply import yacc
27
28from ..error import Error
29from . import ast
30from .lexer import Lexer
31
32
33_MAX_ORDINAL_VALUE = 0xffffffff
34_MAX_ARRAY_SIZE = 0xffffffff
35
36
37class ParseError(Error):
38  """Class for errors from the parser."""
39
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))
43
44
45# We have methods which look like they could be functions:
46# pylint: disable=R0201
47class Parser(object):
48
49  def __init__(self, lexer, source, filename):
50    self.tokens = lexer.tokens
51    self.source = source
52    self.filename = filename
53
54  # Names of functions
55  #
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 : ...|.
58  #
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)|).
64  #
65  # It's also possible to have a function handling multiple rules with different
66  # left-hand-sides. We do not do this.
67  #
68  # See http://www.dabeaz.com/ply/ply.html#ply_nn25 for more details.
69
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):
74    """root : import_list module LBRACE definition_list RBRACE"""
75    p[0] = ast.Mojom(p[2], p[1], p[4])
76
77  def p_root_2(self, p):
78    """root : import_list definition_list"""
79    p[0] = ast.Mojom(None, p[1], p[2])
80
81  def p_import_list_1(self, p):
82    """import_list : """
83    p[0] = ast.ImportList()
84
85  def p_import_list_2(self, p):
86    """import_list : import_list import"""
87    p[0] = p[1]
88    p[0].Append(p[2])
89
90  def p_import(self, p):
91    """import : IMPORT STRING_LITERAL"""
92    # 'eval' the literal to strip the quotes.
93    # TODO(vtl): This eval is dubious. We should unquote/unescape ourselves.
94    p[0] = ast.Import(eval(p[2]))
95
96  def p_module(self, p):
97    """module : attribute_section MODULE identifier_wrapped """
98    p[0] = ast.Module(p[3], p[1], filename=self.filename, lineno=p.lineno(2))
99
100  def p_definition_list(self, p):
101    """definition_list : definition definition_list
102                       | """
103    if len(p) > 1:
104      p[0] = p[2]
105      p[0].insert(0, p[1])
106    else:
107      p[0] = []
108
109  def p_definition(self, p):
110    """definition : struct
111                  | interface
112                  | enum
113                  | const"""
114    p[0] = p[1]
115
116  def p_attribute_section_1(self, p):
117    """attribute_section : """
118    p[0] = None
119
120  def p_attribute_section_2(self, p):
121    """attribute_section : LBRACKET attribute_list RBRACKET"""
122    p[0] = p[2]
123
124  def p_attribute_list_1(self, p):
125    """attribute_list : """
126    p[0] = ast.AttributeList()
127
128  def p_attribute_list_2(self, p):
129    """attribute_list : nonempty_attribute_list"""
130    p[0] = p[1]
131
132  def p_nonempty_attribute_list_1(self, p):
133    """nonempty_attribute_list : attribute"""
134    p[0] = ast.AttributeList(p[1])
135
136  def p_nonempty_attribute_list_2(self, p):
137    """nonempty_attribute_list : nonempty_attribute_list COMMA attribute"""
138    p[0] = p[1]
139    p[0].Append(p[3])
140
141  def p_attribute(self, p):
142    """attribute : NAME EQUALS evaled_literal
143                 | NAME EQUALS NAME"""
144    p[0] = ast.Attribute(p[1], p[3], filename=self.filename, lineno=p.lineno(1))
145
146  def p_evaled_literal(self, p):
147    """evaled_literal : literal"""
148    # 'eval' the literal to strip the quotes.
149    p[0] = eval(p[1])
150
151  def p_struct(self, p):
152    """struct : attribute_section STRUCT NAME LBRACE struct_body RBRACE SEMI"""
153    p[0] = ast.Struct(p[3], p[1], p[5])
154
155  def p_struct_body_1(self, p):
156    """struct_body : """
157    p[0] = ast.StructBody()
158
159  def p_struct_body_2(self, p):
160    """struct_body : struct_body const
161                   | struct_body enum
162                   | struct_body struct_field"""
163    p[0] = p[1]
164    p[0].Append(p[2])
165
166  def p_struct_field(self, p):
167    """struct_field : typename NAME ordinal default SEMI"""
168    p[0] = ast.StructField(p[2], p[3], p[1], p[4])
169
170  def p_default_1(self, p):
171    """default : """
172    p[0] = None
173
174  def p_default_2(self, p):
175    """default : EQUALS constant"""
176    p[0] = p[2]
177
178  def p_interface(self, p):
179    """interface : attribute_section INTERFACE NAME LBRACE interface_body \
180                       RBRACE SEMI"""
181    p[0] = ast.Interface(p[3], p[1], p[5])
182
183  def p_interface_body_1(self, p):
184    """interface_body : """
185    p[0] = ast.InterfaceBody()
186
187  def p_interface_body_2(self, p):
188    """interface_body : interface_body const
189                      | interface_body enum
190                      | interface_body method"""
191    p[0] = p[1]
192    p[0].Append(p[2])
193
194  def p_response_1(self, p):
195    """response : """
196    p[0] = None
197
198  def p_response_2(self, p):
199    """response : RESPONSE LPAREN parameter_list RPAREN"""
200    p[0] = p[3]
201
202  def p_method(self, p):
203    """method : NAME ordinal LPAREN parameter_list RPAREN response SEMI"""
204    p[0] = ast.Method(p[1], p[2], p[4], p[6])
205
206  def p_parameter_list_1(self, p):
207    """parameter_list : """
208    p[0] = ast.ParameterList()
209
210  def p_parameter_list_2(self, p):
211    """parameter_list : nonempty_parameter_list"""
212    p[0] = p[1]
213
214  def p_nonempty_parameter_list_1(self, p):
215    """nonempty_parameter_list : parameter"""
216    p[0] = ast.ParameterList(p[1])
217
218  def p_nonempty_parameter_list_2(self, p):
219    """nonempty_parameter_list : nonempty_parameter_list COMMA parameter"""
220    p[0] = p[1]
221    p[0].Append(p[3])
222
223  def p_parameter(self, p):
224    """parameter : typename NAME ordinal"""
225    p[0] = ast.Parameter(p[2], p[3], p[1],
226                         filename=self.filename, lineno=p.lineno(2))
227
228  def p_typename(self, p):
229    """typename : nonnullable_typename QSTN
230                | nonnullable_typename"""
231    if len(p) == 2:
232      p[0] = p[1]
233    else:
234      p[0] = p[1] + "?"
235
236  def p_nonnullable_typename(self, p):
237    """nonnullable_typename : basictypename
238                            | array
239                            | fixed_array
240                            | interfacerequest"""
241    p[0] = p[1]
242
243  def p_basictypename(self, p):
244    """basictypename : identifier
245                     | handletype"""
246    p[0] = p[1]
247
248  def p_handletype(self, p):
249    """handletype : HANDLE
250                  | HANDLE LANGLE NAME RANGLE"""
251    if len(p) == 2:
252      p[0] = p[1]
253    else:
254      if p[3] not in ('data_pipe_consumer',
255                      'data_pipe_producer',
256                      'message_pipe',
257                      'shared_buffer'):
258        # Note: We don't enable tracking of line numbers for everything, so we
259        # can't use |p.lineno(3)|.
260        raise ParseError(self.filename, "Invalid handle type %r:" % p[3],
261                         lineno=p.lineno(1),
262                         snippet=self._GetSnippet(p.lineno(1)))
263      p[0] = "handle<" + p[3] + ">"
264
265  def p_array(self, p):
266    """array : typename LBRACKET RBRACKET"""
267    p[0] = p[1] + "[]"
268
269  def p_fixed_array(self, p):
270    """fixed_array : typename LBRACKET INT_CONST_DEC RBRACKET"""
271    value = int(p[3])
272    if value == 0 or value > _MAX_ARRAY_SIZE:
273      raise ParseError(self.filename, "Fixed array size %d invalid" % value,
274                       lineno=p.lineno(3),
275                       snippet=self._GetSnippet(p.lineno(3)))
276    p[0] = p[1] + "[" + p[3] + "]"
277
278  def p_interfacerequest(self, p):
279    """interfacerequest : identifier AMP"""
280    p[0] = p[1] + "&"
281
282  def p_ordinal_1(self, p):
283    """ordinal : """
284    p[0] = None
285
286  def p_ordinal_2(self, p):
287    """ordinal : ORDINAL"""
288    value = int(p[1][1:])
289    if value > _MAX_ORDINAL_VALUE:
290      raise ParseError(self.filename, "Ordinal value %d too large:" % value,
291                       lineno=p.lineno(1),
292                       snippet=self._GetSnippet(p.lineno(1)))
293    p[0] = ast.Ordinal(value, filename=self.filename, lineno=p.lineno(1))
294
295  def p_enum(self, p):
296    """enum : ENUM NAME LBRACE nonempty_enum_value_list RBRACE SEMI
297            | ENUM NAME LBRACE nonempty_enum_value_list COMMA RBRACE SEMI"""
298    p[0] = ast.Enum(p[2], p[4], filename=self.filename, lineno=p.lineno(1))
299
300  def p_nonempty_enum_value_list_1(self, p):
301    """nonempty_enum_value_list : enum_value"""
302    p[0] = ast.EnumValueList(p[1])
303
304  def p_nonempty_enum_value_list_2(self, p):
305    """nonempty_enum_value_list : nonempty_enum_value_list COMMA enum_value"""
306    p[0] = p[1]
307    p[0].Append(p[3])
308
309  def p_enum_value(self, p):
310    """enum_value : NAME
311                  | NAME EQUALS int
312                  | NAME EQUALS identifier_wrapped"""
313    p[0] = ast.EnumValue(p[1], p[3] if len(p) == 4 else None,
314                         filename=self.filename, lineno=p.lineno(1))
315
316  def p_const(self, p):
317    """const : CONST typename NAME EQUALS constant SEMI"""
318    p[0] = ast.Const(p[3], p[2], p[5])
319
320  def p_constant(self, p):
321    """constant : literal
322                | identifier_wrapped"""
323    p[0] = p[1]
324
325  def p_identifier_wrapped(self, p):
326    """identifier_wrapped : identifier"""
327    p[0] = ('IDENTIFIER', p[1])
328
329  # TODO(vtl): Make this produce a "wrapped" identifier (probably as an
330  # |ast.Identifier|, to be added) and get rid of identifier_wrapped.
331  def p_identifier(self, p):
332    """identifier : NAME
333                  | NAME DOT identifier"""
334    p[0] = ''.join(p[1:])
335
336  def p_literal(self, p):
337    """literal : int
338               | float
339               | TRUE
340               | FALSE
341               | DEFAULT
342               | STRING_LITERAL"""
343    p[0] = p[1]
344
345  def p_int(self, p):
346    """int : int_const
347           | PLUS int_const
348           | MINUS int_const"""
349    p[0] = ''.join(p[1:])
350
351  def p_int_const(self, p):
352    """int_const : INT_CONST_DEC
353                 | INT_CONST_HEX"""
354    p[0] = p[1]
355
356  def p_float(self, p):
357    """float : FLOAT_CONST
358             | PLUS FLOAT_CONST
359             | MINUS FLOAT_CONST"""
360    p[0] = ''.join(p[1:])
361
362  def p_error(self, e):
363    if e is None:
364      # Unexpected EOF.
365      # TODO(vtl): Can we figure out what's missing?
366      raise ParseError(self.filename, "Unexpected end of file")
367
368    raise ParseError(self.filename, "Unexpected %r:" % e.value, lineno=e.lineno,
369                     snippet=self._GetSnippet(e.lineno))
370
371  def _GetSnippet(self, lineno):
372    return self.source.split('\n')[lineno - 1]
373
374
375def Parse(source, filename):
376  lexer = Lexer(filename)
377  parser = Parser(lexer, source, filename)
378
379  lex.lex(object=lexer)
380  yacc.yacc(module=parser, debug=0, write_tables=0)
381
382  tree = yacc.parse(source)
383  return tree
384