diff options
Diffstat (limited to 'xpcom/idl-parser')
-rw-r--r-- | xpcom/idl-parser/setup.py | 15 | ||||
-rw-r--r-- | xpcom/idl-parser/xpidl/__init__.py | 0 | ||||
-rw-r--r-- | xpcom/idl-parser/xpidl/header.py | 566 | ||||
-rw-r--r-- | xpcom/idl-parser/xpidl/moz.build | 29 | ||||
-rw-r--r-- | xpcom/idl-parser/xpidl/runtests.py | 114 | ||||
-rw-r--r-- | xpcom/idl-parser/xpidl/typelib.py | 307 | ||||
-rwxr-xr-x | xpcom/idl-parser/xpidl/xpidl.py | 1465 |
7 files changed, 2496 insertions, 0 deletions
diff --git a/xpcom/idl-parser/setup.py b/xpcom/idl-parser/setup.py new file mode 100644 index 000000000..f42edbb99 --- /dev/null +++ b/xpcom/idl-parser/setup.py @@ -0,0 +1,15 @@ +from setuptools import setup, find_packages + + +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 000000000..e69de29bb --- /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 000000000..8e02a7d11 --- /dev/null +++ b/xpcom/idl-parser/xpidl/header.py @@ -0,0 +1,566 @@ +#!/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 sys +import os.path +import re +import xpidl +import itertools +import glob + +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): + 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 attributeReturnType(a, macro): + """macro should be NS_IMETHOD or NS_IMETHODIMP""" + if a.nostdcall: + ret = macro == "NS_IMETHOD" and "virtual nsresult" or "nsresult" + else: + ret = macro + if a.must_use: + ret = "MOZ_MUST_USE " + ret + return ret + + +def attributeParamlist(a, getter): + 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'): + deprecated = a.deprecated and "NS_DEPRECATED " or "" + params = {'deprecated': deprecated, + 'returntype': attributeReturnType(a, declType), + 'binaryname': attributeNativeName(a, getter), + 'paramlist': attributeParamlist(a, getter)} + return "%(deprecated)s%(returntype)s %(binaryname)s(%(paramlist)s)" % params + + +def methodNativeName(m): + return m.binaryname is not None and m.binaryname or firstCap(m.name) + + +def methodReturnType(m, macro): + """macro should be NS_IMETHOD or NS_IMETHODIMP""" + if m.nostdcall and m.notxpcom: + ret = "%s%s" % (macro == "NS_IMETHOD" and "virtual " or "", + m.realtype.nativeType('in').strip()) + elif m.nostdcall: + ret = "%snsresult" % (macro == "NS_IMETHOD" and "virtual " or "") + elif m.notxpcom: + ret = "%s_(%s)" % (macro, m.realtype.nativeType('in').strip()) + else: + ret = macro + if m.must_use: + ret = "MOZ_MUST_USE " + ret + return ret + + +def methodAsNative(m, declType = 'NS_IMETHOD'): + return "%s %s(%s)" % (methodReturnType(m, declType), + methodNativeName(m), + paramlistAsNative(m)) + + +def paramlistAsNative(m, empty='void'): + 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': + 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 + m.params[paramIter].paramtype == "out"): + 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 == "DOMString" or t == "AUTF8String": + break + l[paramIter] += " = nullptr" + paramIter -= 1 + + if len(l) == 0: + return empty + + return ", ".join(l) + + +def paramAsNative(p): + return "%s%s" % (p.nativeType(), + p.name) + + +def paramlistNames(m): + 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': + names.append('_retval') + + if len(names) == 0: + return '' + return ', '.join(names) + +header = """/* + * DO NOT EDIT. THIS FILE IS GENERATED FROM %(filename)s + */ + +#ifndef __gen_%(basename)s_h__ +#define __gen_%(basename)s_h__ +""" + +include = """ +#ifndef __gen_%(basename)s_h__ +#include "%(basename)s.h" +#endif +""" + +jsvalue_include = """ +#include "js/Value.h" +""" + +infallible_includes = """ +#include "mozilla/Assertions.h" +#include "mozilla/DebugOnly.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): + fd.write(header % {'filename': filename, + '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. + for iface in [p for p in idl.productions if p.kind == 'interface']: + for attr in [m for m in iface.members if isinstance(m, xpidl.Attribute)]: + if attr.infallible: + fd.write(infallible_includes) + 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 == '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)}) + +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_epilog = """}; + + NS_DEFINE_STATIC_IID_ACCESSOR(%(name)s, %(defname)s_IID) + +/* 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) """ + +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) """ + +iface_template_prolog = """ + +#if 0 +/* Use the code below as a template for the implementation class for this interface. */ + +/* Header file */ +class %(implclass)s : public %(name)s +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_%(macroname)s + + %(implclass)s(); + +private: + ~%(implclass)s(); + +protected: + /* additional members */ +}; + +/* Implementation file */ +NS_IMPL_ISUPPORTS(%(implclass)s, %(name)s) + +%(implclass)s::%(implclass)s() +{ + /* member initializers and constructor code */ +} + +%(implclass)s::~%(implclass)s() +{ + /* destructor code */ +} + +""" + +example_tmpl = """%(returntype)s %(implclass)s::%(nativeName)s(%(paramList)s) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} +""" + +iface_template_epilog = """/* End of implementation class template. */ +#endif + +""" + +attr_infallible_tmpl = """\ + inline %(realtype)s%(nativename)s(%(args)s) + { + %(realtype)sresult; + mozilla::DebugOnly<nsresult> rv = %(nativename)s(%(argnames)s&result); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + return result; + } +""" + + +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_method_decl(m): + printComments(fd, m.doccomments, ' ') + + fd.write(" /* %s */\n" % m.toIDL()) + fd.write(" %s = 0;\n\n" % methodAsNative(m)) + + def write_attr_decl(a): + printComments(fd, a.doccomments, ' ') + + fd.write(" /* %s */\n" % a.toIDL()) + + fd.write(" %s = 0;\n" % attributeAsNative(a, True)) + if a.infallible: + fd.write(attr_infallible_tmpl % + {'realtype': a.realtype.nativeType('in'), + 'nativename': attributeNativeName(a, getter=True), + 'args': '' if not a.implicit_jscontext else 'JSContext* cx', + 'argnames': '' if not a.implicit_jscontext else 'cx, '}) + + if not a.readonly: + fd.write(" %s = 0;\n" % 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 xrange(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 ") + + if iface.attributes.deprecated: + fd.write("MOZ_DEPRECATED ") + fd.write(iface.name) + if iface.base: + fd.write(" : public %s" % iface.base) + fd.write(iface_prolog % 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) + else: + raise Exception("Unexpected interface member: %s" % member) + + fd.write(iface_epilog % names) + + def writeDeclaration(fd, iface, virtual): + declType = "NS_IMETHOD" if virtual else "NS_METHOD" + 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 not member.kind 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))) + fd.write(tmpl % {'asNative': attributeAsNative(member, True), + 'nativeName': attributeNativeName(member, True), + 'paramList': attributeParamNames(member)}) + if not member.readonly: + fd.write(tmpl % {'asNative': attributeAsNative(member, False), + 'nativeName': attributeNativeName(member, False), + 'paramList': attributeParamNames(member)}) + 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 not member.kind 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); } ", + "\\\n %(asNative)s override; ") + + fd.write(iface_template_prolog % names) + + for member in iface.members: + if isinstance(member, xpidl.ConstMember) or isinstance(member, xpidl.CDATA): + continue + fd.write("/* %s */\n" % member.toIDL()) + if isinstance(member, xpidl.Attribute): + fd.write(example_tmpl % {'implclass': implclass, + 'returntype': attributeReturnType(member, 'NS_IMETHODIMP'), + 'nativeName': attributeNativeName(member, True), + 'paramList': attributeParamlist(member, True)}) + if not member.readonly: + fd.write(example_tmpl % {'implclass': implclass, + 'returntype': attributeReturnType(member, 'NS_IMETHODIMP'), + 'nativeName': attributeNativeName(member, False), + 'paramList': attributeParamlist(member, False)}) + elif isinstance(member, xpidl.Method): + fd.write(example_tmpl % {'implclass': implclass, + 'returntype': methodReturnType(member, 'NS_IMETHODIMP'), + 'nativeName': methodNativeName(member), + 'paramList': paramlistAsNative(member, empty='')}) + fd.write('\n') + + fd.write(iface_template_epilog) + + +def main(outputfile): + cachedir = '.' + if not os.path.isdir(cachedir): + os.mkdir(cachedir) + sys.path.append(cachedir) + + # Delete the lex/yacc files. Ply is too stupid to regenerate them + # properly + for fileglobs in [os.path.join(cachedir, f) for f in ["xpidllex.py*", "xpidlyacc.py*"]]: + for filename in glob.glob(fileglobs): + os.remove(filename) + + # Instantiate the parser. + p = xpidl.IDLParser(outputdir=cachedir) + +if __name__ == '__main__': + main(None) diff --git a/xpcom/idl-parser/xpidl/moz.build b/xpcom/idl-parser/xpidl/moz.build new file mode 100644 index 000000000..33c1e7b13 --- /dev/null +++ b/xpcom/idl-parser/xpidl/moz.build @@ -0,0 +1,29 @@ +# -*- 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_UNIT_TESTS += [ + 'runtests.py', +] + +GENERATED_FILES += [ + ('xpidl.stub', 'xpidllex.py', 'xpidlyacc.py'), +] + +GENERATED_FILES[('xpidl.stub', 'xpidllex.py', 'xpidlyacc.py')].script = 'header.py:main' + +SDK_FILES.bin += [ + '!xpidllex.py', + '!xpidlyacc.py', + 'header.py', + 'typelib.py', + 'xpidl.py', +] + +SDK_FILES.bin.ply += [ + '/other-licenses/ply/ply/__init__.py', + '/other-licenses/ply/ply/lex.py', + '/other-licenses/ply/ply/yacc.py', +] diff --git a/xpcom/idl-parser/xpidl/runtests.py b/xpcom/idl-parser/xpidl/runtests.py new file mode 100644 index 000000000..89222d546 --- /dev/null +++ b/xpcom/idl-parser/xpidl/runtests.py @@ -0,0 +1,114 @@ +#!/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 mozunit +import unittest +import xpidl +import header + + +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, deprecated, 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) + self.assertTrue(iface.attributes.deprecated) + + i = self.p.parse("[noscript, 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.noscript) + + 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("void", m.type) + + def testMethodParams(self): + i = self.p.parse("""[uuid(abc)] interface foo { +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[0], xpidl.Interface)) + iface = i.productions[0] + m = iface.members[0] + self.assertTrue(isinstance(m, xpidl.Method)) + self.assertEqual("bar", m.name) + self.assertEqual("long", m.type) + self.assertEqual(3, len(m.params)) + self.assertEqual("long", m.params[0].type) + self.assertEqual("in", m.params[0].paramtype) + self.assertEqual("float", m.params[1].type) + self.assertEqual("in", m.params[1].paramtype) + self.assertEqual("long", m.params[2].type) + self.assertEqual("in", m.params[2].paramtype) + self.assertTrue(isinstance(m.params[2].realtype, xpidl.Array)) + 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("long", a.type) + + def testOverloadedVirtual(self): + i = self.p.parse("""[uuid(abc)] interface foo { +attribute long bar; +void getBar(); +};""", filename='f') + self.assertTrue(isinstance(i, xpidl.IDL)) + class FdMock: + def write(self, s): + pass + try: + header.print_header(i, FdMock(), filename='f') + except Exception as e: + self.assertEqual(e.args[0], "Unexpected overloaded virtual method GetBar in interface foo") + +if __name__ == '__main__': + mozunit.main() diff --git a/xpcom/idl-parser/xpidl/typelib.py b/xpcom/idl-parser/xpidl/typelib.py new file mode 100644 index 000000000..911e3873d --- /dev/null +++ b/xpcom/idl-parser/xpidl/typelib.py @@ -0,0 +1,307 @@ +#!/usr/bin/env python +# typelib.py - Generate XPCOM 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 an XPIDL typelib for the IDL files specified on the command line""" + +import os +import sys +import xpidl +import xpt + +# A map of xpidl.py types to xpt.py types +TypeMap = { + # nsresult is not strictly an xpidl.py type, but it's useful here + 'nsresult': xpt.Type.Tags.uint32, + # builtins + 'boolean': xpt.Type.Tags.boolean, + 'void': xpt.Type.Tags.void, + 'int16_t': xpt.Type.Tags.int16, + 'int32_t': xpt.Type.Tags.int32, + 'int64_t': xpt.Type.Tags.int64, + 'uint8_t': xpt.Type.Tags.uint8, + 'uint16_t': xpt.Type.Tags.uint16, + 'uint32_t': xpt.Type.Tags.uint32, + 'uint64_t': xpt.Type.Tags.uint64, + 'octet': xpt.Type.Tags.uint8, + 'short': xpt.Type.Tags.int16, + 'long': xpt.Type.Tags.int32, + 'long long': xpt.Type.Tags.int64, + 'unsigned short': xpt.Type.Tags.uint16, + 'unsigned long': xpt.Type.Tags.uint32, + 'unsigned long long': xpt.Type.Tags.uint64, + 'float': xpt.Type.Tags.float, + 'double': xpt.Type.Tags.double, + 'char': xpt.Type.Tags.char, + 'string': xpt.Type.Tags.char_ptr, + 'wchar': xpt.Type.Tags.wchar_t, + 'wstring': xpt.Type.Tags.wchar_t_ptr, + # special types + 'nsid': xpt.Type.Tags.nsIID, + 'domstring': xpt.Type.Tags.DOMString, + 'astring': xpt.Type.Tags.AString, + 'utf8string': xpt.Type.Tags.UTF8String, + 'cstring': xpt.Type.Tags.CString, + 'jsval': xpt.Type.Tags.jsval +} + + +# XXXkhuey dipper types should go away (bug 677784) +def isDipperType(type): + return type == xpt.Type.Tags.DOMString or type == xpt.Type.Tags.AString or type == xpt.Type.Tags.CString or type == xpt.Type.Tags.UTF8String + + +def build_interface(iface, ifaces): + def get_type(type, calltype, iid_is=None, size_is=None): + """ Return the appropriate xpt.Type object for this param """ + + while isinstance(type, xpidl.Typedef): + type = type.realtype + + if isinstance(type, xpidl.Builtin): + if type.name == 'string' and size_is is not None: + return xpt.StringWithSizeType(size_is, size_is) + elif type.name == 'wstring' and size_is is not None: + return xpt.WideStringWithSizeType(size_is, size_is) + else: + tag = TypeMap[type.name] + isPtr = (tag == xpt.Type.Tags.char_ptr or tag == xpt.Type.Tags.wchar_t_ptr) + return xpt.SimpleType(tag, + pointer=isPtr, + reference=False) + + if isinstance(type, xpidl.Array): + # NB: For an Array<T> we pass down the iid_is to get the type of T. + # This allows Arrays of InterfaceIs types to work. + return xpt.ArrayType(get_type(type.type, calltype, iid_is), size_is, + #XXXkhuey length_is duplicates size_is (bug 677788), + size_is) + + if isinstance(type, xpidl.Interface) or isinstance(type, xpidl.Forward): + xptiface = None + for i in ifaces: + if i.name == type.name: + xptiface = i + + if not xptiface: + xptiface = xpt.Interface(name=type.name) + ifaces.append(xptiface) + + return xpt.InterfaceType(xptiface) + + if isinstance(type, xpidl.Native): + if type.specialtype: + # XXXkhuey jsval is marked differently in the typelib and in the headers :-( + isPtr = (type.isPtr(calltype) or type.isRef(calltype)) and not type.specialtype == 'jsval' + isRef = type.isRef(calltype) and not type.specialtype == 'jsval' + return xpt.SimpleType(TypeMap[type.specialtype], + pointer=isPtr, + reference=isRef) + elif iid_is is not None: + return xpt.InterfaceIsType(iid_is) + else: + # void ptr + return xpt.SimpleType(TypeMap['void'], + pointer=True, + reference=False) + + raise Exception("Unknown type!") + + def get_nsresult(): + return xpt.SimpleType(TypeMap['nsresult']) + + def build_nsresult_param(): + return xpt.Param(get_nsresult()) + + def get_result_type(m): + if not m.notxpcom: + return get_nsresult() + + return get_type(m.realtype, '') + + def build_result_param(m): + return xpt.Param(get_result_type(m)) + + def build_retval_param(m): + type = get_type(m.realtype, 'out') + if isDipperType(type.tag): + # NB: The retval bit needs to be set here, contrary to what the + # xpt spec says. + return xpt.Param(type, in_=True, retval=True, dipper=True) + return xpt.Param(type, in_=False, out=True, retval=True) + + def build_attr_param(a, getter=False, setter=False): + if not (getter or setter): + raise Exception("Attribute param must be for a getter or a setter!") + + type = get_type(a.realtype, getter and 'out' or 'in') + if setter: + return xpt.Param(type) + else: + if isDipperType(type.tag): + # NB: The retval bit needs to be set here, contrary to what the + # xpt spec says. + return xpt.Param(type, in_=True, retval=True, dipper=True) + return xpt.Param(type, in_=False, out=True, retval=True) + + if iface.namemap is None: + raise Exception("Interface was not resolved.") + + consts = [] + methods = [] + + def build_const(c): + consts.append(xpt.Constant(c.name, get_type(c.basetype, ''), c.getValue())) + + def build_method(m): + params = [] + + def build_param(p): + def findattr(p, attr): + if hasattr(p, attr) and getattr(p, attr): + for i, param in enumerate(m.params): + if param.name == getattr(p, attr): + return i + return None + + iid_is = findattr(p, 'iid_is') + size_is = findattr(p, 'size_is') + + in_ = p.paramtype.count("in") + out = p.paramtype.count("out") + dipper = False + type = get_type(p.realtype, p.paramtype, iid_is=iid_is, size_is=size_is) + if out and isDipperType(type.tag): + out = False + dipper = True + + return xpt.Param(type, in_, out, p.retval, p.shared, dipper, p.optional) + + for p in m.params: + params.append(build_param(p)) + + if not m.notxpcom and m.realtype.name != 'void': + params.append(build_retval_param(m)) + + methods.append(xpt.Method(m.name, build_result_param(m), params, + getter=False, setter=False, notxpcom=m.notxpcom, + constructor=False, hidden=m.noscript, + optargc=m.optional_argc, + implicit_jscontext=m.implicit_jscontext)) + + def build_attr(a): + # Write the getter + methods.append(xpt.Method(a.name, build_nsresult_param(), + [build_attr_param(a, getter=True)], + getter=True, setter=False, + constructor=False, hidden=a.noscript, + optargc=False, + implicit_jscontext=a.implicit_jscontext)) + + # And maybe the setter + if not a.readonly: + methods.append(xpt.Method(a.name, build_nsresult_param(), + [build_attr_param(a, setter=True)], + getter=False, setter=True, + constructor=False, hidden=a.noscript, + optargc=False, + implicit_jscontext=a.implicit_jscontext)) + + 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.CDATA): + pass + else: + raise Exception("Unexpected interface member: %s" % member) + + parent = None + if iface.base: + for i in ifaces: + if i.name == iface.base: + parent = i + if not parent: + parent = xpt.Interface(name=iface.base) + ifaces.append(parent) + + return xpt.Interface(iface.name, iface.attributes.uuid, methods=methods, + constants=consts, resolved=True, parent=parent, + scriptable=iface.attributes.scriptable, + function=iface.attributes.function, + builtinclass=iface.attributes.builtinclass, + main_process_scriptable_only=iface.attributes.main_process_scriptable_only) + + +def write_typelib(idl, fd, filename): + """ Generate the typelib. """ + + # We only care about interfaces that are scriptable. + ifaces = [] + for p in idl.productions: + if p.kind == 'interface' and p.attributes.scriptable: + ifaces.append(build_interface(p, ifaces)) + + typelib = xpt.Typelib(interfaces=ifaces) + typelib.writefd(fd) + +if __name__ == '__main__': + from optparse import OptionParser + o = OptionParser() + o.add_option('-I', action='append', dest='incdirs', default=['.'], + help="Directory to search for imported files") + o.add_option('--cachedir', dest='cachedir', default=None, + help="Directory in which to cache lex/parse tables.") + o.add_option('-o', dest='outfile', default=None, + help="Output file") + o.add_option('-d', dest='depfile', default=None, + help="Generate a make dependency file") + o.add_option('--regen', action='store_true', dest='regen', default=False, + help="Regenerate IDL Parser cache") + options, args = o.parse_args() + file = args[0] if args else None + + if options.cachedir is not None: + if not os.path.isdir(options.cachedir): + os.mkdir(options.cachedir) + sys.path.append(options.cachedir) + + if options.regen: + if options.cachedir is None: + print >>sys.stderr, "--regen requires --cachedir" + sys.exit(1) + + p = xpidl.IDLParser(outputdir=options.cachedir, regen=True) + sys.exit(0) + + if options.depfile is not None and options.outfile is None: + print >>sys.stderr, "-d requires -o" + sys.exit(1) + + if options.outfile is not None: + outfd = open(options.outfile, 'wb') + closeoutfd = True + else: + raise "typelib generation requires an output file" + + p = xpidl.IDLParser(outputdir=options.cachedir) + idl = p.parse(open(file).read(), filename=file) + idl.resolve(options.incdirs, p) + write_typelib(idl, outfd, file) + + if closeoutfd: + outfd.close() + + if options.depfile is not None: + depfd = open(options.depfile, 'w') + deps = [dep.replace('\\', '/') for dep in idl.deps] + + print >>depfd, "%s: %s" % (options.outfile, " ".join(deps)) + for dep in deps: + print >>depfd, "%s:" % dep diff --git a/xpcom/idl-parser/xpidl/xpidl.py b/xpcom/idl-parser/xpidl/xpidl.py new file mode 100755 index 000000000..8b2d8c884 --- /dev/null +++ b/xpcom/idl-parser/xpidl/xpidl.py @@ -0,0 +1,1465 @@ +#!/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 sys +import os.path +import re +from ply import lex +from ply import yacc + +"""A type conforms to the following pattern: + + def isScriptable(self): + 'returns True or False' + + def nativeType(self, calltype): + 'returns a string representation of the native type + calltype must be 'in', 'out', or 'inout' + +Interface members const/method/attribute conform to the following pattern: + + name = 'string' + + def toIDL(self): + 'returns the member signature as IDL' +""" + + +def attlistToIDL(attlist): + if len(attlist) == 0: + return '' + + attlist = list(attlist) + attlist.sort(cmp=lambda a, b: cmp(a[0], b[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, signed=False, maybeConst=False): + self.name = name + self.nativename = nativename + self.signed = signed + self.maybeConst = maybeConst + + def isScriptable(self): + return True + + def nativeType(self, calltype, shared=False, const=False): + if const: + print >>sys.stderr, IDLError("[const] doesn't make sense on builtin types.", self.location, warning=True) + const = 'const ' + elif calltype == 'in' and self.nativename.endswith('*'): + const = 'const ' + elif shared: + if not self.nativename.endswith('*'): + raise IDLError("[shared] not applicable to non-pointer types.", self.location) + const = 'const ' + else: + const = '' + return "%s%s %s" % (const, self.nativename, + calltype != 'in' and '*' or '') + +builtinNames = [ + Builtin('boolean', 'bool'), + Builtin('void', 'void'), + Builtin('octet', 'uint8_t'), + Builtin('short', 'int16_t', True, True), + Builtin('long', 'int32_t', True, True), + Builtin('long long', 'int64_t', True, False), + Builtin('unsigned short', 'uint16_t', False, True), + Builtin('unsigned long', 'uint32_t', False, True), + Builtin('unsigned long long', 'uint64_t', False, False), + Builtin('float', 'float', True, False), + Builtin('double', 'double', True, False), + Builtin('char', 'char', True, False), + Builtin('string', 'char *', False, False), + Builtin('wchar', 'char16_t', False, False), + Builtin('wstring', 'char16_t *', False, False), +] + +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 xrange(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 self._d.itervalues() + + 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("Name '%s' not found", location) + + +class IDLError(Exception): + def __init__(self, message, location, warning=False): + self.message = message + self.location = location + self.warning = warning + + def __str__(self): + return "%s: %s, %s" % (self.warning and 'warning' or 'error', + self.message, self.location) + + +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 + + self.IDL = parent.parser.parse(open(file).read(), filename=file) + self.IDL.resolve(parent.incdirs, parent.parser) + 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.productions = productions + self.deps = [] + + def setName(self, object): + self.namemap.set(object) + + def getName(self, id, location): + try: + return self.namemap[id] + except KeyError: + raise IDLError("type '%s' not found" % id, 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): + self.namemap = NameMap() + self.incdirs = incdirs + self.parser = parser + for p in self.productions: + p.resolve(self) + + def includes(self): + for p in self.productions: + if p.kind == 'include': + yield p + + 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): + pass + + 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) + + def isScriptable(self): + return self.realtype.isScriptable() + + def nativeType(self, calltype): + return "%s %s" % (self.name, + calltype != 'in' and '*' or '') + + 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 xrange(0, len(parent.productions)): + if parent.productions[i] is self: + break + for i in xrange(i + 1, len(parent.productions)): + if hasattr(parent.productions[i], 'doccomments'): + parent.productions[i].doccomments[0:0] = self.doccomments + break + + parent.setName(self) + + def isScriptable(self): + return True + + def nativeType(self, calltype): + return "%s %s" % (self.name, + calltype != 'in' and '* *' or '*') + + def __str__(self): + return "forward-declared %s\n" % self.name + + +class Native(object): + kind = 'native' + + modifier = None + specialtype = None + + specialtypes = { + 'nsid': None, + 'domstring': 'nsAString', + 'utf8string': 'nsACString', + 'cstring': 'nsACString', + 'astring': 'nsAString', + 'jsval': 'JS::Value' + } + + 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 isScriptable(self): + if self.specialtype is None: + return False + + if self.specialtype == 'nsid': + return self.modifier is not None + + return self.modifier == 'ref' + + 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.") + const = True + + if self.specialtype is not None and calltype == 'in': + const = True + + if self.specialtype == 'jsval': + if calltype == 'out' or calltype == 'inout': + return "JS::MutableHandleValue " + return "JS::HandleValue " + + if self.isRef(calltype): + m = '& ' + elif self.isPtr(calltype): + m = '*' + ((self.modifier == 'ptr' and calltype != 'in') and '*' or '') + else: + m = calltype != 'in' and '*' or '' + return "%s%s %s" % (const and 'const ' or '', self.nativename, m) + + def __str__(self): + return "native %s(%s)\n" % (self.name, self.nativename) + + +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 + + # 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(self.name, None).doccomments + + if self.attributes.function: + has_method = False + for member in self.members: + if member.kind is '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(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: + print >>sys.stderr, 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) + + 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 isScriptable(self): + # NOTE: this is not whether *this* interface is scriptable... it's + # whether, when used as a type, it's scriptable, which is true of all + # interfaces. + return True + + def nativeType(self, calltype, const=False): + return "%s%s %s" % (const and 'const ' or '', + self.name, + calltype != 'in' and '* *' or '*') + + 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 is not None: + iface = self.idl.getName(self.base, self.location) + if iface is None: + raise IDLError("cannot find symbol '%s'" % name) + c = iface.namemap.get(name, location) + if c.kind != 'const': + raise IDLError("symbol '%s' is not a constant", c.location) + + return c.getValue() + + def needsJSTypes(self): + for m in self.members: + if m.kind == "attribute" and m.type == "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(self.base, self.location) + total += realbase.countEntries() + return total + + +class InterfaceAttributes(object): + uuid = None + scriptable = False + builtinclass = False + function = False + deprecated = False + noscript = False + main_process_scriptable_only = False + + def setuuid(self, value): + self.uuid = value.lower() + + def setscriptable(self): + self.scriptable = True + + def setfunction(self): + self.function = True + + def setnoscript(self): + self.noscript = True + + def setbuiltinclass(self): + self.builtinclass = True + + def setdeprecated(self): + self.deprecated = True + + def setmain_process_scriptable_only(self): + self.main_process_scriptable_only = True + + actions = { + 'uuid': (True, setuuid), + 'scriptable': (False, setscriptable), + 'builtinclass': (False, setbuiltinclass), + 'function': (False, setfunction), + 'noscript': (False, setnoscript), + 'deprecated': (False, setdeprecated), + 'object': (False, lambda self: True), + 'main_process_scriptable_only': (False, setmain_process_scriptable_only), + } + + 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") + return "".join(l) + + +class ConstMember(object): + kind = 'const' + + def __init__(self, type, name, value, location, doccomments): + self.type = type + self.name = name + self.value = 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 + + def getValue(self): + return self.value(self.iface) + + def __str__(self): + return "\tconst %s %s = %s\n" % (self.type, self.name, self.getValue()) + + def count(self): + return 0 + + +class Attribute(object): + kind = 'attribute' + noscript = False + readonly = False + implicit_jscontext = False + nostdcall = False + must_use = False + binaryname = None + null = None + undefined = None + deprecated = False + infallible = 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 name == 'Null': + if value is None: + raise IDLError("'Null' attribute requires a value", aloc) + if readonly: + raise IDLError("'Null' attribute only makes sense for setters", + aloc) + if value not in ('Empty', 'Null', 'Stringify'): + raise IDLError("'Null' attribute value must be 'Empty', 'Null' or 'Stringify'", + aloc) + self.null = value + elif name == 'Undefined': + if value is None: + raise IDLError("'Undefined' attribute requires a value", aloc) + if readonly: + raise IDLError("'Undefined' attribute only makes sense for setters", + aloc) + if value not in ('Empty', 'Null'): + raise IDLError("'Undefined' attribute value must be 'Empty' or 'Null'", + aloc) + self.undefined = value + else: + if value is not None: + raise IDLError("Unexpected attribute value", aloc) + + if name == 'noscript': + self.noscript = True + elif name == 'implicit_jscontext': + self.implicit_jscontext = True + elif name == 'deprecated': + self.deprecated = True + elif name == 'nostdcall': + self.nostdcall = True + elif name == 'must_use': + self.must_use = True + elif name == 'infallible': + self.infallible = 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) + if (self.null is not None and + getBuiltinOrNativeTypeName(self.realtype) != '[domstring]'): + raise IDLError("'Null' attribute can only be used on DOMString", + self.location) + if (self.undefined is not None and + getBuiltinOrNativeTypeName(self.realtype) != '[domstring]'): + raise IDLError("'Undefined' attribute can only be used on DOMString", + self.location) + if self.infallible and not self.realtype.kind == 'builtin': + raise IDLError('[infallible] only works on builtin types ' + '(numbers, booleans, and raw char types)', + self.location) + if self.infallible and not iface.attributes.builtinclass: + raise IDLError('[infallible] attributes are only allowed on ' + '[builtinclass] interfaces', + self.location) + + 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 + + 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 + binaryname = None + implicit_jscontext = False + nostdcall = False + must_use = False + optional_argc = False + deprecated = 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 == 'implicit_jscontext': + self.implicit_jscontext = True + elif name == 'optional_argc': + self.optional_argc = True + elif name == 'deprecated': + self.deprecated = True + elif name == 'nostdcall': + self.nostdcall = True + elif name == 'must_use': + self.must_use = 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) + 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: + found_size_param = False + for size_param in self.params: + if p.size_is == size_param.name: + found_size_param = True + if getBuiltinOrNativeTypeName(size_param.realtype) != 'unsigned long': + raise IDLError("is_size parameter must have type 'unsigned long'", self.location) + if not found_size_param: + raise IDLError("could not find is_size parameter '%s'" % p.size_is, self.location) + + def isScriptable(self): + if not self.iface.attributes.scriptable: + return False + return not (self.noscript or self.notxpcom) + + 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 == "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 + null = None + undefined = 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 == 'Null': + if value is None: + raise IDLError("'Null' must specify a parameter", aloc) + if value not in ('Empty', 'Null', 'Stringify'): + raise IDLError("'Null' parameter value must be 'Empty', 'Null', or 'Stringify'", + aloc) + self.null = value + elif name == 'Undefined': + if value is None: + raise IDLError("'Undefined' must specify a parameter", aloc) + if value not in ('Empty', 'Null'): + raise IDLError("'Undefined' parameter value must be 'Empty' or 'Null'", + aloc) + self.undefined = 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 = Array(self.realtype) + if (self.null is not None and + getBuiltinOrNativeTypeName(self.realtype) != '[domstring]'): + raise IDLError("'Null' attribute can only be used on DOMString", + self.location) + if (self.undefined is not None and + getBuiltinOrNativeTypeName(self.realtype) != '[domstring]'): + raise IDLError("'Undefined' attribute can only be used on DOMString", + self.location) + + 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, e: + raise IDLError(e.message, self.location) + except TypeError, e: + 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 Array(object): + def __init__(self, basetype): + self.type = basetype + + def isScriptable(self): + return self.type.isScriptable() + + def nativeType(self, calltype, const=False): + return "%s%s*" % (const and 'const ' or '', + self.type.nativeType(calltype)) + + +class IDLParser(object): + keywords = { + 'const': 'CONST', + 'interface': 'INTERFACE', + 'in': 'IN', + 'inout': 'INOUT', + 'out': 'OUT', + 'attribute': 'ATTRIBUTE', + 'raises': 'RAISES', + 'readonly': 'READONLY', + 'native': 'NATIVE', + 'typedef': 'TYPEDEF', + } + + 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'/\*(?s).*?\*/' + t.lexer.lineno += t.value.count('\n') + if t.value.startswith("/**"): + self._doccomments.append(t.value) + + def t_singlelinecomment(self, t): + r'(?m)//.*?$' + + 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]*' + t.type = self.keywords.get(t.value, 'IDENTIFIER') + return t + + def t_LCDATA(self, t): + r'(?s)%\{[ ]*C\+\+[ ]*\n(?P<cdata>.*?\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): + r'[^()\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""" + p[0] = list(p[2]) + p[0].insert(0, p[1]) + + def p_typedef(self, p): + """typedef : TYPEDEF IDENTIFIER 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_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) + + l = lambda: 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", + list[0][3]) + + if base is not None: + raise IDLError("Forward-declared interface must not have a base", + l()) + p[0] = Forward(name=name, location=l(), doccomments=doccomments) + else: + p[0] = Interface(name=name, + attlist=attlist, + base=base, + members=body, + location=l(), + 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 IDENTIFIER 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_att(self, p): + """member : attributes optreadonly ATTRIBUTE IDENTIFIER 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 IDENTIFIER 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 IDENTIFIER IDENTIFIER""" + p[0] = Param(paramtype=p[2], + type=p[3], + name=p[4], + attlist=p[1]['attlist'], + location=self.getLocation(p, 3)) + + 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_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, outputdir=''): + self._doccomments = [] + self.lexer = lex.lex(object=self, + outputdir=outputdir, + lextab='xpidllex', + optimize=1) + self.parser = yacc.yacc(module=self, + outputdir=outputdir, + debug=0, + tabmodule='xpidlyacc', + optimize=1) + + 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).read(), filename=f) |