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 # Disable lint check for finding modules:
     12 # pylint: disable=F0401
     13 
     14 def _GetDirAbove(dirname):
     15   """Returns the directory "above" this file containing |dirname| (which must
     16   also be "above" this file)."""
     17   path = os.path.abspath(__file__)
     18   while True:
     19     path, tail = os.path.split(path)
     20     assert tail
     21     if tail == dirname:
     22       return path
     23 
     24 try:
     25   imp.find_module("ply")
     26 except ImportError:
     27   sys.path.append(os.path.join(_GetDirAbove("mojo"), "third_party"))
     28 from ply import lex
     29 from ply import yacc
     30 
     31 from ..error import Error
     32 import ast
     33 from lexer import Lexer
     34 
     35 
     36 _MAX_ORDINAL_VALUE = 0xffffffff
     37 
     38 
     39 def _ListFromConcat(*items):
     40   """Generate list by concatenating inputs (note: only concatenates lists, not
     41   tuples or other iterables)."""
     42   itemsout = []
     43   for item in items:
     44     if item is None:
     45       continue
     46     if type(item) is not type([]):
     47       itemsout.append(item)
     48     else:
     49       itemsout.extend(item)
     50   return itemsout
     51 
     52 
     53 # Disable lint check for exceptions deriving from Exception:
     54 # pylint: disable=W0710
     55 class ParseError(Error):
     56   """Class for errors from the parser."""
     57 
     58   def __init__(self, filename, message, lineno=None, snippet=None):
     59     Error.__init__(self, filename, message, lineno=lineno,
     60                    addenda=([snippet] if snippet else None))
     61 
     62 
     63 # We have methods which look like they could be functions:
     64 # pylint: disable=R0201
     65 class Parser(object):
     66 
     67   def __init__(self, lexer, source, filename):
     68     self.tokens = lexer.tokens
     69     self.source = source
     70     self.filename = filename
     71 
     72   def p_root(self, p):
     73     """root : import root
     74             | module
     75             | definitions"""
     76     if len(p) > 2:
     77       p[0] = _ListFromConcat(p[1], p[2])
     78     else:
     79       # Generator expects a module. If one wasn't specified insert one with an
     80       # empty name.
     81       if p[1][0] != 'MODULE':
     82         p[0] = [('MODULE', '', None, p[1])]
     83       else:
     84         p[0] = [p[1]]
     85 
     86   def p_import(self, p):
     87     """import : IMPORT STRING_LITERAL"""
     88     # 'eval' the literal to strip the quotes.
     89     p[0] = ('IMPORT', eval(p[2]))
     90 
     91   def p_module(self, p):
     92     """module : attribute_section MODULE identifier LBRACE definitions RBRACE"""
     93     p[0] = ('MODULE', p[3], p[1], p[5])
     94 
     95   def p_definitions(self, p):
     96     """definitions : definition definitions
     97                    | """
     98     if len(p) > 1:
     99       p[0] = _ListFromConcat(p[1], p[2])
    100 
    101   def p_definition(self, p):
    102     """definition : struct
    103                   | interface
    104                   | enum
    105                   | const"""
    106     p[0] = p[1]
    107 
    108   def p_attribute_section(self, p):
    109     """attribute_section : LBRACKET attributes RBRACKET
    110                          | """
    111     if len(p) > 3:
    112       p[0] = p[2]
    113 
    114   def p_attributes(self, p):
    115     """attributes : attribute
    116                   | attribute COMMA attributes
    117                   | """
    118     if len(p) == 2:
    119       p[0] = _ListFromConcat(p[1])
    120     elif len(p) > 3:
    121       p[0] = _ListFromConcat(p[1], p[3])
    122 
    123   def p_attribute(self, p):
    124     """attribute : NAME EQUALS evaled_literal
    125                  | NAME EQUALS NAME"""
    126     p[0] = ('ATTRIBUTE', p[1], p[3])
    127 
    128   def p_evaled_literal(self, p):
    129     """evaled_literal : literal"""
    130     # 'eval' the literal to strip the quotes.
    131     p[0] = eval(p[1])
    132 
    133   def p_struct(self, p):
    134     """struct : attribute_section STRUCT NAME LBRACE struct_body RBRACE SEMI"""
    135     p[0] = ('STRUCT', p[3], p[1], p[5])
    136 
    137   def p_struct_body(self, p):
    138     """struct_body : field struct_body
    139                    | enum struct_body
    140                    | const struct_body
    141                    | """
    142     if len(p) > 1:
    143       p[0] = _ListFromConcat(p[1], p[2])
    144 
    145   def p_field(self, p):
    146     """field : typename NAME ordinal default SEMI"""
    147     p[0] = ('FIELD', p[1], p[2], p[3], p[4])
    148 
    149   def p_default(self, p):
    150     """default : EQUALS constant
    151                | """
    152     if len(p) > 2:
    153       p[0] = p[2]
    154 
    155   def p_interface(self, p):
    156     """interface : attribute_section INTERFACE NAME LBRACE interface_body \
    157                        RBRACE SEMI"""
    158     p[0] = ('INTERFACE', p[3], p[1], p[5])
    159 
    160   def p_interface_body(self, p):
    161     """interface_body : method interface_body
    162                       | enum interface_body
    163                       | const interface_body
    164                       | """
    165     if len(p) > 1:
    166       p[0] = _ListFromConcat(p[1], p[2])
    167 
    168   def p_response(self, p):
    169     """response : RESPONSE LPAREN parameters RPAREN
    170                 | """
    171     if len(p) > 3:
    172       p[0] = p[3]
    173 
    174   def p_method(self, p):
    175     """method : NAME ordinal LPAREN parameters RPAREN response SEMI"""
    176     p[0] = ('METHOD', p[1], p[4], p[2], p[6])
    177 
    178   def p_parameters(self, p):
    179     """parameters : parameter
    180                   | parameter COMMA parameters
    181                   | """
    182     if len(p) == 1:
    183       p[0] = []
    184     elif len(p) == 2:
    185       p[0] = _ListFromConcat(p[1])
    186     elif len(p) > 3:
    187       p[0] = _ListFromConcat(p[1], p[3])
    188 
    189   def p_parameter(self, p):
    190     """parameter : typename NAME ordinal"""
    191     p[0] = ('PARAM', p[1], p[2], p[3])
    192 
    193   def p_typename(self, p):
    194     """typename : basictypename
    195                 | array
    196                 | interfacerequest"""
    197     p[0] = p[1]
    198 
    199   def p_basictypename(self, p):
    200     """basictypename : identifier
    201                      | handletype"""
    202     p[0] = p[1]
    203 
    204   def p_handletype(self, p):
    205     """handletype : HANDLE
    206                   | HANDLE LANGLE NAME RANGLE"""
    207     if len(p) == 2:
    208       p[0] = p[1]
    209     else:
    210       if p[3] not in ('data_pipe_consumer',
    211                       'data_pipe_producer',
    212                       'message_pipe',
    213                       'shared_buffer'):
    214         # Note: We don't enable tracking of line numbers for everything, so we
    215         # can't use |p.lineno(3)|.
    216         raise ParseError(self.filename, "Invalid handle type %r:" % p[3],
    217                          lineno=p.lineno(1),
    218                          snippet=self._GetSnippet(p.lineno(1)))
    219       p[0] = "handle<" + p[3] + ">"
    220 
    221   def p_array(self, p):
    222     """array : typename LBRACKET RBRACKET"""
    223     p[0] = p[1] + "[]"
    224 
    225   def p_interfacerequest(self, p):
    226     """interfacerequest : identifier AMP"""
    227     p[0] = p[1] + "&"
    228 
    229   def p_ordinal(self, p):
    230     """ordinal : ORDINAL
    231                | """
    232     if len(p) > 1:
    233       value = int(p[1][1:])
    234       if value > _MAX_ORDINAL_VALUE:
    235         raise ParseError(self.filename, "Ordinal value %d too large:" % value,
    236                          lineno=p.lineno(1),
    237                          snippet=self._GetSnippet(p.lineno(1)))
    238       p[0] = ast.Ordinal(value, filename=self.filename, lineno=p.lineno(1))
    239     else:
    240       p[0] = ast.Ordinal(None)
    241 
    242   def p_enum(self, p):
    243     """enum : ENUM NAME LBRACE enum_fields RBRACE SEMI"""
    244     p[0] = ('ENUM', p[2], p[4])
    245 
    246   def p_enum_fields(self, p):
    247     """enum_fields : enum_field
    248                    | enum_field COMMA enum_fields
    249                    | """
    250     if len(p) == 2:
    251       p[0] = _ListFromConcat(p[1])
    252     elif len(p) > 3:
    253       p[0] = _ListFromConcat(p[1], p[3])
    254 
    255   def p_enum_field(self, p):
    256     """enum_field : NAME
    257                   | NAME EQUALS constant"""
    258     if len(p) == 2:
    259       p[0] = ('ENUM_FIELD', p[1], None)
    260     else:
    261       p[0] = ('ENUM_FIELD', p[1], p[3])
    262 
    263   def p_const(self, p):
    264     """const : CONST typename NAME EQUALS constant SEMI"""
    265     p[0] = ('CONST', p[2], p[3], p[5])
    266 
    267   def p_constant(self, p):
    268     """constant : literal
    269                 | identifier_wrapped"""
    270     p[0] = p[1]
    271 
    272   def p_identifier_wrapped(self, p):
    273     """identifier_wrapped : identifier"""
    274     p[0] = ('IDENTIFIER', p[1])
    275 
    276   def p_identifier(self, p):
    277     """identifier : NAME
    278                   | NAME DOT identifier"""
    279     p[0] = ''.join(p[1:])
    280 
    281   def p_literal(self, p):
    282     """literal : number
    283                | CHAR_CONST
    284                | TRUE
    285                | FALSE
    286                | DEFAULT
    287                | STRING_LITERAL"""
    288     p[0] = p[1]
    289 
    290   def p_number(self, p):
    291     """number : digits
    292               | PLUS digits
    293               | MINUS digits"""
    294     p[0] = ''.join(p[1:])
    295 
    296   def p_digits(self, p):
    297     """digits : INT_CONST_DEC
    298               | INT_CONST_HEX
    299               | FLOAT_CONST"""
    300     p[0] = p[1]
    301 
    302   def p_error(self, e):
    303     if e is None:
    304       # Unexpected EOF.
    305       # TODO(vtl): Can we figure out what's missing?
    306       raise ParseError(self.filename, "Unexpected end of file")
    307 
    308     raise ParseError(self.filename, "Unexpected %r:" % e.value, lineno=e.lineno,
    309                      snippet=self._GetSnippet(e.lineno))
    310 
    311   def _GetSnippet(self, lineno):
    312     return self.source.split('\n')[lineno - 1]
    313 
    314 
    315 def Parse(source, filename):
    316   lexer = Lexer(filename)
    317   parser = Parser(lexer, source, filename)
    318 
    319   lex.lex(object=lexer)
    320   yacc.yacc(module=parser, debug=0, write_tables=0)
    321 
    322   tree = yacc.parse(source)
    323   return tree
    324