changeset: 68639:c0f24a4841d8 tag: tip user: ysj.ray date: Thu Mar 17 12:29:10 2011 +0800 summary: Fix PyBytes_FromFormatV(): Implement width formatter for %u, %i, %d, %x. diff -r 2b3fb2398f45 -r c0f24a4841d8 Lib/test/test_bytes.py --- a/Lib/test/test_bytes.py Wed Mar 16 22:11:09 2011 -0400 +++ b/Lib/test/test_bytes.py Thu Mar 17 12:29:10 2011 +0800 @@ -502,7 +502,7 @@ # Test PyBytes_FromFormat() def test_from_format(self): test.support.import_module('ctypes') - from ctypes import pythonapi, py_object, c_int, c_char_p + from ctypes import pythonapi, py_object, c_int, c_char_p, c_longlong PyBytes_FromFormat = pythonapi.PyBytes_FromFormat PyBytes_FromFormat.restype = py_object @@ -520,6 +520,41 @@ self.assertEqual(PyBytes_FromFormat(b's:%s', c_char_p(b'cstr')), b's:cstr') + # Test width and precision flags + self.assertEqual(PyBytes_FromFormat(b'd:%5d', c_int(10)), + b'd: 10') + self.assertEqual(PyBytes_FromFormat(b'd:%05d', c_int(10)), + b'd:00010') + self.assertEqual(PyBytes_FromFormat(b'i:%5i', c_int(10)), + b'i: 10') + self.assertEqual(PyBytes_FromFormat(b'i:%05i', c_int(10)), + b'i:00010') + self.assertEqual(PyBytes_FromFormat(b'u:%5u', c_int(10)), + b'u: 10') + self.assertEqual(PyBytes_FromFormat(b'u:%05u', c_int(10)), + b'u:00010') + self.assertEqual(PyBytes_FromFormat(b'x:%5x', c_int(10)), + b'x: a') + self.assertEqual(PyBytes_FromFormat(b'x:%05x', c_int(10)), + b'x:0000a') + + self.assertEqual(PyBytes_FromFormat(b's:%5.2s', c_char_p(b'xxx')), + b's: xx') + + # Test lld and llu flags + self.assertEqual(PyBytes_FromFormat(b'd:%lld', c_longlong(100)), + b'd:100') + self.assertEqual(PyBytes_FromFormat(b'd:%5lld', c_longlong(100)), + b'd: 100') + self.assertEqual(PyBytes_FromFormat(b'd:%05lld', c_longlong(100)), + b'd:00100') + self.assertEqual(PyBytes_FromFormat(b'u:%llu', c_longlong(100)), + b'u:100') + self.assertEqual(PyBytes_FromFormat(b'u:%5llu', c_longlong(100)), + b'u: 100') + self.assertEqual(PyBytes_FromFormat(b'u:%05llu', c_longlong(100)), + b'u:00100') + class ByteArrayTest(BaseBytesTest): type2test = bytearray diff -r 2b3fb2398f45 -r c0f24a4841d8 Objects/bytesobject.c --- a/Objects/bytesobject.c Wed Mar 16 22:11:09 2011 -0400 +++ b/Objects/bytesobject.c Thu Mar 17 12:29:10 2011 +0800 @@ -164,29 +164,129 @@ return (PyObject *) op; } +static void +makefmt(char *fmt, int longflag, int longlongflag, int size_tflag, + int zeropad, int width, int precision, char c) +{ + *fmt++ = '%'; + if (width) { + if (zeropad) + *fmt++ = '0'; + fmt += sprintf(fmt, "%d", width); + } + if (precision != -1) + fmt += sprintf(fmt, ".%d", precision); + if (longflag) + *fmt++ = 'l'; + else if (longlongflag) { + /* longlongflag should only ever be nonzero on machines with + HAVE_LONG_LONG defined */ +#ifdef HAVE_LONG_LONG + char *f = PY_FORMAT_LONG_LONG; + while (*f) + *fmt++ = *f++; +#else + /* we shouldn't ever get here */ + assert(0); + *fmt++ = 'l'; +#endif + } + else if (size_tflag) { + char *f = PY_FORMAT_SIZE_T; + while (*f) + *fmt++ = *f++; + } + *fmt++ = c; + *fmt = '\0'; +} + +/* helper for PyBytes_FromFormatV() */ + +static const char* +parse_format_flags(const char *f, + int *p_width, int *p_precision, + int *p_longflag, int *p_longlongflag, int *p_size_tflag) +{ + int width, precision, longflag, longlongflag, size_tflag; + + /* parse the width.precision part, e.g. "%2.5s" => width=2, precision=5 */ + f++; + width = 0; + while (Py_ISDIGIT((unsigned)*f)) + width = (width*10) + *f++ - '0'; + precision = -1; + if (*f == '.') { + f++; + if Py_ISDIGIT((unsigned)*f) + precision = 0; + while (Py_ISDIGIT((unsigned)*f)) + precision = (precision*10) + *f++ - '0'; + if (*f == '%') { + /* "%.3%s" => f points to "3" */ + f--; + } + } + if (*f == '\0') { + /* bogus format "%.1" => go backward, f points to "1" */ + f--; + } + if (p_width != NULL) + *p_width = width; + if (p_precision != NULL) + *p_precision = precision; + + /* Handle %ld, %lu, %lld and %llu. */ + longflag = 0; + longlongflag = 0; + size_tflag = 0; + + if (*f == 'l') { + if (f[1] == 'd' || f[1] == 'u' || f[1] == 'i') { + longflag = 1; + ++f; + } +#ifdef HAVE_LONG_LONG + else if (f[1] == 'l' && + (f[2] == 'd' || f[2] == 'u' || f[2] == 'i')) { + longlongflag = 1; + f += 2; + } +#endif + } + /* handle the size_t flag. */ + else if (*f == 'z' && (f[1] == 'd' || f[1] == 'u' || f[1] == 'i')) { + size_tflag = 1; + ++f; + } + if (p_longflag != NULL) + *p_longflag = longflag; + if (p_longlongflag != NULL) + *p_longlongflag = longlongflag; + if (p_size_tflag != NULL) + *p_size_tflag = size_tflag; + return f; +} + +#define MAX_SIZE(size1, size2) ((size1) >= (size2) ? (size1) : (size2)) + PyObject * PyBytes_FromFormatV(const char *format, va_list vargs) { va_list count; Py_ssize_t n = 0; + int width = 0, precision = 0; const char* f; char *s; PyObject* string; + char fmt[61]; /* should be enough for %0width.precisionlld */ + int zeropad; 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; + f = parse_format_flags(f, &width, NULL, NULL, NULL, NULL); switch (*f) { case 'c': @@ -200,11 +300,11 @@ /* 20 bytes is enough to hold a 64-bit integer. Decimal takes the most space. This isn't enough for octal. */ - n += 20; + n += MAX_SIZE(width, 20); break; case 's': s = va_arg(count, char*); - n += strlen(s); + n += MAX_SIZE(width, strlen(s)); break; case 'p': (void) va_arg(count, int); @@ -240,76 +340,60 @@ for (f = format; *f; f++) { if (*f == '%') { - const char* p = f++; - Py_ssize_t i; + const char* p = f; int longflag = 0; + int longlongflag = 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; - } + zeropad = (f[1] == '0'); + f = parse_format_flags(f, &width, &precision, &longflag, + &longlongflag, &size_tflag); switch (*f) { case 'c': *s++ = va_arg(vargs, int); break; + case 'i': case 'd': + makefmt(fmt, longflag, longlongflag, size_tflag, zeropad, + width, precision, *f); if (longflag) - sprintf(s, "%ld", va_arg(vargs, long)); + sprintf(s, fmt, va_arg(vargs, long)); +#ifdef HAVE_LONG_LONG + else if (longlongflag) + sprintf(s, fmt, va_arg(vargs, PY_LONG_LONG)); +#endif else if (size_tflag) - sprintf(s, "%" PY_FORMAT_SIZE_T "d", - va_arg(vargs, Py_ssize_t)); + sprintf(s, fmt, va_arg(vargs, Py_ssize_t)); else - sprintf(s, "%d", va_arg(vargs, int)); + sprintf(s, fmt, va_arg(vargs, int)); s += strlen(s); break; case 'u': + makefmt(fmt, longflag, longlongflag, size_tflag, zeropad, + width, precision, *f); if (longflag) - sprintf(s, "%lu", - va_arg(vargs, unsigned long)); + sprintf(s, fmt, va_arg(vargs, unsigned long)); +#ifdef HAVE_LONG_LONG + else if (longlongflag) + sprintf(s, fmt, va_arg(vargs, unsigned PY_LONG_LONG)); +#endif else if (size_tflag) - sprintf(s, "%" PY_FORMAT_SIZE_T "u", - va_arg(vargs, size_t)); + sprintf(s, fmt, 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)); + sprintf(s, fmt, va_arg(vargs, unsigned int)); s += strlen(s); break; case 'x': - sprintf(s, "%x", va_arg(vargs, int)); + makefmt(fmt, 0, 0, 0, zeropad, width, precision, 'x'); + sprintf(s, fmt, va_arg(vargs, int)); s += strlen(s); break; case 's': + makefmt(fmt, 0, 0, 0, 0, width, precision, 's'); p = va_arg(vargs, char*); - i = strlen(p); - if (n > 0 && i > n) - i = n; - Py_MEMCPY(s, p, i); - s += i; + sprintf(s, fmt, p); + s += MAX_SIZE(width, strlen(p)); break; case 'p': sprintf(s, "%p", va_arg(vargs, void*));