More work. It turns out that libidl does keep track of some kinds of comments, so I may need to revise my grammar...
authorBenjamin Smedberg <benjamin@smedbergs.us>
Thu, 05 Jun 2008 16:09:47 -0400
changeset 5 9608c862313a
parent 4 c5b0dfdbfa52
child 6 1914d690fadd
push id5
push userbsmedberg@mozilla.com
push date2008-06-05 20:10 +0000
More work. It turns out that libidl does keep track of some kinds of comments, so I may need to revise my grammar...
header.py
xpidl.py
new file mode 100644
--- /dev/null
+++ b/header.py
@@ -0,0 +1,96 @@
+#!/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
+
+header = """/*
+ * DO NOT EDIT.  THIS FILE IS GENERATED FROM %(filename)s
+ */
+
+#ifndef __gen_%(basename)s_h__
+#define __gen_%(basename)s_h__
+"""
+
+include = """
+#ifndef __gen_%(basename)s_h__
+#include "%(basename)s.h"
+#endif
+"""
+
+header_end = """/* For IDL files that don't want to include root IDL files. */
+#ifndef NS_NO_VTABLE
+#define NS_NO_VTABLE
+#endif
+"""
+
+footer = """
+#endif /* __gen_%(basename)s_h__ */
+"""
+
+forward_decl = """class %(name)s; /* forward declaration */
+
+"""
+
+def idl_basename(f):
+    """returns the base name of a file with the last extension stripped"""
+    return os.path.basename(f).rpartition('.')[0]
+
+def print_header(idl, fd, filename):
+    fd.write(header % {'filename': filename,
+                       'basename': idl_basename(filename)})
+
+    inccount = 0
+    for inc in idl.includes():
+        fd.write('\n')
+        fd.write(include % {'basename': idl_basename(inc.filename)})
+
+    fd.write('\n')
+    fd.write(header_end)
+
+    for p in idl.productions:
+        if p.kind == 'include': continue
+        if p.kind == 'cdata':
+            fd.write(p.data)
+            continue
+
+        if p.kind == 'forward':
+            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 = """
+/* 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) }}
+
+class %(name)s%(attributes)se%(base)s
+ {
+ public: 
+
+"""
+
+def write_interface(iface, fd):
+    fd.write(iface_header % iface.name)
+
+    defname = iface.name.upper()
+    if iface.name[0:2] == 'ns':
+        defname = 'NS_' + defname[2:]
+
+    fd.write(
+
+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])
--- a/xpidl.py
+++ b/xpidl.py
@@ -1,248 +1,678 @@
 #!/usr/bin/env python
 
 """A parser for cross-platform IDL (XPIDL) files."""
 
-from optparse import OptionParser
-import lex, yacc, sys
+import lex, yacc, sys, os.path
+
+class Location(object):
+    _line = None
+
+    def __init__(self, lexer, lineno, lexpos):
+        self._lineno = lineno
+        self._lexpos = lexpos
+        self._lexdata = lexer.lexdata
+        self._file = getattr(lexer, 'filename', "<unknown>")
+
+    def resolve(self):
+        if self._line:
+            return
+
+        startofline = self._lexdata.rfind('\n', 0, self._lexpos) + 1
+        endofline = self._lexdata.find('\n', self._lexpos, self._lexpos + 80)
+        self._line = self._lexdata[startofline:endofline]
+        self._colno = self._lexpos - startofline
+
+    def pointerline(self):
+        def i():
+            for i in xrange(0, self._colno):
+                yield " "
+            yield "^"
+
+        return "".join(i())
+
+    def get(self):
+        self.resolve()
+        return "%s line %s:%s" % (self._file, self._lineno, self._colno)
+
+    def __str__(self):
+        self.resolve()
+        return "%s line %s:%s\n%s\n%s" % (self._file, self._lineno, self._colno,
+                                          self._line, self.pointerline())
+
+class SetOnceMap(object):
+    def __init__(self):
+        self._d = {}
+
+    def __getitem__(self, key):
+        return self._d[key][0]
+
+    def set(self, key, value, location):
+        if key in self._d:
+            raise IDLError("name '%s' specified twice. Previous location: %s" % d[key][1], location)
+
+        self._d[key] = (value, location)
+
+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):
+        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)
+
+    def resolve(self, incdirs):
+        def incfiles():
+            yield self.filename
+            for dir in 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)
+
+        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):
+        try:
+            return self.namemap[id]
+        except KeyError:
+            raise IDLError("type '%s' not found" % id, location)
+
+    def __str__(self):
+        return "".join([str(p) for p in self.productions])
+
+    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 __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
+
+    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
+
+    def __str__(self):
+        return "forward-declared %s\n" % self.name
+
+class Native(object):
+    kind = 'native'
+
+    modifier = None
+    specialtype = None
+
+    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'):
+                if self.specialtype is not None:
+                    raise IDLError("More than one special type", aloc)
+                self.specialtype = name
+            else:
+                raise IDLError("Unexpected attribute", aloc)
+
+    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):
+        self.name = name
+        self.attributes = InterfaceAttributes(attlist, location)
+        self.base = base
+        self.members = members
+        self.location = location
+        self.namemap = SetOnceMap()
+
+        for m in members:
+            if not isinstance(m, CDATA):
+                self.namemap.set(m.name, m, m.location)
 
-def pointerline(colno):
-    for i in xrange(0, colno):
-        yield " "
-    yield "^"
+    def __str__(self):
+        l = ["interface %s\n" % self.name]
+        if self.base is not None:
+            l.append("\tbase %s\n" % self.base)
+        l.append(str(self.attributes))
+        if self.members is None:
+            l.append("\tincomplete type\n")
+        else:
+            for m in self.members:
+                l.append(str(m))
+        return "".join(l)
+
+    def getConst(self, name, location):
+        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
+
+    def setuuid(self, value):
+        self.uuid = value
+
+    def setscriptable(self):
+        self.scriptable = True
+
+    def setfunction(self):
+        self.function = True
+
+    actions = {
+        'uuid':       (True, setuuid),
+        'scriptable': (False, setscriptable),
+        'function':   (False, setfunction),
+        }
+
+    def __init__(self, attlist, location):
+        def badattribute(self):
+            raise IDLError("Unexpected interface attribute '%s'" % att.name, location)
+
+        for name, val, aloc in attlist:
+            hasval, action = self.actions.get(name, (False, badattribute))
+            if hasval:
+                if val is None:
+                    raise IDLError("Expected value for attribute '%s'" % name,
+                                     aloc)
+
+                action(self, val)
+            else:
+                if val is not None:
+                    raise IDLError("Unexpected value for attribute '%s'" % name,
+                                     aloc)
+
+                action(self)
+
+        if self.uuid is None:
+            raise IDLError("interface has no uuid", location)
+
+    def __str__(self):
+        l = []
+        if self.uuid:
+            l.append("\tuuid: %s\n" % self.uuid)
+        if self.scriptable:
+            l.append("\tscriptable\n")
+        if self.function:
+            l.append("\tfunction\n")
+        return "".join(l)
+
+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)
+
+class Attribute(object):
+    kind = 'attribute'
+    noscript = False
+    notxpcom = False
 
-class XPIDLParser(object):
+    def __init__(self, type, name, attlist, readonly, location):
+        self.type = type
+        self.name = name
+        self.readonly = readonly
+        self.location = location
+
+        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)
+
+    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
+
+    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()
+        for p in paramlist:
+            self.namemap.set(p.name, p, p.location)
+        #for p in paramlist:
+        #    p.validate(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
+    const = False
+    array = False
+    retval = False
+    shared = 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':
+                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)
+
+                if name == 'const':
+                    self.const = True
+                elif name == 'array':
+                    self.array = True
+                elif name == 'retval':
+                    self.retval = True
+                elif name == 'shared':
+                    self.shared = True
+                else:
+                    raise IDLError("Unexpected attribute '%s'" % name, aloc)
+
+class IDLParser(object):
+    incdirs = []
+
     keywords = {
+        'const': 'CONST',
         'interface': 'INTERFACE',
         'in': 'IN',
         'inout': 'INOUT',
         'out': 'OUT',
-        'const': 'CONST',
-        'shared': 'SHARED',
-        'array': 'ARRAY',
-        'size_is': 'SIZE_IS',
-        'iid_is': 'IID_IS',
-        'retval': 'RETVAL',
-        'uuid_is':  'UUID_IS',
-        'noscript': 'NOSCRIPT',
-        'notxpcom': 'NOTXPCOM',
-        'scriptable': 'SCRIPTABLE',
         'attribute': 'ATTRIBUTE',
+        'raises': 'RAISES',
         'readonly': 'READONLY',
-        'function': 'FUNCTION',
-        'object': 'OBJECT',
         'native': 'NATIVE',
         'typedef': 'TYPEDEF'
         }
 
     tokens = [
         'IDENTIFIER',
         'CDATA',
         'INCLUDE',
-        'FILENAME',
         'IID',
+        'NUMBER',
+        'HEXNUM',
+        'LSHIFT',
+        'RSHIFT',
+        'NATIVEID',
         ]
 
     tokens.extend(keywords.values())
 
+    states = (
+        ('nativeid', 'exclusive'),
+    )
+
+    hexchar = r'[a-fA-F0-9]'
+
+    t_NUMBER = r'-?\d+'
+    t_HEXNUM = r'0x%s+' % hexchar
+    t_LSHIFT = r'<<'
+    t_RSHIFT=  r'>>'
+
+    literals = '"(){}[],;:=|+-*'
+
+    t_ignore = ' \t'
+
     def t_multilinecomment(self, t):
         r'/\*(?s).*?\*/'
         t.lexer.lineno += t.value.count('\n')
 
     def t_singlelinecomment(self, t):
         r'(?m)//.*?$'
 
-    hexchar = r'[a-fA-F0-9]'
     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).*?%\}'
+        r'%\{[ ]*C\+\+\s*\n(?s)(?P<cdata>.*?\n)%\}[ ]*(C\+\+)?'
         t.type = 'CDATA'
-        t.value = t.value[5:-2]
+        t.value = t.lexer.lexmatch.group('cdata')
         t.lexer.lineno += t.value.count('\n')
         return t
 
     def t_INCLUDE(self, t):
         r'\#include[ \t]+"[^"\n]+"'
         inc, value, end = t.value.split('"')
         t.value = value
         return t
 
-    literals = '"(){}[],;:'
-
-    t_ignore = ' \t'
-
     def t_newline(self, t):
         r'\n+'
         t.lexer.lineno += len(t.value)
 
-    def finderror(self, token):
-        """Given a token and a lexer, return (colno, badline, pointerline)"""
-        lexer = self.lexer
+    def t_nativeid_NATIVEID(self, t):
+        r'[^()\n]+(?=\))'
+        t.lexer.begin('INITIAL')
+        return t
+
+    t_nativeid_ignore = ''
 
-        startofline = lexer.lexdata.rfind('\n', 0, token.lexpos) + 1
-        endofline = lexer.lexdata.find('\n', token.lexpos, token.lexpos + 80)
-        line = lexer.lexdata[startofline:endofline]
-        colno = token.lexpos - startofline
-        return (colno, line, "".join(pointerline(colno)))
+    def t_ANY_error(self, t):
+        raise IDLError("unrecognized input",
+                         Location(lexer=self.lexer,
+                                         lineno=self.lexer.lineno,
+                                         lexpos=self.lexer.lexpos))
 
-    def t_error(self, t):
-        colno, line, pointerline = self.finderror(t)
-        raise Exception("Error: line %i column %i: unrecognized input.\n%s\n%s" % (t.lineno, colno, line, pointerline))
+    precedence = (
+        ('left', '|'),
+        ('left', 'LSHIFT', 'RSHIFT'),
+        ('left', '+', '-'),
+        ('left', 'UMINUS'),
+    )
 
     def p_idlfile(self, p):
-        """idlfile : includes productions"""
-        p[0] = {'includes': p[1],
-                'productions': p[2]}
-
-    def p_includes_start(self, p):
-        """includes : """
-        p[0] = []
-
-    def p_includes_continue(self, p):
-        """includes : INCLUDE includes"""
-        p[0] = list(p[2])
-        p[0].insert(0, p[1])
+        """idlfile : productions"""
+        p[0] = IDL(p[1])
 
     def p_productions_start(self, p):
         """productions : """
         p[0] = []
 
     def p_productions_cdata(self, p):
         """productions : CDATA productions"""
         p[0] = list(p[2])
-        p[0].insert(0, {'kind': 'cdata',
-                        'data': p[1]})
+        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)))
 
     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] = {'kind': 'typedef',
-                'name': p[3],
-                'type': p[2]}
+        p[0] = Typedef(type=p[2],
+                       name=p[3],
+                       location=self.getLocation(p, 1))
 
     def p_native(self, p):
-        """native : '[' nativemodifiers ']' NATIVE IDENTIFIER '(' IDENTIFIER ')' ';'"""
-        p[0] = {'kind': 'native',
-                'name': p[5],
-                'modifiers': p[2],
-                'identifier': p[7]}
+        """native : attributes NATIVE IDENTIFIER afternativeid '(' NATIVEID ')' ';'"""
+        p[0] = Native(name=p[3],
+                      nativename=p[6],
+                      attlist=p[1],
+                      location=self.getLocation(p, 2))
+
+    def p_afternativeid(self, p):
+        """afternativeid : """
+        # this is a place marker: we switch the lexer into literal identifier
+        # mode here, to slurp up everything until the closeparen
+        self.lexer.begin('nativeid')
 
-    def p_nativemodifiers_start(self, p):
-        """nativemodifiers : IDENTIFIER"""
+    def p_anyident(self, p):
+        """anyident : IDENTIFIER
+                    | CONST"""
+        p[0] = p[1]
+
+    def p_attributes(self, p):
+        """attributes : '[' attlist ']'
+                      | """
+        if len(p) == 1:
+            p[0] = []
+        else:
+            p[0] = p[2]
+
+    def p_attlist_start(self, p):
+        """attlist : attribute"""
         p[0] = [p[1]]
 
-    def p_nativemodifiers_continue(self, p):
-        """nativemodifiers : IDENTIFIER ',' nativemodifiers"""
+    def p_attlist_continue(self, p):
+        """attlist : attribute ',' attlist"""
         p[0] = list(p[3])
         p[0].insert(0, p[1])
 
-    def p_interface(self, p):
-        """interface : ifaceatts INTERFACE IDENTIFIER ifacebase '{' members '}' ';'"""
-        p[0] = {'kind': 'interface',
-                'name': p[3],
-                'atts': p[1],
-                'members': p[6]}
+    def p_attribute(self, p):
+        """attribute : anyident attributeval"""
+        p[0] = (p[1], p[2], self.getLocation(p, 1))
 
-    def p_ifaceatts(self, p):
-        """ifaceatts : '[' ifaceattlist ']'
-                     | '[' ']'
-                     | """
-        if len(p) == 4:
+    def p_attributeval(self, p):
+        """attributeval : '(' IDENTIFIER ')'
+                        | '(' IID ')'
+                        | """
+        if len(p) > 1:
             p[0] = p[2]
-        else:
-            p[0] = []
 
-    def p_ifaceattlist_start(self, p):
-        """ifaceattlist : ifaceatt"""
-        p[0] = [p[1]]
-
-    def p_ifaceattlist_continue(self, p):
-        """ifaceattlist : ifaceatt ',' ifaceattlist"""
-        p[0] = list(p[3])
-        p[0].insert(0, p[1])
+    def p_interface(self, p):
+        """interface : attributes INTERFACE IDENTIFIER ifacebase ifacebody ';'"""
+        if p[5] is None:
+            # forward-declared interface... must not have attributes!
+            if len(p[1]) != 0:
+                raise IDLError("Forward-declared interface must not have attributes",
+                               p[1][0][3])
 
-    def p_ifaceatt_uuid(self, p):
-        """ifaceatt : IDENTIFIER '(' IID ')'"""
-        if p[1] != 'uuid':
-            raise Exception("unexpected keyword '%s', expected 'uuid'" % p[1])
-    
-        p[0] = {'name': 'uuid',
-                'value': p[3]}
+            if p[4] 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))
+        else:
+            p[0] = Interface(name=p[3],
+                             attlist=p[1],
+                             base=p[4],
+                             members=p[5],
+                             location=self.getLocation(p, 2))
 
-    def p_ifacatt_scriptable(self, p):
-        """ifaceatt : SCRIPTABLE"""
-        p[0] = {'name': 'scriptable'}
+    def p_ifacebody(self, p):
+        """ifacebody : '{' members '}'
+                     | """
+        if len(p) > 1:
+            p[0] = p[2]
 
     def p_ifacebase(self, p):
         """ifacebase : ':' IDENTIFIER
                      | """
         if len(p) == 3:
             p[0] = p[2]
 
     def p_members_start(self, p):
         """members : """
         p[0] = []
 
     def p_members_continue(self, p):
         """members : member members"""
         p[0] = list(p[2])
         p[0].insert(0, p[1])
 
-    def p_member_att(self, p):
-        """member : memberatts optreadonly ATTRIBUTE IDENTIFIER IDENTIFIER ';'"""
-        p[0] = {'kind': 'attribute',
-                'attributes': p[1],
-                'readonly': p[2],
-                'type': p[4],
-                'name': p[5]}
+    def p_member_cdata(self, p):
+        """member : CDATA"""
+        p[0] = {'kind': 'cdata',
+                'data': 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)
+
+    def p_number_decimal(self, p):
+        """number : NUMBER"""
+        n = int(p[1])
+        p[0] = lambda i: n
+
+    def p_number_hex(self, p):
+        """number : HEXNUM"""
+        n = int(p[1], 16)
+        p[0] = lambda i: n
 
-    def p_memberatts(self, p):
-        """memberatts : '[' memberatt memberattlist ']'
-                      | """
-        if len(p) == 1:
-            p[0] = []
+    def p_number_identifier(self, p):
+        """number : IDENTIFIER"""
+        id = p[1]
+        p[0] = lambda i: i.getConst(id, self.getLocation(p, 1))
+
+    def p_number_paren(self, p):
+        """number : '(' number ')'"""
+        p[0] = p[2]
+
+    def p_number_neg(self, p):
+        """number : '-' number %prec UMINUS"""
+        n = p[2]
+        p[0] = lambda i: - n(i)
+
+    def p_number_add(self, p):
+        """number : number '+' number
+                  | number '-' number
+                  | number '*' number"""
+        n1 = p[1]
+        n2 = p[3]
+        if p[2] == '+':
+            p[0] = lambda i: n1(i) + n2(i)
+        elif p[2] == '-':
+            p[0] = lambda i: n1(i) - n2(i)
         else:
-            p[0] = p[2]
+            p[0] = lambda i: n1(i) * n2(i)
 
-    def p_memberattlist_start(self, p):
-        """memberattlist : """
-        p[0] = []
+    def p_number_shift(self, p):
+        """number : number LSHIFT number
+                  | number RSHIFT number"""
+        n1 = p[1]
+        n2 = p[3]
+        if p[2] == '<<':
+            p[0] = lambda i: n1(i) << n2(i)
+        else:
+            p[0] = lambda i: n1(i) >> n2(i)
 
-    def p_memberattlist_continue(self, p):
-        """memberattlist : ',' memberatt memberattlist"""
-        p[0] = list(p[3])
-        p[0].insert(0, p[2])
+    def p_number_or(self, p):
+        """number : number '|' number"""
+        n1 = p[1]
+        n2 = p[3]
+        p[0] = lambda i: n1(i) | n2(i)
 
-    def p_memberatt(self, p):
-        """memberatt : NOSCRIPT
-                     | NOTXPCOM"""
-        p[0] = p[1]
+    def p_member_att(self, p):
+        """member : attributes optreadonly ATTRIBUTE IDENTIFIER IDENTIFIER ';'"""
+        p[0] = Attribute(type=p[4],
+                         name=p[5],
+                         attlist=p[1],
+                         readonly=p[2],
+                         location=self.getLocation(p, 1))
 
     def p_member_method(self, p):
-        """member : memberatts IDENTIFIER IDENTIFIER '(' paramlist ')' ';'"""
-        p[0] = {'kind': 'method',
-                'attributes': p[1],
-                'type': p[2],
-                'name': p[3],
-                'parameters': p[5]}
+        """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],
+                      paramlist=p[5],
+                      location=self.getLocation(p, 1))
 
     def p_paramlist(self, p):
         """paramlist : param moreparams
                      | """
         if len(p) == 1:
             p[0] = []
         else:
             p[0] = list(p[2])
@@ -253,69 +683,68 @@ class XPIDLParser(object):
         p[0] = []
 
     def p_moreparams_continue(self, p):
         """moreparams : ',' param moreparams"""
         p[0] = list(p[3])
         p[0].insert(0, p[2])
 
     def p_param(self, p):
-        """param : paramatts paramtype IDENTIFIER IDENTIFIER"""
-        p[0] = {'modifiers': p[1],
-                'paramtype': p[2],
-                'type': p[3],
-                'name': p[4]}
-
-    def p_paramatts(self, p):
-        """paramatts : '[' paramattlist ']'
-                     | """
-        if len(p) == 1:
-            p[0] = []
-        else:
-            p[0] = p[2]
-
-    def p_paramattlist_start(self, p):
-        """paramattlist : paramatt"""
-        p[0] = [p[1]]
-
-    def p_paramattlist_continue(self, p):
-        """paramattlist : paramatt ',' paramattlist"""
-        p[0] = list(p[3])
-        p[0].insert(0, p[1])
-
-    def p_paramatt(self, p):
-        """paramatt : RETVAL
-                    | ARRAY"""
-        p[0] = {'name': p[1]}
-
-    def p_paramatt_withval(self, p):
-        """paramatt : paramattnamewithval '(' IDENTIFIER ')'"""
-        p[0] = {'name': p[1],
-                'value': p[3]}
-
-    def p_paramattnamewithval(self, p):
-        """paramattnamewithval : IID_IS
-                               | SIZE_IS"""
-        p[0] = p[1]
+        """param : attributes paramtype IDENTIFIER IDENTIFIER"""
+        p[0] = Param(paramtype=p[2],
+                     type=p[3],
+                     name=p[4],
+                     attlist=p[1],
+                     location=self.getLocation(p, 1))
 
     def p_paramtype(self, p):
         """paramtype : IN
                      | INOUT
                      | OUT"""
         p[0] = p[1]
 
     def p_optreadonly(self, p):
         """optreadonly : READONLY
                        | """
         p[0] = len(p) > 1
 
+    def p_raises(self, p):
+        """raises : RAISES '(' idlist ')'
+                  | """
+
+    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: invalid syntax, line %i column %i.\n%s\n%s" % (p.lineno, colno, line, pointerline))
+        raise Exception("Error: line %i column %i: invalid syntax.\n%s\n%s" % (p.lineno, colno, line, pointerline))
 
     def __init__(self):
         self.lexer = lex.lex(object=self)
         self.parser = yacc.yacc(module=self)
 
-    def parse(self, data):
-        return self.parser.parse(lexer=self.lexer, input=data)
+    def token(self):
+        t = self.lexer.token()
+        # print "Token: %s" % t
+        return t
+
+    def parse(self, data, filename=None):
+        if filename is not None:
+            self.lexer.filename = filename
+        self.lexer.input(data)
+        return self.parser.parse(lexer=self)
 
-print XPIDLParser().parse(sys.stdin.read())
+    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)