[pypy-commit] pypy py3.5: Copy PyBytes_FromFormat from CPython 3.5 (c3da1ee47e6b)

rlamy pypy.commits at gmail.com
Tue Dec 13 19:17:27 EST 2016


Author: Ronan Lamy <ronan.lamy at gmail.com>
Branch: py3.5
Changeset: r89053:f52e625db5b2
Date: 2016-12-14 00:16 +0000
http://bitbucket.org/pypy/pypy/changeset/f52e625db5b2/

Log:	Copy PyBytes_FromFormat from CPython 3.5 (c3da1ee47e6b)

diff --git a/pypy/module/cpyext/api.py b/pypy/module/cpyext/api.py
--- a/pypy/module/cpyext/api.py
+++ b/pypy/module/cpyext/api.py
@@ -1346,6 +1346,7 @@
                          source_dir / "pythread.c",
                          source_dir / "missing.c",
                          source_dir / "pymem.c",
+                         source_dir / "bytesobject.c",
                          ]
 
 def build_eci(building_bridge, export_symbols, code, use_micronumpy=False):
diff --git a/pypy/module/cpyext/include/bytesobject.h b/pypy/module/cpyext/include/bytesobject.h
--- a/pypy/module/cpyext/include/bytesobject.h
+++ b/pypy/module/cpyext/include/bytesobject.h
@@ -56,6 +56,9 @@
 #define PyString_CHECK_INTERNED(op) (((PyStringObject *)(op))->ob_sstate)
 
 
+PyAPI_FUNC(PyObject *) PyBytes_FromFormatV(const char*, va_list);
+PyAPI_FUNC(PyObject *) PyBytes_FromFormat(const char*, ...);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/pypy/module/cpyext/src/bytesobject.c b/pypy/module/cpyext/src/bytesobject.c
new file mode 100644
--- /dev/null
+++ b/pypy/module/cpyext/src/bytesobject.c
@@ -0,0 +1,213 @@
+#include "Python.h"
+
+#if defined(Py_ISDIGIT) || defined(Py_ISALPHA)
+#error remove these definitions
+#endif
+#define Py_ISDIGIT isdigit
+#define Py_ISALPHA isalpha
+
+PyObject *
+PyBytes_FromFormatV(const char *format, va_list vargs)
+{
+    va_list count;
+    Py_ssize_t n = 0;
+    const char* f;
+    char *s;
+    PyObject* string;
+
+    Py_VA_COPY(count, vargs);
+    /* step 1: figure out how large a buffer we need */
+    for (f = format; *f; f++) {
+        if (*f == '%') {
+            const char* p = f;
+            while (*++f && *f != '%' && !Py_ISALPHA(*f))
+                ;
+
+            /* skip the 'l' or 'z' in {%ld, %zd, %lu, %zu} since
+             * they don't affect the amount of space we reserve.
+             */
+            if ((*f == 'l' || *f == 'z') &&
+                            (f[1] == 'd' || f[1] == 'u'))
+                ++f;
+
+            switch (*f) {
+            case 'c':
+            {
+                int c = va_arg(count, int);
+                if (c < 0 || c > 255) {
+                    PyErr_SetString(PyExc_OverflowError,
+                                    "PyBytes_FromFormatV(): %c format "
+                                    "expects an integer in range [0; 255]");
+                    return NULL;
+                }
+                n++;
+                break;
+            }
+            case '%':
+                n++;
+                break;
+            case 'd': case 'u': case 'i': case 'x':
+                (void) va_arg(count, int);
+                /* 20 bytes is enough to hold a 64-bit
+                   integer.  Decimal takes the most space.
+                   This isn't enough for octal. */
+                n += 20;
+                break;
+            case 's':
+                s = va_arg(count, char*);
+                n += strlen(s);
+                break;
+            case 'p':
+                (void) va_arg(count, int);
+                /* maximum 64-bit pointer representation:
+                 * 0xffffffffffffffff
+                 * so 19 characters is enough.
+                 * XXX I count 18 -- what's the extra for?
+                 */
+                n += 19;
+                break;
+            default:
+                /* if we stumble upon an unknown
+                   formatting code, copy the rest of
+                   the format string to the output
+                   string. (we cannot just skip the
+                   code, since there's no way to know
+                   what's in the argument list) */
+                n += strlen(p);
+                goto expand;
+            }
+        } else
+            n++;
+    }
+ expand:
+    /* step 2: fill the buffer */
+    /* Since we've analyzed how much space we need for the worst case,
+       use sprintf directly instead of the slower PyOS_snprintf. */
+    string = PyBytes_FromStringAndSize(NULL, n);
+    if (!string)
+        return NULL;
+
+    s = PyBytes_AsString(string);
+
+    for (f = format; *f; f++) {
+        if (*f == '%') {
+            const char* p = f++;
+            Py_ssize_t i;
+            int longflag = 0;
+            int size_tflag = 0;
+            /* parse the width.precision part (we're only
+               interested in the precision value, if any) */
+            n = 0;
+            while (Py_ISDIGIT(*f))
+                n = (n*10) + *f++ - '0';
+            if (*f == '.') {
+                f++;
+                n = 0;
+                while (Py_ISDIGIT(*f))
+                    n = (n*10) + *f++ - '0';
+            }
+            while (*f && *f != '%' && !Py_ISALPHA(*f))
+                f++;
+            /* handle the long flag, but only for %ld and %lu.
+               others can be added when necessary. */
+            if (*f == 'l' && (f[1] == 'd' || f[1] == 'u')) {
+                longflag = 1;
+                ++f;
+            }
+            /* handle the size_t flag. */
+            if (*f == 'z' && (f[1] == 'd' || f[1] == 'u')) {
+                size_tflag = 1;
+                ++f;
+            }
+
+            switch (*f) {
+            case 'c':
+            {
+                int c = va_arg(vargs, int);
+                /* c has been checked for overflow in the first step */
+                *s++ = (unsigned char)c;
+                break;
+            }
+            case 'd':
+                if (longflag)
+                    sprintf(s, "%ld", va_arg(vargs, long));
+                else if (size_tflag)
+                    sprintf(s, "%" PY_FORMAT_SIZE_T "d",
+                        va_arg(vargs, Py_ssize_t));
+                else
+                    sprintf(s, "%d", va_arg(vargs, int));
+                s += strlen(s);
+                break;
+            case 'u':
+                if (longflag)
+                    sprintf(s, "%lu",
+                        va_arg(vargs, unsigned long));
+                else if (size_tflag)
+                    sprintf(s, "%" PY_FORMAT_SIZE_T "u",
+                        va_arg(vargs, size_t));
+                else
+                    sprintf(s, "%u",
+                        va_arg(vargs, unsigned int));
+                s += strlen(s);
+                break;
+            case 'i':
+                sprintf(s, "%i", va_arg(vargs, int));
+                s += strlen(s);
+                break;
+            case 'x':
+                sprintf(s, "%x", va_arg(vargs, int));
+                s += strlen(s);
+                break;
+            case 's':
+                p = va_arg(vargs, char*);
+                i = strlen(p);
+                if (n > 0 && i > n)
+                    i = n;
+                Py_MEMCPY(s, p, i);
+                s += i;
+                break;
+            case 'p':
+                sprintf(s, "%p", va_arg(vargs, void*));
+                /* %p is ill-defined:  ensure leading 0x. */
+                if (s[1] == 'X')
+                    s[1] = 'x';
+                else if (s[1] != 'x') {
+                    memmove(s+2, s, strlen(s)+1);
+                    s[0] = '0';
+                    s[1] = 'x';
+                }
+                s += strlen(s);
+                break;
+            case '%':
+                *s++ = '%';
+                break;
+            default:
+                strcpy(s, p);
+                s += strlen(s);
+                goto end;
+            }
+        } else
+            *s++ = *f;
+    }
+
+ end:
+    _PyBytes_Resize(&string, s - PyBytes_AS_STRING(string));
+    return string;
+}
+
+PyObject *
+PyBytes_FromFormat(const char *format, ...)
+{
+    PyObject* ret;
+    va_list vargs;
+
+#ifdef HAVE_STDARG_PROTOTYPES
+    va_start(vargs, format);
+#else
+    va_start(vargs);
+#endif
+    ret = PyBytes_FromFormatV(format, vargs);
+    va_end(vargs);
+    return ret;
+}
+
diff --git a/pypy/module/cpyext/test/test_bytesobject.py b/pypy/module/cpyext/test/test_bytesobject.py
--- a/pypy/module/cpyext/test/test_bytesobject.py
+++ b/pypy/module/cpyext/test/test_bytesobject.py
@@ -202,6 +202,19 @@
         module.getbytes()
         module.c_only()
 
+    def test_FromFormat(self):
+        module = self.import_extension('foo', [
+            ("fmt", "METH_VARARGS",
+             """
+                PyObject* fmt = PyTuple_GetItem(args, 0);
+                int n = PyLong_AsLong(PyTuple_GetItem(args, 1));
+                PyObject* result = PyBytes_FromFormat(PyBytes_AsString(fmt), n);
+                return result;
+             """),
+        ])
+        print(module.fmt(b'd:%d', 10))
+        assert module.fmt(b'd:%d', 10) == b'd:10'
+
 
 class TestBytes(BaseApiTest):
     def test_bytes_resize(self, space, api):


More information about the pypy-commit mailing list