diff options
Diffstat (limited to 'xpcom/idl-parser/xpidl/xpidl.py')
-rwxr-xr-x | xpcom/idl-parser/xpidl/xpidl.py | 1465 |
1 files changed, 1465 insertions, 0 deletions
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) |