Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 872 lines (815 sloc) 26.154 kb
9c1d230 committing experimental branch content
Laurent Sansonetti authored
1 /*
2 * MacRuby implementation of Ruby 1.9's sprintf.c.
3 *
4 * This file is covered by the Ruby license. See COPYING for more details.
5 *
4aca101 @drernie Added 2010 Copyrights
drernie authored
6 * Copyright (C) 2007-2010, Apple Inc. All rights reserved.
9c1d230 committing experimental branch content
Laurent Sansonetti authored
7 * Copyright (C) 1993-2007 Yukihiro Matsumoto
8 * Copyright (C) 2000 Network Applied Communication Laboratory, Inc.
9 * Copyright (C) 2000 Information-technology Promotion Agency, Japan
10 */
11
cb65416 the great schism, part I
Laurent Sansonetti authored
12 #include <stdarg.h>
13
468a2ea Move Obj-C related headers around.
Thibault Martin-Lagardette authored
14 #include "ruby/macruby.h"
9c1d230 committing experimental branch content
Laurent Sansonetti authored
15 #include "ruby/encoding.h"
4cecb13 unicode string formats (a work in progress)
Laurent Sansonetti authored
16 #include "encoding.h"
9c1d230 committing experimental branch content
Laurent Sansonetti authored
17
18 /*
19 * call-seq:
20 * format(format_string [, arguments...] ) => string
21 * sprintf(format_string [, arguments...] ) => string
22 *
23 * Returns the string resulting from applying <i>format_string</i> to
24 * any additional arguments. Within the format string, any characters
25 * other than format sequences are copied to the result.
26 *
27 * The syntax of a format sequence is follows.
28 *
29 * %[flags][width][.precision]type
30 *
31 * A format
32 * sequence consists of a percent sign, followed by optional flags,
33 * width, and precision indicators, then terminated with a field type
34 * character. The field type controls how the corresponding
35 * <code>sprintf</code> argument is to be interpreted, while the flags
36 * modify that interpretation.
37 *
38 * The field type characters are:
39 *
40 * Field | Integer Format
41 * ------+--------------------------------------------------------------
42 * b | Convert argument as a binary number.
43 * | Negative numbers will be displayed as a two's complement
44 * | prefixed with `..1'.
45 * B | Equivalent to `b', but uses an uppercase 0B for prefix
46 * | in the alternative format by #.
47 * d | Convert argument as a decimal number.
48 * i | Identical to `d'.
49 * o | Convert argument as an octal number.
50 * | Negative numbers will be displayed as a two's complement
51 * | prefixed with `..7'.
52 * u | Identical to `d'.
53 * x | Convert argument as a hexadecimal number.
54 * | Negative numbers will be displayed as a two's complement
55 * | prefixed with `..f' (representing an infinite string of
56 * | leading 'ff's).
57 * X | Equivalent to `x', but uses uppercase letters.
58 *
59 * Field | Float Format
60 * ------+--------------------------------------------------------------
61 * e | Convert floating point argument into exponential notation
62 * | with one digit before the decimal point as [-]d.dddddde[+-]dd.
63 * | The precision specifies the number of digits after the decimal
64 * | point (defaulting to six).
65 * E | Equivalent to `e', but uses an uppercase E to indicate
66 * | the exponent.
67 * f | Convert floating point argument as [-]ddd.dddddd,
68 * | where the precision specifies the number of digits after
69 * | the decimal point.
70 * g | Convert a floating point number using exponential form
71 * | if the exponent is less than -4 or greater than or
72 * | equal to the precision, or in dd.dddd form otherwise.
73 * | The precision specifies the number of significant digits.
74 * G | Equivalent to `g', but use an uppercase `E' in exponent form.
75 *
76 * Field | Other Format
77 * ------+--------------------------------------------------------------
78 * c | Argument is the numeric code for a single character or
79 * | a single character string itself.
80 * p | The valuing of argument.inspect.
81 * s | Argument is a string to be substituted. If the format
82 * | sequence contains a precision, at most that many characters
83 * | will be copied.
84 *
85 * The flags modifies the behavior of the formats.
86 * The flag characters are:
87 *
88 * Flag | Applies to | Meaning
89 * ---------+---------------+-----------------------------------------
90 * space | bBdiouxX | Leave a space at the start of
91 * | eEfgG | non-negative numbers.
92 * | (numeric fmt) | For `o', `x', `X', `b' and `B', use
93 * | | a minus sign with absolute value for
94 * | | negative values.
95 * ---------+---------------+-----------------------------------------
96 * (digit)$ | all | Specifies the absolute argument number
97 * | | for this field. Absolute and relative
98 * | | argument numbers cannot be mixed in a
99 * | | sprintf string.
100 * ---------+---------------+-----------------------------------------
101 * # | bBoxX | Use an alternative format.
102 * | eEfgG | For the conversions `o', increase the precision
103 * | | until the first digit will be `0' if
104 * | | it is not formatted as complements.
105 * | | For the conversions `x', `X', `b' and `B'
106 * | | on non-zero, prefix the result with ``0x'',
107 * | | ``0X'', ``0b'' and ``0B'', respectively.
108 * | | For `e', `E', `f', `g', and 'G',
109 * | | force a decimal point to be added,
110 * | | even if no digits follow.
111 * | | For `g' and 'G', do not remove trailing zeros.
112 * ---------+---------------+-----------------------------------------
113 * + | bBdiouxX | Add a leading plus sign to non-negative
114 * | eEfgG | numbers.
115 * | (numeric fmt) | For `o', `x', `X', `b' and `B', use
116 * | | a minus sign with absolute value for
117 * | | negative values.
118 * ---------+---------------+-----------------------------------------
119 * - | all | Left-justify the result of this conversion.
120 * ---------+---------------+-----------------------------------------
121 * 0 (zero) | bBdiouxX | Pad with zeros, not spaces.
122 * | eEfgG | For `o', `x', `X', `b' and `B', radix-1
123 * | (numeric fmt) | is used for negative numbers formatted as
124 * | | complements.
125 * ---------+---------------+-----------------------------------------
126 * * | all | Use the next argument as the field width.
127 * | | If negative, left-justify the result. If the
128 * | | asterisk is followed by a number and a dollar
129 * | | sign, use the indicated argument as the width.
130 *
131 * Examples of flags:
132 *
133 * # `+' and space flag specifies the sign of non-negative numbers.
134 * sprintf("%d", 123) #=> "123"
135 * sprintf("%+d", 123) #=> "+123"
136 * sprintf("% d", 123) #=> " 123"
137 *
138 * # `#' flag for `o' increases number of digits to show `0'.
139 * # `+' and space flag changes format of negative numbers.
140 * sprintf("%o", 123) #=> "173"
141 * sprintf("%#o", 123) #=> "0173"
142 * sprintf("%+o", -123) #=> "-173"
143 * sprintf("%o", -123) #=> "..7605"
144 * sprintf("%#o", -123) #=> "..7605"
145 *
146 * # `#' flag for `x' add a prefix `0x' for non-zero numbers.
147 * # `+' and space flag disables complements for negative numbers.
148 * sprintf("%x", 123) #=> "7b"
149 * sprintf("%#x", 123) #=> "0x7b"
150 * sprintf("%+x", -123) #=> "-7b"
151 * sprintf("%x", -123) #=> "..f85"
152 * sprintf("%#x", -123) #=> "0x..f85"
153 * sprintf("%#x", 0) #=> "0"
154 *
155 * # `#' for `X' uses the prefix `0X'.
156 * sprintf("%X", 123) #=> "7B"
157 * sprintf("%#X", 123) #=> "0X7B"
158 *
159 * # `#' flag for `b' add a prefix `0b' for non-zero numbers.
160 * # `+' and space flag disables complements for negative numbers.
161 * sprintf("%b", 123) #=> "1111011"
162 * sprintf("%#b", 123) #=> "0b1111011"
163 * sprintf("%+b", -123) #=> "-1111011"
164 * sprintf("%b", -123) #=> "..10000101"
165 * sprintf("%#b", -123) #=> "0b..10000101"
166 * sprintf("%#b", 0) #=> "0"
167 *
168 * # `#' for `B' uses the prefix `0B'.
169 * sprintf("%B", 123) #=> "1111011"
170 * sprintf("%#B", 123) #=> "0B1111011"
171 *
172 * # `#' for `e' forces to show the decimal point.
173 * sprintf("%.0e", 1) #=> "1e+00"
174 * sprintf("%#.0e", 1) #=> "1.e+00"
175 *
176 * # `#' for `f' forces to show the decimal point.
177 * sprintf("%.0f", 1234) #=> "1234"
178 * sprintf("%#.0f", 1234) #=> "1234."
179 *
180 * # `#' for `g' forces to show the decimal point.
181 * # It also disables stripping lowest zeros.
182 * sprintf("%g", 123.4) #=> "123.4"
183 * sprintf("%#g", 123.4) #=> "123.400"
184 * sprintf("%g", 123456) #=> "123456"
185 * sprintf("%#g", 123456) #=> "123456."
186 *
187 * The field width is an optional integer, followed optionally by a
188 * period and a precision. The width specifies the minimum number of
189 * characters that will be written to the result for this field.
190 *
191 * Examples of width:
192 *
193 * # padding is done by spaces, width=20
194 * # 0 or radix-1. <------------------>
195 * sprintf("%20d", 123) #=> " 123"
196 * sprintf("%+20d", 123) #=> " +123"
197 * sprintf("%020d", 123) #=> "00000000000000000123"
198 * sprintf("%+020d", 123) #=> "+0000000000000000123"
199 * sprintf("% 020d", 123) #=> " 0000000000000000123"
200 * sprintf("%-20d", 123) #=> "123 "
201 * sprintf("%-+20d", 123) #=> "+123 "
202 * sprintf("%- 20d", 123) #=> " 123 "
203 * sprintf("%020x", -123) #=> "..ffffffffffffffff85"
204 *
205 * For
206 * numeric fields, the precision controls the number of decimal places
207 * displayed. For string fields, the precision determines the maximum
208 * number of characters to be copied from the string. (Thus, the format
209 * sequence <code>%10.10s</code> will always contribute exactly ten
210 * characters to the result.)
211 *
212 * Examples of precisions:
213 *
214 * # precision for `d', 'o', 'x' and 'b' is
215 * # minimum number of digits <------>
216 * sprintf("%20.8d", 123) #=> " 00000123"
217 * sprintf("%20.8o", 123) #=> " 00000173"
218 * sprintf("%20.8x", 123) #=> " 0000007b"
219 * sprintf("%20.8b", 123) #=> " 01111011"
220 * sprintf("%20.8d", -123) #=> " -00000123"
221 * sprintf("%20.8o", -123) #=> " ..777605"
222 * sprintf("%20.8x", -123) #=> " ..ffff85"
223 * sprintf("%20.8b", -11) #=> " ..110101"
224 *
225 * # "0x" and "0b" for `#x' and `#b' is not counted for
226 * # precision but "0" for `#o' is counted. <------>
227 * sprintf("%#20.8d", 123) #=> " 00000123"
228 * sprintf("%#20.8o", 123) #=> " 00000173"
229 * sprintf("%#20.8x", 123) #=> " 0x0000007b"
230 * sprintf("%#20.8b", 123) #=> " 0b01111011"
231 * sprintf("%#20.8d", -123) #=> " -00000123"
232 * sprintf("%#20.8o", -123) #=> " ..777605"
233 * sprintf("%#20.8x", -123) #=> " 0x..ffff85"
234 * sprintf("%#20.8b", -11) #=> " 0b..110101"
235 *
236 * # precision for `e' is number of
237 * # digits after the decimal point <------>
238 * sprintf("%20.8e", 1234.56789) #=> " 1.23456789e+03"
239 *
240 * # precision for `f' is number of
241 * # digits after the decimal point <------>
242 * sprintf("%20.8f", 1234.56789) #=> " 1234.56789000"
243 *
244 * # precision for `g' is number of
245 * # significant digits <------->
246 * sprintf("%20.8g", 1234.56789) #=> " 1234.5679"
247 *
248 * # <------->
249 * sprintf("%20.8g", 123456789) #=> " 1.2345679e+08"
250 *
251 * # precision for `s' is
252 * # maximum number of characters <------>
253 * sprintf("%20.8s", "string test") #=> " string t"
254 *
255 * Examples:
256 *
257 * sprintf("%d %04x", 123, 123) #=> "123 007b"
258 * sprintf("%08b '%4s'", 123, 123) #=> "01111011 ' 123'"
259 * sprintf("%1$*2$s %2$d %1$s", "hello", 8) #=> " hello 8 hello"
260 * sprintf("%1$*2$s %2$d", "hello", -8) #=> "hello -8"
261 * sprintf("%+g:% g:%-g", 1.23, 1.23, 1.23) #=> "+1.23: 1.23:1.23"
262 * sprintf("%u", -123) #=> "-123"
263 */
264
265 #define GETNTHARG(nth) \
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
266 ((nth >= argc) ? (rb_raise(rb_eArgError, "too few arguments"), 0) : \
267 argv[nth])
9c1d230 committing experimental branch content
Laurent Sansonetti authored
268
269 VALUE
270 rb_f_sprintf_imp(VALUE recv, SEL sel, int argc, VALUE *argv)
271 {
272 return rb_str_format(argc - 1, argv + 1, GETNTHARG(0));
273 }
274
275 VALUE
276 rb_f_sprintf(int argc, const VALUE *argv)
277 {
278 return rb_str_format(argc - 1, argv + 1, GETNTHARG(0));
279 }
280
281 VALUE
282 rb_enc_vsprintf(rb_encoding *enc, const char *fmt, va_list ap)
283 {
284 char buffer[512];
285 int n;
286 n = vsnprintf(buffer, sizeof buffer, fmt, ap);
287 return rb_enc_str_new(buffer, n, enc);
288 }
289
290 VALUE
291 rb_enc_sprintf(rb_encoding *enc, const char *format, ...)
292 {
293 va_list ap;
294 va_start(ap, format);
4cecb13 unicode string formats (a work in progress)
Laurent Sansonetti authored
295 VALUE result = rb_enc_vsprintf(enc, format, ap);
9c1d230 committing experimental branch content
Laurent Sansonetti authored
296 va_end(ap);
297 return result;
298 }
299
300 VALUE
301 rb_vsprintf(const char *fmt, va_list ap)
302 {
303 return rb_enc_vsprintf(NULL, fmt, ap);
304 }
305
306 VALUE
307 rb_sprintf(const char *format, ...)
308 {
309 va_list ap;
310 va_start(ap, format);
4cecb13 unicode string formats (a work in progress)
Laurent Sansonetti authored
311 VALUE result = rb_vsprintf(format, ap);
9c1d230 committing experimental branch content
Laurent Sansonetti authored
312 va_end(ap);
313 return result;
314 }
cb65416 the great schism, part I
Laurent Sansonetti authored
315
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
316 #define IS_NEG(num) RBIGNUM_NEGATIVE_P(num)
317 #define REL_REF 1
318 #define ABS_REF 2
319 #define NAMED_REF 3
320
321 #define REF_NAME(type) \
322 ((type) == REL_REF ? "relative" : (type) == ABS_REF ? "absolute" : "named")
323
324 #define SET_REF_TYPE(type) \
26e8df9 @Watson1978 sprintf() will be given the positional arguments and precision tokens gi...
Watson1978 authored
325 if (arg == 0 && ref_type != 0 && (type) != ref_type) { \
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
326 rb_raise(rb_eArgError, "can't mix %s references with %s references", \
327 REF_NAME(type), REF_NAME(ref_type)); \
328 } \
329 ref_type = (type);
330
331 #define GET_ARG() \
332 if (arg == 0) { \
333 SET_REF_TYPE(REL_REF); \
334 arg = GETNTHARG(j); \
335 j++; \
336 }
337
338 #define isprenum(ch) ((ch) == '-' || (ch) == ' ' || (ch) == '+')
339
cb65416 the great schism, part I
Laurent Sansonetti authored
340 static void
87717e7 some sprintf fixes
Laurent Sansonetti authored
341 pad_format_value(VALUE arg, long start, long width, VALUE pad)
cb65416 the great schism, part I
Laurent Sansonetti authored
342 {
87717e7 some sprintf fixes
Laurent Sansonetti authored
343 const long slen = rb_str_chars_len(arg);
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
344 if (width <= slen) {
345 return;
346 }
347 if (start < 0) {
348 start += slen + 1;
349 }
350 width -= slen;
351 do {
87717e7 some sprintf fixes
Laurent Sansonetti authored
352 rb_str_update(arg, start, 0, pad);
4cecb13 unicode string formats (a work in progress)
Laurent Sansonetti authored
353 }
354 while (--width > 0);
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
355 }
cb65416 the great schism, part I
Laurent Sansonetti authored
356
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
357 static long
4cecb13 unicode string formats (a work in progress)
Laurent Sansonetti authored
358 cstr_update(UChar **str, long *str_len, long start, long num, VALUE replace)
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
359 {
4cecb13 unicode string formats (a work in progress)
Laurent Sansonetti authored
360 const long len = *str_len;
361 long replace_len = replace == 0 ? 0 : rb_str_chars_len(replace);
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
362 if (start + num > len) {
363 num = len - start;
364 }
365 if (replace_len >= num) {
4cecb13 unicode string formats (a work in progress)
Laurent Sansonetti authored
366 *str_len = len + replace_len - num;
367 *str = (UChar *)xrealloc(*str,
368 sizeof(UChar) * (len + replace_len - num));
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
369 }
370 if (replace_len != num) {
4cecb13 unicode string formats (a work in progress)
Laurent Sansonetti authored
371 bcopy(*str + start + num, *str + start + replace_len,
372 sizeof(UChar) * (len - start - num));
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
373 }
374 if (replace_len > 0) {
d1673a2 introduce a better unichar API, which should be as fast as before the re...
Laurent Sansonetti authored
375 RB_STR_GET_UCHARS(replace, replace_chars, replace_len2);
376 assert(replace_len2 == replace_len);
4cecb13 unicode string formats (a work in progress)
Laurent Sansonetti authored
377 bcopy(replace_chars, *str + start, sizeof(UChar) * replace_len);
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
378 }
379 return replace_len - num;
380 }
381
4cecb13 unicode string formats (a work in progress)
Laurent Sansonetti authored
382 static VALUE
87717e7 some sprintf fixes
Laurent Sansonetti authored
383 get_named_arg(UChar *format_str, long format_len, long *i, VALUE hash)
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
384 {
385 if (TYPE(hash) != T_HASH) {
386 rb_raise(rb_eArgError,
387 "hash required for named references");
388 }
4cecb13 unicode string formats (a work in progress)
Laurent Sansonetti authored
389 UChar closing = format_str[(*i)++] + 2;
390 UChar *str_ptr = &format_str[*i];
18c6303 @Watson1978 fixed the bug of sprintf() within "%<named>" format
Watson1978 authored
391 long length = 0;
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
392 while (*i < format_len && format_str[*i] != closing) {
393 (*i)++;
18c6303 @Watson1978 fixed the bug of sprintf() within "%<named>" format
Watson1978 authored
394 length++;
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
395 }
396 if (*i == format_len) {
397 rb_raise(rb_eArgError,
398 "malformed name - unmatched parenthesis");
399 }
18c6303 @Watson1978 fixed the bug of sprintf() within "%<named>" format
Watson1978 authored
400 VALUE substr = rb_unicode_str_new(str_ptr, (size_t)length);
4cecb13 unicode string formats (a work in progress)
Laurent Sansonetti authored
401 hash = rb_hash_aref(hash, ID2SYM(rb_intern_str(substr)));
402 return hash;
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
403 }
404
405 // XXX look for arguments that are altered but not duped
406 VALUE
407 rb_str_format(int argc, const VALUE *argv, VALUE fmt)
408 {
409 bool tainted = OBJ_TAINTED(fmt);
4cecb13 unicode string formats (a work in progress)
Laurent Sansonetti authored
410
411 long format_len = 0;
d1673a2 introduce a better unichar API, which should be as fast as before the re...
Laurent Sansonetti authored
412 UChar *format_str = rb_str_xcopy_uchars(fmt, &format_len);
4cecb13 unicode string formats (a work in progress)
Laurent Sansonetti authored
413 if (format_len == 0) {
414 goto bail;
415 }
416
417 long num, pos;
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
418 int j = 0;
419 int ref_type = 0;
87717e7 some sprintf fixes
Laurent Sansonetti authored
420 long format_str_capa = format_len;
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
421
87717e7 some sprintf fixes
Laurent Sansonetti authored
422 for (long i = 0; i < format_len; i++) {
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
423 if (format_str[i] != '%') {
cb65416 the great schism, part I
Laurent Sansonetti authored
424 continue;
425 }
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
426 if (format_str[i + 1] == '%') {
a539763 fixed a few bugs in #sprintf
Laurent Sansonetti authored
427 num = cstr_update(&format_str, &format_str_capa, i, 1, 0);
428 format_len += num;
cb65416 the great schism, part I
Laurent Sansonetti authored
429 continue;
430 }
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
431
432 bool sharp_flag = false;
433 bool space_flag = false;
434 bool plus_flag = false;
435 bool minus_flag = false;
436 bool zero_flag = false;
42f22c9 @Watson1978 sprintf() will throw an exception when was given width format twice.
Watson1978 authored
437 bool width_flag = false;
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
438 bool precision_flag = false;
9031bde @Watson1978 sprintf() will throw an exception when was given named format twice.
Watson1978 authored
439 bool named_flag = false;
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
440 bool complete = false;
441 VALUE arg = 0;
442 long width = 0;
443 long precision = 0;
444 int base = 0;
87717e7 some sprintf fixes
Laurent Sansonetti authored
445 VALUE negative_pad = 0;
446 VALUE sharp_pad = rb_str_new2("");
447 const long start = i;
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
448
42f22c9 @Watson1978 sprintf() will throw an exception when was given width format twice.
Watson1978 authored
449 #define CHECK_FOR_WIDTH() \
450 if (width_flag) { \
451 rb_raise(rb_eArgError, "width given twice"); \
452 }
453
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
454 while (i++ < format_len) {
cb65416 the great schism, part I
Laurent Sansonetti authored
455 switch (format_str[i]) {
456 case '#':
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
457 sharp_flag = true;
cb65416 the great schism, part I
Laurent Sansonetti authored
458 break;
459
460 case '*':
42f22c9 @Watson1978 sprintf() will throw an exception when was given width format twice.
Watson1978 authored
461 CHECK_FOR_WIDTH();
462 width_flag = true;
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
463 if (format_str[++i] == '<' || format_str[i] == '{') {
464 SET_REF_TYPE(NAMED_REF);
465 width = NUM2LONG(rb_Integer(get_named_arg(format_str,
466 format_len, &i, GETNTHARG(0))));
467 }
468 else {
469 if (isprenum(format_str[i])) {
470 i--;
471 break;
472 }
4cecb13 unicode string formats (a work in progress)
Laurent Sansonetti authored
473
474 num = rb_uchar_strtol(format_str, format_len, i, &pos);
475 if (pos == i--) {
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
476 SET_REF_TYPE(REL_REF);
477 width = NUM2LONG(rb_Integer(GETNTHARG(j)));
478 j++;
479 }
4cecb13 unicode string formats (a work in progress)
Laurent Sansonetti authored
480 else if (format_str[pos] == '$') {
c9853df @Watson1978 String#% will throw an ArgumentError when was given "*0$" format.
Watson1978 authored
481 if (num == 0) {
482 rb_raise(rb_eArgError, "invalid absolute argument");
483 }
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
484 SET_REF_TYPE(ABS_REF);
485 width = NUM2LONG(rb_Integer(GETNTHARG(num - 1)));
4cecb13 unicode string formats (a work in progress)
Laurent Sansonetti authored
486 i = pos;
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
487 }
488 }
489 if (width < 0) {
490 minus_flag = true;
491 width = -width;
492 }
493 break;
494
495 case ' ':
496 if (!plus_flag) {
497 space_flag = true;
498 }
499 break;
500
501 case '+':
502 plus_flag = true;
503 space_flag = false;
504 break;
505
506 case '-':
507 zero_flag = false;
508 minus_flag = true;
509 break;
510
511 case '0':
512 if (!precision_flag && !minus_flag) {
513 zero_flag = true;
514 }
515 break;
516
517 case '1':
518 case '2':
519 case '3':
520 case '4':
521 case '5':
522 case '6':
523 case '7':
524 case '8':
525 case '9':
4cecb13 unicode string formats (a work in progress)
Laurent Sansonetti authored
526 num = rb_uchar_strtol(format_str, format_len, i, &pos);
527 i = pos;
528 if (format_str[pos] == '$') {
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
529 if (num == 0) {
530 rb_raise(rb_eArgError, "invalid absolute argument");
531 }
09f5ee7 @Watson1978 sprintf() will throw an exception when was given positional format twice...
Watson1978 authored
532 if (arg != 0) {
533 rb_raise(rb_eArgError, "value given twice");
534 }
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
535 SET_REF_TYPE(ABS_REF);
536 arg = GETNTHARG(num - 1);
537 }
538 else {
539 SET_REF_TYPE(REL_REF);
540 width = num;
541 i--;
42f22c9 @Watson1978 sprintf() will throw an exception when was given width format twice.
Watson1978 authored
542 CHECK_FOR_WIDTH();
543 width_flag = true;
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
544 }
545 break;
546
547 case '.':
3314262 @Watson1978 sprintf() will throw an exception when was given precision format twice.
Watson1978 authored
548 if (precision_flag) {
549 rb_raise(rb_eArgError, "precision given twice");
550 }
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
551 zero_flag = false;
552 precision_flag = true;
553 if (format_str[++i] == '*') {
554 if (format_str[++i] == '<' || format_str[i] == '{') {
555 SET_REF_TYPE(NAMED_REF);
556 precision = NUM2LONG(rb_Integer(get_named_arg(
557 format_str, format_len, &i, GETNTHARG(0))));
558 }
559 else {
560 if (isprenum(format_str[i])) {
561 i--;
562 break;
563 }
4cecb13 unicode string formats (a work in progress)
Laurent Sansonetti authored
564
565 num = rb_uchar_strtol(format_str, format_len,
566 i, &pos);
225e90f @Watson1978 sprintf() will be given a format which is width's * and precision's *.
Watson1978 authored
567 if (format_str[pos] == '$') {
c9853df @Watson1978 String#% will throw an ArgumentError when was given "*0$" format.
Watson1978 authored
568 if (num == 0) {
569 rb_raise(rb_eArgError, "invalid absolute argument");
570 }
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
571 SET_REF_TYPE(ABS_REF);
572 precision = NUM2LONG(rb_Integer(GETNTHARG(
573 num - 1)));
4cecb13 unicode string formats (a work in progress)
Laurent Sansonetti authored
574 i = pos;
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
575 }
225e90f @Watson1978 sprintf() will be given a format which is width's * and precision's *.
Watson1978 authored
576 else {
577 SET_REF_TYPE(REL_REF);
578 precision = NUM2LONG(rb_Integer(GETNTHARG(j)));
579 j++;
580 i--;
581 }
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
582 }
583 }
584 else if (isdigit(format_str[i])) {
4cecb13 unicode string formats (a work in progress)
Laurent Sansonetti authored
585 precision = rb_uchar_strtol(format_str, format_len,
586 i, &pos);
587 i = pos - 1;
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
588 }
589 else {
590 rb_raise(rb_eArgError, "invalid precision");
591 }
592
593 if (precision < 0) {
594 precision = 0;
595 }
596 break;
597
598 case '<':
599 case '{':
d8ba17f @Watson1978 fixed the bug of sprintf() within "%{named}" format.
Watson1978 authored
600 {
601 char term = (format_str[i] == '<') ? '>' : '}';
602
9031bde @Watson1978 sprintf() will throw an exception when was given named format twice.
Watson1978 authored
603 if (named_flag) {
604 rb_raise(rb_eArgError, "named given twice");
605 }
26e8df9 @Watson1978 sprintf() will be given the positional arguments and precision tokens gi...
Watson1978 authored
606 if (ref_type != 0) {
607 rb_raise(rb_eArgError, "named after numbered");
608 }
9031bde @Watson1978 sprintf() will throw an exception when was given named format twice.
Watson1978 authored
609 named_flag = true;
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
610 SET_REF_TYPE(NAMED_REF);
611 arg = get_named_arg(format_str, format_len, &i,
612 GETNTHARG(0));
d8ba17f @Watson1978 fixed the bug of sprintf() within "%{named}" format.
Watson1978 authored
613 if (term == '}') {
614 if (TYPE(arg) != T_STRING) {
615 arg = rb_obj_as_string(arg);
616 }
617 goto format_s;
618 }
cb65416 the great schism, part I
Laurent Sansonetti authored
619 break;
d8ba17f @Watson1978 fixed the bug of sprintf() within "%{named}" format.
Watson1978 authored
620 }
cb65416 the great schism, part I
Laurent Sansonetti authored
621
622 case 'd':
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
623 case 'D':
cb65416 the great schism, part I
Laurent Sansonetti authored
624 case 'i':
625 case 'u':
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
626 case 'U':
627 base = 10;
628 complete = true;
629 break;
630
cb65416 the great schism, part I
Laurent Sansonetti authored
631 case 'x':
632 case 'X':
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
633 base = 16;
87717e7 some sprintf fixes
Laurent Sansonetti authored
634 negative_pad = rb_str_new2("f");
635 sharp_pad = rb_str_new2("0x");
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
636 complete = true;
cb65416 the great schism, part I
Laurent Sansonetti authored
637 break;
638
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
639 case 'o':
640 case 'O':
641 base = 8;
87717e7 some sprintf fixes
Laurent Sansonetti authored
642 negative_pad = rb_str_new2("7");
643 sharp_pad = rb_str_new2("0");
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
644 complete = true;
cb65416 the great schism, part I
Laurent Sansonetti authored
645 break;
646
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
647 case 'B':
648 case 'b':
649 base = 2;
87717e7 some sprintf fixes
Laurent Sansonetti authored
650 negative_pad = rb_str_new2("1");
651 sharp_pad = rb_str_new2("0b");
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
652 complete = true;
cb65416 the great schism, part I
Laurent Sansonetti authored
653 break;
654
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
655 case 'c':
656 case 'C':
657 GET_ARG();
658 if (TYPE(arg) == T_STRING) {
abe5fa7 @Watson1978 sprintf("%c") will throw an exception when does not pass one character.
Watson1978 authored
659 if(RSTRING_LEN(arg) != 1) {
660 rb_raise(rb_eArgError, "%%c requires a character");
661 }
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
662 }
663 else {
110e345 fixed some bugs in Numeric#chr
Laurent Sansonetti authored
664 long num = NUM2LONG(arg);
665 if (num < 0 || i > 0xff) {
666 rb_raise(rb_eRangeError, "%ld out of char range",
667 num);
668 }
669 char c = (char)num;
670 arg = rb_str_new(&c, 1);
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
671 }
672 complete = true;
673 break;
674
675 case 'f':
cb65416 the great schism, part I
Laurent Sansonetti authored
676 case 'F':
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
677 case 'e':
cb65416 the great schism, part I
Laurent Sansonetti authored
678 case 'E':
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
679 case 'g':
cb65416 the great schism, part I
Laurent Sansonetti authored
680 case 'G':
681 case 'a':
682 case 'A':
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
683 {
684 // here we construct a new format str and then use
685 // c's sprintf. why? because floats are retarded
686 GET_ARG();
687 double value = RFLOAT_VALUE(rb_Float(arg));
688 complete = true;
689
690 if (isnan(value) || isinf(value)) {
691 arg = rb_str_new2((char *)(isnan(value) ? "NaN" :
692 value < 0 ? "-Inf" : "Inf"));
693 if (isnan(value) || value > 0) {
694 if (plus_flag) {
87717e7 some sprintf fixes
Laurent Sansonetti authored
695 rb_str_update(arg, 0, 0, rb_str_new2("+"));
cb65416 the great schism, part I
Laurent Sansonetti authored
696 }
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
697 else if (space_flag) {
87717e7 some sprintf fixes
Laurent Sansonetti authored
698 rb_str_update(arg, 0, 0, rb_str_new2(" "));
cb65416 the great schism, part I
Laurent Sansonetti authored
699 }
700 }
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
701 break;
702 }
703
4cecb13 unicode string formats (a work in progress)
Laurent Sansonetti authored
704 arg = rb_unicode_str_new(&format_str[i], 1);
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
705 if (precision_flag) {
706 rb_str_update(arg, 0, 0, rb_big2str(LONG2NUM(precision),
707 10));
87717e7 some sprintf fixes
Laurent Sansonetti authored
708 rb_str_update(arg, 0, 0, rb_str_new2("."));
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
709 }
710 rb_str_update(arg, 0, 0, rb_big2str(LONG2NUM(width), 10));
711 if (minus_flag) {
87717e7 some sprintf fixes
Laurent Sansonetti authored
712 rb_str_update(arg, 0, 0, rb_str_new2("-"));
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
713 }
714 else if (zero_flag) {
87717e7 some sprintf fixes
Laurent Sansonetti authored
715 rb_str_update(arg, 0, 0, rb_str_new2("0"));
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
716 }
717 if (plus_flag) {
87717e7 some sprintf fixes
Laurent Sansonetti authored
718 rb_str_update(arg, 0, 0, rb_str_new2("+"));
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
719 }
720 else if (space_flag) {
87717e7 some sprintf fixes
Laurent Sansonetti authored
721 rb_str_update(arg, 0, 0, rb_str_new2(" "));
cb65416 the great schism, part I
Laurent Sansonetti authored
722 }
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
723 if (sharp_flag) {
87717e7 some sprintf fixes
Laurent Sansonetti authored
724 rb_str_update(arg, 0, 0, rb_str_new2("#"));
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
725 }
87717e7 some sprintf fixes
Laurent Sansonetti authored
726 rb_str_update(arg, 0, 0, rb_str_new2("%"));
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
727
4cecb13 unicode string formats (a work in progress)
Laurent Sansonetti authored
728 char *ptr;
729 asprintf(&ptr, RSTRING_PTR(arg), value);
730 arg = rb_str_new2(ptr);
731 free(ptr);
cb65416 the great schism, part I
Laurent Sansonetti authored
732 break;
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
733 }
cb65416 the great schism, part I
Laurent Sansonetti authored
734
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
735 case 's':
736 case 'S':
cb65416 the great schism, part I
Laurent Sansonetti authored
737 case 'p':
738 case '@':
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
739 GET_ARG();
87717e7 some sprintf fixes
Laurent Sansonetti authored
740 arg = (tolower(format_str[i]) != 's'
741 ? rb_inspect(arg) : TYPE(arg) == T_STRING
742 ? rb_str_new3(arg) : rb_obj_as_string(arg));
d8ba17f @Watson1978 fixed the bug of sprintf() within "%{named}" format.
Watson1978 authored
743 format_s:
87717e7 some sprintf fixes
Laurent Sansonetti authored
744 if (precision_flag && precision < rb_str_chars_len(arg)) {
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
745 CFStringPad((CFMutableStringRef)arg, NULL, precision,
746 0);
cb65416 the great schism, part I
Laurent Sansonetti authored
747 }
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
748 complete = true;
cb65416 the great schism, part I
Laurent Sansonetti authored
749 break;
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
750
b62b438 @Watson1978 modified the style of r4928.
Watson1978 authored
751 case '\n':
752 case '\0':
753 if (format_str[i - 1] == '%') {
421ab55 @Watson1978 sprintf() will not throw exception when passed the format of single % ch...
Watson1978 authored
754 if (format_str[i] == '\n') {
755 arg = rb_str_new("%\n", 2);
756 }
757 else {
b62b438 @Watson1978 modified the style of r4928.
Watson1978 authored
758 if (format_len > i) {
759 arg = rb_str_new("%\0", 2);
760 }
761 else {
762 arg = rb_str_new("%", 1);
763 }
421ab55 @Watson1978 sprintf() will not throw exception when passed the format of single % ch...
Watson1978 authored
764 }
765 complete = true;
b62b438 @Watson1978 modified the style of r4928.
Watson1978 authored
766 break;
421ab55 @Watson1978 sprintf() will not throw exception when passed the format of single % ch...
Watson1978 authored
767 }
b62b438 @Watson1978 modified the style of r4928.
Watson1978 authored
768 rb_raise(rb_eArgError, "malformed format string - %%%c",
769 format_str[i]);
770 break;
771
772 default:
773 rb_raise(rb_eArgError, "malformed format string - %%%c",
774 format_str[i]);
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
775 }
776 if (!complete) {
777 continue;
cb65416 the great schism, part I
Laurent Sansonetti authored
778 }
779
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
780 GET_ARG();
cb65416 the great schism, part I
Laurent Sansonetti authored
781
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
782 if (base != 0) {
783 bool sign_pad = false;
784 unsigned long num_index = 0;
87717e7 some sprintf fixes
Laurent Sansonetti authored
785 VALUE zero_pad = rb_str_new2("0");
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
786
787 VALUE num = rb_Integer(arg);
788 if (TYPE(num) == T_FIXNUM) {
789 num = rb_int2big(FIX2LONG(num));
cb65416 the great schism, part I
Laurent Sansonetti authored
790 }
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
791 if (plus_flag || space_flag) {
792 sign_pad = 1;
793 }
794 if (IS_NEG(num)) {
795 num_index = 1;
87717e7 some sprintf fixes
Laurent Sansonetti authored
796 if (!sign_pad && negative_pad != 0) {
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
797 zero_pad = negative_pad;
798 num = rb_big_clone(num);
799 rb_big_2comp(num);
800 }
cb65416 the great schism, part I
Laurent Sansonetti authored
801 }
802
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
803 arg = rb_big2str(num, base);
87717e7 some sprintf fixes
Laurent Sansonetti authored
804 if (!sign_pad && IS_NEG(num) && negative_pad != 0) {
a539763 fixed a few bugs in #sprintf
Laurent Sansonetti authored
805 UChar neg = rb_str_get_uchar(negative_pad, 0);
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
806 if (base == 8) {
a539763 fixed a few bugs in #sprintf
Laurent Sansonetti authored
807 UChar c = rb_str_get_uchar(arg, 1);
808 const long len = rb_str_chars_len(arg) - 1;
809 c |= ((~0 << 3) >> ((3 * len)
810 % (sizeof(BDIGIT) * 8))) & ~(~0 << 3);
811 rb_str_update(arg, 1, 1, rb_unicode_str_new(&c, 1));
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
812 }
a539763 fixed a few bugs in #sprintf
Laurent Sansonetti authored
813 for (int i = 1, count = rb_str_chars_len(arg); i < count;
814 i++) {
815 if (rb_str_get_uchar(arg, i) != neg) {
816 break;
817 }
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
818 num_index++;
819 }
a539763 fixed a few bugs in #sprintf
Laurent Sansonetti authored
820 rb_str_update(arg, 0, num_index, negative_pad);
821 rb_str_update(arg, 0, 0, rb_str_new2(".."));
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
822 num_index = 2;
823 }
824 if (precision_flag) {
4cecb13 unicode string formats (a work in progress)
Laurent Sansonetti authored
825 pad_format_value(arg, num_index,
826 precision + (IS_NEG(num)
87717e7 some sprintf fixes
Laurent Sansonetti authored
827 && (sign_pad || negative_pad == 0) ? 1 : 0),
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
828 zero_pad);
829 }
e9d7b0c sprintf can now be free of C++ evil
Laurent Sansonetti authored
830 if (sharp_flag && rb_cmpint(num, Qfalse, Qfalse) != 0) {
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
831 rb_str_update(arg, sign_pad, 0, (VALUE)sharp_pad);
832 num_index += 2;
833 }
834 if (sign_pad && RBIGNUM_POSITIVE_P(num)) {
835 rb_str_update(arg, 0, 0, (VALUE)(plus_flag ?
87717e7 some sprintf fixes
Laurent Sansonetti authored
836 rb_str_new2("+") : rb_str_new2(" ")));
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
837 num_index++;
838 }
839 if (zero_flag) {
840 pad_format_value(arg, num_index, width, zero_pad);
841 }
842 if (ISUPPER(format_str[i])) {
843 CFStringUppercase((CFMutableStringRef)arg, NULL);
844 }
845 }
846
847 if (OBJ_TAINTED(arg)) {
848 tainted = true;
849 }
cb65416 the great schism, part I
Laurent Sansonetti authored
850
d6e38f5 #sprintf: don't try to mutate symbols
Laurent Sansonetti authored
851 if (TYPE(arg) == T_SYMBOL) {
852 // Because symbols are not mutable and pad_format_value()
853 // mutates its first argument.
854 arg = rb_sym_to_s(arg);
855 }
87717e7 some sprintf fixes
Laurent Sansonetti authored
856 pad_format_value(arg, minus_flag ? -1 : 0, width, rb_str_new2(" "));
857 num = cstr_update(&format_str, &format_str_capa, start,
858 i - start + 1, arg);
859 format_len += num;
77a4b42 new sprintf implementation (thanks Daniel Cavanagh)
Laurent Sansonetti authored
860 i += num;
861 break;
862 }
863 }
cb65416 the great schism, part I
Laurent Sansonetti authored
864
4cecb13 unicode string formats (a work in progress)
Laurent Sansonetti authored
865 bail:
866 fmt = rb_unicode_str_new(format_str, format_len);
867 if (tainted) {
868 OBJ_TAINT(fmt);
869 }
870 return fmt;
cb65416 the great schism, part I
Laurent Sansonetti authored
871 }
Something went wrong with that request. Please try again.