/src/mruby/mrbgems/mruby-sprintf/src/sprintf.c
Line | Count | Source |
1 | | /* |
2 | | ** sprintf.c - Kernel.#sprintf |
3 | | ** |
4 | | ** See Copyright Notice in mruby.h |
5 | | */ |
6 | | |
7 | | #include <mruby.h> |
8 | | #include <mruby/string.h> |
9 | | #include <mruby/hash.h> |
10 | | #include <mruby/numeric.h> |
11 | | #include <mruby/internal.h> |
12 | | #include <string.h> |
13 | | #include <ctype.h> |
14 | | |
15 | 0 | #define BIT_DIGITS(N) (((N)*146)/485 + 1) /* log2(10) =~ 146/485 */ |
16 | 21 | #define BITSPERDIG MRB_INT_BIT |
17 | 21 | #define EXTENDSIGN(n, l) (((~0U << (n)) >> (((n)*(l)) % BITSPERDIG)) & ~(~0U << (n))) |
18 | | |
19 | | mrb_value mrb_bint_2comp(mrb_state *mrb, mrb_value x); |
20 | | |
21 | | static char* |
22 | | remove_sign_bits(char *str, int base) |
23 | 38 | { |
24 | 38 | char *t; |
25 | | |
26 | 38 | t = str; |
27 | 38 | if (base == 16) { |
28 | 88 | while (*t == 'f') { |
29 | 77 | t++; |
30 | 77 | } |
31 | 11 | } |
32 | 27 | else if (base == 8) { |
33 | 21 | *t |= EXTENDSIGN(3, strlen(t)); |
34 | 483 | while (*t == '7') { |
35 | 462 | t++; |
36 | 462 | } |
37 | 21 | } |
38 | 6 | else if (base == 2) { |
39 | 180 | while (*t == '1') { |
40 | 174 | t++; |
41 | 174 | } |
42 | 6 | } |
43 | | |
44 | 38 | return t; |
45 | 38 | } |
46 | | |
47 | | static char * |
48 | | mrb_uint_to_cstr(char *buf, size_t len, mrb_int num, int base) |
49 | 812 | { |
50 | 812 | char *b = buf + len - 1; |
51 | 812 | const int mask = base-1; |
52 | 812 | int shift; |
53 | 812 | mrb_uint val = (mrb_uint)num; |
54 | | |
55 | 812 | if (num == 0) { |
56 | 774 | buf[0] = '0'; buf[1] = '\0'; |
57 | 774 | return buf; |
58 | 774 | } |
59 | 38 | switch (base) { |
60 | 11 | case 16: shift = 4; break; |
61 | 21 | case 8: shift = 3; break; |
62 | 6 | case 2: shift = 1; break; |
63 | 0 | default: return NULL; |
64 | 38 | } |
65 | 38 | *--b = '\0'; |
66 | 1.02k | do { |
67 | 1.02k | *--b = mrb_digitmap[(int)(val & mask)]; |
68 | 1.02k | } while (val >>= shift); |
69 | | |
70 | 38 | if (num < 0) { |
71 | 38 | b = remove_sign_bits(b, base); |
72 | 38 | } |
73 | | |
74 | 38 | return b; |
75 | 38 | } |
76 | | |
77 | 39.1k | #define FNONE 0 |
78 | 1.60k | #define FSHARP 1 |
79 | 3.32k | #define FMINUS 2 |
80 | 7 | #define FPLUS 4 |
81 | 2.41k | #define FZERO 8 |
82 | 68 | #define FSPACE 16 |
83 | 3.47k | #define FWIDTH 32 |
84 | 1.60k | #define FPREC 64 |
85 | 2.55k | #define FPREC0 128 |
86 | | |
87 | | /* Format specifier types for lookup table */ |
88 | 23.2k | #define FMT_INVALID 0 |
89 | 3.40k | #define FMT_FLAG 1 /* space, #, +, -, 0 */ |
90 | 3.30k | #define FMT_DIGIT 2 /* 1-9 for width */ |
91 | 0 | #define FMT_NAMED 3 /* < { for named args */ |
92 | 0 | #define FMT_WIDTH 4 /* * for width from arg */ |
93 | 2 | #define FMT_PREC 5 /* . for precision */ |
94 | 38.1k | #define FMT_LITERAL 6 /* % \n \0 */ |
95 | 120 | #define FMT_CHAR 7 /* c */ |
96 | 0 | #define FMT_STRING 8 /* s p */ |
97 | 1.62k | #define FMT_INTEGER 9 /* d i o x X b B u */ |
98 | 14 | #define FMT_FLOAT 10 /* f g G e E */ |
99 | | |
100 | | /* Format specifier info structure */ |
101 | | typedef struct { |
102 | | int type; /* FMT_* type */ |
103 | | int base; /* number base for integers */ |
104 | | int subtype; /* format-specific subtype */ |
105 | | } fmt_spec_t; |
106 | | |
107 | | |
108 | | /* Get format specifier info for character c */ |
109 | 23.2k | static inline fmt_spec_t get_fmt_spec(unsigned char c) { |
110 | 23.2k | static const fmt_spec_t invalid = {FMT_INVALID, 0, 0}; |
111 | | |
112 | 23.2k | switch (c) { |
113 | | /* Control characters and whitespace */ |
114 | 0 | case '\0': case '\n': |
115 | 0 | return (fmt_spec_t){FMT_LITERAL, 0, 0}; |
116 | | |
117 | | /* Flags */ |
118 | 61 | case ' ': return (fmt_spec_t){FMT_FLAG, 0, FSPACE}; |
119 | 780 | case '#': return (fmt_spec_t){FMT_FLAG, 0, FSHARP}; |
120 | 0 | case '+': return (fmt_spec_t){FMT_FLAG, 0, FPLUS}; |
121 | 80 | case '-': return (fmt_spec_t){FMT_FLAG, 0, FMINUS}; |
122 | 780 | case '0': return (fmt_spec_t){FMT_FLAG, 0, FZERO}; |
123 | | |
124 | | /* Width digits */ |
125 | 798 | case '1': return (fmt_spec_t){FMT_DIGIT, 0, 1}; |
126 | 2 | case '2': return (fmt_spec_t){FMT_DIGIT, 0, 2}; |
127 | 5 | case '3': return (fmt_spec_t){FMT_DIGIT, 0, 3}; |
128 | 18 | case '4': return (fmt_spec_t){FMT_DIGIT, 0, 4}; |
129 | 0 | case '5': return (fmt_spec_t){FMT_DIGIT, 0, 5}; |
130 | 55 | case '6': return (fmt_spec_t){FMT_DIGIT, 0, 6}; |
131 | 0 | case '7': return (fmt_spec_t){FMT_DIGIT, 0, 7}; |
132 | 774 | case '8': return (fmt_spec_t){FMT_DIGIT, 0, 8}; |
133 | 0 | case '9': return (fmt_spec_t){FMT_DIGIT, 0, 9}; |
134 | | |
135 | | /* Width and precision */ |
136 | 0 | case '*': return (fmt_spec_t){FMT_WIDTH, 0, 0}; |
137 | 1 | case '.': return (fmt_spec_t){FMT_PREC, 0, 0}; |
138 | | |
139 | | /* Named arguments */ |
140 | 0 | case '<': return (fmt_spec_t){FMT_NAMED, 0, '<'}; |
141 | 0 | case '{': return (fmt_spec_t){FMT_NAMED, 0, '{'}; |
142 | | |
143 | | /* Literal percent */ |
144 | 19.0k | case '%': return (fmt_spec_t){FMT_LITERAL, 0, '%'}; |
145 | | |
146 | | /* Character formatting */ |
147 | 60 | case 'c': return (fmt_spec_t){FMT_CHAR, 0, 0}; |
148 | | |
149 | | /* String formatting */ |
150 | 0 | case 's': return (fmt_spec_t){FMT_STRING, 0, 0}; |
151 | 0 | case 'p': return (fmt_spec_t){FMT_STRING, 0, 1}; /* inspect format */ |
152 | | |
153 | | /* Integer formatting */ |
154 | 1 | case 'd': return (fmt_spec_t){FMT_INTEGER, 10, 1}; /* signed decimal */ |
155 | 0 | case 'i': return (fmt_spec_t){FMT_INTEGER, 10, 1}; /* signed decimal */ |
156 | 0 | case 'u': return (fmt_spec_t){FMT_INTEGER, 10, 1}; /* unsigned (same as signed in mruby) */ |
157 | 21 | case 'o': return (fmt_spec_t){FMT_INTEGER, 8, 0}; /* octal */ |
158 | 785 | case 'x': return (fmt_spec_t){FMT_INTEGER, 16, 0}; /* hex lowercase */ |
159 | 0 | case 'X': return (fmt_spec_t){FMT_INTEGER, 16, 1}; /* hex uppercase */ |
160 | 6 | case 'b': return (fmt_spec_t){FMT_INTEGER, 2, 0}; /* binary lowercase */ |
161 | 0 | case 'B': return (fmt_spec_t){FMT_INTEGER, 2, 1}; /* binary uppercase */ |
162 | | |
163 | | /* Float formatting */ |
164 | 6 | case 'f': return (fmt_spec_t){FMT_FLOAT, 0, 'f'}; |
165 | 1 | case 'e': return (fmt_spec_t){FMT_FLOAT, 0, 'e'}; |
166 | 0 | case 'E': return (fmt_spec_t){FMT_FLOAT, 0, 'E'}; |
167 | 0 | case 'g': return (fmt_spec_t){FMT_FLOAT, 0, 'g'}; |
168 | 0 | case 'G': return (fmt_spec_t){FMT_FLOAT, 0, 'G'}; |
169 | | |
170 | 0 | default: |
171 | 0 | return invalid; |
172 | 23.2k | } |
173 | 23.2k | } |
174 | | |
175 | | #ifndef MRB_NO_FLOAT |
176 | | static int |
177 | | fmt_float(char *buf, size_t buf_size, char fmt, int flags, int width, int prec, mrb_float f) |
178 | 7 | { |
179 | 7 | char sign = '\0'; |
180 | 7 | int left_align = 0; |
181 | 7 | int zero_pad = 0; |
182 | | |
183 | 7 | if (flags & FSHARP) fmt |= 0x80; |
184 | 7 | if (flags & FPLUS) sign = '+'; |
185 | 7 | if (flags & FMINUS) left_align = 1; |
186 | 7 | if (flags & FZERO) zero_pad = 1; |
187 | 7 | if (flags & FSPACE) sign = ' '; |
188 | | |
189 | 7 | int len = mrb_format_float(f, buf, buf_size, fmt, prec, sign); |
190 | | |
191 | | // buf[0] < '0' returns true if the first character is space, + or - |
192 | | // buf[1] < '9' matches a digit, and doesn't match when we get back +nan or +inf |
193 | 7 | if (buf[0] < '0' && buf[1] <= '9' && zero_pad) { |
194 | 0 | buf++; |
195 | 0 | width--; |
196 | 0 | len--; |
197 | 0 | } |
198 | 7 | if (*buf < '0' || *buf >= '9') { |
199 | | // For inf or nan, we don't want to zero pad. |
200 | 0 | zero_pad = 0; |
201 | 0 | } |
202 | 7 | if (len >= width) { |
203 | 6 | return len; |
204 | 6 | } |
205 | 1 | buf[width] = '\0'; |
206 | 1 | if (left_align) { |
207 | 1 | memset(&buf[len], ' ', width - len); |
208 | 1 | return width; |
209 | 1 | } |
210 | 0 | memmove(&buf[width - len], buf, len); |
211 | 0 | if (zero_pad) { |
212 | 0 | memset(buf, '0', width - len); |
213 | 0 | } |
214 | 0 | else { |
215 | 0 | memset(buf, ' ', width - len); |
216 | 0 | } |
217 | 0 | return width; |
218 | 1 | } |
219 | | #endif |
220 | | |
221 | 41.7k | #define CHECK(l) do { \ |
222 | 41.7k | if (blen+(l) >= bsiz) {\ |
223 | 200 | while (blen+(l) >= bsiz) {\ |
224 | 126 | if (bsiz > MRB_INT_MAX/2) mrb_raise(mrb, E_ARGUMENT_ERROR, "too big specifier");\ |
225 | 126 | bsiz*=2;\ |
226 | 126 | }\ |
227 | 74 | mrb_str_resize(mrb, result, bsiz);\ |
228 | 74 | }\ |
229 | 41.7k | buf = RSTRING_PTR(result);\ |
230 | 41.7k | } while (0) |
231 | | |
232 | 40.1k | #define PUSH(s, l) do { \ |
233 | 40.1k | CHECK(l);\ |
234 | 40.1k | memcpy(&buf[blen], s, l);\ |
235 | 40.1k | blen += (mrb_int)(l);\ |
236 | 40.1k | } while (0) |
237 | | |
238 | 890 | #define FILL(c, l) do { \ |
239 | 890 | CHECK(l);\ |
240 | 890 | memset(&buf[blen], c, l);\ |
241 | 890 | blen += (l);\ |
242 | 890 | } while (0) |
243 | | |
244 | | static void |
245 | | check_next_arg(mrb_state *mrb, int posarg, int nextarg) |
246 | 83 | { |
247 | 83 | switch (posarg) { |
248 | 0 | case -1: |
249 | 0 | mrb_raisef(mrb, E_ARGUMENT_ERROR, "unnumbered(%d) mixed with numbered", nextarg); |
250 | 0 | break; |
251 | 0 | case -2: |
252 | 0 | mrb_raisef(mrb, E_ARGUMENT_ERROR, "unnumbered(%d) mixed with named", nextarg); |
253 | 0 | break; |
254 | 83 | default: |
255 | 83 | break; |
256 | 83 | } |
257 | 83 | } |
258 | | |
259 | | static void |
260 | | check_pos_arg(mrb_state *mrb, int posarg, mrb_int n) |
261 | 798 | { |
262 | 798 | if (posarg > 0) { |
263 | 0 | mrb_raisef(mrb, E_ARGUMENT_ERROR, "numbered(%i) after unnumbered(%d)", |
264 | 0 | n, posarg); |
265 | 0 | } |
266 | 798 | if (posarg == -2) { |
267 | 0 | mrb_raisef(mrb, E_ARGUMENT_ERROR, "numbered(%i) after named", n); |
268 | 0 | } |
269 | 798 | if (n < 1) { |
270 | 0 | mrb_raisef(mrb, E_ARGUMENT_ERROR, "invalid index - %i$", n); |
271 | 0 | } |
272 | 798 | } |
273 | | |
274 | | static void |
275 | | check_name_arg(mrb_state *mrb, int posarg, const char *name, size_t len) |
276 | 0 | { |
277 | 0 | if (posarg > 0) { |
278 | 0 | mrb_raisef(mrb, E_ARGUMENT_ERROR, "named%l after unnumbered(%d)", |
279 | 0 | name, len, posarg); |
280 | 0 | } |
281 | 0 | if (posarg == -1) { |
282 | 0 | mrb_raisef(mrb, E_ARGUMENT_ERROR, "named%l after numbered", name, len); |
283 | 0 | } |
284 | 0 | } |
285 | | |
286 | 83 | #define GETNEXTARG() (\ |
287 | 83 | check_next_arg(mrb, posarg, nextarg),\ |
288 | 83 | (posarg = nextarg++, GETNTHARG(posarg))) |
289 | | |
290 | 880 | #define GETARG() (!mrb_undef_p(nextvalue) ? nextvalue : GETNEXTARG()) |
291 | | |
292 | 798 | #define GETPOSARG(n) (\ |
293 | 798 | check_pos_arg(mrb, posarg, n),\ |
294 | 798 | (posarg = -1, GETNTHARG(n))) |
295 | | |
296 | | #define GETNTHARG(nth) \ |
297 | 881 | ((nth >= argc) ? (mrb_raise(mrb, E_ARGUMENT_ERROR, "too few arguments"), mrb_undef_value()) : argv[nth]) |
298 | | |
299 | 0 | #define CHECKNAMEARG(name, len) (\ |
300 | 0 | check_name_arg(mrb, posarg, name, len),\ |
301 | 0 | posarg = -2) |
302 | | |
303 | 1.65k | #define GETNUM(n, val) do { \ |
304 | 1.65k | if (!(p = get_num(mrb, p, end, &(n)))) \ |
305 | 1.65k | mrb_raise(mrb, E_ARGUMENT_ERROR, #val " too big"); \ |
306 | 1.65k | } while(0) |
307 | | |
308 | 0 | #define GETASTER(num) do { \ |
309 | 0 | mrb_value tmp_v; \ |
310 | 0 | t = p++; \ |
311 | 0 | GETNUM(n, val); \ |
312 | 0 | if (*p == '$') { \ |
313 | 0 | tmp_v = GETPOSARG(n); \ |
314 | 0 | } \ |
315 | 0 | else { \ |
316 | 0 | tmp_v = GETNEXTARG(); \ |
317 | 0 | p = t; \ |
318 | 0 | } \ |
319 | 0 | num = (int)mrb_as_int(mrb, tmp_v); \ |
320 | 0 | } while (0) |
321 | | |
322 | | static const char* |
323 | | get_num(mrb_state *mrb, const char *p, const char *end, int *valp) |
324 | 1.65k | { |
325 | 1.65k | char *e; |
326 | 1.65k | mrb_int n; |
327 | 1.65k | if (!mrb_read_int(p, end, &e, &n) || INT_MAX < n) { |
328 | 0 | return NULL; |
329 | 0 | } |
330 | 1.65k | *valp = (int)n; |
331 | 1.65k | return e; |
332 | 1.65k | } |
333 | | |
334 | | static void |
335 | | get_hash(mrb_state *mrb, mrb_value *hash, mrb_int argc, const mrb_value *argv) |
336 | 0 | { |
337 | 0 | if (!mrb_undef_p(*hash)) return; |
338 | 0 | if (argc != 2) { |
339 | 0 | mrb_raise(mrb, E_ARGUMENT_ERROR, "one hash required"); |
340 | 0 | } |
341 | 0 | mrb_value tmp = mrb_check_hash_type(mrb, argv[1]); |
342 | 0 | if (mrb_nil_p(tmp)) { |
343 | 0 | mrb_raise(mrb, E_ARGUMENT_ERROR, "one hash required"); |
344 | 0 | } |
345 | 0 | *hash = tmp; |
346 | 0 | } |
347 | | |
348 | | static mrb_value |
349 | | mrb_str_format(mrb_state *mrb, mrb_int argc, const mrb_value *argv, mrb_value fmt) |
350 | 223 | { |
351 | 223 | const char *p, *end; |
352 | 223 | char *buf; |
353 | 223 | mrb_int blen; |
354 | 223 | mrb_int bsiz; |
355 | 223 | mrb_value result; |
356 | 223 | int n; |
357 | 223 | int width; |
358 | 223 | int prec; |
359 | 223 | int nextarg = 1; |
360 | 223 | int posarg = 0; |
361 | 223 | mrb_value nextvalue; |
362 | 223 | mrb_value str; |
363 | 223 | mrb_value hash = mrb_undef_value(); |
364 | | |
365 | 223 | #define CHECK_FOR_WIDTH(f) \ |
366 | 854 | if ((f) & FWIDTH) { \ |
367 | 0 | mrb_raise(mrb, E_ARGUMENT_ERROR, "width given twice"); \ |
368 | 0 | } \ |
369 | 854 | if ((f) & FPREC0) { \ |
370 | 0 | mrb_raise(mrb, E_ARGUMENT_ERROR, "width after precision"); \ |
371 | 0 | } |
372 | 223 | #define CHECK_FOR_FLAGS(f) \ |
373 | 1.70k | if ((f) & FWIDTH) { \ |
374 | 0 | mrb_raise(mrb, E_ARGUMENT_ERROR, "flag after width"); \ |
375 | 0 | } \ |
376 | 1.70k | if ((f) & FPREC0) { \ |
377 | 0 | mrb_raise(mrb, E_ARGUMENT_ERROR, "flag after precision"); \ |
378 | 0 | } |
379 | | |
380 | 223 | argc++; |
381 | 223 | argv--; |
382 | 223 | mrb_ensure_string_type(mrb, fmt); |
383 | | /* Duplicate the format string so that to_s/inspect callbacks invoked |
384 | | during the loop cannot invalidate p/end by mutating the original |
385 | | via String#replace or similar. mrb_str_dup shares the underlying |
386 | | buffer, so this is O(1); String#replace on the original goes |
387 | | through str_replace which decrements the shared refcount, leaving |
388 | | our copy's buffer intact. */ |
389 | 223 | fmt = mrb_str_dup_frozen(mrb, fmt); |
390 | 223 | p = RSTRING_PTR(fmt); |
391 | 223 | end = p + RSTRING_LEN(fmt); |
392 | 223 | blen = 0; |
393 | | /* Estimate initial buffer size to reduce reallocations: |
394 | | * - format string length (for literal text) |
395 | | * - base headroom (120 bytes) |
396 | | * - per-specifier headroom (24 bytes each) |
397 | | * - capped at 4096 to prevent over-allocation |
398 | | */ |
399 | 223 | bsiz = (end - p) + 120; |
400 | 211k | for (const char *scan = p; scan < end; scan++) { |
401 | 211k | if (*scan == '%') bsiz += 24; |
402 | 211k | } |
403 | 223 | if (bsiz > 4096) bsiz = 4096; |
404 | 223 | result = mrb_str_new_capa(mrb, bsiz); |
405 | 223 | buf = RSTRING_PTR(result); |
406 | 223 | memset(buf, 0, bsiz); |
407 | | |
408 | 223 | int ai = mrb_gc_arena_save(mrb); |
409 | 20.1k | for (; p < end; p++) { |
410 | 20.1k | const char *t; |
411 | 20.1k | mrb_sym id = 0; |
412 | 20.1k | int flags = FNONE; |
413 | | |
414 | 187k | for (t = p; t < end && *t != '%'; t++) |
415 | 167k | ; |
416 | 20.1k | if (t + 1 == end) { |
417 | | /* % at the bottom */ |
418 | 0 | mrb_raise(mrb, E_ARGUMENT_ERROR, "incomplete format specifier; use %% (double %) instead"); |
419 | 0 | } |
420 | 20.1k | PUSH(p, t - p); |
421 | 20.1k | if (t >= end) |
422 | 201 | goto sprint_exit; /* end of fmt string */ |
423 | | |
424 | 19.9k | p = t + 1; /* skip '%' */ |
425 | | |
426 | 19.9k | width = prec = -1; |
427 | 19.9k | nextvalue = mrb_undef_value(); |
428 | | |
429 | 23.2k | retry: |
430 | 23.2k | if (p >= end) { |
431 | 1 | mrb_raise(mrb, E_ARGUMENT_ERROR, "malformed format string - unexpected end"); |
432 | 1 | } |
433 | 23.2k | { |
434 | 23.2k | fmt_spec_t spec = get_fmt_spec(*p); |
435 | | |
436 | 23.2k | switch (spec.type) { |
437 | 0 | case FMT_INVALID: |
438 | 0 | mrb_raisef(mrb, E_ARGUMENT_ERROR, "malformed format string - %%%c", *p); |
439 | 0 | break; |
440 | | |
441 | 1.70k | case FMT_FLAG: |
442 | 1.70k | CHECK_FOR_FLAGS(flags); |
443 | 1.70k | flags |= spec.subtype; |
444 | 1.70k | p++; |
445 | 1.70k | goto retry; |
446 | | |
447 | 1.65k | case FMT_DIGIT: |
448 | 1.65k | GETNUM(n, width); |
449 | 1.65k | if (*p == '$') { |
450 | 798 | if (!mrb_undef_p(nextvalue)) { |
451 | 0 | mrb_raisef(mrb, E_ARGUMENT_ERROR, "value given twice - %i$", n); |
452 | 0 | } |
453 | 798 | nextvalue = GETPOSARG(n); |
454 | 798 | p++; |
455 | 798 | goto retry; |
456 | 798 | } |
457 | 1.70k | CHECK_FOR_WIDTH(flags); |
458 | 854 | width = n; |
459 | 854 | flags |= FWIDTH; |
460 | 854 | goto retry; |
461 | | |
462 | 0 | case FMT_NAMED: { |
463 | 0 | const char *start = p; |
464 | 0 | char term = (spec.subtype == '<') ? '>' : '}'; |
465 | |
|
466 | 0 | for (; p < end && *p != term; ) |
467 | 0 | p++; |
468 | 0 | if (id) { |
469 | 0 | mrb_raisef(mrb, E_ARGUMENT_ERROR, "name%l after <%n>", |
470 | 0 | start, p - start + 1, id); |
471 | 0 | } |
472 | 0 | CHECKNAMEARG(start, p - start + 1); |
473 | 0 | get_hash(mrb, &hash, argc, argv); |
474 | 0 | id = mrb_intern_check(mrb, start + 1, p - start - 1); |
475 | 0 | if (id) { |
476 | 0 | nextvalue = mrb_hash_fetch(mrb, hash, mrb_symbol_value(id), mrb_undef_value()); |
477 | 0 | } |
478 | 0 | if (!id || mrb_undef_p(nextvalue)) { |
479 | 0 | mrb_raisef(mrb, E_KEY_ERROR, "key%l not found", start, p - start + 1); |
480 | 0 | } |
481 | 0 | if (term == '}') goto format_s; |
482 | 0 | p++; |
483 | 0 | goto retry; |
484 | 0 | } |
485 | | |
486 | 0 | case FMT_WIDTH: |
487 | 0 | CHECK_FOR_WIDTH(flags); |
488 | 0 | flags |= FWIDTH; |
489 | 0 | GETASTER(width); |
490 | 0 | if (width > INT16_MAX || INT16_MIN > width) { |
491 | 0 | mrb_raise(mrb, E_ARGUMENT_ERROR, "width too big"); |
492 | 0 | } |
493 | 0 | if (width < 0) { |
494 | 0 | flags |= FMINUS; |
495 | 0 | width = -width; |
496 | 0 | } |
497 | 0 | p++; |
498 | 0 | goto retry; |
499 | | |
500 | 1 | case FMT_PREC: |
501 | 1 | if (flags & FPREC0) { |
502 | 0 | mrb_raise(mrb, E_ARGUMENT_ERROR, "precision given twice"); |
503 | 0 | } |
504 | 1 | flags |= FPREC|FPREC0; |
505 | | |
506 | 1 | p++; |
507 | 1 | if (*p == '*') { |
508 | 0 | GETASTER(prec); |
509 | 0 | if (prec < 0) { /* ignore negative precision */ |
510 | 0 | flags &= ~FPREC; |
511 | 0 | } |
512 | 0 | p++; |
513 | 0 | goto retry; |
514 | 0 | } |
515 | 1 | GETNUM(prec, precision); |
516 | 1 | goto retry; |
517 | | |
518 | 19.0k | case FMT_LITERAL: |
519 | 19.0k | if (spec.subtype == 0) { /* \n or \0 */ |
520 | 0 | p--; |
521 | 0 | } |
522 | 19.0k | if (flags != FNONE) { |
523 | 0 | mrb_raise(mrb, E_ARGUMENT_ERROR, "invalid format character - %"); |
524 | 0 | } |
525 | 19.0k | PUSH("%", 1); |
526 | 19.0k | break; |
527 | | |
528 | 19.0k | case FMT_CHAR: { |
529 | | /* CHARACTER FORMATTING (%c) */ |
530 | 60 | mrb_value val = GETARG(); |
531 | 60 | const char *c; |
532 | 60 | char cbuf[4]; /* stack buffer for character bytes */ |
533 | 60 | int clen; |
534 | | |
535 | 60 | if (mrb_integer_p(val)) { |
536 | | /* Integer: encode directly to stack buffer (no allocation) */ |
537 | 60 | mrb_int code = mrb_integer(val); |
538 | | #ifdef MRB_UTF8_STRING |
539 | | clen = (int)mrb_utf8_to_buf(cbuf, (uint32_t)code); |
540 | | if (clen == 0) clen = 1; /* invalid codepoint: write single byte */ |
541 | | #else |
542 | 60 | cbuf[0] = (char)(code & 0xff); |
543 | 60 | clen = 1; |
544 | 60 | #endif |
545 | 60 | c = cbuf; |
546 | 60 | } |
547 | 0 | else { |
548 | | /* String: validate and use directly */ |
549 | 0 | mrb_value tmp = mrb_check_string_type(mrb, val); |
550 | 0 | if (mrb_nil_p(tmp)) { |
551 | 0 | mrb_raise(mrb, E_ARGUMENT_ERROR, "invalid character"); |
552 | 0 | } |
553 | 0 | if (RSTRING_LEN(tmp) != 1) { |
554 | 0 | mrb_raise(mrb, E_ARGUMENT_ERROR, "%c requires a character"); |
555 | 0 | } |
556 | 0 | c = RSTRING_PTR(tmp); |
557 | 0 | clen = (int)RSTRING_LEN(tmp); |
558 | 0 | } |
559 | | |
560 | | /* Format and output the character with width/alignment */ |
561 | 60 | n = clen; |
562 | 60 | if (!(flags & FWIDTH)) { |
563 | 0 | PUSH(c, n); |
564 | 0 | } |
565 | 60 | else if ((flags & FMINUS)) { |
566 | 60 | PUSH(c, n); |
567 | 60 | if (width>0) FILL(' ', width-1); |
568 | 60 | } |
569 | 0 | else { |
570 | 0 | if (width>0) FILL(' ', width-1); |
571 | 0 | PUSH(c, n); |
572 | 0 | } |
573 | 60 | } |
574 | 60 | break; |
575 | | |
576 | 60 | case FMT_STRING: |
577 | 0 | format_s: |
578 | 0 | { |
579 | | /* STRING FORMATTING (%s, %p) */ |
580 | 0 | mrb_value arg = GETARG(); |
581 | 0 | mrb_int len; |
582 | 0 | mrb_int slen; |
583 | | |
584 | | /* Convert to string (with inspect for %p) */ |
585 | 0 | if (spec.subtype == 1) arg = mrb_inspect(mrb, arg); /* 'p' format */ |
586 | 0 | str = mrb_obj_as_string(mrb, arg); |
587 | 0 | len = RSTRING_LEN(str); |
588 | | |
589 | | /* Update result string length for embedded strings */ |
590 | 0 | if (RSTRING(result)->flags & MRB_STR_EMBED) { |
591 | 0 | mrb_int tmp_n = len; |
592 | 0 | RSTRING(result)->flags &= ~MRB_STR_EMBED_LEN_MASK; |
593 | 0 | RSTRING(result)->flags |= tmp_n << MRB_STR_EMBED_LEN_SHIFT; |
594 | 0 | } |
595 | 0 | else { |
596 | 0 | RSTRING(result)->as.heap.len = blen; |
597 | 0 | } |
598 | | |
599 | | /* Handle precision and width formatting */ |
600 | 0 | if (flags&(FPREC|FWIDTH)) { |
601 | 0 | slen = RSTRING_LEN(str); |
602 | 0 | if (slen < 0) { |
603 | 0 | mrb_raise(mrb, E_ARGUMENT_ERROR, "invalid mbstring sequence"); |
604 | 0 | } |
605 | 0 | if ((flags&FPREC) && (prec < slen)) { |
606 | 0 | char *p = RSTRING_PTR(str) + prec; |
607 | 0 | slen = prec; |
608 | 0 | len = (mrb_int)(p - RSTRING_PTR(str)); |
609 | 0 | } |
610 | | /* Apply width formatting with padding */ |
611 | 0 | if ((flags&FWIDTH) && (width > slen)) { |
612 | 0 | width -= (int)slen; |
613 | 0 | if (!(flags&FMINUS)) { |
614 | 0 | FILL(' ', width); |
615 | 0 | } |
616 | 0 | PUSH(RSTRING_PTR(str), len); |
617 | 0 | if (flags&FMINUS) { |
618 | 0 | FILL(' ', width); |
619 | 0 | } |
620 | 0 | break; |
621 | 0 | } |
622 | 0 | } |
623 | 0 | PUSH(RSTRING_PTR(str), len); |
624 | 0 | mrb_gc_arena_restore(mrb, ai); |
625 | 0 | } |
626 | 0 | break; |
627 | | |
628 | 813 | case FMT_INTEGER: { |
629 | | /* INTEGER FORMATTING (%d, %i, %o, %x, %X, %b, %B, %u) */ |
630 | 813 | mrb_value val = GETARG(); |
631 | 813 | char nbuf[69], *s; |
632 | 813 | const char *prefix = NULL; |
633 | 813 | int sign = 0, dots = 0; |
634 | 813 | char sc = 0; |
635 | 813 | char fc = 0; |
636 | 813 | mrb_int v = 0; |
637 | 813 | int base; |
638 | 813 | int len; |
639 | | |
640 | | /* Determine base and signedness from lookup table */ |
641 | 813 | base = spec.base; |
642 | 813 | if (spec.subtype == 1) { /* signed formats: d, i, u */ |
643 | 1 | sign = 1; |
644 | 1 | } |
645 | | |
646 | | /* Set prefix for alternative format (#) */ |
647 | 813 | if (flags & FSHARP) { |
648 | 774 | switch (base) { |
649 | 0 | case 8: prefix = "0"; break; |
650 | 774 | case 16: prefix = (spec.subtype == 1) ? "0X" : "0x"; break; |
651 | 0 | case 2: prefix = (spec.subtype == 1) ? "0B" : "0b"; break; |
652 | 0 | default: break; |
653 | 774 | } |
654 | 774 | } |
655 | | |
656 | | /* Convert value to integer and format as string */ |
657 | 1.58k | bin_retry: |
658 | 1.58k | switch (mrb_type(val)) { |
659 | 0 | #ifndef MRB_NO_FLOAT |
660 | 774 | case MRB_TT_FLOAT: |
661 | 774 | val = mrb_float_to_integer(mrb, val); |
662 | 774 | goto bin_retry; |
663 | 0 | #endif |
664 | 0 | #ifdef MRB_USE_BIGINT |
665 | 0 | case MRB_TT_BIGINT: |
666 | 0 | { |
667 | 0 | mrb_int n = (mrb_bint_cmp(mrb, val, mrb_fixnum_value(0))); |
668 | 0 | mrb_bool need_dots = ((flags & FPLUS) == 0) && (base == 16 || base == 8 || base == 2) && n < 0; |
669 | 0 | if (need_dots) { |
670 | 0 | val = mrb_bint_2comp(mrb, val); |
671 | 0 | dots = 1; |
672 | 0 | v = -1; |
673 | 0 | } |
674 | 0 | mrb_value str = mrb_bint_to_s(mrb, val, base); |
675 | 0 | s = RSTRING_PTR(str); |
676 | 0 | len = (int)RSTRING_LEN(str); |
677 | 0 | } |
678 | 0 | goto str_skip; |
679 | 0 | #endif |
680 | 0 | case MRB_TT_STRING: |
681 | 0 | val = mrb_str_to_integer(mrb, val, 0, TRUE); |
682 | 0 | goto bin_retry; |
683 | 813 | case MRB_TT_INTEGER: |
684 | 813 | v = mrb_integer(val); |
685 | 813 | break; |
686 | 0 | default: |
687 | 0 | v = mrb_as_int(mrb, val); |
688 | 0 | break; |
689 | 1.58k | } |
690 | | |
691 | 813 | if (sign) { |
692 | 1 | if (v >= 0) { |
693 | 0 | if (flags & FPLUS) { |
694 | 0 | sc = '+'; |
695 | 0 | width--; |
696 | 0 | } |
697 | 0 | else if (flags & FSPACE) { |
698 | 0 | sc = ' '; |
699 | 0 | width--; |
700 | 0 | } |
701 | 0 | } |
702 | 1 | else { |
703 | 1 | sc = '-'; |
704 | 1 | width--; |
705 | 1 | } |
706 | 1 | s = mrb_int_to_cstr(nbuf, sizeof(nbuf), v, base); |
707 | 1 | if (v < 0) s++; /* skip minus sign */ |
708 | 1 | } |
709 | 812 | else { |
710 | | /* print as unsigned */ |
711 | 812 | s = mrb_uint_to_cstr(nbuf, sizeof(nbuf), v, base); |
712 | 812 | if (v < 0) { |
713 | 38 | dots = 1; |
714 | 38 | } |
715 | 812 | } |
716 | | |
717 | 813 | { |
718 | 813 | size_t size = strlen(s); |
719 | | /* PARANOID: assert(size <= MRB_INT_MAX) */ |
720 | 813 | len = (int)size; |
721 | 813 | } |
722 | | |
723 | 813 | #ifdef MRB_USE_BIGINT |
724 | 813 | str_skip: |
725 | 813 | #endif |
726 | 813 | switch (base) { |
727 | 785 | case 16: |
728 | 785 | fc = 'f'; break; |
729 | 21 | case 8: |
730 | 21 | fc = '7'; break; |
731 | 6 | case 2: |
732 | 6 | fc = '1'; break; |
733 | 813 | } |
734 | | |
735 | 813 | if (dots) { |
736 | 38 | if (base == 8 && (*s == '1' || *s == '3')) { |
737 | 0 | s++; len--; |
738 | 0 | } |
739 | 38 | while (*s == fc) { |
740 | 0 | s++; len--; |
741 | 0 | } |
742 | 38 | } |
743 | | /* Convert to uppercase for X, B formats */ |
744 | 813 | if (spec.subtype == 1) { /* uppercase formats: X, B */ |
745 | 1 | char *pp = s; |
746 | 1 | int c; |
747 | 5 | while ((c = (int)(unsigned char)*pp) != 0) { |
748 | 4 | *pp = toupper(c); |
749 | 4 | pp++; |
750 | 4 | } |
751 | 1 | if (base == 16) { |
752 | 0 | fc = 'F'; |
753 | 0 | } |
754 | 1 | } |
755 | | |
756 | 813 | if (prefix && !prefix[1]) { /* octal */ |
757 | 0 | if (dots) { |
758 | 0 | prefix = NULL; |
759 | 0 | } |
760 | 0 | else if (len == 1 && *s == '0') { |
761 | 0 | len = 0; |
762 | 0 | if (flags & FPREC) prec--; |
763 | 0 | } |
764 | 0 | else if ((flags & FPREC) && (prec > len)) { |
765 | 0 | prefix = NULL; |
766 | 0 | } |
767 | 0 | } |
768 | 813 | else if (len == 1 && *s == '0') { |
769 | 774 | prefix = NULL; |
770 | 774 | } |
771 | | |
772 | 813 | if (prefix) { |
773 | 0 | size_t size = strlen(prefix); |
774 | | /* PARANOID: assert(size <= MRB_INT_MAX). |
775 | | * this check is absolutely paranoid. */ |
776 | 0 | width -= (int)size; |
777 | 0 | } |
778 | | |
779 | 813 | if ((flags & (FZERO|FMINUS|FPREC)) == FZERO) { |
780 | 774 | prec = width; |
781 | 774 | width = 0; |
782 | 774 | } |
783 | 39 | else { |
784 | 39 | if (prec < len) { |
785 | 39 | if (!prefix && prec == 0 && len == 1 && *s == '0') len = 0; |
786 | 39 | prec = len; |
787 | 39 | } |
788 | 39 | width -= prec; |
789 | 39 | } |
790 | | |
791 | 813 | if (!(flags&FMINUS) && width > 0) { |
792 | 0 | FILL(' ', width); |
793 | 0 | width = 0; |
794 | 0 | } |
795 | | |
796 | 813 | if (sc) PUSH(&sc, 1); |
797 | | |
798 | 813 | if (prefix) { |
799 | 0 | int plen = (int)strlen(prefix); |
800 | 0 | PUSH(prefix, plen); |
801 | 0 | } |
802 | 813 | if (dots) { |
803 | 38 | prec -= 2; |
804 | 38 | width -= 2; |
805 | 38 | PUSH("..", 2); |
806 | 38 | if (*s != fc) { |
807 | 38 | FILL(fc, 1); |
808 | 38 | prec--; width--; |
809 | 38 | } |
810 | 38 | } |
811 | | |
812 | 813 | if (prec > len) { |
813 | 774 | CHECK(prec - len); |
814 | 774 | if ((flags & (FMINUS|FPREC)) != FMINUS) { |
815 | 774 | char c = '0'; |
816 | 774 | FILL(c, prec - len); |
817 | 774 | } |
818 | 0 | else if (v < 0) { |
819 | 0 | FILL(fc, prec - len); |
820 | 0 | } |
821 | 774 | } |
822 | 813 | PUSH(s, len); |
823 | 813 | if (width > 0) { |
824 | 18 | FILL(' ', width); |
825 | 18 | } |
826 | 813 | } |
827 | 813 | break; |
828 | | |
829 | 813 | case FMT_FLOAT: { |
830 | | /* FLOAT FORMATTING (%f, %g, %G, %e, %E) */ |
831 | | #ifdef MRB_NO_FLOAT |
832 | | mrb_raisef(mrb, E_ARGUMENT_ERROR, "%%%c not supported with MRB_NO_FLOAT defined", spec.subtype); |
833 | | #else |
834 | 7 | mrb_value val = GETARG(); |
835 | 7 | double fval; |
836 | 7 | mrb_int need = 6; |
837 | | |
838 | 7 | fval = mrb_as_float(mrb, val); |
839 | 7 | if (!isfinite(fval)) { |
840 | 0 | const char *expr; |
841 | 0 | const int elen = 3; |
842 | 0 | char sign = '\0'; |
843 | |
|
844 | 0 | if (isnan(fval)) { |
845 | 0 | expr = "NaN"; |
846 | 0 | } |
847 | 0 | else { |
848 | 0 | expr = "Inf"; |
849 | 0 | } |
850 | 0 | need = elen; |
851 | 0 | if (!isnan(fval) && fval < 0.0) |
852 | 0 | sign = '-'; |
853 | 0 | else if (flags & (FPLUS|FSPACE)) |
854 | 0 | sign = (flags & FPLUS) ? '+' : ' '; |
855 | 0 | if (sign) |
856 | 0 | need++; |
857 | 0 | if ((flags & FWIDTH) && need < width) |
858 | 0 | need = width; |
859 | |
|
860 | 0 | if (need < 0) { |
861 | 0 | mrb_raise(mrb, E_ARGUMENT_ERROR, "width too big"); |
862 | 0 | } |
863 | 0 | FILL(' ', need); |
864 | 0 | if (flags & FMINUS) { |
865 | 0 | if (sign) |
866 | 0 | buf[blen - need--] = sign; |
867 | 0 | memcpy(&buf[blen - need], expr, elen); |
868 | 0 | } |
869 | 0 | else { |
870 | 0 | if (sign) |
871 | 0 | buf[blen - elen - 1] = sign; |
872 | 0 | memcpy(&buf[blen - elen], expr, (size_t)elen); |
873 | 0 | } |
874 | 0 | break; |
875 | 0 | } |
876 | | |
877 | 7 | need = 0; |
878 | 7 | if (*p != 'e' && *p != 'E') { |
879 | 6 | int i; |
880 | 6 | frexp(fval, &i); |
881 | 6 | if (i > 0) |
882 | 0 | need = BIT_DIGITS(i); |
883 | 6 | } |
884 | 7 | if (need > MRB_INT_MAX - ((flags&FPREC) ? prec : 6)) { |
885 | 0 | too_big_width_prec: |
886 | 0 | mrb_raise(mrb, E_ARGUMENT_ERROR, |
887 | 0 | (width > prec ? "width too big" : "prec too big")); |
888 | 0 | } |
889 | 7 | need += (flags&FPREC) ? prec : 6; |
890 | 7 | if ((flags&FWIDTH) && need < width) |
891 | 1 | need = width; |
892 | 7 | if ((mrb_int)need > MRB_INT_MAX - 20) { |
893 | 0 | goto too_big_width_prec; |
894 | 0 | } |
895 | 7 | need += 20; |
896 | | |
897 | 7 | CHECK(need); |
898 | 7 | n = fmt_float(&buf[blen], need, spec.subtype, flags, width, prec, fval); |
899 | 7 | if (n < 0 || n >= need) { |
900 | 0 | mrb_raise(mrb, E_RUNTIME_ERROR, "formatting error"); |
901 | 0 | } |
902 | 7 | blen += n; |
903 | 7 | #endif |
904 | 7 | } |
905 | 0 | break; |
906 | 23.2k | } |
907 | 23.2k | } |
908 | 23.2k | } |
909 | | |
910 | 222 | sprint_exit: |
911 | 222 | mrb_str_resize(mrb, result, blen); |
912 | | |
913 | 222 | return result; |
914 | 223 | } |
915 | | |
916 | | /* |
917 | | * call-seq: |
918 | | * format(format_string [, arguments...] ) -> string |
919 | | * sprintf(format_string [, arguments...] ) -> string |
920 | | * |
921 | | * Returns the string resulting from applying *format_string* to |
922 | | * any additional arguments. Within the format string, any characters |
923 | | * other than format sequences are copied to the result. |
924 | | * |
925 | | * The syntax of a format sequence is follows. |
926 | | * |
927 | | * %[flags][width][.precision]type |
928 | | * |
929 | | * A format |
930 | | * sequence consists of a percent sign, followed by optional flags, |
931 | | * width, and precision indicators, then terminated with a field type |
932 | | * character. The field type controls how the corresponding |
933 | | * `sprintf` argument is to be interpreted, while the flags |
934 | | * modify that interpretation. |
935 | | * |
936 | | * The field type characters are: |
937 | | * |
938 | | * Field | Integer Format |
939 | | * ------+-------------------------------------------------------------- |
940 | | * b | Convert argument as a binary number. |
941 | | * | Negative numbers will be displayed as a two's complement |
942 | | * | prefixed with '..1'. |
943 | | * B | Equivalent to 'b', but uses an uppercase 0B for prefix |
944 | | * | in the alternative format by #. |
945 | | * d | Convert argument as a decimal number. |
946 | | * i | Identical to 'd'. |
947 | | * o | Convert argument as an octal number. |
948 | | * | Negative numbers will be displayed as a two's complement |
949 | | * | prefixed with '..7'. |
950 | | * u | Identical to 'd'. |
951 | | * x | Convert argument as a hexadecimal number. |
952 | | * | Negative numbers will be displayed as a two's complement |
953 | | * | prefixed with '..f' (representing an infinite string of |
954 | | * | leading 'ff's). |
955 | | * X | Equivalent to 'x', but uses uppercase letters. |
956 | | * |
957 | | * Field | Float Format |
958 | | * ------+-------------------------------------------------------------- |
959 | | * e | Convert floating-point argument into exponential notation |
960 | | * | with one digit before the decimal point as [-]d.dddddde[+-]dd. |
961 | | * | The precision specifies the number of digits after the decimal |
962 | | * | point (defaulting to six). |
963 | | * E | Equivalent to 'e', but uses an uppercase E to indicate |
964 | | * | the exponent. |
965 | | * f | Convert floating-point argument as [-]ddd.dddddd, |
966 | | * | where the precision specifies the number of digits after |
967 | | * | the decimal point. |
968 | | * g | Convert a floating-point number using exponential form |
969 | | * | if the exponent is less than -4 or greater than or |
970 | | * | equal to the precision, or in dd.dddd form otherwise. |
971 | | * | The precision specifies the number of significant digits. |
972 | | * G | Equivalent to 'g', but use an uppercase 'E' in exponent form. |
973 | | * |
974 | | * Field | Other Format |
975 | | * ------+-------------------------------------------------------------- |
976 | | * c | Argument is the numeric code for a single character or |
977 | | * | a single character string itself. |
978 | | * p | The valuing of argument.inspect. |
979 | | * s | Argument is a string to be substituted. If the format |
980 | | * | sequence contains a precision, at most that many characters |
981 | | * | will be copied. |
982 | | * % | A percent sign itself will be displayed. No argument taken. |
983 | | * |
984 | | * The flags modifies the behavior of the formats. |
985 | | * The flag characters are: |
986 | | * |
987 | | * Flag | Applies to | Meaning |
988 | | * ---------+---------------+----------------------------------------- |
989 | | * space | bBdiouxX | Leave a space at the start of |
990 | | * | aAeEfgG | non-negative numbers. |
991 | | * | (numeric fmt) | For 'o', 'x', 'X', 'b' and 'B', use |
992 | | * | | a minus sign with absolute value for |
993 | | * | | negative values. |
994 | | * ---------+---------------+----------------------------------------- |
995 | | * (digit)$ | all | Specifies the absolute argument number |
996 | | * | | for this field. Absolute and relative |
997 | | * | | argument numbers cannot be mixed in a |
998 | | * | | sprintf string. |
999 | | * ---------+---------------+----------------------------------------- |
1000 | | * # | bBoxX | Use an alternative format. |
1001 | | * | aAeEfgG | For the conversions 'o', increase the precision |
1002 | | * | | until the first digit will be '0' if |
1003 | | * | | it is not formatted as complements. |
1004 | | * | | For the conversions 'x', 'X', 'b' and 'B' |
1005 | | * | | on non-zero, prefix the result with "0x", |
1006 | | * | | "0X", "0b" and "0B", respectively. |
1007 | | * | | For 'e', 'E', 'f', 'g', and 'G', |
1008 | | * | | force a decimal point to be added, |
1009 | | * | | even if no digits follow. |
1010 | | * | | For 'g' and 'G', do not remove trailing zeros. |
1011 | | * ---------+---------------+----------------------------------------- |
1012 | | * + | bBdiouxX | Add a leading plus sign to non-negative |
1013 | | * | aAeEfgG | numbers. |
1014 | | * | (numeric fmt) | For 'o', 'x', 'X', 'b' and 'B', use |
1015 | | * | | a minus sign with absolute value for |
1016 | | * | | negative values. |
1017 | | * ---------+---------------+----------------------------------------- |
1018 | | * - | all | Left-justify the result of this conversion. |
1019 | | * ---------+---------------+----------------------------------------- |
1020 | | * 0 (zero) | bBdiouxX | Pad with zeros, not spaces. |
1021 | | * | aAeEfgG | For 'o', 'x', 'X', 'b' and 'B', radix-1 |
1022 | | * | (numeric fmt) | is used for negative numbers formatted as |
1023 | | * | | complements. |
1024 | | * ---------+---------------+----------------------------------------- |
1025 | | * * | all | Use the next argument as the field width. |
1026 | | * | | If negative, left-justify the result. If the |
1027 | | * | | asterisk is followed by a number and a dollar |
1028 | | * | | sign, use the indicated argument as the width. |
1029 | | * |
1030 | | * Examples of flags: |
1031 | | * |
1032 | | * # '+' and space flag specifies the sign of non-negative numbers. |
1033 | | * sprintf("%d", 123) #=> "123" |
1034 | | * sprintf("%+d", 123) #=> "+123" |
1035 | | * sprintf("% d", 123) #=> " 123" |
1036 | | * |
1037 | | * # '#' flag for 'o' increases number of digits to show '0'. |
1038 | | * # '+' and space flag changes format of negative numbers. |
1039 | | * sprintf("%o", 123) #=> "173" |
1040 | | * sprintf("%#o", 123) #=> "0173" |
1041 | | * sprintf("%+o", -123) #=> "-173" |
1042 | | * sprintf("%o", -123) #=> "..7605" |
1043 | | * sprintf("%#o", -123) #=> "..7605" |
1044 | | * |
1045 | | * # '#' flag for 'x' add a prefix '0x' for non-zero numbers. |
1046 | | * # '+' and space flag disables complements for negative numbers. |
1047 | | * sprintf("%x", 123) #=> "7b" |
1048 | | * sprintf("%#x", 123) #=> "0x7b" |
1049 | | * sprintf("%+x", -123) #=> "-7b" |
1050 | | * sprintf("%x", -123) #=> "..f85" |
1051 | | * sprintf("%#x", -123) #=> "0x..f85" |
1052 | | * sprintf("%#x", 0) #=> "0" |
1053 | | * |
1054 | | * # '#' for 'X' uses the prefix '0X'. |
1055 | | * sprintf("%X", 123) #=> "7B" |
1056 | | * sprintf("%#X", 123) #=> "0X7B" |
1057 | | * |
1058 | | * # '#' flag for 'b' add a prefix '0b' for non-zero numbers. |
1059 | | * # '+' and space flag disables complements for negative numbers. |
1060 | | * sprintf("%b", 123) #=> "1111011" |
1061 | | * sprintf("%#b", 123) #=> "0b1111011" |
1062 | | * sprintf("%+b", -123) #=> "-1111011" |
1063 | | * sprintf("%b", -123) #=> "..10000101" |
1064 | | * sprintf("%#b", -123) #=> "0b..10000101" |
1065 | | * sprintf("%#b", 0) #=> "0" |
1066 | | * |
1067 | | * # '#' for 'B' uses the prefix '0B'. |
1068 | | * sprintf("%B", 123) #=> "1111011" |
1069 | | * sprintf("%#B", 123) #=> "0B1111011" |
1070 | | * |
1071 | | * # '#' for 'e' forces to show the decimal point. |
1072 | | * sprintf("%.0e", 1) #=> "1e+00" |
1073 | | * sprintf("%#.0e", 1) #=> "1.e+00" |
1074 | | * |
1075 | | * # '#' for 'f' forces to show the decimal point. |
1076 | | * sprintf("%.0f", 1234) #=> "1234" |
1077 | | * sprintf("%#.0f", 1234) #=> "1234." |
1078 | | * |
1079 | | * # '#' for 'g' forces to show the decimal point. |
1080 | | * # It also disables stripping lowest zeros. |
1081 | | * sprintf("%g", 123.4) #=> "123.4" |
1082 | | * sprintf("%#g", 123.4) #=> "123.400" |
1083 | | * sprintf("%g", 123456) #=> "123456" |
1084 | | * sprintf("%#g", 123456) #=> "123456." |
1085 | | * |
1086 | | * The field width is an optional integer, followed optionally by a |
1087 | | * period and a precision. The width specifies the minimum number of |
1088 | | * characters that will be written to the result for this field. |
1089 | | * |
1090 | | * Examples of width: |
1091 | | * |
1092 | | * # padding is done by spaces, width=20 |
1093 | | * # 0 or radix-1. <------------------> |
1094 | | * sprintf("%20d", 123) #=> " 123" |
1095 | | * sprintf("%+20d", 123) #=> " +123" |
1096 | | * sprintf("%020d", 123) #=> "00000000000000000123" |
1097 | | * sprintf("%+020d", 123) #=> "+0000000000000000123" |
1098 | | * sprintf("% 020d", 123) #=> " 0000000000000000123" |
1099 | | * sprintf("%-20d", 123) #=> "123 " |
1100 | | * sprintf("%-+20d", 123) #=> "+123 " |
1101 | | * sprintf("%- 20d", 123) #=> " 123 " |
1102 | | * sprintf("%020x", -123) #=> "..ffffffffffffffff85" |
1103 | | * |
1104 | | * For |
1105 | | * numeric fields, the precision controls the number of decimal places |
1106 | | * displayed. For string fields, the precision determines the maximum |
1107 | | * number of characters to be copied from the string. (Thus, the format |
1108 | | * sequence `%10.10s` will always contribute exactly ten |
1109 | | * characters to the result.) |
1110 | | * |
1111 | | * Examples of precisions: |
1112 | | * |
1113 | | * # precision for 'd', 'o', 'x' and 'b' is |
1114 | | * # minimum number of digits <------> |
1115 | | * sprintf("%20.8d", 123) #=> " 00000123" |
1116 | | * sprintf("%20.8o", 123) #=> " 00000173" |
1117 | | * sprintf("%20.8x", 123) #=> " 0000007b" |
1118 | | * sprintf("%20.8b", 123) #=> " 01111011" |
1119 | | * sprintf("%20.8d", -123) #=> " -00000123" |
1120 | | * sprintf("%20.8o", -123) #=> " ..777605" |
1121 | | * sprintf("%20.8x", -123) #=> " ..ffff85" |
1122 | | * sprintf("%20.8b", -11) #=> " ..110101" |
1123 | | * |
1124 | | * # "0x" and "0b" for '#x' and '#b' is not counted for |
1125 | | * # precision but "0" for '#o' is counted. <------> |
1126 | | * sprintf("%#20.8d", 123) #=> " 00000123" |
1127 | | * sprintf("%#20.8o", 123) #=> " 00000173" |
1128 | | * sprintf("%#20.8x", 123) #=> " 0x0000007b" |
1129 | | * sprintf("%#20.8b", 123) #=> " 0b01111011" |
1130 | | * sprintf("%#20.8d", -123) #=> " -00000123" |
1131 | | * sprintf("%#20.8o", -123) #=> " ..777605" |
1132 | | * sprintf("%#20.8x", -123) #=> " 0x..ffff85" |
1133 | | * sprintf("%#20.8b", -11) #=> " 0b..110101" |
1134 | | * |
1135 | | * # precision for 'e' is number of |
1136 | | * # digits after the decimal point <------> |
1137 | | * sprintf("%20.8e", 1234.56789) #=> " 1.23456789e+03" |
1138 | | * |
1139 | | * # precision for 'f' is number of |
1140 | | * # digits after the decimal point <------> |
1141 | | * sprintf("%20.8f", 1234.56789) #=> " 1234.56789000" |
1142 | | * |
1143 | | * # precision for 'g' is number of |
1144 | | * # significant digits <-------> |
1145 | | * sprintf("%20.8g", 1234.56789) #=> " 1234.5679" |
1146 | | * |
1147 | | * # <-------> |
1148 | | * sprintf("%20.8g", 123456789) #=> " 1.2345679e+08" |
1149 | | * |
1150 | | * # precision for 's' is |
1151 | | * # maximum number of characters <------> |
1152 | | * sprintf("%20.8s", "string test") #=> " string t" |
1153 | | * |
1154 | | * Examples: |
1155 | | * |
1156 | | * sprintf("%d %04x", 123, 123) #=> "123 007b" |
1157 | | * sprintf("%08b '%4s'", 123, 123) #=> "01111011 ' 123'" |
1158 | | * sprintf("%1$*2$s %2$d %1$s", "hello", 8) #=> " hello 8 hello" |
1159 | | * sprintf("%1$*2$s %2$d", "hello", -8) #=> "hello -8" |
1160 | | * sprintf("%+g:% g:%-g", 1.23, 1.23, 1.23) #=> "+1.23: 1.23:1.23" |
1161 | | * sprintf("%u", -123) #=> "-123" |
1162 | | * |
1163 | | * For more complex formatting, Ruby supports a reference by name. |
1164 | | * %<name>s style uses format style, but %{name} style doesn't. |
1165 | | * |
1166 | | * Examples: |
1167 | | * sprintf("%<foo>d : %<bar>f", { :foo => 1, :bar => 2 }) |
1168 | | * #=> 1 : 2.000000 |
1169 | | * sprintf("%{foo}f", { :foo => 1 }) |
1170 | | * # => "1f" |
1171 | | */ |
1172 | | |
1173 | | static mrb_value |
1174 | | mrb_f_sprintf(mrb_state *mrb, mrb_value obj) |
1175 | 223 | { |
1176 | 223 | mrb_int argc; |
1177 | 223 | const mrb_value *argv; |
1178 | | |
1179 | 223 | mrb_get_args(mrb, "*", &argv, &argc); |
1180 | | |
1181 | 223 | if (argc <= 0) { |
1182 | 0 | mrb_raise(mrb, E_ARGUMENT_ERROR, "too few arguments"); |
1183 | 0 | return mrb_nil_value(); |
1184 | 0 | } |
1185 | 223 | else { |
1186 | 223 | return mrb_str_format(mrb, argc - 1, argv + 1, argv[0]); |
1187 | 223 | } |
1188 | 223 | } |
1189 | | |
1190 | | void |
1191 | | mrb_mruby_sprintf_gem_init(mrb_state *mrb) |
1192 | 15.0k | { |
1193 | 15.0k | struct RClass *krn = mrb->kernel_module; |
1194 | 15.0k | mrb_define_module_function_id(mrb, krn, MRB_SYM(sprintf), mrb_f_sprintf, MRB_ARGS_ANY()); |
1195 | 15.0k | mrb_define_module_function_id(mrb, krn, MRB_SYM(format), mrb_f_sprintf, MRB_ARGS_ANY()); |
1196 | 15.0k | } |
1197 | | |
1198 | | void |
1199 | | mrb_mruby_sprintf_gem_final(mrb_state *mrb) |
1200 | 15.0k | { |
1201 | 15.0k | } |