Home | History | Annotate | Download | only in parse
      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 
      7 import imp
      8 import os.path
      9 import sys
     10 
     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__)
     15   while True:
     16     path, tail = os.path.split(path)
     17     assert tail
     18     if tail == dirname:
     19       return path
     20 
     21 try:
     22   imp.find_module("ply")
     23 except ImportError:
     24   sys.path.append(os.path.join(_GetDirAbove("mojo"), "third_party"))
     25 from ply import lex
     26 from ply import yacc
     27 
     28 from ..error import Error
     29 from . import ast
     30 from .lexer import Lexer
     31 
     32 
     33 _MAX_ORDINAL_VALUE = 0xffffffff
     34 _MAX_ARRAY_SIZE = 0xffffffff
     35 
     36 
     37 class 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
     47 class 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 
    375 def 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