More WIP... some doccomments, need to come up with a type "interface".
authorBenjamin Smedberg <benjamin@smedbergs.us>
Sat, 14 Jun 2008 15:08:24 -0400
changeset 6 1914d690fadd
parent 5 9608c862313a
child 7 c9cc546fc95a
push id6
push userbsmedberg@mozilla.com
push dateSun, 15 Jun 2008 03:27:17 +0000
More WIP... some doccomments, need to come up with a type "interface".
header.py
xpidl.py
--- a/header.py
+++ b/header.py
@@ -1,14 +1,16 @@
 #!/usr/bin/env python
 
 """Print a C++ header file for the IDL files specified on the command line"""
 
-import sys, os.path
-from xpidl import IDLParser
+import sys, os.path, re, xpidl
+
+def firstCap(str):
+    return str[0].upper() + str[1:]
 
 header = """/*
  * DO NOT EDIT.  THIS FILE IS GENERATED FROM %(filename)s
  */
 
 #ifndef __gen_%(basename)s_h__
 #define __gen_%(basename)s_h__
 """
@@ -59,38 +61,113 @@ def print_header(idl, fd, filename):
             fd.write(forward_decl % {'name': p.name})
             continue
         if p.kind == 'interface':
             write_interface(p, fd)
             continue
 
     fd.write(footer % {'basename': idl_basename(filename)})
 
-iface_header = """
+iface_header = r"""
 /* starting interface:    %(name)s */
-
 #define %(defname)s_IID_STR "%(iid)s"
 
 #define %(defname)s_IID \
   {0x%(m0)s, 0x%(m1)s, 0x%(m2)s, \
-    { %(m3) }}
+    { %(m3joined)s }}
+
+"""
+
+uuid_decoder = re.compile(r"""(?P<m0>[a-f0-9]{8})-
+                              (?P<m1>[a-f0-9]{4})-
+                              (?P<m2>[a-f0-9]{4})-
+                              (?P<m3>[a-f0-9]{4})-
+                              (?P<m4>[a-f0-9]{12})$""", re.X)
 
-class %(name)s%(attributes)se%(base)s
- {
+iface_prolog = """ {
  public: 
 
+  NS_DECLARE_STATIC_IID_ACCESSOR(%(defname)s_IID)
+
+"""
+
+iface_epilog = """};
+
+  NS_DEFINE_STATIC_IID_ACCESSOR(%(name)s, %(defname)s_IID)
+
 """
 
 def write_interface(iface, fd):
-    fd.write(iface_header % iface.name)
+    if iface.namemap is None:
+        raise Exception("Interface was not resolved.")
+
+    def write_const(c):
+        basetype = c.basetype
+        value = c.getValue(iface)
+
+        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(c):
+        pass
+
+    def write_attr(c):
+        fd.write("  /* %(ro)sattribute %(type)s %(name)s; */\n" % {
+                     'ro': c.readonly and "readonly " or "",
+                     'type': c.type,
+                     'name': c.name});
+
+        scriptable = c.isScriptable() and "NS_SCRIPTABLE " or ""
+        binaryname = c.binaryname is not None and c.binaryname or firstCap(c.name)
+        paramname = "a" + firstCap(c.name)
+        realintype = c.realtype.nativename
+
+        fd.write("  %(scriptable)sNS_IMETHOD Get%(binaryname)s(%(realintype)s %(paramname)s);\n" % {
+                     'scriptable': scriptable,
+                     'binaryname': binaryname,
+                     'realintype': realintype,
+                     'paramname': paramname})
 
     defname = iface.name.upper()
     if iface.name[0:2] == 'ns':
         defname = 'NS_' + defname[2:]
 
-    fd.write(
+    names = uuid_decoder.match(iface.attributes.uuid).groupdict()
+    m3str = names['m3'] + names['m4']
+    names['m3joined'] = ", ".join(["0x%s" % m3str[i:i+2] for i in xrange(0, 16, 2)])
+
+    names.update({'defname': defname,
+                  'name': iface.name,
+                  'iid': iface.attributes.uuid})
+
+    fd.write(iface_header % names)
+
+    for comment in iface.doccomments:
+        fd.write("%s\n" % comment)
+
+    fd.write("class NS_NO_VTABLE ")
+    if iface.attributes.scriptable:
+        fd.write("NS_SCRIPTABLE ")
+    if iface.attributes.deprecated:
+        fd.write("NS_DEPRECATED ")
+    fd.write(iface.name)
+    if iface.base:
+        fd.write(" : public %s" % iface.base)
+    fd.write(iface_prolog % names)
+    for member in iface.members:
+        if isinstance(member, xpidl.ConstMember):
+            write_const(member)
+        elif isinstance(member, xpidl.Attribute):
+            write_attr(member)
+        elif isinstance(member, xpidl.Method):
+            write_method(member)
+        else:
+            raise Exception("Unexpected interface member: %s" % member)
+
+    fd.write(iface_epilog % names)
 
 if __name__ == '__main__':
-    p = IDLParser()
-    p.incdirs.append('/builds/mozilla-central/src/xpcom/base')
-    print_header(p.parse(open(sys.argv[1]).read(), filename=sys.argv[1]),
-                 sys.stdout,
-                 sys.argv[1])
+    p = xpidl.IDLParser()
+    idl = p.parse(open(sys.argv[1]).read(), filename=sys.argv[1])
+    idl.resolve(['/builds/mozilla-central/src/xpcom/base'])
+    print_header(idl, sys.stdout, sys.argv[1])
--- a/xpidl.py
+++ b/xpidl.py
@@ -1,14 +1,52 @@
 #!/usr/bin/env python
 
 """A parser for cross-platform IDL (XPIDL) files."""
 
 import lex, yacc, sys, os.path
 
+class BuiltinLocation(object):
+    def get(self):
+        return "<builtin type>"
+
+    def __str__(self):
+        return self.get()
+
+class Builtin(object):
+    location = BuiltinLocation
+
+    def __init__(self, name, nativename, signed=False, maybeConst=False):
+        self.name = name
+        self.nativename = nativename
+        self.signed = signed
+        self.maybeConst = maybeConst
+
+builtinNames = [
+    Builtin('boolean', 'PRBool'),
+    Builtin('void', 'void'),
+    Builtin('octet', 'unsigned char'),
+    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),
+    Builtin('char', 'char', True, False),
+    Builtin('string', 'char *', False, False),
+    Builtin('wchar', 'PRUnichar', False, False),
+    Builtin('wstring', 'PRUnichar *', 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>")
@@ -34,159 +72,222 @@ class Location(object):
         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 SetOnceMap(object):
+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):
-        return self._d[key][0]
+        if key in builtinMap:
+            return builtinMap[key]
+        return self._d[key]
+
+    def __iter__(self):
+        return self._d.itervalues()
 
-    def set(self, key, value, location):
-        if key in self._d:
-            raise IDLError("name '%s' specified twice. Previous location: %s" % d[key][1], location)
+    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:
+            raise IDLError("name '%s' specified twice. Previous location: %s" % (object.name, d[object.name].location), object.location)
 
-        self._d[key] = (value, location)
+        self._d[object.name] = object
 
 class IDLError(Exception):
     def __init__(self, message, location):
         self.message = message
         self.location = location
 
     def __str__(self):
         return "Error: %s, %s" % (self.message, self.location)
 
 class Include(object):
     kind = 'include'
 
-    def __init__(self, filename, incdirs, location):
+    def __init__(self, filename, location):
         self.filename = filename
         self.location = location
-        self.IDL = self.resolve(incdirs)
 
     def __str__(self):
-        l = ["include '%s'\n" % self.filename]
-        for line in str(self.IDL).splitlines(True):
-            l.append("\t" + line)
-        return "".join(l)
+        return "".join(["include '%s'\n" % self.filename])
 
-    def resolve(self, incdirs):
+    def resolve(self, parent):
         def incfiles():
             yield self.filename
-            for dir in incdirs:
+            for dir in parent.incdirs:
                 yield os.path.join(dir, self.filename)
 
         for file in incfiles():
             if not os.path.exists(file): continue
 
-            return IDLParser().parse(open(file).read(), filename=file)
+            self.IDL = IDLParser().parse(open(file).read(), filename=file)
+            self.IDL.resolve(parent.incdirs)
+            for type in self.IDL.getNames():
+                parent.setName(type)
+            return
 
         raise IDLError("File '%s' not found" % self.filename, self.location)
 
 class IDL(object):
     def __init__(self, productions):
         self.productions = productions
-        self.namemap = SetOnceMap()
-        for p in productions:
-            if p.kind in ('interface', 'forward', 'typedef', 'native'):
-                self.namemap.set(p.name, p, p.location)
 
-    def getName(id, location):
+    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 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()
+        self.incdirs = incdirs
+        for p in self.productions:
+            p.resolve(self)
+
     def includes(self):
         for p in self.productions:
             if p.kind == 'include':
                 yield p
 
 class CDATA(object):
     kind = 'cdata'
 
     def __init__(self, data, location):
         self.data = data
         self.location = location
 
+    def resolve(self, parent):
+        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):
         self.type = type
         self.name = name
         self.location = location
+        self.nativename = name
+
+    def resolve(self, parent):
+        parent.setName(self)
+        self.realtype = parent.getName(self.type, self.location)
 
     def __str__(self):
         return "typedef %s %s\n" % (self.type, self.name)
 
 class Forward(object):
     kind = 'forward'
 
     def __init__(self, name, location):
         self.name = name
         self.location = location
+        self.nativename = name
+
+    def resolve(self, parent):
+        parent.setName(self)
 
     def __str__(self):
         return "forward-declared %s\n" % self.name
 
 class Native(object):
     kind = 'native'
 
     modifier = None
     specialtype = None
+    scriptable = False
 
+    specialtypes = {
+        'nsid': 'nsID',
+        'domstring': 'nsAString',
+        'utf8string': 'nsACString',
+        'cstring': 'nsACString',
+        'astring': 'nsAString'
+        }
+        
     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 ('nsid', 'domstring', 'utf8string', 'cstring', 'astring'):
+            elif name in self.specialtypes.keys():
                 if self.specialtype is not None:
                     raise IDLError("More than one special type", aloc)
                 self.specialtype = name
+                self.nativename = self.specialtypes[name]
+                self.scriptable = True
             else:
                 raise IDLError("Unexpected attribute", aloc)
 
+    def resolve(self, parent):
+        parent.setName(self)
+
     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):
+    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 = SetOnceMap()
+        self.namemap = NameMap()
+        self.doccomments = doccomments
+        self.nativename = name
 
         for m in members:
             if not isinstance(m, CDATA):
-                self.namemap.set(m.name, m, m.location)
+                self.namemap.set(m)
+
+    def resolve(self, parent):
+        self.idl = parent
+        parent.setName(self)
+        if self.base is None:
+            if self.name != 'nsISupports':
+                raise IDLError("interface '%s' not derived from nsISupports",
+                               self.location)
+        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))
+
+        for member in self.members:
+            member.resolve(self)
 
     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")
@@ -197,62 +298,61 @@ class Interface(object):
 
     def getConst(self, name, location):
         c = self.get(name)
         if not isistance(c, ConstMember):
             raise IDLError("symbol '%s' is not a constant", c.location)
 
         return c.resolve(self)
 
-    def verify(self, idl):
-        if self.base is None:
-            if self.name != 'nsISupports':
-                raise IDLError("interface '%s' not derived from nsISupports",
-                               self.location)
-        else:
-            base = idl.get(self.name, self.location)
-            if self.attributes.scriptable and not base.attributes.scriptable:
-                raise IDLError("interface '%s' is scriptable but derives from non-scriptable '%s'" % (self.name, self.base))
-
 class InterfaceAttributes(object):
     uuid = None
     scriptable = False
     function = False
+    deprecated = False
 
     def setuuid(self, value):
-        self.uuid = value
+        self.uuid = value.lower()
 
     def setscriptable(self):
         self.scriptable = True
 
     def setfunction(self):
         self.function = True
 
+    def setnoscript(self):
+        self.noscript = True
+
+    def setdeprecated(self):
+        self.deprecated = True
+
     actions = {
         'uuid':       (True, setuuid),
         'scriptable': (False, setscriptable),
         'function':   (False, setfunction),
+        'noscript':   (False, setnoscript),
+        'deprecated': (False, setdeprecated),
         }
 
     def __init__(self, attlist, location):
         def badattribute(self):
-            raise IDLError("Unexpected interface attribute '%s'" % att.name, location)
+            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)
+                                   aloc)
 
                 action(self, val)
             else:
                 if val is not None:
                     raise IDLError("Unexpected value for attribute '%s'" % name,
-                                     aloc)
+                                   aloc)
 
                 action(self)
 
         if self.uuid is None:
             raise IDLError("interface has no uuid", location)
 
     def __str__(self):
         l = []
@@ -267,119 +367,165 @@ class InterfaceAttributes(object):
 class ConstMember(object):
     kind = 'const'
     def __init__(self, type, name, value, location):
         self.type = type
         self.name = name
         self.value = value
         self.location = location
 
-    def resolve(self, interface):
-        return self.value(interface)
+    def resolve(self, parent):
+        self.realtype = parent.idl.getName(self.type, self.location)
+        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)
 
 class Attribute(object):
     kind = 'attribute'
     noscript = False
     notxpcom = False
 
     def __init__(self, type, name, attlist, readonly, location):
         self.type = type
         self.name = name
         self.readonly = readonly
         self.location = location
+        self.binaryname = None
 
         for name, value, aloc in attlist:
-            if value is not None:
-                raise IDLError("Unexpcted attribute value", aloc)
-
-            if name == 'noscript':
-                self.noscript = True
-            elif name == 'notxpcom':
-                self.notxpcom = True
-            else:
-                raise IDLError("Unexpected attribute '%s'", aloc)
+            if name == 'binaryname':
+                if value is None:
+                    raise IDLError("binaryname attribute requires a value",
+                                   aloc)
 
-    def __str__(self):
-        return "\t%sattribute %s %s\n" % (self.readonly and 'readonly ' or '',
-                                          self.type, self.name)
+                self.binaryname = value
+                continue
 
-class Method(object):
-    kind = 'method'
-    noscript = False
-    notxpcom = False
-
-    def __init__(self, type, name, attlist, paramlist, location):
-        self.type = type
-        self.name = name
-        self.params = paramlist
-        self.location = location
-
-        for name, value, aloc in attlist:
             if value is not None:
                 raise IDLError("Unexpected attribute value", aloc)
 
             if name == 'noscript':
                 self.noscript = True
             elif name == 'notxpcom':
                 self.notxpcom = True
             else:
                 raise IDLError("Unexpected attribute '%s'", aloc)
 
-        self.namemap = SetOnceMap()
+    def resolve(self, iface):
+        self.iface = iface
+        self.realtype = iface.idl.getName(self.type, 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%sattribute %s %s\n" % (self.readonly and 'readonly ' or '',
+                                          self.type, self.name)
+
+class Method(object):
+    kind = 'method'
+    noscript = False
+    notxpcom = False
+    binaryname = None
+
+    def __init__(self, type, name, attlist, paramlist, location):
+        self.type = type
+        self.name = name
+        self.params = paramlist
+        self.location = location
+
+        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
+            else:
+                raise IDLError("Unexpected attribute '%s'", aloc)
+
+        self.namemap = NameMap()
         for p in paramlist:
-            self.namemap.set(p.name, p, p.location)
+            self.namemap.set(p)
         #for p in paramlist:
         #    p.validate(self)
 
+    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)
+
     def __str__(self):
         return "\t%s %s(%s)\n" % (self.type, self.name, ", ".join([p.name for p in self.params]))
 
 class Param(object):
-    size_is = False
-    iid_is = False
+    size_is = None
+    iid_is = None
     const = False
     array = False
     retval = False
     shared = False
+    optional = False
 
     def __init__(self, paramtype, type, name, attlist, location):
         self.paramtype = paramtype
         self.type = type
         self.name = name
         self.location = location
 
         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
-            if name == 'iid_is':
+            elif name == 'iid_is':
                 if value is None:
                     raise IDLError("'iid_is' must specify a parameter", aloc)
                 self.iid_is = value
             else:
                 if value is not None:
                     raise IDLError("Unexpected value for attribute '%s'" % name,
-                                     aloc)
+                                   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)
+
 class IDLParser(object):
-    incdirs = []
-
     keywords = {
         'const': 'CONST',
         'interface': 'INTERFACE',
         'in': 'IN',
         'inout': 'INOUT',
         'out': 'OUT',
         'attribute': 'ATTRIBUTE',
         'raises': 'RAISES',
@@ -392,63 +538,69 @@ class IDLParser(object):
         'IDENTIFIER',
         'CDATA',
         'INCLUDE',
         'IID',
         'NUMBER',
         'HEXNUM',
         'LSHIFT',
         'RSHIFT',
+        'LBRACKET',
         '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'>>'
+    t_LBRACKET = r'\['
 
-    literals = '"(){}[],;:=|+-*'
+    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]*'
         t.type = self.keywords.get(t.value, 'IDENTIFIER')
         return t
 
     def t_LCDATA(self, t):
-        r'%\{[ ]*C\+\+\s*\n(?s)(?P<cdata>.*?\n)%\}[ ]*(C\+\+)?'
+        r'%\{[ ]*C\+\+\s*\n(?s)(?P<cdata>.*?\n?)%\}[ ]*(C\+\+)?'
         t.type = 'CDATA'
         t.value = t.lexer.lexmatch.group('cdata')
         t.lexer.lineno += t.value.count('\n')
+        self.clearComments()
         return t
 
     def t_INCLUDE(self, t):
         r'\#include[ \t]+"[^"\n]+"'
         inc, value, end = t.value.split('"')
         t.value = value
+        self.clearComments()
         return t
 
     def t_newline(self, t):
         r'\n+'
         t.lexer.lineno += len(t.value)
 
     def t_nativeid_NATIVEID(self, t):
         r'[^()\n]+(?=\))'
@@ -481,95 +633,111 @@ class IDLParser(object):
     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.incdirs, self.getLocation(p, 1)))
+        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))
+        self.clearComments()
 
     def p_native(self, p):
         """native : attributes NATIVE IDENTIFIER afternativeid '(' NATIVEID ')' ';'"""
         p[0] = Native(name=p[3],
                       nativename=p[6],
-                      attlist=p[1],
+                      attlist=p[1]['attlist'],
                       location=self.getLocation(p, 2))
+        self.clearComments()
 
     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] = p[1]
+        p[0] = {'value': p[1],
+                'location': self.getLocation(p, 1)}
 
     def p_attributes(self, p):
-        """attributes : '[' attlist ']'
+        """attributes : LBRACKET attlist ']'
                       | """
         if len(p) == 1:
-            p[0] = []
+            p[0] = {'attlist': []}
         else:
-            p[0] = p[2]
+            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], p[2], self.getLocation(p, 1))
+        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 ';'"""
-        if p[5] is None:
+        atts, INTERFACE, name, base, body, SEMI = p[1:]
+        attlist = atts['attlist']
+        if 'doccomments' in atts:
+            doccomments = atts['doccomments']
+        else:
+            doccomments = p.slice[2].doccomments
+
+        l = lambda: self.getLocation(p, 2)
+
+        if body is None:
             # forward-declared interface... must not have attributes!
-            if len(p[1]) != 0:
+            if len(attlist) != 0:
                 raise IDLError("Forward-declared interface must not have attributes",
-                               p[1][0][3])
+                               list[0][3])
 
-            if p[4] is not None:
+            if base is not None:
                 raise IDLError("Forward-declared interface must not have a base",
-                               self.getLocation(p, 2))
-            p[0] = Forward(name=p[3], location=self.getLocation(p, 2))
+                               l())
+            p[0] = Forward(name=name, location=l())
         else:
-            p[0] = Interface(name=p[3],
-                             attlist=p[1],
-                             base=p[4],
-                             members=p[5],
-                             location=self.getLocation(p, 2))
+            p[0] = Interface(name=name,
+                             attlist=attlist,
+                             base=base,
+                             members=body,
+                             location=l(),
+                             doccomments=doccomments)
+
+        self.clearComments()
 
     def p_ifacebody(self, p):
         """ifacebody : '{' members '}'
                      | """
         if len(p) > 1:
             p[0] = p[2]
 
     def p_ifacebase(self, p):
@@ -584,18 +752,17 @@ class IDLParser(object):
 
     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] = {'kind': 'cdata',
-                'data': p[1]}
+        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))
 
 # All "number" products return a function(interface)
 
@@ -651,26 +818,26 @@ class IDLParser(object):
         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 ';'"""
         p[0] = Attribute(type=p[4],
                          name=p[5],
-                         attlist=p[1],
+                         attlist=p[1]['attlist'],
                          readonly=p[2],
                          location=self.getLocation(p, 1))
 
     def p_member_method(self, p):
         """member : attributes IDENTIFIER IDENTIFIER '(' paramlist ')' raises ';'"""
         # Ignoring "raises" for the moment, because I think it's unused
         p[0] = Method(type=p[2],
                       name=p[3],
-                      attlist=p[1],
+                      attlist=p[1]['attlist'],
                       paramlist=p[5],
                       location=self.getLocation(p, 1))
 
     def p_paramlist(self, p):
         """paramlist : param moreparams
                      | """
         if len(p) == 1:
             p[0] = []
@@ -687,17 +854,17 @@ class IDLParser(object):
         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=p[1]['attlist'],
                      location=self.getLocation(p, 1))
 
     def p_paramtype(self, p):
         """paramtype : IN
                      | INOUT
                      | OUT"""
         p[0] = p[1]
 
@@ -711,40 +878,41 @@ class IDLParser(object):
                   | """
 
     def p_idlist(self, p):
         """idlist : IDENTIFIER"""
 
     def p_idlist_continue(self, p):
         """idlist : IDENTIFIER ',' idlist"""
 
-    def p_error(self, p):
-        colno, line, pointerline = self.finderror(p)
-        raise Exception("Error: line %i column %i: invalid syntax.\n%s\n%s" % (p.lineno, colno, line, pointerline))
+    def p_error(self, t):
+        location = Location(self.lexer, t.lineno, t.lexpos)
+        raise IDLError("invalid syntax", location)
 
     def __init__(self):
+        self._doccomments = []
         self.lexer = lex.lex(object=self)
         self.parser = yacc.yacc(module=self)
 
+    def clearComments(self):
+        self._doccomments = []
+
     def token(self):
         t = self.lexer.token()
-        # print "Token: %s" % t
+        if t is not None:
+            t.doccomments = list(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)
         return self.parser.parse(lexer=self)
 
     def getLocation(self, p, i):
         return Location(self.lexer, p.lineno(i), p.lexpos(i))
 
 if __name__ == '__main__':
     p = IDLParser()
-    p.incdirs.append('/builds/mozilla-central/src/xpcom/base')
     for f in sys.argv[1:]:
         print "Parsing %s" % f
-        try:
-            print p.parse(open(f).read(), filename=f)
-        except IDLError, e:
-            print e
-            exit(1)
+        p.parse(open(f).read(), filename=f)