Author: Ronan Lamy <ronan.l...@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): _______________________________________________ pypy-commit mailing list pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit