Forward port of r64958.

Added '#' formatting to integers.  This adds the 0b, 0o, or 0x prefix for bin, oct, hex.  There's still one failing case, and I need to finish the docs.  I hope to finish those today.
This commit is contained in:
Eric Smith 2008-07-15 13:02:41 +00:00
parent e840b9ad51
commit b1ebcc6b0b
3 changed files with 85 additions and 13 deletions

View File

@ -293,6 +293,40 @@ class TypesTests(unittest.TestCase):
test(1234, "+b", "+10011010010") test(1234, "+b", "+10011010010")
test(-1234, "+b", "-10011010010") test(-1234, "+b", "-10011010010")
# alternate (#) formatting
test(0, "#b", '0b0')
test(0, "-#b", '0b0')
test(1, "-#b", '0b1')
test(-1, "-#b", '-0b1')
test(-1, "-#5b", ' -0b1')
test(1, "+#5b", ' +0b1')
test(100, "+#b", '+0b1100100')
# test(100, "#012b", '0b001100100')
test(0, "#o", '0o0')
test(0, "-#o", '0o0')
test(1, "-#o", '0o1')
test(-1, "-#o", '-0o1')
test(-1, "-#5o", ' -0o1')
test(1, "+#5o", ' +0o1')
test(100, "+#o", '+0o144')
test(0, "#x", '0x0')
test(0, "-#x", '0x0')
test(1, "-#x", '0x1')
test(-1, "-#x", '-0x1')
test(-1, "-#5x", ' -0x1')
test(1, "+#5x", ' +0x1')
test(100, "+#x", '+0x64')
test(0, "#X", '0X0')
test(0, "-#X", '0X0')
test(1, "-#X", '0X1')
test(-1, "-#X", '-0X1')
test(-1, "-#5X", ' -0X1')
test(1, "+#5X", ' +0X1')
test(100, "+#X", '+0X64')
# make sure these are errors # make sure these are errors
# precision disallowed # precision disallowed
@ -509,6 +543,10 @@ class TypesTests(unittest.TestCase):
self.assertRaises(ValueError, format, 1e-100, format_spec) self.assertRaises(ValueError, format, 1e-100, format_spec)
self.assertRaises(ValueError, format, -1e-100, format_spec) self.assertRaises(ValueError, format, -1e-100, format_spec)
# Alternate formatting is not supported
self.assertRaises(ValueError, format, 0.0, '#')
self.assertRaises(ValueError, format, 0.0, '#20f')
def test_main(): def test_main():
run_unittest(TypesTests) run_unittest(TypesTests)

View File

@ -700,6 +700,10 @@ class UnicodeTest(
self.assertRaises(ValueError, format, "", "-") self.assertRaises(ValueError, format, "", "-")
self.assertRaises(ValueError, "{0:=s}".format, '') self.assertRaises(ValueError, "{0:=s}".format, '')
# Alternate formatting is not supported
self.assertRaises(ValueError, format, '', '#')
self.assertRaises(ValueError, format, '', '#20')
def test_formatting(self): def test_formatting(self):
string_tests.MixinStrUnicodeUserStringTest.test_formatting(self) string_tests.MixinStrUnicodeUserStringTest.test_formatting(self)
# Testing Unicode formatting strings... # Testing Unicode formatting strings...

View File

@ -89,6 +89,7 @@ is_sign_element(STRINGLIB_CHAR c)
typedef struct { typedef struct {
STRINGLIB_CHAR fill_char; STRINGLIB_CHAR fill_char;
STRINGLIB_CHAR align; STRINGLIB_CHAR align;
int alternate;
STRINGLIB_CHAR sign; STRINGLIB_CHAR sign;
Py_ssize_t width; Py_ssize_t width;
Py_ssize_t precision; Py_ssize_t precision;
@ -117,6 +118,7 @@ parse_internal_render_format_spec(STRINGLIB_CHAR *format_spec,
format->fill_char = '\0'; format->fill_char = '\0';
format->align = '\0'; format->align = '\0';
format->alternate = 0;
format->sign = '\0'; format->sign = '\0';
format->width = -1; format->width = -1;
format->precision = -1; format->precision = -1;
@ -154,6 +156,13 @@ parse_internal_render_format_spec(STRINGLIB_CHAR *format_spec,
++ptr; ++ptr;
} }
/* If the next character is #, we're in alternate mode. This only
applies to integers. */
if (end-ptr >= 1 && ptr[0] == '#') {
format->alternate = 1;
++ptr;
}
/* XXX add error checking */ /* XXX add error checking */
specified_width = get_integer(&ptr, end, &format->width); specified_width = get_integer(&ptr, end, &format->width);
@ -221,7 +230,8 @@ typedef struct {
and more efficient enough to justify a little obfuscation? */ and more efficient enough to justify a little obfuscation? */
static void static void
calc_number_widths(NumberFieldWidths *r, STRINGLIB_CHAR actual_sign, calc_number_widths(NumberFieldWidths *r, STRINGLIB_CHAR actual_sign,
Py_ssize_t n_digits, const InternalFormatSpec *format) Py_ssize_t n_prefix, Py_ssize_t n_digits,
const InternalFormatSpec *format)
{ {
r->n_lpadding = 0; r->n_lpadding = 0;
r->n_spadding = 0; r->n_spadding = 0;
@ -233,12 +243,14 @@ calc_number_widths(NumberFieldWidths *r, STRINGLIB_CHAR actual_sign,
/* the output will look like: /* the output will look like:
| | | |
| <lpadding> <lsign> <spadding> <digits> <rsign> <rpadding> | | <lpadding> <lsign> <prefix> <spadding> <digits> <rsign> <rpadding> |
| | | |
lsign and rsign are computed from format->sign and the actual lsign and rsign are computed from format->sign and the actual
sign of the number sign of the number
prefix is given (it's for the '0x' prefix)
digits is already known digits is already known
the total width is either given, or computed from the the total width is either given, or computed from the
@ -363,6 +375,14 @@ format_string_internal(PyObject *value, const InternalFormatSpec *format)
goto done; goto done;
} }
/* alternate is not allowed on strings */
if (format->alternate) {
PyErr_SetString(PyExc_ValueError,
"Alternate form (#) not allowed in string format "
"specifier");
goto done;
}
/* '=' alignment not allowed on strings */ /* '=' alignment not allowed on strings */
if (format->align == '=') { if (format->align == '=') {
PyErr_SetString(PyExc_ValueError, PyErr_SetString(PyExc_ValueError,
@ -505,7 +525,7 @@ format_int_or_long_internal(PyObject *value, const InternalFormatSpec *format,
} }
else { else {
int base; int base;
int leading_chars_to_skip; /* Number of characters added by int leading_chars_to_skip = 0; /* Number of characters added by
PyNumber_ToBase that we want to PyNumber_ToBase that we want to
skip over. */ skip over. */
@ -514,22 +534,24 @@ format_int_or_long_internal(PyObject *value, const InternalFormatSpec *format,
switch (format->type) { switch (format->type) {
case 'b': case 'b':
base = 2; base = 2;
if (!format->alternate)
leading_chars_to_skip = 2; /* 0b */ leading_chars_to_skip = 2; /* 0b */
break; break;
case 'o': case 'o':
base = 8; base = 8;
if (!format->alternate)
leading_chars_to_skip = 2; /* 0o */ leading_chars_to_skip = 2; /* 0o */
break; break;
case 'x': case 'x':
case 'X': case 'X':
base = 16; base = 16;
if (!format->alternate)
leading_chars_to_skip = 2; /* 0x */ leading_chars_to_skip = 2; /* 0x */
break; break;
default: /* shouldn't be needed, but stops a compiler warning */ default: /* shouldn't be needed, but stops a compiler warning */
case 'd': case 'd':
case 'n': case 'n':
base = 10; base = 10;
leading_chars_to_skip = 0;
break; break;
} }
@ -564,7 +586,7 @@ format_int_or_long_internal(PyObject *value, const InternalFormatSpec *format,
0, &n_grouping_chars, 0); 0, &n_grouping_chars, 0);
/* Calculate the widths of the various leading and trailing parts */ /* Calculate the widths of the various leading and trailing parts */
calc_number_widths(&spec, sign, n_digits + n_grouping_chars, format); calc_number_widths(&spec, sign, 0, n_digits + n_grouping_chars, format);
/* Allocate a new string to hold the result */ /* Allocate a new string to hold the result */
result = STRINGLIB_NEW(NULL, spec.n_total); result = STRINGLIB_NEW(NULL, spec.n_total);
@ -670,6 +692,14 @@ format_float_internal(PyObject *value,
Py_UNICODE unicodebuf[FLOAT_FORMATBUFLEN]; Py_UNICODE unicodebuf[FLOAT_FORMATBUFLEN];
#endif #endif
/* alternate is not allowed on floats. */
if (format->alternate) {
PyErr_SetString(PyExc_ValueError,
"Alternate form (#) not allowed in float format "
"specifier");
goto done;
}
/* first, do the conversion as 8-bit chars, using the platform's /* first, do the conversion as 8-bit chars, using the platform's
snprintf. then, if needed, convert to unicode. */ snprintf. then, if needed, convert to unicode. */
@ -730,7 +760,7 @@ format_float_internal(PyObject *value,
--n_digits; --n_digits;
} }
calc_number_widths(&spec, sign, n_digits, format); calc_number_widths(&spec, sign, 0, n_digits, format);
/* allocate a string with enough space */ /* allocate a string with enough space */
result = STRINGLIB_NEW(NULL, spec.n_total); result = STRINGLIB_NEW(NULL, spec.n_total);