diff options
author | Josh Matthews <josh@joshmatthews.net> | 2013-01-16 15:04:36 +0100 |
---|---|---|
committer | Josh Matthews <josh@joshmatthews.net> | 2013-03-13 11:40:16 -0400 |
commit | ebd1ce8055fcca488ca91fff768afdbf34d24a5f (patch) | |
tree | 9b17b8d9b39ba75a92ee570bedfe8b39c485ad5f /src/servo/dom/bindings/codegen/CodegenRust.py | |
parent | 30676402f5de81f869fcbeff1d639f74c5ebcc9c (diff) | |
download | servo-ebd1ce8055fcca488ca91fff768afdbf34d24a5f.tar.gz servo-ebd1ce8055fcca488ca91fff768afdbf34d24a5f.zip |
Initial dump of codegen work. Requires manual running of various python scripts to build servo.
Diffstat (limited to 'src/servo/dom/bindings/codegen/CodegenRust.py')
-rw-r--r-- | src/servo/dom/bindings/codegen/CodegenRust.py | 1291 |
1 files changed, 1291 insertions, 0 deletions
diff --git a/src/servo/dom/bindings/codegen/CodegenRust.py b/src/servo/dom/bindings/codegen/CodegenRust.py new file mode 100644 index 00000000000..44d01e4ed9a --- /dev/null +++ b/src/servo/dom/bindings/codegen/CodegenRust.py @@ -0,0 +1,1291 @@ +# 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/. + +# Common codegen classes. + +import os +import string +import operator + +from WebIDL import * +from Configuration import NoSuchDescriptorError + +AUTOGENERATED_WARNING_COMMENT = \ + "/* THIS FILE IS AUTOGENERATED - DO NOT EDIT */\n\n" +ADDPROPERTY_HOOK_NAME = '_addProperty' +FINALIZE_HOOK_NAME = '_finalize' +TRACE_HOOK_NAME = '_trace' +CONSTRUCT_HOOK_NAME = '_constructor' +HASINSTANCE_HOOK_NAME = '_hasInstance' + +def replaceFileIfChanged(filename, newContents): + """ + Read a copy of the old file, so that we don't touch it if it hasn't changed. + Returns True if the file was updated, false otherwise. + """ + oldFileContents = "" + try: + oldFile = open(filename, 'rb') + oldFileContents = ''.join(oldFile.readlines()) + oldFile.close() + except: + pass + + if newContents == oldFileContents: + return False + + f = open(filename, 'wb') + f.write(newContents) + f.close() + +def toStringBool(arg): + return str(not not arg).lower() + +def toBindingNamespace(arg): + return re.sub("((_workers)?$)", "Binding\\1", arg); + +def stripTrailingWhitespace(text): + tail = '\n' if text.endswith('\n') else '' + lines = text.splitlines() + for i in range(len(lines)): + lines[i] = lines[i].rstrip() + return '\n'.join(lines) + tail + +def MakeNativeName(name): + return name[0].upper() + name[1:] + +builtinNames = { + IDLType.Tags.bool: 'bool', + IDLType.Tags.int8: 'i8', + IDLType.Tags.int16: 'i16', + IDLType.Tags.int32: 'i32', + IDLType.Tags.int64: 'i64', + IDLType.Tags.uint8: 'u8', + IDLType.Tags.uint16: 'u16', + IDLType.Tags.uint32: 'u32', + IDLType.Tags.uint64: 'u64', + IDLType.Tags.float: 'f32', + IDLType.Tags.double: 'f64' +} + +class CastableObjectUnwrapper(): + """ + A class for unwrapping an object named by the "source" argument + based on the passed-in descriptor and storing it in a variable + called by the name in the "target" argument. + + codeOnFailure is the code to run if unwrapping fails. + """ + def __init__(self, descriptor, source, target, codeOnFailure): + assert descriptor.castable + + self.substitution = { "type" : descriptor.nativeType, + "protoID" : "prototypes::id::" + descriptor.name, + "source" : source, + "target" : target, + "codeOnFailure" : CGIndenter(CGGeneric(codeOnFailure), 4).define() } + if descriptor.hasXPConnectImpls: + # We don't use xpc_qsUnwrapThis because it will always throw on + # unwrap failure, whereas we want to control whether we throw or + # not. + self.substitution["codeOnFailure"] = CGIndenter(CGGeneric(string.Template( + "${type} *objPtr;\n" + "xpc_qsSelfRef objRef;\n" + "JS::Value val = JS::ObjectValue(*${source});\n" + "nsresult rv = xpc_qsUnwrapArg<${type}>(cx, val, &objPtr, &objRef.ptr, &val);\n" + "if (NS_FAILED(rv)) {\n" + "${codeOnFailure}\n" + "}\n" + "// We should be castable!\n" + "MOZ_ASSERT(!objRef.ptr);\n" + "// We should have an object, too!\n" + "MOZ_ASSERT(objPtr);\n" + "${target} = objPtr;").substitute(self.substitution)), 4).define() + + def __str__(self): + return string.Template( +"""${target} = unwrap<${type}>(${source}); +""").substitute(self.substitution) +#"""{ +# nsresult rv = UnwrapObject<${protoID}, ${type}>(cx, ${source}, ${target}); +# if (NS_FAILED(rv)) { +#${codeOnFailure} +# } +#}""").substitute(self.substitution) + +class FakeCastableDescriptor(): + def __init__(self, descriptor): + self.castable = True + self.workers = descriptor.workers + self.nativeType = descriptor.nativeType + self.name = descriptor.name + self.hasXPConnectImpls = descriptor.hasXPConnectImpls + +def getWrapTemplateForType(type, descriptorProvider, result, successCode, + isCreator): + """ + Reflect a C++ value stored in "result", of IDL type "type" into JS. The + "successCode" is the code to run once we have successfully done the + conversion. The resulting string should be used with string.Template, it + needs the following keys when substituting: jsvalPtr/jsvalRef/obj. + + Returns (templateString, infallibility of conversion template) + """ + haveSuccessCode = successCode is not None + if not haveSuccessCode: + successCode = "return true;" + + def setValue(value, callWrapValue=False): + """ + Returns the code to set the jsval to value. If "callWrapValue" is true + JS_WrapValue will be called on the jsval. + """ + if not callWrapValue: + tail = successCode + elif haveSuccessCode: + tail = ("if (!JS_WrapValue(cx, ${jsvalPtr})) {\n" + + " return false;\n" + + "}\n" + + successCode) + else: + tail = "return JS_WrapValue(cx, ${jsvalPtr});" + return ("${jsvalRef} = %s;\n" + + tail) % (value) + + def wrapAndSetPtr(wrapCall, failureCode=None): + """ + Returns the code to set the jsval by calling "wrapCall". "failureCode" + is the code to run if calling "wrapCall" fails + """ + if failureCode is None: + if not haveSuccessCode: + return "return " + wrapCall + ";" + failureCode = "return false;" + str = ("if (!%s) {\n" + + CGIndenter(CGGeneric(failureCode)).define() + "\n" + + "}\n" + + successCode) % (wrapCall) + return str + + if type is None or type.isVoid(): + return (setValue("JSVAL_VOID"), True) + + if type.isArray(): + raise TypeError("Can't handle array return values yet") + + if type.isSequence(): + if type.nullable(): + # Nullable sequences are Nullable< nsTArray<T> > + (recTemplate, recInfall) = getWrapTemplateForType(type.inner, descriptorProvider, + "%s.Value()" % result, successCode, + isCreator) + return (""" +if (%s.IsNull()) { +%s +} +%s""" % (result, CGIndenter(CGGeneric(setValue("JSVAL_NULL"))).define(), recTemplate), recInfall) + + # Now do non-nullable sequences. We use setting the element + # in the array as our succcess code because when we succeed in + # wrapping that's what we should do. + innerTemplate = wrapForType( + type.inner, descriptorProvider, + { + 'result' : "%s[i]" % result, + 'successCode': ("if (!JS_DefineElement(cx, returnArray, i, tmp,\n" + " NULL, NULL, JSPROP_ENUMERATE)) {\n" + " return false;\n" + "}"), + 'jsvalRef': "tmp", + 'jsvalPtr': "&tmp", + 'isCreator': isCreator + } + ) + innerTemplate = CGIndenter(CGGeneric(innerTemplate)).define() + return ((""" +uint32_t length = %s.Length(); +JSObject *returnArray = JS_NewArrayObject(cx, length, NULL); +if (!returnArray) { + return false; +} +jsval tmp; +for (uint32_t i = 0; i < length; ++i) { +%s +}\n""" % (result, innerTemplate)) + setValue("JS::ObjectValue(*returnArray)"), False) + + if type.isGeckoInterface(): + descriptor = descriptorProvider.getDescriptor(type.unroll().inner.identifier.name) + if type.nullable(): + wrappingCode = ("if (!%s) {\n" % (result) + + CGIndenter(CGGeneric(setValue("JSVAL_NULL"))).define() + "\n" + + "}\n") + else: + wrappingCode = "" + if (not descriptor.interface.isExternal() and + not descriptor.interface.isCallback()): + if descriptor.wrapperCache: + wrapMethod = "WrapNewBindingObject" + else: + if not isCreator: + raise MethodNotCreatorError(descriptor.interface.identifier.name) + wrapMethod = "WrapNewBindingNonWrapperCachedObject" + wrap = "%s(cx, ${obj}, %s, ${jsvalPtr})" % (wrapMethod, result) + # We don't support prefable stuff in workers. + assert(not descriptor.prefable or not descriptor.workers) + if not descriptor.prefable: + # Non-prefable bindings can only fail to wrap as a new-binding object + # if they already threw an exception. Same thing for + # non-prefable bindings. + failed = ("MOZ_ASSERT(JS_IsExceptionPending(cx));\n" + + "return false;") + else: + if descriptor.notflattened: + raise TypeError("%s is prefable but not flattened; " + "fallback won't work correctly" % + descriptor.interface.identifier.name) + # Try old-style wrapping for bindings which might be preffed off. + failed = wrapAndSetPtr("HandleNewBindingWrappingFailure(cx, ${obj}, %s, ${jsvalPtr})" % result) + wrappingCode += wrapAndSetPtr(wrap, failed) + else: + if descriptor.notflattened: + getIID = "&NS_GET_IID(%s), " % descriptor.nativeType + else: + getIID = "" + wrap = "WrapObject(cx, ${obj}, %s, %s${jsvalPtr})" % (result, getIID) + wrappingCode += wrapAndSetPtr(wrap) + return (wrappingCode, False) + + if type.isString(): + if type.nullable(): + return (wrapAndSetPtr("xpc::StringToJsval(cx, %s, ${jsvalPtr})" % result), False) + else: + return (wrapAndSetPtr("xpc::NonVoidStringToJsval(cx, %s, ${jsvalPtr})" % result), False) + + if type.isEnum(): + if type.nullable(): + raise TypeError("We don't support nullable enumerated return types " + "yet") + return ("""MOZ_ASSERT(uint32_t(%(result)s) < ArrayLength(%(strings)s)); +JSString* %(resultStr)s = JS_NewStringCopyN(cx, %(strings)s[uint32_t(%(result)s)].value, %(strings)s[uint32_t(%(result)s)].length); +if (!%(resultStr)s) { + return false; +} +""" % { "result" : result, + "resultStr" : result + "_str", + "strings" : type.inner.identifier.name + "Values::strings" } + + setValue("JS::StringValue(%s_str)" % result), False) + + if type.isCallback(): + assert not type.isInterface() + # XXXbz we're going to assume that callback types are always + # nullable and always have [TreatNonCallableAsNull] for now. + # See comments in WrapNewBindingObject explaining why we need + # to wrap here. + # NB: setValue(..., True) calls JS_WrapValue(), so is fallible + return (setValue("JS::ObjectOrNullValue(%s)" % result, True), False) + + if type.tag() == IDLType.Tags.any: + # See comments in WrapNewBindingObject explaining why we need + # to wrap here. + # NB: setValue(..., True) calls JS_WrapValue(), so is fallible + return (setValue(result, True), False) + + if type.isObject() or type.isSpiderMonkeyInterface(): + # See comments in WrapNewBindingObject explaining why we need + # to wrap here. + if type.nullable(): + toValue = "JS::ObjectOrNullValue(%s)" + else: + toValue = "JS::ObjectValue(*%s)" + # NB: setValue(..., True) calls JS_WrapValue(), so is fallible + return (setValue(toValue % result, True), False) + + if not type.isPrimitive(): + raise TypeError("Need to learn to wrap %s" % type) + + if type.nullable(): + (recTemplate, recInfal) = getWrapTemplateForType(type.inner, descriptorProvider, + "%s.Value()" % result, successCode, + isCreator) + return ("if (%s.IsNull()) {\n" % result + + CGIndenter(CGGeneric(setValue("JSVAL_NULL"))).define() + "\n" + + "}\n" + recTemplate, recInfal) + + tag = type.tag() + + if tag in [IDLType.Tags.int8, IDLType.Tags.uint8, IDLType.Tags.int16, + IDLType.Tags.uint16, IDLType.Tags.int32]: + return (setValue("INT_TO_JSVAL(int32_t(%s))" % result), True) + + elif tag in [IDLType.Tags.int64, IDLType.Tags.uint64, IDLType.Tags.float, + IDLType.Tags.double]: + # XXXbz will cast to double do the "even significand" thing that webidl + # calls for for 64-bit ints? Do we care? + return (setValue("JS_NumberValue(%s as f64)" % result), True) + + elif tag == IDLType.Tags.uint32: + return (setValue("UINT_TO_JSVAL(%s)" % result), True) + + elif tag == IDLType.Tags.bool: + return (setValue("BOOLEAN_TO_JSVAL(%s)" % result), True) + + else: + raise TypeError("Need to learn to wrap primitive: %s" % type) + +def wrapForType(type, descriptorProvider, templateValues): + """ + Reflect a C++ value of IDL type "type" into JS. TemplateValues is a dict + that should contain: + + * 'jsvalRef': a C++ reference to the jsval in which to store the result of + the conversion + * 'jsvalPtr': a C++ pointer to the jsval in which to store the result of + the conversion + * 'obj' (optional): the name of the variable that contains the JSObject to + use as a scope when wrapping, if not supplied 'obj' + will be used as the name + * 'result' (optional): the name of the variable in which the C++ value is + stored, if not supplied 'result' will be used as + the name + * 'successCode' (optional): the code to run once we have successfully done + the conversion, if not supplied 'return true;' + will be used as the code + * 'isCreator' (optional): If true, we're wrapping for the return value of + a [Creator] method. Assumed false if not set. + """ + wrap = getWrapTemplateForType(type, descriptorProvider, + templateValues.get('result', 'result'), + templateValues.get('successCode', None), + templateValues.get('isCreator', False))[0] + + defaultValues = {'obj': 'obj'} + return string.Template(wrap).substitute(defaultValues, **templateValues) + +def typeNeedsCx(type, retVal=False): + if type is None: + return False + if type.nullable(): + type = type.inner + if type.isSequence() or type.isArray(): + type = type.inner + if type.isUnion(): + return any(typeNeedsCx(t) for t in type.unroll().flatMemberTypes) + if retVal and type.isSpiderMonkeyInterface(): + return True + return type.isCallback() or type.isAny() or type.isObject() + +def memberIsCreator(member): + return member.getExtendedAttribute("Creator") is not None + +# Returns a tuple consisting of a CGThing containing the type of the return +# value, or None if there is no need for a return value, and a boolean signaling +# whether the return value is passed in an out parameter. +def getRetvalDeclarationForType(returnType, descriptorProvider, + resultAlreadyAddRefed): + if returnType is None or returnType.isVoid(): + # Nothing to declare + return None, False + if returnType.isPrimitive() and returnType.tag() in builtinNames: + result = CGGeneric(builtinNames[returnType.tag()]) + if returnType.nullable(): + result = CGWrapper(result, pre="Nullable<", post=">") + return result, False + if returnType.isString(): + return CGGeneric("nsString"), True + if returnType.isEnum(): + if returnType.nullable(): + raise TypeError("We don't support nullable enum return values") + return CGGeneric(returnType.inner.identifier.name), False + if returnType.isGeckoInterface(): + result = CGGeneric(descriptorProvider.getDescriptor( + returnType.unroll().inner.identifier.name).nativeType) + if resultAlreadyAddRefed: + result = CGWrapper(result, pre="nsRefPtr<", post=">") + else: + result = CGWrapper(result, post="*") + return result, False + if returnType.isCallback(): + # XXXbz we're going to assume that callback types are always + # nullable for now. + return CGGeneric("*JSObject"), False + if returnType.isAny(): + return CGGeneric("jsval"), False + if returnType.isObject() or returnType.isSpiderMonkeyInterface(): + return CGGeneric("*JSObject"), False + if returnType.isSequence(): + nullable = returnType.nullable() + if nullable: + returnType = returnType.inner + # If our result is already addrefed, use the right type in the + # sequence argument here. + (result, _) = getRetvalDeclarationForType(returnType.inner, + descriptorProvider, + resultAlreadyAddRefed) + result = CGWrapper(result, pre="nsTArray< ", post=" >") + if nullable: + result = CGWrapper(result, pre="Nullable< ", post=" >") + return result, True + raise TypeError("Don't know how to declare return value for %s" % + returnType) + +class PropertyArrays(): + def __init__(self, descriptor): + #self.staticMethods = MethodDefiner(descriptor, "StaticMethods", True) + #self.methods = MethodDefiner(descriptor, "Methods", False) + #self.attrs = AttrDefiner(descriptor, "Attributes") + #self.consts = ConstDefiner(descriptor, "Constants") + pass + + @staticmethod + def arrayNames(): + return [ "staticMethods", "methods", "attrs", "consts" ] + + @staticmethod + def xrayRelevantArrayNames(): + return [ "methods", "attrs", "consts" ] + + def hasChromeOnly(self): + return reduce(lambda b, a: b or getattr(self, a).hasChromeOnly(), + self.arrayNames(), False) + def variableNames(self, chrome): + names = {} + for array in self.arrayNames(): + names[array] = getattr(self, array).variableName(chrome) + return names + def __str__(self): + define = "" + for array in self.arrayNames(): + define += str(getattr(self, array)) + return define + +class CGThing(): + """ + Abstract base class for things that spit out code. + """ + def __init__(self): + pass # Nothing for now + def declare(self): + """Produce code for a header file.""" + assert(False) # Override me! + def define(self): + """Produce code for a cpp file.""" + assert(False) # Override me! + +# We'll want to insert the indent at the beginnings of lines, but we +# don't want to indent empty lines. So only indent lines that have a +# non-newline character on them. +lineStartDetector = re.compile("^(?=[^\n#])", re.MULTILINE) +class CGIndenter(CGThing): + """ + A class that takes another CGThing and generates code that indents that + CGThing by some number of spaces. The default indent is two spaces. + """ + def __init__(self, child, indentLevel=2, declareOnly=False): + CGThing.__init__(self) + self.child = child + self.indent = " " * indentLevel + self.declareOnly = declareOnly + def declare(self): + decl = self.child.declare() + if decl is not "": + return re.sub(lineStartDetector, self.indent, decl) + else: + return "" + def define(self): + defn = self.child.define() + if defn is not "" and not self.declareOnly: + return re.sub(lineStartDetector, self.indent, defn) + else: + return defn + +class CGWrapper(CGThing): + """ + Generic CGThing that wraps other CGThings with pre and post text. + """ + def __init__(self, child, pre="", post="", declarePre=None, + declarePost=None, definePre=None, definePost=None, + declareOnly=False, defineOnly=False, reindent=False): + CGThing.__init__(self) + self.child = child + self.declarePre = declarePre or pre + self.declarePost = declarePost or post + self.definePre = definePre or pre + self.definePost = definePost or post + self.declareOnly = declareOnly + self.defineOnly = defineOnly + self.reindent = reindent + def declare(self): + if self.defineOnly: + return '' + decl = self.child.declare() + if self.reindent: + # We don't use lineStartDetector because we don't want to + # insert whitespace at the beginning of our _first_ line. + decl = stripTrailingWhitespace( + decl.replace("\n", "\n" + (" " * len(self.declarePre)))) + return self.declarePre + decl + self.declarePost + def define(self): + if self.declareOnly: + return '' + defn = self.child.define() + if self.reindent: + # We don't use lineStartDetector because we don't want to + # insert whitespace at the beginning of our _first_ line. + defn = stripTrailingWhitespace( + defn.replace("\n", "\n" + (" " * len(self.definePre)))) + return self.definePre + defn + self.definePost + +class CGImports(CGWrapper): + """ + Generates the appropriate import/use statements. + """ + def __init__(self, descriptors, dictionaries, declareImports, defineImports, child): + """ + Builds a set of imports to cover |descriptors|. + + Also includes the files in |declareIncludes| in the header + file and the files in |defineIncludes| in the .cpp. + """ + + # TODO imports to cover descriptors, etc. + + def _useString(imports): + return ''.join(['use %s;\n' % i for i in imports]) + '\n' + CGWrapper.__init__(self, child, + definePre=_useString(sorted(defineImports))) + +class CGNamespace(CGWrapper): + def __init__(self, namespace, child, declareOnly=False): + pre = "mod %s {\n" % namespace + post = "} // mod %s\n" % namespace + CGWrapper.__init__(self, child, pre=pre, post=post, + declareOnly=declareOnly) + @staticmethod + def build(namespaces, child, declareOnly=False): + """ + Static helper method to build multiple wrapped namespaces. + """ + if not namespaces: + return CGWrapper(child, declareOnly=declareOnly) + inner = CGNamespace.build(namespaces[1:], child, declareOnly=declareOnly) + return CGNamespace(namespaces[0], inner, declareOnly=declareOnly) + +class CGList(CGThing): + """ + Generate code for a list of GCThings. Just concatenates them together, with + an optional joiner string. "\n" is a common joiner. + """ + def __init__(self, children, joiner=""): + CGThing.__init__(self) + self.children = children + self.joiner = joiner + def append(self, child): + self.children.append(child) + def prepend(self, child): + self.children.insert(0, child) + def join(self, generator): + return self.joiner.join(filter(lambda s: len(s) > 0, (child for child in generator))) + def declare(self): + return self.join(child.declare() for child in self.children if child is not None) + def define(self): + return self.join(child.define() for child in self.children if child is not None) + +class CGGeneric(CGThing): + """ + A class that spits out a fixed string into the codegen. Can spit out a + separate string for the declaration too. + """ + def __init__(self, define="", declare=""): + self.declareText = declare + self.defineText = define + def declare(self): + return self.declareText + def define(self): + return self.defineText + +class Argument(): + """ + A class for outputting the type and name of an argument + """ + def __init__(self, argType, name): + self.argType = argType + self.name = name + def __str__(self): + return self.name + ': ' + self.argType + +class CGAbstractMethod(CGThing): + """ + An abstract class for generating code for a method. Subclasses + should override definition_body to create the actual code. + + descriptor is the descriptor for the interface the method is associated with + + name is the name of the method as a string + + returnType is the IDLType of the return value + + args is a list of Argument objects + + inline should be True to generate an inline method, whose body is + part of the declaration. + + alwaysInline should be True to generate an inline method annotated with + MOZ_ALWAYS_INLINE. + + static should be True to generate a static method, which only has + a definition. + + If templateArgs is not None it should be a list of strings containing + template arguments, and the function will be templatized using those + arguments. + """ + def __init__(self, descriptor, name, returnType, args, inline=False, alwaysInline=False, static=False, extern=False, templateArgs=None): + CGThing.__init__(self) + self.descriptor = descriptor + self.name = name + self.returnType = returnType + self.args = args + self.inline = inline + self.alwaysInline = alwaysInline + self.static = static + self.extern = extern + self.templateArgs = templateArgs + def _argstring(self): + return ', '.join([str(a) for a in self.args]) + def _template(self): + if self.templateArgs is None: + return '' + return '<%s>\n' % ', '.join(self.templateArgs) + def _decorators(self): + decorators = [] + if self.alwaysInline: + decorators.append('#[inline(always)]') + elif self.inline: + #decorators.append('inline') + pass + if self.extern: + decorators.append('extern') + if self.static: + #decorators.append('static') + pass + if not decorators: + return '' + #maybeNewline = " " if self.inline else "\n" + maybeNewline = " " + return ' '.join(decorators) + maybeNewline + def _returnType(self): + return (" -> %s" % self.returnType) if self.returnType != "void" else "" + def declare(self): + if self.inline: + return self._define() + return "%sfn %s%s(%s)%s;\n" % (self._decorators(), self.name, self._template(), + self.name, self._argstring(), self._returnType()) + def _define(self): + return self.definition_prologue() + "\n" + self.definition_body() + self.definition_epilogue() + def define(self): + return "" if self.inline else self._define() + def definition_prologue(self): + return "%sfn %s%s(%s)%s unsafe {" % (self._decorators(), self.name, self._template(), + self._argstring(), self._returnType()) + def definition_epilogue(self): + return "\n}\n" + def definition_body(self): + assert(False) # Override me! + +class CGAbstractExternMethod(CGAbstractMethod): + """ + Abstract base class for codegen of implementation-only (no + declaration) static methods. + """ + def __init__(self, descriptor, name, returnType, args): + CGAbstractMethod.__init__(self, descriptor, name, returnType, args, + inline=False, extern=True) + def declare(self): + # We only have implementation + return "" + +class CGCallGenerator(CGThing): + """ + A class to generate an actual call to a C++ object. Assumes that the C++ + object is stored in a variable whose name is given by the |object| argument. + + errorReport should be a CGThing for an error report or None if no + error reporting is needed. + """ + def __init__(self, errorReport, arguments, argsPre, returnType, + extendedAttributes, descriptorProvider, nativeMethodName, + static, object="self", declareResult=True): + CGThing.__init__(self) + + assert errorReport is None or isinstance(errorReport, CGThing) + + isFallible = errorReport is not None + + #resultAlreadyAddRefed = isResultAlreadyAddRefed(descriptorProvider, + # extendedAttributes) + resultAlreadyAddRefed = True + (result, resultOutParam) = getRetvalDeclarationForType(returnType, + descriptorProvider, + resultAlreadyAddRefed) + + args = CGList([CGGeneric(arg) for arg in argsPre], ", ") + for (a, name) in arguments: + # This is a workaround for a bug in Apple's clang. + if a.type.isObject() and not a.type.nullable() and not a.optional: + name = "(JSObject&)" + name + args.append(CGGeneric(name)) + + # Return values that go in outparams go here + if resultOutParam: + args.append(CGGeneric("result")) + if isFallible: + args.append(CGGeneric("rv")) + + needsCx = (typeNeedsCx(returnType, True) or + any(typeNeedsCx(a.type) for (a, _) in arguments) or + 'implicitJSContext' in extendedAttributes) + + if not "cx" in argsPre and needsCx: + args.prepend(CGGeneric("cx")) + + # Build up our actual call + self.cgRoot = CGList([], "\n") + + call = CGGeneric(nativeMethodName) + if static: + call = CGWrapper(call, pre="%s::" % descriptorProvider.nativeType) + else: + call = CGWrapper(call, pre="(*%s)." % object) + call = CGList([call, CGWrapper(args, pre="(", post=");")]) + if result is not None: + if declareResult: + result = CGWrapper(result, pre="let result: ", post=";") + self.cgRoot.prepend(result) + if not resultOutParam: + call = CGWrapper(call, pre="result = ") + + call = CGWrapper(call) + self.cgRoot.append(call) + + if isFallible: + self.cgRoot.prepend(CGGeneric("ErrorResult rv;")) + self.cgRoot.append(CGGeneric("if (rv.Failed()) {")) + self.cgRoot.append(CGIndenter(errorReport)) + self.cgRoot.append(CGGeneric("}")) + + def define(self): + return self.cgRoot.define() + +class MethodNotCreatorError(Exception): + def __init__(self, typename): + self.typename = typename + +class CGPerSignatureCall(CGThing): + """ + This class handles the guts of generating code for a particular + call signature. A call signature consists of four things: + + 1) A return type, which can be None to indicate that there is no + actual return value (e.g. this is an attribute setter) or an + IDLType if there's an IDL type involved (including |void|). + 2) An argument list, which is allowed to be empty. + 3) A name of a native method to call. + 4) Whether or not this method is static. + + We also need to know whether this is a method or a getter/setter + to do error reporting correctly. + + The idlNode parameter can be either a method or an attr. We can query + |idlNode.identifier| in both cases, so we can be agnostic between the two. + """ + # XXXbz For now each entry in the argument list is either an + # IDLArgument or a FakeArgument, but longer-term we may want to + # have ways of flagging things like JSContext* or optional_argc in + # there. + + def __init__(self, returnType, argsPre, arguments, nativeMethodName, static, + descriptor, idlNode, argConversionStartsAt=0, + getter=False, setter=False): + CGThing.__init__(self) + self.returnType = returnType + self.descriptor = descriptor + self.idlNode = idlNode + self.extendedAttributes = descriptor.getExtendedAttributes(idlNode, + getter=getter, + setter=setter) + self.argsPre = argsPre + self.arguments = arguments + self.argCount = len(arguments) + if self.argCount > argConversionStartsAt: + # Insert our argv in there + cgThings = [CGGeneric(self.getArgvDecl())] + else: + cgThings = [] + cgThings.extend([CGArgumentConverter(arguments[i], i, self.getArgv(), + self.getArgc(), self.descriptor, + invalidEnumValueFatal=not setter) for + i in range(argConversionStartsAt, self.argCount)]) + + cgThings.append(CGCallGenerator( + self.getErrorReport() if self.isFallible() else None, + self.getArguments(), self.argsPre, returnType, + self.extendedAttributes, descriptor, nativeMethodName, + static)) + self.cgRoot = CGList(cgThings, "\n") + + def getArgv(self): + return "argv" if self.argCount > 0 else "" + def getArgvDecl(self): + return "\nargv: *jsval = JS_ARGV(cx, vp);\n" + def getArgc(self): + return "argc" + def getArguments(self): + return [(a, "arg" + str(i)) for (i, a) in enumerate(self.arguments)] + + def isFallible(self): + return not 'infallible' in self.extendedAttributes + + def wrap_return_value(self): + isCreator = memberIsCreator(self.idlNode) + if isCreator: + # We better be returning addrefed things! + #assert(isResultAlreadyAddRefed(self.descriptor, + # self.extendedAttributes) or + # Workers use raw pointers for new-object return + # values or something + # self.descriptor.workers) + pass + + resultTemplateValues = { 'jsvalRef': '*vp', 'jsvalPtr': 'vp', + 'isCreator': isCreator} + try: + return wrapForType(self.returnType, self.descriptor, + resultTemplateValues) + except MethodNotCreatorError, err: + assert not isCreator + raise TypeError("%s being returned from non-creator method or property %s.%s" % + (err.typename, + self.descriptor.interface.identifier.name, + self.idlNode.identifier.name)) + + def getErrorReport(self): + return CGGeneric('return ThrowMethodFailedWithDetails<%s>(cx, rv, "%s", "%s");' + % (toStringBool(not self.descriptor.workers), + self.descriptor.interface.identifier.name, + self.idlNode.identifier.name)) + + def define(self): + return (self.cgRoot.define() + "\n" + self.wrap_return_value()) + +class CGGetterCall(CGPerSignatureCall): + """ + A class to generate a native object getter call for a particular IDL + getter. + """ + def __init__(self, returnType, nativeMethodName, descriptor, attr): + CGPerSignatureCall.__init__(self, returnType, [], [], + nativeMethodName, False, descriptor, + attr, getter=True) + +class CGAbstractBindingMethod(CGAbstractExternMethod): + """ + Common class to generate the JSNatives for all our methods, getters, and + setters. This will generate the function declaration and unwrap the + |this| object. Subclasses are expected to override the generate_code + function to do the rest of the work. This function should return a + CGThing which is already properly indented. + """ + def __init__(self, descriptor, name, args, unwrapFailureCode=None): + CGAbstractExternMethod.__init__(self, descriptor, name, "JSBool", args) + + if unwrapFailureCode is None: + self.unwrapFailureCode = ("return Throw<%s>(cx, rv);" % + toStringBool(not descriptor.workers)) + else: + self.unwrapFailureCode = unwrapFailureCode + + def definition_body(self): + # Our descriptor might claim that we're not castable, simply because + # we're someone's consequential interface. But for this-unwrapping, we + # know that we're the real deal. So fake a descriptor here for + # consumption by FailureFatalCastableObjectUnwrapper. + unwrapThis = CGIndenter(CGGeneric( + str(CastableObjectUnwrapper( + FakeCastableDescriptor(self.descriptor), + "obj", "self", self.unwrapFailureCode)))) + return CGList([ self.getThis(), unwrapThis, + self.generate_code() ], "\n").define() + + def getThis(self): + return CGIndenter( + CGGeneric("let obj: *JSObject = JS_THIS_OBJECT(cx, vp);\n" + "if obj.is_null() {\n" + " return false as JSBool;\n" + "}\n" + "\n" + "let self: *%s;" % self.descriptor.nativeType)) + + def generate_code(self): + assert(False) # Override me + +class CGGenericMethod(CGAbstractBindingMethod): + """ + A class for generating the C++ code for an IDL method.. + """ + def __init__(self, descriptor): + args = [Argument('*JSContext', 'cx'), Argument('uint', 'argc'), + Argument('*jsval', 'vp')] + CGAbstractBindingMethod.__init__(self, descriptor, 'genericMethod', args) + + def generate_code(self): + return CGIndenter(CGGeneric( + "let info: *JSJitInfo = FUNCTION_VALUE_TO_JITINFO(JS_CALLEE(cx, vp));\n" + "let method: JSJitMethodOp = (*info).op;\n" + "return method(cx, obj, self, argc, vp);")) + +class CGGenericGetter(CGAbstractBindingMethod): + """ + A class for generating the C++ code for an IDL attribute getter. + """ + def __init__(self, descriptor, lenientThis=False): + args = [Argument('*JSContext', 'cx'), Argument('uint', 'argc'), + Argument('*jsval', 'vp')] + if lenientThis: + name = "genericLenientGetter" + unwrapFailureCode = ( + "MOZ_ASSERT(!JS_IsExceptionPending(cx));\n" + "JS_SET_RVAL(cx, vp, JS::UndefinedValue());\n" + "return true;") + else: + name = "genericGetter" + unwrapFailureCode = None + CGAbstractBindingMethod.__init__(self, descriptor, name, args, + unwrapFailureCode) + + def generate_code(self): + return CGIndenter(CGGeneric( + "let info: *JSJitInfo = FUNCTION_VALUE_TO_JITINFO(JS_CALLEE(cx, vp));\n" + "let getter: JSJitPropertyOp = (*info).op;\n" + "return getter(cx, obj, self, vp);")) + +class CGSpecializedGetter(CGAbstractExternMethod): + """ + A class for generating the code for a specialized attribute getter + that the JIT can call with lower overhead. + """ + def __init__(self, descriptor, attr): + self.attr = attr + name = 'get_' + attr.identifier.name + args = [ Argument('*JSContext', 'cx'), + Argument('*JSObject', 'obj'), + Argument('*%s' % descriptor.nativeType, 'self'), + Argument('*mut jsval', 'vp') ] + CGAbstractExternMethod.__init__(self, descriptor, name, "bool", args) + + def definition_body(self): + name = self.attr.identifier.name + nativeName = MakeNativeName(self.descriptor.binaryNames.get(name, name)) + # resultOutParam does not depend on whether resultAlreadyAddRefed is set + (_, resultOutParam) = getRetvalDeclarationForType(self.attr.type, + self.descriptor, + False) + infallible = ('infallible' in + self.descriptor.getExtendedAttributes(self.attr, + getter=True)) + if resultOutParam or self.attr.type.nullable() or not infallible: + nativeName = "Get" + nativeName + return CGIndenter(CGGetterCall(self.attr.type, nativeName, + self.descriptor, self.attr)).define() + +def infallibleForMember(member, type, descriptorProvider): + """ + Determine the fallibility of changing a C++ value of IDL type "type" into + JS for the given attribute. Apart from isCreator, all the defaults are used, + since the fallbility does not change based on the boolean values, + and the template will be discarded. + + CURRENT ASSUMPTIONS: + We assume that successCode for wrapping up return values cannot contain + failure conditions. + """ + return getWrapTemplateForType(type, descriptorProvider, 'result', None,\ + memberIsCreator(member))[1] + +class CGMemberJITInfo(CGThing): + """ + A class for generating the JITInfo for a property that points to + our specialized getter and setter. + """ + def __init__(self, descriptor, member): + self.member = member + self.descriptor = descriptor + + def declare(self): + return "" + + def defineJitInfo(self, infoName, opName, infallible): + protoID = "prototypes::id::%s" % self.descriptor.name + depth = "PrototypeTraits<%s>::Depth" % protoID + failstr = "true" if infallible else "false" + return ("\n" + "const %s: JSJitInfo = {\n" + " op: %s,\n" + " protoID: %s,\n" + " depth: %s,\n" + " isInfallible: %s, /* False in setters. */\n" + " isConstant: false /* Only relevant for getters. */\n" + "};\n" % (infoName, opName, protoID, depth, failstr)) + + def define(self): + if self.member.isAttr(): + getterinfo = ("%s_getterinfo" % self.member.identifier.name) + getter = ("get_%s" % self.member.identifier.name) + getterinfal = "infallible" in self.descriptor.getExtendedAttributes(self.member, getter=True) + getterinfal = getterinfal and infallibleForMember(self.member, self.member.type, self.descriptor) + result = self.defineJitInfo(getterinfo, getter, getterinfal) + if not self.member.readonly: + setterinfo = ("%s_setterinfo" % self.member.identifier.name) + setter = ("set_%s" % self.member.identifier.name) + # Setters are always fallible, since they have to do a typed unwrap. + result += self.defineJitInfo(setterinfo, setter, False) + return result + if self.member.isMethod(): + methodinfo = ("%s_methodinfo" % self.member.identifier.name) + # Actually a JSJitMethodOp, but JSJitPropertyOp by struct definition. + method = ("%s" % self.member.identifier.name) + + # Methods are infallible if they are infallible, have no arguments + # to unwrap, and have a return type that's infallible to wrap up for + # return. + methodInfal = False + sigs = self.member.signatures() + if len(sigs) == 1: + # Don't handle overloading. If there's more than one signature, + # one of them must take arguments. + sig = sigs[0] + if len(sig[1]) == 0 and infallibleForMember(self.member, sig[0], self.descriptor): + # No arguments and infallible return boxing + methodInfal = True + + result = self.defineJitInfo(methodinfo, method, methodInfal) + return result + raise TypeError("Illegal member type to CGPropertyJITInfo") + +class CGAbstractClassHook(CGAbstractExternMethod): + """ + Meant for implementing JSClass hooks, like Finalize or Trace. Does very raw + 'this' unwrapping as it assumes that the unwrapped type is always known. + """ + def __init__(self, descriptor, name, returnType, args): + CGAbstractExternMethod.__init__(self, descriptor, name, returnType, + args) + + def definition_body_prologue(self): + return """ + %s* self = unwrap<%s>(obj); +""" % (self.descriptor.nativeType, self.descriptor.nativeType) + + def definition_body(self): + return self.definition_body_prologue() + self.generate_code() + + def generate_code(self): + # Override me + assert(False) + +def finalizeHook(descriptor, hookName, context): + if descriptor.customFinalize: + return """if (self) { + self->%s(%s); +}""" % (hookName, context) + #clearWrapper = "ClearWrapper(self, self);\n" if descriptor.wrapperCache else "" + if descriptor.workers: + release = "self->Release();" + else: + assert descriptor.nativeIsISupports + release = """let val = JS_GetReservedSlot(obj, 0); +let _: %s = cast::reinterpret_cast(&RUST_JSVAL_TO_PRIVATE(val)); +""" % (descriptor.pointerType + descriptor.nativeType) + #return clearWrapper + release + return release + +class CGClassFinalizeHook(CGAbstractClassHook): + """ + A hook for finalize, used to release our native object. + """ + def __init__(self, descriptor): + args = [Argument('*JSFreeOp', 'fop'), Argument('*JSObject', 'obj')] + CGAbstractClassHook.__init__(self, descriptor, FINALIZE_HOOK_NAME, + 'void', args) + + def generate_code(self): + return CGIndenter(CGGeneric(finalizeHook(self.descriptor, self.name, self.args[0].name))).define() + +class CGDescriptor(CGThing): + def __init__(self, descriptor): + CGThing.__init__(self) + + assert not descriptor.concrete or descriptor.interface.hasInterfacePrototypeObject() + + cgThings = [] + if descriptor.interface.hasInterfacePrototypeObject(): + (hasMethod, hasGetter, hasLenientGetter, + hasSetter, hasLenientSetter) = False, False, False, False, False + for m in descriptor.interface.members: + if m.isMethod() and not m.isStatic() and not m.isIdentifierLess(): + #cgThings.append(CGSpecializedMethod(descriptor, m)) + #cgThings.append(CGMemberJITInfo(descriptor, m)) + hasMethod = True + elif m.isAttr(): + cgThings.append(CGSpecializedGetter(descriptor, m)) + if m.hasLenientThis(): + hasLenientGetter = True + else: + hasGetter = True + if not m.readonly: + #cgThings.append(CGSpecializedSetter(descriptor, m)) + if m.hasLenientThis(): + hasLenientSetter = True + else: + hasSetter = True + cgThings.append(CGMemberJITInfo(descriptor, m)) + #if hasMethod: cgThings.append(CGGenericMethod(descriptor)) + if hasGetter: cgThings.append(CGGenericGetter(descriptor)) + #if hasLenientGetter: cgThings.append(CGGenericGetter(descriptor, + # lenientThis=True)) + #if hasSetter: cgThings.append(CGGenericSetter(descriptor)) + #if hasLenientSetter: cgThings.append(CGGenericSetter(descriptor, + # lenientThis=True)) + + if descriptor.concrete and not descriptor.proxy: + if not descriptor.workers and descriptor.wrapperCache: + #cgThings.append(CGAddPropertyHook(descriptor)) + pass + + # Always have a finalize hook, regardless of whether the class wants a + # custom hook. + cgThings.append(CGClassFinalizeHook(descriptor)) + + # Only generate a trace hook if the class wants a custom hook. + if (descriptor.customTrace): + #cgThings.append(CGClassTraceHook(descriptor)) + pass + + if descriptor.interface.hasInterfaceObject(): + #cgThings.append(CGClassConstructHook(descriptor)) + #cgThings.append(CGClassHasInstanceHook(descriptor)) + #cgThings.append(CGInterfaceObjectJSClass(descriptor)) + pass + + if descriptor.interface.hasInterfacePrototypeObject(): + #cgThings.append(CGPrototypeJSClass(descriptor)) + pass + + properties = PropertyArrays(descriptor) + #cgThings.append(CGGeneric(define=str(properties))) + #cgThings.append(CGCreateInterfaceObjectsMethod(descriptor, properties)) + if descriptor.interface.hasInterfacePrototypeObject(): + #cgThings.append(CGGetProtoObjectMethod(descriptor)) + pass + else: + #cgThings.append(CGGetConstructorObjectMethod(descriptor)) + pass + + # Set up our Xray callbacks as needed. Note that we don't need to do + # it in workers. + if (descriptor.interface.hasInterfacePrototypeObject() and + not descriptor.workers): + if descriptor.concrete and descriptor.proxy: + #cgThings.append(CGResolveOwnProperty(descriptor)) + #cgThings.append(CGEnumerateOwnProperties(descriptor)) + pass + #cgThings.append(CGResolveProperty(descriptor, properties)) + #cgThings.append(CGEnumerateProperties(descriptor, properties)) + + if descriptor.interface.hasInterfaceObject(): + #cgThings.append(CGDefineDOMInterfaceMethod(descriptor)) + if (not descriptor.interface.isExternal() and + # Workers stuff is never pref-controlled + not descriptor.workers and + descriptor.interface.getExtendedAttribute("PrefControlled") is not None): + #cgThings.append(CGPrefEnabled(descriptor)) + pass + + if descriptor.interface.hasInterfacePrototypeObject(): + #cgThings.append(CGNativePropertyHooks(descriptor)) + pass + + if descriptor.concrete: + if descriptor.proxy: + #cgThings.append(CGProxyIsProxy(descriptor)) + #cgThings.append(CGProxyUnwrap(descriptor)) + #cgThings.append(CGDOMJSProxyHandlerDOMClass(descriptor)) + #cgThings.append(CGDOMJSProxyHandler(descriptor)) + #cgThings.append(CGIsMethod(descriptor)) + pass + else: + #cgThings.append(CGDOMJSClass(descriptor)) + pass + + if descriptor.wrapperCache: + #cgThings.append(CGWrapWithCacheMethod(descriptor)) + #cgThings.append(CGWrapMethod(descriptor)) + pass + else: + #cgThings.append(CGWrapNonWrapperCacheMethod(descriptor)) + pass + + cgThings = CGList((CGIndenter(t, declareOnly=True) for t in cgThings), "\n") + cgThings = CGWrapper(cgThings, pre='\n', post='\n') + #self.cgRoot = CGWrapper(CGNamespace(toBindingNamespace(descriptor.name), + # cgThings), + # post='\n') + self.cgRoot = cgThings + + def declare(self): + return self.cgRoot.declare() + def define(self): + return self.cgRoot.define() + +class CGBindingRoot(CGThing): + """ + Root codegen class for binding generation. Instantiate the class, and call + declare or define to generate header or cpp code (respectively). + """ + def __init__(self, config, prefix, webIDLFile): + descriptors = config.getDescriptors(webIDLFile=webIDLFile, + hasInterfaceOrInterfacePrototypeObject=True) + dictionaries = config.getDictionaries(webIDLFile) + + cgthings = [] + + # Do codegen for all the descriptors + cgthings.extend([CGDescriptor(x) for x in descriptors]) + + # And make sure we have the right number of newlines at the end + curr = CGWrapper(CGList(cgthings, "\n\n"), post="\n\n") + + # Wrap all of that in our namespaces. + #curr = CGNamespace.build(['dom'], + # CGWrapper(curr, pre="\n")) + + # Add imports + curr = CGImports(descriptors, + dictionaries, + [], + ['js::*', + 'js::jsapi::*', + 'js::jsapi::bindgen::*', + 'js::glue::bindgen::*', + 'dom::bindings::utils::*'], + curr) + + # Add the auto-generated comment. + curr = CGWrapper(curr, pre=AUTOGENERATED_WARNING_COMMENT) + + # Store the final result. + self.root = curr + def declare(self): + return stripTrailingWhitespace(self.root.declare()) + def define(self): + return stripTrailingWhitespace(self.root.define()) |