summaryrefslogtreecommitdiffstats
path: root/xpcom/idl-parser
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--xpcom/idl-parser/setup.py18
-rw-r--r--xpcom/idl-parser/xpidl/__init__.py0
-rw-r--r--xpcom/idl-parser/xpidl/header.py723
-rw-r--r--xpcom/idl-parser/xpidl/jsonxpt.py282
-rw-r--r--xpcom/idl-parser/xpidl/moz.build11
-rw-r--r--xpcom/idl-parser/xpidl/python.toml4
-rwxr-xr-xxpcom/idl-parser/xpidl/runtests.py257
-rw-r--r--xpcom/idl-parser/xpidl/rust.py693
-rw-r--r--xpcom/idl-parser/xpidl/rust_macros.py111
-rwxr-xr-xxpcom/idl-parser/xpidl/xpidl.py2124
10 files changed, 4223 insertions, 0 deletions
diff --git a/xpcom/idl-parser/setup.py b/xpcom/idl-parser/setup.py
new file mode 100644
index 0000000000..af6b417cb7
--- /dev/null
+++ b/xpcom/idl-parser/setup.py
@@ -0,0 +1,18 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from setuptools import find_packages, setup
+
+setup(
+ name="xpidl",
+ version="1.0",
+ description="Parser and header generator for xpidl files.",
+ author="Mozilla Foundation",
+ license="MPL 2.0",
+ packages=find_packages(),
+ install_requires=["ply>=3.6,<4.0"],
+ url="https://github.com/pelmers/xpidl",
+ entry_points={"console_scripts": ["header.py = xpidl.header:main"]},
+ keywords=["xpidl", "parser"],
+)
diff --git a/xpcom/idl-parser/xpidl/__init__.py b/xpcom/idl-parser/xpidl/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/xpcom/idl-parser/xpidl/__init__.py
diff --git a/xpcom/idl-parser/xpidl/header.py b/xpcom/idl-parser/xpidl/header.py
new file mode 100644
index 0000000000..ed179b1bad
--- /dev/null
+++ b/xpcom/idl-parser/xpidl/header.py
@@ -0,0 +1,723 @@
+#!/usr/bin/env python
+# header.py - Generate C++ header files from IDL.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+"""Print a C++ header file for the IDL files specified on the command line"""
+
+import itertools
+import os.path
+import re
+
+from xpidl import xpidl
+
+printdoccomments = False
+
+if printdoccomments:
+
+ def printComments(fd, clist, indent):
+ for c in clist:
+ fd.write("%s%s\n" % (indent, c))
+
+else:
+
+ def printComments(fd, clist, indent):
+ pass
+
+
+def firstCap(str):
+ return str[0].upper() + str[1:]
+
+
+def attributeParamName(a):
+ return "a" + firstCap(a.name)
+
+
+def attributeParamNames(a, getter, return_param=True):
+ if getter and (a.notxpcom or not return_param):
+ l = []
+ else:
+ l = [attributeParamName(a)]
+ if a.implicit_jscontext:
+ l.insert(0, "cx")
+ return ", ".join(l)
+
+
+def attributeNativeName(a, getter):
+ binaryname = a.binaryname is not None and a.binaryname or firstCap(a.name)
+ return "%s%s" % (getter and "Get" or "Set", binaryname)
+
+
+def attributeAttributes(a, getter):
+ ret = ""
+
+ if a.must_use:
+ ret = "[[nodiscard]] " + ret
+
+ # Ideally, we'd set MOZ_CAN_RUN_SCRIPT in the "scriptable and not
+ # builtinclass" case too, so we'd just have memberCanRunScript() check
+ # explicit_setter_can_run_script/explicit_setter_can_run_script and call it
+ # here. But that would likely require a fair amount of Gecko-side
+ # annotation work. See bug 1534292.
+ if (a.explicit_getter_can_run_script and getter) or (
+ a.explicit_setter_can_run_script and not getter
+ ):
+ ret = "MOZ_CAN_RUN_SCRIPT " + ret
+
+ return ret
+
+
+def attributeReturnType(a, getter, macro):
+ """macro should be NS_IMETHOD or NS_IMETHODIMP"""
+ # Pick the type to be returned from the getter/setter.
+ if a.notxpcom:
+ ret = a.realtype.nativeType("in").strip() if getter else "void"
+ else:
+ ret = "nsresult"
+
+ # Set calling convention and virtual-ness
+ if a.nostdcall:
+ if macro == "NS_IMETHOD":
+ # This is the declaration.
+ ret = "virtual %s" % ret
+ else:
+ if ret == "nsresult":
+ ret = macro
+ else:
+ ret = "%s_(%s)" % (macro, ret)
+
+ return attributeAttributes(a, getter) + ret
+
+
+def attributeParamlist(a, getter, return_param=True):
+ if getter and (a.notxpcom or not return_param):
+ l = []
+ else:
+ l = [
+ "%s%s"
+ % (a.realtype.nativeType(getter and "out" or "in"), attributeParamName(a))
+ ]
+ if a.implicit_jscontext:
+ l.insert(0, "JSContext* cx")
+
+ return ", ".join(l)
+
+
+def attributeAsNative(a, getter, declType="NS_IMETHOD"):
+ params = {
+ "returntype": attributeReturnType(a, getter, declType),
+ "binaryname": attributeNativeName(a, getter),
+ "paramlist": attributeParamlist(a, getter),
+ }
+ return "%(returntype)s %(binaryname)s(%(paramlist)s)" % params
+
+
+def methodNativeName(m):
+ return m.binaryname is not None and m.binaryname or firstCap(m.name)
+
+
+def methodAttributes(m):
+ ret = ""
+
+ if m.must_use:
+ ret = "[[nodiscard]] " + ret
+
+ # Ideally, we'd set MOZ_CAN_RUN_SCRIPT in the "scriptable and not
+ # builtinclass" case too, so we'd just have memberCanRunScript() check
+ # explicit_can_run_script and call it here. But that would likely require
+ # a fair amount of Gecko-side annotation work. See bug 1534292.
+ if m.explicit_can_run_script:
+ ret = "MOZ_CAN_RUN_SCRIPT " + ret
+
+ return ret
+
+
+def methodReturnType(m, macro):
+ """macro should be NS_IMETHOD or NS_IMETHODIMP"""
+ if m.notxpcom:
+ ret = m.realtype.nativeType("in").strip()
+ else:
+ ret = "nsresult"
+
+ # Set calling convention and virtual-ness
+ if m.nostdcall:
+ if macro == "NS_IMETHOD":
+ # This is the declaration
+ ret = "virtual %s" % ret
+ else:
+ if ret == "nsresult":
+ ret = macro
+ else:
+ ret = "%s_(%s)" % (macro, ret)
+
+ return methodAttributes(m) + ret
+
+
+def methodAsNative(m, declType="NS_IMETHOD"):
+ return "%s %s(%s)" % (
+ methodReturnType(m, declType),
+ methodNativeName(m),
+ paramlistAsNative(m),
+ )
+
+
+def paramlistAsNative(m, empty="void", return_param=True):
+ l = [paramAsNative(p) for p in m.params]
+
+ if m.implicit_jscontext:
+ l.append("JSContext* cx")
+
+ if m.optional_argc:
+ l.append("uint8_t _argc")
+
+ if not m.notxpcom and m.realtype.name != "void" and return_param:
+ l.append(
+ paramAsNative(
+ xpidl.Param(
+ paramtype="out",
+ type=None,
+ name="_retval",
+ attlist=[],
+ location=None,
+ realtype=m.realtype,
+ )
+ )
+ )
+
+ # Set any optional out params to default to nullptr. Skip if we just added
+ # extra non-optional args to l.
+ if len(l) == len(m.params):
+ paramIter = len(m.params) - 1
+ while (
+ paramIter >= 0
+ and m.params[paramIter].optional
+ and "out" in m.params[paramIter].paramtype
+ ):
+ t = m.params[paramIter].type
+ # Strings can't be optional, so this shouldn't happen, but let's make sure:
+ if t == "AString" or t == "ACString" or t == "AUTF8String":
+ break
+ l[paramIter] += " = nullptr"
+ paramIter -= 1
+
+ if len(l) == 0:
+ return empty
+
+ return ", ".join(l)
+
+
+def memberCanRunScript(member):
+ # This can only happen if the member is scriptable and its interface is not builtinclass.
+ return member.isScriptable() and not member.iface.attributes.builtinclass
+
+
+def runScriptAnnotation(member):
+ return "JS_HAZ_CAN_RUN_SCRIPT " if memberCanRunScript(member) else ""
+
+
+def paramAsNative(p):
+ default_spec = ""
+ if p.default_value:
+ default_spec = " = " + p.default_value
+ return "%s%s%s" % (p.nativeType(), p.name, default_spec)
+
+
+def paramlistNames(m, return_param=True):
+ names = [p.name for p in m.params]
+
+ if m.implicit_jscontext:
+ names.append("cx")
+
+ if m.optional_argc:
+ names.append("_argc")
+
+ if not m.notxpcom and m.realtype.name != "void" and return_param:
+ names.append("_retval")
+
+ if len(names) == 0:
+ return ""
+ return ", ".join(names)
+
+
+header = """/*
+ * DO NOT EDIT. THIS FILE IS GENERATED FROM $SRCDIR/%(relpath)s
+ */
+
+#ifndef __gen_%(basename)s_h__
+#define __gen_%(basename)s_h__
+"""
+
+include = """
+#include "%(basename)s.h"
+"""
+
+jsvalue_include = """
+#include "js/Value.h"
+"""
+
+infallible_includes = """
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/DebugOnly.h"
+"""
+
+can_run_script_includes = """
+#include "js/GCAnnotations.h"
+"""
+
+header_end = """/* For IDL files that don't want to include root IDL files. */
+#ifndef NS_NO_VTABLE
+#define NS_NO_VTABLE
+#endif
+"""
+
+footer = """
+#endif /* __gen_%(basename)s_h__ */
+"""
+
+forward_decl = """class %(name)s; /* forward declaration */
+
+"""
+
+
+def idl_basename(f):
+ """returns the base name of a file with the last extension stripped"""
+ return os.path.basename(f).rpartition(".")[0]
+
+
+def print_header(idl, fd, filename, relpath):
+ fd.write(header % {"relpath": relpath, "basename": idl_basename(filename)})
+
+ foundinc = False
+ for inc in idl.includes():
+ if not foundinc:
+ foundinc = True
+ fd.write("\n")
+ fd.write(include % {"basename": idl_basename(inc.filename)})
+
+ if idl.needsJSTypes():
+ fd.write(jsvalue_include)
+
+ # Include some extra files if any attributes are infallible.
+ interfaces = [p for p in idl.productions if p.kind == "interface"]
+ wroteRunScriptIncludes = False
+ wroteInfallibleIncludes = False
+ for iface in interfaces:
+ for member in iface.members:
+ if not isinstance(member, xpidl.Attribute) and not isinstance(
+ member, xpidl.Method
+ ):
+ continue
+ if not wroteInfallibleIncludes and member.infallible:
+ fd.write(infallible_includes)
+ wroteInfallibleIncludes = True
+ if not wroteRunScriptIncludes and memberCanRunScript(member):
+ fd.write(can_run_script_includes)
+ wroteRunScriptIncludes = True
+ if wroteRunScriptIncludes and wroteInfallibleIncludes:
+ break
+
+ fd.write("\n")
+ fd.write(header_end)
+
+ for p in idl.productions:
+ if p.kind == "include":
+ continue
+ if p.kind == "cdata":
+ fd.write(p.data)
+ continue
+
+ if p.kind == "webidl":
+ write_webidl(p, fd)
+ continue
+ if p.kind == "forward":
+ fd.write(forward_decl % {"name": p.name})
+ continue
+ if p.kind == "interface":
+ write_interface(p, fd)
+ continue
+ if p.kind == "typedef":
+ printComments(fd, p.doccomments, "")
+ fd.write("typedef %s %s;\n\n" % (p.realtype.nativeType("in"), p.name))
+
+ fd.write(footer % {"basename": idl_basename(filename)})
+
+
+def write_webidl(p, fd):
+ path = p.native.split("::")
+ for seg in path[:-1]:
+ fd.write("namespace %s {\n" % seg)
+ fd.write("class %s; /* webidl %s */\n" % (path[-1], p.name))
+ for seg in reversed(path[:-1]):
+ fd.write("} // namespace %s\n" % seg)
+ fd.write("\n")
+
+
+iface_header = r"""
+/* starting interface: %(name)s */
+#define %(defname)s_IID_STR "%(iid)s"
+
+#define %(defname)s_IID \
+ {0x%(m0)s, 0x%(m1)s, 0x%(m2)s, \
+ { %(m3joined)s }}
+
+"""
+
+uuid_decoder = re.compile(
+ r"""(?P<m0>[a-f0-9]{8})-
+ (?P<m1>[a-f0-9]{4})-
+ (?P<m2>[a-f0-9]{4})-
+ (?P<m3>[a-f0-9]{4})-
+ (?P<m4>[a-f0-9]{12})$""",
+ re.X,
+)
+
+iface_prolog = """ {
+ public:
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(%(defname)s_IID)
+
+"""
+
+iface_scriptable = """\
+ /* Used by ToJSValue to check which scriptable interface is implemented. */
+ using ScriptableInterfaceType = %(name)s;
+
+"""
+
+iface_epilog = """};
+
+ NS_DEFINE_STATIC_IID_ACCESSOR(%(name)s, %(defname)s_IID)"""
+
+iface_decl = """
+
+/* Use this macro when declaring classes that implement this interface. */
+#define NS_DECL_%(macroname)s """
+
+iface_nonvirtual = """
+
+/* Use this macro when declaring the members of this interface when the
+ class doesn't implement the interface. This is useful for forwarding. */
+#define NS_DECL_NON_VIRTUAL_%(macroname)s """
+
+iface_forward = """
+
+/* Use this macro to declare functions that forward the behavior of this interface to another object. */
+#define NS_FORWARD_%(macroname)s(_to) """ # NOQA: E501
+
+iface_forward_safe = """
+
+/* Use this macro to declare functions that forward the behavior of this interface to another object in a safe way. */
+#define NS_FORWARD_SAFE_%(macroname)s(_to) """ # NOQA: E501
+
+builtin_infallible_tmpl = """\
+ %(attributes)sinline %(realtype)s %(nativename)s(%(args)s)
+ {
+ %(realtype)sresult;
+ mozilla::DebugOnly<nsresult> rv = %(nativename)s(%(argnames)s&result);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ return result;
+ }
+"""
+
+# NOTE: We don't use RefPtr::forget here because we don't want to need the
+# definition of %(realtype)s in scope, which we would need for the
+# AddRef/Release calls.
+refcnt_infallible_tmpl = """\
+ %(attributes)s inline already_AddRefed<%(realtype)s> %(nativename)s(%(args)s)
+ {
+ %(realtype)s* result = nullptr;
+ mozilla::DebugOnly<nsresult> rv = %(nativename)s(%(argnames)s&result);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ return already_AddRefed<%(realtype)s>(result);
+ }
+"""
+
+iface_threadsafe_tmpl = """\
+namespace mozilla::detail {
+template <>
+class InterfaceNeedsThreadSafeRefCnt<%(name)s> : public std::true_type {};
+}
+"""
+
+
+def infallibleDecl(member):
+ isattr = isinstance(member, xpidl.Attribute)
+ ismethod = isinstance(member, xpidl.Method)
+ assert isattr or ismethod
+
+ realtype = member.realtype.nativeType("in")
+ tmpl = builtin_infallible_tmpl
+
+ if member.realtype.kind != "builtin" and member.realtype.kind != "cenum":
+ assert realtype.endswith(" *"), "bad infallible type"
+ tmpl = refcnt_infallible_tmpl
+ realtype = realtype[:-2] # strip trailing pointer
+
+ if isattr:
+ nativename = attributeNativeName(member, getter=True)
+ args = attributeParamlist(member, getter=True, return_param=False)
+ argnames = attributeParamNames(member, getter=True, return_param=False)
+ attributes = attributeAttributes(member, getter=True)
+ else:
+ nativename = methodNativeName(member)
+ args = paramlistAsNative(member, return_param=False)
+ argnames = paramlistNames(member, return_param=False)
+ attributes = methodAttributes(member)
+
+ return tmpl % {
+ "attributes": attributes,
+ "realtype": realtype,
+ "nativename": nativename,
+ "args": args,
+ "argnames": argnames + ", " if argnames else "",
+ }
+
+
+def write_interface(iface, fd):
+ if iface.namemap is None:
+ raise Exception("Interface was not resolved.")
+
+ # Confirm that no names of methods will overload in this interface
+ names = set()
+
+ def record_name(name):
+ if name in names:
+ raise Exception(
+ "Unexpected overloaded virtual method %s in interface %s"
+ % (name, iface.name)
+ )
+ names.add(name)
+
+ for m in iface.members:
+ if type(m) == xpidl.Attribute:
+ record_name(attributeNativeName(m, getter=True))
+ if not m.readonly:
+ record_name(attributeNativeName(m, getter=False))
+ elif type(m) == xpidl.Method:
+ record_name(methodNativeName(m))
+
+ def write_const_decls(g):
+ fd.write(" enum {\n")
+ enums = []
+ for c in g:
+ printComments(fd, c.doccomments, " ")
+ basetype = c.basetype
+ value = c.getValue()
+ enums.append(
+ " %(name)s = %(value)s%(signed)s"
+ % {
+ "name": c.name,
+ "value": value,
+ "signed": (not basetype.signed) and "U" or "",
+ }
+ )
+ fd.write(",\n".join(enums))
+ fd.write("\n };\n\n")
+
+ def write_cenum_decl(b):
+ fd.write(" enum %s : uint%d_t {\n" % (b.basename, b.width))
+ for var in b.variants:
+ fd.write(" %s = %s,\n" % (var.name, var.value))
+ fd.write(" };\n\n")
+
+ def write_method_decl(m):
+ printComments(fd, m.doccomments, " ")
+
+ fd.write(" /* %s */\n" % m.toIDL())
+ fd.write(" %s%s = 0;\n\n" % (runScriptAnnotation(m), methodAsNative(m)))
+
+ if m.infallible:
+ fd.write(infallibleDecl(m))
+
+ def write_attr_decl(a):
+ printComments(fd, a.doccomments, " ")
+
+ fd.write(" /* %s */\n" % a.toIDL())
+
+ fd.write(" %s%s = 0;\n" % (runScriptAnnotation(a), attributeAsNative(a, True)))
+ if a.infallible:
+ fd.write(infallibleDecl(a))
+
+ if not a.readonly:
+ fd.write(
+ " %s%s = 0;\n" % (runScriptAnnotation(a), attributeAsNative(a, False))
+ )
+ fd.write("\n")
+
+ defname = iface.name.upper()
+ if iface.name[0:2] == "ns":
+ defname = "NS_" + defname[2:]
+
+ names = uuid_decoder.match(iface.attributes.uuid).groupdict()
+ m3str = names["m3"] + names["m4"]
+ names["m3joined"] = ", ".join(["0x%s" % m3str[i : i + 2] for i in range(0, 16, 2)])
+
+ if iface.name[2] == "I":
+ implclass = iface.name[:2] + iface.name[3:]
+ else:
+ implclass = "_MYCLASS_"
+
+ names.update(
+ {
+ "defname": defname,
+ "macroname": iface.name.upper(),
+ "name": iface.name,
+ "iid": iface.attributes.uuid,
+ "implclass": implclass,
+ }
+ )
+
+ fd.write(iface_header % names)
+
+ printComments(fd, iface.doccomments, "")
+
+ fd.write("class ")
+ foundcdata = False
+ for m in iface.members:
+ if isinstance(m, xpidl.CDATA):
+ foundcdata = True
+
+ if not foundcdata:
+ fd.write("NS_NO_VTABLE ")
+
+ fd.write(iface.name)
+ if iface.base:
+ fd.write(" : public %s" % iface.base)
+ fd.write(iface_prolog % names)
+
+ if iface.attributes.scriptable:
+ fd.write(iface_scriptable % names)
+
+ for key, group in itertools.groupby(iface.members, key=type):
+ if key == xpidl.ConstMember:
+ write_const_decls(group) # iterator of all the consts
+ else:
+ for member in group:
+ if key == xpidl.Attribute:
+ write_attr_decl(member)
+ elif key == xpidl.Method:
+ write_method_decl(member)
+ elif key == xpidl.CDATA:
+ fd.write(" %s" % member.data)
+ elif key == xpidl.CEnum:
+ write_cenum_decl(member)
+ else:
+ raise Exception("Unexpected interface member: %s" % member)
+
+ fd.write(iface_epilog % names)
+
+ if iface.attributes.rust_sync:
+ fd.write(iface_threadsafe_tmpl % names)
+
+ fd.write(iface_decl % names)
+
+ def writeDeclaration(fd, iface, virtual):
+ declType = "NS_IMETHOD" if virtual else "nsresult"
+ suffix = " override" if virtual else ""
+ for member in iface.members:
+ if isinstance(member, xpidl.Attribute):
+ if member.infallible:
+ fd.write(
+ "\\\n using %s::%s; "
+ % (iface.name, attributeNativeName(member, True))
+ )
+ fd.write(
+ "\\\n %s%s; " % (attributeAsNative(member, True, declType), suffix)
+ )
+ if not member.readonly:
+ fd.write(
+ "\\\n %s%s; "
+ % (attributeAsNative(member, False, declType), suffix)
+ )
+ elif isinstance(member, xpidl.Method):
+ fd.write("\\\n %s%s; " % (methodAsNative(member, declType), suffix))
+ if len(iface.members) == 0:
+ fd.write("\\\n /* no methods! */")
+ elif member.kind not in ("attribute", "method"):
+ fd.write("\\")
+
+ writeDeclaration(fd, iface, True)
+ fd.write(iface_nonvirtual % names)
+ writeDeclaration(fd, iface, False)
+ fd.write(iface_forward % names)
+
+ def emitTemplate(forward_infallible, tmpl, tmpl_notxpcom=None):
+ if tmpl_notxpcom is None:
+ tmpl_notxpcom = tmpl
+ for member in iface.members:
+ if isinstance(member, xpidl.Attribute):
+ if forward_infallible and member.infallible:
+ fd.write(
+ "\\\n using %s::%s; "
+ % (iface.name, attributeNativeName(member, True))
+ )
+ attr_tmpl = tmpl_notxpcom if member.notxpcom else tmpl
+ fd.write(
+ attr_tmpl
+ % {
+ "asNative": attributeAsNative(member, True),
+ "nativeName": attributeNativeName(member, True),
+ "paramList": attributeParamNames(member, True),
+ }
+ )
+ if not member.readonly:
+ fd.write(
+ attr_tmpl
+ % {
+ "asNative": attributeAsNative(member, False),
+ "nativeName": attributeNativeName(member, False),
+ "paramList": attributeParamNames(member, False),
+ }
+ )
+ elif isinstance(member, xpidl.Method):
+ if member.notxpcom:
+ fd.write(
+ tmpl_notxpcom
+ % {
+ "asNative": methodAsNative(member),
+ "nativeName": methodNativeName(member),
+ "paramList": paramlistNames(member),
+ }
+ )
+ else:
+ fd.write(
+ tmpl
+ % {
+ "asNative": methodAsNative(member),
+ "nativeName": methodNativeName(member),
+ "paramList": paramlistNames(member),
+ }
+ )
+ if len(iface.members) == 0:
+ fd.write("\\\n /* no methods! */")
+ elif member.kind not in ("attribute", "method"):
+ fd.write("\\")
+
+ emitTemplate(
+ True,
+ "\\\n %(asNative)s override { return _to %(nativeName)s(%(paramList)s); } ",
+ )
+
+ fd.write(iface_forward_safe % names)
+
+ # Don't try to safely forward notxpcom functions, because we have no
+ # sensible default error return. Instead, the caller will have to
+ # implement them.
+ emitTemplate(
+ False,
+ "\\\n %(asNative)s override { return !_to ? NS_ERROR_NULL_POINTER : _to->%(nativeName)s(%(paramList)s); } ", # NOQA: E501
+ "\\\n %(asNative)s override; ",
+ )
+
+ fd.write("\n\n")
+
+
+def main(outputfile):
+ xpidl.IDLParser()
+
+
+if __name__ == "__main__":
+ main(None)
diff --git a/xpcom/idl-parser/xpidl/jsonxpt.py b/xpcom/idl-parser/xpidl/jsonxpt.py
new file mode 100644
index 0000000000..342188f645
--- /dev/null
+++ b/xpcom/idl-parser/xpidl/jsonxpt.py
@@ -0,0 +1,282 @@
+#!/usr/bin/env python
+# jsonxpt.py - Generate json XPT typelib files from IDL.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+"""Generate a json XPT typelib for an IDL file"""
+
+import itertools
+import json
+
+from xpidl import xpidl
+
+# A map of xpidl.py types to xpt enum variants
+TypeMap = {
+ # builtins
+ "boolean": "TD_BOOL",
+ "void": "TD_VOID",
+ "int16_t": "TD_INT16",
+ "int32_t": "TD_INT32",
+ "int64_t": "TD_INT64",
+ "uint8_t": "TD_UINT8",
+ "uint16_t": "TD_UINT16",
+ "uint32_t": "TD_UINT32",
+ "uint64_t": "TD_UINT64",
+ "octet": "TD_UINT8",
+ "short": "TD_INT16",
+ "long": "TD_INT32",
+ "long long": "TD_INT64",
+ "unsigned short": "TD_UINT16",
+ "unsigned long": "TD_UINT32",
+ "unsigned long long": "TD_UINT64",
+ "float": "TD_FLOAT",
+ "double": "TD_DOUBLE",
+ "char": "TD_CHAR",
+ "string": "TD_PSTRING",
+ "wchar": "TD_WCHAR",
+ "wstring": "TD_PWSTRING",
+ # special types
+ "nsid": "TD_NSID",
+ "astring": "TD_ASTRING",
+ "utf8string": "TD_UTF8STRING",
+ "cstring": "TD_CSTRING",
+ "jsval": "TD_JSVAL",
+ "promise": "TD_PROMISE",
+}
+
+
+def flags(*flags):
+ return [flag for flag, cond in flags if cond]
+
+
+def get_type(type, calltype, iid_is=None, size_is=None):
+ while isinstance(type, xpidl.Typedef):
+ type = type.realtype
+
+ if isinstance(type, xpidl.Builtin):
+ ret = {"tag": TypeMap[type.name]}
+ if type.name in ["string", "wstring"] and size_is is not None:
+ ret["tag"] += "_SIZE_IS"
+ ret["size_is"] = size_is
+ return ret
+
+ if isinstance(type, xpidl.Array):
+ # NB: For a Array<T> we pass down the iid_is to get the type of T.
+ # This allows Arrays of InterfaceIs types to work.
+ return {
+ "tag": "TD_ARRAY",
+ "element": get_type(type.type, calltype, iid_is),
+ }
+
+ if isinstance(type, xpidl.LegacyArray):
+ # NB: For a Legacy [array] T we pass down iid_is to get the type of T.
+ # This allows [array] of InterfaceIs types to work.
+ return {
+ "tag": "TD_LEGACY_ARRAY",
+ "size_is": size_is,
+ "element": get_type(type.type, calltype, iid_is),
+ }
+
+ if isinstance(type, xpidl.Interface) or isinstance(type, xpidl.Forward):
+ return {
+ "tag": "TD_INTERFACE_TYPE",
+ "name": type.name,
+ }
+
+ if isinstance(type, xpidl.WebIDL):
+ return {
+ "tag": "TD_DOMOBJECT",
+ "name": type.name,
+ "native": type.native,
+ "headerFile": type.headerFile,
+ }
+
+ if isinstance(type, xpidl.Native):
+ if type.specialtype == "nsid" and type.isPtr(calltype):
+ return {"tag": "TD_NSIDPTR"}
+ elif type.specialtype:
+ return {"tag": TypeMap[type.specialtype]}
+ elif iid_is is not None:
+ return {
+ "tag": "TD_INTERFACE_IS_TYPE",
+ "iid_is": iid_is,
+ }
+ else:
+ return {"tag": "TD_VOID"}
+
+ if isinstance(type, xpidl.CEnum):
+ # As far as XPConnect is concerned, cenums are just unsigned integers.
+ return {"tag": "TD_UINT%d" % type.width}
+
+ raise Exception("Unknown type!")
+
+
+def mk_param(type, in_=0, out=0, optional=0):
+ return {
+ "type": type,
+ "flags": flags(
+ ("in", in_),
+ ("out", out),
+ ("optional", optional),
+ ),
+ }
+
+
+def mk_method(method, params, getter=0, setter=0, optargc=0, hasretval=0, symbol=0):
+ return {
+ "name": method.name,
+ # NOTE: We don't include any return value information here, as we'll
+ # never call the methods if they're marked notxpcom, and all xpcom
+ # methods return the same type (nsresult).
+ # XXX: If we ever use these files for other purposes than xptcodegen we
+ # may want to write that info.
+ "params": params,
+ "flags": flags(
+ ("getter", getter),
+ ("setter", setter),
+ ("hidden", method.noscript or method.notxpcom),
+ ("optargc", optargc),
+ ("jscontext", method.implicit_jscontext),
+ ("hasretval", hasretval),
+ ("symbol", method.symbol),
+ ),
+ }
+
+
+def attr_param_idx(p, m, attr):
+ attr_val = getattr(p, attr, None)
+ if not attr_val:
+ return None
+ for i, param in enumerate(m.params):
+ if param.name == attr_val:
+ return i
+ raise Exception(f"Need parameter named '{attr_val}' for attribute '{attr}'")
+
+
+def build_interface(iface):
+ if iface.namemap is None:
+ raise Exception("Interface was not resolved.")
+
+ assert (
+ iface.attributes.scriptable
+ ), "Don't generate XPT info for non-scriptable interfaces"
+
+ # State used while building an interface
+ consts = []
+ methods = []
+
+ def build_const(c):
+ consts.append(
+ {
+ "name": c.name,
+ "type": get_type(c.basetype, ""),
+ "value": c.getValue(), # All of our consts are numbers
+ }
+ )
+
+ def build_cenum(b):
+ for var in b.variants:
+ consts.append(
+ {
+ "name": var.name,
+ "type": get_type(b, "in"),
+ "value": var.value,
+ }
+ )
+
+ def build_method(m):
+ params = []
+ for p in m.params:
+ params.append(
+ mk_param(
+ get_type(
+ p.realtype,
+ p.paramtype,
+ iid_is=attr_param_idx(p, m, "iid_is"),
+ size_is=attr_param_idx(p, m, "size_is"),
+ ),
+ in_=p.paramtype.count("in"),
+ out=p.paramtype.count("out"),
+ optional=p.optional,
+ )
+ )
+
+ hasretval = len(m.params) > 0 and m.params[-1].retval
+ if not m.notxpcom and m.realtype.name != "void":
+ hasretval = True
+ params.append(mk_param(get_type(m.realtype, "out"), out=1))
+
+ methods.append(
+ mk_method(m, params, optargc=m.optional_argc, hasretval=hasretval)
+ )
+
+ def build_attr(a):
+ assert a.realtype.name != "void"
+ # Write the getter
+ getter_params = []
+ if not a.notxpcom:
+ getter_params.append(mk_param(get_type(a.realtype, "out"), out=1))
+
+ methods.append(mk_method(a, getter_params, getter=1, hasretval=1))
+
+ # And maybe the setter
+ if not a.readonly:
+ param = mk_param(get_type(a.realtype, "in"), in_=1)
+ methods.append(mk_method(a, [param], setter=1))
+
+ for member in iface.members:
+ if isinstance(member, xpidl.ConstMember):
+ build_const(member)
+ elif isinstance(member, xpidl.Attribute):
+ build_attr(member)
+ elif isinstance(member, xpidl.Method):
+ build_method(member)
+ elif isinstance(member, xpidl.CEnum):
+ build_cenum(member)
+ elif isinstance(member, xpidl.CDATA):
+ pass
+ else:
+ raise Exception("Unexpected interface member: %s" % member)
+
+ return {
+ "name": iface.name,
+ "uuid": iface.attributes.uuid,
+ "methods": methods,
+ "consts": consts,
+ "parent": iface.base,
+ "flags": flags(
+ ("function", iface.attributes.function),
+ ("builtinclass", iface.attributes.builtinclass),
+ ("main_process_only", iface.attributes.main_process_scriptable_only),
+ ),
+ }
+
+
+# These functions are the public interface of this module. They are very simple
+# functions, but are exported so that if we need to do something more
+# complex in them in the future we can.
+
+
+def build_typelib(idl):
+ """Given a parsed IDL file, generate and return the typelib"""
+ return [
+ build_interface(p)
+ for p in idl.productions
+ if p.kind == "interface" and p.attributes.scriptable
+ ]
+
+
+def link(typelibs):
+ """Link a list of typelibs together into a single typelib"""
+ linked = list(itertools.chain.from_iterable(typelibs))
+ assert len(set(iface["name"] for iface in linked)) == len(
+ linked
+ ), "Multiple typelibs containing the same interface were linked together"
+ return linked
+
+
+def write(typelib, fd):
+ """Write typelib into fd"""
+ json.dump(typelib, fd, indent=2, sort_keys=True)
diff --git a/xpcom/idl-parser/xpidl/moz.build b/xpcom/idl-parser/xpidl/moz.build
new file mode 100644
index 0000000000..60252a0b81
--- /dev/null
+++ b/xpcom/idl-parser/xpidl/moz.build
@@ -0,0 +1,11 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+PYTHON_UNITTEST_MANIFESTS += [
+ "python.toml",
+]
+
+GeneratedFile("xpidl.stub", script="header.py", entry_point="main")
diff --git a/xpcom/idl-parser/xpidl/python.toml b/xpcom/idl-parser/xpidl/python.toml
new file mode 100644
index 0000000000..577127329e
--- /dev/null
+++ b/xpcom/idl-parser/xpidl/python.toml
@@ -0,0 +1,4 @@
+[DEFAULT]
+subsuite = "xpcom"
+
+["runtests.py"]
diff --git a/xpcom/idl-parser/xpidl/runtests.py b/xpcom/idl-parser/xpidl/runtests.py
new file mode 100755
index 0000000000..2dd269dfd9
--- /dev/null
+++ b/xpcom/idl-parser/xpidl/runtests.py
@@ -0,0 +1,257 @@
+#!/usr/bin/env python
+#
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+#
+# Unit tests for xpidl.py
+
+import sys
+
+# Hack: the first entry in sys.path is the directory containing the script.
+# This messes things up because that directory is the xpidl module, and that
+# which conflicts with the xpidl submodule in the imports further below.
+sys.path.pop(0)
+
+import unittest
+
+import mozunit
+
+from xpidl import header, xpidl
+
+
+class TestParser(unittest.TestCase):
+ def setUp(self):
+ self.p = xpidl.IDLParser()
+
+ def testEmpty(self):
+ i = self.p.parse("", filename="f")
+ self.assertTrue(isinstance(i, xpidl.IDL))
+ self.assertEqual([], i.productions)
+
+ def testForwardInterface(self):
+ i = self.p.parse("interface foo;", filename="f")
+ self.assertTrue(isinstance(i, xpidl.IDL))
+ self.assertTrue(isinstance(i.productions[0], xpidl.Forward))
+ self.assertEqual("foo", i.productions[0].name)
+
+ def testInterface(self):
+ i = self.p.parse("[uuid(abc)] interface foo {};", filename="f")
+ self.assertTrue(isinstance(i, xpidl.IDL))
+ self.assertTrue(isinstance(i.productions[0], xpidl.Interface))
+ self.assertEqual("foo", i.productions[0].name)
+
+ def testAttributes(self):
+ i = self.p.parse(
+ "[scriptable, builtinclass, function, uuid(abc)] interface foo {};",
+ filename="f",
+ )
+ self.assertTrue(isinstance(i, xpidl.IDL))
+ self.assertTrue(isinstance(i.productions[0], xpidl.Interface))
+ iface = i.productions[0]
+ self.assertEqual("foo", iface.name)
+ self.assertTrue(iface.attributes.scriptable)
+ self.assertTrue(iface.attributes.builtinclass)
+ self.assertTrue(iface.attributes.function)
+
+ i = self.p.parse("[uuid(abc)] interface foo {};", filename="f")
+ self.assertTrue(isinstance(i, xpidl.IDL))
+ self.assertTrue(isinstance(i.productions[0], xpidl.Interface))
+ iface = i.productions[0]
+ self.assertEqual("foo", iface.name)
+ self.assertFalse(iface.attributes.scriptable)
+
+ def testMethod(self):
+ i = self.p.parse(
+ """[uuid(abc)] interface foo {
+void bar();
+};""",
+ filename="f",
+ )
+ self.assertTrue(isinstance(i, xpidl.IDL))
+ self.assertTrue(isinstance(i.productions[0], xpidl.Interface))
+ iface = i.productions[0]
+ m = iface.members[0]
+ self.assertTrue(isinstance(m, xpidl.Method))
+ self.assertEqual("bar", m.name)
+ self.assertEqual(xpidl.TypeId("void"), m.type)
+
+ def testMethodParams(self):
+ i = self.p.parse(
+ """
+ [scriptable, uuid(aaa)] interface nsISupports {};
+ [uuid(abc)] interface foo : nsISupports {
+ long bar(in long a, in float b, [array] in long c);
+ };""",
+ filename="f",
+ )
+ i.resolve([], self.p, {})
+ self.assertTrue(isinstance(i, xpidl.IDL))
+ self.assertTrue(isinstance(i.productions[1], xpidl.Interface))
+ iface = i.productions[1]
+ m = iface.members[0]
+ self.assertTrue(isinstance(m, xpidl.Method))
+ self.assertEqual("bar", m.name)
+ self.assertEqual(xpidl.TypeId("long"), m.type)
+ self.assertEqual(3, len(m.params))
+ self.assertEqual(xpidl.TypeId("long"), m.params[0].type)
+ self.assertEqual("in", m.params[0].paramtype)
+ self.assertEqual(xpidl.TypeId("float"), m.params[1].type)
+ self.assertEqual("in", m.params[1].paramtype)
+ self.assertEqual(xpidl.TypeId("long"), m.params[2].type)
+ self.assertEqual("in", m.params[2].paramtype)
+ self.assertTrue(isinstance(m.params[2].realtype, xpidl.LegacyArray))
+ self.assertEqual("long", m.params[2].realtype.type.name)
+
+ def testAttribute(self):
+ i = self.p.parse(
+ """[uuid(abc)] interface foo {
+attribute long bar;
+};""",
+ filename="f",
+ )
+ self.assertTrue(isinstance(i, xpidl.IDL))
+ self.assertTrue(isinstance(i.productions[0], xpidl.Interface))
+ iface = i.productions[0]
+ a = iface.members[0]
+ self.assertTrue(isinstance(a, xpidl.Attribute))
+ self.assertEqual("bar", a.name)
+ self.assertEqual(xpidl.TypeId("long"), a.type)
+
+ def testOverloadedVirtual(self):
+ i = self.p.parse(
+ """
+ [scriptable, uuid(00000000-0000-0000-0000-000000000000)] interface nsISupports {};
+ [uuid(abc)] interface foo : nsISupports {
+ attribute long bar;
+ void getBar();
+ };""",
+ filename="f",
+ )
+ self.assertTrue(isinstance(i, xpidl.IDL))
+ i.resolve([], self.p, {})
+
+ class FdMock:
+ def write(self, s):
+ pass
+
+ try:
+ header.print_header(i, FdMock(), filename="f", relpath="f")
+ self.assertTrue(False, "Header printing failed to fail")
+ except Exception as e:
+ self.assertEqual(
+ e.args[0],
+ "Unexpected overloaded virtual method GetBar in interface foo",
+ )
+
+ def testNotISupports(self):
+ i = self.p.parse(
+ """
+ [uuid(abc)] interface foo {};
+ """,
+ filename="f",
+ )
+ self.assertTrue(isinstance(i, xpidl.IDL))
+ try:
+ i.resolve([], self.p, {})
+ self.assertTrue(
+ False, "Must check that interfaces inherit from nsISupports"
+ )
+ except xpidl.IDLError as e:
+ self.assertEqual(e.args[0], "Interface 'foo' must inherit from nsISupports")
+
+ def testBuiltinClassParent(self):
+ i = self.p.parse(
+ """
+ [scriptable, uuid(aaa)] interface nsISupports {};
+ [scriptable, builtinclass, uuid(abc)] interface foo : nsISupports {};
+ [scriptable, uuid(def)] interface bar : foo {};
+ """,
+ filename="f",
+ )
+ self.assertTrue(isinstance(i, xpidl.IDL))
+ try:
+ i.resolve([], self.p, {})
+ self.assertTrue(
+ False, "non-builtinclasses can't inherit from builtinclasses"
+ )
+ except xpidl.IDLError as e:
+ self.assertEqual(
+ e.args[0],
+ "interface 'bar' is not builtinclass but derives from builtinclass 'foo'",
+ )
+
+ def testScriptableNotXPCOM(self):
+ # notxpcom method requires builtinclass on the interface
+ i = self.p.parse(
+ """
+ [scriptable, uuid(aaa)] interface nsISupports {};
+ [scriptable, uuid(abc)] interface nsIScriptableWithNotXPCOM : nsISupports {
+ [notxpcom] void method2();
+ };
+ """,
+ filename="f",
+ )
+ self.assertTrue(isinstance(i, xpidl.IDL))
+ try:
+ i.resolve([], self.p, {})
+ self.assertTrue(
+ False,
+ "Resolve should fail for non-builtinclasses with notxpcom methods",
+ )
+ except xpidl.IDLError as e:
+ self.assertEqual(
+ e.args[0],
+ (
+ "scriptable interface 'nsIScriptableWithNotXPCOM' "
+ "must be marked [builtinclass] because it contains a [notxpcom] "
+ "method 'method2'"
+ ),
+ )
+
+ # notxpcom attribute requires builtinclass on the interface
+ i = self.p.parse(
+ """
+ interface nsISomeInterface;
+ [scriptable, uuid(aaa)] interface nsISupports {};
+ [scriptable, uuid(abc)] interface nsIScriptableWithNotXPCOM : nsISupports {
+ [notxpcom] attribute nsISomeInterface attrib;
+ };
+ """,
+ filename="f",
+ )
+ self.assertTrue(isinstance(i, xpidl.IDL))
+ try:
+ i.resolve([], self.p, {})
+ self.assertTrue(
+ False,
+ "Resolve should fail for non-builtinclasses with notxpcom attributes",
+ )
+ except xpidl.IDLError as e:
+ self.assertEqual(
+ e.args[0],
+ (
+ "scriptable interface 'nsIScriptableWithNotXPCOM' must be marked "
+ "[builtinclass] because it contains a [notxpcom] attribute 'attrib'"
+ ),
+ )
+
+ def testUndefinedConst(self):
+ i = self.p.parse(
+ """
+ [scriptable, uuid(aaa)] interface nsISupports {};
+ [scriptable, uuid(abc)] interface foo : nsISupports {
+ const unsigned long X = Y + 1;
+ };
+ """,
+ filename="f",
+ )
+ self.assertTrue(isinstance(i, xpidl.IDL))
+ try:
+ i.resolve([], self.p, {})
+ self.assertTrue(False, "Must detect undefined symbols")
+ except xpidl.IDLError as e:
+ self.assertEqual(e.args[0], ("cannot find symbol 'Y'"))
+
+
+if __name__ == "__main__":
+ mozunit.main(runwith="unittest")
diff --git a/xpcom/idl-parser/xpidl/rust.py b/xpcom/idl-parser/xpidl/rust.py
new file mode 100644
index 0000000000..b9fc6627fb
--- /dev/null
+++ b/xpcom/idl-parser/xpidl/rust.py
@@ -0,0 +1,693 @@
+# rust.py - Generate rust bindings from IDL.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+"""Print a runtime Rust bindings file for the IDL file specified"""
+
+# --- Safety Hazards ---
+
+# We currently don't generate some bindings for some IDL methods in rust code,
+# due to there being ABI safety hazards if we were to do so. This is the
+# documentation for the reasons why we don't generate certain types of bindings,
+# so that we don't accidentally start generating them in the future.
+
+# notxpcom methods and attributes return their results directly by value. The x86
+# windows stdcall ABI returns aggregates by value differently for methods than
+# functions, and rust only exposes the function ABI, so that's the one we're
+# using. The correct ABI can be emulated for notxpcom methods returning aggregates
+# by passing an &mut ReturnType parameter as the second parameter. This strategy
+# is used by the winapi-rs crate.
+# https://github.com/retep998/winapi-rs/blob/7338a5216a6a7abeefcc6bb1bc34381c81d3e247/src/macros.rs#L220-L231
+#
+# Right now we can generate code for notxpcom methods and attributes, as we don't
+# support passing aggregates by value over these APIs ever (the types which are
+# allowed in xpidl.py shouldn't include any aggregates), so the code is
+# correct. In the future if we want to start supporting returning aggregates by
+# value, we will need to use a workaround such as the one used by winapi.rs.
+
+# nostdcall methods on x86 windows will use the thiscall ABI, which is not
+# stable in rust right now, so we cannot generate bindings to them.
+
+# In general, passing C++ objects by value over the C ABI is not a good idea,
+# and when possible we should avoid doing so. We don't generate bindings for
+# these methods here currently.
+
+import os.path
+import re
+
+from xpidl import xpidl
+
+
+class AutoIndent(object):
+ """A small autoindenting wrapper around a fd.
+ Used to make the code output more readable."""
+
+ def __init__(self, fd):
+ self.fd = fd
+ self.indent = 0
+
+ def write(self, string):
+ """A smart write function which automatically adjusts the
+ indentation of each line as it is written by counting braces"""
+ for s in string.split("\n"):
+ s = s.strip()
+ indent = self.indent
+ if len(s) == 0:
+ indent = 0
+ elif s[0] == "}":
+ indent -= 1
+
+ self.fd.write(" " * indent + s + "\n")
+ for c in s:
+ if c == "(" or c == "{" or c == "[":
+ self.indent += 1
+ elif c == ")" or c == "}" or c == "]":
+ self.indent -= 1
+
+
+def rustSanitize(s):
+ keywords = [
+ "abstract",
+ "alignof",
+ "as",
+ "become",
+ "box",
+ "break",
+ "const",
+ "continue",
+ "crate",
+ "do",
+ "else",
+ "enum",
+ "extern",
+ "false",
+ "final",
+ "fn",
+ "for",
+ "if",
+ "impl",
+ "in",
+ "let",
+ "loop",
+ "macro",
+ "match",
+ "mod",
+ "move",
+ "mut",
+ "offsetof",
+ "override",
+ "priv",
+ "proc",
+ "pub",
+ "pure",
+ "ref",
+ "return",
+ "Self",
+ "self",
+ "sizeof",
+ "static",
+ "struct",
+ "super",
+ "trait",
+ "true",
+ "type",
+ "typeof",
+ "unsafe",
+ "unsized",
+ "use",
+ "virtual",
+ "where",
+ "while",
+ "yield",
+ ]
+ if s in keywords:
+ return s + "_"
+ return s
+
+
+# printdoccomments = False
+printdoccomments = True
+
+if printdoccomments:
+
+ def printComments(fd, clist, indent):
+ fd.write("%s%s" % (indent, doccomments(clist)))
+
+ def doccomments(clist):
+ if len(clist) == 0:
+ return ""
+ s = "/// ```text"
+ for c in clist:
+ for cc in c.splitlines():
+ s += "\n/// " + cc
+ s += "\n/// ```\n///\n"
+ return s
+
+else:
+
+ def printComments(fd, clist, indent):
+ pass
+
+ def doccomments(clist):
+ return ""
+
+
+def firstCap(str):
+ return str[0].upper() + str[1:]
+
+
+# Attribute VTable Methods
+def attributeNativeName(a, getter):
+ binaryname = rustSanitize(a.binaryname if a.binaryname else firstCap(a.name))
+ return "%s%s" % ("Get" if getter else "Set", binaryname)
+
+
+def attributeReturnType(a, getter):
+ if a.notxpcom:
+ if getter:
+ return a.realtype.rustType("in").strip()
+ return "::libc::c_void"
+ return "::nserror::nsresult"
+
+
+def attributeParamName(a):
+ return "a" + firstCap(a.name)
+
+
+def attributeRawParamList(iface, a, getter):
+ if getter and a.notxpcom:
+ l = []
+ else:
+ l = [(attributeParamName(a), a.realtype.rustType("out" if getter else "in"))]
+ if a.implicit_jscontext:
+ raise xpidl.RustNoncompat("jscontext is unsupported")
+ if a.nostdcall:
+ raise xpidl.RustNoncompat("nostdcall is unsupported")
+ return l
+
+
+def attributeParamList(iface, a, getter):
+ l = ["this: *const " + iface.name]
+ l += ["%s: %s" % x for x in attributeRawParamList(iface, a, getter)]
+ return ", ".join(l)
+
+
+def attrAsVTableEntry(iface, m, getter):
+ try:
+ return 'pub %s: unsafe extern "system" fn (%s) -> %s' % (
+ attributeNativeName(m, getter),
+ attributeParamList(iface, m, getter),
+ attributeReturnType(m, getter),
+ )
+ except xpidl.RustNoncompat as reason:
+ return """\
+/// Unable to generate binding because `%s`
+pub %s: *const ::libc::c_void""" % (
+ reason,
+ attributeNativeName(m, getter),
+ )
+
+
+# Method VTable generation functions
+def methodNativeName(m):
+ binaryname = m.binaryname is not None and m.binaryname or firstCap(m.name)
+ return rustSanitize(binaryname)
+
+
+def methodReturnType(m):
+ if m.notxpcom:
+ return m.realtype.rustType("in").strip()
+ return "::nserror::nsresult"
+
+
+def methodRawParamList(iface, m):
+ l = [(rustSanitize(p.name), p.rustType()) for p in m.params]
+
+ if m.implicit_jscontext:
+ raise xpidl.RustNoncompat("jscontext is unsupported")
+
+ if m.optional_argc:
+ raise xpidl.RustNoncompat("optional_argc is unsupported")
+
+ if m.nostdcall:
+ raise xpidl.RustNoncompat("nostdcall is unsupported")
+
+ if not m.notxpcom and m.realtype.name != "void":
+ l.append(("_retval", m.realtype.rustType("out")))
+
+ return l
+
+
+def methodParamList(iface, m):
+ l = ["this: *const %s" % iface.name]
+ l += ["%s: %s" % x for x in methodRawParamList(iface, m)]
+ return ", ".join(l)
+
+
+def methodAsVTableEntry(iface, m):
+ try:
+ return 'pub %s: unsafe extern "system" fn (%s) -> %s' % (
+ methodNativeName(m),
+ methodParamList(iface, m),
+ methodReturnType(m),
+ )
+ except xpidl.RustNoncompat as reason:
+ return """\
+/// Unable to generate binding because `%s`
+pub %s: *const ::libc::c_void""" % (
+ reason,
+ methodNativeName(m),
+ )
+
+
+method_impl_tmpl = """\
+#[inline]
+pub unsafe fn %(name)s(&self, %(params)s) -> %(ret_ty)s {
+ ((*self.vtable).%(name)s)(self, %(args)s)
+}
+"""
+
+
+def methodAsWrapper(iface, m):
+ try:
+ param_list = methodRawParamList(iface, m)
+ params = ["%s: %s" % x for x in param_list]
+ args = [x[0] for x in param_list]
+
+ return method_impl_tmpl % {
+ "name": methodNativeName(m),
+ "params": ", ".join(params),
+ "ret_ty": methodReturnType(m),
+ "args": ", ".join(args),
+ }
+ except xpidl.RustNoncompat:
+ # Dummy field for the doc comments to attach to.
+ # Private so that it's not shown in rustdoc.
+ return "const _%s: () = ();" % methodNativeName(m)
+
+
+infallible_impl_tmpl = """\
+#[inline]
+pub unsafe fn %(name)s(&self) -> %(realtype)s {
+ let mut result = <%(realtype)s as ::std::default::Default>::default();
+ let _rv = ((*self.vtable).%(name)s)(self, &mut result);
+ debug_assert!(_rv.succeeded());
+ result
+}
+"""
+
+
+def attrAsWrapper(iface, m, getter):
+ try:
+ if m.implicit_jscontext:
+ raise xpidl.RustNoncompat("jscontext is unsupported")
+
+ if m.nostdcall:
+ raise xpidl.RustNoncompat("nostdcall is unsupported")
+
+ name = attributeParamName(m)
+
+ if getter and m.infallible and m.realtype.kind == "builtin":
+ # NOTE: We don't support non-builtin infallible getters in Rust code.
+ return infallible_impl_tmpl % {
+ "name": attributeNativeName(m, getter),
+ "realtype": m.realtype.rustType("in"),
+ }
+
+ param_list = attributeRawParamList(iface, m, getter)
+ params = ["%s: %s" % x for x in param_list]
+ return method_impl_tmpl % {
+ "name": attributeNativeName(m, getter),
+ "params": ", ".join(params),
+ "ret_ty": attributeReturnType(m, getter),
+ "args": "" if getter and m.notxpcom else name,
+ }
+
+ except xpidl.RustNoncompat:
+ # Dummy field for the doc comments to attach to.
+ # Private so that it's not shown in rustdoc.
+ return "const _%s: () = ();" % attributeNativeName(m, getter)
+
+
+header = """\
+//
+// DO NOT EDIT. THIS FILE IS GENERATED FROM $SRCDIR/%(relpath)s
+//
+
+"""
+
+
+def idl_basename(f):
+ """returns the base name of a file with the last extension stripped"""
+ return os.path.splitext(os.path.basename(f))[0]
+
+
+def print_rust_bindings(idl, fd, relpath):
+ fd = AutoIndent(fd)
+
+ fd.write(header % {"relpath": relpath})
+
+ # All of the idl files will be included into the same rust module, as we
+ # can't do forward declarations. Because of this, we want to ignore all
+ # import statements
+
+ for p in idl.productions:
+ if p.kind == "include" or p.kind == "cdata" or p.kind == "forward":
+ continue
+
+ if p.kind == "interface":
+ write_interface(p, fd)
+ continue
+
+ if p.kind == "typedef":
+ try:
+ # We have to skip the typedef of bool to bool (it doesn't make any sense anyways)
+ if p.name == "bool":
+ continue
+
+ if printdoccomments:
+ fd.write(
+ "/// `typedef %s %s;`\n///\n"
+ % (p.realtype.nativeType("in"), p.name)
+ )
+ fd.write(doccomments(p.doccomments))
+ fd.write("pub type %s = %s;\n\n" % (p.name, p.realtype.rustType("in")))
+ except xpidl.RustNoncompat as reason:
+ fd.write(
+ "/* unable to generate %s typedef because `%s` */\n\n"
+ % (p.name, reason)
+ )
+
+
+base_vtable_tmpl = """
+/// We need to include the members from the base interface's vtable at the start
+/// of the VTable definition.
+pub __base: %sVTable,
+
+"""
+
+
+vtable_tmpl = """\
+// This struct represents the interface's VTable. A pointer to a statically
+// allocated version of this struct is at the beginning of every %(name)s
+// object. It contains one pointer field for each method in the interface. In
+// the case where we can't generate a binding for a method, we include a void
+// pointer.
+#[doc(hidden)]
+#[repr(C)]
+pub struct %(name)sVTable {%(base)s%(entries)s}
+
+"""
+
+
+# NOTE: This template is not generated for nsISupports, as it has no base interfaces.
+deref_tmpl = """\
+// Every interface struct type implements `Deref` to its base interface. This
+// causes methods on the base interfaces to be directly avaliable on the
+// object. For example, you can call `.AddRef` or `.QueryInterface` directly
+// on any interface which inherits from `nsISupports`.
+impl ::std::ops::Deref for %(name)s {
+ type Target = %(base)s;
+ #[inline]
+ fn deref(&self) -> &%(base)s {
+ unsafe {
+ ::std::mem::transmute(self)
+ }
+ }
+}
+
+// Ensure we can use .coerce() to cast to our base types as well. Any type which
+// our base interface can coerce from should be coercable from us as well.
+impl<T: %(base)sCoerce> %(name)sCoerce for T {
+ #[inline]
+ fn coerce_from(v: &%(name)s) -> &Self {
+ T::coerce_from(v)
+ }
+}
+"""
+
+
+struct_tmpl = """\
+// The actual type definition for the interface. This struct has methods
+// declared on it which will call through its vtable. You never want to pass
+// this type around by value, always pass it behind a reference.
+
+#[repr(C)]
+pub struct %(name)s {
+ vtable: &'static %(name)sVTable,
+
+ /// This field is a phantomdata to ensure that the VTable type and any
+ /// struct containing it is not safe to send across threads by default, as
+ /// XPCOM is generally not threadsafe.
+ ///
+ /// If this type is marked as [rust_sync], there will be explicit `Send` and
+ /// `Sync` implementations on this type, which will override the inherited
+ /// negative impls from `Rc`.
+ __nosync: ::std::marker::PhantomData<::std::rc::Rc<u8>>,
+
+ // Make the rust compiler aware that there might be interior mutability
+ // in what actually implements the interface. This works around UB
+ // introduced by https://github.com/llvm/llvm-project/commit/01859da84bad95fd51d6a03b08b60c660e642a4f
+ // that a rust lint would make blatantly obvious, but doesn't exist.
+ // (See https://github.com/rust-lang/rust/issues/111229).
+ // This prevents optimizations, but those optimizations weren't available
+ // before rustc switched to LLVM 16, and they now cause problems because
+ // of the UB.
+ // Until there's a lint available to find all our UB, it's simpler to
+ // avoid the UB in the first place, at the cost of preventing optimizations
+ // in places that don't cause UB. But again, those optimizations weren't
+ // available before.
+ __maybe_interior_mutability: ::std::cell::UnsafeCell<[u8; 0]>,
+}
+
+// Implementing XpCom for an interface exposes its IID, which allows for easy
+// use of the `.query_interface<T>` helper method. This also defines that
+// method for %(name)s.
+unsafe impl XpCom for %(name)s {
+ const IID: nsIID = nsID(0x%(m0)s, 0x%(m1)s, 0x%(m2)s,
+ [%(m3joined)s]);
+}
+
+// We need to implement the RefCounted trait so we can be used with `RefPtr`.
+// This trait teaches `RefPtr` how to manage our memory.
+unsafe impl RefCounted for %(name)s {
+ #[inline]
+ unsafe fn addref(&self) {
+ self.AddRef();
+ }
+ #[inline]
+ unsafe fn release(&self) {
+ self.Release();
+ }
+}
+
+// This trait is implemented on all types which can be coerced to from %(name)s.
+// It is used in the implementation of `fn coerce<T>`. We hide it from the
+// documentation, because it clutters it up a lot.
+#[doc(hidden)]
+pub trait %(name)sCoerce {
+ /// Cheaply cast a value of this type from a `%(name)s`.
+ fn coerce_from(v: &%(name)s) -> &Self;
+}
+
+// The trivial implementation: We can obviously coerce ourselves to ourselves.
+impl %(name)sCoerce for %(name)s {
+ #[inline]
+ fn coerce_from(v: &%(name)s) -> &Self {
+ v
+ }
+}
+
+impl %(name)s {
+ /// Cast this `%(name)s` to one of its base interfaces.
+ #[inline]
+ pub fn coerce<T: %(name)sCoerce>(&self) -> &T {
+ T::coerce_from(self)
+ }
+}
+"""
+
+
+sendsync_tmpl = """\
+// This interface is marked as [rust_sync], meaning it is safe to be transferred
+// and used from multiple threads silmultaneously. These override the default
+// from the __nosync marker type allowng the type to be sent between threads.
+unsafe impl Send for %(name)s {}
+unsafe impl Sync for %(name)s {}
+"""
+
+
+wrapper_tmpl = """\
+// The implementations of the function wrappers which are exposed to rust code.
+// Call these methods rather than manually calling through the VTable struct.
+impl %(name)s {
+%(consts)s
+%(methods)s
+}
+
+"""
+
+vtable_entry_tmpl = """\
+/* %(idl)s */
+%(entry)s,
+"""
+
+
+const_wrapper_tmpl = """\
+%(docs)s
+pub const %(name)s: %(type)s = %(val)s;
+"""
+
+
+method_wrapper_tmpl = """\
+%(docs)s
+/// `%(idl)s`
+%(wrapper)s
+"""
+
+
+uuid_decoder = re.compile(
+ r"""(?P<m0>[a-f0-9]{8})-
+ (?P<m1>[a-f0-9]{4})-
+ (?P<m2>[a-f0-9]{4})-
+ (?P<m3>[a-f0-9]{4})-
+ (?P<m4>[a-f0-9]{12})$""",
+ re.X,
+)
+
+
+def write_interface(iface, fd):
+ if iface.namemap is None:
+ raise Exception("Interface was not resolved.")
+
+ assert iface.base or (iface.name == "nsISupports")
+
+ # Extract the UUID's information so that it can be written into the struct definition
+ names = uuid_decoder.match(iface.attributes.uuid).groupdict()
+ m3str = names["m3"] + names["m4"]
+ names["m3joined"] = ", ".join(["0x%s" % m3str[i : i + 2] for i in range(0, 16, 2)])
+ names["name"] = iface.name
+
+ if printdoccomments:
+ if iface.base is not None:
+ fd.write("/// `interface %s : %s`\n///\n" % (iface.name, iface.base))
+ else:
+ fd.write("/// `interface %s`\n///\n" % iface.name)
+ printComments(fd, iface.doccomments, "")
+ fd.write(struct_tmpl % names)
+
+ if iface.attributes.rust_sync:
+ fd.write(sendsync_tmpl % {"name": iface.name})
+
+ if iface.base is not None:
+ fd.write(
+ deref_tmpl
+ % {
+ "name": iface.name,
+ "base": iface.base,
+ }
+ )
+
+ entries = []
+ for member in iface.members:
+ if type(member) == xpidl.Attribute:
+ entries.append(
+ vtable_entry_tmpl
+ % {
+ "idl": member.toIDL(),
+ "entry": attrAsVTableEntry(iface, member, True),
+ }
+ )
+ if not member.readonly:
+ entries.append(
+ vtable_entry_tmpl
+ % {
+ "idl": member.toIDL(),
+ "entry": attrAsVTableEntry(iface, member, False),
+ }
+ )
+
+ elif type(member) == xpidl.Method:
+ entries.append(
+ vtable_entry_tmpl
+ % {
+ "idl": member.toIDL(),
+ "entry": methodAsVTableEntry(iface, member),
+ }
+ )
+
+ fd.write(
+ vtable_tmpl
+ % {
+ "name": iface.name,
+ "base": base_vtable_tmpl % iface.base if iface.base is not None else "",
+ "entries": "\n".join(entries),
+ }
+ )
+
+ # Get all of the constants
+ consts = []
+ for member in iface.members:
+ if type(member) == xpidl.ConstMember:
+ consts.append(
+ const_wrapper_tmpl
+ % {
+ "docs": doccomments(member.doccomments),
+ "type": member.realtype.rustType("in"),
+ "name": member.name,
+ "val": member.getValue(),
+ }
+ )
+ if type(member) == xpidl.CEnum:
+ for var in member.variants:
+ consts.append(
+ const_wrapper_tmpl
+ % {
+ "docs": "",
+ "type": member.rustType("in"),
+ "name": var.name,
+ "val": var.getValue(),
+ }
+ )
+
+ methods = []
+ for member in iface.members:
+ if type(member) == xpidl.Attribute:
+ methods.append(
+ method_wrapper_tmpl
+ % {
+ "docs": doccomments(member.doccomments),
+ "idl": member.toIDL(),
+ "wrapper": attrAsWrapper(iface, member, True),
+ }
+ )
+ if not member.readonly:
+ methods.append(
+ method_wrapper_tmpl
+ % {
+ "docs": doccomments(member.doccomments),
+ "idl": member.toIDL(),
+ "wrapper": attrAsWrapper(iface, member, False),
+ }
+ )
+
+ elif type(member) == xpidl.Method:
+ methods.append(
+ method_wrapper_tmpl
+ % {
+ "docs": doccomments(member.doccomments),
+ "idl": member.toIDL(),
+ "wrapper": methodAsWrapper(iface, member),
+ }
+ )
+
+ fd.write(
+ wrapper_tmpl
+ % {
+ "name": iface.name,
+ "consts": "\n".join(consts),
+ "methods": "\n".join(methods),
+ }
+ )
diff --git a/xpcom/idl-parser/xpidl/rust_macros.py b/xpcom/idl-parser/xpidl/rust_macros.py
new file mode 100644
index 0000000000..bc1788b96c
--- /dev/null
+++ b/xpcom/idl-parser/xpidl/rust_macros.py
@@ -0,0 +1,111 @@
+# rust_macros.py - Generate rust_macros bindings from IDL.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+"""Generate rust bindings information for the IDL file specified"""
+
+from xpidl import rust, xpidl
+
+derive_method_tmpl = """\
+Method {
+ name: "%(name)s",
+ params: &[%(params)s],
+ ret: "%(ret)s",
+}"""
+
+
+def attrAsMethodStruct(iface, m, getter):
+ params = [
+ 'Param { name: "%s", ty: "%s" }' % x
+ for x in rust.attributeRawParamList(iface, m, getter)
+ ]
+ return derive_method_tmpl % {
+ "name": rust.attributeNativeName(m, getter),
+ "params": ", ".join(params),
+ "ret": "::nserror::nsresult",
+ }
+
+
+def methodAsMethodStruct(iface, m):
+ params = [
+ 'Param { name: "%s", ty: "%s" }' % x for x in rust.methodRawParamList(iface, m)
+ ]
+ return derive_method_tmpl % {
+ "name": rust.methodNativeName(m),
+ "params": ", ".join(params),
+ "ret": rust.methodReturnType(m),
+ }
+
+
+derive_iface_tmpl = """\
+Interface {
+ name: "%(name)s",
+ base: %(base)s,
+ sync: %(sync)s,
+ methods: %(methods)s,
+},
+"""
+
+
+def write_interface(iface, fd):
+ if iface.namemap is None:
+ raise Exception("Interface was not resolved.")
+
+ assert iface.base or (iface.name == "nsISupports")
+
+ base = 'Some("%s")' % iface.base if iface.base is not None else "None"
+ try:
+ methods = ""
+ for member in iface.members:
+ if type(member) == xpidl.Attribute:
+ methods += "/* %s */\n" % member.toIDL()
+ methods += "%s,\n" % attrAsMethodStruct(iface, member, True)
+ if not member.readonly:
+ methods += "%s,\n" % attrAsMethodStruct(iface, member, False)
+ methods += "\n"
+
+ elif type(member) == xpidl.Method:
+ methods += "/* %s */\n" % member.toIDL()
+ methods += "%s,\n\n" % methodAsMethodStruct(iface, member)
+ fd.write(
+ derive_iface_tmpl
+ % {
+ "name": iface.name,
+ "base": base,
+ "sync": "true" if iface.attributes.rust_sync else "false",
+ "methods": "Ok(&[\n%s])" % methods,
+ }
+ )
+ except xpidl.RustNoncompat as reason:
+ fd.write(
+ derive_iface_tmpl
+ % {
+ "name": iface.name,
+ "base": base,
+ "sync": "false",
+ "methods": 'Err("%s")' % reason,
+ }
+ )
+
+
+header = """\
+//
+// DO NOT EDIT. THIS FILE IS GENERATED FROM $SRCDIR/%(relpath)s
+//
+
+"""
+
+
+def print_rust_macros_bindings(idl, fd, relpath):
+ fd = rust.AutoIndent(fd)
+
+ fd.write(header % {"relpath": relpath})
+ fd.write("{static D: &[Interface] = &[\n")
+
+ for p in idl.productions:
+ if p.kind == "interface":
+ write_interface(p, fd)
+
+ fd.write("]; D}\n")
diff --git a/xpcom/idl-parser/xpidl/xpidl.py b/xpcom/idl-parser/xpidl/xpidl.py
new file mode 100755
index 0000000000..b95fd14bc5
--- /dev/null
+++ b/xpcom/idl-parser/xpidl/xpidl.py
@@ -0,0 +1,2124 @@
+#!/usr/bin/env python
+# xpidl.py - A parser for cross-platform IDL (XPIDL) files.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+"""A parser for cross-platform IDL (XPIDL) files."""
+
+import os.path
+import re
+import sys
+import textwrap
+from collections import namedtuple
+
+import six
+from ply import lex, yacc
+
+"""A type conforms to the following pattern:
+
+ def nativeType(self, calltype):
+ 'returns a string representation of the native type
+ calltype must be 'in', 'out', 'inout', or 'element'
+
+Interface members const/method/attribute conform to the following pattern:
+
+ name = 'string'
+
+ def toIDL(self):
+ 'returns the member signature as IDL'
+"""
+
+
+# XXX(nika): Fix the IDL files which do this so we can remove this list?
+def rustPreventForward(s):
+ """These types are foward declared as interfaces, but never actually defined
+ in IDL files. We don't want to generate references to them in rust for that
+ reason."""
+ return s in (
+ "nsIFrame",
+ "nsSubDocumentFrame",
+ )
+
+
+def attlistToIDL(attlist):
+ if len(attlist) == 0:
+ return ""
+
+ attlist = list(attlist)
+ attlist.sort(key=lambda a: a[0])
+
+ return "[%s] " % ",".join(
+ [
+ "%s%s" % (name, value is not None and "(%s)" % value or "")
+ for name, value, aloc in attlist
+ ]
+ )
+
+
+_paramsHardcode = {
+ 2: ("array", "shared", "iid_is", "size_is", "retval"),
+ 3: ("array", "size_is", "const"),
+}
+
+
+def paramAttlistToIDL(attlist):
+ if len(attlist) == 0:
+ return ""
+
+ # Hack alert: g_hash_table_foreach is pretty much unimitatable... hardcode
+ # quirk
+ attlist = list(attlist)
+ sorted = []
+ if len(attlist) in _paramsHardcode:
+ for p in _paramsHardcode[len(attlist)]:
+ i = 0
+ while i < len(attlist):
+ if attlist[i][0] == p:
+ sorted.append(attlist[i])
+ del attlist[i]
+ continue
+
+ i += 1
+
+ sorted.extend(attlist)
+
+ return "[%s] " % ", ".join(
+ [
+ "%s%s" % (name, value is not None and " (%s)" % value or "")
+ for name, value, aloc in sorted
+ ]
+ )
+
+
+def unaliasType(t):
+ while t.kind == "typedef":
+ t = t.realtype
+ assert t is not None
+ return t
+
+
+def getBuiltinOrNativeTypeName(t):
+ t = unaliasType(t)
+ if t.kind == "builtin":
+ return t.name
+ elif t.kind == "native":
+ assert t.specialtype is not None
+ return "[%s]" % t.specialtype
+ else:
+ return None
+
+
+class BuiltinLocation(object):
+ def get(self):
+ return "<builtin type>"
+
+ def __str__(self):
+ return self.get()
+
+
+class Builtin(object):
+ kind = "builtin"
+ location = BuiltinLocation
+
+ def __init__(self, name, nativename, rustname, signed=False, maybeConst=False):
+ self.name = name
+ self.nativename = nativename
+ self.rustname = rustname
+ self.signed = signed
+ self.maybeConst = maybeConst
+
+ def isPointer(self):
+ """Check if this type is a pointer type - this will control how pointers act"""
+ return self.nativename.endswith("*")
+
+ def nativeType(self, calltype, shared=False, const=False):
+ if self.name in ["string", "wstring"] and calltype == "element":
+ raise IDLError(
+ "Use string class types for string Array elements", self.location
+ )
+
+ if const:
+ print(
+ IDLError(
+ "[const] doesn't make sense on builtin types.",
+ self.location,
+ warning=True,
+ ),
+ file=sys.stderr,
+ )
+ const = "const "
+ elif calltype == "in" and self.isPointer():
+ const = "const "
+ elif shared:
+ if not self.isPointer():
+ raise IDLError(
+ "[shared] not applicable to non-pointer types.", self.location
+ )
+ const = "const "
+ else:
+ const = ""
+ return "%s%s %s" % (const, self.nativename, "*" if "out" in calltype else "")
+
+ def rustType(self, calltype, shared=False, const=False):
+ # We want to rewrite any *mut pointers to *const pointers if constness
+ # was requested.
+ const = const or ("out" not in calltype and self.isPointer()) or shared
+ rustname = self.rustname
+ if const and self.isPointer():
+ rustname = self.rustname.replace("*mut", "*const")
+
+ return "%s%s" % ("*mut " if "out" in calltype else "", rustname)
+
+
+builtinNames = [
+ Builtin("boolean", "bool", "bool"),
+ Builtin("void", "void", "libc::c_void"),
+ Builtin("octet", "uint8_t", "u8", False, True),
+ Builtin("short", "int16_t", "i16", True, True),
+ Builtin("long", "int32_t", "i32", True, True),
+ Builtin("long long", "int64_t", "i64", True, True),
+ Builtin("unsigned short", "uint16_t", "u16", False, True),
+ Builtin("unsigned long", "uint32_t", "u32", False, True),
+ Builtin("unsigned long long", "uint64_t", "u64", False, True),
+ Builtin("float", "float", "libc::c_float", True, False),
+ Builtin("double", "double", "libc::c_double", True, False),
+ Builtin("char", "char", "libc::c_char", True, False),
+ Builtin("string", "char *", "*const libc::c_char", False, False),
+ Builtin("wchar", "char16_t", "u16", False, False),
+ Builtin("wstring", "char16_t *", "*const u16", False, False),
+ # As seen in mfbt/RefCountType.h, this type has special handling to
+ # maintain binary compatibility with MSCOM's IUnknown that cannot be
+ # expressed in XPIDL.
+ Builtin(
+ "MozExternalRefCountType", "MozExternalRefCountType", "MozExternalRefCountType"
+ ),
+]
+
+builtinMap = {}
+for b in builtinNames:
+ builtinMap[b.name] = b
+
+
+class Location(object):
+ _line = None
+
+ def __init__(self, lexer, lineno, lexpos):
+ self._lineno = lineno
+ self._lexpos = lexpos
+ self._lexdata = lexer.lexdata
+ self._file = getattr(lexer, "filename", "<unknown>")
+
+ def __eq__(self, other):
+ return self._lexpos == other._lexpos and self._file == other._file
+
+ def resolve(self):
+ if self._line:
+ return
+
+ startofline = self._lexdata.rfind("\n", 0, self._lexpos) + 1
+ endofline = self._lexdata.find("\n", self._lexpos, self._lexpos + 80)
+ self._line = self._lexdata[startofline:endofline]
+ self._colno = self._lexpos - startofline
+
+ def pointerline(self):
+ def i():
+ for i in range(0, self._colno):
+ yield " "
+ yield "^"
+
+ return "".join(i())
+
+ def get(self):
+ self.resolve()
+ return "%s line %s:%s" % (self._file, self._lineno, self._colno)
+
+ def __str__(self):
+ self.resolve()
+ return "%s line %s:%s\n%s\n%s" % (
+ self._file,
+ self._lineno,
+ self._colno,
+ self._line,
+ self.pointerline(),
+ )
+
+
+class NameMap(object):
+ """Map of name -> object. Each object must have a .name and .location property.
+ Setting the same name twice throws an error."""
+
+ def __init__(self):
+ self._d = {}
+
+ def __getitem__(self, key):
+ if key in builtinMap:
+ return builtinMap[key]
+ return self._d[key]
+
+ def __iter__(self):
+ return six.itervalues(self._d)
+
+ def __contains__(self, key):
+ return key in builtinMap or key in self._d
+
+ def set(self, object):
+ if object.name in builtinMap:
+ raise IDLError(
+ "name '%s' is a builtin and cannot be redeclared" % (object.name),
+ object.location,
+ )
+ if object.name.startswith("_"):
+ object.name = object.name[1:]
+ if object.name in self._d:
+ old = self._d[object.name]
+ if old == object:
+ return
+ if isinstance(old, Forward) and isinstance(object, Interface):
+ self._d[object.name] = object
+ elif isinstance(old, Interface) and isinstance(object, Forward):
+ pass
+ else:
+ raise IDLError(
+ "name '%s' specified twice. Previous location: %s"
+ % (object.name, self._d[object.name].location),
+ object.location,
+ )
+ else:
+ self._d[object.name] = object
+
+ def get(self, id, location):
+ try:
+ return self[id]
+ except KeyError:
+ raise IDLError(f"Name '{id}' not found", location)
+
+
+class RustNoncompat(Exception):
+ """
+ This exception is raised when a particular type or function cannot be safely exposed to
+ rust code
+ """
+
+ def __init__(self, reason):
+ self.reason = reason
+
+ def __str__(self):
+ return self.reason
+
+
+class IDLError(Exception):
+ def __init__(self, message, location, warning=False, notes=None):
+ self.message = message
+ self.location = location
+ self.warning = warning
+ self.notes = notes
+
+ def __str__(self):
+ error = "%s: %s, %s" % (
+ self.warning and "warning" or "error",
+ self.message,
+ self.location,
+ )
+ if self.notes is not None:
+ error += "\nnote: %s" % self.notes
+ return error
+
+
+class Include(object):
+ kind = "include"
+
+ def __init__(self, filename, location):
+ self.filename = filename
+ self.location = location
+
+ def __str__(self):
+ return "".join(["include '%s'\n" % self.filename])
+
+ def resolve(self, parent):
+ def incfiles():
+ yield self.filename
+ for dir in parent.incdirs:
+ yield os.path.join(dir, self.filename)
+
+ for file in incfiles():
+ if not os.path.exists(file):
+ continue
+
+ if file in parent.includeCache:
+ self.IDL = parent.includeCache[file]
+ else:
+ self.IDL = parent.parser.parse(
+ open(file, encoding="utf-8").read(), filename=file
+ )
+ self.IDL.resolve(
+ parent.incdirs,
+ parent.parser,
+ parent.webidlconfig,
+ parent.includeCache,
+ )
+ parent.includeCache[file] = self.IDL
+
+ for type in self.IDL.getNames():
+ parent.setName(type)
+ parent.deps.extend(self.IDL.deps)
+ return
+
+ raise IDLError("File '%s' not found" % self.filename, self.location)
+
+
+class IDL(object):
+ def __init__(self, productions):
+ self.hasSequence = False
+ self.productions = productions
+ self.deps = []
+
+ def setName(self, object):
+ self.namemap.set(object)
+
+ def getName(self, id, location):
+ if id.name == "Array":
+ if id.params is None or len(id.params) != 1:
+ raise IDLError("Array takes exactly 1 parameter", location)
+ self.hasSequence = True
+ return Array(self.getName(id.params[0], location), location)
+
+ if id.params is not None:
+ raise IDLError("Generic type '%s' unrecognized" % id.name, location)
+
+ try:
+ return self.namemap[id.name]
+ except KeyError:
+ raise IDLError("type '%s' not found" % id.name, location)
+
+ def hasName(self, id):
+ return id in self.namemap
+
+ def getNames(self):
+ return iter(self.namemap)
+
+ def __str__(self):
+ return "".join([str(p) for p in self.productions])
+
+ def resolve(self, incdirs, parser, webidlconfig, includeCache=None):
+ self.namemap = NameMap()
+ self.incdirs = incdirs
+ self.parser = parser
+ self.webidlconfig = webidlconfig
+ self.includeCache = {} if includeCache is None else includeCache
+ for p in self.productions:
+ p.resolve(self)
+
+ def includes(self):
+ for p in self.productions:
+ if p.kind == "include":
+ yield p
+ if self.hasSequence:
+ yield Include("nsTArray.h", BuiltinLocation)
+
+ def needsJSTypes(self):
+ for p in self.productions:
+ if p.kind == "interface" and p.needsJSTypes():
+ return True
+ return False
+
+
+class CDATA(object):
+ kind = "cdata"
+ _re = re.compile(r"\n+")
+
+ def __init__(self, data, location):
+ self.data = self._re.sub("\n", data)
+ self.location = location
+
+ def resolve(self, parent):
+ # This can be a false-positive if the word `virtual` is included in a
+ # comment, however this doesn't seem to happen very often.
+ if isinstance(parent, Interface) and re.search(r"\bvirtual\b", self.data):
+ raise IDLError(
+ "cannot declare a C++ `virtual` member in XPIDL interface",
+ self.location,
+ notes=textwrap.fill(
+ """All virtual members must be declared directly using XPIDL.
+ Both the Rust bindings and XPConnect rely on the per-platform
+ vtable layouts generated by the XPIDL compiler to allow
+ cross-language XPCOM method calls between JS and C++.
+ Consider using a `[notxpcom, nostdcall]` method instead."""
+ ),
+ )
+
+ def __str__(self):
+ return "cdata: %s\n\t%r\n" % (self.location.get(), self.data)
+
+ def count(self):
+ return 0
+
+
+class Typedef(object):
+ kind = "typedef"
+
+ def __init__(self, type, name, location, doccomments):
+ self.type = type
+ self.name = name
+ self.location = location
+ self.doccomments = doccomments
+
+ def __eq__(self, other):
+ return self.name == other.name and self.type == other.type
+
+ def resolve(self, parent):
+ parent.setName(self)
+ self.realtype = parent.getName(self.type, self.location)
+
+ if not isinstance(self.realtype, (Builtin, CEnum, Native, Typedef)):
+ raise IDLError("Unsupported typedef target type", self.location)
+
+ def nativeType(self, calltype):
+ return "%s %s" % (self.name, "*" if "out" in calltype else "")
+
+ def rustType(self, calltype):
+ if self.name == "nsresult":
+ return "%s::nserror::nsresult" % ("*mut " if "out" in calltype else "")
+
+ return "%s%s" % ("*mut " if "out" in calltype else "", self.name)
+
+ def __str__(self):
+ return "typedef %s %s\n" % (self.type, self.name)
+
+
+class Forward(object):
+ kind = "forward"
+
+ def __init__(self, name, location, doccomments):
+ self.name = name
+ self.location = location
+ self.doccomments = doccomments
+
+ def __eq__(self, other):
+ return other.kind == "forward" and other.name == self.name
+
+ def resolve(self, parent):
+ # Hack alert: if an identifier is already present, move the doccomments
+ # forward.
+ if parent.hasName(self.name):
+ for i in range(0, len(parent.productions)):
+ if parent.productions[i] is self:
+ break
+ for i in range(i + 1, len(parent.productions)):
+ if hasattr(parent.productions[i], "doccomments"):
+ parent.productions[i].doccomments[0:0] = self.doccomments
+ break
+
+ parent.setName(self)
+
+ def nativeType(self, calltype):
+ if calltype == "element":
+ return "RefPtr<%s>" % self.name
+ return "%s *%s" % (self.name, "*" if "out" in calltype else "")
+
+ def rustType(self, calltype):
+ if rustPreventForward(self.name):
+ raise RustNoncompat("forward declaration %s is unsupported" % self.name)
+ if calltype == "element":
+ return "Option<RefPtr<%s>>" % self.name
+ return "%s*const %s" % ("*mut" if "out" in calltype else "", self.name)
+
+ def __str__(self):
+ return "forward-declared %s\n" % self.name
+
+
+class Native(object):
+ kind = "native"
+
+ modifier = None
+ specialtype = None
+
+ # A tuple type here means that a custom value is used for each calltype:
+ # (in, out/inout, array element) respectively.
+ # A `None` here means that the written type should be used as-is.
+ specialtypes = {
+ "nsid": None,
+ "utf8string": ("const nsACString&", "nsACString&", "nsCString"),
+ "cstring": ("const nsACString&", "nsACString&", "nsCString"),
+ "astring": ("const nsAString&", "nsAString&", "nsString"),
+ "jsval": ("JS::Handle<JS::Value>", "JS::MutableHandle<JS::Value>", "JS::Value"),
+ "promise": "::mozilla::dom::Promise",
+ }
+
+ def __init__(self, name, nativename, attlist, location):
+ self.name = name
+ self.nativename = nativename
+ self.location = location
+
+ for name, value, aloc in attlist:
+ if value is not None:
+ raise IDLError("Unexpected attribute value", aloc)
+ if name in ("ptr", "ref"):
+ if self.modifier is not None:
+ raise IDLError("More than one ptr/ref modifier", aloc)
+ self.modifier = name
+ elif name in self.specialtypes.keys():
+ if self.specialtype is not None:
+ raise IDLError("More than one special type", aloc)
+ self.specialtype = name
+ if self.specialtypes[name] is not None:
+ self.nativename = self.specialtypes[name]
+ else:
+ raise IDLError("Unexpected attribute", aloc)
+
+ def __eq__(self, other):
+ return (
+ self.name == other.name
+ and self.nativename == other.nativename
+ and self.modifier == other.modifier
+ and self.specialtype == other.specialtype
+ )
+
+ def resolve(self, parent):
+ parent.setName(self)
+
+ def isPtr(self, calltype):
+ return self.modifier == "ptr"
+
+ def isRef(self, calltype):
+ return self.modifier == "ref"
+
+ def nativeType(self, calltype, const=False, shared=False):
+ if shared:
+ if calltype != "out":
+ raise IDLError(
+ "[shared] only applies to out parameters.", self.location
+ )
+ const = True
+
+ if isinstance(self.nativename, tuple):
+ if calltype == "in":
+ return self.nativename[0] + " "
+ elif "out" in calltype:
+ return self.nativename[1] + " "
+ else:
+ return self.nativename[2] + " "
+
+ # 'in' nsid parameters should be made 'const'
+ if self.specialtype == "nsid" and calltype == "in":
+ const = True
+
+ if calltype == "element":
+ if self.specialtype == "nsid":
+ if self.isPtr(calltype):
+ raise IDLError(
+ "Array<nsIDPtr> not yet supported. "
+ "File an XPConnect bug if you need it.",
+ self.location,
+ )
+
+ # ns[CI]?IDs should be held directly in Array<T>s
+ return self.nativename
+
+ if self.isRef(calltype):
+ raise IDLError(
+ "[ref] qualified type unsupported in Array<T>", self.location
+ )
+
+ # Promises should be held in RefPtr<T> in Array<T>s
+ if self.specialtype == "promise":
+ return "RefPtr<mozilla::dom::Promise>"
+
+ if self.isRef(calltype):
+ m = "& " # [ref] is always passed with a single indirection
+ else:
+ m = "* " if "out" in calltype else ""
+ if self.isPtr(calltype):
+ m += "* "
+ return "%s%s %s" % (const and "const " or "", self.nativename, m)
+
+ def rustType(self, calltype, const=False, shared=False):
+ # For the most part, 'native' types don't make sense in rust, as they
+ # are native C++ types. However, we can support a few types here, as
+ # they're important and can easily be translated.
+ #
+ # NOTE: This code doesn't try to perfectly match C++ constness, as
+ # constness doesn't affect ABI, and raw pointers are already unsafe.
+
+ if self.modifier not in ["ptr", "ref"]:
+ raise RustNoncompat("Rust only supports [ref] / [ptr] native types")
+
+ if shared:
+ if calltype != "out":
+ raise IDLError(
+ "[shared] only applies to out parameters.", self.location
+ )
+ const = True
+
+ # 'in' nsid parameters should be made 'const'
+ if self.specialtype == "nsid" and calltype == "in":
+ const = True
+
+ prefix = "*const " if const or shared else "*mut "
+ if "out" in calltype and self.isPtr(calltype):
+ prefix = "*mut " + prefix
+
+ if self.specialtype:
+ # The string types are very special, and need to be handled seperately.
+ if self.specialtype in ["cstring", "utf8string"]:
+ if calltype == "in":
+ return "*const ::nsstring::nsACString"
+ elif "out" in calltype:
+ return "*mut ::nsstring::nsACString"
+ else:
+ return "::nsstring::nsCString"
+ if self.specialtype == "astring":
+ if calltype == "in":
+ return "*const ::nsstring::nsAString"
+ elif "out" in calltype:
+ return "*mut ::nsstring::nsAString"
+ else:
+ return "::nsstring::nsString"
+ # nsid has some special handling, but generally re-uses the generic
+ # prefix handling above.
+ if self.specialtype == "nsid":
+ if "element" in calltype:
+ if self.isPtr(calltype):
+ raise IDLError(
+ "Array<nsIDPtr> not yet supported. "
+ "File an XPConnect bug if you need it.",
+ self.location,
+ )
+ return self.nativename
+ return prefix + self.nativename
+ raise RustNoncompat("special type %s unsupported" % self.specialtype)
+
+ # These 3 special types correspond to native pointer types which can
+ # generally be supported behind pointers. Other types are not supported
+ # for now.
+ if self.nativename == "void":
+ return prefix + "libc::c_void"
+ if self.nativename == "char":
+ return prefix + "libc::c_char"
+ if self.nativename == "char16_t":
+ return prefix + "u16"
+
+ raise RustNoncompat("native type %s unsupported" % self.nativename)
+
+ def __str__(self):
+ return "native %s(%s)\n" % (self.name, self.nativename)
+
+
+class WebIDL(object):
+ kind = "webidl"
+
+ def __init__(self, name, location):
+ self.name = name
+ self.location = location
+
+ def __eq__(self, other):
+ return other.kind == "webidl" and self.name == other.name
+
+ def resolve(self, parent):
+ # XXX(nika): We don't handle _every_ kind of webidl object here (as that
+ # would be hard). For example, we don't support nsIDOM*-defaulting
+ # interfaces.
+ # TODO: More explicit compile-time checks?
+
+ assert (
+ parent.webidlconfig is not None
+ ), "WebIDL declarations require passing webidlconfig to resolve."
+
+ # Resolve our native name according to the WebIDL configs.
+ config = parent.webidlconfig.get(self.name, {})
+ self.native = config.get("nativeType")
+ if self.native is None:
+ self.native = "mozilla::dom::%s" % self.name
+ self.headerFile = config.get("headerFile")
+ if self.headerFile is None:
+ self.headerFile = self.native.replace("::", "/") + ".h"
+
+ parent.setName(self)
+
+ def nativeType(self, calltype, const=False):
+ if calltype == "element":
+ return "RefPtr<%s%s>" % ("const " if const else "", self.native)
+ return "%s%s *%s" % (
+ "const " if const else "",
+ self.native,
+ "*" if "out" in calltype else "",
+ )
+
+ def rustType(self, calltype, const=False):
+ # Just expose the type as a void* - we can't do any better.
+ return "%s*const libc::c_void" % ("*mut " if "out" in calltype else "")
+
+ def __str__(self):
+ return "webidl %s\n" % self.name
+
+
+class Interface(object):
+ kind = "interface"
+
+ def __init__(self, name, attlist, base, members, location, doccomments):
+ self.name = name
+ self.attributes = InterfaceAttributes(attlist, location)
+ self.base = base
+ self.members = members
+ self.location = location
+ self.namemap = NameMap()
+ self.doccomments = doccomments
+ self.nativename = name
+
+ for m in members:
+ if not isinstance(m, CDATA):
+ self.namemap.set(m)
+
+ def __eq__(self, other):
+ return self.name == other.name and self.location == other.location
+
+ def resolve(self, parent):
+ self.idl = parent
+
+ if not self.attributes.scriptable and self.attributes.builtinclass:
+ raise IDLError(
+ "Non-scriptable interface '%s' doesn't need to be marked builtinclass"
+ % self.name,
+ self.location,
+ )
+
+ # Hack alert: if an identifier is already present, libIDL assigns
+ # doc comments incorrectly. This is quirks-mode extraordinaire!
+ if parent.hasName(self.name):
+ for member in self.members:
+ if hasattr(member, "doccomments"):
+ member.doccomments[0:0] = self.doccomments
+ break
+ self.doccomments = parent.getName(TypeId(self.name), None).doccomments
+
+ if self.attributes.function:
+ has_method = False
+ for member in self.members:
+ if member.kind == "method":
+ if has_method:
+ raise IDLError(
+ "interface '%s' has multiple methods, but marked 'function'"
+ % self.name,
+ self.location,
+ )
+ else:
+ has_method = True
+
+ parent.setName(self)
+ if self.base is not None:
+ realbase = parent.getName(TypeId(self.base), self.location)
+ if realbase.kind != "interface":
+ raise IDLError(
+ "interface '%s' inherits from non-interface type '%s'"
+ % (self.name, self.base),
+ self.location,
+ )
+
+ if self.attributes.scriptable and not realbase.attributes.scriptable:
+ raise IDLError(
+ "interface '%s' is scriptable but derives from "
+ "non-scriptable '%s'" % (self.name, self.base),
+ self.location,
+ warning=True,
+ )
+
+ if (
+ self.attributes.scriptable
+ and realbase.attributes.builtinclass
+ and not self.attributes.builtinclass
+ ):
+ raise IDLError(
+ "interface '%s' is not builtinclass but derives from "
+ "builtinclass '%s'" % (self.name, self.base),
+ self.location,
+ )
+
+ if realbase.attributes.rust_sync and not self.attributes.rust_sync:
+ raise IDLError(
+ "interface '%s' is not rust_sync but derives from rust_sync '%s'"
+ % (self.name, self.base),
+ self.location,
+ )
+
+ if (
+ self.attributes.rust_sync
+ and self.attributes.scriptable
+ and not self.attributes.builtinclass
+ ):
+ raise IDLError(
+ "interface '%s' is rust_sync but is not builtinclass" % self.name,
+ self.location,
+ )
+ elif self.name != "nsISupports":
+ raise IDLError(
+ "Interface '%s' must inherit from nsISupports" % self.name,
+ self.location,
+ )
+
+ for member in self.members:
+ member.resolve(self)
+
+ # The number 250 is NOT arbitrary; this number is the maximum number of
+ # stub entries defined in xpcom/reflect/xptcall/genstubs.pl
+ # Do not increase this value without increasing the number in that
+ # location, or you WILL cause otherwise unknown problems!
+ if self.countEntries() > 250 and not self.attributes.builtinclass:
+ raise IDLError(
+ "interface '%s' has too many entries" % self.name, self.location
+ )
+
+ def nativeType(self, calltype, const=False):
+ if calltype == "element":
+ return "RefPtr<%s>" % self.name
+ return "%s%s *%s" % (
+ "const " if const else "",
+ self.name,
+ "*" if "out" in calltype else "",
+ )
+
+ def rustType(self, calltype, const=False):
+ if calltype == "element":
+ return "Option<RefPtr<%s>>" % self.name
+ return "%s*const %s" % ("*mut " if "out" in calltype else "", self.name)
+
+ def __str__(self):
+ l = ["interface %s\n" % self.name]
+ if self.base is not None:
+ l.append("\tbase %s\n" % self.base)
+ l.append(str(self.attributes))
+ if self.members is None:
+ l.append("\tincomplete type\n")
+ else:
+ for m in self.members:
+ l.append(str(m))
+ return "".join(l)
+
+ def getConst(self, name, location):
+ # The constant may be in a base class
+ iface = self
+ while name not in iface.namemap and iface.base is not None:
+ iface = self.idl.getName(TypeId(iface.base), self.location)
+ if name not in iface.namemap:
+ raise IDLError("cannot find symbol '%s'" % name, location)
+ c = iface.namemap.get(name, location)
+ if c.kind != "const":
+ raise IDLError("symbol '%s' is not a constant" % name, location)
+
+ return c.getValue()
+
+ def needsJSTypes(self):
+ for m in self.members:
+ if m.kind == "attribute" and m.type == TypeId("jsval"):
+ return True
+ if m.kind == "method" and m.needsJSTypes():
+ return True
+ return False
+
+ def countEntries(self):
+ """Returns the number of entries in the vtable for this interface."""
+ total = sum(member.count() for member in self.members)
+ if self.base is not None:
+ realbase = self.idl.getName(TypeId(self.base), self.location)
+ total += realbase.countEntries()
+ return total
+
+
+class InterfaceAttributes(object):
+ uuid = None
+ scriptable = False
+ builtinclass = False
+ function = False
+ main_process_scriptable_only = False
+ rust_sync = False
+
+ def setuuid(self, value):
+ self.uuid = value.lower()
+
+ def setscriptable(self):
+ self.scriptable = True
+
+ def setfunction(self):
+ self.function = True
+
+ def setbuiltinclass(self):
+ self.builtinclass = True
+
+ def setmain_process_scriptable_only(self):
+ self.main_process_scriptable_only = True
+
+ def setrust_sync(self):
+ self.rust_sync = True
+
+ actions = {
+ "uuid": (True, setuuid),
+ "scriptable": (False, setscriptable),
+ "builtinclass": (False, setbuiltinclass),
+ "function": (False, setfunction),
+ "object": (False, lambda self: True),
+ "main_process_scriptable_only": (False, setmain_process_scriptable_only),
+ "rust_sync": (False, setrust_sync),
+ }
+
+ def __init__(self, attlist, location):
+ def badattribute(self):
+ raise IDLError("Unexpected interface attribute '%s'" % name, location)
+
+ for name, val, aloc in attlist:
+ hasval, action = self.actions.get(name, (False, badattribute))
+ if hasval:
+ if val is None:
+ raise IDLError("Expected value for attribute '%s'" % name, aloc)
+
+ action(self, val)
+ else:
+ if val is not None:
+ raise IDLError("Unexpected value for attribute '%s'" % name, aloc)
+
+ action(self)
+
+ if self.uuid is None:
+ raise IDLError("interface has no uuid", location)
+
+ def __str__(self):
+ l = []
+ if self.uuid:
+ l.append("\tuuid: %s\n" % self.uuid)
+ if self.scriptable:
+ l.append("\tscriptable\n")
+ if self.builtinclass:
+ l.append("\tbuiltinclass\n")
+ if self.function:
+ l.append("\tfunction\n")
+ if self.main_process_scriptable_only:
+ l.append("\tmain_process_scriptable_only\n")
+ if self.rust_sync:
+ l.append("\trust_sync\n")
+ return "".join(l)
+
+
+class ConstMember(object):
+ kind = "const"
+
+ def __init__(self, type, name, value, location, doccomments):
+ self.type = type
+ self.name = name
+ self.valueFn = value
+ self.location = location
+ self.doccomments = doccomments
+
+ def resolve(self, parent):
+ self.realtype = parent.idl.getName(self.type, self.location)
+ self.iface = parent
+ basetype = self.realtype
+ while isinstance(basetype, Typedef):
+ basetype = basetype.realtype
+ if not isinstance(basetype, Builtin) or not basetype.maybeConst:
+ raise IDLError(
+ "const may only be a short or long type, not %s" % self.type,
+ self.location,
+ )
+
+ self.basetype = basetype
+ # Value is a lambda. Resolve it.
+ self.value = self.valueFn(self.iface)
+
+ min_val = -(2**31) if basetype.signed else 0
+ max_val = 2**31 - 1 if basetype.signed else 2**32 - 1
+ if self.value < min_val or self.value > max_val:
+ raise IDLError(
+ "xpidl constants must fit within %s"
+ % ("int32_t" if basetype.signed else "uint32_t"),
+ self.location,
+ )
+
+ def getValue(self):
+ return self.value
+
+ def __str__(self):
+ return "\tconst %s %s = %s\n" % (self.type, self.name, self.getValue())
+
+ def count(self):
+ return 0
+
+
+# Represents a single name/value pair in a CEnum
+class CEnumVariant(object):
+ # Treat CEnumVariants as consts in terms of value resolution, so we can
+ # do things like binary operation values for enum members.
+ kind = "const"
+
+ def __init__(self, name, value, location):
+ self.name = name
+ self.valueFn = value
+ self.location = location
+
+ def getValue(self):
+ return self.value
+
+
+class CEnum(object):
+ kind = "cenum"
+
+ def __init__(self, width, name, variants, location, doccomments):
+ # We have to set a name here, otherwise we won't pass namemap checks on
+ # the interface. This name will change it in resolve(), in order to
+ # namespace the enum within the interface.
+ self.name = name
+ self.basename = name
+ self.width = width
+ self.location = location
+ self.namemap = NameMap()
+ self.doccomments = doccomments
+ self.variants = variants
+ if self.width not in (8, 16, 32):
+ raise IDLError("Width must be one of {8, 16, 32}", self.location)
+
+ def resolve(self, iface):
+ self.iface = iface
+ # Renaming enum to faux-namespace the enum type to the interface in JS
+ # so we don't collide in the global namespace. Hacky/ugly but it does
+ # the job well enough, and the name will still be interface::variant in
+ # C++.
+ self.name = "%s_%s" % (self.iface.name, self.basename)
+ self.iface.idl.setName(self)
+
+ # Compute the value for each enum variant that doesn't set its own
+ # value
+ next_value = 0
+ for variant in self.variants:
+ # CEnum variants resolve to interface level consts in javascript,
+ # meaning their names could collide with other interface members.
+ # Iterate through all CEnum variants to make sure there are no
+ # collisions.
+ self.iface.namemap.set(variant)
+ # Value may be a lambda. If it is, resolve it.
+ if variant.valueFn:
+ next_value = variant.value = variant.valueFn(self.iface)
+ else:
+ variant.value = next_value
+ next_value += 1
+
+ def count(self):
+ return 0
+
+ def nativeType(self, calltype):
+ if "out" in calltype:
+ return "%s::%s *" % (self.iface.name, self.basename)
+ return "%s::%s " % (self.iface.name, self.basename)
+
+ def rustType(self, calltype):
+ return "%s u%d" % ("*mut" if "out" in calltype else "", self.width)
+
+ def __str__(self):
+ body = ", ".join("%s = %s" % v for v in self.variants)
+ return "\tcenum %s : %d { %s };\n" % (self.name, self.width, body)
+
+
+# Infallible doesn't work for all return types.
+#
+# It also must be implemented on a builtinclass (otherwise it'd be unsound as
+# it could be implemented by JS).
+def ensureInfallibleIsSound(methodOrAttribute):
+ if not methodOrAttribute.infallible:
+ return
+ if methodOrAttribute.realtype.kind not in [
+ "builtin",
+ "interface",
+ "forward",
+ "webidl",
+ "cenum",
+ ]:
+ raise IDLError(
+ "[infallible] only works on interfaces, domobjects, and builtin types "
+ "(numbers, booleans, cenum, and raw char types)",
+ methodOrAttribute.location,
+ )
+ ifaceAttributes = methodOrAttribute.iface.attributes
+ if ifaceAttributes.scriptable and not ifaceAttributes.builtinclass:
+ raise IDLError(
+ "[infallible] attributes and methods are only allowed on "
+ "non-[scriptable] or [builtinclass] interfaces",
+ methodOrAttribute.location,
+ )
+
+ if methodOrAttribute.notxpcom:
+ raise IDLError(
+ "[infallible] does not make sense for a [notxpcom] " "method or attribute",
+ methodOrAttribute.location,
+ )
+
+
+# An interface cannot be implemented by JS if it has a notxpcom or nostdcall
+# method or attribute, so it must be marked as builtinclass.
+def ensureBuiltinClassIfNeeded(methodOrAttribute):
+ iface = methodOrAttribute.iface
+ if not iface.attributes.scriptable or iface.attributes.builtinclass:
+ return
+ if iface.name == "nsISupports":
+ return
+ if methodOrAttribute.notxpcom:
+ raise IDLError(
+ (
+ "scriptable interface '%s' must be marked [builtinclass] because it "
+ "contains a [notxpcom] %s '%s'"
+ )
+ % (iface.name, methodOrAttribute.kind, methodOrAttribute.name),
+ methodOrAttribute.location,
+ )
+ if methodOrAttribute.nostdcall:
+ raise IDLError(
+ (
+ "scriptable interface '%s' must be marked [builtinclass] because it "
+ "contains a [nostdcall] %s '%s'"
+ )
+ % (iface.name, methodOrAttribute.kind, methodOrAttribute.name),
+ methodOrAttribute.location,
+ )
+
+
+class Attribute(object):
+ kind = "attribute"
+ noscript = False
+ notxpcom = False
+ readonly = False
+ symbol = False
+ implicit_jscontext = False
+ nostdcall = False
+ must_use = False
+ binaryname = None
+ infallible = False
+ # explicit_setter_can_run_script is true if the attribute is explicitly
+ # annotated as having a setter that can cause script to run.
+ explicit_setter_can_run_script = False
+ # explicit_getter_can_run_script is true if the attribute is explicitly
+ # annotated as having a getter that can cause script to run.
+ explicit_getter_can_run_script = False
+
+ def __init__(self, type, name, attlist, readonly, location, doccomments):
+ self.type = type
+ self.name = name
+ self.attlist = attlist
+ self.readonly = readonly
+ self.location = location
+ self.doccomments = doccomments
+
+ for name, value, aloc in attlist:
+ if name == "binaryname":
+ if value is None:
+ raise IDLError("binaryname attribute requires a value", aloc)
+
+ self.binaryname = value
+ continue
+
+ if value is not None:
+ raise IDLError("Unexpected attribute value", aloc)
+
+ if name == "noscript":
+ self.noscript = True
+ elif name == "notxpcom":
+ self.notxpcom = True
+ elif name == "symbol":
+ self.symbol = True
+ elif name == "implicit_jscontext":
+ self.implicit_jscontext = True
+ elif name == "nostdcall":
+ self.nostdcall = True
+ elif name == "must_use":
+ self.must_use = True
+ elif name == "infallible":
+ self.infallible = True
+ elif name == "can_run_script":
+ if (
+ self.explicit_setter_can_run_script
+ or self.explicit_getter_can_run_script
+ ):
+ raise IDLError(
+ "Redundant getter_can_run_script or "
+ "setter_can_run_script annotation on "
+ "attribute",
+ aloc,
+ )
+ self.explicit_setter_can_run_script = True
+ self.explicit_getter_can_run_script = True
+ elif name == "setter_can_run_script":
+ if self.explicit_setter_can_run_script:
+ raise IDLError(
+ "Redundant setter_can_run_script annotation " "on attribute",
+ aloc,
+ )
+ self.explicit_setter_can_run_script = True
+ elif name == "getter_can_run_script":
+ if self.explicit_getter_can_run_script:
+ raise IDLError(
+ "Redundant getter_can_run_script annotation " "on attribute",
+ aloc,
+ )
+ self.explicit_getter_can_run_script = True
+ else:
+ raise IDLError("Unexpected attribute '%s'" % name, aloc)
+
+ def resolve(self, iface):
+ self.iface = iface
+ self.realtype = iface.idl.getName(self.type, self.location)
+
+ ensureInfallibleIsSound(self)
+ ensureBuiltinClassIfNeeded(self)
+
+ def toIDL(self):
+ attribs = attlistToIDL(self.attlist)
+ readonly = self.readonly and "readonly " or ""
+ return "%s%sattribute %s %s;" % (attribs, readonly, self.type, self.name)
+
+ def isScriptable(self):
+ if not self.iface.attributes.scriptable:
+ return False
+ return not (self.noscript or self.notxpcom or self.nostdcall)
+
+ def __str__(self):
+ return "\t%sattribute %s %s\n" % (
+ self.readonly and "readonly " or "",
+ self.type,
+ self.name,
+ )
+
+ def count(self):
+ return self.readonly and 1 or 2
+
+
+class Method(object):
+ kind = "method"
+ noscript = False
+ notxpcom = False
+ symbol = False
+ binaryname = None
+ implicit_jscontext = False
+ nostdcall = False
+ must_use = False
+ optional_argc = False
+ # explicit_can_run_script is true if the method is explicitly annotated
+ # as being able to cause script to run.
+ explicit_can_run_script = False
+ infallible = False
+
+ def __init__(self, type, name, attlist, paramlist, location, doccomments, raises):
+ self.type = type
+ self.name = name
+ self.attlist = attlist
+ self.params = paramlist
+ self.location = location
+ self.doccomments = doccomments
+ self.raises = raises
+
+ for name, value, aloc in attlist:
+ if name == "binaryname":
+ if value is None:
+ raise IDLError("binaryname attribute requires a value", aloc)
+
+ self.binaryname = value
+ continue
+
+ if value is not None:
+ raise IDLError("Unexpected attribute value", aloc)
+
+ if name == "noscript":
+ self.noscript = True
+ elif name == "notxpcom":
+ self.notxpcom = True
+ elif name == "symbol":
+ self.symbol = True
+ elif name == "implicit_jscontext":
+ self.implicit_jscontext = True
+ elif name == "optional_argc":
+ self.optional_argc = True
+ elif name == "nostdcall":
+ self.nostdcall = True
+ elif name == "must_use":
+ self.must_use = True
+ elif name == "can_run_script":
+ self.explicit_can_run_script = True
+ elif name == "infallible":
+ self.infallible = True
+ else:
+ raise IDLError("Unexpected attribute '%s'" % name, aloc)
+
+ self.namemap = NameMap()
+ for p in paramlist:
+ self.namemap.set(p)
+
+ def resolve(self, iface):
+ self.iface = iface
+ self.realtype = self.iface.idl.getName(self.type, self.location)
+
+ ensureInfallibleIsSound(self)
+ ensureBuiltinClassIfNeeded(self)
+
+ for p in self.params:
+ p.resolve(self)
+ for p in self.params:
+ if p.retval and p != self.params[-1]:
+ raise IDLError(
+ "'retval' parameter '%s' is not the last parameter" % p.name,
+ self.location,
+ )
+ if p.size_is:
+ size_param = self.namemap.get(p.size_is, p.location)
+ if (
+ p.paramtype.count("in") == 1
+ and size_param.paramtype.count("in") == 0
+ ):
+ raise IDLError(
+ "size_is parameter of an input must also be an input",
+ p.location,
+ )
+ if getBuiltinOrNativeTypeName(size_param.realtype) != "unsigned long":
+ raise IDLError(
+ "size_is parameter must have type 'unsigned long'",
+ p.location,
+ )
+ if p.iid_is:
+ iid_param = self.namemap.get(p.iid_is, p.location)
+ if (
+ p.paramtype.count("in") == 1
+ and iid_param.paramtype.count("in") == 0
+ ):
+ raise IDLError(
+ "iid_is parameter of an input must also be an input",
+ p.location,
+ )
+ if getBuiltinOrNativeTypeName(iid_param.realtype) != "[nsid]":
+ raise IDLError(
+ "iid_is parameter must be an nsIID",
+ self.location,
+ )
+
+ def isScriptable(self):
+ if not self.iface.attributes.scriptable:
+ return False
+ return not (self.noscript or self.notxpcom or self.nostdcall)
+
+ def __str__(self):
+ return "\t%s %s(%s)\n" % (
+ self.type,
+ self.name,
+ ", ".join([p.name for p in self.params]),
+ )
+
+ def toIDL(self):
+ if len(self.raises):
+ raises = " raises (%s)" % ",".join(self.raises)
+ else:
+ raises = ""
+
+ return "%s%s %s (%s)%s;" % (
+ attlistToIDL(self.attlist),
+ self.type,
+ self.name,
+ ", ".join([p.toIDL() for p in self.params]),
+ raises,
+ )
+
+ def needsJSTypes(self):
+ if self.implicit_jscontext:
+ return True
+ if self.type == TypeId("jsval"):
+ return True
+ for p in self.params:
+ t = p.realtype
+ if isinstance(t, Native) and t.specialtype == "jsval":
+ return True
+ return False
+
+ def count(self):
+ return 1
+
+
+class Param(object):
+ size_is = None
+ iid_is = None
+ const = False
+ array = False
+ retval = False
+ shared = False
+ optional = False
+ default_value = None
+
+ def __init__(self, paramtype, type, name, attlist, location, realtype=None):
+ self.paramtype = paramtype
+ self.type = type
+ self.name = name
+ self.attlist = attlist
+ self.location = location
+ self.realtype = realtype
+
+ for name, value, aloc in attlist:
+ # Put the value-taking attributes first!
+ if name == "size_is":
+ if value is None:
+ raise IDLError("'size_is' must specify a parameter", aloc)
+ self.size_is = value
+ elif name == "iid_is":
+ if value is None:
+ raise IDLError("'iid_is' must specify a parameter", aloc)
+ self.iid_is = value
+ elif name == "default":
+ if value is None:
+ raise IDLError("'default' must specify a default value", aloc)
+ self.default_value = value
+ else:
+ if value is not None:
+ raise IDLError("Unexpected value for attribute '%s'" % name, aloc)
+
+ if name == "const":
+ self.const = True
+ elif name == "array":
+ self.array = True
+ elif name == "retval":
+ self.retval = True
+ elif name == "shared":
+ self.shared = True
+ elif name == "optional":
+ self.optional = True
+ else:
+ raise IDLError("Unexpected attribute '%s'" % name, aloc)
+
+ def resolve(self, method):
+ self.realtype = method.iface.idl.getName(self.type, self.location)
+ if self.array:
+ self.realtype = LegacyArray(self.realtype)
+
+ def nativeType(self):
+ kwargs = {}
+ if self.shared:
+ kwargs["shared"] = True
+ if self.const:
+ kwargs["const"] = True
+
+ try:
+ return self.realtype.nativeType(self.paramtype, **kwargs)
+ except IDLError as e:
+ raise IDLError(str(e), self.location)
+ except TypeError:
+ raise IDLError("Unexpected parameter attribute", self.location)
+
+ def rustType(self):
+ kwargs = {}
+ if self.shared:
+ kwargs["shared"] = True
+ if self.const:
+ kwargs["const"] = True
+
+ try:
+ return self.realtype.rustType(self.paramtype, **kwargs)
+ except IDLError as e:
+ raise IDLError(str(e), self.location)
+ except TypeError:
+ raise IDLError("Unexpected parameter attribute", self.location)
+
+ def toIDL(self):
+ return "%s%s %s %s" % (
+ paramAttlistToIDL(self.attlist),
+ self.paramtype,
+ self.type,
+ self.name,
+ )
+
+
+class LegacyArray(object):
+ def __init__(self, basetype):
+ self.type = basetype
+ self.location = self.type.location
+
+ def nativeType(self, calltype, const=False):
+ if "element" in calltype:
+ raise IDLError("nested [array] unsupported", self.location)
+
+ # For legacy reasons, we have to add a 'const ' to builtin pointer array
+ # types. (`[array] in string` and `[array] in wstring` parameters)
+ if (
+ calltype == "in"
+ and isinstance(self.type, Builtin)
+ and self.type.isPointer()
+ ):
+ const = True
+
+ return "%s%s*%s" % (
+ "const " if const else "",
+ self.type.nativeType("legacyelement"),
+ "*" if "out" in calltype else "",
+ )
+
+ def rustType(self, calltype, const=False):
+ return "%s%s%s" % (
+ "*mut " if "out" in calltype else "",
+ "*const " if const else "*mut ",
+ self.type.rustType("legacyelement"),
+ )
+
+
+class Array(object):
+ kind = "array"
+
+ def __init__(self, type, location):
+ self.type = type
+ self.location = location
+
+ @property
+ def name(self):
+ return "Array<%s>" % self.type.name
+
+ def resolve(self, idl):
+ idl.getName(self.type, self.location)
+
+ def nativeType(self, calltype):
+ if calltype == "legacyelement":
+ raise IDLError("[array] Array<T> is unsupported", self.location)
+
+ base = "nsTArray<%s>" % self.type.nativeType("element")
+ if "out" in calltype:
+ return "%s& " % base
+ elif "in" == calltype:
+ return "const %s& " % base
+ else:
+ return base
+
+ def rustType(self, calltype):
+ if calltype == "legacyelement":
+ raise IDLError("[array] Array<T> is unsupported", self.location)
+
+ base = "thin_vec::ThinVec<%s>" % self.type.rustType("element")
+ if "out" in calltype:
+ return "*mut %s" % base
+ elif "in" == calltype:
+ return "*const %s" % base
+ else:
+ return base
+
+
+TypeId = namedtuple("TypeId", "name params")
+
+
+# Make str(TypeId) produce a nicer value
+TypeId.__str__ = (
+ lambda self: "%s<%s>" % (self.name, ", ".join(str(p) for p in self.params))
+ if self.params is not None
+ else self.name
+)
+
+
+# Allow skipping 'params' in TypeId(..)
+TypeId.__new__.__defaults__ = (None,)
+
+
+class IDLParser(object):
+ keywords = {
+ "cenum": "CENUM",
+ "const": "CONST",
+ "interface": "INTERFACE",
+ "in": "IN",
+ "inout": "INOUT",
+ "out": "OUT",
+ "attribute": "ATTRIBUTE",
+ "raises": "RAISES",
+ "readonly": "READONLY",
+ "native": "NATIVE",
+ "typedef": "TYPEDEF",
+ "webidl": "WEBIDL",
+ }
+
+ tokens = [
+ "IDENTIFIER",
+ "CDATA",
+ "INCLUDE",
+ "IID",
+ "NUMBER",
+ "HEXNUM",
+ "LSHIFT",
+ "RSHIFT",
+ "NATIVEID",
+ ]
+
+ tokens.extend(keywords.values())
+
+ states = (("nativeid", "exclusive"),)
+
+ hexchar = r"[a-fA-F0-9]"
+
+ t_NUMBER = r"-?\d+"
+ t_HEXNUM = r"0x%s+" % hexchar
+ t_LSHIFT = r"<<"
+ t_RSHIFT = r">>"
+
+ literals = '"(){}[]<>,;:=|+-*'
+
+ t_ignore = " \t"
+
+ def t_multilinecomment(self, t):
+ r"/\*(\n|.)*?\*/"
+ t.lexer.lineno += t.value.count("\n")
+ if t.value.startswith("/**"):
+ self._doccomments.append(t.value)
+
+ def t_singlelinecomment(self, t):
+ r"//[^\n]*"
+
+ def t_IID(self, t):
+ return t
+
+ t_IID.__doc__ = r"%(c)s{8}-%(c)s{4}-%(c)s{4}-%(c)s{4}-%(c)s{12}" % {"c": hexchar}
+
+ def t_IDENTIFIER(self, t):
+ 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
+ t.type = self.keywords.get(t.value, "IDENTIFIER")
+ return t
+
+ def t_LCDATA(self, t):
+ r"%\{[ ]*C\+\+[ ]*\n(?P<cdata>(\n|.)*?\n?)%\}[ ]*(C\+\+)?"
+ t.type = "CDATA"
+ t.value = t.lexer.lexmatch.group("cdata")
+ t.lexer.lineno += t.value.count("\n")
+ return t
+
+ def t_INCLUDE(self, t):
+ r'\#include[ \t]+"[^"\n]+"'
+ inc, value, end = t.value.split('"')
+ t.value = value
+ return t
+
+ def t_directive(self, t):
+ r"\#(?P<directive>[a-zA-Z]+)[^\n]+"
+ raise IDLError(
+ "Unrecognized directive %s" % t.lexer.lexmatch.group("directive"),
+ Location(
+ lexer=self.lexer, lineno=self.lexer.lineno, lexpos=self.lexer.lexpos
+ ),
+ )
+
+ def t_newline(self, t):
+ r"\n+"
+ t.lexer.lineno += len(t.value)
+
+ def t_nativeid_NATIVEID(self, t):
+ # Matches non-parenthesis characters, or a single open and closing
+ # parenthesis with at least one non-parenthesis character before,
+ # between and after them (for compatibility with std::function).
+ r"[^()\n]+(?:\([^()\n]+\)[^()\n]+)?(?=\))"
+ t.lexer.begin("INITIAL")
+ return t
+
+ t_nativeid_ignore = ""
+
+ def t_ANY_error(self, t):
+ raise IDLError(
+ "unrecognized input",
+ Location(
+ lexer=self.lexer, lineno=self.lexer.lineno, lexpos=self.lexer.lexpos
+ ),
+ )
+
+ precedence = (
+ ("left", "|"),
+ ("left", "LSHIFT", "RSHIFT"),
+ ("left", "+", "-"),
+ ("left", "*"),
+ ("left", "UMINUS"),
+ )
+
+ def p_idlfile(self, p):
+ """idlfile : productions"""
+ p[0] = IDL(p[1])
+
+ def p_productions_start(self, p):
+ """productions :"""
+ p[0] = []
+
+ def p_productions_cdata(self, p):
+ """productions : CDATA productions"""
+ p[0] = list(p[2])
+ p[0].insert(0, CDATA(p[1], self.getLocation(p, 1)))
+
+ def p_productions_include(self, p):
+ """productions : INCLUDE productions"""
+ p[0] = list(p[2])
+ p[0].insert(0, Include(p[1], self.getLocation(p, 1)))
+
+ def p_productions_interface(self, p):
+ """productions : interface productions
+ | typedef productions
+ | native productions
+ | webidl productions"""
+ p[0] = list(p[2])
+ p[0].insert(0, p[1])
+
+ def p_typedef(self, p):
+ """typedef : TYPEDEF type IDENTIFIER ';'"""
+ p[0] = Typedef(
+ type=p[2],
+ name=p[3],
+ location=self.getLocation(p, 1),
+ doccomments=p.slice[1].doccomments,
+ )
+
+ def p_native(self, p):
+ """native : attributes NATIVE IDENTIFIER afternativeid '(' NATIVEID ')' ';'"""
+ p[0] = Native(
+ name=p[3],
+ nativename=p[6],
+ attlist=p[1]["attlist"],
+ location=self.getLocation(p, 2),
+ )
+
+ def p_afternativeid(self, p):
+ """afternativeid :"""
+ # this is a place marker: we switch the lexer into literal identifier
+ # mode here, to slurp up everything until the closeparen
+ self.lexer.begin("nativeid")
+
+ def p_webidl(self, p):
+ """webidl : WEBIDL IDENTIFIER ';'"""
+ p[0] = WebIDL(name=p[2], location=self.getLocation(p, 2))
+
+ def p_anyident(self, p):
+ """anyident : IDENTIFIER
+ | CONST"""
+ p[0] = {"value": p[1], "location": self.getLocation(p, 1)}
+
+ def p_attributes(self, p):
+ """attributes : '[' attlist ']'
+ |"""
+ if len(p) == 1:
+ p[0] = {"attlist": []}
+ else:
+ p[0] = {"attlist": p[2], "doccomments": p.slice[1].doccomments}
+
+ def p_attlist_start(self, p):
+ """attlist : attribute"""
+ p[0] = [p[1]]
+
+ def p_attlist_continue(self, p):
+ """attlist : attribute ',' attlist"""
+ p[0] = list(p[3])
+ p[0].insert(0, p[1])
+
+ def p_attribute(self, p):
+ """attribute : anyident attributeval"""
+ p[0] = (p[1]["value"], p[2], p[1]["location"])
+
+ def p_attributeval(self, p):
+ """attributeval : '(' IDENTIFIER ')'
+ | '(' IID ')'
+ |"""
+ if len(p) > 1:
+ p[0] = p[2]
+
+ def p_interface(self, p):
+ """interface : attributes INTERFACE IDENTIFIER ifacebase ifacebody ';'"""
+ atts, INTERFACE, name, base, body, SEMI = p[1:]
+ attlist = atts["attlist"]
+ doccomments = []
+ if "doccomments" in atts:
+ doccomments.extend(atts["doccomments"])
+ doccomments.extend(p.slice[2].doccomments)
+
+ def loc():
+ return self.getLocation(p, 2)
+
+ if body is None:
+ # forward-declared interface... must not have attributes!
+ if len(attlist) != 0:
+ raise IDLError(
+ "Forward-declared interface must not have attributes", loc()
+ )
+
+ if base is not None:
+ raise IDLError("Forward-declared interface must not have a base", loc())
+ p[0] = Forward(name=name, location=loc(), doccomments=doccomments)
+ else:
+ p[0] = Interface(
+ name=name,
+ attlist=attlist,
+ base=base,
+ members=body,
+ location=loc(),
+ doccomments=doccomments,
+ )
+
+ def p_ifacebody(self, p):
+ """ifacebody : '{' members '}'
+ |"""
+ if len(p) > 1:
+ p[0] = p[2]
+
+ def p_ifacebase(self, p):
+ """ifacebase : ':' IDENTIFIER
+ |"""
+ if len(p) == 3:
+ p[0] = p[2]
+
+ def p_members_start(self, p):
+ """members :"""
+ p[0] = []
+
+ def p_members_continue(self, p):
+ """members : member members"""
+ p[0] = list(p[2])
+ p[0].insert(0, p[1])
+
+ def p_member_cdata(self, p):
+ """member : CDATA"""
+ p[0] = CDATA(p[1], self.getLocation(p, 1))
+
+ def p_member_const(self, p):
+ """member : CONST type IDENTIFIER '=' number ';'"""
+ p[0] = ConstMember(
+ type=p[2],
+ name=p[3],
+ value=p[5],
+ location=self.getLocation(p, 1),
+ doccomments=p.slice[1].doccomments,
+ )
+
+ # All "number" products return a function(interface)
+
+ def p_number_decimal(self, p):
+ """number : NUMBER"""
+ n = int(p[1])
+ p[0] = lambda i: n
+
+ def p_number_hex(self, p):
+ """number : HEXNUM"""
+ n = int(p[1], 16)
+ p[0] = lambda i: n
+
+ def p_number_identifier(self, p):
+ """number : IDENTIFIER"""
+ id = p[1]
+ loc = self.getLocation(p, 1)
+ p[0] = lambda i: i.getConst(id, loc)
+
+ def p_number_paren(self, p):
+ """number : '(' number ')'"""
+ p[0] = p[2]
+
+ def p_number_neg(self, p):
+ """number : '-' number %prec UMINUS"""
+ n = p[2]
+ p[0] = lambda i: -n(i)
+
+ def p_number_add(self, p):
+ """number : number '+' number
+ | number '-' number
+ | number '*' number"""
+ n1 = p[1]
+ n2 = p[3]
+ if p[2] == "+":
+ p[0] = lambda i: n1(i) + n2(i)
+ elif p[2] == "-":
+ p[0] = lambda i: n1(i) - n2(i)
+ else:
+ p[0] = lambda i: n1(i) * n2(i)
+
+ def p_number_shift(self, p):
+ """number : number LSHIFT number
+ | number RSHIFT number"""
+ n1 = p[1]
+ n2 = p[3]
+ if p[2] == "<<":
+ p[0] = lambda i: n1(i) << n2(i)
+ else:
+ p[0] = lambda i: n1(i) >> n2(i)
+
+ def p_number_bitor(self, p):
+ """number : number '|' number"""
+ n1 = p[1]
+ n2 = p[3]
+ p[0] = lambda i: n1(i) | n2(i)
+
+ def p_member_cenum(self, p):
+ """member : CENUM IDENTIFIER ':' NUMBER '{' variants '}' ';'"""
+ p[0] = CEnum(
+ name=p[2],
+ width=int(p[4]),
+ variants=p[6],
+ location=self.getLocation(p, 1),
+ doccomments=p.slice[1].doccomments,
+ )
+
+ def p_variants_start(self, p):
+ """variants :"""
+ p[0] = []
+
+ def p_variants_single(self, p):
+ """variants : variant"""
+ p[0] = [p[1]]
+
+ def p_variants_continue(self, p):
+ """variants : variant ',' variants"""
+ p[0] = [p[1]] + p[3]
+
+ def p_variant_implicit(self, p):
+ """variant : IDENTIFIER"""
+ p[0] = CEnumVariant(p[1], None, self.getLocation(p, 1))
+
+ def p_variant_explicit(self, p):
+ """variant : IDENTIFIER '=' number"""
+ p[0] = CEnumVariant(p[1], p[3], self.getLocation(p, 1))
+
+ def p_member_att(self, p):
+ """member : attributes optreadonly ATTRIBUTE type IDENTIFIER ';'"""
+ if "doccomments" in p[1]:
+ doccomments = p[1]["doccomments"]
+ elif p[2] is not None:
+ doccomments = p[2]
+ else:
+ doccomments = p.slice[3].doccomments
+
+ p[0] = Attribute(
+ type=p[4],
+ name=p[5],
+ attlist=p[1]["attlist"],
+ readonly=p[2] is not None,
+ location=self.getLocation(p, 3),
+ doccomments=doccomments,
+ )
+
+ def p_member_method(self, p):
+ """member : attributes type IDENTIFIER '(' paramlist ')' raises ';'"""
+ if "doccomments" in p[1]:
+ doccomments = p[1]["doccomments"]
+ else:
+ doccomments = p.slice[2].doccomments
+
+ p[0] = Method(
+ type=p[2],
+ name=p[3],
+ attlist=p[1]["attlist"],
+ paramlist=p[5],
+ location=self.getLocation(p, 3),
+ doccomments=doccomments,
+ raises=p[7],
+ )
+
+ def p_paramlist(self, p):
+ """paramlist : param moreparams
+ |"""
+ if len(p) == 1:
+ p[0] = []
+ else:
+ p[0] = list(p[2])
+ p[0].insert(0, p[1])
+
+ def p_moreparams_start(self, p):
+ """moreparams :"""
+ p[0] = []
+
+ def p_moreparams_continue(self, p):
+ """moreparams : ',' param moreparams"""
+ p[0] = list(p[3])
+ p[0].insert(0, p[2])
+
+ def p_param(self, p):
+ """param : attributes paramtype type IDENTIFIER"""
+ p[0] = Param(
+ paramtype=p[2],
+ type=p[3],
+ name=p[4],
+ attlist=p[1]["attlist"],
+ location=self.getLocation(p, 4),
+ )
+
+ def p_paramtype(self, p):
+ """paramtype : IN
+ | INOUT
+ | OUT"""
+ p[0] = p[1]
+
+ def p_optreadonly(self, p):
+ """optreadonly : READONLY
+ |"""
+ if len(p) > 1:
+ p[0] = p.slice[1].doccomments
+ else:
+ p[0] = None
+
+ def p_raises(self, p):
+ """raises : RAISES '(' idlist ')'
+ |"""
+ if len(p) == 1:
+ p[0] = []
+ else:
+ p[0] = p[3]
+
+ def p_idlist(self, p):
+ """idlist : IDENTIFIER"""
+ p[0] = [p[1]]
+
+ def p_idlist_continue(self, p):
+ """idlist : IDENTIFIER ',' idlist"""
+ p[0] = list(p[3])
+ p[0].insert(0, p[1])
+
+ def p_type_id(self, p):
+ """type : IDENTIFIER"""
+ p[0] = TypeId(name=p[1])
+ p.slice[0].doccomments = p.slice[1].doccomments
+
+ def p_type_generic(self, p):
+ """type : IDENTIFIER '<' typelist '>'"""
+ p[0] = TypeId(name=p[1], params=p[3])
+ p.slice[0].doccomments = p.slice[1].doccomments
+
+ def p_typelist(self, p):
+ """typelist : type"""
+ p[0] = [p[1]]
+
+ def p_typelist_continue(self, p):
+ """typelist : type ',' typelist"""
+ p[0] = list(p[3])
+ p[0].insert(0, p[1])
+
+ def p_error(self, t):
+ if not t:
+ raise IDLError(
+ "Syntax Error at end of file. Possibly due to missing semicolon(;), braces(}) "
+ "or both",
+ None,
+ )
+ else:
+ location = Location(self.lexer, t.lineno, t.lexpos)
+ raise IDLError("invalid syntax", location)
+
+ def __init__(self):
+ self._doccomments = []
+ self.lexer = lex.lex(object=self, debug=False)
+ self.parser = yacc.yacc(module=self, write_tables=False, debug=False)
+
+ def clearComments(self):
+ self._doccomments = []
+
+ def token(self):
+ t = self.lexer.token()
+ if t is not None and t.type != "CDATA":
+ t.doccomments = self._doccomments
+ self._doccomments = []
+ return t
+
+ def parse(self, data, filename=None):
+ if filename is not None:
+ self.lexer.filename = filename
+ self.lexer.lineno = 1
+ self.lexer.input(data)
+ idl = self.parser.parse(lexer=self)
+ if filename is not None:
+ idl.deps.append(filename)
+ return idl
+
+ def getLocation(self, p, i):
+ return Location(self.lexer, p.lineno(i), p.lexpos(i))
+
+
+if __name__ == "__main__":
+ p = IDLParser()
+ for f in sys.argv[1:]:
+ print("Parsing %s" % f)
+ p.parse(open(f, encoding="utf-8").read(), filename=f)