1 /* stringlib: locale related helpers implementation */
2 
3 #include <locale.h>
4 
5 #if !STRINGLIB_IS_UNICODE
6 #   error "localeutil.h is specific to Unicode"
7 #endif
8 
9 typedef struct {
10     const char *grouping;
11     char previous;
12     Py_ssize_t i; /* Where we're currently pointing in grouping. */
13 } STRINGLIB(GroupGenerator);
14 
15 static void
STRINGLIB(GroupGenerator_init)16 STRINGLIB(GroupGenerator_init)(STRINGLIB(GroupGenerator) *self, const char *grouping)
17 {
18     self->grouping = grouping;
19     self->i = 0;
20     self->previous = 0;
21 }
22 
23 /* Returns the next grouping, or 0 to signify end. */
24 static Py_ssize_t
STRINGLIB(GroupGenerator_next)25 STRINGLIB(GroupGenerator_next)(STRINGLIB(GroupGenerator) *self)
26 {
27     /* Note that we don't really do much error checking here. If a
28        grouping string contains just CHAR_MAX, for example, then just
29        terminate the generator. That shouldn't happen, but at least we
30        fail gracefully. */
31     switch (self->grouping[self->i]) {
32     case 0:
33         return self->previous;
34     case CHAR_MAX:
35         /* Stop the generator. */
36         return 0;
37     default: {
38         char ch = self->grouping[self->i];
39         self->previous = ch;
40         self->i++;
41         return (Py_ssize_t)ch;
42     }
43     }
44 }
45 
46 /* Fill in some digits, leading zeros, and thousands separator. All
47    are optional, depending on when we're called. */
48 static void
STRINGLIB(fill)49 STRINGLIB(fill)(STRINGLIB_CHAR **digits_end, STRINGLIB_CHAR **buffer_end,
50      Py_ssize_t n_chars, Py_ssize_t n_zeros, STRINGLIB_CHAR* thousands_sep,
51      Py_ssize_t thousands_sep_len)
52 {
53     Py_ssize_t i;
54 
55     if (thousands_sep) {
56         *buffer_end -= thousands_sep_len;
57 
58         /* Copy the thousands_sep chars into the buffer. */
59         memcpy(*buffer_end, thousands_sep,
60                thousands_sep_len * STRINGLIB_SIZEOF_CHAR);
61     }
62 
63     *buffer_end -= n_chars;
64     *digits_end -= n_chars;
65     memcpy(*buffer_end, *digits_end, n_chars * sizeof(STRINGLIB_CHAR));
66 
67     *buffer_end -= n_zeros;
68     for (i = 0; i < n_zeros; i++)
69         (*buffer_end)[i] = '0';
70 }
71 
72 /**
73  * InsertThousandsGrouping:
74  * @buffer: A pointer to the start of a string.
75  * @n_buffer: Number of characters in @buffer.
76  * @digits: A pointer to the digits we're reading from. If count
77  *          is non-NULL, this is unused.
78  * @n_digits: The number of digits in the string, in which we want
79  *            to put the grouping chars.
80  * @min_width: The minimum width of the digits in the output string.
81  *             Output will be zero-padded on the left to fill.
82  * @grouping: see definition in localeconv().
83  * @thousands_sep: see definition in localeconv().
84  *
85  * There are 2 modes: counting and filling. If @buffer is NULL,
86  *  we are in counting mode, else filling mode.
87  * If counting, the required buffer size is returned.
88  * If filling, we know the buffer will be large enough, so we don't
89  *  need to pass in the buffer size.
90  * Inserts thousand grouping characters (as defined by grouping and
91  *  thousands_sep) into the string between buffer and buffer+n_digits.
92  *
93  * Return value: 0 on error, else 1.  Note that no error can occur if
94  *  count is non-NULL.
95  *
96  * This name won't be used, the includer of this file should define
97  *  it to be the actual function name, based on unicode or string.
98  *
99  * As closely as possible, this code mimics the logic in decimal.py's
100     _insert_thousands_sep().
101  **/
102 static Py_ssize_t
STRINGLIB(InsertThousandsGrouping)103 STRINGLIB(InsertThousandsGrouping)(
104     STRINGLIB_CHAR *buffer,
105     Py_ssize_t n_buffer,
106     STRINGLIB_CHAR *digits,
107     Py_ssize_t n_digits,
108     Py_ssize_t min_width,
109     const char *grouping,
110     STRINGLIB_CHAR *thousands_sep,
111     Py_ssize_t thousands_sep_len)
112 {
113     Py_ssize_t count = 0;
114     Py_ssize_t n_zeros;
115     int loop_broken = 0;
116     int use_separator = 0; /* First time through, don't append the
117                               separator. They only go between
118                               groups. */
119     STRINGLIB_CHAR *buffer_end = NULL;
120     STRINGLIB_CHAR *digits_end = NULL;
121     Py_ssize_t l;
122     Py_ssize_t n_chars;
123     Py_ssize_t remaining = n_digits; /* Number of chars remaining to
124                                         be looked at */
125     /* A generator that returns all of the grouping widths, until it
126        returns 0. */
127     STRINGLIB(GroupGenerator) groupgen;
128     STRINGLIB(GroupGenerator_init)(&groupgen, grouping);
129 
130     if (buffer) {
131         buffer_end = buffer + n_buffer;
132         digits_end = digits + n_digits;
133     }
134 
135     while ((l = STRINGLIB(GroupGenerator_next)(&groupgen)) > 0) {
136         l = Py_MIN(l, Py_MAX(Py_MAX(remaining, min_width), 1));
137         n_zeros = Py_MAX(0, l - remaining);
138         n_chars = Py_MAX(0, Py_MIN(remaining, l));
139 
140         /* Use n_zero zero's and n_chars chars */
141 
142         /* Count only, don't do anything. */
143         count += (use_separator ? thousands_sep_len : 0) + n_zeros + n_chars;
144 
145         if (buffer) {
146             /* Copy into the output buffer. */
147             STRINGLIB(fill)(&digits_end, &buffer_end, n_chars, n_zeros,
148                  use_separator ? thousands_sep : NULL, thousands_sep_len);
149         }
150 
151         /* Use a separator next time. */
152         use_separator = 1;
153 
154         remaining -= n_chars;
155         min_width -= l;
156 
157         if (remaining <= 0 && min_width <= 0) {
158             loop_broken = 1;
159             break;
160         }
161         min_width -= thousands_sep_len;
162     }
163     if (!loop_broken) {
164         /* We left the loop without using a break statement. */
165 
166         l = Py_MAX(Py_MAX(remaining, min_width), 1);
167         n_zeros = Py_MAX(0, l - remaining);
168         n_chars = Py_MAX(0, Py_MIN(remaining, l));
169 
170         /* Use n_zero zero's and n_chars chars */
171         count += (use_separator ? thousands_sep_len : 0) + n_zeros + n_chars;
172         if (buffer) {
173             /* Copy into the output buffer. */
174             STRINGLIB(fill)(&digits_end, &buffer_end, n_chars, n_zeros,
175                  use_separator ? thousands_sep : NULL, thousands_sep_len);
176         }
177     }
178     return count;
179 }
180 
181