• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nickg / nvc / 27865744029

20 Jun 2026 08:32AM UTC coverage: 92.264% (-0.005%) from 92.269%
27865744029

push

github

nickg
Bump version to 1.21.1

78741 of 85343 relevant lines covered (92.26%)

642699.33 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.43
/src/printf.c
1
//
2
//  Copyright (C) 2025-2026  Nick Gasson
3
//
4
//  This program is free software: you can redistribute it and/or modify
5
//  it under the terms of the GNU General Public License as published by
6
//  the Free Software Foundation, either version 3 of the License, or
7
//  (at your option) any later version.
8
//
9
//  This program is distributed in the hope that it will be useful,
10
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
11
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
//  GNU General Public License for more details.
13
//
14
//  You should have received a copy of the GNU General Public License
15
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
//
17

18
#include "util.h"
19
#include "ident.h"
20
#include "mir/mir-node.h"
21
#include "object.h"
22
#include "printf.h"
23
#include "thread.h"
24
#include "type.h"
25

26
#include <assert.h>
27
#include <errno.h>
28
#include <stdio.h>
29
#include <string.h>
30
#include <stdlib.h>
31
#include <unistd.h>
32
#include <limits.h>
33

34
#define ANSI_RESET      0
35
#define ANSI_BOLD       1
36
#define ANSI_FG_BLACK   30
37
#define ANSI_FG_RED     31
38
#define ANSI_FG_GREEN   32
39
#define ANSI_FG_YELLOW  33
40
#define ANSI_FG_BLUE    34
41
#define ANSI_FG_MAGENTA 35
42
#define ANSI_FG_CYAN    36
43
#define ANSI_FG_WHITE   37
44

45
#define MAX_ARGS 25
46

47
typedef struct _printf_state printf_state_t;
48
typedef struct _printf_arg printf_arg_t;
49

50
typedef union {
51
   long long  ll;
52
   long       l;
53
   size_t     z;
54
   double     f;
55
   int        i;
56
   void      *p;
57
} printf_value_t;
58

59
typedef int (*fmt_fn_t)(ostream_t *, printf_state_t *, printf_arg_t *);
60

61
typedef struct _printf_arg {
62
   const char     *start;
63
   size_t          len;
64
   fmt_fn_t        fn;
65
   printf_value_t  value;
66
   int             precision;
67
} printf_arg_t;
68

69
typedef struct _printf_state {
70
   printf_arg_t args[MAX_ARGS];
71
   unsigned     nargs;
72
   unsigned     pos;
73
} printf_state_t;
74

75
typedef struct {
76
   const char *name;
77
   int         value;
78
} color_escape_t;
79

80
static const color_escape_t escapes[] = {
81
   { "",        ANSI_RESET },
82
   { "bold",    ANSI_BOLD },
83
   { "black",   ANSI_FG_BLACK },
84
   { "red",     ANSI_FG_RED },
85
   { "green",   ANSI_FG_GREEN },
86
   { "yellow",  ANSI_FG_YELLOW },
87
   { "blue",    ANSI_FG_BLUE },
88
   { "magenta", ANSI_FG_MAGENTA },
89
   { "cyan",    ANSI_FG_CYAN },
90
   { "white",   ANSI_FG_WHITE },
91
};
92

93
static int printf_interpret(ostream_t *os, printf_state_t *state,
94
                            const char *fmt, const char *end);
95

96
int ostream_write(ostream_t *os, const char *buf, size_t len)
2,508,941✔
97
{
98
   (*os->callback)(buf, len, os->context);
2,508,941✔
99
   return len;
2,508,941✔
100
}
101

102
int ostream_putc(ostream_t *os, char ch)
803,715✔
103
{
104
   char buf[1] = { ch };
803,715✔
105
   return ostream_write(os, buf, 1);
803,715✔
106
}
107

108
int ostream_puts(ostream_t *os, const char *str)
11,839✔
109
{
110
   if (str == NULL)
11,839✔
111
      return ostream_write(os, "(null)", 6);
×
112
   else
113
      return ostream_write(os, str, strlen(str));
11,839✔
114
}
115

116
static int format_ident(ostream_t *os, printf_state_t *state, printf_arg_t *arg)
363✔
117
{
118
   ident_t id = arg->value.p;
363✔
119
   return ostream_write(os, istr(id), ident_len(id));
363✔
120
}
121

122
static int format_ident_toupper(ostream_t *os, printf_state_t *state,
229✔
123
                                printf_arg_t *arg)
124
{
125
   ident_t id = arg->value.p;
229✔
126
   size_t len = ident_len(id);
229✔
127
   const char *str = istr(id);
229✔
128
   int nchars = 0;
229✔
129
   char chunk[32];
229✔
130

131
   for (int i = 0; i < len; i += sizeof(chunk)) {
458✔
132
      const int tocopy = MIN(sizeof(chunk), len - i);
229✔
133
      for (int j = 0; j < tocopy; j++)
1,090✔
134
         chunk[j] = toupper_iso88591(str[i + j]);
861✔
135

136
      nchars += ostream_write(os, chunk, tocopy);
229✔
137
   }
138

139
   return nchars;
229✔
140
}
141

142
static int format_type(ostream_t *os, printf_state_t *state, printf_arg_t *arg)
228✔
143
{
144
   type_t type = arg->value.p;
228✔
145

146
   // Print a fully qualified name if there is another type in the
147
   // argument list with the same simple name
148

149
   for (int i = 0; i < state->nargs; i++) {
443✔
150
      const printf_arg_t *other = &(state->args[i]);
353✔
151
      if (other != arg && other->fn == format_type)
353✔
152
         return ostream_puts(os, type_pp2(type, other->value.p));
138✔
153
   }
154

155
   return ostream_puts(os, type_pp(type));
90✔
156
}
157

158
static int format_object_kind(ostream_t *os, printf_state_t *state,
×
159
                              printf_arg_t *arg)
160
{
161
   object_t *obj = arg->value.p;
×
162
   return ostream_puts(os, object_kind_str(obj));
×
163
}
164

165
static int format_mir(ostream_t *os, printf_state_t *state, printf_arg_t *arg)
16✔
166
{
167
   mir_value_t *value = arg->value.p;
16✔
168
   char buf[64];
16✔
169

170
   switch (value->tag) {
16✔
171
   case MIR_TAG_NODE:
4✔
172
      checked_sprintf(buf, sizeof(buf), "%%%d", value->id);
4✔
173
      break;
4✔
174
   case MIR_TAG_CONST:
12✔
175
      checked_sprintf(buf, sizeof(buf), "#%d",
12✔
176
                      value->id - (1 << (_MIR_ID_BITS - 1)));
12✔
177
      break;
12✔
178
   default:
×
179
      checked_sprintf(buf, sizeof(buf), "{tag:%d, id:%x}",
×
180
                      value->tag, value->id);
×
181
      break;
×
182
   }
183

184
   return ostream_puts(os, buf);
16✔
185
}
186

187
static int delegate(ostream_t *os, printf_state_t *s, printf_arg_t *arg, ...)
849,442✔
188
{
189
   char spec[32];
849,442✔
190
   assert(arg->len + 1 < sizeof(spec));
849,442✔
191
   memcpy(spec, arg->start, arg->len);
849,442✔
192
   spec[arg->len] = '\0';
849,442✔
193

194
   va_list ap, ap2;
849,442✔
195
   va_start(ap, arg);
849,442✔
196
   va_copy(ap2, ap);
849,442✔
197

198
   char small[64];
849,442✔
199
   int req = vsnprintf(small, sizeof(small), spec, ap);
849,442✔
200

201
   if (req + 1 > sizeof(small)) {
849,442✔
202
      char *large = xmalloc(req + 1);
872✔
203
      vsnprintf(large, req + 1, spec, ap2);
872✔
204
      ostream_write(os, large, req);
872✔
205
      free(large);
872✔
206
   }
207
   else
208
      ostream_write(os, small, req);
848,570✔
209

210
   va_end(ap);
849,442✔
211
   va_end(ap2);
849,442✔
212
   return req;
849,442✔
213
}
214

215
static fmt_fn_t get_pointer_formatter(char ch)
841✔
216
{
217
   switch (ch) {
841✔
218
   case 'i': return format_ident;
219
   case 'I': return format_ident_toupper;
229✔
220
   case 'T': return format_type;
228✔
221
   case 'K': return format_object_kind;
×
222
   case 'M': return format_mir;
16✔
223
   default: return NULL;
5✔
224
   }
225
}
226

227
static int ansi_escape(ostream_t *os, printf_state_t *state, const char **fmt)
205,727✔
228
{
229
   bool has_format = false;
205,727✔
230
   const char *start = *fmt;
205,727✔
231
   do {
1,082,101✔
232
      has_format |= (**fmt == '%');
1,082,101✔
233
      (*fmt)++;
1,082,101✔
234
   } while (**fmt != '\0' && **fmt != '$');
1,082,101✔
235

236
   if (**fmt == '\0')
205,727✔
237
      return ostream_write(os, start, *fmt - start);
157✔
238

239
   const char *end = *fmt;
205,570✔
240
   (*fmt)++;   // Advance past final '$'
205,570✔
241

242
   size_t len = len = *fmt - start - 2;
205,570✔
243
   const char *e = e = start + 1;
205,570✔
244
   LOCAL_TEXT_BUF tb = NULL;
411,140✔
245

246
   if (has_format) {
205,570✔
247
      // Expand any embedded formatting inside the ANSI escape
248
      tb = tb_new();
22,234✔
249

250
      ostream_t aos = {
22,234✔
251
         tb_ostream_write,
252
         tb,
253
         os->charset,
22,234✔
254
      };
255

256
      printf_interpret(&aos, state, start + 1, end);
22,234✔
257

258
      e = tb_get(tb);
22,234✔
259
      len = tb_len(tb);
22,234✔
260
   }
261

262
   bool bold;
205,570✔
263
   if ((bold = (*e == '!')))
205,570✔
264
      ++e, --len;
2✔
265

266
   bool bright;
205,570✔
267
   if ((bright = (*e == '+')))
205,570✔
268
      ++e, --len;
39✔
269

270
   if (*e == '#') {
205,570✔
271
      char *eptr;
1✔
272
      int code = strtoul(e + 1, &eptr, 10);
1✔
273
      if (eptr == e + len) {
1✔
274
         char buf[16];
1✔
275
         if (bold)
1✔
276
            checked_sprintf(buf, sizeof(buf), "\033[1;38;5;%dm", code);
×
277
         else
278
            checked_sprintf(buf, sizeof(buf), "\033[38;5;%dm", code);
1✔
279

280
         if (os->flags & OS_COLOR)
1✔
281
            ostream_puts(os, buf);
1✔
282

283
         return 0;
1✔
284
      }
285
   }
286

287
   if (strncmp(e, "link:", 5) == 0) {
205,569✔
288
      const char *bel = strchr(e, '\07');
22,235✔
289

290
#ifndef __MINGW32__    // Winpty doesn't recognise these
291
      if (os->flags & OS_TERMINAL) {
22,235✔
292
         ostream_puts(os, "\033]8;;");
2✔
293
         ostream_write(os, e + 5, len - 5);
2✔
294
         ostream_puts(os, "\033]8;;\07");
2✔
295
         return e + len - bel - 1;
2✔
296
      }
297
#endif
298

299
      return ostream_write(os, bel + 1, e + len - bel - 1);
22,233✔
300
   }
301

302
   for (int i = 0; i < ARRAY_LEN(escapes); i++) {
693,098✔
303
      if (strncmp(e, escapes[i].name, len) == 0) {
693,096✔
304
         int code = escapes[i].value + (bright ? 60 : 0);
183,332✔
305
         char buf[16];
183,332✔
306
         if (bold)
183,332✔
307
            checked_sprintf(buf, sizeof(buf), "\033[1;%dm", code);
1✔
308
         else
309
            checked_sprintf(buf, sizeof(buf), "\033[%dm", code);
183,331✔
310

311
         if (os->flags & OS_COLOR)
183,332✔
312
            ostream_puts(os, buf);
1,839✔
313

314
         return 0;
183,332✔
315
      }
316
   }
317

318
   return ostream_write(os, start, *fmt - start);
2✔
319
}
320

321
static int format_z(ostream_t *os, printf_state_t *s, printf_arg_t *arg)
149,387✔
322
{
323
   assert(arg->precision == INT_MIN);
149,387✔
324
   return delegate(os, s, arg, arg->value.z);
149,387✔
325
}
326

327
static int format_ll(ostream_t *os, printf_state_t *s, printf_arg_t *arg)
×
328
{
329
   if (arg->precision != INT_MIN)
×
330
      return delegate(os, s, arg, arg->precision, arg->value.ll);
×
331
   else
332
      return delegate(os, s, arg, arg->value.ll);
×
333
}
334

335
static int format_l(ostream_t *os, printf_state_t *s, printf_arg_t *arg)
45,442✔
336
{
337
   if (arg->precision != INT_MIN)
45,442✔
338
      return delegate(os, s, arg, arg->precision, arg->value.l);
11,082✔
339
   else
340
      return delegate(os, s, arg, arg->value.l);
34,360✔
341
}
342

343
static int format_i(ostream_t *os, printf_state_t *s, printf_arg_t *arg)
124,756✔
344
{
345
   if (arg->precision != INT_MIN)
124,756✔
346
      return delegate(os, s, arg, arg->precision, arg->value.i);
13,700✔
347
   else
348
      return delegate(os, s, arg, arg->value.i);
111,056✔
349
}
350

351
static int format_f(ostream_t *os, printf_state_t *s, printf_arg_t *arg)
890✔
352
{
353
   if (arg->precision != INT_MIN)
890✔
354
      return delegate(os, s, arg, arg->precision, arg->value.f);
9✔
355
   else
356
      return delegate(os, s, arg, arg->value.f);
881✔
357
}
358

359
static int format_s(ostream_t *os, printf_state_t *s, printf_arg_t *arg)
528,962✔
360
{
361
   if (arg->precision != INT_MIN)
528,962✔
362
      return delegate(os, s, arg, arg->precision, arg->value.p);
55,577✔
363
   else
364
      return delegate(os, s, arg, arg->value.p);
473,385✔
365
}
366

367
static int format_p(ostream_t *os, printf_state_t *s, printf_arg_t *arg)
5✔
368
{
369
   return delegate(os, s, arg, arg->value.p);
5✔
370
}
371

372
static int printf_interpret(ostream_t *os, printf_state_t *state,
669,533✔
373
                            const char *fmt, const char *end)
374
{
375
   int nchars = 0;
669,533✔
376
   for (const char *p = fmt;;) {
669,533✔
377
      const char *start = p;
1,770,700✔
378
      while (p < end && *p != '%' && *p != '$')
4,624,882✔
379
         p++;
2,854,182✔
380

381
      if (start < p)
1,770,700✔
382
         nchars += ostream_write(os, start, p - start);
744,944✔
383

384
      if (p == end)
1,770,700✔
385
         return nchars;
669,533✔
386
      else if (*p == '$') {
1,101,167✔
387
         nchars += ansi_escape(os, state, &p);
205,727✔
388
         continue;
205,727✔
389
      }
390
      else if (*p == '%' && *(p + 1) == '%') {
895,440✔
391
         nchars += ostream_write(os, "%", 1);
45,162✔
392
         p += 2;
45,162✔
393
         continue;
45,162✔
394
      }
395

396
      assert(state->pos < state->nargs);
850,278✔
397

398
      printf_arg_t *arg = &(state->args[state->pos]);
850,278✔
399
      nchars += (*arg->fn)(os, state, arg);
850,278✔
400
      p += arg->len;
850,278✔
401

402
      state->pos++;
850,278✔
403
   }
404
}
405

406
int nvc_vfprintf(ostream_t *os, const char *fmt, va_list ap)
647,299✔
407
{
408
   printf_state_t state = {};
647,299✔
409

410
   const char *end = NULL;
647,299✔
411
   for (const char *p = fmt;;) {
647,299✔
412
      while (*p != '\0' && *p != '%')
5,173,223✔
413
         p++;
3,630,484✔
414

415
      if (*p == '\0') {
1,542,739✔
416
         end = p;
647,299✔
417
         break;
647,299✔
418
      }
419

420
      printf_arg_t arg = { .start = p, .precision = INT_MIN };
895,440✔
421
      bool z_mod = false;
895,440✔
422
      int l_mod = 0;
895,440✔
423
   again:
1,198,039✔
424
      switch (*++p) {
1,198,039✔
425
      case 'l':
45,444✔
426
         l_mod++;
45,444✔
427
         goto again;
45,444✔
428
      case 'z':
149,387✔
429
         z_mod = true;
149,387✔
430
         goto again;
149,387✔
431
      case '-':
27,400✔
432
      case '+':
433
      case '.':
434
      case '0'...'9':
435
         goto again;
27,400✔
436
      case '*':
80,368✔
437
         arg.precision = va_arg(ap, int);
80,368✔
438
         goto again;
80,368✔
439
      case 'd':
318,103✔
440
      case 'i':
441
      case 'x':
442
      case 'u':
443
      case 'o':
444
         if (z_mod) {
318,103✔
445
            arg.value.z = va_arg(ap, size_t);
149,387✔
446
            arg.fn = format_z;
149,387✔
447
         }
448
         else if (l_mod >= 2) {
168,716✔
449
            arg.value.ll = va_arg(ap, long long);
×
450
            arg.fn = format_ll;
×
451
         }
452
         else if (l_mod == 1) {
168,716✔
453
            arg.value.l = va_arg(ap, long);
45,442✔
454
            arg.fn = format_l;
45,442✔
455
         }
456
         else {
457
            arg.value.i = va_arg(ap, int);
123,274✔
458
            arg.fn = format_i;
123,274✔
459
         }
460
         break;
461
      case 'c':
1,482✔
462
         arg.value.i = va_arg(ap, int);
1,482✔
463
         arg.fn = format_i;
1,482✔
464
         break;
1,482✔
465
      case 'e':
890✔
466
      case 'f':
467
      case 'g':
468
         arg.value.f = va_arg(ap, double);
890✔
469
         arg.fn = format_f;
890✔
470
         break;
890✔
471
      case 's':
528,962✔
472
         arg.value.p = va_arg(ap, char *);
528,962✔
473
         arg.fn = format_s;
528,962✔
474
         break;
528,962✔
475
      case 'p':
841✔
476
         arg.value.p = va_arg(ap, void *);
841✔
477
         if ((arg.fn = get_pointer_formatter(p[1])))
841✔
478
            p++;
836✔
479
         else
480
            arg.fn = format_p;
481
         break;
482
      case '%':
45,162✔
483
         p++;
45,162✔
484
         continue;
45,162✔
485
      default:
×
486
         fatal_trace("unhandled character '%c' in format", *p);
487
      }
488

489
      arg.len = ++p - arg.start;
850,278✔
490

491
      if (state.nargs == MAX_ARGS)
850,278✔
492
         fatal_trace("maximum of %d printf arguments supported", MAX_ARGS);
493
      else
494
         state.args[state.nargs++] = arg;
850,278✔
495
   }
496

497
   return printf_interpret(os, &state, fmt, end);
1,294,598✔
498
}
499

500
int nvc_vprintf(const char *fmt, va_list ap)
8✔
501
{
502
   return nvc_vfprintf(nvc_stdout(), fmt, ap);
8✔
503
}
504

505
int nvc_printf(const char *fmt, ...)
8✔
506
{
507
   va_list ap;
8✔
508
   va_start(ap, fmt);
8✔
509
   const int nchars = nvc_vprintf(fmt, ap);
8✔
510
   va_end(ap);
8✔
511
   return nchars;
8✔
512
}
513

514
int nvc_fprintf(ostream_t *os, const char *fmt, ...)
173,442✔
515
{
516
   va_list ap;
173,442✔
517
   va_start(ap, fmt);
173,442✔
518
   const int nchars = nvc_vfprintf(os, fmt, ap);
173,442✔
519
   va_end(ap);
173,442✔
520
   return nchars;
173,442✔
521
}
522

523
void stdio_ostream_write(const char *buf, size_t len, void *ctx)
1,171,858✔
524
{
525
   FILE *f = ctx;
1,171,858✔
526
   fwrite(buf, len, 1, f);
1,171,858✔
527
}
1,171,858✔
528

529
static void init_terminal_ostream(ostream_t *os, FILE *f)
4,321✔
530
{
531
   os->callback = stdio_ostream_write;
4,321✔
532
   os->context = f;
4,321✔
533
   os->flags = 0;
4,321✔
534

535
   const int saved_errno = errno;
4,321✔
536

537
   if (isatty(fileno(f))) {
4,321✔
538
      os->charset = utf8_terminal() ? CHARSET_UTF8 : CHARSET_ISO88591;
×
539
      os->flags |= OS_TERMINAL;
×
540
   }
541
   else
542
      os->charset = CHARSET_ISO88591;
4,321✔
543

544
   if (color_terminal())
4,321✔
545
      os->flags |= OS_COLOR;
4✔
546

547
   errno = saved_errno;
4,321✔
548
}
4,321✔
549

550
ostream_t *nvc_stdout(void)
226✔
551
{
552
   static ostream_t os;
226✔
553
   INIT_ONCE(init_terminal_ostream(&os, stdout));
226✔
554
   return &os;
226✔
555
}
556

557
ostream_t *nvc_stderr(void)
63,760✔
558
{
559
   static ostream_t os;
63,760✔
560
   INIT_ONCE(init_terminal_ostream(&os, stderr));
63,760✔
561
   return &os;
63,760✔
562
}
563

564
char *color_asprintf(const char *fmt, ...)
31✔
565
{
566
   LOCAL_TEXT_BUF tb = tb_new();
62✔
567
   ostream_t os = { tb_ostream_write, tb, CHARSET_ISO88591, OS_COLOR };
31✔
568

569
   va_list ap;
31✔
570
   va_start(ap, fmt);
31✔
571
   nvc_vfprintf(&os, fmt, ap);
31✔
572
   va_end(ap);
31✔
573

574
   return tb_claim(tb);
31✔
575
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc