Hacks, quirks, etc... getting closer!
authorBenjamin Smedberg <benjamin@smedbergs.us>
Tue, 17 Jun 2008 11:40:49 -0400
changeset 10 3394439f0ddc
parent 9 521ac1e42cbb
child 11 024b54ccabc6
push id9
push userbsmedberg@mozilla.com
push dateThu, 19 Jun 2008 17:57:09 +0000
Hacks, quirks, etc... getting closer!
header.py
testheaderrules.mk
xpidl.py
--- a/header.py
+++ b/header.py
@@ -131,16 +131,20 @@ def print_header(idl, fd, filename):
             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':
+            for c in p.doccomments: fd.write("%s\n" % c)
+            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 \
@@ -231,17 +235,17 @@ def write_interface(iface, fd):
     if iface.namemap is None:
         raise Exception("Interface was not resolved.")
 
     def write_const_decl(c):
         for comment in c.doccomments:
             fd.write("  %s\n" % comment)
 
         basetype = c.basetype
-        value = c.getValue(iface)
+        value = c.getValue()
 
         fd.write("  enum { %(name)s = %(value)s%(signed)s };\n\n" % {
                      'name': c.name,
                      'value': value,
                      'signed': (not basetype.signed) and 'U' or ''})
 
     def write_method_decl(m):
         for comment in m.doccomments:
@@ -318,18 +322,19 @@ def write_interface(iface, fd):
 
     for member in iface.members:
         if isinstance(member, xpidl.Attribute):
             fd.write("\\\n  %s; " % attributeAsNative(member, True))
             if not member.readonly:
                 fd.write("\\\n  %s; " % attributeAsNative(member, False))
         elif isinstance(member, xpidl.Method):
             fd.write("\\\n  %s; " % methodAsNative(member))
-    if len(iface.members) == 0 or \
-       not member.kind in ('attribute', 'method'):
+    if len(iface.members) == 0:
+        fd.write('\\\n  /* no methods! */')
+    elif not member.kind in ('attribute', 'method'):
        fd.write('\\')
 
     fd.write(iface_forward % names)
 
     def emitTemplate(tmpl):
         for member in iface.members:
             if isinstance(member, xpidl.Attribute):
                 fd.write(tmpl % {'asNative': attributeAsNative(member, True),
@@ -338,18 +343,19 @@ def write_interface(iface, fd):
                 if not member.readonly:
                     fd.write(tmpl % {'asNative': attributeAsNative(member, False),
                                      'nativeName': attributeNativeName(member, False),
                                      'paramList': attributeParamName(member)})
             elif isinstance(member, xpidl.Method):
                 fd.write(tmpl % {'asNative': methodAsNative(member),
                                  'nativeName': methodNativeName(member),
                                  'paramList': paramlistNames(member.params, member.realtype, member.notxpcom)})
-        if len(iface.members) == 0 or \
-           not member.kind in ('attribute', 'method'):
+        if len(iface.members) == 0:
+            fd.write('\\\n  /* no methods! */')
+        elif not member.kind in ('attribute', 'method'):
             fd.write('\\')
 
     emitTemplate("\\\n  %(asNative)s { return _to %(nativeName)s(%(paramList)s); } ")
 
     fd.write(iface_forward_safe % names)
 
     emitTemplate("\\\n  %(asNative)s { return !_to ? NS_ERROR_NULL_POINTER : _to->%(nativeName)s(%(paramList)s); } ")
 
--- a/testheaderrules.mk
+++ b/testheaderrules.mk
@@ -1,9 +1,9 @@
 # Copy this to objdir/myrules.mk to test
 
 ifneq ($(XPIDLSRCS)$(SDK_XPIDLSRCS),)
 export:: $(patsubst %.idl,$(XPIDL_GEN_DIR)/%.pyh, $(XPIDLSRCS))
 endif
 
-$(XPIDL_GEN_DIR)/%.pyh: %.idl $(XPIDL_GEN_DIR)/%.h
+$(XPIDL_GEN_DIR)/%.pyh: %.idl $(XPIDL_GEN_DIR)/%.h /builds/idl-parser/header.py /builds/idl-parser/xpidl.py
 	python /builds/idl-parser/header.py $< $(XPIDL_FLAGS) > $@
-	diff -w -U 3 $(XPIDL_GEN_DIR)/$*.h $@
+	diff -w -B -U 3 $(XPIDL_GEN_DIR)/$*.h $@
--- a/xpidl.py
+++ b/xpidl.py
@@ -26,22 +26,44 @@ def attlistToIDL(attlist):
         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 attlist])
+                                for name, value, aloc in sorted])
 
 class BuiltinLocation(object):
     def get(self):
         return "<builtin type>"
 
     def __str__(self):
         return self.get()
 
@@ -67,17 +89,17 @@ class Builtin(object):
         else:
             const = ''
         return "%s%s %s" % (const, self.nativename,
                             calltype != 'in' and '*' or '')
 
 builtinNames = [
     Builtin('boolean', 'PRBool'),
     Builtin('void', 'void'),
-    Builtin('octet', 'unsigned char'),
+    Builtin('octet', 'PRUint8'),
     Builtin('short', 'PRInt16', True, True),
     Builtin('long', 'PRInt32', True, True),
     Builtin('long long', 'PRInt64', True, False),
     Builtin('unsigned short', 'PRUint16', False, True),
     Builtin('unsigned long', 'PRUint32', False, True),
     Builtin('unsigned long long', 'PRUint64', False, False),
     Builtin('float', 'float', True, False),
     Builtin('double', 'double', True, False),
@@ -139,38 +161,49 @@ class NameMap(object):
     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 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):
+    def __init__(self, message, location, warning=False):
         self.message = message
         self.location = location
+        self.warning = warning
 
     def __str__(self):
-        return "Error: %s, %s" % (self.message, self.location)
+        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
 
@@ -192,39 +225,29 @@ class Include(object):
                 parent.setName(type)
             return
 
         raise IDLError("File '%s' not found" % self.filename, self.location)
 
 class IDL(object):
     def __init__(self, productions):
         self.productions = productions
-        # Hack alert: libidl parses doccomments on the non-first interface
-        # incorrectly, assigning them to the first member of the interface
-        # instead of the interface itself. Good god.
-        
-        found = False
-        for p in productions:
-            if p.kind == 'interface':
-                dc = p.doccomments
-                p.doccomments = []
-                if len(p.members):
-                    p.members[0].doccomments[0:0] = dc
-
-            if p.kind in ('interface', 'cdata'): found = True
 
     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):
         self.namemap = NameMap()
@@ -249,45 +272,49 @@ class CDATA(object):
         pass
 
     def __str__(self):
         return "cdata: %s\n\t%r\n" % (self.location.get(), self.data)
 
 class Typedef(object):
     kind = 'typedef'
 
-    def __init__(self, type, name, location):
+    def __init__(self, type, name, location, doccomments):
         self.type = type
         self.name = name
         self.location = location
-        self.nativename = name
+        self.doccomments = doccomments
 
     def __eq__(self, other):
         return self.name == other.name and self.type == other.type
 
     def resolve(self, parent):
         parent.setName(self)
         self.realtype = parent.getName(self.type, self.location)
+        if isinstance(self.realtype, Native):
+            raise IDLError("Typedefs of native types are not supported.", 
+                           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):
+    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):
         parent.setName(self)
 
     def isScriptable(self):
@@ -348,29 +375,32 @@ class Native(object):
         if self.specialtype is None:
             return False
 
         if self.specialtype == 'nsid':
             return self.modifier is not None
 
         return self.modifier == 'ref'
 
-    def nativeType(self, calltype):
+    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 = 'const '
-        else:
-            const = ''
+            const = True
 
         if self.modifier == 'ptr':
             m = '*' + (calltype != 'in' and '*' or '')
         elif self.modifier == 'ref':
             m = '& '
         else:
             m = calltype != 'in' and '*' or ''
-        return "%s%s %s" % (const, self.nativename, m)
+        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):
@@ -387,28 +417,39 @@ class Interface(object):
             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'):
+                    print >>sys.stderr, "Moving comments to %s" % member
+                    member.doccomments[0:0] = self.doccomments
+                    break
+            self.doccomments = parent.getName(self.name, None).doccomments
+
         parent.setName(self)
         if self.base is None:
             if self.name != 'nsISupports':
-                raise IDLError("interface '%s' not derived from nsISupports",
-                               self.location)
+                print >>sys.stderr, IDLError("interface '%s' not derived from nsISupports",
+                                             self.location, warning=True)
         else:
             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:
-                raise IDLError("interface '%s' is scriptable but derives from non-scriptable '%s'" % (self.name, self.base))
+                print >>sys.stderr, IDLError("interface '%s' is scriptable but derives from non-scriptable '%s'" % (self.name, self.base), self.location, warning=True)
 
         for member in self.members:
             member.resolve(self)
 
     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.
@@ -427,21 +468,21 @@ class Interface(object):
         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):
-        c = self.get(name)
-        if not isistance(c, ConstMember):
+        c = self.namemap.get(name, location)
+        if c.kind != 'const':
             raise IDLError("symbol '%s' is not a constant", c.location)
 
-        return c.resolve(self)
+        return c.getValue()
 
 class InterfaceAttributes(object):
     uuid = None
     scriptable = False
     function = False
     deprecated = False
 
     def setuuid(self, value):
@@ -460,16 +501,17 @@ class InterfaceAttributes(object):
         self.deprecated = True
 
     actions = {
         'uuid':       (True, setuuid),
         'scriptable': (False, setscriptable),
         'function':   (False, setfunction),
         'noscript':   (False, setnoscript),
         'deprecated': (False, setdeprecated),
+        'object':     (False, lambda self: True),
         }
 
     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))
@@ -505,26 +547,27 @@ class ConstMember(object):
         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, iface):
-        return self.value(iface)
+    def getValue(self):
+        return self.value(self.iface)
 
 class Attribute(object):
     kind = 'attribute'
     noscript = False
     notxpcom = False
 
     def __init__(self, type, name, attlist, readonly, location, doccomments):
         self.type = type
@@ -682,18 +725,18 @@ class Param(object):
         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)
+        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):
@@ -829,17 +872,18 @@ class IDLParser(object):
                        | 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))
+                       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))
 
@@ -883,33 +927,33 @@ class IDLParser(object):
                         | """
         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 = atts['doccomments']
-        else:
-            doccomments = p.slice[2].doccomments
+            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())
+            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)
 
@@ -954,17 +998,18 @@ class IDLParser(object):
     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]
-        p[0] = lambda i: i.getConst(id, self.getLocation(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]
@@ -988,17 +1033,17 @@ class IDLParser(object):
                   | 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_or(self, p):
+    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]:
@@ -1089,17 +1134,17 @@ class IDLParser(object):
         self.lexer = lex.lex(object=self)
         self.parser = yacc.yacc(module=self)
 
     def clearComments(self):
         self._doccomments = []
 
     def token(self):
         t = self.lexer.token()
-        if t is not None:
+        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