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

nickg / nvc / 22055729275

16 Feb 2026 08:40AM UTC coverage: 92.6% (+0.03%) from 92.57%
22055729275

push

github

nickg
Use case insensitive comparison when setting generics

3 of 3 new or added lines in 1 file covered. (100.0%)

603 existing lines in 8 files now uncovered.

76804 of 82942 relevant lines covered (92.6%)

443138.55 hits per line

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

96.18
/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 "printf.h"
21
#include "thread.h"
22
#include "type.h"
23

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

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

42
#define MAX_ARGS 25
43

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

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

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

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

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

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

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

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

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

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

105
int ostream_puts(ostream_t *os, const char *str)
7,385✔
106
{
107
   return ostream_write(os, str, strlen(str));
7,385✔
108
}
109

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

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

125
   for (int i = 0; i < len; i += sizeof(chunk)) {
332✔
126
      const int tocopy = MIN(sizeof(chunk), len - i);
166✔
127
      for (int j = 0; j < tocopy; j++)
679✔
128
         chunk[j] = toupper_iso88591(str[i + j]);
513✔
129

130
      nchars += ostream_write(os, chunk, tocopy);
166✔
131
   }
132

133
   return nchars;
166✔
134
}
135

136
static int format_type(ostream_t *os, printf_state_t *state, printf_arg_t *arg)
202✔
137
{
138
   type_t type = arg->value.p;
202✔
139

140
   // Print a fully qualified name if there is another type in the
141
   // argument list with the same simple name
142

143
   for (int i = 0; i < state->nargs; i++) {
384✔
144
      const printf_arg_t *other = &(state->args[i]);
316✔
145
      if (other != arg && other->fn == format_type)
316✔
146
         return ostream_puts(os, type_pp2(type, other->value.p));
134✔
147
   }
148

149
   return ostream_puts(os, type_pp(type));
68✔
150
}
151

152
static int delegate(ostream_t *os, printf_state_t *s, printf_arg_t *arg, ...)
508,476✔
153
{
154
   char spec[32];
508,476✔
155
   assert(arg->len + 1 < sizeof(spec));
508,476✔
156
   memcpy(spec, arg->start, arg->len);
508,476✔
157
   spec[arg->len] = '\0';
508,476✔
158

159
   va_list ap, ap2;
508,476✔
160
   va_start(ap, arg);
508,476✔
161
   va_copy(ap2, ap);
508,476✔
162

163
   char small[64];
508,476✔
164
   int req = vsnprintf(small, sizeof(small), spec, ap);
508,476✔
165

166
   if (req + 1 > sizeof(small)) {
508,476✔
167
      char *large = xmalloc(req + 1);
644✔
168
      vsnprintf(large, req + 1, spec, ap2);
644✔
169
      ostream_write(os, large, req);
644✔
170
      free(large);
644✔
171
   }
172
   else
173
      ostream_write(os, small, req);
507,832✔
174

175
   va_end(ap);
508,476✔
176
   va_end(ap2);
508,476✔
177
   return req;
508,476✔
178
}
179

180
static fmt_fn_t get_pointer_formatter(char ch)
428✔
181
{
182
   switch (ch) {
428✔
183
   case 'i': return format_ident;
184
   case 'I': return format_ident_toupper;
166✔
185
   case 'T': return format_type;
202✔
186
   default: return NULL;
4✔
187
   }
188
}
189

190
static int ansi_escape(ostream_t *os, printf_state_t *state, const char **fmt)
152,199✔
191
{
192
   bool has_format = false;
152,199✔
193
   const char *start = *fmt;
152,199✔
194
   do {
799,382✔
195
      has_format |= (**fmt == '%');
799,382✔
196
      (*fmt)++;
799,382✔
197
   } while (**fmt != '\0' && **fmt != '$');
799,382✔
198

199
   if (**fmt == '\0')
152,199✔
200
      return ostream_write(os, start, *fmt - start);
73✔
201

202
   const char *end = *fmt;
152,126✔
203
   (*fmt)++;   // Advance past final '$'
152,126✔
204

205
   size_t len = len = *fmt - start - 2;
152,126✔
206
   const char *e = e = start + 1;
152,126✔
207
   LOCAL_TEXT_BUF tb = NULL;
304,252✔
208

209
   if (has_format) {
152,126✔
210
      // Expand any embedded formatting inside the ANSI escape
211
      tb = tb_new();
16,405✔
212

213
      ostream_t aos = {
16,405✔
214
         tb_ostream_write,
215
         tb,
216
         os->charset,
16,405✔
217
         false,
218
      };
219

220
      printf_interpret(&aos, state, start + 1, end);
16,405✔
221

222
      e = tb_get(tb);
16,405✔
223
      len = tb_len(tb);
16,405✔
224
   }
225

226
   bool bold;
152,126✔
227
   if ((bold = (*e == '!')))
152,126✔
228
      ++e, --len;
5✔
229

230
   bool bright;
152,126✔
231
   if ((bright = (*e == '+')))
152,126✔
232
      ++e, --len;
29✔
233

234
   if (*e == '#') {
152,126✔
235
      char *eptr;
1✔
236
      int code = strtoul(e + 1, &eptr, 10);
1✔
237
      if (eptr == e + len) {
1✔
238
         char buf[16];
1✔
239
         if (bold)
1✔
UNCOV
240
            checked_sprintf(buf, sizeof(buf), "\033[1;38;5;%dm", code);
×
241
         else
242
            checked_sprintf(buf, sizeof(buf), "\033[38;5;%dm", code);
1✔
243

244
         if (os->terminal)
1✔
245
            ostream_puts(os, buf);
1✔
246

247
         return 0;
1✔
248
      }
249
   }
250

251
   if (strncmp(e, "link:", 5) == 0) {
152,125✔
252
      const char *bel = strchr(e, '\07');
16,406✔
253

254
#ifndef __MINGW32__    // Winpty doesn't recognise these
255
      if (os->terminal) {
16,406✔
256
         ostream_puts(os, "\033]8;;");
2✔
257
         ostream_write(os, e + 5, len - 5);
2✔
258
         ostream_puts(os, "\033]8;;\07");
2✔
259
         return e + len - bel - 1;
2✔
260
      }
261
#endif
262

263
      return ostream_write(os, bel + 1, e + len - bel - 1);
16,404✔
264
   }
265

266
   for (int i = 0; i < ARRAY_LEN(escapes); i++) {
513,577✔
267
      if (strncmp(e, escapes[i].name, len) == 0) {
513,575✔
268
         int code = escapes[i].value + (bright ? 60 : 0);
135,717✔
269
         char buf[16];
135,717✔
270
         if (bold)
135,717✔
271
            checked_sprintf(buf, sizeof(buf), "\033[1;%dm", code);
4✔
272
         else
273
            checked_sprintf(buf, sizeof(buf), "\033[%dm", code);
135,713✔
274

275
         if (os->terminal)
135,717✔
276
            ostream_puts(os, buf);
5✔
277

278
         return 0;
135,717✔
279
      }
280
   }
281

282
   return ostream_write(os, start, *fmt - start);
2✔
283
}
284

285
static int format_z(ostream_t *os, printf_state_t *s, printf_arg_t *arg)
120,385✔
286
{
287
   assert(arg->precision == INT_MIN);
120,385✔
288
   return delegate(os, s, arg, arg->value.z);
120,385✔
289
}
290

UNCOV
291
static int format_ll(ostream_t *os, printf_state_t *s, printf_arg_t *arg)
×
292
{
UNCOV
293
   if (arg->precision != INT_MIN)
×
UNCOV
294
      return delegate(os, s, arg, arg->precision, arg->value.ll);
×
295
   else
UNCOV
296
      return delegate(os, s, arg, arg->value.ll);
×
297
}
298

299
static int format_l(ostream_t *os, printf_state_t *s, printf_arg_t *arg)
8,940✔
300
{
301
   if (arg->precision != INT_MIN)
8,940✔
302
      return delegate(os, s, arg, arg->precision, arg->value.l);
6,232✔
303
   else
304
      return delegate(os, s, arg, arg->value.l);
2,708✔
305
}
306

307
static int format_i(ostream_t *os, printf_state_t *s, printf_arg_t *arg)
66,488✔
308
{
309
   if (arg->precision != INT_MIN)
66,488✔
310
      return delegate(os, s, arg, arg->precision, arg->value.i);
10,145✔
311
   else
312
      return delegate(os, s, arg, arg->value.i);
56,343✔
313
}
314

315
static int format_f(ostream_t *os, printf_state_t *s, printf_arg_t *arg)
656✔
316
{
317
   if (arg->precision != INT_MIN)
656✔
318
      return delegate(os, s, arg, arg->precision, arg->value.f);
7✔
319
   else
320
      return delegate(os, s, arg, arg->value.f);
649✔
321
}
322

323
static int format_s(ostream_t *os, printf_state_t *s, printf_arg_t *arg)
312,003✔
324
{
325
   if (arg->precision != INT_MIN)
312,003✔
326
      return delegate(os, s, arg, arg->precision, arg->value.p);
41,213✔
327
   else
328
      return delegate(os, s, arg, arg->value.p);
270,790✔
329
}
330

331
static int format_p(ostream_t *os, printf_state_t *s, printf_arg_t *arg)
4✔
332
{
333
   return delegate(os, s, arg, arg->value.p);
4✔
334
}
335

336
static int printf_interpret(ostream_t *os, printf_state_t *state,
437,689✔
337
                            const char *fmt, const char *end)
338
{
339
   int nchars = 0;
437,689✔
340
   for (const char *p = fmt;;) {
437,689✔
341
      const char *start = p;
1,106,625✔
342
      while (p < end && *p != '%' && *p != '$')
2,636,970✔
343
         p++;
1,530,345✔
344

345
      if (start < p)
1,106,625✔
346
         nchars += ostream_write(os, start, p - start);
438,015✔
347

348
      if (p == end)
1,106,625✔
349
         return nchars;
437,689✔
350
      else if (*p == '$') {
668,936✔
351
         nchars += ansi_escape(os, state, &p);
152,199✔
352
         continue;
152,199✔
353
      }
354
      else if (*p == '%' && *(p + 1) == '%') {
516,737✔
355
         nchars += ostream_write(os, "%", 1);
7,837✔
356
         p += 2;
7,837✔
357
         continue;
7,837✔
358
      }
359

360
      assert(state->pos < state->nargs);
508,900✔
361

362
      printf_arg_t *arg = &(state->args[state->pos]);
508,900✔
363
      nchars += (*arg->fn)(os, state, arg);
508,900✔
364
      p += arg->len;
508,900✔
365

366
      state->pos++;
508,900✔
367
   }
368
}
369

370
int nvc_vfprintf(ostream_t *os, const char *fmt, va_list ap)
421,284✔
371
{
372
   printf_state_t state = {};
421,284✔
373

374
   const char *end = NULL;
421,284✔
375
   for (const char *p = fmt;;) {
421,284✔
376
      while (*p != '\0' && *p != '%')
3,042,572✔
377
         p++;
2,104,551✔
378

379
      if (*p == '\0') {
938,021✔
380
         end = p;
421,284✔
381
         break;
421,284✔
382
      }
383

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

452
      arg.len = ++p - arg.start;
508,900✔
453

454
      if (state.nargs == MAX_ARGS)
508,900✔
455
         fatal_trace("maximum of %d printf arguments supported", MAX_ARGS);
456
      else
457
         state.args[state.nargs++] = arg;
508,900✔
458
   }
459

460
   return printf_interpret(os, &state, fmt, end);
842,568✔
461
}
462

463
int nvc_vprintf(const char *fmt, va_list ap)
10✔
464
{
465
   return nvc_vfprintf(nvc_stdout(), fmt, ap);
10✔
466
}
467

468
int nvc_printf(const char *fmt, ...)
10✔
469
{
470
   va_list ap;
10✔
471
   va_start(ap, fmt);
10✔
472
   const int nchars = nvc_vprintf(fmt, ap);
10✔
473
   va_end(ap);
10✔
474
   return nchars;
10✔
475
}
476

477
int nvc_fprintf(ostream_t *os, const char *fmt, ...)
127,815✔
478
{
479
   va_list ap;
127,815✔
480
   va_start(ap, fmt);
127,815✔
481
   const int nchars = nvc_vfprintf(os, fmt, ap);
127,815✔
482
   va_end(ap);
127,815✔
483
   return nchars;
127,815✔
484
}
485

486
void stdio_ostream_write(const char *buf, size_t len, void *ctx)
867,344✔
487
{
488
   FILE *f = ctx;
867,344✔
489
   fwrite(buf, len, 1, f);
867,344✔
490
}
867,344✔
491

492
static void init_terminal_ostream(ostream_t *os, FILE *f)
7✔
493
{
494
   os->callback = stdio_ostream_write;
7✔
495
   os->context = f;
7✔
496

497
   if (isatty(fileno(f))) {
7✔
UNCOV
498
      os->charset = utf8_terminal() ? CHARSET_UTF8 : CHARSET_ISO88591;
×
UNCOV
499
      os->terminal = color_terminal();
×
500
   }
501
   else {
502
      os->charset = CHARSET_ISO88591;
7✔
503
      os->terminal = false;
7✔
504
   }
505
}
7✔
506

507
ostream_t *nvc_stdout(void)
16✔
508
{
509
   static ostream_t os;
16✔
510
   INIT_ONCE(init_terminal_ostream(&os, stdout));
16✔
511
   return &os;
16✔
512
}
513

514
ostream_t *nvc_stderr(void)
51✔
515
{
516
   static ostream_t os;
51✔
517
   INIT_ONCE(init_terminal_ostream(&os, stderr));
51✔
518
   return &os;
51✔
519
}
520

521
char *color_asprintf(const char *fmt, ...)
23✔
522
{
523
   LOCAL_TEXT_BUF tb = tb_new();
46✔
524
   ostream_t os = { tb_ostream_write, tb, CHARSET_ISO88591, color_terminal() };
23✔
525

526
   va_list ap;
23✔
527
   va_start(ap, fmt);
23✔
528
   nvc_vfprintf(&os, fmt, ap);
23✔
529
   va_end(ap);
23✔
530

531
   return tb_claim(tb);
23✔
532
}
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