support for new buffer protocol in Py3
authorStefan Behnel <scoder@users.berlios.de>
Fri, 23 May 2008 11:24:57 +0000 (13:24 +0200)
committerStefan Behnel <scoder@users.berlios.de>
Fri, 23 May 2008 11:24:57 +0000 (13:24 +0200)
Cython/Compiler/Builtin.py
Cython/Compiler/ModuleNode.py
Cython/Compiler/Parsing.py
Cython/Compiler/PyrexTypes.py
Cython/Compiler/TypeSlots.py
tests/run/buffer.pyx [new file with mode: 0644]

index e30f7c0ee3aebb9c90e6551273b7777f3afd24a5..f9c6605f37cbee2e16db093ee91be8115cb21fbe 100644 (file)
@@ -2,8 +2,9 @@
 #   Pyrex - Builtin Definitions
 #
 
-from Symtab import BuiltinScope
+from Symtab import BuiltinScope, StructOrUnionScope
 from TypeSlots import Signature
+import PyrexTypes
 
 builtin_function_table = [
     # name,        args,   return,  C API func,           py equiv = "*"
@@ -100,6 +101,21 @@ builtin_types_table = [
                                     ("values","O",   "O", "PyDict_Values")]),
 ]
 
+builtin_structs_table = [
+    ('Py_buffer', 'Py_buffer',
+     [("buf",        PyrexTypes.c_void_ptr_type),
+      ("len",        PyrexTypes.c_py_ssize_t_type),
+      ("readonly",   PyrexTypes.c_bint_type),
+      ("format",     PyrexTypes.c_char_ptr_type),
+      ("ndim",       PyrexTypes.c_int_type),
+      ("shape",      PyrexTypes.c_py_ssize_t_ptr_type),
+      ("strides",    PyrexTypes.c_py_ssize_t_ptr_type),
+      ("suboffsets", PyrexTypes.c_py_ssize_t_ptr_type),
+      ("itemsize",   PyrexTypes.c_py_ssize_t_type),
+      ("internal",   PyrexTypes.c_void_ptr_type),
+      ])
+]
+
 getattr3_utility_code = ["""
 static PyObject *__Pyx_GetAttr3(PyObject *, PyObject *, PyObject *); /*proto*/
 ""","""
@@ -151,12 +167,22 @@ def init_builtin_types():
             sig = Signature(args, ret)
             the_type.scope.declare_cfunction(name, sig.function_type(), None, cname)
 
+def init_builtin_structs():
+    for name, cname, attribute_types in builtin_structs_table:
+        scope = StructOrUnionScope(name)
+        for attribute_name, attribute_type in attribute_types:
+            scope.declare_var(
+                attribute_name, attribute_type, None, attribute_name)
+        builtin_scope.declare_struct_or_union(
+            name, "struct", scope, 1, None, cname = cname)
+
 def init_builtins():
     init_builtin_funcs()
     init_builtin_types()
+    init_builtin_structs()
     global list_type, tuple_type, dict_type
     list_type  = builtin_scope.lookup('list').type
     tuple_type = builtin_scope.lookup('tuple').type
     dict_type  = builtin_scope.lookup('dict').type
-    
+
 init_builtins()
index 276551aa7fdb8a238b3dffa81f6432ebe5f96eb5..99065b6132484dc54bc8c831836b648177a50770 100644 (file)
@@ -388,6 +388,30 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
         code.putln("  #define Py_SIZE(ob)   ((PyVarObject*)(ob))->ob_size)")
         code.putln("  #define PyVarObject_HEAD_INIT(type, size) \\")
         code.putln("          PyObject_HEAD_INIT(type) size,")
+        code.putln("")
+        code.putln("  typedef struct {")
+        code.putln("     void *buf;")
+        code.putln("     Py_ssize_t len;")
+        code.putln("     int readonly;")
+        code.putln("     const char *format;")
+        code.putln("     int ndim;")
+        code.putln("     Py_ssize_t *shape;")
+        code.putln("     Py_ssize_t *strides;")
+        code.putln("     Py_ssize_t *suboffsets;")
+        code.putln("     Py_ssize_t itemsize;")
+        code.putln("     void *internal;")
+        code.putln("  } Py_buffer;")
+        code.putln("")
+        code.putln("  #define PyBUF_SIMPLE 0")
+        code.putln("  #define PyBUF_WRITABLE 0x0001")
+        code.putln("  #define PyBUF_LOCK 0x0002")
+        code.putln("  #define PyBUF_FORMAT 0x0004")
+        code.putln("  #define PyBUF_ND 0x0008")
+        code.putln("  #define PyBUF_STRIDES (0x0010 | PyBUF_ND)")
+        code.putln("  #define PyBUF_C_CONTIGUOUS (0x0020 | PyBUF_STRIDES)")
+        code.putln("  #define PyBUF_F_CONTIGUOUS (0x0040 | PyBUF_STRIDES)")
+        code.putln("  #define PyBUF_ANY_CONTIGUOUS (0x0080 | PyBUF_STRIDES)")
+        code.putln("  #define PyBUF_INDIRECT (0x0100 | PyBUF_STRIDES)")
         code.putln("#endif")
 
         code.put(builtin_module_name_utility_code[0])
index 159c2e56c834ab360a615281c5e63e5152937450..34df8cd51c21353c89f5c2a28b8260cc9d408757 100644 (file)
@@ -2136,6 +2136,7 @@ def p_doc_string(s):
 
 def p_module(s, pxd, full_module_name):
     s.add_type_name("object")
+    s.add_type_name("Py_buffer")
     pos = s.position()
     doc = p_doc_string(s)
     if pxd:
index d82650e4f4c6a8182955a842822fa27c711e61eb..b7950da7a02d42674944f968c8f5944c043ca8a6 100644 (file)
@@ -1049,12 +1049,16 @@ c_char_array_type =   CCharArrayType(None)
 c_utf8_char_array_type =   CUTF8CharArrayType(None)
 c_char_ptr_type =     CCharPtrType()
 c_char_ptr_ptr_type = CPtrType(c_char_ptr_type)
+c_py_ssize_t_ptr_type =  CPtrType(c_py_ssize_t_type)
 c_int_ptr_type =      CPtrType(c_int_type)
 
 c_returncode_type =   CIntType(2, 1, "T_INT", is_returncode = 1)
 
 c_anon_enum_type =    CAnonEnumType(-1, 1)
 
+# the Py_buffer type is defined in Builtin.py
+c_py_buffer_ptr_type = CPtrType(CStructOrUnionType("Py_buffer", "struct", None, 1, "Py_buffer"))
+
 error_type =    ErrorType()
 
 lowest_float_rank = 6
index c697a1260d57e98ca09a8de7f93b57266f061ae2..78c59e2fc6896875efc1db792014ac7cbb54b475 100644 (file)
@@ -26,12 +26,14 @@ class Signature:
     #    'p'  void *
     #    'P'  void **
     #    'i'  int
+    #    'b'  bint
     #    'I'  int *
     #    'l'  long
     #    'Z'  Py_ssize_t
     #    's'  char *
     #    'S'  char **
     #    'r'  int used only to signal exception
+    #    'B'  Py_buffer *
     #    '-'  dummy 'self' argument (not used)
     #    '*'  rest of args passed as generic Python
     #           arg tuple and kw dict (must be last
@@ -50,6 +52,7 @@ class Signature:
         's': PyrexTypes.c_char_ptr_type,
         'S': PyrexTypes.c_char_ptr_ptr_type,
         'r': PyrexTypes.c_returncode_type,
+        'B': PyrexTypes.c_py_buffer_ptr_type,
         # 'T', '-' and '*' are handled otherwise
         # and are not looked up in here
     }
@@ -123,12 +126,14 @@ class SlotDescriptor:
     #  is_initialised_dynamically    Is initialised by code in the module init function
     #  flag                          Py_TPFLAGS_XXX value indicating presence of slot
     #  py3k                          Indicates presence of slot in Python 3
-    
-    def __init__(self, slot_name, dynamic = 0, flag = None, py3k = True):
+    #  py2                           Indicates presence of slot in Python 2
+
+    def __init__(self, slot_name, dynamic = 0, flag = None, py3k = True, py2 = True):
         self.slot_name = slot_name
         self.is_initialised_dynamically = dynamic
         self.flag = flag
         self.py3k = py3k
+        self.py2  = py2
 
     def generate(self, scope, code):
         if self.is_initialised_dynamically:
@@ -137,15 +142,18 @@ class SlotDescriptor:
             value = self.slot_code(scope)
         flag = self.flag
         py3k = self.py3k
+        py2  = self.py2
         if not py3k:
             code.putln("#if PY_MAJOR_VERSION < 3")
+        elif not py2:
+            code.putln("#if PY_MAJOR_VERSION >= 3")
         if flag:
             code.putln("#if (PY_MAJOR_VERSION >= 3) || (Py_TPFLAGS_DEFAULT & %s)" % flag)
         code.putln("%s, /*%s*/" % (value, self.slot_name))
-        if not py3k:
-            code.putln("#endif")
         if flag:
             code.putln("#endif")
+        if not py3k or not py2:
+            code.putln("#endif")
 
     # Some C implementations have trouble statically 
     # initialising a global with a pointer to an extern 
@@ -191,8 +199,8 @@ class MethodSlot(SlotDescriptor):
     #  method_name  string           The __xxx__ name of the method
     #  default      string or None   Default value of the slot
     
-    def __init__(self, signature, slot_name, method_name, default = None, flag = None, py3k=True):
-        SlotDescriptor.__init__(self, slot_name, flag = flag, py3k = py3k)
+    def __init__(self, signature, slot_name, method_name, default = None, flag = None, py3k=True, py2=True):
+        SlotDescriptor.__init__(self, slot_name, flag = flag, py3k = py3k, py2=py2)
         self.signature = signature
         self.slot_name = slot_name
         self.method_name = method_name
@@ -213,8 +221,8 @@ class InternalMethodSlot(SlotDescriptor):
     #
     #  slot_name    string           Member name of the slot in the type object
 
-    def __init__(self, slot_name):
-        SlotDescriptor.__init__(self, slot_name)
+    def __init__(self, slot_name, py2 = True):
+        SlotDescriptor.__init__(self, slot_name, py2 = py2)
 
     def slot_code(self, scope):
         return scope.mangle_internal(self.slot_name)
@@ -272,8 +280,8 @@ class SyntheticSlot(InternalMethodSlot):
     #  alternative default value will be placed in the type
     #  slot.
     
-    def __init__(self, slot_name, user_methods, default_value):
-        InternalMethodSlot.__init__(self, slot_name)
+    def __init__(self, slot_name, user_methods, default_value, py2 = True):
+        InternalMethodSlot.__init__(self, slot_name, py2 = py2)
         self.user_methods = user_methods
         self.default_value = default_value
     
@@ -504,6 +512,10 @@ initproc = Signature("T*", 'r')            # typedef int (*initproc)(PyObject *,
                                            # typedef PyObject *(*newfunc)(struct _typeobject *, PyObject *, PyObject *);
                                            # typedef PyObject *(*allocfunc)(struct _typeobject *, int);
 
+getbufferproc = Signature("TBi", "r")      # typedef int (*getbufferproc)(PyObject *, Py_buffer *, int);
+releasebufferproc = Signature("TB", "v")   # typedef void (*releasebufferproc)(PyObject *, Py_buffer *);
+
+
 #------------------------------------------------------------------------------------------
 #
 #  Signatures for accessor methods of properties.
@@ -596,6 +608,9 @@ PyBufferProcs = (
     MethodSlot(getwritebufferproc, "bf_getwritebuffer", "__getwritebuffer__", py3k = False),
     MethodSlot(getsegcountproc, "bf_getsegcount", "__getsegcount__", py3k = False),
     MethodSlot(getcharbufferproc, "bf_getcharbuffer", "__getcharbuffer__", py3k = False),
+
+    MethodSlot(getbufferproc, "bf_getbuffer", "__getbuffer__", flag = "Py_TPFLAGS_HAVE_NEWBUFFER"),
+    MethodSlot(releasebufferproc, "bf_releasebuffer", "__releasebuffer__", flag = "Py_TPFLAGS_HAVE_NEWBUFFER"),
 )
 
 #------------------------------------------------------------------------------------------
diff --git a/tests/run/buffer.pyx b/tests/run/buffer.pyx
new file mode 100644 (file)
index 0000000..be93bfa
--- /dev/null
@@ -0,0 +1,52 @@
+__doc__ = u"""
+>>> b1 = TestBuffer()
+>>> b2 = TestBufferRelease()
+"""
+
+import sys
+if sys.version_info[0] >= 3:
+    __doc__ += u"""
+>>> ms = memoryview(s)
+>>> ms.tobytes()
+bytearray(b'abcdefg')
+
+>>> m1 = memoryview(b1)
+>>> m1.tobytes()
+locking!
+bytearray(b'abcdefg')
+
+>>> m2 = memoryview(b2)
+>>> m2.tobytes()
+locking!
+unlocking!
+bytearray(b'abcdefg')
+
+>>> del m1
+>>> del m2
+releasing!
+"""
+
+s = "abcdefg"
+
+cdef class TestBuffer:
+    def __getbuffer__(self, Py_buffer* buffer, int flags):
+        if buffer is NULL:
+            print u"locking!"
+            return
+        buffer.buf = <char*>s
+        buffer.len = len(s)
+        buffer.readonly = 0
+        buffer.format = "B"
+        buffer.ndim = 0
+        buffer.shape = NULL
+        buffer.strides = NULL
+        buffer.suboffsets = NULL
+        buffer.itemsize = 1
+        buffer.internal = NULL
+
+cdef class TestBufferRelease(TestBuffer):
+    def __releasebuffer__(self, Py_buffer* buffer):
+        if buffer is NULL:
+            print u"unlocking!"
+        else:
+            print u"releasing!"