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