Add enum unwrapping codegen
authorBoris Zbarsky <bzbarsky@mit.edu>
Sat, 11 Feb 2012 23:07:51 -0500
changeset 86754 147cdb4f2a852a96fbaa1fcae4cf3d38ceaeda1e
parent 86753 36b356cfa9429acc28f33c0871bb2ad86c54beb4
child 86755 6532286d5c6192898603ba1af901b81e507b7ee0
push id106
push userbzbarsky@mozilla.com
push dateSun, 12 Feb 2012 04:09:14 +0000
milestone13.0a1
Add enum unwrapping codegen
dom/bindings/Codegen.py
dom/bindings/Configuration.py
dom/bindings/Utils.h
dom/bindings/parser/WebIDL.py
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -569,16 +569,29 @@ def getArgumentConversionTemplate(type, 
                 "                       xpc_qsDOMString::eDefaultUndefinedBehavior);\n"
                 "  if (!${name}.IsValid()) {\n"
                 "    return false;\n"
                 "  }\n")
     except:
         # Some types just throw when you call tag() on them....
         pass
 
+    if type.isEnum():
+        enum = type.inner.identifier.name
+        return (
+            "  %(enumtype)s ${name};\n"
+            "  {\n"
+            "    bool ok;\n"
+            "    $name = (%(enumtype)s) FindEnumStringIndex(cx, ${argVal}, %(values)s, &ok);\n"
+            "    if (!ok) {\n"
+            "      return false;\n"
+            "    }\n"
+            "  }" % { "enumtype" : enum + "::value",
+                      "values" : enum + "::strings" })
+
     if not type.isPrimitive():
         return """
   // XXXbz Need conversion for argument type '%s'""" % type
 
     tag = type.tag()
     replacements = dict()
     if type.nullable():
         replacements["declareArg"] = (
@@ -1000,16 +1013,54 @@ class CGNativeSetter(CGNativeBindingMeth
                 Argument('JS::Value*', 'vp')]
         CGNativeBindingMethod.__init__(self, descriptor, 'set_' + baseName,
                                        'JSBool', args, baseName)
     def generate_code(self):
         nativeMethodName = "Set" + MakeNativeName(self.attr.identifier.name)
         return str(SetterCall(self.attr.type, nativeMethodName, self.descriptor,
                               self.attr, self.extendedAttributes))
 
+def getEnumValueName(value):
+    # Some enum values can be empty strings.  Others might have weird
+    # characters in them.  Deal with the former by returning "_empty",
+    # deal with possible name collisions from that by throwing if the
+    # enum value is actually "_empty", and throw on any value
+    # containing chars other than [a-z] for now.
+    if value == "_empty":
+        raise SyntaxError('"_empty" is not an IDL enum value we support yet')
+    if value == "":
+        return "_empty"
+    if not re.match("^[a-z]+$", value):
+        raise SyntaxError('Enum value "' + value + '" contains characters '
+                          'outside [a-z]')
+    return value
+
+class CGEnum(CGThing):
+    def __init__(self, enum):
+        CGThing.__init__(self)
+        self.enum = enum
+
+    def declare(self):
+        return """
+  enum value {
+    %s
+  };
+
+  const char* strings [] = {
+    %s,
+    NULL
+  };
+
+""" % (",\n    ".join(map(getEnumValueName, self.enum.values())),
+       ",\n    ".join(['"' + val + '"' for val in self.enum.values()]))
+
+    def define(self):
+        # We only go in the header
+        return "";
+
 class CGDescriptor(CGThing):
     def __init__(self, descriptor):
         CGThing.__init__(self)
 
         # XXXbholley - Not everything should actually have a jsclass.
         cgThings = [CGNativeMethod(descriptor, m) for m in
                     descriptor.interface.members if m.isMethod()]
         cgThings.extend([CGNativeGetter(descriptor, a) for a in
@@ -1084,18 +1135,23 @@ class CGNamespacedEnum(CGThing):
 
 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):
 
-        # Do codegen for all the descriptors.
-        curr = CGList([CGDescriptor(x) for x in config.getConcreteDescriptors(webIDLFile)])
+        # Do codegen for all the descriptors and enums.
+        cgthings = [CGWrapper(CGNamespace.build([e.identifier.name],
+                                                CGEnum(e)),
+                              post="\n") for e in config.getEnums(webIDLFile)]
+        cgthings.extend([CGDescriptor(x) for x
+                         in config.getConcreteDescriptors(webIDLFile)])
+        curr = CGList(cgthings)
 
         # Wrap all of that in our namespaces.
         curr = CGNamespace.build(['mozilla', 'dom', 'bindings', 'prototypes'],
                                  CGWrapper(curr, pre="\n"))
 
         # Add header includes.
         curr = CGHeaders(config.getConcreteDescriptors(webIDLFile), ['DOMJSClass.h', 'Utils.h'], curr)
 
--- a/dom/bindings/Configuration.py
+++ b/dom/bindings/Configuration.py
@@ -26,31 +26,36 @@ class Configuration:
             self.interfaces[iface.identifier.name] = iface
             if iface.identifier.name not in config: continue
             entry = config[iface.identifier.name]
             if not isinstance(entry, list):
                 assert isinstance(entry, dict)
                 entry = [entry]
             self.descriptors.extend([Descriptor(self, iface, x) for x in entry])
 
+        self.enums = [e for e in parseData if e.isEnum()]
+
         # Keep the descriptor list sorted for determinism.
         self.descriptors.sort(lambda x,y: cmp(x.name, y.name))
 
     def getInterface(self, ifname):
         return self.interfaces[ifname]
     def getAllDescriptors(self, webIDLFile=None):
         if not webIDLFile:
             return self.descriptors
         else:
             return filter(lambda x: x.interface.filename() == webIDLFile, self.descriptors)
     def getConcreteDescriptors(self, webIDLFile=None):
         return filter(lambda x: x.concrete, self.getAllDescriptors(webIDLFile))
     def getDescriptorsForInterface(self, iface):
         return filter(lambda x: x.interface is iface, self.descriptors)
 
+    def getEnums(self, webIDLFile):
+        return filter(lambda e: e.filename() == webIDLFile, self.enums)
+
 class Descriptor:
     """
     Represents a single descriptor for an interface. See Bindings.conf.
     """
     def __init__(self, config, interface, desc):
         self.config = config
         self.interface = interface
 
--- a/dom/bindings/Utils.h
+++ b/dom/bindings/Utils.h
@@ -345,13 +345,40 @@ public:
 template<template <class> class SmartPtr, class T>
 inline bool
 WrapOtherObject(JSContext *cx, JSObject *scope, const SmartPtr<T>& value,
                 jsval *vp)
 {
   return WrapOtherObject(cx, scope, value.get(), vp);
 }
 
+int
+FindEnumStringIndex(JSContext *cx, jsval v, const char** values, bool *ok)
+{
+  JSString* str = JS_ValueToString(cx, v);
+  if (!str) {
+    *ok = false;
+    return 0;
+  }
+  JS::Anchor<JSString*> anchor(str);
+  
+  for (int i = 0; values[i]; ++i) {
+    JSBool equal;
+    if (!JS_StringEqualsAscii(cx, str, values[i], &equal)) {
+      *ok = false;
+      return 0;
+    }
+
+    if (equal) {
+      *ok = true;
+      return i;
+    }
+  }
+
+  *ok = xpc_qsThrow(cx, NS_ERROR_XPC_BAD_CONVERT_JS);
+  return 0;
+}
+
 } // namespace bindings
 } // namespace dom
 } // namespace mozilla
 
 #endif /* mozilla_dom_bindings_Utils_h__ */
--- a/dom/bindings/parser/WebIDL.py
+++ b/dom/bindings/parser/WebIDL.py
@@ -770,23 +770,26 @@ class IDLWrapperType(IDLType):
         return False
 
     def isDictionary(self):
         return False
 
     def isInterface(self):
         return isinstance(self.inner, IDLInterface)
 
+    def isEnum(self):
+        return isinstance(self.inner, IDLEnum)
+
     def isComplete(self):
         return True
 
     def tag(self):
-        if isinstance(self.inner, IDLInterface):
+        if isInterface():
             return IDLType.Tags.interface
-        elif isinstance(self.inner, IDLEnum):
+        elif isEnum():
             return IDLType.Tags.enum
         else:
             assert False
 
 class IDLBuiltinType(IDLType):
 
     Types = enum(
         # The integer types