1#!/usr/bin/env python 2# xpidl.py - A parser for cross-platform IDL (XPIDL) files. 3# 4# This Source Code Form is subject to the terms of the Mozilla Public 5# License, v. 2.0. If a copy of the MPL was not distributed with this 6# file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 8"""A parser for cross-platform IDL (XPIDL) files.""" 9 10# Note that this file is used by the searchfox indexer in ways that are 11# not tested in Firefox's CI. Please try to keep this file py2-compatible 12# if the burden for that is low. If you are making changes you know to be 13# incompatible with py2, please give a searchfox maintainer a heads-up so 14# that any necessary changes can be made on the searchfox side. 15 16from __future__ import absolute_import 17from __future__ import print_function 18 19import sys 20import os.path 21import re 22from ply import lex 23from ply import yacc 24import six 25from collections import namedtuple 26 27"""A type conforms to the following pattern: 28 29 def nativeType(self, calltype): 30 'returns a string representation of the native type 31 calltype must be 'in', 'out', 'inout', or 'element' 32 33Interface members const/method/attribute conform to the following pattern: 34 35 name = 'string' 36 37 def toIDL(self): 38 'returns the member signature as IDL' 39""" 40 41 42# XXX(nika): Fix the IDL files which do this so we can remove this list? 43def rustPreventForward(s): 44 """These types are foward declared as interfaces, but never actually defined 45 in IDL files. We don't want to generate references to them in rust for that 46 reason.""" 47 return s in ( 48 "nsIFrame", 49 "nsSubDocumentFrame", 50 ) 51 52 53def attlistToIDL(attlist): 54 if len(attlist) == 0: 55 return "" 56 57 attlist = list(attlist) 58 attlist.sort(key=lambda a: a[0]) 59 60 return "[%s] " % ",".join( 61 [ 62 "%s%s" % (name, value is not None and "(%s)" % value or "") 63 for name, value, aloc in attlist 64 ] 65 ) 66 67 68_paramsHardcode = { 69 2: ("array", "shared", "iid_is", "size_is", "retval"), 70 3: ("array", "size_is", "const"), 71} 72 73 74def paramAttlistToIDL(attlist): 75 if len(attlist) == 0: 76 return "" 77 78 # Hack alert: g_hash_table_foreach is pretty much unimitatable... hardcode 79 # quirk 80 attlist = list(attlist) 81 sorted = [] 82 if len(attlist) in _paramsHardcode: 83 for p in _paramsHardcode[len(attlist)]: 84 i = 0 85 while i < len(attlist): 86 if attlist[i][0] == p: 87 sorted.append(attlist[i]) 88 del attlist[i] 89 continue 90 91 i += 1 92 93 sorted.extend(attlist) 94 95 return "[%s] " % ", ".join( 96 [ 97 "%s%s" % (name, value is not None and " (%s)" % value or "") 98 for name, value, aloc in sorted 99 ] 100 ) 101 102 103def unaliasType(t): 104 while t.kind == "typedef": 105 t = t.realtype 106 assert t is not None 107 return t 108 109 110def getBuiltinOrNativeTypeName(t): 111 t = unaliasType(t) 112 if t.kind == "builtin": 113 return t.name 114 elif t.kind == "native": 115 assert t.specialtype is not None 116 return "[%s]" % t.specialtype 117 else: 118 return None 119 120 121class BuiltinLocation(object): 122 def get(self): 123 return "<builtin type>" 124 125 def __str__(self): 126 return self.get() 127 128 129class Builtin(object): 130 kind = "builtin" 131 location = BuiltinLocation 132 133 def __init__(self, name, nativename, rustname, signed=False, maybeConst=False): 134 self.name = name 135 self.nativename = nativename 136 self.rustname = rustname 137 self.signed = signed 138 self.maybeConst = maybeConst 139 140 def isPointer(self): 141 """Check if this type is a pointer type - this will control how pointers act""" 142 return self.nativename.endswith("*") 143 144 def nativeType(self, calltype, shared=False, const=False): 145 if self.name in ["string", "wstring"] and calltype == "element": 146 raise IDLError( 147 "Use string class types for string Array elements", self.location 148 ) 149 150 if const: 151 print( 152 IDLError( 153 "[const] doesn't make sense on builtin types.", 154 self.location, 155 warning=True, 156 ), 157 file=sys.stderr, 158 ) 159 const = "const " 160 elif calltype == "in" and self.isPointer(): 161 const = "const " 162 elif shared: 163 if not self.isPointer(): 164 raise IDLError( 165 "[shared] not applicable to non-pointer types.", self.location 166 ) 167 const = "const " 168 else: 169 const = "" 170 return "%s%s %s" % (const, self.nativename, "*" if "out" in calltype else "") 171 172 def rustType(self, calltype, shared=False, const=False): 173 # We want to rewrite any *mut pointers to *const pointers if constness 174 # was requested. 175 const = const or ("out" not in calltype and self.isPointer()) or shared 176 rustname = self.rustname 177 if const and self.isPointer(): 178 rustname = self.rustname.replace("*mut", "*const") 179 180 return "%s%s" % ("*mut " if "out" in calltype else "", rustname) 181 182 183builtinNames = [ 184 Builtin("boolean", "bool", "bool"), 185 Builtin("void", "void", "libc::c_void"), 186 Builtin("octet", "uint8_t", "u8", False, True), 187 Builtin("short", "int16_t", "i16", True, True), 188 Builtin("long", "int32_t", "i32", True, True), 189 Builtin("long long", "int64_t", "i64", True, True), 190 Builtin("unsigned short", "uint16_t", "u16", False, True), 191 Builtin("unsigned long", "uint32_t", "u32", False, True), 192 Builtin("unsigned long long", "uint64_t", "u64", False, True), 193 Builtin("float", "float", "libc::c_float", True, False), 194 Builtin("double", "double", "libc::c_double", True, False), 195 Builtin("char", "char", "libc::c_char", True, False), 196 Builtin("string", "char *", "*const libc::c_char", False, False), 197 Builtin("wchar", "char16_t", "i16", False, False), 198 Builtin("wstring", "char16_t *", "*const i16", False, False), 199] 200 201builtinMap = {} 202for b in builtinNames: 203 builtinMap[b.name] = b 204 205 206class Location(object): 207 _line = None 208 209 def __init__(self, lexer, lineno, lexpos): 210 self._lineno = lineno 211 self._lexpos = lexpos 212 self._lexdata = lexer.lexdata 213 self._file = getattr(lexer, "filename", "<unknown>") 214 215 def __eq__(self, other): 216 return self._lexpos == other._lexpos and self._file == other._file 217 218 def resolve(self): 219 if self._line: 220 return 221 222 startofline = self._lexdata.rfind("\n", 0, self._lexpos) + 1 223 endofline = self._lexdata.find("\n", self._lexpos, self._lexpos + 80) 224 self._line = self._lexdata[startofline:endofline] 225 self._colno = self._lexpos - startofline 226 227 def pointerline(self): 228 def i(): 229 for i in range(0, self._colno): 230 yield " " 231 yield "^" 232 233 return "".join(i()) 234 235 def get(self): 236 self.resolve() 237 return "%s line %s:%s" % (self._file, self._lineno, self._colno) 238 239 def __str__(self): 240 self.resolve() 241 return "%s line %s:%s\n%s\n%s" % ( 242 self._file, 243 self._lineno, 244 self._colno, 245 self._line, 246 self.pointerline(), 247 ) 248 249 250class NameMap(object): 251 """Map of name -> object. Each object must have a .name and .location property. 252 Setting the same name twice throws an error.""" 253 254 def __init__(self): 255 self._d = {} 256 257 def __getitem__(self, key): 258 if key in builtinMap: 259 return builtinMap[key] 260 return self._d[key] 261 262 def __iter__(self): 263 return six.itervalues(self._d) 264 265 def __contains__(self, key): 266 return key in builtinMap or key in self._d 267 268 def set(self, object): 269 if object.name in builtinMap: 270 raise IDLError( 271 "name '%s' is a builtin and cannot be redeclared" % (object.name), 272 object.location, 273 ) 274 if object.name.startswith("_"): 275 object.name = object.name[1:] 276 if object.name in self._d: 277 old = self._d[object.name] 278 if old == object: 279 return 280 if isinstance(old, Forward) and isinstance(object, Interface): 281 self._d[object.name] = object 282 elif isinstance(old, Interface) and isinstance(object, Forward): 283 pass 284 else: 285 raise IDLError( 286 "name '%s' specified twice. Previous location: %s" 287 % (object.name, self._d[object.name].location), 288 object.location, 289 ) 290 else: 291 self._d[object.name] = object 292 293 def get(self, id, location): 294 try: 295 return self[id] 296 except KeyError: 297 raise IDLError("Name '%s' not found", location) 298 299 300class RustNoncompat(Exception): 301 """ 302 This exception is raised when a particular type or function cannot be safely exposed to 303 rust code 304 """ 305 306 def __init__(self, reason): 307 self.reason = reason 308 309 def __str__(self): 310 return self.reason 311 312 313class IDLError(Exception): 314 def __init__(self, message, location, warning=False): 315 self.message = message 316 self.location = location 317 self.warning = warning 318 319 def __str__(self): 320 return "%s: %s, %s" % ( 321 self.warning and "warning" or "error", 322 self.message, 323 self.location, 324 ) 325 326 327class Include(object): 328 kind = "include" 329 330 def __init__(self, filename, location): 331 self.filename = filename 332 self.location = location 333 334 def __str__(self): 335 return "".join(["include '%s'\n" % self.filename]) 336 337 def resolve(self, parent): 338 def incfiles(): 339 yield self.filename 340 for dir in parent.incdirs: 341 yield os.path.join(dir, self.filename) 342 343 for file in incfiles(): 344 if not os.path.exists(file): 345 continue 346 347 if file in parent.includeCache: 348 self.IDL = parent.includeCache[file] 349 else: 350 self.IDL = parent.parser.parse( 351 open(file, encoding="utf-8").read(), filename=file 352 ) 353 self.IDL.resolve( 354 parent.incdirs, 355 parent.parser, 356 parent.webidlconfig, 357 parent.includeCache, 358 ) 359 parent.includeCache[file] = self.IDL 360 361 for type in self.IDL.getNames(): 362 parent.setName(type) 363 parent.deps.extend(self.IDL.deps) 364 return 365 366 raise IDLError("File '%s' not found" % self.filename, self.location) 367 368 369class IDL(object): 370 def __init__(self, productions): 371 self.hasSequence = False 372 self.productions = productions 373 self.deps = [] 374 375 def setName(self, object): 376 self.namemap.set(object) 377 378 def getName(self, id, location): 379 if id.name == "Array": 380 if id.params is None or len(id.params) != 1: 381 raise IDLError("Array takes exactly 1 parameter", location) 382 self.hasSequence = True 383 return Array(self.getName(id.params[0], location), location) 384 385 if id.params is not None: 386 raise IDLError("Generic type '%s' unrecognized" % id.name, location) 387 388 try: 389 return self.namemap[id.name] 390 except KeyError: 391 raise IDLError("type '%s' not found" % id.name, location) 392 393 def hasName(self, id): 394 return id in self.namemap 395 396 def getNames(self): 397 return iter(self.namemap) 398 399 def __str__(self): 400 return "".join([str(p) for p in self.productions]) 401 402 def resolve(self, incdirs, parser, webidlconfig, includeCache=None): 403 self.namemap = NameMap() 404 self.incdirs = incdirs 405 self.parser = parser 406 self.webidlconfig = webidlconfig 407 self.includeCache = {} if includeCache is None else includeCache 408 for p in self.productions: 409 p.resolve(self) 410 411 def includes(self): 412 for p in self.productions: 413 if p.kind == "include": 414 yield p 415 if self.hasSequence: 416 yield Include("nsTArray.h", BuiltinLocation) 417 418 def needsJSTypes(self): 419 for p in self.productions: 420 if p.kind == "interface" and p.needsJSTypes(): 421 return True 422 return False 423 424 425class CDATA(object): 426 kind = "cdata" 427 _re = re.compile(r"\n+") 428 429 def __init__(self, data, location): 430 self.data = self._re.sub("\n", data) 431 self.location = location 432 433 def resolve(self, parent): 434 pass 435 436 def __str__(self): 437 return "cdata: %s\n\t%r\n" % (self.location.get(), self.data) 438 439 def count(self): 440 return 0 441 442 443class Typedef(object): 444 kind = "typedef" 445 446 def __init__(self, type, name, location, doccomments): 447 self.type = type 448 self.name = name 449 self.location = location 450 self.doccomments = doccomments 451 452 def __eq__(self, other): 453 return self.name == other.name and self.type == other.type 454 455 def resolve(self, parent): 456 parent.setName(self) 457 self.realtype = parent.getName(self.type, self.location) 458 459 if not isinstance(self.realtype, (Builtin, CEnum, Native, Typedef)): 460 raise IDLError("Unsupported typedef target type", self.location) 461 462 def nativeType(self, calltype): 463 return "%s %s" % (self.name, "*" if "out" in calltype else "") 464 465 def rustType(self, calltype): 466 if self.name == "nsresult": 467 return "%s::nserror::nsresult" % ("*mut " if "out" in calltype else "") 468 469 return "%s%s" % ("*mut " if "out" in calltype else "", self.name) 470 471 def __str__(self): 472 return "typedef %s %s\n" % (self.type, self.name) 473 474 475class Forward(object): 476 kind = "forward" 477 478 def __init__(self, name, location, doccomments): 479 self.name = name 480 self.location = location 481 self.doccomments = doccomments 482 483 def __eq__(self, other): 484 return other.kind == "forward" and other.name == self.name 485 486 def resolve(self, parent): 487 # Hack alert: if an identifier is already present, move the doccomments 488 # forward. 489 if parent.hasName(self.name): 490 for i in range(0, len(parent.productions)): 491 if parent.productions[i] is self: 492 break 493 for i in range(i + 1, len(parent.productions)): 494 if hasattr(parent.productions[i], "doccomments"): 495 parent.productions[i].doccomments[0:0] = self.doccomments 496 break 497 498 parent.setName(self) 499 500 def nativeType(self, calltype): 501 if calltype == "element": 502 return "RefPtr<%s>" % self.name 503 return "%s *%s" % (self.name, "*" if "out" in calltype else "") 504 505 def rustType(self, calltype): 506 if rustPreventForward(self.name): 507 raise RustNoncompat("forward declaration %s is unsupported" % self.name) 508 if calltype == "element": 509 return "RefPtr<%s>" % self.name 510 return "%s*const %s" % ("*mut" if "out" in calltype else "", self.name) 511 512 def __str__(self): 513 return "forward-declared %s\n" % self.name 514 515 516class Native(object): 517 kind = "native" 518 519 modifier = None 520 specialtype = None 521 522 # A tuple type here means that a custom value is used for each calltype: 523 # (in, out/inout, array element) respectively. 524 # A `None` here means that the written type should be used as-is. 525 specialtypes = { 526 "nsid": None, 527 "utf8string": ("const nsACString&", "nsACString&", "nsCString"), 528 "cstring": ("const nsACString&", "nsACString&", "nsCString"), 529 "astring": ("const nsAString&", "nsAString&", "nsString"), 530 "jsval": ("JS::HandleValue", "JS::MutableHandleValue", "JS::Value"), 531 "promise": "::mozilla::dom::Promise", 532 } 533 534 def __init__(self, name, nativename, attlist, location): 535 self.name = name 536 self.nativename = nativename 537 self.location = location 538 539 for name, value, aloc in attlist: 540 if value is not None: 541 raise IDLError("Unexpected attribute value", aloc) 542 if name in ("ptr", "ref"): 543 if self.modifier is not None: 544 raise IDLError("More than one ptr/ref modifier", aloc) 545 self.modifier = name 546 elif name in self.specialtypes.keys(): 547 if self.specialtype is not None: 548 raise IDLError("More than one special type", aloc) 549 self.specialtype = name 550 if self.specialtypes[name] is not None: 551 self.nativename = self.specialtypes[name] 552 else: 553 raise IDLError("Unexpected attribute", aloc) 554 555 def __eq__(self, other): 556 return ( 557 self.name == other.name 558 and self.nativename == other.nativename 559 and self.modifier == other.modifier 560 and self.specialtype == other.specialtype 561 ) 562 563 def resolve(self, parent): 564 parent.setName(self) 565 566 def isPtr(self, calltype): 567 return self.modifier == "ptr" 568 569 def isRef(self, calltype): 570 return self.modifier == "ref" 571 572 def nativeType(self, calltype, const=False, shared=False): 573 if shared: 574 if calltype != "out": 575 raise IDLError( 576 "[shared] only applies to out parameters.", self.location 577 ) 578 const = True 579 580 if isinstance(self.nativename, tuple): 581 if calltype == "in": 582 return self.nativename[0] + " " 583 elif "out" in calltype: 584 return self.nativename[1] + " " 585 else: 586 return self.nativename[2] + " " 587 588 # 'in' nsid parameters should be made 'const' 589 if self.specialtype == "nsid" and calltype == "in": 590 const = True 591 592 if calltype == "element": 593 if self.specialtype == "nsid": 594 if self.isPtr(calltype): 595 raise IDLError( 596 "Array<nsIDPtr> not yet supported. " 597 "File an XPConnect bug if you need it.", 598 self.location, 599 ) 600 601 # ns[CI]?IDs should be held directly in Array<T>s 602 return self.nativename 603 604 if self.isRef(calltype): 605 raise IDLError( 606 "[ref] qualified type unsupported in Array<T>", self.location 607 ) 608 609 # Promises should be held in RefPtr<T> in Array<T>s 610 if self.specialtype == "promise": 611 return "RefPtr<mozilla::dom::Promise>" 612 613 if self.isRef(calltype): 614 m = "& " # [ref] is always passed with a single indirection 615 else: 616 m = "* " if "out" in calltype else "" 617 if self.isPtr(calltype): 618 m += "* " 619 return "%s%s %s" % (const and "const " or "", self.nativename, m) 620 621 def rustType(self, calltype, const=False, shared=False): 622 # For the most part, 'native' types don't make sense in rust, as they 623 # are native C++ types. However, we can support a few types here, as 624 # they're important. 625 # 626 # NOTE: This code doesn't try to perfectly match C++ constness, as 627 # constness doesn't affect ABI, and raw pointers are already unsafe. 628 629 if self.modifier not in ["ptr", "ref"]: 630 raise RustNoncompat("Rust only supports [ref] / [ptr] native types") 631 632 prefix = "*mut " if "out" in calltype else "*const " 633 if "out" in calltype and self.modifier == "ptr": 634 prefix += "*mut " 635 636 if self.specialtype == "nsid": 637 if "element" in calltype: 638 if self.isPtr(calltype): 639 raise IDLError( 640 "Array<nsIDPtr> not yet supported. " 641 "File an XPConnect bug if you need it.", 642 self.location, 643 ) 644 return self.nativename 645 return prefix + self.nativename 646 if self.specialtype in ["cstring", "utf8string"]: 647 if "element" in calltype: 648 return "::nsstring::nsCString" 649 return prefix + "::nsstring::nsACString" 650 if self.specialtype == "astring": 651 if "element" in calltype: 652 return "::nsstring::nsString" 653 return prefix + "::nsstring::nsAString" 654 if self.nativename == "void": 655 return prefix + "libc::c_void" 656 657 if self.specialtype: 658 raise RustNoncompat("specialtype %s unsupported" % self.specialtype) 659 raise RustNoncompat("native type %s unsupported" % self.nativename) 660 661 def __str__(self): 662 return "native %s(%s)\n" % (self.name, self.nativename) 663 664 665class WebIDL(object): 666 kind = "webidl" 667 668 def __init__(self, name, location): 669 self.name = name 670 self.location = location 671 672 def __eq__(self, other): 673 return other.kind == "webidl" and self.name == other.name 674 675 def resolve(self, parent): 676 # XXX(nika): We don't handle _every_ kind of webidl object here (as that 677 # would be hard). For example, we don't support nsIDOM*-defaulting 678 # interfaces. 679 # TODO: More explicit compile-time checks? 680 681 assert ( 682 parent.webidlconfig is not None 683 ), "WebIDL declarations require passing webidlconfig to resolve." 684 685 # Resolve our native name according to the WebIDL configs. 686 config = parent.webidlconfig.get(self.name, {}) 687 self.native = config.get("nativeType") 688 if self.native is None: 689 self.native = "mozilla::dom::%s" % self.name 690 self.headerFile = config.get("headerFile") 691 if self.headerFile is None: 692 self.headerFile = self.native.replace("::", "/") + ".h" 693 694 parent.setName(self) 695 696 def nativeType(self, calltype, const=False): 697 if calltype == "element": 698 return "RefPtr<%s%s>" % ("const " if const else "", self.native) 699 return "%s%s *%s" % ( 700 "const " if const else "", 701 self.native, 702 "*" if "out" in calltype else "", 703 ) 704 705 def rustType(self, calltype, const=False): 706 # Just expose the type as a void* - we can't do any better. 707 return "%s*const libc::c_void" % ("*mut " if "out" in calltype else "") 708 709 def __str__(self): 710 return "webidl %s\n" % self.name 711 712 713class Interface(object): 714 kind = "interface" 715 716 def __init__(self, name, attlist, base, members, location, doccomments): 717 self.name = name 718 self.attributes = InterfaceAttributes(attlist, location) 719 self.base = base 720 self.members = members 721 self.location = location 722 self.namemap = NameMap() 723 self.doccomments = doccomments 724 self.nativename = name 725 726 for m in members: 727 if not isinstance(m, CDATA): 728 self.namemap.set(m) 729 730 def __eq__(self, other): 731 return self.name == other.name and self.location == other.location 732 733 def resolve(self, parent): 734 self.idl = parent 735 736 # Hack alert: if an identifier is already present, libIDL assigns 737 # doc comments incorrectly. This is quirks-mode extraordinaire! 738 if parent.hasName(self.name): 739 for member in self.members: 740 if hasattr(member, "doccomments"): 741 member.doccomments[0:0] = self.doccomments 742 break 743 self.doccomments = parent.getName(TypeId(self.name), None).doccomments 744 745 if self.attributes.function: 746 has_method = False 747 for member in self.members: 748 if member.kind == "method": 749 if has_method: 750 raise IDLError( 751 "interface '%s' has multiple methods, but marked 'function'" 752 % self.name, 753 self.location, 754 ) 755 else: 756 has_method = True 757 758 parent.setName(self) 759 if self.base is not None: 760 realbase = parent.getName(TypeId(self.base), self.location) 761 if realbase.kind != "interface": 762 raise IDLError( 763 "interface '%s' inherits from non-interface type '%s'" 764 % (self.name, self.base), 765 self.location, 766 ) 767 768 if self.attributes.scriptable and not realbase.attributes.scriptable: 769 raise IDLError( 770 "interface '%s' is scriptable but derives from " 771 "non-scriptable '%s'" % (self.name, self.base), 772 self.location, 773 warning=True, 774 ) 775 776 if ( 777 self.attributes.scriptable 778 and realbase.attributes.builtinclass 779 and not self.attributes.builtinclass 780 ): 781 raise IDLError( 782 "interface '%s' is not builtinclass but derives from " 783 "builtinclass '%s'" % (self.name, self.base), 784 self.location, 785 ) 786 elif self.name != "nsISupports": 787 raise IDLError( 788 "Interface '%s' must inherit from nsISupports" % self.name, 789 self.location, 790 ) 791 792 for member in self.members: 793 member.resolve(self) 794 795 # The number 250 is NOT arbitrary; this number is the maximum number of 796 # stub entries defined in xpcom/reflect/xptcall/genstubs.pl 797 # Do not increase this value without increasing the number in that 798 # location, or you WILL cause otherwise unknown problems! 799 if self.countEntries() > 250 and not self.attributes.builtinclass: 800 raise IDLError( 801 "interface '%s' has too many entries" % self.name, self.location 802 ) 803 804 def nativeType(self, calltype, const=False): 805 if calltype == "element": 806 return "RefPtr<%s>" % self.name 807 return "%s%s *%s" % ( 808 "const " if const else "", 809 self.name, 810 "*" if "out" in calltype else "", 811 ) 812 813 def rustType(self, calltype, const=False): 814 if calltype == "element": 815 return "RefPtr<%s>" % self.name 816 return "%s*const %s" % ("*mut " if "out" in calltype else "", self.name) 817 818 def __str__(self): 819 l = ["interface %s\n" % self.name] 820 if self.base is not None: 821 l.append("\tbase %s\n" % self.base) 822 l.append(str(self.attributes)) 823 if self.members is None: 824 l.append("\tincomplete type\n") 825 else: 826 for m in self.members: 827 l.append(str(m)) 828 return "".join(l) 829 830 def getConst(self, name, location): 831 # The constant may be in a base class 832 iface = self 833 while name not in iface.namemap and iface.base is not None: 834 iface = self.idl.getName(TypeId(iface.base), self.location) 835 if name not in iface.namemap: 836 raise IDLError("cannot find symbol '%s'" % name, location) 837 c = iface.namemap.get(name, location) 838 if c.kind != "const": 839 raise IDLError("symbol '%s' is not a constant" % name, location) 840 841 return c.getValue() 842 843 def needsJSTypes(self): 844 for m in self.members: 845 if m.kind == "attribute" and m.type == TypeId("jsval"): 846 return True 847 if m.kind == "method" and m.needsJSTypes(): 848 return True 849 return False 850 851 def countEntries(self): 852 """Returns the number of entries in the vtable for this interface.""" 853 total = sum(member.count() for member in self.members) 854 if self.base is not None: 855 realbase = self.idl.getName(TypeId(self.base), self.location) 856 total += realbase.countEntries() 857 return total 858 859 860class InterfaceAttributes(object): 861 uuid = None 862 scriptable = False 863 builtinclass = False 864 function = False 865 noscript = False 866 main_process_scriptable_only = False 867 868 def setuuid(self, value): 869 self.uuid = value.lower() 870 871 def setscriptable(self): 872 self.scriptable = True 873 874 def setfunction(self): 875 self.function = True 876 877 def setnoscript(self): 878 self.noscript = True 879 880 def setbuiltinclass(self): 881 self.builtinclass = True 882 883 def setmain_process_scriptable_only(self): 884 self.main_process_scriptable_only = True 885 886 actions = { 887 "uuid": (True, setuuid), 888 "scriptable": (False, setscriptable), 889 "builtinclass": (False, setbuiltinclass), 890 "function": (False, setfunction), 891 "noscript": (False, setnoscript), 892 "object": (False, lambda self: True), 893 "main_process_scriptable_only": (False, setmain_process_scriptable_only), 894 } 895 896 def __init__(self, attlist, location): 897 def badattribute(self): 898 raise IDLError("Unexpected interface attribute '%s'" % name, location) 899 900 for name, val, aloc in attlist: 901 hasval, action = self.actions.get(name, (False, badattribute)) 902 if hasval: 903 if val is None: 904 raise IDLError("Expected value for attribute '%s'" % name, aloc) 905 906 action(self, val) 907 else: 908 if val is not None: 909 raise IDLError("Unexpected value for attribute '%s'" % name, aloc) 910 911 action(self) 912 913 if self.uuid is None: 914 raise IDLError("interface has no uuid", location) 915 916 def __str__(self): 917 l = [] 918 if self.uuid: 919 l.append("\tuuid: %s\n" % self.uuid) 920 if self.scriptable: 921 l.append("\tscriptable\n") 922 if self.builtinclass: 923 l.append("\tbuiltinclass\n") 924 if self.function: 925 l.append("\tfunction\n") 926 if self.main_process_scriptable_only: 927 l.append("\tmain_process_scriptable_only\n") 928 return "".join(l) 929 930 931class ConstMember(object): 932 kind = "const" 933 934 def __init__(self, type, name, value, location, doccomments): 935 self.type = type 936 self.name = name 937 self.valueFn = value 938 self.location = location 939 self.doccomments = doccomments 940 941 def resolve(self, parent): 942 self.realtype = parent.idl.getName(self.type, self.location) 943 self.iface = parent 944 basetype = self.realtype 945 while isinstance(basetype, Typedef): 946 basetype = basetype.realtype 947 if not isinstance(basetype, Builtin) or not basetype.maybeConst: 948 raise IDLError( 949 "const may only be a short or long type, not %s" % self.type, 950 self.location, 951 ) 952 953 self.basetype = basetype 954 # Value is a lambda. Resolve it. 955 self.value = self.valueFn(self.iface) 956 957 min_val = -(2 ** 31) if basetype.signed else 0 958 max_val = 2 ** 31 - 1 if basetype.signed else 2 ** 32 - 1 959 if self.value < min_val or self.value > max_val: 960 raise IDLError( 961 "xpidl constants must fit within %s" 962 % ("int32_t" if basetype.signed else "uint32_t"), 963 self.location, 964 ) 965 966 def getValue(self): 967 return self.value 968 969 def __str__(self): 970 return "\tconst %s %s = %s\n" % (self.type, self.name, self.getValue()) 971 972 def count(self): 973 return 0 974 975 976# Represents a single name/value pair in a CEnum 977class CEnumVariant(object): 978 # Treat CEnumVariants as consts in terms of value resolution, so we can 979 # do things like binary operation values for enum members. 980 kind = "const" 981 982 def __init__(self, name, value, location): 983 self.name = name 984 self.valueFn = value 985 self.location = location 986 987 def getValue(self): 988 return self.value 989 990 991class CEnum(object): 992 kind = "cenum" 993 994 def __init__(self, width, name, variants, location, doccomments): 995 # We have to set a name here, otherwise we won't pass namemap checks on 996 # the interface. This name will change it in resolve(), in order to 997 # namespace the enum within the interface. 998 self.name = name 999 self.basename = name 1000 self.width = width 1001 self.location = location 1002 self.namemap = NameMap() 1003 self.doccomments = doccomments 1004 self.variants = variants 1005 if self.width not in (8, 16, 32): 1006 raise IDLError("Width must be one of {8, 16, 32}", self.location) 1007 1008 def resolve(self, iface): 1009 self.iface = iface 1010 # Renaming enum to faux-namespace the enum type to the interface in JS 1011 # so we don't collide in the global namespace. Hacky/ugly but it does 1012 # the job well enough, and the name will still be interface::variant in 1013 # C++. 1014 self.name = "%s_%s" % (self.iface.name, self.basename) 1015 self.iface.idl.setName(self) 1016 1017 # Compute the value for each enum variant that doesn't set its own 1018 # value 1019 next_value = 0 1020 for variant in self.variants: 1021 # CEnum variants resolve to interface level consts in javascript, 1022 # meaning their names could collide with other interface members. 1023 # Iterate through all CEnum variants to make sure there are no 1024 # collisions. 1025 self.iface.namemap.set(variant) 1026 # Value may be a lambda. If it is, resolve it. 1027 if variant.valueFn: 1028 next_value = variant.value = variant.valueFn(self.iface) 1029 else: 1030 variant.value = next_value 1031 next_value += 1 1032 1033 def count(self): 1034 return 0 1035 1036 def nativeType(self, calltype): 1037 if "out" in calltype: 1038 return "%s::%s *" % (self.iface.name, self.basename) 1039 return "%s::%s " % (self.iface.name, self.basename) 1040 1041 def rustType(self, calltype): 1042 return "%s u%d" % ("*mut" if "out" in calltype else "", self.width) 1043 1044 def __str__(self): 1045 body = ", ".join("%s = %s" % v for v in self.variants) 1046 return "\tcenum %s : %d { %s };\n" % (self.name, self.width, body) 1047 1048 1049# Infallible doesn't work for all return types. 1050# 1051# It also must be implemented on a builtinclass (otherwise it'd be unsound as 1052# it could be implemented by JS). 1053def ensureInfallibleIsSound(methodOrAttribute): 1054 if not methodOrAttribute.infallible: 1055 return 1056 if methodOrAttribute.realtype.kind not in [ 1057 "builtin", 1058 "interface", 1059 "forward", 1060 "webidl", 1061 "cenum", 1062 ]: 1063 raise IDLError( 1064 "[infallible] only works on interfaces, domobjects, and builtin types " 1065 "(numbers, booleans, cenum, and raw char types)", 1066 methodOrAttribute.location, 1067 ) 1068 if not methodOrAttribute.iface.attributes.builtinclass: 1069 raise IDLError( 1070 "[infallible] attributes and methods are only allowed on " 1071 "[builtinclass] interfaces", 1072 methodOrAttribute.location, 1073 ) 1074 1075 if methodOrAttribute.notxpcom: 1076 raise IDLError( 1077 "[infallible] does not make sense for a [notxpcom] " "method or attribute", 1078 methodOrAttribute.location, 1079 ) 1080 1081 1082# An interface cannot be implemented by JS if it has a notxpcom or nostdcall 1083# method or attribute, so it must be marked as builtinclass. 1084def ensureBuiltinClassIfNeeded(methodOrAttribute): 1085 iface = methodOrAttribute.iface 1086 if not iface.attributes.scriptable or iface.attributes.builtinclass: 1087 return 1088 if iface.name == "nsISupports": 1089 return 1090 if methodOrAttribute.notxpcom: 1091 raise IDLError( 1092 ( 1093 "scriptable interface '%s' must be marked [builtinclass] because it " 1094 "contains a [notxpcom] %s '%s'" 1095 ) 1096 % (iface.name, methodOrAttribute.kind, methodOrAttribute.name), 1097 methodOrAttribute.location, 1098 ) 1099 if methodOrAttribute.nostdcall: 1100 raise IDLError( 1101 ( 1102 "scriptable interface '%s' must be marked [builtinclass] because it " 1103 "contains a [nostdcall] %s '%s'" 1104 ) 1105 % (iface.name, methodOrAttribute.kind, methodOrAttribute.name), 1106 methodOrAttribute.location, 1107 ) 1108 1109 1110class Attribute(object): 1111 kind = "attribute" 1112 noscript = False 1113 notxpcom = False 1114 readonly = False 1115 symbol = False 1116 implicit_jscontext = False 1117 nostdcall = False 1118 must_use = False 1119 binaryname = None 1120 infallible = False 1121 # explicit_setter_can_run_script is true if the attribute is explicitly 1122 # annotated as having a setter that can cause script to run. 1123 explicit_setter_can_run_script = False 1124 # explicit_getter_can_run_script is true if the attribute is explicitly 1125 # annotated as having a getter that can cause script to run. 1126 explicit_getter_can_run_script = False 1127 1128 def __init__(self, type, name, attlist, readonly, location, doccomments): 1129 self.type = type 1130 self.name = name 1131 self.attlist = attlist 1132 self.readonly = readonly 1133 self.location = location 1134 self.doccomments = doccomments 1135 1136 for name, value, aloc in attlist: 1137 if name == "binaryname": 1138 if value is None: 1139 raise IDLError("binaryname attribute requires a value", aloc) 1140 1141 self.binaryname = value 1142 continue 1143 1144 if value is not None: 1145 raise IDLError("Unexpected attribute value", aloc) 1146 1147 if name == "noscript": 1148 self.noscript = True 1149 elif name == "notxpcom": 1150 self.notxpcom = True 1151 elif name == "symbol": 1152 self.symbol = True 1153 elif name == "implicit_jscontext": 1154 self.implicit_jscontext = True 1155 elif name == "nostdcall": 1156 self.nostdcall = True 1157 elif name == "must_use": 1158 self.must_use = True 1159 elif name == "infallible": 1160 self.infallible = True 1161 elif name == "can_run_script": 1162 if ( 1163 self.explicit_setter_can_run_script 1164 or self.explicit_getter_can_run_script 1165 ): 1166 raise IDLError( 1167 "Redundant getter_can_run_script or " 1168 "setter_can_run_script annotation on " 1169 "attribute", 1170 aloc, 1171 ) 1172 self.explicit_setter_can_run_script = True 1173 self.explicit_getter_can_run_script = True 1174 elif name == "setter_can_run_script": 1175 if self.explicit_setter_can_run_script: 1176 raise IDLError( 1177 "Redundant setter_can_run_script annotation " "on attribute", 1178 aloc, 1179 ) 1180 self.explicit_setter_can_run_script = True 1181 elif name == "getter_can_run_script": 1182 if self.explicit_getter_can_run_script: 1183 raise IDLError( 1184 "Redundant getter_can_run_script annotation " "on attribute", 1185 aloc, 1186 ) 1187 self.explicit_getter_can_run_script = True 1188 else: 1189 raise IDLError("Unexpected attribute '%s'" % name, aloc) 1190 1191 def resolve(self, iface): 1192 self.iface = iface 1193 self.realtype = iface.idl.getName(self.type, self.location) 1194 1195 ensureInfallibleIsSound(self) 1196 ensureBuiltinClassIfNeeded(self) 1197 1198 def toIDL(self): 1199 attribs = attlistToIDL(self.attlist) 1200 readonly = self.readonly and "readonly " or "" 1201 return "%s%sattribute %s %s;" % (attribs, readonly, self.type, self.name) 1202 1203 def isScriptable(self): 1204 if not self.iface.attributes.scriptable: 1205 return False 1206 return not (self.noscript or self.notxpcom or self.nostdcall) 1207 1208 def __str__(self): 1209 return "\t%sattribute %s %s\n" % ( 1210 self.readonly and "readonly " or "", 1211 self.type, 1212 self.name, 1213 ) 1214 1215 def count(self): 1216 return self.readonly and 1 or 2 1217 1218 1219class Method(object): 1220 kind = "method" 1221 noscript = False 1222 notxpcom = False 1223 symbol = False 1224 binaryname = None 1225 implicit_jscontext = False 1226 nostdcall = False 1227 must_use = False 1228 optional_argc = False 1229 # explicit_can_run_script is true if the method is explicitly annotated 1230 # as being able to cause script to run. 1231 explicit_can_run_script = False 1232 infallible = False 1233 1234 def __init__(self, type, name, attlist, paramlist, location, doccomments, raises): 1235 self.type = type 1236 self.name = name 1237 self.attlist = attlist 1238 self.params = paramlist 1239 self.location = location 1240 self.doccomments = doccomments 1241 self.raises = raises 1242 1243 for name, value, aloc in attlist: 1244 if name == "binaryname": 1245 if value is None: 1246 raise IDLError("binaryname attribute requires a value", aloc) 1247 1248 self.binaryname = value 1249 continue 1250 1251 if value is not None: 1252 raise IDLError("Unexpected attribute value", aloc) 1253 1254 if name == "noscript": 1255 self.noscript = True 1256 elif name == "notxpcom": 1257 self.notxpcom = True 1258 elif name == "symbol": 1259 self.symbol = True 1260 elif name == "implicit_jscontext": 1261 self.implicit_jscontext = True 1262 elif name == "optional_argc": 1263 self.optional_argc = True 1264 elif name == "nostdcall": 1265 self.nostdcall = True 1266 elif name == "must_use": 1267 self.must_use = True 1268 elif name == "can_run_script": 1269 self.explicit_can_run_script = True 1270 elif name == "infallible": 1271 self.infallible = True 1272 else: 1273 raise IDLError("Unexpected attribute '%s'" % name, aloc) 1274 1275 self.namemap = NameMap() 1276 for p in paramlist: 1277 self.namemap.set(p) 1278 1279 def resolve(self, iface): 1280 self.iface = iface 1281 self.realtype = self.iface.idl.getName(self.type, self.location) 1282 1283 ensureInfallibleIsSound(self) 1284 ensureBuiltinClassIfNeeded(self) 1285 1286 for p in self.params: 1287 p.resolve(self) 1288 for p in self.params: 1289 if p.retval and p != self.params[-1]: 1290 raise IDLError( 1291 "'retval' parameter '%s' is not the last parameter" % p.name, 1292 self.location, 1293 ) 1294 if p.size_is: 1295 found_size_param = False 1296 for size_param in self.params: 1297 if p.size_is == size_param.name: 1298 found_size_param = True 1299 if ( 1300 getBuiltinOrNativeTypeName(size_param.realtype) 1301 != "unsigned long" 1302 ): 1303 raise IDLError( 1304 "is_size parameter must have type 'unsigned long'", 1305 self.location, 1306 ) 1307 if not found_size_param: 1308 raise IDLError( 1309 "could not find is_size parameter '%s'" % p.size_is, 1310 self.location, 1311 ) 1312 1313 def isScriptable(self): 1314 if not self.iface.attributes.scriptable: 1315 return False 1316 return not (self.noscript or self.notxpcom or self.nostdcall) 1317 1318 def __str__(self): 1319 return "\t%s %s(%s)\n" % ( 1320 self.type, 1321 self.name, 1322 ", ".join([p.name for p in self.params]), 1323 ) 1324 1325 def toIDL(self): 1326 if len(self.raises): 1327 raises = " raises (%s)" % ",".join(self.raises) 1328 else: 1329 raises = "" 1330 1331 return "%s%s %s (%s)%s;" % ( 1332 attlistToIDL(self.attlist), 1333 self.type, 1334 self.name, 1335 ", ".join([p.toIDL() for p in self.params]), 1336 raises, 1337 ) 1338 1339 def needsJSTypes(self): 1340 if self.implicit_jscontext: 1341 return True 1342 if self.type == TypeId("jsval"): 1343 return True 1344 for p in self.params: 1345 t = p.realtype 1346 if isinstance(t, Native) and t.specialtype == "jsval": 1347 return True 1348 return False 1349 1350 def count(self): 1351 return 1 1352 1353 1354class Param(object): 1355 size_is = None 1356 iid_is = None 1357 const = False 1358 array = False 1359 retval = False 1360 shared = False 1361 optional = False 1362 default_value = None 1363 1364 def __init__(self, paramtype, type, name, attlist, location, realtype=None): 1365 self.paramtype = paramtype 1366 self.type = type 1367 self.name = name 1368 self.attlist = attlist 1369 self.location = location 1370 self.realtype = realtype 1371 1372 for name, value, aloc in attlist: 1373 # Put the value-taking attributes first! 1374 if name == "size_is": 1375 if value is None: 1376 raise IDLError("'size_is' must specify a parameter", aloc) 1377 self.size_is = value 1378 elif name == "iid_is": 1379 if value is None: 1380 raise IDLError("'iid_is' must specify a parameter", aloc) 1381 self.iid_is = value 1382 elif name == "default": 1383 if value is None: 1384 raise IDLError("'default' must specify a default value", aloc) 1385 self.default_value = value 1386 else: 1387 if value is not None: 1388 raise IDLError("Unexpected value for attribute '%s'" % name, aloc) 1389 1390 if name == "const": 1391 self.const = True 1392 elif name == "array": 1393 self.array = True 1394 elif name == "retval": 1395 self.retval = True 1396 elif name == "shared": 1397 self.shared = True 1398 elif name == "optional": 1399 self.optional = True 1400 else: 1401 raise IDLError("Unexpected attribute '%s'" % name, aloc) 1402 1403 def resolve(self, method): 1404 self.realtype = method.iface.idl.getName(self.type, self.location) 1405 if self.array: 1406 self.realtype = LegacyArray(self.realtype) 1407 1408 def nativeType(self): 1409 kwargs = {} 1410 if self.shared: 1411 kwargs["shared"] = True 1412 if self.const: 1413 kwargs["const"] = True 1414 1415 try: 1416 return self.realtype.nativeType(self.paramtype, **kwargs) 1417 except IDLError as e: 1418 raise IDLError(str(e), self.location) 1419 except TypeError: 1420 raise IDLError("Unexpected parameter attribute", self.location) 1421 1422 def rustType(self): 1423 kwargs = {} 1424 if self.shared: 1425 kwargs["shared"] = True 1426 if self.const: 1427 kwargs["const"] = True 1428 1429 try: 1430 return self.realtype.rustType(self.paramtype, **kwargs) 1431 except IDLError as e: 1432 raise IDLError(str(e), self.location) 1433 except TypeError: 1434 raise IDLError("Unexpected parameter attribute", self.location) 1435 1436 def toIDL(self): 1437 return "%s%s %s %s" % ( 1438 paramAttlistToIDL(self.attlist), 1439 self.paramtype, 1440 self.type, 1441 self.name, 1442 ) 1443 1444 1445class LegacyArray(object): 1446 def __init__(self, basetype): 1447 self.type = basetype 1448 self.location = self.type.location 1449 1450 def nativeType(self, calltype, const=False): 1451 if "element" in calltype: 1452 raise IDLError("nested [array] unsupported", self.location) 1453 1454 # For legacy reasons, we have to add a 'const ' to builtin pointer array 1455 # types. (`[array] in string` and `[array] in wstring` parameters) 1456 if ( 1457 calltype == "in" 1458 and isinstance(self.type, Builtin) 1459 and self.type.isPointer() 1460 ): 1461 const = True 1462 1463 return "%s%s*%s" % ( 1464 "const " if const else "", 1465 self.type.nativeType("legacyelement"), 1466 "*" if "out" in calltype else "", 1467 ) 1468 1469 def rustType(self, calltype, const=False): 1470 return "%s%s%s" % ( 1471 "*mut " if "out" in calltype else "", 1472 "*const " if const else "*mut ", 1473 self.type.rustType("legacyelement"), 1474 ) 1475 1476 1477class Array(object): 1478 kind = "array" 1479 1480 def __init__(self, type, location): 1481 self.type = type 1482 self.location = location 1483 1484 @property 1485 def name(self): 1486 return "Array<%s>" % self.type.name 1487 1488 def resolve(self, idl): 1489 idl.getName(self.type, self.location) 1490 1491 def nativeType(self, calltype): 1492 if calltype == "legacyelement": 1493 raise IDLError("[array] Array<T> is unsupported", self.location) 1494 1495 base = "nsTArray<%s>" % self.type.nativeType("element") 1496 if "out" in calltype: 1497 return "%s& " % base 1498 elif "in" == calltype: 1499 return "const %s& " % base 1500 else: 1501 return base 1502 1503 def rustType(self, calltype): 1504 if calltype == "legacyelement": 1505 raise IDLError("[array] Array<T> is unsupported", self.location) 1506 1507 base = "thin_vec::ThinVec<%s>" % self.type.rustType("element") 1508 if "out" in calltype: 1509 return "*mut %s" % base 1510 elif "in" == calltype: 1511 return "*const %s" % base 1512 else: 1513 return base 1514 1515 1516TypeId = namedtuple("TypeId", "name params") 1517 1518 1519# Make str(TypeId) produce a nicer value 1520TypeId.__str__ = ( 1521 lambda self: "%s<%s>" % (self.name, ", ".join(str(p) for p in self.params)) 1522 if self.params is not None 1523 else self.name 1524) 1525 1526 1527# Allow skipping 'params' in TypeId(..) 1528TypeId.__new__.__defaults__ = (None,) 1529 1530 1531class IDLParser(object): 1532 keywords = { 1533 "cenum": "CENUM", 1534 "const": "CONST", 1535 "interface": "INTERFACE", 1536 "in": "IN", 1537 "inout": "INOUT", 1538 "out": "OUT", 1539 "attribute": "ATTRIBUTE", 1540 "raises": "RAISES", 1541 "readonly": "READONLY", 1542 "native": "NATIVE", 1543 "typedef": "TYPEDEF", 1544 "webidl": "WEBIDL", 1545 } 1546 1547 tokens = [ 1548 "IDENTIFIER", 1549 "CDATA", 1550 "INCLUDE", 1551 "IID", 1552 "NUMBER", 1553 "HEXNUM", 1554 "LSHIFT", 1555 "RSHIFT", 1556 "NATIVEID", 1557 ] 1558 1559 tokens.extend(keywords.values()) 1560 1561 states = (("nativeid", "exclusive"),) 1562 1563 hexchar = r"[a-fA-F0-9]" 1564 1565 t_NUMBER = r"-?\d+" 1566 t_HEXNUM = r"0x%s+" % hexchar 1567 t_LSHIFT = r"<<" 1568 t_RSHIFT = r">>" 1569 1570 literals = '"(){}[]<>,;:=|+-*' 1571 1572 t_ignore = " \t" 1573 1574 def t_multilinecomment(self, t): 1575 r"/\*(?s).*?\*/" 1576 t.lexer.lineno += t.value.count("\n") 1577 if t.value.startswith("/**"): 1578 self._doccomments.append(t.value) 1579 1580 def t_singlelinecomment(self, t): 1581 r"(?m)//.*?$" 1582 1583 def t_IID(self, t): 1584 return t 1585 1586 t_IID.__doc__ = r"%(c)s{8}-%(c)s{4}-%(c)s{4}-%(c)s{4}-%(c)s{12}" % {"c": hexchar} 1587 1588 def t_IDENTIFIER(self, t): 1589 r"(unsigned\ long\ long|unsigned\ short|unsigned\ long|long\ long)(?!_?[A-Za-z][A-Za-z_0-9])|_?[A-Za-z][A-Za-z_0-9]*" # NOQA: E501 1590 t.type = self.keywords.get(t.value, "IDENTIFIER") 1591 return t 1592 1593 def t_LCDATA(self, t): 1594 r"(?s)%\{[ ]*C\+\+[ ]*\n(?P<cdata>.*?\n?)%\}[ ]*(C\+\+)?" 1595 t.type = "CDATA" 1596 t.value = t.lexer.lexmatch.group("cdata") 1597 t.lexer.lineno += t.value.count("\n") 1598 return t 1599 1600 def t_INCLUDE(self, t): 1601 r'\#include[ \t]+"[^"\n]+"' 1602 inc, value, end = t.value.split('"') 1603 t.value = value 1604 return t 1605 1606 def t_directive(self, t): 1607 r"\#(?P<directive>[a-zA-Z]+)[^\n]+" 1608 raise IDLError( 1609 "Unrecognized directive %s" % t.lexer.lexmatch.group("directive"), 1610 Location( 1611 lexer=self.lexer, lineno=self.lexer.lineno, lexpos=self.lexer.lexpos 1612 ), 1613 ) 1614 1615 def t_newline(self, t): 1616 r"\n+" 1617 t.lexer.lineno += len(t.value) 1618 1619 def t_nativeid_NATIVEID(self, t): 1620 # Matches non-parenthesis characters, or a single open and closing 1621 # parenthesis with at least one non-parenthesis character before, 1622 # between and after them (for compatibility with std::function). 1623 r"[^()\n]+(?:\([^()\n]+\)[^()\n]+)?(?=\))" 1624 t.lexer.begin("INITIAL") 1625 return t 1626 1627 t_nativeid_ignore = "" 1628 1629 def t_ANY_error(self, t): 1630 raise IDLError( 1631 "unrecognized input", 1632 Location( 1633 lexer=self.lexer, lineno=self.lexer.lineno, lexpos=self.lexer.lexpos 1634 ), 1635 ) 1636 1637 precedence = ( 1638 ("left", "|"), 1639 ("left", "LSHIFT", "RSHIFT"), 1640 ("left", "+", "-"), 1641 ("left", "*"), 1642 ("left", "UMINUS"), 1643 ) 1644 1645 def p_idlfile(self, p): 1646 """idlfile : productions""" 1647 p[0] = IDL(p[1]) 1648 1649 def p_productions_start(self, p): 1650 """productions :""" 1651 p[0] = [] 1652 1653 def p_productions_cdata(self, p): 1654 """productions : CDATA productions""" 1655 p[0] = list(p[2]) 1656 p[0].insert(0, CDATA(p[1], self.getLocation(p, 1))) 1657 1658 def p_productions_include(self, p): 1659 """productions : INCLUDE productions""" 1660 p[0] = list(p[2]) 1661 p[0].insert(0, Include(p[1], self.getLocation(p, 1))) 1662 1663 def p_productions_interface(self, p): 1664 """productions : interface productions 1665 | typedef productions 1666 | native productions 1667 | webidl productions""" 1668 p[0] = list(p[2]) 1669 p[0].insert(0, p[1]) 1670 1671 def p_typedef(self, p): 1672 """typedef : TYPEDEF type IDENTIFIER ';'""" 1673 p[0] = Typedef( 1674 type=p[2], 1675 name=p[3], 1676 location=self.getLocation(p, 1), 1677 doccomments=p.slice[1].doccomments, 1678 ) 1679 1680 def p_native(self, p): 1681 """native : attributes NATIVE IDENTIFIER afternativeid '(' NATIVEID ')' ';'""" 1682 p[0] = Native( 1683 name=p[3], 1684 nativename=p[6], 1685 attlist=p[1]["attlist"], 1686 location=self.getLocation(p, 2), 1687 ) 1688 1689 def p_afternativeid(self, p): 1690 """afternativeid :""" 1691 # this is a place marker: we switch the lexer into literal identifier 1692 # mode here, to slurp up everything until the closeparen 1693 self.lexer.begin("nativeid") 1694 1695 def p_webidl(self, p): 1696 """webidl : WEBIDL IDENTIFIER ';'""" 1697 p[0] = WebIDL(name=p[2], location=self.getLocation(p, 2)) 1698 1699 def p_anyident(self, p): 1700 """anyident : IDENTIFIER 1701 | CONST""" 1702 p[0] = {"value": p[1], "location": self.getLocation(p, 1)} 1703 1704 def p_attributes(self, p): 1705 """attributes : '[' attlist ']' 1706 |""" 1707 if len(p) == 1: 1708 p[0] = {"attlist": []} 1709 else: 1710 p[0] = {"attlist": p[2], "doccomments": p.slice[1].doccomments} 1711 1712 def p_attlist_start(self, p): 1713 """attlist : attribute""" 1714 p[0] = [p[1]] 1715 1716 def p_attlist_continue(self, p): 1717 """attlist : attribute ',' attlist""" 1718 p[0] = list(p[3]) 1719 p[0].insert(0, p[1]) 1720 1721 def p_attribute(self, p): 1722 """attribute : anyident attributeval""" 1723 p[0] = (p[1]["value"], p[2], p[1]["location"]) 1724 1725 def p_attributeval(self, p): 1726 """attributeval : '(' IDENTIFIER ')' 1727 | '(' IID ')' 1728 |""" 1729 if len(p) > 1: 1730 p[0] = p[2] 1731 1732 def p_interface(self, p): 1733 """interface : attributes INTERFACE IDENTIFIER ifacebase ifacebody ';'""" 1734 atts, INTERFACE, name, base, body, SEMI = p[1:] 1735 attlist = atts["attlist"] 1736 doccomments = [] 1737 if "doccomments" in atts: 1738 doccomments.extend(atts["doccomments"]) 1739 doccomments.extend(p.slice[2].doccomments) 1740 1741 def loc(): 1742 return self.getLocation(p, 2) 1743 1744 if body is None: 1745 # forward-declared interface... must not have attributes! 1746 if len(attlist) != 0: 1747 raise IDLError( 1748 "Forward-declared interface must not have attributes", loc() 1749 ) 1750 1751 if base is not None: 1752 raise IDLError("Forward-declared interface must not have a base", loc()) 1753 p[0] = Forward(name=name, location=loc(), doccomments=doccomments) 1754 else: 1755 p[0] = Interface( 1756 name=name, 1757 attlist=attlist, 1758 base=base, 1759 members=body, 1760 location=loc(), 1761 doccomments=doccomments, 1762 ) 1763 1764 def p_ifacebody(self, p): 1765 """ifacebody : '{' members '}' 1766 |""" 1767 if len(p) > 1: 1768 p[0] = p[2] 1769 1770 def p_ifacebase(self, p): 1771 """ifacebase : ':' IDENTIFIER 1772 |""" 1773 if len(p) == 3: 1774 p[0] = p[2] 1775 1776 def p_members_start(self, p): 1777 """members :""" 1778 p[0] = [] 1779 1780 def p_members_continue(self, p): 1781 """members : member members""" 1782 p[0] = list(p[2]) 1783 p[0].insert(0, p[1]) 1784 1785 def p_member_cdata(self, p): 1786 """member : CDATA""" 1787 p[0] = CDATA(p[1], self.getLocation(p, 1)) 1788 1789 def p_member_const(self, p): 1790 """member : CONST type IDENTIFIER '=' number ';'""" 1791 p[0] = ConstMember( 1792 type=p[2], 1793 name=p[3], 1794 value=p[5], 1795 location=self.getLocation(p, 1), 1796 doccomments=p.slice[1].doccomments, 1797 ) 1798 1799 # All "number" products return a function(interface) 1800 1801 def p_number_decimal(self, p): 1802 """number : NUMBER""" 1803 n = int(p[1]) 1804 p[0] = lambda i: n 1805 1806 def p_number_hex(self, p): 1807 """number : HEXNUM""" 1808 n = int(p[1], 16) 1809 p[0] = lambda i: n 1810 1811 def p_number_identifier(self, p): 1812 """number : IDENTIFIER""" 1813 id = p[1] 1814 loc = self.getLocation(p, 1) 1815 p[0] = lambda i: i.getConst(id, loc) 1816 1817 def p_number_paren(self, p): 1818 """number : '(' number ')'""" 1819 p[0] = p[2] 1820 1821 def p_number_neg(self, p): 1822 """number : '-' number %prec UMINUS""" 1823 n = p[2] 1824 p[0] = lambda i: -n(i) 1825 1826 def p_number_add(self, p): 1827 """number : number '+' number 1828 | number '-' number 1829 | number '*' number""" 1830 n1 = p[1] 1831 n2 = p[3] 1832 if p[2] == "+": 1833 p[0] = lambda i: n1(i) + n2(i) 1834 elif p[2] == "-": 1835 p[0] = lambda i: n1(i) - n2(i) 1836 else: 1837 p[0] = lambda i: n1(i) * n2(i) 1838 1839 def p_number_shift(self, p): 1840 """number : number LSHIFT number 1841 | number RSHIFT number""" 1842 n1 = p[1] 1843 n2 = p[3] 1844 if p[2] == "<<": 1845 p[0] = lambda i: n1(i) << n2(i) 1846 else: 1847 p[0] = lambda i: n1(i) >> n2(i) 1848 1849 def p_number_bitor(self, p): 1850 """number : number '|' number""" 1851 n1 = p[1] 1852 n2 = p[3] 1853 p[0] = lambda i: n1(i) | n2(i) 1854 1855 def p_member_cenum(self, p): 1856 """member : CENUM IDENTIFIER ':' NUMBER '{' variants '}' ';'""" 1857 p[0] = CEnum( 1858 name=p[2], 1859 width=int(p[4]), 1860 variants=p[6], 1861 location=self.getLocation(p, 1), 1862 doccomments=p.slice[1].doccomments, 1863 ) 1864 1865 def p_variants_start(self, p): 1866 """variants :""" 1867 p[0] = [] 1868 1869 def p_variants_single(self, p): 1870 """variants : variant""" 1871 p[0] = [p[1]] 1872 1873 def p_variants_continue(self, p): 1874 """variants : variant ',' variants""" 1875 p[0] = [p[1]] + p[3] 1876 1877 def p_variant_implicit(self, p): 1878 """variant : IDENTIFIER""" 1879 p[0] = CEnumVariant(p[1], None, self.getLocation(p, 1)) 1880 1881 def p_variant_explicit(self, p): 1882 """variant : IDENTIFIER '=' number""" 1883 p[0] = CEnumVariant(p[1], p[3], self.getLocation(p, 1)) 1884 1885 def p_member_att(self, p): 1886 """member : attributes optreadonly ATTRIBUTE type IDENTIFIER ';'""" 1887 if "doccomments" in p[1]: 1888 doccomments = p[1]["doccomments"] 1889 elif p[2] is not None: 1890 doccomments = p[2] 1891 else: 1892 doccomments = p.slice[3].doccomments 1893 1894 p[0] = Attribute( 1895 type=p[4], 1896 name=p[5], 1897 attlist=p[1]["attlist"], 1898 readonly=p[2] is not None, 1899 location=self.getLocation(p, 3), 1900 doccomments=doccomments, 1901 ) 1902 1903 def p_member_method(self, p): 1904 """member : attributes type IDENTIFIER '(' paramlist ')' raises ';'""" 1905 if "doccomments" in p[1]: 1906 doccomments = p[1]["doccomments"] 1907 else: 1908 doccomments = p.slice[2].doccomments 1909 1910 p[0] = Method( 1911 type=p[2], 1912 name=p[3], 1913 attlist=p[1]["attlist"], 1914 paramlist=p[5], 1915 location=self.getLocation(p, 3), 1916 doccomments=doccomments, 1917 raises=p[7], 1918 ) 1919 1920 def p_paramlist(self, p): 1921 """paramlist : param moreparams 1922 |""" 1923 if len(p) == 1: 1924 p[0] = [] 1925 else: 1926 p[0] = list(p[2]) 1927 p[0].insert(0, p[1]) 1928 1929 def p_moreparams_start(self, p): 1930 """moreparams :""" 1931 p[0] = [] 1932 1933 def p_moreparams_continue(self, p): 1934 """moreparams : ',' param moreparams""" 1935 p[0] = list(p[3]) 1936 p[0].insert(0, p[2]) 1937 1938 def p_param(self, p): 1939 """param : attributes paramtype type IDENTIFIER""" 1940 p[0] = Param( 1941 paramtype=p[2], 1942 type=p[3], 1943 name=p[4], 1944 attlist=p[1]["attlist"], 1945 location=self.getLocation(p, 3), 1946 ) 1947 1948 def p_paramtype(self, p): 1949 """paramtype : IN 1950 | INOUT 1951 | OUT""" 1952 p[0] = p[1] 1953 1954 def p_optreadonly(self, p): 1955 """optreadonly : READONLY 1956 |""" 1957 if len(p) > 1: 1958 p[0] = p.slice[1].doccomments 1959 else: 1960 p[0] = None 1961 1962 def p_raises(self, p): 1963 """raises : RAISES '(' idlist ')' 1964 |""" 1965 if len(p) == 1: 1966 p[0] = [] 1967 else: 1968 p[0] = p[3] 1969 1970 def p_idlist(self, p): 1971 """idlist : IDENTIFIER""" 1972 p[0] = [p[1]] 1973 1974 def p_idlist_continue(self, p): 1975 """idlist : IDENTIFIER ',' idlist""" 1976 p[0] = list(p[3]) 1977 p[0].insert(0, p[1]) 1978 1979 def p_type_id(self, p): 1980 """type : IDENTIFIER""" 1981 p[0] = TypeId(name=p[1]) 1982 p.slice[0].doccomments = p.slice[1].doccomments 1983 1984 def p_type_generic(self, p): 1985 """type : IDENTIFIER '<' typelist '>'""" 1986 p[0] = TypeId(name=p[1], params=p[3]) 1987 p.slice[0].doccomments = p.slice[1].doccomments 1988 1989 def p_typelist(self, p): 1990 """typelist : type""" 1991 p[0] = [p[1]] 1992 1993 def p_typelist_continue(self, p): 1994 """typelist : type ',' typelist""" 1995 p[0] = list(p[3]) 1996 p[0].insert(0, p[1]) 1997 1998 def p_error(self, t): 1999 if not t: 2000 raise IDLError( 2001 "Syntax Error at end of file. Possibly due to missing semicolon(;), braces(}) " 2002 "or both", 2003 None, 2004 ) 2005 else: 2006 location = Location(self.lexer, t.lineno, t.lexpos) 2007 raise IDLError("invalid syntax", location) 2008 2009 def __init__(self): 2010 self._doccomments = [] 2011 self.lexer = lex.lex(object=self, debug=False) 2012 self.parser = yacc.yacc(module=self, write_tables=False, debug=False) 2013 2014 def clearComments(self): 2015 self._doccomments = [] 2016 2017 def token(self): 2018 t = self.lexer.token() 2019 if t is not None and t.type != "CDATA": 2020 t.doccomments = self._doccomments 2021 self._doccomments = [] 2022 return t 2023 2024 def parse(self, data, filename=None): 2025 if filename is not None: 2026 self.lexer.filename = filename 2027 self.lexer.lineno = 1 2028 self.lexer.input(data) 2029 idl = self.parser.parse(lexer=self) 2030 if filename is not None: 2031 idl.deps.append(filename) 2032 return idl 2033 2034 def getLocation(self, p, i): 2035 return Location(self.lexer, p.lineno(i), p.lexpos(i)) 2036 2037 2038if __name__ == "__main__": 2039 p = IDLParser() 2040 for f in sys.argv[1:]: 2041 print("Parsing %s" % f) 2042 p.parse(open(f, encoding="utf-8").read(), filename=f) 2043