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

nickg / nvc / 20414715417

21 Dec 2025 07:27PM UTC coverage: 92.603% (+0.001%) from 92.602%
20414715417

push

github

nickg
Switch GitHub Actions tests to MSYS2 clang64 environment

75654 of 81697 relevant lines covered (92.6%)

463841.41 hits per line

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

94.66
/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 20
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,521,617✔
94
{
95
   (*os->callback)(buf, len, os->context);
1,521,617✔
96
   return len;
1,521,617✔
97
}
98

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

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

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

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

125
   for (int i = 0; i < len; i += sizeof(chunk)) {
320✔
126
      const int tocopy = MIN(sizeof(chunk), len - i);
160✔
127
      for (int j = 0; j < tocopy; j++)
645✔
128
         chunk[j] = toupper_iso88591(str[i + j]);
485✔
129

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

133
   return nchars;
160✔
134
}
135

136
static int format_type(ostream_t *os, printf_state_t *state, printf_arg_t *arg)
201✔
137
{
138
   type_t type = arg->value.p;
201✔
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++) {
381✔
144
      const printf_arg_t *other = &(state->args[i]);
314✔
145
      if (other != arg && other->fn == format_type)
314✔
146
         return ostream_puts(os, type_pp2(type, other->value.p));
134✔
147
   }
148

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

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

159
   va_list ap, ap2;
467,097✔
160
   va_start(ap, arg);
467,097✔
161
   va_copy(ap2, ap);
467,097✔
162

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

166
   if (req + 1 > sizeof(small)) {
467,097✔
167
      char *large = xmalloc(req + 1);
604✔
168
      vsnprintf(large, req + 1, arg->start, ap2);
604✔
169
      ostream_write(os, large, req);
604✔
170
      free(large);
604✔
171
   }
172
   else
173
      ostream_write(os, small, req);
466,493✔
174

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

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

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

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

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

205
   size_t len = len = *fmt - start - 2;
151,789✔
206
   const char *e = e = start + 1;
151,789✔
207
   LOCAL_TEXT_BUF tb = NULL;
303,578✔
208

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

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

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

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

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

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

234
   if (*e == '#') {
151,789✔
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✔
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) {
151,788✔
252
      const char *bel = strchr(e, '\07');
16,287✔
253

254
#ifndef __MINGW32__    // Winpty doesn't recognise these
255
      if (os->terminal) {
16,287✔
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,285✔
264
   }
265

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

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

278
         return 0;
135,499✔
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)
119,476✔
286
{
287
   assert(arg->precision == INT_MIN);
119,476✔
288
   return delegate(os, s, arg, arg->value.z);
119,476✔
289
}
290

291
static int format_ll(ostream_t *os, printf_state_t *s, printf_arg_t *arg)
×
292
{
293
   if (arg->precision != INT_MIN)
×
294
      return delegate(os, s, arg, arg->precision, arg->value.ll);
×
295
   else
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)
3,525✔
300
{
301
   if (arg->precision != INT_MIN)
3,525✔
302
      return delegate(os, s, arg, arg->precision, arg->value.l);
856✔
303
   else
304
      return delegate(os, s, arg, arg->value.l);
2,669✔
305
}
306

307
static int format_i(ostream_t *os, printf_state_t *s, printf_arg_t *arg)
58,868✔
308
{
309
   if (arg->precision != INT_MIN)
58,868✔
310
      return delegate(os, s, arg, arg->precision, arg->value.i);
10,144✔
311
   else
312
      return delegate(os, s, arg, arg->value.i);
48,724✔
313
}
314

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

323
static int format_s(ostream_t *os, printf_state_t *s, printf_arg_t *arg)
284,643✔
324
{
325
   if (arg->precision != INT_MIN)
284,643✔
326
      return delegate(os, s, arg, arg->precision, arg->value.p);
41,238✔
327
   else
328
      return delegate(os, s, arg, arg->value.p);
243,405✔
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,
409,002✔
337
                            const char *fmt, const char *end)
338
{
339
   int nchars = 0;
409,002✔
340
   for (const char *p = fmt;;) {
409,002✔
341
      const char *start = p;
1,029,977✔
342
      while (p < end && *p != '%' && *p != '$')
2,402,880✔
343
         p++;
1,372,903✔
344

345
      if (start < p)
1,029,977✔
346
         nchars += ostream_write(os, start, p - start);
409,985✔
347

348
      if (p == end)
1,029,977✔
349
         return nchars;
409,002✔
350
      else if (*p == '$') {
620,975✔
351
         nchars += ansi_escape(os, state, &p);
151,862✔
352
         continue;
151,862✔
353
      }
354
      else if (*p == '%' && *(p + 1) == '%') {
469,113✔
355
         nchars += ostream_write(os, "%", 1);
1,626✔
356
         p += 2;
1,626✔
357
         continue;
1,626✔
358
      }
359

360
      assert(state->pos < state->nargs);
467,487✔
361

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

366
      state->pos++;
467,487✔
367
   }
368
}
369

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

374
   const char *end = NULL;
392,716✔
375
   for (const char *p = fmt;;) {
392,716✔
376
      while (*p != '\0' && *p != '%')
2,807,886✔
377
         p++;
1,946,057✔
378

379
      if (*p == '\0') {
861,829✔
380
         end = p;
392,716✔
381
         break;
392,716✔
382
      }
383

384
      printf_arg_t arg = { .start = p, .precision = INT_MIN };
469,113✔
385
      bool z_mod = false;
469,113✔
386
      int l_mod = 0;
469,113✔
387
   again:
657,369✔
388
      switch (*++p) {
657,369✔
389
      case 'l':
3,527✔
390
         l_mod++;
3,527✔
391
         goto again;
3,527✔
392
      case 'z':
119,476✔
393
         z_mod = true;
119,476✔
394
         goto again;
119,476✔
395
      case '-':
13,008✔
396
      case '+':
397
      case '.':
398
      case '0'...'9':
399
         goto again;
13,008✔
400
      case '*':
52,245✔
401
         arg.precision = va_arg(ap, int);
52,245✔
402
         goto again;
52,245✔
403
      case 'd':
181,151✔
404
      case 'i':
405
      case 'x':
406
      case 'u':
407
         if (z_mod) {
181,151✔
408
            arg.value.z = va_arg(ap, size_t);
119,476✔
409
            arg.fn = format_z;
119,476✔
410
         }
411
         else if (l_mod >= 2) {
61,675✔
412
            arg.value.ll = va_arg(ap, long long);
×
413
            arg.fn = format_ll;
×
414
         }
415
         else if (l_mod == 1) {
61,675✔
416
            arg.value.l = va_arg(ap, long);
3,525✔
417
            arg.fn = format_l;
3,525✔
418
         }
419
         else {
420
            arg.value.i = va_arg(ap, int);
58,150✔
421
            arg.fn = format_i;
58,150✔
422
         }
423
         break;
424
      case 'c':
718✔
425
         arg.value.i = va_arg(ap, int);
718✔
426
         arg.fn = format_i;
718✔
427
         break;
718✔
428
      case 'e':
581✔
429
      case 'f':
430
      case 'g':
431
         arg.value.f = va_arg(ap, double);
581✔
432
         arg.fn = format_f;
581✔
433
         break;
581✔
434
      case 's':
284,643✔
435
         arg.value.p = va_arg(ap, char *);
284,643✔
436
         arg.fn = format_s;
284,643✔
437
         break;
284,643✔
438
      case 'p':
394✔
439
         arg.value.p = va_arg(ap, void *);
394✔
440
         if ((arg.fn = get_pointer_formatter(p[1])))
394✔
441
            p++;
390✔
442
         else
443
            arg.fn = format_p;
444
         break;
445
      case '%':
1,626✔
446
         p++;
1,626✔
447
         continue;
1,626✔
448
      default:
×
449
         fatal_trace("unhandled character '%c' in format", *p);
450
      }
451

452
      arg.len = ++p - arg.start;
467,487✔
453

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

460
   return printf_interpret(os, &state, fmt, end);
785,432✔
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,243✔
478
{
479
   va_list ap;
127,243✔
480
   va_start(ap, fmt);
127,243✔
481
   const int nchars = nvc_vfprintf(os, fmt, ap);
127,243✔
482
   va_end(ap);
127,243✔
483
   return nchars;
127,243✔
484
}
485

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

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

497
   if (isatty(fileno(f))) {
4✔
498
      os->charset = utf8_terminal() ? CHARSET_UTF8 : CHARSET_ISO88591;
×
499
      os->terminal = color_terminal();
×
500
   }
501
   else {
502
      os->charset = CHARSET_ISO88591;
4✔
503
      os->terminal = false;
4✔
504
   }
505
}
4✔
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)
×
515
{
516
   static ostream_t os;
×
517
   INIT_ONCE(init_terminal_ostream(&os, stderr));
×
518
   return &os;
×
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

© 2025 Coveralls, Inc