Coverage Report

Created: 2026-06-20 07:34

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}