diff --git a/Lib/test/test_string.py b/Lib/test/test_string.py index 5c20a2b0e58..20cff0a8fb5 100644 --- a/Lib/test/test_string.py +++ b/Lib/test/test_string.py @@ -192,6 +192,10 @@ class ModuleTest(unittest.TestCase): self.assertRaises(ValueError, fmt.format, "{0}", 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): def test_builtin(self): diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index a10f2a61fc8..b185479c4f4 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -357,6 +357,40 @@ class TypesTests(unittest.TestCase): 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 # precision disallowed @@ -461,6 +495,9 @@ class TypesTests(unittest.TestCase): # format spec must be string self.assertRaises(TypeError, 3L .__format__, None) 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 for format_spec in ([chr(x) for x in range(ord('a'), ord('z')+1)] + @@ -579,6 +616,10 @@ class TypesTests(unittest.TestCase): 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(): run_unittest(TypesTests) diff --git a/Objects/stringlib/formatter.h b/Objects/stringlib/formatter.h index 018121a4505..9b7d607a5a8 100644 --- a/Objects/stringlib/formatter.h +++ b/Objects/stringlib/formatter.h @@ -89,6 +89,7 @@ is_sign_element(STRINGLIB_CHAR c) typedef struct { STRINGLIB_CHAR fill_char; STRINGLIB_CHAR align; + int alternate; STRINGLIB_CHAR sign; Py_ssize_t width; Py_ssize_t precision; @@ -117,6 +118,7 @@ parse_internal_render_format_spec(STRINGLIB_CHAR *format_spec, format->fill_char = '\0'; format->align = '\0'; + format->alternate = 0; format->sign = '\0'; format->width = -1; format->precision = -1; @@ -154,6 +156,13 @@ parse_internal_render_format_spec(STRINGLIB_CHAR *format_spec, ++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 */ specified_width = get_integer(&ptr, end, &format->width); @@ -221,7 +230,8 @@ typedef struct { and more efficient enough to justify a little obfuscation? */ static void 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_spadding = 0; @@ -232,13 +242,15 @@ calc_number_widths(NumberFieldWidths *r, STRINGLIB_CHAR actual_sign, r->n_rsign = 0; /* the output will look like: - | | - | | - | | + | | + | | + | | lsign and rsign are computed from format->sign and the actual sign of the number + prefix is given (it's for the '0x' prefix) + digits is already known the total width is either given, or computed from the @@ -363,6 +375,14 @@ format_string_internal(PyObject *value, const InternalFormatSpec *format) 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 */ if (format->align == '=') { PyErr_SetString(PyExc_ValueError, @@ -505,31 +525,33 @@ format_int_or_long_internal(PyObject *value, const InternalFormatSpec *format, } else { int base; - int leading_chars_to_skip; /* Number of characters added by - PyNumber_ToBase that we want to - skip over. */ + int leading_chars_to_skip = 0; /* Number of characters added by + PyNumber_ToBase that we want to + skip over. */ /* Compute the base and how many characters will be added by PyNumber_ToBase */ switch (format->type) { case 'b': base = 2; - leading_chars_to_skip = 2; /* 0b */ + if (!format->alternate) + leading_chars_to_skip = 2; /* 0b */ break; case 'o': base = 8; - leading_chars_to_skip = 2; /* 0o */ + if (!format->alternate) + leading_chars_to_skip = 2; /* 0o */ break; case 'x': case 'X': base = 16; - leading_chars_to_skip = 2; /* 0x */ + if (!format->alternate) + leading_chars_to_skip = 2; /* 0x */ break; default: /* shouldn't be needed, but stops a compiler warning */ case 'd': case 'n': base = 10; - leading_chars_to_skip = 0; break; } @@ -564,7 +586,7 @@ format_int_or_long_internal(PyObject *value, const InternalFormatSpec *format, 0, &n_grouping_chars, 0); /* 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 */ result = STRINGLIB_NEW(NULL, spec.n_total); @@ -670,6 +692,14 @@ format_float_internal(PyObject *value, Py_UNICODE unicodebuf[FLOAT_FORMATBUFLEN]; #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 snprintf. then, if needed, convert to unicode. */ @@ -730,7 +760,7 @@ format_float_internal(PyObject *value, --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 */ result = STRINGLIB_NEW(NULL, spec.n_total);