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

nickg / nvc / 29315661317

14 Jul 2026 07:45AM UTC coverage: 92.126% (+0.002%) from 92.124%
29315661317

push

github

nickg
Add %pQ formatter for quoted identifiers

49 of 58 new or added lines in 9 files covered. (84.48%)

47 existing lines in 2 files now uncovered.

79802 of 86623 relevant lines covered (92.13%)

645047.56 hits per line

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

93.83
/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             width;
67
   int             precision;
68
} printf_arg_t;
69

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

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

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

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

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

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

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

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

123
static int format_ident_quoted(ostream_t *os, printf_state_t *state,
441✔
124
                               printf_arg_t *arg)
125
{
126
   ident_t id = arg->value.p;
441✔
127
   int nchars = 0;
441✔
128

129
   nchars += ostream_putc(os, '\'');
441✔
130
   nchars += ostream_write(os, istr(id), ident_len(id));
441✔
131
   nchars += ostream_putc(os, '\'');
441✔
132

133
   return nchars;
441✔
134
}
135

136
static int format_ident_toupper(ostream_t *os, printf_state_t *state,
279✔
137
                                printf_arg_t *arg)
138
{
139
   ident_t id = arg->value.p;
279✔
140
   size_t len = ident_len(id);
279✔
141
   const char *str = istr(id);
279✔
142
   int nchars = 0;
279✔
143
   char chunk[32];
279✔
144

145
   for (int i = 0; i < len; i += sizeof(chunk)) {
558✔
146
      const int tocopy = MIN(sizeof(chunk), len - i);
279✔
147
      for (int j = 0; j < tocopy; j++)
1,410✔
148
         chunk[j] = toupper_iso88591(str[i + j]);
1,131✔
149

150
      nchars += ostream_write(os, chunk, tocopy);
279✔
151
   }
152

153
   return nchars;
279✔
154
}
155

156
static int format_type(ostream_t *os, printf_state_t *state, printf_arg_t *arg)
3,312✔
157
{
158
   type_t type = arg->value.p;
3,312✔
159

160
   // Print a fully qualified name if there is another type in the
161
   // argument list with the same simple name
162

163
   for (int i = 0; i < state->nargs; i++) {
6,611✔
164
      const printf_arg_t *other = &(state->args[i]);
3,437✔
165
      if (other != arg && other->fn == format_type)
3,437✔
166
         return ostream_puts(os, type_pp2(type, other->value.p));
138✔
167
   }
168

169
   return ostream_puts(os, type_pp(type));
3,174✔
170
}
171

172
static int format_class(ostream_t *os, printf_state_t *state, printf_arg_t *arg)
9✔
173
{
174
   tree_t t = arg->value.p;
9✔
175
   return ostream_puts(os, class_str(class_of(t)));
9✔
176
}
177

UNCOV
178
static int format_object_kind(ostream_t *os, printf_state_t *state,
×
179
                              printf_arg_t *arg)
180
{
UNCOV
181
   object_t *obj = arg->value.p;
×
UNCOV
182
   return ostream_puts(os, object_kind_str(obj));
×
183
}
184

185
static int format_mir(ostream_t *os, printf_state_t *state, printf_arg_t *arg)
16✔
186
{
187
   mir_value_t *value = arg->value.p;
16✔
188
   char buf[64];
16✔
189

190
   switch (value->tag) {
16✔
191
   case MIR_TAG_NODE:
4✔
192
      checked_sprintf(buf, sizeof(buf), "%%%d", value->id);
4✔
193
      break;
4✔
194
   case MIR_TAG_CONST:
12✔
195
      checked_sprintf(buf, sizeof(buf), "#%d",
12✔
196
                      value->id - (1 << (_MIR_ID_BITS - 1)));
12✔
197
      break;
12✔
UNCOV
198
   default:
×
UNCOV
199
      checked_sprintf(buf, sizeof(buf), "{tag:%d, id:%x}",
×
UNCOV
200
                      value->tag, value->id);
×
UNCOV
201
      break;
×
202
   }
203

204
   return ostream_puts(os, buf);
16✔
205
}
206

207
static int delegate(ostream_t *os, printf_state_t *s, printf_arg_t *arg, ...)
916,785✔
208
{
209
   char spec[32];
916,785✔
210
   assert(arg->len + 1 < sizeof(spec));
916,785✔
211
   memcpy(spec, arg->start, arg->len);
916,785✔
212
   spec[arg->len] = '\0';
916,785✔
213

214
   va_list ap, ap2;
916,785✔
215
   va_start(ap, arg);
916,785✔
216
   va_copy(ap2, ap);
916,785✔
217

218
   char small[64];
916,785✔
219
   int req = vsnprintf(small, sizeof(small), spec, ap);
916,785✔
220

221
   if (req + 1 > sizeof(small)) {
916,785✔
222
      char *large = xmalloc(req + 1);
757✔
223
      vsnprintf(large, req + 1, spec, ap2);
757✔
224
      ostream_write(os, large, req);
757✔
225
      free(large);
757✔
226
   }
227
   else
228
      ostream_write(os, small, req);
916,028✔
229

230
   va_end(ap);
916,785✔
231
   va_end(ap2);
916,785✔
232
   return req;
916,785✔
233
}
234

235
static fmt_fn_t get_pointer_formatter(char ch)
10,668✔
236
{
237
   switch (ch) {
10,668✔
238
   case 'i': return format_ident;
239
   case 'I': return format_ident_toupper;
279✔
240
   case 'Q': return format_ident_quoted;
441✔
241
   case 'T': return format_type;
3,312✔
242
   case 'C': return format_class;
9✔
UNCOV
243
   case 'K': return format_object_kind;
×
244
   case 'M': return format_mir;
16✔
245
   default: return NULL;
5✔
246
   }
247
}
248

249
static int ansi_escape(ostream_t *os, printf_state_t *state, const char **fmt)
206,107✔
250
{
251
   bool has_format = false;
206,107✔
252
   const char *start = *fmt;
206,107✔
253
   do {
1,085,304✔
254
      has_format |= (**fmt == '%');
1,085,304✔
255
      (*fmt)++;
1,085,304✔
256
   } while (**fmt != '\0' && **fmt != '$');
1,085,304✔
257

258
   if (**fmt == '\0') {
206,107✔
259
      int nchars = ostream_putc(os, '$');
198✔
260
      nchars += printf_interpret(os, state, start + 1, *fmt);
198✔
261
      return nchars;
198✔
262
   }
263

264
   const char *end = *fmt;
205,909✔
265
   (*fmt)++;   // Advance past final '$'
205,909✔
266

267
   size_t len = len = *fmt - start - 2;
205,909✔
268
   const char *e = e = start + 1;
205,909✔
269
   LOCAL_TEXT_BUF tb = NULL;
411,818✔
270

271
   if (has_format) {
205,909✔
272
      // Expand any embedded formatting inside the ANSI escape
273
      tb = tb_new();
22,286✔
274

275
      ostream_t aos = {
22,286✔
276
         tb_ostream_write,
277
         tb,
278
         os->charset,
22,286✔
279
      };
280

281
      printf_interpret(&aos, state, start + 1, end);
22,286✔
282

283
      e = tb_get(tb);
22,286✔
284
      len = tb_len(tb);
22,286✔
285
   }
286

287
   bool bold;
205,909✔
288
   if ((bold = (*e == '!')))
205,909✔
289
      ++e, --len;
2✔
290

291
   bool bright;
205,909✔
292
   if ((bright = (*e == '+')))
205,909✔
293
      ++e, --len;
43✔
294

295
   if (*e == '#') {
205,909✔
296
      char *eptr;
1✔
297
      int code = strtoul(e + 1, &eptr, 10);
1✔
298
      if (eptr == e + len) {
1✔
299
         char buf[16];
1✔
300
         if (bold)
1✔
UNCOV
301
            checked_sprintf(buf, sizeof(buf), "\033[1;38;5;%dm", code);
×
302
         else
303
            checked_sprintf(buf, sizeof(buf), "\033[38;5;%dm", code);
1✔
304

305
         if (os->flags & OS_COLOR)
1✔
306
            ostream_puts(os, buf);
1✔
307

308
         return 0;
1✔
309
      }
310
   }
311

312
   if (strncmp(e, "link:", 5) == 0) {
205,908✔
313
      const char *bel = strchr(e, '\07');
22,287✔
314

315
#ifndef __MINGW32__    // Winpty doesn't recognise these
316
      if (os->flags & OS_TERMINAL) {
22,287✔
317
         ostream_puts(os, "\033]8;;");
2✔
318
         ostream_write(os, e + 5, len - 5);
2✔
319
         ostream_puts(os, "\033]8;;\07");
2✔
320
         return e + len - bel - 1;
2✔
321
      }
322
#endif
323

324
      return ostream_write(os, bel + 1, e + len - bel - 1);
22,285✔
325
   }
326

327
   for (int i = 0; i < ARRAY_LEN(escapes); i++) {
694,089✔
328
      if (strncmp(e, escapes[i].name, len) == 0) {
694,087✔
329
         int code = escapes[i].value + (bright ? 60 : 0);
183,619✔
330
         char buf[16];
183,619✔
331
         if (bold)
183,619✔
332
            checked_sprintf(buf, sizeof(buf), "\033[1;%dm", code);
1✔
333
         else
334
            checked_sprintf(buf, sizeof(buf), "\033[%dm", code);
183,618✔
335

336
         if (os->flags & OS_COLOR)
183,619✔
337
            ostream_puts(os, buf);
1,847✔
338

339
         return 0;
183,619✔
340
      }
341
   }
342

343
   return ostream_write(os, start, *fmt - start);
2✔
344
}
345

346
static int format_z(ostream_t *os, printf_state_t *s, printf_arg_t *arg)
160,120✔
347
{
348
   assert(arg->precision == INT_MIN);
160,120✔
349
   return delegate(os, s, arg, arg->value.z);
160,120✔
350
}
351

UNCOV
352
static int format_ll(ostream_t *os, printf_state_t *s, printf_arg_t *arg)
×
353
{
UNCOV
354
   if (arg->precision != INT_MIN)
×
UNCOV
355
      return delegate(os, s, arg, arg->precision, arg->value.ll);
×
356
   else
UNCOV
357
      return delegate(os, s, arg, arg->value.ll);
×
358
}
359

360
static int format_l(ostream_t *os, printf_state_t *s, printf_arg_t *arg)
45,641✔
361
{
362
   if (arg->precision != INT_MIN)
45,641✔
363
      return delegate(os, s, arg, arg->precision, arg->value.l);
11,130✔
364
   else
365
      return delegate(os, s, arg, arg->value.l);
34,511✔
366
}
367

368
static int format_i(ostream_t *os, printf_state_t *s, printf_arg_t *arg)
164,808✔
369
{
370
   if (arg->precision != INT_MIN)
164,808✔
371
      return delegate(os, s, arg, arg->precision, arg->value.i);
13,707✔
372
   else
373
      return delegate(os, s, arg, arg->value.i);
151,101✔
374
}
375

376
static int format_f(ostream_t *os, printf_state_t *s, printf_arg_t *arg)
1,107✔
377
{
378
   if (arg->width != INT_MIN)
1,107✔
379
      return delegate(os, s, arg, arg->width, arg->precision, arg->value.f);
77✔
380
   else if (arg->precision != INT_MIN)
1,030✔
381
      return delegate(os, s, arg, arg->precision, arg->value.f);
21✔
382
   else
383
      return delegate(os, s, arg, arg->value.f);
1,009✔
384
}
385

386
static int format_s(ostream_t *os, printf_state_t *s, printf_arg_t *arg)
545,104✔
387
{
388
   if (arg->width != INT_MIN)
545,104✔
389
      return delegate(os, s, arg, arg->width, arg->precision, arg->value.p);
1✔
390
   else if (arg->precision != INT_MIN)
545,103✔
391
      return delegate(os, s, arg, arg->precision, arg->value.p);
56,146✔
392
   else
393
      return delegate(os, s, arg, arg->value.p);
488,957✔
394
}
395

396
static int format_p(ostream_t *os, printf_state_t *s, printf_arg_t *arg)
5✔
397
{
398
   return delegate(os, s, arg, arg->value.p);
5✔
399
}
400

401
static int printf_interpret(ostream_t *os, printf_state_t *state,
725,683✔
402
                            const char *fmt, const char *end)
403
{
404
   int nchars = 0;
725,683✔
405
   for (const char *p = fmt;;) {
725,683✔
406
      const char *start = p;
1,922,377✔
407
      while (p < end && *p != '%' && *p != '$')
5,843,808✔
408
         p++;
3,921,431✔
409

410
      if (start < p)
1,922,377✔
411
         nchars += ostream_write(os, start, p - start);
850,418✔
412

413
      if (p == end)
1,922,377✔
414
         return nchars;
725,683✔
415
      else if (*p == '$') {
1,196,694✔
416
         nchars += ansi_escape(os, state, &p);
206,107✔
417
         continue;
206,107✔
418
      }
419
      else if (*p == '%' && *(p + 1) == '%') {
990,587✔
420
         nchars += ostream_write(os, "%", 1);
63,139✔
421
         p += 2;
63,139✔
422
         continue;
63,139✔
423
      }
424

425
      assert(state->pos < state->nargs);
927,448✔
426

427
      printf_arg_t *arg = &(state->args[state->pos]);
927,448✔
428
      nchars += (*arg->fn)(os, state, arg);
927,448✔
429
      p += arg->len;
927,448✔
430

431
      state->pos++;
927,448✔
432
   }
433
}
434

435
int nvc_vfprintf(ostream_t *os, const char *fmt, va_list ap)
703,199✔
436
{
437
   printf_state_t state = {};
703,199✔
438

439
   const char *end = NULL;
703,199✔
440
   for (const char *p = fmt;;) {
703,199✔
441
      while (*p != '\0' && *p != '%')
6,390,882✔
442
         p++;
4,697,096✔
443

444
      if (*p == '\0') {
1,693,786✔
445
         end = p;
703,199✔
446
         break;
703,199✔
447
      }
448

449
      printf_arg_t arg = {
990,587✔
450
         .start = p,
451
         .width = INT_MIN,
452
         .precision = INT_MIN,
453
      };
454
      bool z_mod = false;
990,587✔
455
      int l_mod = 0;
990,587✔
456
   again:
1,305,043✔
457
      switch (*++p) {
1,305,043✔
458
      case 'l':
45,643✔
459
         l_mod++;
45,643✔
460
         goto again;
45,643✔
461
      case 'z':
160,120✔
462
         z_mod = true;
160,120✔
463
         goto again;
160,120✔
464
      case '-':
27,533✔
465
      case '+':
466
      case '.':
467
      case '0'...'9':
468
         goto again;
27,533✔
469
      case '*':
81,160✔
470
         arg.width = arg.precision;
81,160✔
471
         arg.precision = va_arg(ap, int);
81,160✔
472
         goto again;
81,160✔
473
      case 'd':
369,103✔
474
      case 'i':
475
      case 'x':
476
      case 'u':
477
      case 'o':
478
         if (z_mod) {
369,103✔
479
            arg.value.z = va_arg(ap, size_t);
160,120✔
480
            arg.fn = format_z;
160,120✔
481
         }
482
         else if (l_mod >= 2) {
208,983✔
UNCOV
483
            arg.value.ll = va_arg(ap, long long);
×
UNCOV
484
            arg.fn = format_ll;
×
485
         }
486
         else if (l_mod == 1) {
208,983✔
487
            arg.value.l = va_arg(ap, long);
45,641✔
488
            arg.fn = format_l;
45,641✔
489
         }
490
         else {
491
            arg.value.i = va_arg(ap, int);
163,342✔
492
            arg.fn = format_i;
163,342✔
493
         }
494
         break;
495
      case 'c':
1,466✔
496
         arg.value.i = va_arg(ap, int);
1,466✔
497
         arg.fn = format_i;
1,466✔
498
         break;
1,466✔
499
      case 'e':
1,107✔
500
      case 'f':
501
      case 'g':
502
         arg.value.f = va_arg(ap, double);
1,107✔
503
         arg.fn = format_f;
1,107✔
504
         break;
1,107✔
505
      case 's':
545,104✔
506
         arg.value.p = va_arg(ap, char *);
545,104✔
507
         arg.fn = format_s;
545,104✔
508
         break;
545,104✔
509
      case 'p':
10,668✔
510
         arg.value.p = va_arg(ap, void *);
10,668✔
511
         if ((arg.fn = get_pointer_formatter(p[1])))
10,668✔
512
            p++;
10,663✔
513
         else
514
            arg.fn = format_p;
515
         break;
516
      case '%':
63,139✔
517
         p++;
63,139✔
518
         continue;
63,139✔
UNCOV
519
      default:
×
520
         fatal_trace("unhandled character '%c' in format", *p);
521
      }
522

523
      arg.len = ++p - arg.start;
927,448✔
524

525
      if (state.nargs == MAX_ARGS)
927,448✔
526
         fatal_trace("maximum of %d printf arguments supported", MAX_ARGS);
527
      else
528
         state.args[state.nargs++] = arg;
927,448✔
529
   }
530

531
   return printf_interpret(os, &state, fmt, end);
1,406,398✔
532
}
533

534
int nvc_vprintf(const char *fmt, va_list ap)
8✔
535
{
536
   return nvc_vfprintf(nvc_stdout(), fmt, ap);
8✔
537
}
538

539
int nvc_printf(const char *fmt, ...)
8✔
540
{
541
   va_list ap;
8✔
542
   va_start(ap, fmt);
8✔
543
   const int nchars = nvc_vprintf(fmt, ap);
8✔
544
   va_end(ap);
8✔
545
   return nchars;
8✔
546
}
547

548
int nvc_fprintf(ostream_t *os, const char *fmt, ...)
179,756✔
549
{
550
   va_list ap;
179,756✔
551
   va_start(ap, fmt);
179,756✔
552
   const int nchars = nvc_vfprintf(os, fmt, ap);
179,756✔
553
   va_end(ap);
179,756✔
554
   return nchars;
179,756✔
555
}
556

557
void stdio_ostream_write(const char *buf, size_t len, void *ctx)
1,224,108✔
558
{
559
   FILE *f = ctx;
1,224,108✔
560
   fwrite(buf, len, 1, f);
1,224,108✔
561
}
1,224,108✔
562

563
static void init_terminal_ostream(ostream_t *os, FILE *f)
7,552✔
564
{
565
   os->callback = stdio_ostream_write;
7,552✔
566
   os->context = f;
7,552✔
567
   os->flags = 0;
7,552✔
568

569
   const int saved_errno = errno;
7,552✔
570

571
   if (isatty(fileno(f))) {
7,552✔
UNCOV
572
      os->charset = utf8_terminal() ? CHARSET_UTF8 : CHARSET_ISO88591;
×
UNCOV
573
      os->flags |= OS_TERMINAL;
×
574
   }
575
   else
576
      os->charset = CHARSET_ISO88591;
7,552✔
577

578
   if (color_terminal())
7,552✔
579
      os->flags |= OS_COLOR;
4✔
580

581
   errno = saved_errno;
7,552✔
582
}
7,552✔
583

584
ostream_t *nvc_stdout(void)
7,073✔
585
{
586
   static ostream_t os;
7,073✔
587
   INIT_ONCE(init_terminal_ostream(&os, stdout));
7,073✔
588
   return &os;
7,073✔
589
}
590

591
ostream_t *nvc_stderr(void)
74,555✔
592
{
593
   static ostream_t os;
74,555✔
594
   INIT_ONCE(init_terminal_ostream(&os, stderr));
74,555✔
595
   return &os;
74,555✔
596
}
597

598
char *color_asprintf(const char *fmt, ...)
35✔
599
{
600
   LOCAL_TEXT_BUF tb = tb_new();
70✔
601
   ostream_t os = { tb_ostream_write, tb, CHARSET_ISO88591, OS_COLOR };
35✔
602

603
   va_list ap;
35✔
604
   va_start(ap, fmt);
35✔
605
   nvc_vfprintf(&os, fmt, ap);
35✔
606
   va_end(ap);
35✔
607

608
   return tb_claim(tb);
35✔
609
}
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