[pypy-commit] cffi cpy-extension: Fixed the docstring of ffi.verify() to document the new idea.

arigo noreply at buildbot.pypy.org
Tue Jun 12 14:43:13 CEST 2012


Author: Armin Rigo <arigo at tunes.org>
Branch: cpy-extension
Changeset: r285:b33ba96da706
Date: 2012-06-12 14:42 +0200
http://bitbucket.org/cffi/cffi/changeset/b33ba96da706/

Log:	Fixed the docstring of ffi.verify() to document the new idea.
	Starting on generating the CPython C extension.

diff --git a/cffi/api.py b/cffi/api.py
--- a/cffi/api.py
+++ b/cffi/api.py
@@ -22,7 +22,12 @@
         ffi.cdef("""
             int printf(const char *, ...);
         """)
-        ffi.C.printf("hello, %s!\n", ffi.new("char[]", "world"))
+
+        C = ffi.rawload(name=None)   # standard library
+        -or-
+        C = ffi.verify()  # use a C compiler: verify the decl above is right
+
+        C.printf("hello, %s!\n", ffi.new("char[]", "world"))
     '''
 
     def __init__(self, backend=None):
@@ -46,7 +51,6 @@
         self._new_types = new.module('new_types').__dict__
         if hasattr(backend, 'set_ffi'):
             backend.set_ffi(self)
-        self.C = _make_ffi_library(self, None)
         #
         lines = []
         by_size = {}
@@ -64,19 +68,19 @@
     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
+        then be accessed via 'ffi.rawload()'.  The types can be used
         in 'ffi.new()' and other functions.
         """
         self._parser.parse(csource)
 
-    def load(self, name):
+    def rawload(self, name):
         """Load and return a dynamic library identified by 'name'.
-        The standard C library is preloaded into 'ffi.C'.
+        The standard C library can be loaded by passing None.
         Note that functions and types declared by 'ffi.cdef()' are not
         linked to a particular library, just like C headers; in the
         library we only look for the actual (untyped) symbols.
         """
-        assert isinstance(name, str)
+        assert isinstance(name, str) or name is None
         return _make_ffi_library(self, name)
 
     def typeof(self, cdecl):
@@ -181,7 +185,13 @@
         return BType
 
     def verify(self, preamble='', **kwargs):
-        """ Verify that the current ffi signatures compile on this machine
+        """Verify that the current ffi signatures compile on this
+        machine, and return a dynamic library object.  The dynamic
+        library can be used to call functions and access global
+        variables declared in this 'ffi'.  The library is compiled
+        by the C compiler: it gives you C-level API compatibility
+        (including calling macros).  This is unlike 'ffi.rawload()',
+        which requires binary compatibility in the signatures.
         """
         from .verifier import Verifier
         return Verifier(self).verify(preamble, **kwargs)
diff --git a/cffi/ffiplatform.py b/cffi/ffiplatform.py
--- a/cffi/ffiplatform.py
+++ b/cffi/ffiplatform.py
@@ -10,15 +10,22 @@
     cdef, but no verification has been done
     """
 
-test_file_counter = 0
+_file_counter = 0
+_tmpdir = None
 
-def _get_test_file_base():
+def undercffi_module_name():
+    global _file_counter
+    modname = '_cffi_%d' % _file_counter
+    _file_counter += 1
+    return modname
+
+def tmpdir():
     # for now, living in the __pycache__ subdirectory
-    global test_file_counter
-    try:
-        os.mkdir('__pycache__')
-    except OSError:
-        pass
-    tst_file_base = '__pycache__/test%d' % test_file_counter
-    test_file_counter += 1
-    return tst_file_base
+    global _tmpdir
+    if _tmpdir is None:
+        try:
+            os.mkdir('__pycache__')
+        except OSError:
+            pass
+        _tmpdir = os.path.abspath('__pycache__')
+    return _tmpdir
diff --git a/cffi/model.py b/cffi/model.py
--- a/cffi/model.py
+++ b/cffi/model.py
@@ -26,9 +26,8 @@
         except KeyError:
             return self.new_backend_type(ffi, *args)
 
-    def verifier_declare_typedef(self, verifier, name):
-        verifier.write('__sametype__(%s, %s)' % (
-            self.get_c_name('** result'), name))
+    #def generate_cpy_typedef(self, verifier, name, step):
+    #    XXX
 
 
 class VoidType(BaseType):
@@ -79,9 +78,58 @@
     def new_backend_type(self, ffi, result, *args):
         return ffi._backend.new_function_type(args, result, self.ellipsis)
 
-    def verifier_declare_function(self, verifier, name):
-        verifier.write('{ %s = %s; }' % (
-            self.get_c_name('result'), name))
+    def generate_cpy_function_decl(self, verifier, name):
+        prnt = verifier.prnt
+        numargs = len(self.args)
+        if numargs == 0:
+            argname = 'no_arg'
+        elif numargs == 1:
+            argname = 'arg0'
+        else:
+            argname = 'args'
+        prnt('static PyObject *_cffi_f_%s(PyObject *self, PyObject *%s)' %
+             (name, argname))
+        prnt('{')
+        assert not self.ellipsis  # XXX later
+        #
+        for i in range(len(self.args)):
+            prnt('  double x%d;' % i)
+        prnt('  double result;')
+        #
+        if len(self.args) > 1:
+            rng = range(len(self.args))
+            for i in rng:
+                prnt('  PyObject *arg%d;' % i)
+            prnt()
+            prnt('  if (!PyArg_ParseTuple("%s:%s", %s)) {' % (
+                'O' * numargs, name, ', '.join(['&arg%d' % i for i in rng])))
+            prnt('    return NULL;')
+        prnt()
+        #
+        for i in range(len(self.args)):
+            prnt('  x%d = PyFloat_AsDouble(arg%d);' % (i, i))
+            prnt('  if (x%d == -1.0 && PyErr_Occurred())' % i)
+            prnt('    return NULL;')
+            prnt()
+        #
+        prnt('  { result = %s(%s); }' % (
+            name, ', '.join(['x%d' % i for i in range(len(self.args))])))
+        prnt()
+        #
+        prnt('  return PyFloat_FromDouble(result);')
+        prnt('}')
+        prnt()
+
+    def generate_cpy_function_method(self, verifier, name):
+        numargs = len(self.args)
+        if numargs == 0:
+            meth = 'METH_NOARGS'
+        elif numargs == 1:
+            meth = 'METH_O'
+        else:
+            meth = 'METH_VARARGS'
+        verifier.prnt('  {"%s", _cffi_f_%s, %s},' % (name, name, meth))
+
 
 
 class PointerType(BaseType):
diff --git a/cffi/verifier.py b/cffi/verifier.py
--- a/cffi/verifier.py
+++ b/cffi/verifier.py
@@ -6,47 +6,62 @@
     def __init__(self, ffi):
         self.ffi = ffi
 
-    def write(self, what):
-        print >> self.f, '  ' + what
+    def prnt(self, what=''):
+        print >> self.f, what
 
-    def write_printf(self, what, *args):
-        self.has_printf = True
-        if not args:
-            print >> self.f, '  printf("%s\\n");' % (what,)
-        else:
-            print >> self.f, '  printf("%s\\n", %s);' % (
-                what, ', '.join(args))
+##    def write_printf(self, what, *args):
+##        if not args:
+##            print >> self.f, '  printf("%s\\n");' % (what,)
+##        else:
+##            print >> self.f, '  printf("%s\\n", %s);' % (
+##                what, ', '.join(args))
+
+    def generate(self, step_name):
+        for name, tp in self.ffi._parser._declarations.iteritems():
+            kind, realname = name.split(' ', 1)
+            method = getattr(tp, 'generate_cpy_%s_%s' % (kind, step_name), 0)
+            if method:
+                method(self, realname)
 
     def verify(self, preamble, **kwargs):
-        tst_file_base = ffiplatform._get_test_file_base()
-        self.has_printf = False
-        with open(tst_file_base + '.c', 'w') as f:
-            f.write("""\
-#include <stdio.h>
-#include <stdint.h>
-#include <stddef.h>
-#include <unistd.h>
+        modname = ffiplatform.undercffi_module_name()
+        filebase = os.path.join(ffiplatform.tmpdir(), modname)
+        
+        with open(filebase + 'module.c', 'w') as f:
+            self.f = f
+            self.prnt("#include <Python.h>")
+            self.prnt()
+            self.prnt(preamble)
+            self.prnt()
+            #
+            self.generate("decl")
+            #
+            self.prnt('static PyMethodDef _cffi_methods[] = {')
+            self.generate("method")
+            self.prnt('  {NULL, NULL}    /* Sentinel */')
+            self.prnt('};')
+            self.prnt()
+            #
+            self.prnt('void init%s()' % modname)
+            self.prnt('{')
+            self.prnt('  Py_InitModule("%s", _cffi_methods);' % modname)
+            self.prnt('  if (PyErr_Occurred()) return;')
+            self.generate("init")
+            self.prnt('}')
+            #
+            del self.f
 
-#define __sameconstant__(x, y) \\
-  { int result[1-2*((x)-(y))*((x)-(y))]; }
-
-#define __sametype__(ppresult, typename) \\
-  { ppresult = (typename**)0; }
-
-""")
-            f.write(preamble + "\n\n")
-            f.write('int main() {\n')
-            self.f = f
-            for name, tp in self.ffi._parser._declarations.iteritems():
-                kind, realname = name.split(' ', 1)
-                method = getattr(tp, 'verifier_declare_' + kind)
-                method(self, realname)
-            del self.f
-            f.write('  return 0;\n')
-            f.write('}\n')
-        err = os.system('gcc -Werror -S %s.c -o %s.s' %
-                        (tst_file_base, tst_file_base))
+        # XXX use more distutils?
+        import distutils.sysconfig
+        python_h = distutils.sysconfig.get_python_inc()
+        err = os.system("gcc -I'%s' -O2 -shared %smodule.c -o %s.so" %
+                        (python_h, filebase, filebase))
         if err:
             raise ffiplatform.VerificationError(
-                '%s.c: see compilation warnings and errors above' %
-                (tst_file_base,))
+                '%smodule.c: see compilation errors above' % (filebase,))
+        #
+        import imp
+        try:
+            return imp.load_dynamic(modname, '%s.so' % filebase)
+        except ImportError, e:
+            raise ffiplatform.VerificationError(str(e))
diff --git a/testing/test_verify.py b/testing/test_verify.py
--- a/testing/test_verify.py
+++ b/testing/test_verify.py
@@ -1,27 +1,46 @@
 import py
+import math
 from cffi import FFI, VerificationError, VerificationMissing
 
 
-def test_simple_verify():
+def test_missing_function():
     ffi = FFI()
     ffi.cdef("void some_completely_unknown_function();")
     py.test.raises(VerificationError, ffi.verify)
+
+def test_simple_case():
     ffi = FFI()
     ffi.cdef("double sin(double x);")
-    # omission of math.h
-    py.test.raises(VerificationError, ffi.verify)
-    assert ffi.verify('#include <math.h>') is None
-    #
+    lib = ffi.verify('#include <math.h>')
+    assert lib.sin(1.23) == math.sin(1.23)
+
+def test_rounding_1():
     ffi = FFI()
     ffi.cdef("float sin(double x);")
-    py.test.raises(VerificationError, ffi.verify, '#include <math.h>')
+    lib = ffi.verify('#include <math.h>')
+    res = lib.sin(1.23)
+    assert res != math.sin(1.23)     # not exact, because of double->float
+    assert abs(res - math.sin(1.23)) < 1E-5
+
+def test_rounding_2():
     ffi = FFI()
     ffi.cdef("double sin(float x);")
-    py.test.raises(VerificationError, ffi.verify, '#include <math.h>')
-    #
+    lib = ffi.verify('#include <math.h>')
+    res = lib.sin(1.23)
+    assert res != math.sin(1.23)     # not exact, because of double->float
+    assert abs(res - math.sin(1.23)) < 1E-5
+
+def test_strlen_exact():
     ffi = FFI()
     ffi.cdef("size_t strlen(const char *s);")
-    ffi.verify("#include <string.h>")
+    lib = ffi.verify("#include <string.h>")
+    assert lib.strlen("hi there!") == 9
+
+def test_strlen_approximate():
+    ffi = FFI()
+    ffi.cdef("int strlen(char *s);")
+    lib = ffi.verify("#include <string.h>")
+    assert lib.strlen("hi there!") == 9
 
 
 def test_verify_typedefs():


More information about the pypy-commit mailing list