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

nickg / nvc / 20414172721

21 Dec 2025 06:41PM UTC coverage: 92.602% (+0.006%) from 92.596%
20414172721

push

github

nickg
Move all printf-related functions to a new file

357 of 413 new or added lines in 12 files covered. (86.44%)

3 existing lines in 3 files now uncovered.

75646 of 81689 relevant lines covered (92.6%)

463080.1 hits per line

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

94.49
/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

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

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

41
#define MAX_ARGS 20
42

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

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

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

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

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

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

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

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

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

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

104
int ostream_puts(ostream_t *os, const char *str)
6,986✔
105
{
106
   return ostream_write(os, str, strlen(str));
6,986✔
107
}
108

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

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

124
   for (int i = 0; i < len; i += sizeof(chunk)) {
2✔
125
      const int tocopy = MIN(sizeof(chunk), len - i);
1✔
126
      for (int j = 0; j < tocopy; j++)
6✔
127
         chunk[j] = toupper_iso88591(str[i + j]);
5✔
128

129
      nchars += ostream_write(os, chunk, tocopy);
1✔
130
   }
131

132
   return nchars;
1✔
133
}
134

135
static int delegate(ostream_t *os, printf_state_t *s, printf_arg_t *arg, ...)
467,448✔
136
{
137
   char spec[32];
467,448✔
138
   assert(arg->len + 1 < sizeof(spec));
467,448✔
139
   memcpy(spec, arg->start, arg->len);
467,448✔
140
   spec[arg->len] = '\0';
467,448✔
141

142
   va_list ap, ap2;
467,448✔
143
   va_start(ap, arg);
467,448✔
144
   va_copy(ap2, ap);
467,448✔
145

146
   char small[64];
467,448✔
147
   int req = vsnprintf(small, sizeof(small), spec, ap);
467,448✔
148

149
   if (req + 1 > sizeof(small)) {
467,448✔
150
      char *large = xmalloc(req + 1);
604✔
151
      vsnprintf(large, req + 1, arg->start, ap2);
604✔
152
      ostream_write(os, large, req);
604✔
153
      free(large);
604✔
154
   }
155
   else
156
      ostream_write(os, small, req);
466,844✔
157

158
   va_end(ap);
467,448✔
159
   va_end(ap2);
467,448✔
160
   return req;
467,448✔
161
}
162

163
static fmt_fn_t get_pointer_formatter(char ch)
34✔
164
{
165
   switch (ch) {
34✔
166
   case 'i': return format_ident;
167
   case 'I': return format_ident_toupper;
1✔
168
   default: return NULL;
4✔
169
   }
170
}
171

172
static int ansi_escape(ostream_t *os, printf_state_t *state, const char **fmt)
151,862✔
173
{
174
   bool has_format = false;
151,862✔
175
   const char *start = *fmt;
151,862✔
176
   do {
795,930✔
177
      has_format |= (**fmt == '%');
795,930✔
178
      (*fmt)++;
795,930✔
179
   } while (**fmt != '\0' && **fmt != '$');
795,930✔
180

181
   if (**fmt == '\0')
151,862✔
182
      return ostream_write(os, start, *fmt - start);
73✔
183

184
   const char *end = *fmt;
151,789✔
185
   (*fmt)++;   // Advance past final '$'
151,789✔
186

187
   size_t len = len = *fmt - start - 2;
151,789✔
188
   const char *e = e = start + 1;
151,789✔
189
   LOCAL_TEXT_BUF tb = NULL;
303,578✔
190

191
   if (has_format) {
151,789✔
192
      // Expand any embedded formatting inside the ANSI escape
193
      tb = tb_new();
16,286✔
194

195
      ostream_t aos = {
16,286✔
196
         tb_ostream_write,
197
         tb,
198
         os->charset,
16,286✔
199
         false,
200
      };
201

202
      printf_interpret(&aos, state, start + 1, end);
16,286✔
203

204
      e = tb_get(tb);
16,286✔
205
      len = tb_len(tb);
16,286✔
206
   }
207

208
   bool bold;
151,789✔
209
   if ((bold = (*e == '!')))
151,789✔
210
      ++e, --len;
5✔
211

212
   bool bright;
151,789✔
213
   if ((bright = (*e == '+')))
151,789✔
214
      ++e, --len;
29✔
215

216
   if (*e == '#') {
151,789✔
217
      char *eptr;
1✔
218
      int code = strtoul(e + 1, &eptr, 10);
1✔
219
      if (eptr == e + len) {
1✔
220
         char buf[16];
1✔
221
         if (bold)
1✔
NEW
222
            checked_sprintf(buf, sizeof(buf), "\033[1;38;5;%dm", code);
×
223
         else
224
            checked_sprintf(buf, sizeof(buf), "\033[38;5;%dm", code);
1✔
225

226
         if (os->terminal)
1✔
227
            ostream_puts(os, buf);
1✔
228

229
         return 0;
1✔
230
      }
231
   }
232

233
   if (strncmp(e, "link:", 5) == 0) {
151,788✔
234
      const char *bel = strchr(e, '\07');
16,287✔
235

236
#ifndef __MINGW32__    // Winpty doesn't recognise these
237
      if (os->terminal) {
16,287✔
238
         ostream_puts(os, "\033]8;;");
2✔
239
         ostream_write(os, e + 5, len - 5);
2✔
240
         ostream_puts(os, "\033]8;;\07");
2✔
241
         return e + len - bel - 1;
2✔
242
      }
243
#endif
244

245
      return ostream_write(os, bel + 1, e + len - bel - 1);
16,285✔
246
   }
247

248
   for (int i = 0; i < ARRAY_LEN(escapes); i++) {
513,050✔
249
      if (strncmp(e, escapes[i].name, len) == 0) {
513,048✔
250
         int code = escapes[i].value + (bright ? 60 : 0);
135,499✔
251
         char buf[16];
135,499✔
252
         if (bold)
135,499✔
253
            checked_sprintf(buf, sizeof(buf), "\033[1;%dm", code);
4✔
254
         else
255
            checked_sprintf(buf, sizeof(buf), "\033[%dm", code);
135,495✔
256

257
         if (os->terminal)
135,499✔
258
            ostream_puts(os, buf);
5✔
259

260
         return 0;
135,499✔
261
      }
262
   }
263

264
   return ostream_write(os, start, *fmt - start);
2✔
265
}
266

267
static int format_z(ostream_t *os, printf_state_t *s, printf_arg_t *arg)
119,477✔
268
{
269
   assert(arg->precision == INT_MIN);
119,477✔
270
   return delegate(os, s, arg, arg->value.z);
119,477✔
271
}
272

NEW
273
static int format_ll(ostream_t *os, printf_state_t *s, printf_arg_t *arg)
×
274
{
NEW
275
   if (arg->precision != INT_MIN)
×
NEW
276
      return delegate(os, s, arg, arg->precision, arg->value.ll);
×
277
   else
NEW
278
      return delegate(os, s, arg, arg->value.ll);
×
279
}
280

281
static int format_l(ostream_t *os, printf_state_t *s, printf_arg_t *arg)
3,525✔
282
{
283
   if (arg->precision != INT_MIN)
3,525✔
284
      return delegate(os, s, arg, arg->precision, arg->value.l);
856✔
285
   else
286
      return delegate(os, s, arg, arg->value.l);
2,669✔
287
}
288

289
static int format_i(ostream_t *os, printf_state_t *s, printf_arg_t *arg)
58,868✔
290
{
291
   if (arg->precision != INT_MIN)
58,868✔
292
      return delegate(os, s, arg, arg->precision, arg->value.i);
10,144✔
293
   else
294
      return delegate(os, s, arg, arg->value.i);
48,724✔
295
}
296

297
static int format_f(ostream_t *os, printf_state_t *s, printf_arg_t *arg)
581✔
298
{
299
   if (arg->precision != INT_MIN)
581✔
300
      return delegate(os, s, arg, arg->precision, arg->value.f);
7✔
301
   else
302
      return delegate(os, s, arg, arg->value.f);
574✔
303
}
304

305
static int format_s(ostream_t *os, printf_state_t *s, printf_arg_t *arg)
284,993✔
306
{
307
   if (arg->precision != INT_MIN)
284,993✔
308
      return delegate(os, s, arg, arg->precision, arg->value.p);
41,238✔
309
   else
310
      return delegate(os, s, arg, arg->value.p);
243,755✔
311
}
312

313
static int format_p(ostream_t *os, printf_state_t *s, printf_arg_t *arg)
4✔
314
{
315
   return delegate(os, s, arg, arg->value.p);
4✔
316
}
317

318
static int printf_interpret(ostream_t *os, printf_state_t *state,
408,995✔
319
                            const char *fmt, const char *end)
320
{
321
   int nchars = 0;
408,995✔
322
   for (const char *p = fmt;;) {
408,995✔
323
      const char *start = p;
1,029,961✔
324
      while (p < end && *p != '%' && *p != '$')
2,402,877✔
325
         p++;
1,372,916✔
326

327
      if (start < p)
1,029,961✔
328
         nchars += ostream_write(os, start, p - start);
409,979✔
329

330
      if (p == end)
1,029,961✔
331
         return nchars;
408,995✔
332
      else if (*p == '$') {
620,966✔
333
         nchars += ansi_escape(os, state, &p);
151,862✔
334
         continue;
151,862✔
335
      }
336
      else if (*p == '%' && *(p + 1) == '%') {
469,104✔
337
         nchars += ostream_write(os, "%", 1);
1,626✔
338
         p += 2;
1,626✔
339
         continue;
1,626✔
340
      }
341

342
      assert(state->pos < state->nargs);
467,478✔
343

344
      printf_arg_t *arg = &(state->args[state->pos]);
467,478✔
345
      nchars += (*arg->fn)(os, state, arg);
467,478✔
346
      p += arg->len;
467,478✔
347

348
      state->pos++;
467,478✔
349
   }
350
}
351

352
int nvc_vfprintf(ostream_t *os, const char *fmt, va_list ap)
392,709✔
353
{
354
   printf_state_t state = {};
392,709✔
355

356
   const char *end = NULL;
392,709✔
357
   for (const char *p = fmt;;) {
392,709✔
358
      while (*p != '\0' && *p != '%')
2,807,883✔
359
         p++;
1,946,070✔
360

361
      if (*p == '\0') {
861,813✔
362
         end = p;
392,709✔
363
         break;
392,709✔
364
      }
365

366
      printf_arg_t arg = { .start = p, .precision = INT_MIN };
469,104✔
367
      bool z_mod = false;
469,104✔
368
      int l_mod = 0;
469,104✔
369
   again:
657,361✔
370
      switch (*++p) {
657,361✔
371
      case 'l':
3,527✔
372
         l_mod++;
3,527✔
373
         goto again;
3,527✔
374
      case 'z':
119,477✔
375
         z_mod = true;
119,477✔
376
         goto again;
119,477✔
377
      case '-':
13,008✔
378
      case '+':
379
      case '.':
380
      case '0'...'9':
381
         goto again;
13,008✔
382
      case '*':
52,245✔
383
         arg.precision = va_arg(ap, int);
52,245✔
384
         goto again;
52,245✔
385
      case 'd':
181,152✔
386
      case 'i':
387
      case 'x':
388
      case 'u':
389
         if (z_mod) {
181,152✔
390
            arg.value.z = va_arg(ap, size_t);
119,477✔
391
            arg.fn = format_z;
119,477✔
392
         }
393
         else if (l_mod >= 2) {
61,675✔
NEW
394
            arg.value.ll = va_arg(ap, long long);
×
NEW
395
            arg.fn = format_ll;
×
396
         }
397
         else if (l_mod == 1) {
61,675✔
398
            arg.value.l = va_arg(ap, long);
3,525✔
399
            arg.fn = format_l;
3,525✔
400
         }
401
         else {
402
            arg.value.i = va_arg(ap, int);
58,150✔
403
            arg.fn = format_i;
58,150✔
404
         }
405
         break;
406
      case 'c':
718✔
407
         arg.value.i = va_arg(ap, int);
718✔
408
         arg.fn = format_i;
718✔
409
         break;
718✔
410
      case 'e':
581✔
411
      case 'f':
412
      case 'g':
413
         arg.value.f = va_arg(ap, double);
581✔
414
         arg.fn = format_f;
581✔
415
         break;
581✔
416
      case 's':
284,993✔
417
         arg.value.p = va_arg(ap, char *);
284,993✔
418
         arg.fn = format_s;
284,993✔
419
         break;
284,993✔
420
      case 'p':
34✔
421
         arg.value.p = va_arg(ap, void *);
34✔
422
         if ((arg.fn = get_pointer_formatter(p[1])))
34✔
423
            p++;
30✔
424
         else
425
            arg.fn = format_p;
426
         break;
427
      case '%':
1,626✔
428
         p++;
1,626✔
429
         continue;
1,626✔
NEW
430
      default:
×
431
         fatal_trace("unhandled character '%c' in format", *p);
432
      }
433

434
      arg.len = ++p - arg.start;
467,478✔
435

436
      if (state.nargs == MAX_ARGS)
467,478✔
437
         fatal_trace("maximum of %d printf arguments supported", MAX_ARGS);
438
      else
439
         state.args[state.nargs++] = arg;
467,478✔
440
   }
441

442
   return printf_interpret(os, &state, fmt, end);
785,418✔
443
}
444

445
int nvc_vprintf(const char *fmt, va_list ap)
10✔
446
{
447
   return nvc_vfprintf(nvc_stdout(), fmt, ap);
10✔
448
}
449

450
int nvc_printf(const char *fmt, ...)
10✔
451
{
452
   va_list ap;
10✔
453
   va_start(ap, fmt);
10✔
454
   const int nchars = nvc_vprintf(fmt, ap);
10✔
455
   va_end(ap);
10✔
456
   return nchars;
10✔
457
}
458

459
int nvc_fprintf(ostream_t *os, const char *fmt, ...)
127,242✔
460
{
461
   va_list ap;
127,242✔
462
   va_start(ap, fmt);
127,242✔
463
   const int nchars = nvc_vfprintf(os, fmt, ap);
127,242✔
464
   va_end(ap);
127,242✔
465
   return nchars;
127,242✔
466
}
467

468
void stdio_ostream_write(const char *buf, size_t len, void *ctx)
865,365✔
469
{
470
   FILE *f = ctx;
865,365✔
471
   fwrite(buf, len, 1, f);
865,365✔
472
}
865,365✔
473

474
static void init_terminal_ostream(ostream_t *os, FILE *f)
4✔
475
{
476
   os->callback = stdio_ostream_write;
4✔
477
   os->context = f;
4✔
478

479
   if (isatty(fileno(f))) {
4✔
NEW
480
      os->charset = utf8_terminal() ? CHARSET_UTF8 : CHARSET_ISO88591;
×
NEW
481
      os->terminal = color_terminal();
×
482
   }
483
   else {
484
      os->charset = CHARSET_ISO88591;
4✔
485
      os->terminal = false;
4✔
486
   }
487
}
4✔
488

489
ostream_t *nvc_stdout(void)
16✔
490
{
491
   static ostream_t os;
16✔
492
   INIT_ONCE(init_terminal_ostream(&os, stdout));
16✔
493
   return &os;
16✔
494
}
495

NEW
496
ostream_t *nvc_stderr(void)
×
497
{
NEW
498
   static ostream_t os;
×
NEW
499
   INIT_ONCE(init_terminal_ostream(&os, stderr));
×
NEW
500
   return &os;
×
501
}
502

503
char *color_asprintf(const char *fmt, ...)
23✔
504
{
505
   LOCAL_TEXT_BUF tb = tb_new();
46✔
506
   ostream_t os = { tb_ostream_write, tb, CHARSET_ISO88591, color_terminal() };
23✔
507

508
   va_list ap;
23✔
509
   va_start(ap, fmt);
23✔
510
   nvc_vfprintf(&os, fmt, ap);
23✔
511
   va_end(ap);
23✔
512

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