Commit a3b1ac8d authored by Eric Smith's avatar Eric Smith

Added ',' thousands grouping to int.__format__. See PEP 378.

This is incomplete, but I want to get some version into the next alpha. I am still working on:
Documentation.
More tests.
Implement for floats.

In addition, there's an existing bug with 'n' formatting that carries forward to thousands grouping (issue 5515).
parent f8c8b6d3
...@@ -91,13 +91,25 @@ PyAPI_FUNC(int) PyBytes_AsStringAndSize( ...@@ -91,13 +91,25 @@ PyAPI_FUNC(int) PyBytes_AsStringAndSize(
into the string pointed to by buffer. For the argument descriptions, into the string pointed to by buffer. For the argument descriptions,
see Objects/stringlib/localeutil.h */ see Objects/stringlib/localeutil.h */
PyAPI_FUNC(int) _PyBytes_InsertThousandsGrouping(char *buffer, PyAPI_FUNC(int) _PyBytes_InsertThousandsGroupingLocale(char *buffer,
Py_ssize_t n_buffer, Py_ssize_t n_buffer,
Py_ssize_t n_digits, Py_ssize_t n_digits,
Py_ssize_t buf_size, Py_ssize_t buf_size,
Py_ssize_t *count, Py_ssize_t *count,
int append_zero_char); int append_zero_char);
/* Using explicit passed-in values, insert the thousands grouping
into the string pointed to by buffer. For the argument descriptions,
see Objects/stringlib/localeutil.h */
PyAPI_FUNC(int) _PyBytes_InsertThousandsGrouping(char *buffer,
Py_ssize_t n_buffer,
Py_ssize_t n_digits,
Py_ssize_t buf_size,
Py_ssize_t *count,
int append_zero_char,
const char *grouping,
const char *thousands_sep);
/* Flags used by string formatting */ /* Flags used by string formatting */
#define F_LJUST (1<<0) #define F_LJUST (1<<0)
#define F_SIGN (1<<1) #define F_SIGN (1<<1)
......
...@@ -1482,13 +1482,24 @@ PyAPI_FUNC(PyObject *) _PyUnicode_XStrip( ...@@ -1482,13 +1482,24 @@ PyAPI_FUNC(PyObject *) _PyUnicode_XStrip(
into the string pointed to by buffer. For the argument descriptions, into the string pointed to by buffer. For the argument descriptions,
see Objects/stringlib/localeutil.h */ see Objects/stringlib/localeutil.h */
PyAPI_FUNC(int) _PyUnicode_InsertThousandsGrouping(Py_UNICODE *buffer, PyAPI_FUNC(int) _PyUnicode_InsertThousandsGroupingLocale(Py_UNICODE *buffer,
Py_ssize_t n_buffer, Py_ssize_t n_buffer,
Py_ssize_t n_digits, Py_ssize_t n_digits,
Py_ssize_t buf_size, Py_ssize_t buf_size,
Py_ssize_t *count, Py_ssize_t *count,
int append_zero_char); int append_zero_char);
/* Using explicit passed-in values, insert the thousands grouping
into the string pointed to by buffer. For the argument descriptions,
see Objects/stringlib/localeutil.h */
PyAPI_FUNC(int) _PyUnicode_InsertThousandsGrouping(Py_UNICODE *buffer,
Py_ssize_t n_buffer,
Py_ssize_t n_digits,
Py_ssize_t buf_size,
Py_ssize_t *count,
int append_zero_char,
const char *grouping,
const char *thousands_sep);
/* === Characters Type APIs =============================================== */ /* === Characters Type APIs =============================================== */
/* Helper array used by Py_UNICODE_ISSPACE(). */ /* Helper array used by Py_UNICODE_ISSPACE(). */
......
...@@ -338,6 +338,15 @@ class TypesTests(unittest.TestCase): ...@@ -338,6 +338,15 @@ class TypesTests(unittest.TestCase):
test(123456, "#012X", '0X000001E240') test(123456, "#012X", '0X000001E240')
test(-123456, "#012X", '-0X00001E240') test(-123456, "#012X", '-0X00001E240')
test(123, ',', '123')
test(-123, ',', '-123')
test(1234, ',', '1,234')
test(-1234, ',', '-1,234')
test(123456, ',', '123,456')
test(-123456, ',', '-123,456')
test(1234567, ',', '1,234,567')
test(-1234567, ',', '-1,234,567')
# make sure these are errors # make sure these are errors
# precision disallowed # precision disallowed
...@@ -347,6 +356,8 @@ class TypesTests(unittest.TestCase): ...@@ -347,6 +356,8 @@ class TypesTests(unittest.TestCase):
# format spec must be string # format spec must be string
self.assertRaises(TypeError, 3 .__format__, None) self.assertRaises(TypeError, 3 .__format__, None)
self.assertRaises(TypeError, 3 .__format__, 0) self.assertRaises(TypeError, 3 .__format__, 0)
# can't have ',' with 'n'
self.assertRaises(ValueError, 3 .__format__, ",n")
# 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)] +
......
...@@ -583,6 +583,7 @@ PyBytes_AsStringAndSize(register PyObject *obj, ...@@ -583,6 +583,7 @@ PyBytes_AsStringAndSize(register PyObject *obj,
#include "stringlib/transmogrify.h" #include "stringlib/transmogrify.h"
#define _Py_InsertThousandsGrouping _PyBytes_InsertThousandsGrouping #define _Py_InsertThousandsGrouping _PyBytes_InsertThousandsGrouping
#define _Py_InsertThousandsGroupingLocale _PyBytes_InsertThousandsGroupingLocale
#include "stringlib/localeutil.h" #include "stringlib/localeutil.h"
PyObject * PyObject *
......
...@@ -120,6 +120,7 @@ typedef struct { ...@@ -120,6 +120,7 @@ typedef struct {
int alternate; int alternate;
STRINGLIB_CHAR sign; STRINGLIB_CHAR sign;
Py_ssize_t width; Py_ssize_t width;
int thousands_separators;
Py_ssize_t precision; Py_ssize_t precision;
STRINGLIB_CHAR type; STRINGLIB_CHAR type;
} InternalFormatSpec; } InternalFormatSpec;
...@@ -149,6 +150,7 @@ parse_internal_render_format_spec(STRINGLIB_CHAR *format_spec, ...@@ -149,6 +150,7 @@ parse_internal_render_format_spec(STRINGLIB_CHAR *format_spec,
format->alternate = 0; format->alternate = 0;
format->sign = '\0'; format->sign = '\0';
format->width = -1; format->width = -1;
format->thousands_separators = 0;
format->precision = -1; format->precision = -1;
format->type = default_type; format->type = default_type;
...@@ -201,6 +203,12 @@ parse_internal_render_format_spec(STRINGLIB_CHAR *format_spec, ...@@ -201,6 +203,12 @@ parse_internal_render_format_spec(STRINGLIB_CHAR *format_spec,
format->width = -1; format->width = -1;
} }
/* Comma signifies add thousands separators */
if (end-ptr && ptr[0] == ',') {
format->thousands_separators = 1;
++ptr;
}
/* Parse field precision */ /* Parse field precision */
if (end-ptr && ptr[0] == '.') { if (end-ptr && ptr[0] == '.') {
++ptr; ++ptr;
...@@ -230,6 +238,11 @@ parse_internal_render_format_spec(STRINGLIB_CHAR *format_spec, ...@@ -230,6 +238,11 @@ parse_internal_render_format_spec(STRINGLIB_CHAR *format_spec,
++ptr; ++ptr;
} }
if (format->type == 'n' && format->thousands_separators) {
PyErr_Format(PyExc_ValueError, "Cannot specify ',' with 'n'.");
return 0;
}
return 1; return 1;
} }
...@@ -630,8 +643,13 @@ format_int_or_long_internal(PyObject *value, const InternalFormatSpec *format, ...@@ -630,8 +643,13 @@ format_int_or_long_internal(PyObject *value, const InternalFormatSpec *format,
if (format->type == 'n') if (format->type == 'n')
/* Compute how many additional chars we need to allocate /* Compute how many additional chars we need to allocate
to hold the thousands grouping. */ to hold the thousands grouping. */
STRINGLIB_GROUPING(NULL, n_digits, n_digits, STRINGLIB_GROUPING_LOCALE(NULL, n_digits, n_digits,
0, &n_grouping_chars, 0); 0, &n_grouping_chars, 0);
if (format->thousands_separators)
/* Compute how many additional chars we need to allocate
to hold the thousands grouping. */
STRINGLIB_GROUPING(NULL, n_digits, n_digits,
0, &n_grouping_chars, 0, "\3", ",");
/* 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_prefix, n_digits + n_grouping_chars, calc_number_widths(&spec, sign, n_prefix, n_digits + n_grouping_chars,
...@@ -670,11 +688,22 @@ format_int_or_long_internal(PyObject *value, const InternalFormatSpec *format, ...@@ -670,11 +688,22 @@ format_int_or_long_internal(PyObject *value, const InternalFormatSpec *format,
reserved enough space. */ reserved enough space. */
STRINGLIB_CHAR *pstart = p + n_leading_chars; STRINGLIB_CHAR *pstart = p + n_leading_chars;
#ifndef NDEBUG #ifndef NDEBUG
int r = int r;
#endif
if (format->type == 'n')
#ifndef NDEBUG
r =
#endif #endif
STRINGLIB_GROUPING(pstart, n_digits, n_digits, STRINGLIB_GROUPING_LOCALE(pstart, n_digits, n_digits,
spec.n_total+n_grouping_chars-n_leading_chars, spec.n_total+n_grouping_chars-n_leading_chars,
NULL, 0); NULL, 0);
else
#ifndef NDEBUG
r =
STRINGLIB_GROUPING(pstart, n_digits, n_digits,
spec.n_total+n_grouping_chars-n_leading_chars,
NULL, 0, "\3", ",");
#endif
assert(r); assert(r);
} }
......
...@@ -18,11 +18,13 @@ ...@@ -18,11 +18,13 @@
* @append_zero_char: If non-zero, put a trailing zero at the end of * @append_zero_char: If non-zero, put a trailing zero at the end of
* of the resulting string, if and only if we modified the * of the resulting string, if and only if we modified the
* string. * string.
* @grouping: see definition in localeconv().
* @thousands_sep: see definition in localeconv().
* *
* Inserts thousand grouping characters (as defined in the current * Inserts thousand grouping characters (as defined by grouping and
* locale) into the string between buffer and buffer+n_digits. If * thousands_sep) into the string between buffer and buffer+n_digits.
* count is non-NULL, don't do any formatting, just count the number * If count is non-NULL, don't do any formatting, just count the
* of characters to insert. This is used by the caller to * number of characters to insert. This is used by the caller to
* appropriately resize the buffer, if needed. If count is non-NULL, * appropriately resize the buffer, if needed. If count is non-NULL,
* buffer can be NULL (it is not dereferenced at all in that case). * buffer can be NULL (it is not dereferenced at all in that case).
* *
...@@ -34,97 +36,130 @@ ...@@ -34,97 +36,130 @@
**/ **/
int int
_Py_InsertThousandsGrouping(STRINGLIB_CHAR *buffer, _Py_InsertThousandsGrouping(STRINGLIB_CHAR *buffer,
Py_ssize_t n_buffer, Py_ssize_t n_buffer,
Py_ssize_t n_digits, Py_ssize_t n_digits,
Py_ssize_t buf_size, Py_ssize_t buf_size,
Py_ssize_t *count, Py_ssize_t *count,
int append_zero_char) int append_zero_char,
const char *grouping,
const char *thousands_sep)
{ {
struct lconv *locale_data = localeconv(); Py_ssize_t thousands_sep_len = strlen(thousands_sep);
const char *grouping = locale_data->grouping; STRINGLIB_CHAR *pend = NULL; /* current end of buffer */
const char *thousands_sep = locale_data->thousands_sep; STRINGLIB_CHAR *pmax = NULL; /* max of buffer */
Py_ssize_t thousands_sep_len = strlen(thousands_sep); char current_grouping;
STRINGLIB_CHAR *pend = NULL; /* current end of buffer */ Py_ssize_t remaining = n_digits; /* Number of chars remaining to
STRINGLIB_CHAR *pmax = NULL; /* max of buffer */ be looked at */
char current_grouping;
Py_ssize_t remaining = n_digits; /* Number of chars remaining to
be looked at */
/* Initialize the character count, if we're just counting. */ /* Initialize the character count, if we're just counting. */
if (count) if (count)
*count = 0; *count = 0;
else { else {
/* We're not just counting, we're modifying buffer */ /* We're not just counting, we're modifying buffer */
pend = buffer + n_buffer; pend = buffer + n_buffer;
pmax = buffer + buf_size; pmax = buffer + buf_size;
} }
/* Starting at the end and working right-to-left, keep track of /* Starting at the end and working right-to-left, keep track of
what grouping needs to be added and insert that. */ what grouping needs to be added and insert that. */
current_grouping = *grouping++; current_grouping = *grouping++;
/* If the first character is 0, perform no grouping at all. */ /* If the first character is 0, perform no grouping at all. */
if (current_grouping == 0) if (current_grouping == 0)
return 1; return 1;
while (remaining > current_grouping) { while (remaining > current_grouping) {
/* Always leave buffer and pend valid at the end of this /* Always leave buffer and pend valid at the end of this
loop, since we might leave with a return statement. */ loop, since we might leave with a return statement. */
remaining -= current_grouping; remaining -= current_grouping;
if (count) { if (count) {
/* We're only counting, not touching the memory. */ /* We're only counting, not touching the memory. */
*count += thousands_sep_len; *count += thousands_sep_len;
} }
else { else {
/* Do the formatting. */ /* Do the formatting. */
STRINGLIB_CHAR *plast = buffer + remaining; STRINGLIB_CHAR *plast = buffer + remaining;
/* Is there room to insert thousands_sep_len chars? */ /* Is there room to insert thousands_sep_len chars? */
if (pmax - pend < thousands_sep_len) if (pmax - pend < thousands_sep_len)
/* No room. */ /* No room. */
return 0; return 0;
/* Move the rest of the string down. */ /* Move the rest of the string down. */
memmove(plast + thousands_sep_len, memmove(plast + thousands_sep_len,
plast, plast,
(pend - plast) * sizeof(STRINGLIB_CHAR)); (pend - plast) * sizeof(STRINGLIB_CHAR));
/* Copy the thousands_sep chars into the buffer. */ /* Copy the thousands_sep chars into the buffer. */
#if STRINGLIB_IS_UNICODE #if STRINGLIB_IS_UNICODE
/* Convert from the char's of the thousands_sep from /* Convert from the char's of the thousands_sep from
the locale into unicode. */ the locale into unicode. */
{ {
Py_ssize_t i; Py_ssize_t i;
for (i = 0; i < thousands_sep_len; ++i) for (i = 0; i < thousands_sep_len; ++i)
plast[i] = thousands_sep[i]; plast[i] = thousands_sep[i];
} }
#else #else
/* No conversion, just memcpy the thousands_sep. */ /* No conversion, just memcpy the thousands_sep. */
memcpy(plast, thousands_sep, thousands_sep_len); memcpy(plast, thousands_sep, thousands_sep_len);
#endif #endif
} }
/* Adjust end pointer. */ /* Adjust end pointer. */
pend += thousands_sep_len; pend += thousands_sep_len;
/* Move to the next grouping character, unless we're /* Move to the next grouping character, unless we're
repeating (which is designated by a grouping of 0). */ repeating (which is designated by a grouping of 0). */
if (*grouping != 0) { if (*grouping != 0) {
current_grouping = *grouping++; current_grouping = *grouping++;
if (current_grouping == CHAR_MAX) if (current_grouping == CHAR_MAX)
/* We're done. */ /* We're done. */
break; break;
} }
} }
if (append_zero_char) { if (append_zero_char) {
/* Append a zero character to mark the end of the string, /* Append a zero character to mark the end of the string,
if there's room. */ if there's room. */
if (pend - (buffer + remaining) < 1) if (pend - (buffer + remaining) < 1)
/* No room, error. */ /* No room, error. */
return 0; return 0;
*pend = 0; *pend = 0;
} }
return 1; return 1;
}
/**
* _Py_InsertThousandsGroupingLocale:
* @buffer: A pointer to the start of a string.
* @n_buffer: The length of the string.
* @n_digits: The number of digits in the string, in which we want
* to put the grouping chars.
* @buf_size: The maximum size of the buffer pointed to by buffer.
* @count: If non-NULL, points to a variable that will receive the
* number of characters we need to insert (and no formatting
* will actually occur).
* @append_zero_char: If non-zero, put a trailing zero at the end of
* of the resulting string, if and only if we modified the
* string.
*
* Reads thee current locale and calls _Py_InsertThousandsGrouping().
**/
int
_Py_InsertThousandsGroupingLocale(STRINGLIB_CHAR *buffer,
Py_ssize_t n_buffer,
Py_ssize_t n_digits,
Py_ssize_t buf_size,
Py_ssize_t *count,
int append_zero_char)
{
struct lconv *locale_data = localeconv();
const char *grouping = locale_data->grouping;
const char *thousands_sep = locale_data->thousands_sep;
return _Py_InsertThousandsGrouping(buffer, n_buffer, n_digits,
buf_size, count,
append_zero_char, grouping,
thousands_sep);
} }
#endif /* STRINGLIB_LOCALEUTIL_H */ #endif /* STRINGLIB_LOCALEUTIL_H */
...@@ -24,5 +24,6 @@ ...@@ -24,5 +24,6 @@
#define STRINGLIB_CMP memcmp #define STRINGLIB_CMP memcmp
#define STRINGLIB_TOSTR PyObject_Str #define STRINGLIB_TOSTR PyObject_Str
#define STRINGLIB_GROUPING _PyBytes_InsertThousandsGrouping #define STRINGLIB_GROUPING _PyBytes_InsertThousandsGrouping
#define STRINGLIB_GROUPING_LOCALE _PyBytes_InsertThousandsGroupingLocale
#define STRINGLIB_TOASCII PyObject_Repr #define STRINGLIB_TOASCII PyObject_Repr
#endif /* !STRINGLIB_STRINGDEFS_H */ #endif /* !STRINGLIB_STRINGDEFS_H */
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
#define STRINGLIB_RESIZE PyUnicode_Resize #define STRINGLIB_RESIZE PyUnicode_Resize
#define STRINGLIB_CHECK PyUnicode_Check #define STRINGLIB_CHECK PyUnicode_Check
#define STRINGLIB_GROUPING _PyUnicode_InsertThousandsGrouping #define STRINGLIB_GROUPING _PyUnicode_InsertThousandsGrouping
#define STRINGLIB_GROUPING_LOCALE _PyUnicode_InsertThousandsGroupingLocale
#if PY_VERSION_HEX < 0x03000000 #if PY_VERSION_HEX < 0x03000000
#define STRINGLIB_TOSTR PyObject_Unicode #define STRINGLIB_TOSTR PyObject_Unicode
......
...@@ -5635,6 +5635,7 @@ int PyUnicode_EncodeDecimal(Py_UNICODE *s, ...@@ -5635,6 +5635,7 @@ int PyUnicode_EncodeDecimal(Py_UNICODE *s,
#include "stringlib/partition.h" #include "stringlib/partition.h"
#define _Py_InsertThousandsGrouping _PyUnicode_InsertThousandsGrouping #define _Py_InsertThousandsGrouping _PyUnicode_InsertThousandsGrouping
#define _Py_InsertThousandsGroupingLocale _PyUnicode_InsertThousandsGroupingLocale
#include "stringlib/localeutil.h" #include "stringlib/localeutil.h"
/* helper macro to fixup start/end slice values */ /* helper macro to fixup start/end slice values */
......
...@@ -368,7 +368,7 @@ add_thousands_grouping(char* buffer, size_t buf_size) ...@@ -368,7 +368,7 @@ add_thousands_grouping(char* buffer, size_t buf_size)
/* At this point, p points just past the right-most character we /* At this point, p points just past the right-most character we
want to format. We need to add the grouping string for the want to format. We need to add the grouping string for the
characters between buffer and p. */ characters between buffer and p. */
return _PyBytes_InsertThousandsGrouping(buffer, len, p-buffer, return _PyBytes_InsertThousandsGroupingLocale(buffer, len, p-buffer,
buf_size, NULL, 1); buf_size, NULL, 1);
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment