Commits

Armin Rigo committed 3e9461e

Integrate function types with the other types.

  • Participants
  • Parent commits 5d94b8b

Comments (0)

Files changed (5)

 
     def _get_btype_pointer(self, type):
         BItem = self._get_btype(type)
+        if isinstance(type, pycparser.c_ast.FuncDecl):
+            return BItem      # "pointer-to-function" ~== "function"
         if ('const' in type.quals and
             BItem is self._backend.get_cached_btype("new_primitive_type",
                                                     "char")):
                                                   BItem, length)
         #
         if isinstance(typenode, pycparser.c_ast.PtrDecl):
+            # pointer type
             return self._get_btype_pointer(typenode.type)
         #
         if isinstance(typenode, pycparser.c_ast.TypeDecl):
                     '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(typenode, pycparser.c_ast.FuncDecl):
+            # a function type
+            params = list(typenode.args.params)
+            ellipsis = (len(params) > 0 and
+                        isinstance(params[-1], pycparser.c_ast.EllipsisParam))
+            if ellipsis:
+                params.pop()
+            args = [self._get_btype(argdeclnode.type,
+                                    convert_array_to_pointer=True)
+                    for argdeclnode in params]
+            result = self._get_btype(typenode.type)
+            return self._backend.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):
             key = 'function ' + name
             if key in ffi._declarations:
                 node = ffi._declarations[key]
-                name = node.type.declname
-                params = list(node.args.params)
-                ellipsis = (len(params) > 0 and isinstance(params[-1],
-                                              pycparser.c_ast.EllipsisParam))
-                if ellipsis:
-                    params.pop()
-                args = [ffi._get_btype(argdeclnode.type,
-                                       convert_array_to_pointer=True)
-                        for argdeclnode in params]
-                result = ffi._get_btype(node.type)
-                value = backendlib.load_function(name, args, result,
-                                                 varargs=ellipsis)
+                assert name == node.type.declname
+                BType = ffi._get_btype(node)
+                value = backendlib.load_function(BType, name)
                 function_cache[name] = value
                 return value
             #

File ffi/backend_ctypes.py

                                          'union', ctypes.Union,
                                          initializer)
 
+    def new_function_type(self, BArgs, BResult, has_varargs):
+        nameargs = [BArg._get_c_name() for BArg in BArgs]
+        if has_varargs:
+            nameargs.append('...')
+        nameargs = ', '.join(nameargs)
+        ctypes_stuff = [BArg._ctype for BArg in BArgs]
+        #self.void_type = self.backend.get_cached_btype('new_void_type')
+        ctypes_stuff.insert(0, BResult._ctype)
+        #
+        class CTypesFunction(CTypesData):
+            _ctype = ctypes.CFUNCTYPE(*ctypes_stuff)
+            _reftypename = '%s(* &)(%s)' % (BResult._get_c_name(), nameargs)
+            _name = None
+
+            def __init__(self, init):
+                if init is None:
+                    self._ctypes_func = CTypesFunction._ctype(0)
+                elif isinstance(init, CTypesFunction):
+                    self._ctypes_func = init._ctypes_func
+                elif callable(init):
+                    # create a callback to the Python callable init()
+                    self._ctypes_func = CTypesFunction._ctype(init)
+                else:
+                    raise TypeError("argument must be a callable object")
+                self._address = ctypes.cast(self._ctypes_func,
+                                            ctypes.c_void_p).value or 0
+
+            def __nonzero__(self):
+                return self._address
+
+            def __eq__(self, other):
+                return ((isinstance(other, CTypesFunction) and
+                         self._address == other._address)
+                        or (self._address == 0 and other is None))
+
+            def __ne__(self, other):
+                return not self.__eq__(other)
+
+            def __repr__(self):
+                name = self._name
+                if name:
+                    i = self._reftypename.index('(* &)')
+                    if self._reftypename[i-1] not in ' )*':
+                        name = ' ' + name
+                    name = self._reftypename.replace('(* &)', name)
+                else:
+                    name = self._get_c_name()
+                return '<cfunc %r>' % (name,)
+
+            @staticmethod
+            def _from_ctypes(c_func):
+                if not c_func:
+                    return None
+                self = CTypesFunction.__new__(CTypesFunction)
+                self._address = ctypes.addressof(c_func)
+                self._ctypes_func = ctypes.cast(c_func, CTypesFunction._ctype)
+                return self
+
+            def __call__(self, *args):
+                if has_varargs:
+                    assert len(args) >= len(BArgs)
+                    extraargs = args[len(BArgs):]
+                    args = args[:len(BArgs)]
+                else:
+                    assert len(args) == len(BArgs)
+                ctypes_args = []
+                for arg, BArg in zip(args, BArgs):
+                    ctypes_args.append(BArg._arg_to_ctypes(arg))
+                if has_varargs:
+                    for i, arg in enumerate(extraargs):
+                        if not isinstance(arg, CTypesData):
+                            raise TypeError("argument %d needs to be a cdata" %
+                                            (1 + len(BArgs) + i,))
+                        ctypes_args.append(arg._arg_to_ctypes(arg))
+                result = self._ctypes_func(*ctypes_args)
+                return BResult._from_ctypes(result)
+        #
+        CTypesFunction._fix_class()
+        return CTypesFunction
+
 
 class CTypesLibrary(object):
 
     def __init__(self, backend, cdll):
         self.backend = backend
         self.cdll = cdll
-        self.void_type = self.backend.get_cached_btype('new_void_type')
 
-    def load_function(self, name, BArgs, BResult, varargs=False):
+    def load_function(self, BType, name):
         c_func = getattr(self.cdll, name)
-        if BResult is self.void_type:
-            c_func.restype = None
-        else:
-            c_func.restype = BResult._ctype
-        #
-        def call(*args):
-            if varargs:
-                assert len(args) >= len(BArgs)
-                extraargs = args[len(BArgs):]
-                args = args[:len(BArgs)]
-            else:
-                assert len(args) == len(BArgs)
-            ctypes_args = []
-            for arg, BArg in zip(args, BArgs):
-                ctypes_args.append(BArg._arg_to_ctypes(arg))
-            if varargs:
-                for i, arg in enumerate(extraargs):
-                    if not isinstance(arg, CTypesData):
-                        raise TypeError("argument %d needs to be a cdata" %
-                                        (1 + len(BArgs) + i,))
-                    ctypes_args.append(arg._arg_to_ctypes(arg))
-            result = c_func(*ctypes_args)
-            return BResult._from_ctypes(result)
-        #
-        call.func_name = name
-        return call
+        funcobj = BType._from_ctypes(c_func)
+        funcobj._name = name
+        return funcobj
 
 
 def _identity(x):

File testing/backend_tests.py

         a = ffi.new("int[]", [10, 11, 12])
         p = ffi.new("void *", a)
         py.test.raises(TypeError, "p[0]")
+
+    def test_functionptr_simple(self):
+        ffi = FFI(backend=self.Backend())
+        p = ffi.new("int(*)(int)")
+        assert not p
+        p = ffi.new("int(*)(int)", lambda n: n + 1)
+        res = p(41)
+        assert res == 42 and type(res) is int
+        res = p(ffi.new("int", -41))
+        assert res == -40 and type(res) is int

File testing/test_math.py

        int puts(const char *);
        int fflush(void *);
     """)
+    ffi.C.puts   # fetch before capturing, for easier debugging
     with FdWriteCapture() as fd:
         ffi.C.puts("hello")
         ffi.C.puts("  world")
     e = py.test.raises(TypeError, ffi.C.printf, "hello %d\n", 42)
     assert str(e.value) == 'argument 2 needs to be a cdata'
 
+def test_function_has_a_c_type():
+    ffi = FFI()
+    ffi.cdef("""
+        int puts(const char *);
+    """)
+    fptr = ffi.C.puts
+    assert type(fptr) == ffi.typeof("int(*)(const char*)")
+
 def test_function_pointer():
     py.test.skip("in-progress")
     ffi = FFI()

File testing/test_parsing.py

         assert name in [Ellipsis, "foobar"]
         return FakeLibrary()
 
+    def new_function_type(self, args, result, has_varargs):
+        return '<func (%s), %s, %s>' % (', '.join(args), result, has_varargs)
+
     def new_primitive_type(self, name):
         return '<%s>' % name
 
 
 class FakeLibrary(object):
     
-    def load_function(self, name, args, result, varargs):
-        return FakeFunction(name, args, result, varargs)
+    def load_function(self, BType, name):
+        return FakeFunction(BType, name)
 
 class FakeFunction(object):
 
-    def __init__(self, name, args, result, varargs):
+    def __init__(self, BType, name):
+        self.BType = BType
         self.name = name
-        self.args = args
-        self.result = result
-        self.varargs = varargs
 
 
 def test_simple():
     m = ffi.load("foobar")
     func = m.sin    # should be a callable on real backends
     assert func.name == 'sin'
-    assert func.args == ['<double>']
-    assert func.result == '<double>'
-    assert func.varargs is False
+    assert func.BType == '<func (<double>), <double>, False>'
 
 def test_pipe():
     ffi = FFI(backend=FakeBackend())
     ffi.cdef("int pipe(int pipefd[2]);")
     func = ffi.C.pipe
     assert func.name == 'pipe'
-    assert func.args == ['<pointer to <int>>']
-    assert func.result == '<int>'
-    assert func.varargs is False
+    assert func.BType == '<func (<pointer to <int>>), <int>, False>'
 
 def test_vararg():
     ffi = FFI(backend=FakeBackend())
     ffi.cdef("short foo(int, ...);")
     func = ffi.C.foo
     assert func.name == 'foo'
-    assert func.args == ['<int>']
-    assert func.result == '<short>'
-    assert func.varargs is True
+    assert func.BType == '<func (<int>), <short>, True>'