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 10:10:07 +00:00
parent a6864e0d9f
commit d0c841243c
3 changed files with 88 additions and 13 deletions

View File

@ -192,6 +192,10 @@ def check_unused_args(self, used_args, args, kwargs):
self.assertRaises(ValueError, fmt.format, "{0}", 10, 20, i=100) self.assertRaises(ValueError, fmt.format, "{0}", 10, 20, i=100)
self.assertRaises(ValueError, fmt.format, "{i}", 10, 20, i=100) self.assertRaises(ValueError, fmt.format, "{i}", 10, 20, i=100)
# Alternate formatting is not supported
self.assertRaises(ValueError, format, '', '#')
self.assertRaises(ValueError, format, '', '#20')
class BytesAliasTest(unittest.TestCase): class BytesAliasTest(unittest.TestCase):
def test_builtin(self): def test_builtin(self):

View File

@ -357,6 +357,40 @@ def test(i, format_spec, result):
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
@ -461,6 +495,9 @@ def test(i, format_spec, result):
# format spec must be string # format spec must be string
self.assertRaises(TypeError, 3L .__format__, None) self.assertRaises(TypeError, 3L .__format__, None)
self.assertRaises(TypeError, 3L .__format__, 0) self.assertRaises(TypeError, 3L .__format__, 0)
# alternate specifier in wrong place
self.assertRaises(ValueError, 1L .__format__, "#+5x")
self.assertRaises(ValueError, 1L .__format__, "+5#x")
# ensure that only int and float type specifiers work # ensure that only int and float type specifiers work
for format_spec in ([chr(x) for x in range(ord('a'), ord('z')+1)] + for format_spec in ([chr(x) for x in range(ord('a'), ord('z')+1)] +
@ -579,6 +616,10 @@ def test(f, format_spec, result):
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

@ -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);