Commits

Maciej Fijalkowski committed 3d57e93 Merge with conflicts

Merge branch 'api-refactor-2'

Conflicts:
c/_ffi_backend.c

  • Participants
  • Parent commits abcf53f, a828ced

Comments (0)

Files changed (9)

     &CData_Type,                                /* tp_base */
 };
 
-static PyObject *b_new(PyObject *self, PyObject *args)
+static PyObject *b_newp(PyObject *self, PyObject *args)
 {
     CTypeDescrObject *ct, *ctitem;
     CDataObject *cd;
     PyObject *init;
     Py_ssize_t dataoffset, datasize, explicitlength;
-    if (!PyArg_ParseTuple(args, "O!O:new", &CTypeDescr_Type, &ct, &init))
+    if (!PyArg_ParseTuple(args, "O!O:newp", &CTypeDescr_Type, &ct, &init))
         return NULL;
 
     explicitlength = -1;
     {"new_function_type", b_new_function_type, METH_VARARGS},
     {"new_enum_type", b_new_enum_type, METH_VARARGS},
     {"_getfields", b__getfields, METH_O},
-    {"new", b_new, METH_VARARGS},
+    {"newp", b_newp, METH_VARARGS},
     {"cast", b_cast, METH_VARARGS},
     {"callback", b_callback, METH_VARARGS},
     {"alignof", b_alignof, METH_O},
-__all__ = ['FFI', 'VerificationError', 'VerificationMissing']
+__all__ = ['FFI', 'VerificationError', 'VerificationMissing', 'CDefError',
+           'FFIError']
 
-from ffi.api import FFI
+from ffi.api import FFI, CDefError, FFIError
 from ffi.ffiplatform import VerificationError, VerificationMissing
 import new
-import pycparser    # http://code.google.com/p/pycparser/
-from ffi import ffiplatform
+from ffi import cparser
 
 class FFIError(Exception):
     pass
                 from . import backend_ctypes
                 backend = backend_ctypes.CTypesBackend()
         self._backend = backend
-        self._declarations = {}
+        self._parser = cparser.Parser()
         self._cached_btypes = {}
         self._parsed_types = new.module('parsed_types').__dict__
         self._new_types = new.module('new_types').__dict__
-        self._completed_struct_or_union = set()
         if hasattr(backend, 'set_ffi'):
             backend.set_ffi(self)
         self.C = _make_ffi_library(self, None)
             lines.append('typedef %s %s;' % (equiv % by_size[size], name))
         self.cdef('\n'.join(lines))
 
-    def _declare(self, name, node):
-        if name == 'typedef __dotdotdot__':
-            return
-        if name in self._declarations:
-            raise FFIError("multiple declarations of %s" % (name,))
-        self._declarations[name] = node
-
     def cdef(self, csource):
         """Parse the given C source.  This registers all declared functions,
         types, and global variables.  The functions and global variables can
         then be accessed via 'ffi.C' or 'ffi.load()'.  The types can be used
         in 'ffi.new()' and other functions.
         """
-        csource = ("typedef int __dotdotdot__;\n" +
-                   csource.replace('...', '__dotdotdot__'))
-        ast = _get_parser().parse(csource)
-
-        for decl in ast.ext:
-            if isinstance(decl, pycparser.c_ast.Decl):
-                self._parse_decl(decl)
-            elif isinstance(decl, pycparser.c_ast.Typedef):
-                if not decl.name:
-                    raise CDefError("typedef does not declare any name", decl)
-                self._declare('typedef ' + decl.name, decl.type)
-            else:
-                raise CDefError("unrecognized construct", decl)
-
-    def _parse_decl(self, decl):
-        node = decl.type
-        if isinstance(node, pycparser.c_ast.FuncDecl):
-            self._declare('function ' + decl.name, node)
-        else:
-            if isinstance(node, pycparser.c_ast.Struct):
-                if node.decls is not None:
-                    self._declare('struct ' + node.name, node)
-            elif isinstance(node, pycparser.c_ast.Union):
-                if node.decls is not None:
-                    self._declare('union ' + node.name, node)
-            elif isinstance(node, pycparser.c_ast.Enum):
-                if node.values is not None:
-                    self._declare('enum ' + node.name, node)
-            elif not decl.name:
-                raise CDefError("construct does not declare any variable",
-                                decl)
-            #
-            if decl.name:
-                self._declare('variable ' + decl.name, node)
+        self._parser.parse(csource)
 
     def load(self, name):
         """Load and return a dynamic library identified by 'name'.
             try:
                 return self._parsed_types[cdecl]
             except KeyError:
-                typenode = self._parse_type(cdecl)
-                btype = self._get_btype(typenode)
+                type = self._parser.parse_type(cdecl)
+                btype = type.get_backend_type(self)
                 self._parsed_types[cdecl] = btype
                 return btype
         else:
         try:
             BType = self._new_types[cdecl]
         except KeyError:
-            typenode = self._parse_type(cdecl)
-            BType = self._get_btype(typenode, force_pointer=True)
+            type = self._parser.parse_type(cdecl, force_pointer=True)
+            BType = type.get_backend_type(self)
             self._new_types[cdecl] = BType
         #
-        return self._backend.new(BType, init)
+        return self._backend.newp(BType, init)
 
     def cast(self, cdecl, source):
         """Similar to a C cast: returns an instance of the named C
         BFunc = self.typeof(cdecl)
         return self._backend.callback(BFunc, python_callable)
 
-    def _parse_type(self, cdecl):
-        # XXX: for more efficiency we would need to poke into the
-        # internals of CParser...  the following registers the
-        # typedefs, because their presence or absence influences the
-        # parsing itself (but what they are typedef'ed to plays no role)
-        csourcelines = []
-        for name in sorted(self._declarations):
-            if name.startswith('typedef '):
-                csourcelines.append('typedef int %s;' % (name[8:],))
-        #
-        csourcelines.append('void __dummy(%s);' % cdecl)
-        ast = _get_parser().parse('\n'.join(csourcelines))
-        # XXX: insert some sanity check
-        typenode = ast.ext[-1].type.args.params[0].type
-        return typenode
-
-    def _get_cached_btype(self, methname, *args):
+    def _get_cached_btype(self, type):
         try:
-            BType = self._cached_btypes[methname, args]
+            BType = self._cached_btypes[type]
         except KeyError:
-            BType = getattr(self._backend, methname)(*args)
-            self._cached_btypes[methname, args] = BType
+            args = type.prepare_backend_type(self)
+            if args is None:
+                args = ()
+            BType = type.finish_backend_type(self, *args)
+            self._cached_btypes[type] = BType
         return BType
 
-    def _get_btype_pointer(self, type):
-        BItem = self._get_btype(type)
-        if isinstance(type, pycparser.c_ast.FuncDecl):
-            return BItem      # "pointer-to-function" ~== "function"
-        return self._get_cached_btype("new_pointer_type", BItem)
-
-    def _get_btype(self, typenode, convert_array_to_pointer=False,
-                   force_pointer=False):
-        # first, dereference typedefs, if necessary several times
-        while (isinstance(typenode, pycparser.c_ast.TypeDecl) and
-               isinstance(typenode.type, pycparser.c_ast.IdentifierType) and
-               len(typenode.type.names) == 1 and
-               ('typedef ' + typenode.type.names[0]) in self._declarations):
-            typenode = self._declarations['typedef ' + typenode.type.names[0]]
-        #
-        if isinstance(typenode, pycparser.c_ast.ArrayDecl):
-            # array type
-            if convert_array_to_pointer:
-                return self._get_btype_pointer(typenode.type)
-            if typenode.dim is None:
-                length = None
-            else:
-                length = self._parse_constant(typenode.dim)
-            BItem = self._get_btype(typenode.type)
-            BPtr = self._get_cached_btype('new_pointer_type', BItem)
-            return self._get_cached_btype('new_array_type', BPtr, length)
-        #
-        if force_pointer:
-            return self._get_btype_pointer(typenode)
-        #
-        if isinstance(typenode, pycparser.c_ast.PtrDecl):
-            # pointer type
-            return self._get_btype_pointer(typenode.type)
-        #
-        if isinstance(typenode, pycparser.c_ast.TypeDecl):
-            type = typenode.type
-            if isinstance(type, pycparser.c_ast.IdentifierType):
-                # assume a primitive type.  get it from .names, but reduce
-                # synonyms to a single chosen combination
-                names = list(type.names)
-                if names == ['signed'] or names == ['unsigned']:
-                    names.append('int')
-                if names[0] == 'signed' and names != ['signed', 'char']:
-                    names.pop(0)
-                if (len(names) > 1 and names[-1] == 'int'
-                        and names != ['unsigned', 'int']):
-                    names.pop()
-                ident = ' '.join(names)
-                if ident == 'void':
-                    return self._get_cached_btype("new_void_type")
-                return self._get_cached_btype('new_primitive_type', ident)
-            #
-            if isinstance(type, pycparser.c_ast.Struct):
-                # 'struct foobar'
-                return self._get_struct_or_union_type('struct', type)
-            #
-            if isinstance(type, pycparser.c_ast.Union):
-                # 'union foobar'
-                return self._get_struct_or_union_type('union', type)
-            #
-            if isinstance(type, pycparser.c_ast.Enum):
-                # 'enum foobar'
-                return self._get_enum_type(type)
-        #
-        if isinstance(typenode, pycparser.c_ast.FuncDecl):
-            # a function type
-            params = list(typenode.args.params)
-            ellipsis = (
-                len(params) > 0 and
-                isinstance(params[-1].type, pycparser.c_ast.TypeDecl) and
-                isinstance(params[-1].type.type,
-                           pycparser.c_ast.IdentifierType) and
-                ''.join(params[-1].type.type.names) == '__dotdotdot__')
-            if ellipsis:
-                params.pop()
-            if (len(params) == 1 and
-                isinstance(params[0].type, pycparser.c_ast.TypeDecl) and
-                isinstance(params[0].type.type, pycparser.c_ast.IdentifierType)
-                    and list(params[0].type.type.names) == ['void']):
-                del params[0]
-            args = [self._get_btype(argdeclnode.type,
-                                    convert_array_to_pointer=True)
-                    for argdeclnode in params]
-            result = self._get_btype(typenode.type)
-            return self._get_cached_btype('new_function_type',
-                                          tuple(args), result, ellipsis)
-        #
-        raise FFIError("bad or unsupported type declaration")
-
-    def _get_struct_or_union_type(self, kind, type):
-        name = type.name
-        result = self._get_cached_btype('new_%s_type' % kind, name)
-        if (kind, name) in self._completed_struct_or_union:
-            return result
-        #
-        decls = type.decls
-        if decls is None and name is not None:
-            key = '%s %s' % (kind, name)
-            if key in self._declarations:
-                decls = self._declarations[key].decls
-        if decls is None:
-            return result    # opaque type, so far
-        #
-        # mark it as complete *first*, to handle recursion
-        self._completed_struct_or_union.add((kind, name))
-        fields = []
-        for decl in decls:
-            if (isinstance(decl.type, pycparser.c_ast.IdentifierType) and
-                    ''.join(decl.type.names) == '__dotdotdot__'):
-                # XXX pycparser is inconsistent: 'names' should be a list
-                # of strings, but is sometimes just one string.  Use
-                # str.join() as a way to cope with both.
-                raise ffiplatform.VerificationMissing("%s %s only partially"
-                                                  " specified" % (kind, name))
-            if decl.bitsize is None:
-                bitsize = -1
-            else:
-                bitsize = self._parse_constant(decl.bitsize)
-            fields.append((decl.name,
-                           self._get_btype(decl.type),
-                           bitsize))
-        self._backend.complete_struct_or_union(result, fields)
-        return result
-
-    def _get_enum_type(self, type):
-        name = type.name
-        decls = type.values
-        if decls is None and name is not None:
-            key = 'enum %s' % (name,)
-            if key in self._declarations:
-                decls = self._declarations[key].values
-        if decls is not None:
-            enumerators = tuple([enum.name for enum in decls.enumerators])
-            enumvalues = []
-            nextenumvalue = 0
-            for enum in decls.enumerators:
-                if enum.value is not None:
-                    nextenumvalue = self._parse_constant(enum.value)
-                enumvalues.append(nextenumvalue)
-                nextenumvalue += 1
-            enumvalues = tuple(enumvalues)
-        else:   # opaque enum
-            enumerators = ()
-            enumvalues = ()
-        return self._get_cached_btype('new_enum_type', name,
-                                      enumerators, enumvalues)
-
-    def _parse_constant(self, exprnode):
-        # for now, limited to expressions that are an immediate number
-        # or negative number
-        if isinstance(exprnode, pycparser.c_ast.Constant):
-            return int(exprnode.value)
-        #
-        if (isinstance(exprnode, pycparser.c_ast.UnaryOp) and
-                exprnode.op == '-'):
-            return -self._parse_constant(exprnode.expr)
-        #
-        raise FFIError("unsupported non-constant or "
-                       "not immediately constant expression")
-
     def verify(self, preamble='', **kwargs):
         """ Verify that the current ffi signatures compile on this machine
         """
                 pass
             #
             key = 'function ' + name
-            if key in ffi._declarations:
-                node = ffi._declarations[key]
-                BType = ffi._get_btype(node)
+            if key in ffi._parser._declarations:
+                tp = ffi._parser._declarations[key]
+                BType = ffi._get_cached_btype(tp)
                 value = backendlib.load_function(BType, name)
                 function_cache[name] = value
                 return value
             #
             key = 'variable ' + name
-            if key in ffi._declarations:
-                node = ffi._declarations[key]
-                BType = ffi._get_btype(node)
+            if key in ffi._parser._declarations:
+                tp = ffi._parser._declarations[key]
+                BType = ffi._get_cached_btype(tp)
                 return backendlib.read_variable(BType, name)
             #
             raise AttributeError(name)
                 return
             #
             key = 'variable ' + name
-            if key in ffi._declarations:
-                node = ffi._declarations[key]
-                BType = ffi._get_btype(node)
+            if key in ffi._parser._declarations:
+                tp = ffi._parser._declarations[key]
+                BType = ffi._get_cached_btype(tp)
                 backendlib.write_variable(BType, name, value)
                 return
             #
     if libname is not None:
         FFILibrary.__name__ = 'FFILibrary_%s' % libname
     return FFILibrary()
-
-
-_parser_cache = None
-
-def _get_parser():
-    global _parser_cache
-    if _parser_cache is None:
-        _parser_cache = pycparser.CParser()
-    return _parser_cache

ffi/backend_ctypes.py

 import ctypes, ctypes.util
-
+from ffi import model
 
 class CTypesData(object):
     __slots__ = []
         return CTypesPrimitive
 
     def new_pointer_type(self, BItem):
-        if BItem is self.ffi._get_cached_btype('new_primitive_type', 'char'):
+        if BItem is self.ffi._get_cached_btype(model.PrimitiveType('char')):
             kind = 'charp'
         else:
             kind = 'generic'
                         ctypes.sizeof(self._as_ctype_ptr.contents),)
                 return ''
         #
-        if (BItem is self.ffi._get_cached_btype('new_void_type') or
-            BItem is self.ffi._get_cached_btype('new_primitive_type', 'char')):
+        if (BItem is self.ffi._get_cached_btype(model.void_type) or
+            BItem is self.ffi._get_cached_btype(model.PrimitiveType('char'))):
             CTypesPtr._automatic_casts = True
         #
         CTypesPtr._fix_class()
         else:
             brackets = ' &[%d]' % length
         BItem = CTypesPtr._BItem
-        if BItem is self.ffi._get_cached_btype('new_primitive_type', 'char'):
+        if BItem is self.ffi._get_cached_btype(model.PrimitiveType('char')):
             kind = 'char'
         else:
             kind = 'generic'
     def new_union_type(self, name):
         return self._new_struct_or_union('union', name, ctypes.Union)
 
-    def complete_struct_or_union(self, CTypesStructOrUnion, fields):
+    def complete_struct_or_union(self, CTypesStructOrUnion, tp, btypes):
         struct_or_union = CTypesStructOrUnion._ctype
-        fnames = [fname for (fname, BField, bitsize) in fields]
-        btypes = [BField for (fname, BField, bitsize) in fields]
-        bitfields = [bitsize for (fname, BField, bitsize) in fields]
+        fnames = tp.fldnames
+        bitfields = tp.fldbitsize
+        fields = zip(fnames, btypes, bitfields)
         #
         cfields = []
         for (fname, BField, bitsize) in fields:
             def initialize(blob, init):
                 if not isinstance(init, dict):
                     init = tuple(init)
-                    if len(init) > len(fields):
+                    if len(init) > len(fnames):
                         raise ValueError("too many values for %s initializer" %
                                          CTypesStructOrUnion._get_c_name())
                     init = dict(zip(fnames, init))
         if CTypesStructOrUnion._kind == 'union':
             def initialize(blob, init):
                 addr = ctypes.addressof(blob)
-                fname = fnames[0]
+                #fname = fnames[0]
                 BField = btypes[0]
                 PTR = ctypes.POINTER(BField._ctype)
                 BField._initialize(ctypes.cast(addr, PTR).contents, init)
                         raise OverflowError("value too large for bitfield")
             setattr(CTypesStructOrUnion, fname, property(getter, setter))
         #
-        CTypesPtr = self.ffi._get_cached_btype('new_pointer_type',
-                                               CTypesStructOrUnion)
+        CTypesPtr = self.ffi._get_cached_btype(model.PointerType(tp))
         for fname in fnames:
             if hasattr(CTypesPtr, fname):
                 raise ValueError("the field name %r conflicts in "
     def new_enum_type(self, name, enumerators, enumvalues):
         mapping = dict(zip(enumerators, enumvalues))
         reverse_mapping = dict(reversed(zip(enumvalues, enumerators)))
-        CTypesInt = self.ffi._get_cached_btype('new_primitive_type', 'int')
+        CTypesInt = self.ffi._get_cached_btype(model.PrimitiveType('int'))
         #
         def forward_map(source):
             if not isinstance(source, str):
         assert issubclass(BType, CTypesData)
         return BType._offsetof(fieldname)
 
-    def new(self, BType, source):
+    def newp(self, BType, source):
         return BType(source)
 
     def cast(self, BType, source):
+
+import ffi
+import pycparser
+from ffi import model
+
+_parser_cache = None
+
+def _get_parser():
+    global _parser_cache
+    if _parser_cache is None:
+        _parser_cache = pycparser.CParser()
+    return _parser_cache
+
+class Parser(object):
+    def __init__(self):
+        self._declarations = {}
+
+    def parse(self, csource):
+        csource = ("typedef int __dotdotdot__;\n" +
+                   csource.replace('...', '__dotdotdot__'))
+        ast = _get_parser().parse(csource)
+        for decl in ast.ext:
+            if isinstance(decl, pycparser.c_ast.Decl):
+                self._parse_decl(decl)
+            elif isinstance(decl, pycparser.c_ast.Typedef):
+                if not decl.name:
+                    raise ffi.CDefError("typedef does not declare any name",
+                                        decl)
+                if decl.name != '__dotdotdot__':
+                    self._declare('typedef ' + decl.name,
+                                  self._get_type(decl.type))
+            else:
+                raise ffi.CDefError("unrecognized construct", decl)
+
+    def _parse_decl(self, decl):
+        node = decl.type
+        if isinstance(node, pycparser.c_ast.FuncDecl):
+            self._declare('function ' + decl.name,
+                          self._get_type(node, name=decl.name))
+        else:
+            if isinstance(node, pycparser.c_ast.Struct):
+                # XXX do we need self._declare in any of those?
+                if node.decls is not None:
+                    self._get_struct_or_union_type('struct', node)
+            elif isinstance(node, pycparser.c_ast.Union):
+                if node.decls is not None:
+                    self._get_struct_or_union_type('union', node)
+            elif isinstance(node, pycparser.c_ast.Enum):
+                if node.values is not None:
+                    self._get_enum_type(node)
+            elif not decl.name:
+                raise ffi.CDefError("construct does not declare any variable",
+                                    decl)
+            #
+            if decl.name:
+                self._declare('variable ' + decl.name, self._get_type(node))
+
+    def parse_type(self, cdecl, force_pointer=False,
+                   convert_array_to_pointer=False):
+        # XXX: for more efficiency we would need to poke into the
+        # internals of CParser...  the following registers the
+        # typedefs, because their presence or absence influences the
+        # parsing itself (but what they are typedef'ed to plays no role)
+        csourcelines = []
+        for name in sorted(self._declarations):
+            if name.startswith('typedef '):
+                csourcelines.append('typedef int %s;' % (name[8:],))
+        #
+        csourcelines.append('void __dummy(%s);' % cdecl)
+        ast = _get_parser().parse('\n'.join(csourcelines))
+        typenode = ast.ext[-1].type.args.params[0].type
+        return self._get_type(typenode, force_pointer=force_pointer,
+                              convert_array_to_pointer=convert_array_to_pointer)
+
+    def _declare(self, name, obj):
+        if name in self._declarations:
+            raise ffi.FFIError("multiple declarations of %s" % (name,))
+        self._declarations[name] = obj
+
+    def _get_type_pointer(self, type):
+        if isinstance(type, model.FunctionType):
+            return type # "pointer-to-function" ~== "function"
+        return model.PointerType(type)
+
+    def _get_type(self, typenode, convert_array_to_pointer=False,
+                  force_pointer=False, name=None):
+        # first, dereference typedefs, if we have it already parsed, we're good
+        if (isinstance(typenode, pycparser.c_ast.TypeDecl) and
+            isinstance(typenode.type, pycparser.c_ast.IdentifierType) and
+            len(typenode.type.names) == 1 and
+            ('typedef ' + typenode.type.names[0]) in self._declarations):
+            type = self._declarations['typedef ' + typenode.type.names[0]]
+            if force_pointer:
+                return self._get_type_pointer(type)
+            if convert_array_to_pointer:
+                xxx
+            return type
+        #
+        if isinstance(typenode, pycparser.c_ast.ArrayDecl):
+            # array type
+            if convert_array_to_pointer:
+                return self._get_type_pointer(self._get_type(typenode.type))
+            if typenode.dim is None:
+                length = None
+            else:
+                length = self._parse_constant(typenode.dim)
+            return model.ArrayType(self._get_type(typenode.type), length)
+        #
+        if force_pointer:
+            return model.PointerType(self._get_type(typenode))
+        #
+        if isinstance(typenode, pycparser.c_ast.PtrDecl):
+            # pointer type
+            return self._get_type_pointer(self._get_type(typenode.type))
+        #
+        if isinstance(typenode, pycparser.c_ast.TypeDecl):
+            type = typenode.type
+            if isinstance(type, pycparser.c_ast.IdentifierType):
+                # assume a primitive type.  get it from .names, but reduce
+                # synonyms to a single chosen combination
+                names = list(type.names)
+                if names == ['signed'] or names == ['unsigned']:
+                    names.append('int')
+                if names[0] == 'signed' and names != ['signed', 'char']:
+                    names.pop(0)
+                if (len(names) > 1 and names[-1] == 'int'
+                        and names != ['unsigned', 'int']):
+                    names.pop()
+                ident = ' '.join(names)
+                if ident == 'void':
+                    return model.void_type
+                return model.PrimitiveType(ident)
+            #
+            if isinstance(type, pycparser.c_ast.Struct):
+                # 'struct foobar'
+                return self._get_struct_or_union_type('struct', type)
+            #
+            if isinstance(type, pycparser.c_ast.Union):
+                # 'union foobar'
+                return self._get_struct_or_union_type('union', type)
+            #
+            if isinstance(type, pycparser.c_ast.Enum):
+                # 'enum foobar'
+                return self._get_enum_type(type)
+        #
+        if isinstance(typenode, pycparser.c_ast.FuncDecl):
+            # a function type
+            return self._parse_function_type(typenode, name)
+        #
+        raise ffi.FFIError("bad or unsupported type declaration")
+
+    def _parse_function_type(self, typenode, name=None):
+        params = list(getattr(typenode.args, 'params', []))
+        ellipsis = (
+            len(params) > 0 and
+            isinstance(params[-1].type, pycparser.c_ast.TypeDecl) and
+            isinstance(params[-1].type.type,
+                       pycparser.c_ast.IdentifierType) and
+            ''.join(params[-1].type.type.names) == '__dotdotdot__')
+        if ellipsis:
+            params.pop()
+        if (len(params) == 1 and
+            isinstance(params[0].type, pycparser.c_ast.TypeDecl) and
+            isinstance(params[0].type.type, pycparser.c_ast.IdentifierType)
+                and list(params[0].type.type.names) == ['void']):
+            del params[0]
+        args = [self._get_type(argdeclnode.type,
+                               convert_array_to_pointer=True)
+                for argdeclnode in params]
+        result = self._get_type(typenode.type)
+        return model.FunctionType(name, tuple(args), result, ellipsis)
+
+    def _get_struct_or_union_type(self, kind, type):
+        name = type.name
+        key = '%s %s' % (kind, name)
+        if key in self._declarations:
+            return self._declarations[key]
+        #
+        decls = type.decls
+        # create an empty type for now
+        if kind == 'struct':
+            tp = model.StructType(name, None, None, None)
+        else:
+            assert kind == 'union'
+            tp = model.UnionType(name, None, None, None)
+        self._declarations[key] = tp
+
+        #if decls is None and name is not None:
+        #    key = '%s %s' % (kind, name)
+        #    if key in self._declarations:
+        #        decls = self._declarations[key].decls
+        if decls is None:
+            return tp    # opaque type, so far
+        #
+        # mark it as complete *first*, to handle recursion
+        fldnames = []
+        fldtypes = []
+        fldbitsize = []
+        for decl in decls:
+            if (isinstance(decl.type, pycparser.c_ast.IdentifierType) and
+                    ''.join(decl.type.names) == '__dotdotdot__'):
+                xxxx
+                # XXX pycparser is inconsistent: 'names' should be a list
+                # of strings, but is sometimes just one string.  Use
+                # str.join() as a way to cope with both.
+            if decl.bitsize is None:
+                bitsize = -1
+            else:
+                bitsize = self._parse_constant(decl.bitsize)
+            fldnames.append(decl.name)
+            fldtypes.append(self._get_type(decl.type))
+            fldbitsize.append(bitsize)
+        tp.fldnames = tuple(fldnames)
+        tp.fldtypes = tuple(fldtypes)
+        tp.fldbitsize = tuple(fldbitsize)
+        return tp
+
+    def _parse_constant(self, exprnode):
+        # for now, limited to expressions that are an immediate number
+        # or negative number
+        if isinstance(exprnode, pycparser.c_ast.Constant):
+            return int(exprnode.value)
+        #
+        if (isinstance(exprnode, pycparser.c_ast.UnaryOp) and
+                exprnode.op == '-'):
+            return -self._parse_constant(exprnode.expr)
+        #
+        raise ffi.FFIError("unsupported non-constant or "
+                           "not immediately constant expression")
+
+    def _get_enum_type(self, type):
+        name = type.name
+        decls = type.values
+        key = 'enum %s' % (name,)
+        if key in self._declarations:
+            return self._declarations[key]
+        if decls is not None:
+            enumerators = tuple([enum.name for enum in decls.enumerators])
+            enumvalues = []
+            nextenumvalue = 0
+            for enum in decls.enumerators:
+                if enum.value is not None:
+                    nextenumvalue = self._parse_constant(enum.value)
+                enumvalues.append(nextenumvalue)
+                nextenumvalue += 1
+            enumvalues = tuple(enumvalues) 
+            tp = model.EnumType(name, enumerators, enumvalues)
+            self._declarations[key] = tp
+        else:   # opaque enum
+            enumerators = ()
+            enumvalues = ()
+            tp = model.EnumType(name, (), ())
+        return tp
+
+class BaseType(object):
+    def __eq__(self, other):
+        return (self.__class__ == other.__class__ and
+                self._get_items() == other._get_items())
+
+    def __ne__(self, other):
+        return not self == other
+
+    def _get_items(self):
+        return [(name, getattr(self, name)) for name in self._attrs_]
+
+    def __hash__(self):
+        return hash((self.__class__, tuple(self._get_items())))
+
+    def prepare_backend_type(self, ffi):
+        pass
+
+    def finish_backend_type(self, ffi, *args):
+        try:
+            return ffi._cached_btypes[self]
+        except KeyError:
+            return self.new_backend_type(ffi, *args)
+
+    def get_backend_type(self, ffi):
+        return ffi._get_cached_btype(self)
+
+    def verifier_declare(self, verifier, f):
+        # nothing to see here
+        pass
+
+class VoidType(BaseType):
+    _attrs_ = ()
+    name = 'void'
+    
+    def new_backend_type(self, ffi):
+        return ffi._backend.new_void_type()
+
+    def __repr__(self):
+        return '<void>'
+
+void_type = VoidType()
+
+class PrimitiveType(BaseType):
+    _attrs_ = ('name',)
+
+    def __init__(self, name):
+        self.name = name
+
+    def new_backend_type(self, ffi):
+        return ffi._backend.new_primitive_type(self.name)
+
+    def __repr__(self):
+        return '<%s>' % (self.name,)
+
+class FunctionType(BaseType):
+    _attrs_ = ('args', 'result', 'ellipsis')
+
+    def __init__(self, name, args, result, ellipsis):
+        self.name = name # can be None in case it's an empty type
+        self.args = args
+        self.result = result
+        self.ellipsis = ellipsis
+
+    def __repr__(self):
+        args = ', '.join([repr(x) for x in self.args])
+        if self.ellipsis:
+            return '<(%s, ...) -> %r>' % (args, self.result)
+        return '<(%s) -> %r>' % (args, self.result)
+
+    def prepare_backend_type(self, ffi):
+        args = [ffi._get_cached_btype(self.result)]
+        for tp in self.args:
+            args.append(ffi._get_cached_btype(tp))
+        return args
+
+    def new_backend_type(self, ffi, result, *args):
+        return ffi._backend.new_function_type(args, result, self.ellipsis)
+
+    def verifier_declare(self, verifier, f):
+        restype = self.result.name
+        args = []
+        for arg in self.args:
+            args.append(arg.name)
+        args = ', '.join(args)
+        f.write('  %s(* res%d)(%s) = %s;\n' % (restype, verifier.rescount,
+                                               args, self.name))
+        verifier.rescount += 1
+
+class PointerType(BaseType):
+    _attrs_ = ('totype',)
+    
+    def __init__(self, totype):
+        self.totype = totype
+
+    def prepare_backend_type(self, ffi):
+        return (ffi._get_cached_btype(self.totype),)
+
+    def new_backend_type(self, ffi, BItem):
+        return ffi._backend.new_pointer_type(BItem)
+
+    def __repr__(self):
+        return '<*%r>' % (self.totype,)
+
+class ArrayType(BaseType):
+    _attrs_ = ('item', 'length')
+
+    def __init__(self, item, length):
+        self.item = PointerType(item) # XXX why is this pointer?
+        self.length = length
+
+    def __repr__(self):
+        if self.length is None:
+            return '<%r[]>' % (self.item,)
+        return '<%r[%s]>' % (self.item, self.length)
+
+    def prepare_backend_type(self, ffi):
+        return (ffi._get_cached_btype(self.item),)
+
+    def new_backend_type(self, ffi, BItem):
+        return ffi._backend.new_array_type(BItem, self.length)
+
+class StructOrUnion(BaseType):
+    _attrs_ = ('name',)
+        
+    def __init__(self, name, fldnames, fldtypes, fldbitsize):
+        self.name = name
+        self.fldnames = fldnames
+        self.fldtypes = fldtypes
+        self.fldbitsize = fldbitsize
+
+    def __repr__(self):
+        if self.fldnames is None:
+            return '<struct %s>' % (self.name,)
+        fldrepr = ', '.join(['%s: %r' % (name, tp) for name, tp in
+                             zip(self.fldnames, self.fldtypes)])
+        return '<struct %s {%s}>' % (self.name, fldrepr)
+
+    def prepare_backend_type(self, ffi):
+        BType = self.get_btype(ffi)
+        ffi._cached_btypes[self] = BType
+        args = [BType]
+        for tp in self.fldtypes:
+            args.append(ffi._get_cached_btype(tp))
+        return args
+
+    def finish_backend_type(self, ffi, BType, *fldtypes):
+        ffi._backend.complete_struct_or_union(BType, self, fldtypes)
+        return BType
+
+class StructType(StructOrUnion):
+    def get_btype(self, ffi):
+        return ffi._backend.new_struct_type(self.name)
+
+    def verifier_declare(self, verifier, f):
+        verifier._write_printf(f, 'BEGIN struct %s size(%%ld)' % self.name,
+                      'sizeof(struct %s)' % self.name)
+        for decl in decl.decls:
+            pass
+            #_write_printf(f, 'FIELD ofs(%s) size(%s)')
+        verifier._write_printf(f, 'END struct %s' % self.name)
+
+class UnionType(StructOrUnion):
+    def get_btype(self, ffi):
+        return ffi._backend.new_union_type(self.name)
+    
+class EnumType(BaseType):
+    _attrs_ = ('name',)
+
+    def __init__(self, name, enumerators, enumvalues):
+        self.name = name
+        self.enumerators = enumerators
+        self.enumvalues = enumvalues
+
+    def new_backend_type(self, ffi):
+        return ffi._backend.new_enum_type(self.name, self.enumerators,
+                                          self.enumvalues)
 from platformer import platform, ExternalCompilationInfo
 from ffi import ffiplatform
 
-def _write_printf(f, what, *args):
-    if not args:
-        f.write('  printf("%s\\n");\n' % (what,))
-    else:
-        f.write('  printf("%s\\n", %s);\n' % (what, ', '.join(args)))
-
 class Verifier(object):
     def __init__(self):
         self.rescount = 0
-    
+
+    def _write_printf(f, what, *args):
+        if not args:
+            f.write('  printf("%s\\n");\n' % (what,))
+        else:
+            f.write('  printf("%s\\n", %s);\n' % (what, ', '.join(args)))
+
     def verify(self, ffi, preamble, **kwargs):
         tst_file = ffiplatform._get_test_file()
         with tst_file.open('w') as f:
             f.write('#include <stdio.h>\n')
             f.write(preamble + "\n\n")
             f.write('int main() {\n')
-            for name, decl in ffi._declarations.iteritems():
-                if name.startswith('function '):
-                    self._declare_function(f, decl)
-                if name.startswith('struct '):
-                    self._declare_struct(f, decl)
+            for name, tp in ffi._parser._declarations.iteritems():
+                tp.verifier_declare(self, f)
             f.write('  return 0;\n')
             f.write('}\n')
         f.close()
         out = platform.execute(exe_name)
         assert out.returncode == 0
         outlines = out.out.splitlines()
-
-    def _declare_function(self, f, decl):
-        funcname = decl.type.declname
-        restype = decl.type.type.names[0]
-        if decl.args is None:
-            args = ''
-        else:
-            args = []
-            for arg in decl.args.params:
-                args.append(arg.type.type.names[0])
-            args = ', '.join(args)
-        f.write('  %s(* res%d)(%s) = %s;\n' % (restype, self.rescount,
-                                            args, funcname))
-        self.rescount += 1
-
-    def _declare_struct(self, f, decl):
-        structname = decl.name
-        _write_printf(f, 'BEGIN struct %s size(%%ld)' % structname,
-                      'sizeof(struct %s)' % structname)
-        for decl in decl.decls:
-            pass
-            #_write_printf(f, 'FIELD ofs(%s) size(%s)')
-        _write_printf(f, 'END struct %s' % structname)

testing/test_ctypes.py

 
 
 class TestCTypes(backend_tests.BackendTests):
+    # for individual tests see
+    # ====> backend_tests.py
+    
     Backend = CTypesBackend
     TypeRepr = "<class 'ffi.CData<%s>'>"

testing/test_parsing.py

     def new_struct_type(self, name):
         return FakeStruct(name)
 
-    def complete_struct_or_union(self, s, fields):
+    def complete_struct_or_union(self, s, tp, fields):
         assert isinstance(s, FakeStruct)
-        s.fields = fields
+        s.fields = zip(tp.fldnames, fields, tp.fldbitsize)
 
 class FakeStruct(object):
     def __init__(self, name):