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