support casting '<bytes>int_val' and coercing integers to bytes on assignment
authorStefan Behnel <scoder@users.berlios.de>
Tue, 27 Apr 2010 05:34:45 +0000 (07:34 +0200)
committerStefan Behnel <scoder@users.berlios.de>
Tue, 27 Apr 2010 05:34:45 +0000 (07:34 +0200)
Cython/Compiler/ExprNodes.py
tests/run/bytes_char_coercion.pyx [new file with mode: 0644]

index 27ace4837b074e033c8bd7c0cfc0de2bab63c071..9631ad6ab2367ac131b20ab1c9c3224be0fc4a12 100755 (executable)
@@ -554,7 +554,10 @@ class ExprNode(Node):
 
         if dst_type.is_pyobject:
             if not src.type.is_pyobject:
-                src = CoerceToPyTypeNode(src, env)
+                if dst_type is bytes_type and src.type.is_int:
+                    src = CoerceIntToBytesNode(src, env)
+                else:
+                    src = CoerceToPyTypeNode(src, env)
             if not src.type.subtype_of(dst_type):
                 if not isinstance(src, NoneNode):
                     src = PyTypeTestNode(src, dst_type, env)
@@ -4555,7 +4558,12 @@ class TypecastNode(ExprNode):
         if from_py and not to_py and self.operand.is_ephemeral() and not self.type.is_numeric:
             error(self.pos, "Casting temporary Python object to non-numeric non-Python type")
         if to_py and not from_py:
-            if self.operand.type.can_coerce_to_pyobject(env):
+            if self.type is bytes_type and self.operand.type.is_int:
+                # FIXME: the type cast node isn't needed in this case
+                # and can be dropped once analyse_types() can return a
+                # different node
+                self.operand = CoerceIntToBytesNode(self.operand, env)
+            elif self.operand.type.can_coerce_to_pyobject(env):
                 self.result_ctype = py_object_type
                 self.operand = self.operand.coerce_to_pyobject(env)
             else:
@@ -5614,10 +5622,8 @@ class CmpNode(object):
 
     def is_c_string_contains(self):
         return self.operator in ('in', 'not_in') and \
-               ((self.operand1.type in (PyrexTypes.c_char_type, PyrexTypes.c_uchar_type)
-                 and self.operand2.type in (PyrexTypes.c_char_ptr_type,
-                                            PyrexTypes.c_uchar_ptr_type,
-                                            bytes_type)) or
+               ((self.operand1.type.is_int
+                 and (self.operand2.type.is_string or self.operand2.type is bytes_type)) or
                 (self.operand1.type is PyrexTypes.c_py_unicode_type
                  and self.operand2.type is unicode_type))
 
@@ -6181,7 +6187,7 @@ class CoerceToPyTypeNode(CoercionNode):
         CoercionNode.__init__(self, arg)
         if not arg.type.create_to_py_utility_code(env):
             error(arg.pos,
-                "Cannot convert '%s' to Python object" % arg.type)
+                  "Cannot convert '%s' to Python object" % arg.type)
         if type is not py_object_type:
             self.type = py_object_type
         elif arg.type.is_string:
@@ -6217,6 +6223,46 @@ class CoerceToPyTypeNode(CoercionNode):
         code.put_gotref(self.py_result())
 
 
+class CoerceIntToBytesNode(CoerceToPyTypeNode):
+    #  This node is used to convert a C int type to a Python bytes
+    #  object.
+
+    is_temp = 1
+
+    def __init__(self, arg, env):
+        arg = arg.coerce_to_simple(env)
+        CoercionNode.__init__(self, arg)
+        self.type = Builtin.bytes_type
+
+    def generate_result_code(self, code):
+        arg = self.arg
+        arg_result = arg.result()
+        if arg.type not in (PyrexTypes.c_char_type,
+                            PyrexTypes.c_uchar_type,
+                            PyrexTypes.c_schar_type):
+            if arg.type.signed:
+                code.putln("if ((%s < 0) || (%s > 255)) {" % (
+                    arg_result, arg_result))
+            else:
+                code.putln("if (%s > 255) {" % arg_result)
+            code.putln('PyErr_Format(PyExc_OverflowError, '
+                       '"value too large to pack into a byte"); %s' % (
+                           code.error_goto(self.pos)))
+            code.putln('}')
+        temp = None
+        if arg.type is not PyrexTypes.c_char_type:
+            temp = code.funcstate.allocate_temp(PyrexTypes.c_char_type, manage_ref=False)
+            code.putln("%s = (char)%s;" % (temp, arg_result))
+            arg_result = temp
+        code.putln('%s = PyBytes_FromStringAndSize(&%s, 1); %s' % (
+            self.result(),
+            arg_result,
+            code.error_goto_if_null(self.result(), self.pos)))
+        if temp is not None:
+            code.funcstate.release_temp(temp)
+        code.put_gotref(self.py_result())
+
+
 class CoerceFromPyTypeNode(CoercionNode):
     #  This node is used to convert a Python object
     #  to a C data type.
diff --git a/tests/run/bytes_char_coercion.pyx b/tests/run/bytes_char_coercion.pyx
new file mode 100644 (file)
index 0000000..82c63e7
--- /dev/null
@@ -0,0 +1,154 @@
+
+cimport cython
+
+def coerce_char_default(char c):
+    """
+    Default char -> int coercion
+
+    >>> coerce_char_default(ord('A')) == ord('A')
+    True
+    """
+    return c
+
+
+def coerce_uchar_default(unsigned char c):
+    """
+    Default char -> int coercion
+
+    >>> coerce_uchar_default(ord('A')) == ord('A')
+    True
+    """
+    return c
+
+
+@cython.test_assert_path_exists("//CoerceIntToBytesNode")
+@cython.test_fail_if_path_exists("//CoerceToPyTypeNode")
+def coerce_char_bytes_cast(char c):
+    """
+    Explicit char -> bytes coercion
+
+    >>> coerce_char_bytes_cast(ord('A')) == 'A'.encode('ASCII')
+    True
+    """
+    return <bytes>c
+
+
+@cython.test_assert_path_exists("//CoerceIntToBytesNode")
+@cython.test_fail_if_path_exists("//CoerceToPyTypeNode")
+def coerce_uchar_bytes_cast(unsigned char c):
+    """
+    Explicit uchar -> bytes coercion
+
+    >>> coerce_uchar_bytes_cast(ord('A')) == 'A'.encode('ASCII')
+    True
+    >>> b = coerce_uchar_bytes_cast(ord('\\xff'))
+    >>> b == '\\xff' or b == '\\xff'.encode('ISO-8859-1') # Py2 or Py3
+    True
+    """
+    return <bytes>c
+
+
+@cython.test_assert_path_exists("//CoerceIntToBytesNode")
+@cython.test_fail_if_path_exists("//CoerceToPyTypeNode")
+def coerce_int_bytes_cast(int c):
+    """
+    Explicit int -> bytes coercion
+
+    >>> coerce_int_bytes_cast(ord('A')) == 'A'.encode('ASCII')
+    True
+    >>> coerce_int_bytes_cast(ord('A') + 0x100)
+    Traceback (most recent call last):
+    OverflowError: value too large to pack into a byte
+    >>> coerce_int_bytes_cast(ord('A') - 0x100)
+    Traceback (most recent call last):
+    OverflowError: value too large to pack into a byte
+    """
+    return <bytes>c
+
+
+@cython.test_assert_path_exists("//CoerceIntToBytesNode")
+@cython.test_fail_if_path_exists("//CoerceToPyTypeNode")
+def coerce_uint_bytes_cast(unsigned int c):
+    """
+    Explicit uint -> bytes coercion
+
+    >>> coerce_uint_bytes_cast(ord('A')) == 'A'.encode('ASCII')
+    True
+    >>> b = coerce_uint_bytes_cast(ord('\\xff'))
+    >>> b == '\\xff' or b == '\\xff'.encode('ISO-8859-1') # Py2 or Py3
+    True
+
+    >>> coerce_uint_bytes_cast(ord('A') + 0x100)
+    Traceback (most recent call last):
+    OverflowError: value too large to pack into a byte
+    """
+    return <bytes>c
+
+
+@cython.test_assert_path_exists("//CoerceIntToBytesNode")
+@cython.test_fail_if_path_exists("//CoerceToPyTypeNode")
+def coerce_char_bytes_assign(char c):
+    """
+    Implicit char -> bytes coercion in assignments
+
+    >>> coerce_char_bytes_assign(ord('A')) == 'A'.encode('ASCII')
+    True
+    """
+    cdef bytes s = c
+    return s
+
+
+@cython.test_assert_path_exists("//CoerceIntToBytesNode")
+@cython.test_fail_if_path_exists("//CoerceToPyTypeNode")
+def coerce_uchar_bytes_assign(unsigned char c):
+    """
+    Implicit uchar -> bytes coercion in assignments
+
+    >>> coerce_uchar_bytes_assign(ord('A')) == 'A'.encode('ASCII')
+    True
+    >>> b = coerce_uchar_bytes_assign(ord('\\xff'))
+    >>> b == '\\xff' or b == '\\xff'.encode('ISO-8859-1') # Py2 or Py3
+    True
+    """
+    cdef bytes s = c
+    return s
+
+
+@cython.test_assert_path_exists("//CoerceIntToBytesNode")
+@cython.test_fail_if_path_exists("//CoerceToPyTypeNode")
+def coerce_int_bytes_assign(int c):
+    """
+    Implicit int -> bytes coercion in assignments
+
+    >>> coerce_int_bytes_assign(ord('A')) == 'A'.encode('ASCII')
+    True
+
+    >>> coerce_int_bytes_assign(ord('A') + 0x100)
+    Traceback (most recent call last):
+    OverflowError: value too large to pack into a byte
+    >>> coerce_int_bytes_assign(ord('A') - 0x100)
+    Traceback (most recent call last):
+    OverflowError: value too large to pack into a byte
+    """
+    cdef bytes s = c
+    return s
+
+
+@cython.test_assert_path_exists("//CoerceIntToBytesNode")
+@cython.test_fail_if_path_exists("//CoerceToPyTypeNode")
+def coerce_uint_bytes_assign(unsigned int c):
+    """
+    Implicit uint -> bytes coercion in assignments
+
+    >>> coerce_uint_bytes_assign(ord('A')) == 'A'.encode('ASCII')
+    True
+    >>> b = coerce_uint_bytes_assign(ord('\\xff'))
+    >>> b == '\\xff' or b == '\\xff'.encode('ISO-8859-1') # Py2 or Py3
+    True
+
+    >>> coerce_uint_bytes_assign(ord('A') + 0x100)
+    Traceback (most recent call last):
+    OverflowError: value too large to pack into a byte
+    """
+    cdef bytes s = c
+    return s