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