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

nickg / nvc / 22091528042

17 Feb 2026 08:40AM UTC coverage: 92.57% (-0.03%) from 92.6%
22091528042

push

github

nickg
Refactor parsing of type marks

74 of 76 new or added lines in 2 files covered. (97.37%)

501 existing lines in 7 files now uncovered.

76868 of 83038 relevant lines covered (92.57%)

443051.54 hits per line

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

94.4
/src/printf.c
1
//
2
//  Copyright (C) 2025  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 "object.h"
21
#include "printf.h"
22
#include "thread.h"
23
#include "type.h"
24

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

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

43
#define MAX_ARGS 25
44

45
typedef struct _printf_state printf_state_t;
46
typedef struct _printf_arg printf_arg_t;
47

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

57
typedef int (*fmt_fn_t)(ostream_t *, printf_state_t *, printf_arg_t *);
58

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

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

73
typedef struct {
74
   const char *name;
75
   int         value;
76
} color_escape_t;
77

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

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

94
int ostream_write(ostream_t *os, const char *buf, size_t len)
1,598,509✔
95
{
96
   (*os->callback)(buf, len, os->context);
1,598,509✔
97
   return len;
1,598,509✔
98
}
99

100
int ostream_putc(ostream_t *os, char ch)
597,525✔
101
{
102
   char buf[1] = { ch };
597,525✔
103
   return ostream_write(os, buf, 1);
597,525✔
104
}
105

106
int ostream_puts(ostream_t *os, const char *str)
7,395✔
107
{
108
   if (str == NULL)
7,395✔
UNCOV
109
      return ostream_write(os, "(null)", 6);
×
110
   else
111
      return ostream_write(os, str, strlen(str));
7,395✔
112
}
113

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

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

129
   for (int i = 0; i < len; i += sizeof(chunk)) {
348✔
130
      const int tocopy = MIN(sizeof(chunk), len - i);
174✔
131
      for (int j = 0; j < tocopy; j++)
709✔
132
         chunk[j] = toupper_iso88591(str[i + j]);
535✔
133

134
      nchars += ostream_write(os, chunk, tocopy);
174✔
135
   }
136

137
   return nchars;
174✔
138
}
139

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

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

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

153
   return ostream_puts(os, type_pp(type));
78✔
154
}
155

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

163
static int delegate(ostream_t *os, printf_state_t *s, printf_arg_t *arg, ...)
508,546✔
164
{
165
   char spec[32];
508,546✔
166
   assert(arg->len + 1 < sizeof(spec));
508,546✔
167
   memcpy(spec, arg->start, arg->len);
508,546✔
168
   spec[arg->len] = '\0';
508,546✔
169

170
   va_list ap, ap2;
508,546✔
171
   va_start(ap, arg);
508,546✔
172
   va_copy(ap2, ap);
508,546✔
173

174
   char small[64];
508,546✔
175
   int req = vsnprintf(small, sizeof(small), spec, ap);
508,546✔
176

177
   if (req + 1 > sizeof(small)) {
508,546✔
178
      char *large = xmalloc(req + 1);
644✔
179
      vsnprintf(large, req + 1, spec, ap2);
644✔
180
      ostream_write(os, large, req);
644✔
181
      free(large);
644✔
182
   }
183
   else
184
      ostream_write(os, small, req);
507,902✔
185

186
   va_end(ap);
508,546✔
187
   va_end(ap2);
508,546✔
188
   return req;
508,546✔
189
}
190

191
static fmt_fn_t get_pointer_formatter(char ch)
446✔
192
{
193
   switch (ch) {
446✔
194
   case 'i': return format_ident;
195
   case 'I': return format_ident_toupper;
174✔
196
   case 'T': return format_type;
212✔
UNCOV
197
   case 'K': return format_object_kind;
×
198
   default: return NULL;
4✔
199
   }
200
}
201

202
static int ansi_escape(ostream_t *os, printf_state_t *state, const char **fmt)
152,219✔
203
{
204
   bool has_format = false;
152,219✔
205
   const char *start = *fmt;
152,219✔
206
   do {
799,459✔
207
      has_format |= (**fmt == '%');
799,459✔
208
      (*fmt)++;
799,459✔
209
   } while (**fmt != '\0' && **fmt != '$');
799,459✔
210

211
   if (**fmt == '\0')
152,219✔
212
      return ostream_write(os, start, *fmt - start);
73✔
213

214
   const char *end = *fmt;
152,146✔
215
   (*fmt)++;   // Advance past final '$'
152,146✔
216

217
   size_t len = len = *fmt - start - 2;
152,146✔
218
   const char *e = e = start + 1;
152,146✔
219
   LOCAL_TEXT_BUF tb = NULL;
304,292✔
220

221
   if (has_format) {
152,146✔
222
      // Expand any embedded formatting inside the ANSI escape
223
      tb = tb_new();
16,405✔
224

225
      ostream_t aos = {
16,405✔
226
         tb_ostream_write,
227
         tb,
228
         os->charset,
16,405✔
229
         false,
230
      };
231

232
      printf_interpret(&aos, state, start + 1, end);
16,405✔
233

234
      e = tb_get(tb);
16,405✔
235
      len = tb_len(tb);
16,405✔
236
   }
237

238
   bool bold;
152,146✔
239
   if ((bold = (*e == '!')))
152,146✔
240
      ++e, --len;
5✔
241

242
   bool bright;
152,146✔
243
   if ((bright = (*e == '+')))
152,146✔
244
      ++e, --len;
29✔
245

246
   if (*e == '#') {
152,146✔
247
      char *eptr;
1✔
248
      int code = strtoul(e + 1, &eptr, 10);
1✔
249
      if (eptr == e + len) {
1✔
250
         char buf[16];
1✔
251
         if (bold)
1✔
UNCOV
252
            checked_sprintf(buf, sizeof(buf), "\033[1;38;5;%dm", code);
×
253
         else
254
            checked_sprintf(buf, sizeof(buf), "\033[38;5;%dm", code);
1✔
255

256
         if (os->terminal)
1✔
257
            ostream_puts(os, buf);
1✔
258

259
         return 0;
1✔
260
      }
261
   }
262

263
   if (strncmp(e, "link:", 5) == 0) {
152,145✔
264
      const char *bel = strchr(e, '\07');
16,406✔
265

266
#ifndef __MINGW32__    // Winpty doesn't recognise these
267
      if (os->terminal) {
16,406✔
268
         ostream_puts(os, "\033]8;;");
2✔
269
         ostream_write(os, e + 5, len - 5);
2✔
270
         ostream_puts(os, "\033]8;;\07");
2✔
271
         return e + len - bel - 1;
2✔
272
      }
273
#endif
274

275
      return ostream_write(os, bel + 1, e + len - bel - 1);
16,404✔
276
   }
277

278
   for (int i = 0; i < ARRAY_LEN(escapes); i++) {
513,644✔
279
      if (strncmp(e, escapes[i].name, len) == 0) {
513,642✔
280
         int code = escapes[i].value + (bright ? 60 : 0);
135,737✔
281
         char buf[16];
135,737✔
282
         if (bold)
135,737✔
283
            checked_sprintf(buf, sizeof(buf), "\033[1;%dm", code);
4✔
284
         else
285
            checked_sprintf(buf, sizeof(buf), "\033[%dm", code);
135,733✔
286

287
         if (os->terminal)
135,737✔
288
            ostream_puts(os, buf);
5✔
289

290
         return 0;
135,737✔
291
      }
292
   }
293

294
   return ostream_write(os, start, *fmt - start);
2✔
295
}
296

297
static int format_z(ostream_t *os, printf_state_t *s, printf_arg_t *arg)
120,390✔
298
{
299
   assert(arg->precision == INT_MIN);
120,390✔
300
   return delegate(os, s, arg, arg->value.z);
120,390✔
301
}
302

UNCOV
303
static int format_ll(ostream_t *os, printf_state_t *s, printf_arg_t *arg)
×
304
{
UNCOV
305
   if (arg->precision != INT_MIN)
×
UNCOV
306
      return delegate(os, s, arg, arg->precision, arg->value.ll);
×
307
   else
UNCOV
308
      return delegate(os, s, arg, arg->value.ll);
×
309
}
310

311
static int format_l(ostream_t *os, printf_state_t *s, printf_arg_t *arg)
8,940✔
312
{
313
   if (arg->precision != INT_MIN)
8,940✔
314
      return delegate(os, s, arg, arg->precision, arg->value.l);
6,232✔
315
   else
316
      return delegate(os, s, arg, arg->value.l);
2,708✔
317
}
318

319
static int format_i(ostream_t *os, printf_state_t *s, printf_arg_t *arg)
66,496✔
320
{
321
   if (arg->precision != INT_MIN)
66,496✔
322
      return delegate(os, s, arg, arg->precision, arg->value.i);
10,145✔
323
   else
324
      return delegate(os, s, arg, arg->value.i);
56,351✔
325
}
326

327
static int format_f(ostream_t *os, printf_state_t *s, printf_arg_t *arg)
656✔
328
{
329
   if (arg->precision != INT_MIN)
656✔
330
      return delegate(os, s, arg, arg->precision, arg->value.f);
7✔
331
   else
332
      return delegate(os, s, arg, arg->value.f);
649✔
333
}
334

335
static int format_s(ostream_t *os, printf_state_t *s, printf_arg_t *arg)
312,060✔
336
{
337
   if (arg->precision != INT_MIN)
312,060✔
338
      return delegate(os, s, arg, arg->precision, arg->value.p);
41,213✔
339
   else
340
      return delegate(os, s, arg, arg->value.p);
270,847✔
341
}
342

343
static int format_p(ostream_t *os, printf_state_t *s, printf_arg_t *arg)
4✔
344
{
345
   return delegate(os, s, arg, arg->value.p);
4✔
346
}
347

348
static int printf_interpret(ostream_t *os, printf_state_t *state,
437,728✔
349
                            const char *fmt, const char *end)
350
{
351
   int nchars = 0;
437,728✔
352
   for (const char *p = fmt;;) {
437,728✔
353
      const char *start = p;
1,106,776✔
354
      while (p < end && *p != '%' && *p != '$')
2,637,745✔
355
         p++;
1,530,969✔
356

357
      if (start < p)
1,106,776✔
358
         nchars += ostream_write(os, start, p - start);
438,094✔
359

360
      if (p == end)
1,106,776✔
361
         return nchars;
437,728✔
362
      else if (*p == '$') {
669,048✔
363
         nchars += ansi_escape(os, state, &p);
152,219✔
364
         continue;
152,219✔
365
      }
366
      else if (*p == '%' && *(p + 1) == '%') {
516,829✔
367
         nchars += ostream_write(os, "%", 1);
7,841✔
368
         p += 2;
7,841✔
369
         continue;
7,841✔
370
      }
371

372
      assert(state->pos < state->nargs);
508,988✔
373

374
      printf_arg_t *arg = &(state->args[state->pos]);
508,988✔
375
      nchars += (*arg->fn)(os, state, arg);
508,988✔
376
      p += arg->len;
508,988✔
377

378
      state->pos++;
508,988✔
379
   }
380
}
381

382
int nvc_vfprintf(ostream_t *os, const char *fmt, va_list ap)
421,323✔
383
{
384
   printf_state_t state = {};
421,323✔
385

386
   const char *end = NULL;
421,323✔
387
   for (const char *p = fmt;;) {
421,323✔
388
      while (*p != '\0' && *p != '%')
3,043,424✔
389
         p++;
2,105,272✔
390

391
      if (*p == '\0') {
938,152✔
392
         end = p;
421,323✔
393
         break;
421,323✔
394
      }
395

396
      printf_arg_t arg = { .start = p, .precision = INT_MIN };
516,829✔
397
      bool z_mod = false;
516,829✔
398
      int l_mod = 0;
516,829✔
399
   again:
722,121✔
400
      switch (*++p) {
722,121✔
401
      case 'l':
8,942✔
402
         l_mod++;
8,942✔
403
         goto again;
8,942✔
404
      case 'z':
120,390✔
405
         z_mod = true;
120,390✔
406
         goto again;
120,390✔
407
      case '-':
18,363✔
408
      case '+':
409
      case '.':
410
      case '0'...'9':
411
         goto again;
18,363✔
412
      case '*':
57,597✔
413
         arg.precision = va_arg(ap, int);
57,597✔
414
         goto again;
57,597✔
415
      case 'd':
194,769✔
416
      case 'i':
417
      case 'x':
418
      case 'u':
419
         if (z_mod) {
194,769✔
420
            arg.value.z = va_arg(ap, size_t);
120,390✔
421
            arg.fn = format_z;
120,390✔
422
         }
423
         else if (l_mod >= 2) {
74,379✔
UNCOV
424
            arg.value.ll = va_arg(ap, long long);
×
UNCOV
425
            arg.fn = format_ll;
×
426
         }
427
         else if (l_mod == 1) {
74,379✔
428
            arg.value.l = va_arg(ap, long);
8,940✔
429
            arg.fn = format_l;
8,940✔
430
         }
431
         else {
432
            arg.value.i = va_arg(ap, int);
65,439✔
433
            arg.fn = format_i;
65,439✔
434
         }
435
         break;
436
      case 'c':
1,057✔
437
         arg.value.i = va_arg(ap, int);
1,057✔
438
         arg.fn = format_i;
1,057✔
439
         break;
1,057✔
440
      case 'e':
656✔
441
      case 'f':
442
      case 'g':
443
         arg.value.f = va_arg(ap, double);
656✔
444
         arg.fn = format_f;
656✔
445
         break;
656✔
446
      case 's':
312,060✔
447
         arg.value.p = va_arg(ap, char *);
312,060✔
448
         arg.fn = format_s;
312,060✔
449
         break;
312,060✔
450
      case 'p':
446✔
451
         arg.value.p = va_arg(ap, void *);
446✔
452
         if ((arg.fn = get_pointer_formatter(p[1])))
446✔
453
            p++;
442✔
454
         else
455
            arg.fn = format_p;
456
         break;
457
      case '%':
7,841✔
458
         p++;
7,841✔
459
         continue;
7,841✔
UNCOV
460
      default:
×
461
         fatal_trace("unhandled character '%c' in format", *p);
462
      }
463

464
      arg.len = ++p - arg.start;
508,988✔
465

466
      if (state.nargs == MAX_ARGS)
508,988✔
467
         fatal_trace("maximum of %d printf arguments supported", MAX_ARGS);
468
      else
469
         state.args[state.nargs++] = arg;
508,988✔
470
   }
471

472
   return printf_interpret(os, &state, fmt, end);
842,646✔
473
}
474

475
int nvc_vprintf(const char *fmt, va_list ap)
10✔
476
{
477
   return nvc_vfprintf(nvc_stdout(), fmt, ap);
10✔
478
}
479

480
int nvc_printf(const char *fmt, ...)
10✔
481
{
482
   va_list ap;
10✔
483
   va_start(ap, fmt);
10✔
484
   const int nchars = nvc_vprintf(fmt, ap);
10✔
485
   va_end(ap);
10✔
486
   return nchars;
10✔
487
}
488

489
int nvc_fprintf(ostream_t *os, const char *fmt, ...)
127,814✔
490
{
491
   va_list ap;
127,814✔
492
   va_start(ap, fmt);
127,814✔
493
   const int nchars = nvc_vfprintf(os, fmt, ap);
127,814✔
494
   va_end(ap);
127,814✔
495
   return nchars;
127,814✔
496
}
497

498
void stdio_ostream_write(const char *buf, size_t len, void *ctx)
867,350✔
499
{
500
   FILE *f = ctx;
867,350✔
501
   fwrite(buf, len, 1, f);
867,350✔
502
}
867,350✔
503

504
static void init_terminal_ostream(ostream_t *os, FILE *f)
7✔
505
{
506
   os->callback = stdio_ostream_write;
7✔
507
   os->context = f;
7✔
508

509
   if (isatty(fileno(f))) {
7✔
UNCOV
510
      os->charset = utf8_terminal() ? CHARSET_UTF8 : CHARSET_ISO88591;
×
UNCOV
511
      os->terminal = color_terminal();
×
512
   }
513
   else {
514
      os->charset = CHARSET_ISO88591;
7✔
515
      os->terminal = false;
7✔
516
   }
517
}
7✔
518

519
ostream_t *nvc_stdout(void)
16✔
520
{
521
   static ostream_t os;
16✔
522
   INIT_ONCE(init_terminal_ostream(&os, stdout));
16✔
523
   return &os;
16✔
524
}
525

526
ostream_t *nvc_stderr(void)
51✔
527
{
528
   static ostream_t os;
51✔
529
   INIT_ONCE(init_terminal_ostream(&os, stderr));
51✔
530
   return &os;
51✔
531
}
532

533
char *color_asprintf(const char *fmt, ...)
23✔
534
{
535
   LOCAL_TEXT_BUF tb = tb_new();
46✔
536
   ostream_t os = { tb_ostream_write, tb, CHARSET_ISO88591, color_terminal() };
23✔
537

538
   va_list ap;
23✔
539
   va_start(ap, fmt);
23✔
540
   nvc_vfprintf(&os, fmt, ap);
23✔
541
   va_end(ap);
23✔
542

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

© 2026 Coveralls, Inc