1# This Source Code Form is subject to the terms of the Mozilla Public 2# License, v. 2.0. If a copy of the MPL was not distributed with this 3# file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 5import os 6from ply import lex, yacc 7 8from ipdl.ast import * 9 10# ----------------------------------------------------------------------------- 11 12 13class ParseError(Exception): 14 def __init__(self, loc, fmt, *args): 15 self.loc = loc 16 self.error = ( 17 "%s%s: error: %s" % (Parser.includeStackString(), loc, fmt) 18 ) % args 19 20 def __str__(self): 21 return self.error 22 23 24def _safeLinenoValue(t): 25 lineno, value = 0, "???" 26 if hasattr(t, "lineno"): 27 lineno = t.lineno 28 if hasattr(t, "value"): 29 value = t.value 30 return lineno, value 31 32 33def _error(loc, fmt, *args): 34 raise ParseError(loc, fmt, *args) 35 36 37class Parser: 38 # when we reach an |include [protocol] foo;| statement, we need to 39 # save the current parser state and create a new one. this "stack" is 40 # where that state is saved 41 # 42 # there is one Parser per file 43 current = None 44 parseStack = [] 45 parsed = {} 46 47 def __init__(self, type, name, debug=False): 48 assert type and name 49 self.type = type 50 self.debug = debug 51 self.filename = None 52 self.includedirs = None 53 self.loc = None # not always up to date 54 self.lexer = None 55 self.parser = None 56 self.tu = TranslationUnit(type, name) 57 self.direction = None 58 59 def parse(self, input, filename, includedirs): 60 assert os.path.isabs(filename) 61 62 if self.tu.name in Parser.parsed: 63 priorTU = Parser.parsed[self.tu.name].tu 64 if priorTU.filename != filename: 65 _error( 66 Loc(filename), 67 "Trying to load `%s' from a file when we'd already seen it in file `%s'" 68 % (self.tu.name, priorTU.filename), 69 ) 70 71 return priorTU 72 73 self.lexer = lex.lex(debug=self.debug) 74 self.parser = yacc.yacc(debug=self.debug, write_tables=False) 75 self.filename = filename 76 self.includedirs = includedirs 77 self.tu.filename = filename 78 79 Parser.parsed[self.tu.name] = self 80 Parser.parseStack.append(Parser.current) 81 Parser.current = self 82 83 try: 84 ast = self.parser.parse(input=input, lexer=self.lexer, debug=self.debug) 85 finally: 86 Parser.current = Parser.parseStack.pop() 87 88 return ast 89 90 def resolveIncludePath(self, filepath): 91 """Return the absolute path from which the possibly partial 92 |filepath| should be read, or |None| if |filepath| cannot be located.""" 93 for incdir in self.includedirs + [""]: 94 realpath = os.path.join(incdir, filepath) 95 if os.path.isfile(realpath): 96 return os.path.abspath(realpath) 97 return None 98 99 # returns a GCC-style string representation of the include stack. 100 # e.g., 101 # in file included from 'foo.ipdl', line 120: 102 # in file included from 'bar.ipd', line 12: 103 # which can be printed above a proper error message or warning 104 @staticmethod 105 def includeStackString(): 106 s = "" 107 for parse in Parser.parseStack[1:]: 108 s += " in file included from `%s', line %d:\n" % ( 109 parse.loc.filename, 110 parse.loc.lineno, 111 ) 112 return s 113 114 115def locFromTok(p, num): 116 return Loc(Parser.current.filename, p.lineno(num)) 117 118 119# ----------------------------------------------------------------------------- 120 121reserved = set( 122 ( 123 "async", 124 "both", 125 "child", 126 "class", 127 "from", 128 "include", 129 "intr", 130 "manager", 131 "manages", 132 "namespace", 133 "nested", 134 "nullable", 135 "or", 136 "parent", 137 "protocol", 138 "refcounted", 139 "returns", 140 "struct", 141 "sync", 142 "union", 143 "UniquePtr", 144 "upto", 145 "using", 146 ) 147) 148tokens = [ 149 "COLONCOLON", 150 "ID", 151 "STRING", 152] + [r.upper() for r in reserved] 153 154t_COLONCOLON = "::" 155 156literals = "(){}[]<>;:,?=" 157t_ignore = " \f\t\v" 158 159 160def t_linecomment(t): 161 r"//[^\n]*" 162 163 164def t_multilinecomment(t): 165 r"/\*(\n|.)*?\*/" 166 t.lexer.lineno += t.value.count("\n") 167 168 169def t_NL(t): 170 r"(?:\r\n|\n|\n)+" 171 t.lexer.lineno += len(t.value) 172 173 174def t_ID(t): 175 r"[a-zA-Z_][a-zA-Z0-9_]*" 176 if t.value in reserved: 177 t.type = t.value.upper() 178 return t 179 180 181def t_STRING(t): 182 r'"[^"\n]*"' 183 t.value = t.value[1:-1] 184 return t 185 186 187def t_error(t): 188 _error( 189 Loc(Parser.current.filename, t.lineno), 190 "lexically invalid characters `%s", 191 t.value, 192 ) 193 194 195# ----------------------------------------------------------------------------- 196 197 198def p_TranslationUnit(p): 199 """TranslationUnit : Preamble NamespacedStuff""" 200 tu = Parser.current.tu 201 tu.loc = Loc(tu.filename) 202 for stmt in p[1]: 203 if isinstance(stmt, CxxInclude): 204 tu.addCxxInclude(stmt) 205 elif isinstance(stmt, Include): 206 tu.addInclude(stmt) 207 elif isinstance(stmt, UsingStmt): 208 tu.addUsingStmt(stmt) 209 else: 210 assert 0 211 212 for thing in p[2]: 213 if isinstance(thing, StructDecl): 214 tu.addStructDecl(thing) 215 elif isinstance(thing, UnionDecl): 216 tu.addUnionDecl(thing) 217 elif isinstance(thing, Protocol): 218 if tu.protocol is not None: 219 _error(thing.loc, "only one protocol definition per file") 220 tu.protocol = thing 221 else: 222 assert 0 223 224 # The "canonical" namespace of the tu, what it's considered to be 225 # in for the purposes of C++: |#include "foo/bar/TU.h"| 226 if tu.protocol: 227 assert tu.filetype == "protocol" 228 tu.namespaces = tu.protocol.namespaces 229 tu.name = tu.protocol.name 230 else: 231 assert tu.filetype == "header" 232 # There's not really a canonical "thing" in headers. So 233 # somewhat arbitrarily use the namespace of the last 234 # interesting thing that was declared. 235 for thing in reversed(tu.structsAndUnions): 236 tu.namespaces = thing.namespaces 237 break 238 239 p[0] = tu 240 241 242# -------------------- 243# Preamble 244 245 246def p_Preamble(p): 247 """Preamble : Preamble PreambleStmt ';' 248 |""" 249 if 1 == len(p): 250 p[0] = [] 251 else: 252 p[1].append(p[2]) 253 p[0] = p[1] 254 255 256def p_PreambleStmt(p): 257 """PreambleStmt : CxxIncludeStmt 258 | IncludeStmt 259 | UsingStmt""" 260 p[0] = p[1] 261 262 263def p_CxxIncludeStmt(p): 264 """CxxIncludeStmt : INCLUDE STRING""" 265 p[0] = CxxInclude(locFromTok(p, 1), p[2]) 266 267 268def p_IncludeStmt(p): 269 """IncludeStmt : INCLUDE PROTOCOL ID 270 | INCLUDE ID""" 271 loc = locFromTok(p, 1) 272 273 Parser.current.loc = loc 274 if 4 == len(p): 275 id = p[3] 276 type = "protocol" 277 else: 278 id = p[2] 279 type = "header" 280 inc = Include(loc, type, id) 281 282 path = Parser.current.resolveIncludePath(inc.file) 283 if path is None: 284 raise ParseError(loc, "can't locate include file `%s'" % (inc.file)) 285 286 inc.tu = Parser(type, id).parse(open(path).read(), path, Parser.current.includedirs) 287 p[0] = inc 288 289 290def p_UsingKind(p): 291 """UsingKind : CLASS 292 | STRUCT 293 |""" 294 p[0] = p[1] if 2 == len(p) else None 295 296 297def p_MaybeRefcounted(p): 298 """MaybeRefcounted : REFCOUNTED 299 |""" 300 p[0] = 2 == len(p) 301 302 303def p_UsingStmt(p): 304 """UsingStmt : Attributes USING UsingKind CxxType FROM STRING""" 305 p[0] = UsingStmt( 306 locFromTok(p, 2), 307 attributes=p[1], 308 kind=p[3], 309 cxxTypeSpec=p[4], 310 cxxHeader=p[6], 311 ) 312 313 314# -------------------- 315# Namespaced stuff 316 317 318def p_NamespacedStuff(p): 319 """NamespacedStuff : NamespacedStuff NamespaceThing 320 | NamespaceThing""" 321 if 2 == len(p): 322 p[0] = p[1] 323 else: 324 p[1].extend(p[2]) 325 p[0] = p[1] 326 327 328def p_NamespaceThing(p): 329 """NamespaceThing : NAMESPACE ID '{' NamespacedStuff '}' 330 | StructDecl 331 | UnionDecl 332 | ProtocolDefn""" 333 if 2 == len(p): 334 p[0] = [p[1]] 335 else: 336 for thing in p[4]: 337 thing.addOuterNamespace(Namespace(locFromTok(p, 1), p[2])) 338 p[0] = p[4] 339 340 341def p_StructDecl(p): 342 """StructDecl : Attributes STRUCT ID '{' StructFields '}' ';' 343 | Attributes STRUCT ID '{' '}' ';'""" 344 if 8 == len(p): 345 p[0] = StructDecl(locFromTok(p, 2), p[3], p[5], p[1]) 346 else: 347 p[0] = StructDecl(locFromTok(p, 2), p[3], [], p[1]) 348 349 350def p_StructFields(p): 351 """StructFields : StructFields StructField ';' 352 | StructField ';'""" 353 if 3 == len(p): 354 p[0] = [p[1]] 355 else: 356 p[1].append(p[2]) 357 p[0] = p[1] 358 359 360def p_StructField(p): 361 """StructField : Type ID""" 362 p[0] = StructField(locFromTok(p, 1), p[1], p[2]) 363 364 365def p_UnionDecl(p): 366 """UnionDecl : Attributes UNION ID '{' ComponentTypes '}' ';'""" 367 p[0] = UnionDecl(locFromTok(p, 2), p[3], p[5], p[1]) 368 369 370def p_ComponentTypes(p): 371 """ComponentTypes : ComponentTypes Type ';' 372 | Type ';'""" 373 if 3 == len(p): 374 p[0] = [p[1]] 375 else: 376 p[1].append(p[2]) 377 p[0] = p[1] 378 379 380def p_ProtocolDefn(p): 381 """ProtocolDefn : OptionalProtocolSendSemanticsQual MaybeRefcounted \ 382 PROTOCOL ID '{' ProtocolBody '}' ';'""" 383 protocol = p[6] 384 protocol.loc = locFromTok(p, 3) 385 protocol.name = p[4] 386 protocol.nested = p[1][0] 387 protocol.sendSemantics = p[1][1] 388 protocol.refcounted = p[2] 389 p[0] = protocol 390 391 if Parser.current.type == "header": 392 _error( 393 protocol.loc, 394 "can't define a protocol in a header. Do it in a protocol spec instead.", 395 ) 396 397 398def p_ProtocolBody(p): 399 """ProtocolBody : ManagersStmtOpt""" 400 p[0] = p[1] 401 402 403# -------------------- 404# manager/manages stmts 405 406 407def p_ManagersStmtOpt(p): 408 """ManagersStmtOpt : ManagersStmt ManagesStmtsOpt 409 | ManagesStmtsOpt""" 410 if 2 == len(p): 411 p[0] = p[1] 412 else: 413 p[2].managers = p[1] 414 p[0] = p[2] 415 416 417def p_ManagersStmt(p): 418 """ManagersStmt : MANAGER ManagerList ';'""" 419 if 1 == len(p): 420 p[0] = [] 421 else: 422 p[0] = p[2] 423 424 425def p_ManagerList(p): 426 """ManagerList : ID 427 | ManagerList OR ID""" 428 if 2 == len(p): 429 p[0] = [Manager(locFromTok(p, 1), p[1])] 430 else: 431 p[1].append(Manager(locFromTok(p, 3), p[3])) 432 p[0] = p[1] 433 434 435def p_ManagesStmtsOpt(p): 436 """ManagesStmtsOpt : ManagesStmt ManagesStmtsOpt 437 | MessageDeclsOpt""" 438 if 2 == len(p): 439 p[0] = p[1] 440 else: 441 p[2].managesStmts.insert(0, p[1]) 442 p[0] = p[2] 443 444 445def p_ManagesStmt(p): 446 """ManagesStmt : MANAGES ID ';'""" 447 p[0] = ManagesStmt(locFromTok(p, 1), p[2]) 448 449 450# -------------------- 451# Message decls 452 453 454def p_MessageDeclsOpt(p): 455 """MessageDeclsOpt : MessageDeclThing MessageDeclsOpt 456 |""" 457 if 1 == len(p): 458 # we fill in |loc| in the Protocol rule 459 p[0] = Protocol(None) 460 else: 461 p[2].messageDecls.insert(0, p[1]) 462 p[0] = p[2] 463 464 465def p_MessageDeclThing(p): 466 """MessageDeclThing : MessageDirectionLabel ':' MessageDecl ';' 467 | MessageDecl ';'""" 468 if 3 == len(p): 469 p[0] = p[1] 470 else: 471 p[0] = p[3] 472 473 474def p_MessageDirectionLabel(p): 475 """MessageDirectionLabel : PARENT 476 | CHILD 477 | BOTH""" 478 if p[1] == "parent": 479 Parser.current.direction = IN 480 elif p[1] == "child": 481 Parser.current.direction = OUT 482 elif p[1] == "both": 483 Parser.current.direction = INOUT 484 else: 485 assert 0 486 487 488def p_MessageDecl(p): 489 """MessageDecl : Attributes SendSemantics MessageBody""" 490 msg = p[3] 491 msg.attributes = p[1] 492 msg.sendSemantics = p[2] 493 494 if Parser.current.direction is None: 495 _error(msg.loc, "missing message direction") 496 msg.direction = Parser.current.direction 497 498 p[0] = msg 499 500 501def p_MessageBody(p): 502 """MessageBody : ID MessageInParams MessageOutParams""" 503 # FIXME/cjones: need better loc info: use one of the quals 504 name = p[1] 505 msg = MessageDecl(locFromTok(p, 1)) 506 msg.name = name 507 msg.addInParams(p[2]) 508 msg.addOutParams(p[3]) 509 510 p[0] = msg 511 512 513def p_MessageInParams(p): 514 """MessageInParams : '(' ParamList ')'""" 515 p[0] = p[2] 516 517 518def p_MessageOutParams(p): 519 """MessageOutParams : RETURNS '(' ParamList ')' 520 |""" 521 if 1 == len(p): 522 p[0] = [] 523 else: 524 p[0] = p[3] 525 526 527# -------------------- 528# Attributes 529def p_Attributes(p): 530 """Attributes : '[' AttributeList ']' 531 |""" 532 p[0] = {} 533 if 4 == len(p): 534 for attr in p[2]: 535 if attr.name in p[0]: 536 _error(attr.loc, "Repeated extended attribute `%s'", attr.name) 537 p[0][attr.name] = attr 538 539 540def p_AttributeList(p): 541 """AttributeList : Attribute ',' AttributeList 542 | Attribute""" 543 p[0] = [p[1]] 544 if 4 == len(p): 545 p[0] += p[3] 546 547 548def p_Attribute(p): 549 """Attribute : ID AttributeValue""" 550 p[0] = Attribute(locFromTok(p, 1), p[1], p[2]) 551 552 553def p_AttributeValue(p): 554 """AttributeValue : '=' ID 555 |""" 556 if 1 == len(p): 557 p[0] = None 558 else: 559 p[0] = p[2] 560 561 562# -------------------- 563# Minor stuff 564def p_Nested(p): 565 """Nested : ID""" 566 kinds = {"not": 1, "inside_sync": 2, "inside_cpow": 3} 567 if p[1] not in kinds: 568 _error( 569 locFromTok(p, 1), "Expected not, inside_sync, or inside_cpow for nested()" 570 ) 571 572 p[0] = {"nested": kinds[p[1]]} 573 574 575def p_SendSemantics(p): 576 """SendSemantics : ASYNC 577 | SYNC 578 | INTR""" 579 if p[1] == "async": 580 p[0] = ASYNC 581 elif p[1] == "sync": 582 p[0] = SYNC 583 else: 584 assert p[1] == "intr" 585 p[0] = INTR 586 587 588def p_OptionalProtocolSendSemanticsQual(p): 589 """OptionalProtocolSendSemanticsQual : ProtocolSendSemanticsQual 590 |""" 591 if 2 == len(p): 592 p[0] = p[1] 593 else: 594 p[0] = [NOT_NESTED, ASYNC] 595 596 597def p_ProtocolSendSemanticsQual(p): 598 """ProtocolSendSemanticsQual : ASYNC 599 | SYNC 600 | NESTED '(' UPTO Nested ')' ASYNC 601 | NESTED '(' UPTO Nested ')' SYNC 602 | INTR""" 603 if p[1] == "nested": 604 mtype = p[6] 605 nested = p[4] 606 else: 607 mtype = p[1] 608 nested = NOT_NESTED 609 610 if mtype == "async": 611 mtype = ASYNC 612 elif mtype == "sync": 613 mtype = SYNC 614 elif mtype == "intr": 615 mtype = INTR 616 else: 617 assert 0 618 619 p[0] = [nested, mtype] 620 621 622def p_ParamList(p): 623 """ParamList : ParamList ',' Param 624 | Param 625 |""" 626 if 1 == len(p): 627 p[0] = [] 628 elif 2 == len(p): 629 p[0] = [p[1]] 630 else: 631 p[1].append(p[3]) 632 p[0] = p[1] 633 634 635def p_Param(p): 636 """Param : Attributes Type ID""" 637 p[0] = Param(locFromTok(p, 2), p[2], p[3], p[1]) 638 639 640def p_Type(p): 641 """Type : MaybeNullable BasicType""" 642 # only actor types are nullable; we check this in the type checker 643 p[2].nullable = p[1] 644 p[0] = p[2] 645 646 647def p_BasicType(p): 648 """BasicType : CxxID 649 | CxxID '[' ']' 650 | CxxID '?' 651 | CxxUniquePtrInst""" 652 # ID == CxxType; we forbid qnames here, 653 # in favor of the |using| declaration 654 if not isinstance(p[1], TypeSpec): 655 assert (len(p[1]) == 2) or (len(p[1]) == 3) 656 if 2 == len(p[1]): 657 # p[1] is CxxID. isunique = False 658 p[1] = p[1] + (False,) 659 loc, id, isunique = p[1] 660 p[1] = TypeSpec(loc, QualifiedId(loc, id)) 661 p[1].uniqueptr = isunique 662 if 4 == len(p): 663 p[1].array = True 664 if 3 == len(p): 665 p[1].maybe = True 666 p[0] = p[1] 667 668 669def p_MaybeNullable(p): 670 """MaybeNullable : NULLABLE 671 |""" 672 p[0] = 2 == len(p) 673 674 675# -------------------- 676# C++ stuff 677 678 679def p_CxxType(p): 680 """CxxType : QualifiedID 681 | CxxID""" 682 if isinstance(p[1], QualifiedId): 683 p[0] = TypeSpec(p[1].loc, p[1]) 684 else: 685 loc, id = p[1] 686 p[0] = TypeSpec(loc, QualifiedId(loc, id)) 687 688 689def p_QualifiedID(p): 690 """QualifiedID : QualifiedID COLONCOLON CxxID 691 | CxxID COLONCOLON CxxID""" 692 if isinstance(p[1], QualifiedId): 693 loc, id = p[3] 694 p[1].qualify(id) 695 p[0] = p[1] 696 else: 697 loc1, id1 = p[1] 698 _, id2 = p[3] 699 p[0] = QualifiedId(loc1, id2, [id1]) 700 701 702def p_CxxID(p): 703 """CxxID : ID 704 | CxxTemplateInst""" 705 if isinstance(p[1], tuple): 706 p[0] = p[1] 707 else: 708 p[0] = (locFromTok(p, 1), str(p[1])) 709 710 711def p_CxxTemplateInst(p): 712 """CxxTemplateInst : ID '<' ID '>'""" 713 p[0] = (locFromTok(p, 1), str(p[1]) + "<" + str(p[3]) + ">") 714 715 716def p_CxxUniquePtrInst(p): 717 """CxxUniquePtrInst : UNIQUEPTR '<' ID '>'""" 718 p[0] = (locFromTok(p, 1), str(p[3]), True) 719 720 721def p_error(t): 722 lineno, value = _safeLinenoValue(t) 723 _error(Loc(Parser.current.filename, lineno), "bad syntax near `%s'", value) 724