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

nickg / nvc / 28699921732

04 Jul 2026 07:41AM UTC coverage: 92.113% (-0.2%) from 92.286%
28699921732

push

github

nickg
More fixes for Verilog expression width propagation

59 of 60 new or added lines in 2 files covered. (98.33%)

669 existing lines in 12 files now uncovered.

79618 of 86435 relevant lines covered (92.11%)

657975.43 hits per line

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

93.58
/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,752,330✔
98
{
99
   (*os->callback)(buf, len, os->context);
2,752,330✔
100
   return len;
2,752,330✔
101
}
102

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

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

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

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

132
   for (int i = 0; i < len; i += sizeof(chunk)) {
478✔
133
      const int tocopy = MIN(sizeof(chunk), len - i);
239✔
134
      for (int j = 0; j < tocopy; j++)
1,158✔
135
         chunk[j] = toupper_iso88591(str[i + j]);
919✔
136

137
      nchars += ostream_write(os, chunk, tocopy);
239✔
138
   }
139

140
   return nchars;
239✔
141
}
142

143
static int format_type(ostream_t *os, printf_state_t *state, printf_arg_t *arg)
228✔
144
{
145
   type_t type = arg->value.p;
228✔
146

147
   // Print a fully qualified name if there is another type in the
148
   // argument list with the same simple name
149

150
   for (int i = 0; i < state->nargs; i++) {
443✔
151
      const printf_arg_t *other = &(state->args[i]);
353✔
152
      if (other != arg && other->fn == format_type)
353✔
153
         return ostream_puts(os, type_pp2(type, other->value.p));
138✔
154
   }
155

156
   return ostream_puts(os, type_pp(type));
90✔
157
}
158

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

166
static int format_mir(ostream_t *os, printf_state_t *state, printf_arg_t *arg)
16✔
167
{
168
   mir_value_t *value = arg->value.p;
16✔
169
   char buf[64];
16✔
170

171
   switch (value->tag) {
16✔
172
   case MIR_TAG_NODE:
4✔
173
      checked_sprintf(buf, sizeof(buf), "%%%d", value->id);
4✔
174
      break;
4✔
175
   case MIR_TAG_CONST:
12✔
176
      checked_sprintf(buf, sizeof(buf), "#%d",
12✔
177
                      value->id - (1 << (_MIR_ID_BITS - 1)));
12✔
178
      break;
12✔
179
   default:
×
180
      checked_sprintf(buf, sizeof(buf), "{tag:%d, id:%x}",
×
181
                      value->tag, value->id);
×
182
      break;
×
183
   }
184

185
   return ostream_puts(os, buf);
16✔
186
}
187

188
static int delegate(ostream_t *os, printf_state_t *s, printf_arg_t *arg, ...)
924,948✔
189
{
190
   char spec[32];
924,948✔
191
   assert(arg->len + 1 < sizeof(spec));
924,948✔
192
   memcpy(spec, arg->start, arg->len);
924,948✔
193
   spec[arg->len] = '\0';
924,948✔
194

195
   va_list ap, ap2;
924,948✔
196
   va_start(ap, arg);
924,948✔
197
   va_copy(ap2, ap);
924,948✔
198

199
   char small[64];
924,948✔
200
   int req = vsnprintf(small, sizeof(small), spec, ap);
924,948✔
201

202
   if (req + 1 > sizeof(small)) {
924,948✔
203
      char *large = xmalloc(req + 1);
872✔
204
      vsnprintf(large, req + 1, spec, ap2);
872✔
205
      ostream_write(os, large, req);
872✔
206
      free(large);
872✔
207
   }
208
   else
209
      ostream_write(os, small, req);
924,076✔
210

211
   va_end(ap);
924,948✔
212
   va_end(ap2);
924,948✔
213
   return req;
924,948✔
214
}
215

216
static fmt_fn_t get_pointer_formatter(char ch)
911✔
217
{
218
   switch (ch) {
911✔
219
   case 'i': return format_ident;
220
   case 'I': return format_ident_toupper;
239✔
221
   case 'T': return format_type;
228✔
222
   case 'K': return format_object_kind;
×
223
   case 'M': return format_mir;
16✔
224
   default: return NULL;
5✔
225
   }
226
}
227

228
static int ansi_escape(ostream_t *os, printf_state_t *state, const char **fmt)
205,827✔
229
{
230
   bool has_format = false;
205,827✔
231
   const char *start = *fmt;
205,827✔
232
   do {
1,084,206✔
233
      has_format |= (**fmt == '%');
1,084,206✔
234
      (*fmt)++;
1,084,206✔
235
   } while (**fmt != '\0' && **fmt != '$');
1,084,206✔
236

237
   if (**fmt == '\0') {
205,827✔
238
      int nchars = ostream_putc(os, '$');
198✔
239
      nchars += printf_interpret(os, state, start + 1, *fmt);
198✔
240
      return nchars;
198✔
241
   }
242

243
   const char *end = *fmt;
205,629✔
244
   (*fmt)++;   // Advance past final '$'
205,629✔
245

246
   size_t len = len = *fmt - start - 2;
205,629✔
247
   const char *e = e = start + 1;
205,629✔
248
   LOCAL_TEXT_BUF tb = NULL;
411,258✔
249

250
   if (has_format) {
205,629✔
251
      // Expand any embedded formatting inside the ANSI escape
252
      tb = tb_new();
22,281✔
253

254
      ostream_t aos = {
22,281✔
255
         tb_ostream_write,
256
         tb,
257
         os->charset,
22,281✔
258
      };
259

260
      printf_interpret(&aos, state, start + 1, end);
22,281✔
261

262
      e = tb_get(tb);
22,281✔
263
      len = tb_len(tb);
22,281✔
264
   }
265

266
   bool bold;
205,629✔
267
   if ((bold = (*e == '!')))
205,629✔
268
      ++e, --len;
2✔
269

270
   bool bright;
205,629✔
271
   if ((bright = (*e == '+')))
205,629✔
272
      ++e, --len;
39✔
273

274
   if (*e == '#') {
205,629✔
275
      char *eptr;
1✔
276
      int code = strtoul(e + 1, &eptr, 10);
1✔
277
      if (eptr == e + len) {
1✔
278
         char buf[16];
1✔
279
         if (bold)
1✔
UNCOV
280
            checked_sprintf(buf, sizeof(buf), "\033[1;38;5;%dm", code);
×
281
         else
282
            checked_sprintf(buf, sizeof(buf), "\033[38;5;%dm", code);
1✔
283

284
         if (os->flags & OS_COLOR)
1✔
285
            ostream_puts(os, buf);
1✔
286

287
         return 0;
1✔
288
      }
289
   }
290

291
   if (strncmp(e, "link:", 5) == 0) {
205,628✔
292
      const char *bel = strchr(e, '\07');
22,282✔
293

294
#ifndef __MINGW32__    // Winpty doesn't recognise these
295
      if (os->flags & OS_TERMINAL) {
22,282✔
296
         ostream_puts(os, "\033]8;;");
2✔
297
         ostream_write(os, e + 5, len - 5);
2✔
298
         ostream_puts(os, "\033]8;;\07");
2✔
299
         return e + len - bel - 1;
2✔
300
      }
301
#endif
302

303
      return ostream_write(os, bel + 1, e + len - bel - 1);
22,280✔
304
   }
305

306
   for (int i = 0; i < ARRAY_LEN(escapes); i++) {
693,094✔
307
      if (strncmp(e, escapes[i].name, len) == 0) {
693,092✔
308
         int code = escapes[i].value + (bright ? 60 : 0);
183,344✔
309
         char buf[16];
183,344✔
310
         if (bold)
183,344✔
311
            checked_sprintf(buf, sizeof(buf), "\033[1;%dm", code);
1✔
312
         else
313
            checked_sprintf(buf, sizeof(buf), "\033[%dm", code);
183,343✔
314

315
         if (os->flags & OS_COLOR)
183,344✔
316
            ostream_puts(os, buf);
1,839✔
317

318
         return 0;
183,344✔
319
      }
320
   }
321

322
   return ostream_write(os, start, *fmt - start);
2✔
323
}
324

325
static int format_z(ostream_t *os, printf_state_t *s, printf_arg_t *arg)
159,979✔
326
{
327
   assert(arg->precision == INT_MIN);
159,979✔
328
   return delegate(os, s, arg, arg->value.z);
159,979✔
329
}
330

331
static int format_ll(ostream_t *os, printf_state_t *s, printf_arg_t *arg)
×
332
{
333
   if (arg->precision != INT_MIN)
×
UNCOV
334
      return delegate(os, s, arg, arg->precision, arg->value.ll);
×
335
   else
UNCOV
336
      return delegate(os, s, arg, arg->value.ll);
×
337
}
338

339
static int format_l(ostream_t *os, printf_state_t *s, printf_arg_t *arg)
45,657✔
340
{
341
   if (arg->precision != INT_MIN)
45,657✔
342
      return delegate(os, s, arg, arg->precision, arg->value.l);
11,181✔
343
   else
344
      return delegate(os, s, arg, arg->value.l);
34,476✔
345
}
346

347
static int format_i(ostream_t *os, printf_state_t *s, printf_arg_t *arg)
164,500✔
348
{
349
   if (arg->precision != INT_MIN)
164,500✔
350
      return delegate(os, s, arg, arg->precision, arg->value.i);
13,699✔
351
   else
352
      return delegate(os, s, arg, arg->value.i);
150,801✔
353
}
354

355
static int format_f(ostream_t *os, printf_state_t *s, printf_arg_t *arg)
1,107✔
356
{
357
   if (arg->width != INT_MIN)
1,107✔
358
      return delegate(os, s, arg, arg->width, arg->precision, arg->value.f);
77✔
359
   else if (arg->precision != INT_MIN)
1,030✔
360
      return delegate(os, s, arg, arg->precision, arg->value.f);
21✔
361
   else
362
      return delegate(os, s, arg, arg->value.f);
1,009✔
363
}
364

365
static int format_s(ostream_t *os, printf_state_t *s, printf_arg_t *arg)
553,700✔
366
{
367
   if (arg->width != INT_MIN)
553,700✔
368
      return delegate(os, s, arg, arg->width, arg->precision, arg->value.p);
1✔
369
   else if (arg->precision != INT_MIN)
553,699✔
370
      return delegate(os, s, arg, arg->precision, arg->value.p);
56,089✔
371
   else
372
      return delegate(os, s, arg, arg->value.p);
497,610✔
373
}
374

375
static int format_p(ostream_t *os, printf_state_t *s, printf_arg_t *arg)
5✔
376
{
377
   return delegate(os, s, arg, arg->value.p);
5✔
378
}
379

380
static int printf_interpret(ostream_t *os, printf_state_t *state,
724,621✔
381
                            const char *fmt, const char *end)
382
{
383
   int nchars = 0;
724,621✔
384
   for (const char *p = fmt;;) {
724,621✔
385
      const char *start = p;
1,919,169✔
386
      while (p < end && *p != '%' && *p != '$')
5,832,673✔
387
         p++;
3,913,504✔
388

389
      if (start < p)
1,919,169✔
390
         nchars += ostream_write(os, start, p - start);
849,319✔
391

392
      if (p == end)
1,919,169✔
393
         return nchars;
724,621✔
394
      else if (*p == '$') {
1,194,548✔
395
         nchars += ansi_escape(os, state, &p);
205,827✔
396
         continue;
205,827✔
397
      }
398
      else if (*p == '%' && *(p + 1) == '%') {
988,721✔
399
         nchars += ostream_write(os, "%", 1);
62,867✔
400
         p += 2;
62,867✔
401
         continue;
62,867✔
402
      }
403

404
      assert(state->pos < state->nargs);
925,854✔
405

406
      printf_arg_t *arg = &(state->args[state->pos]);
925,854✔
407
      nchars += (*arg->fn)(os, state, arg);
925,854✔
408
      p += arg->len;
925,854✔
409

410
      state->pos++;
925,854✔
411
   }
412
}
413

414
int nvc_vfprintf(ostream_t *os, const char *fmt, va_list ap)
702,142✔
415
{
416
   printf_state_t state = {};
702,142✔
417

418
   const char *end = NULL;
702,142✔
419
   for (const char *p = fmt;;) {
702,142✔
420
      while (*p != '\0' && *p != '%')
6,378,769✔
421
         p++;
4,687,906✔
422

423
      if (*p == '\0') {
1,690,863✔
424
         end = p;
702,142✔
425
         break;
702,142✔
426
      }
427

428
      printf_arg_t arg = {
988,721✔
429
         .start = p,
430
         .width = INT_MIN,
431
         .precision = INT_MIN,
432
      };
433
      bool z_mod = false;
988,721✔
434
      int l_mod = 0;
988,721✔
435
   again:
1,303,081✔
436
      switch (*++p) {
1,303,081✔
437
      case 'l':
45,659✔
438
         l_mod++;
45,659✔
439
         goto again;
45,659✔
440
      case 'z':
159,979✔
441
         z_mod = true;
159,979✔
442
         goto again;
159,979✔
443
      case '-':
27,576✔
444
      case '+':
445
      case '.':
446
      case '0'...'9':
447
         goto again;
27,576✔
448
      case '*':
81,146✔
449
         arg.width = arg.precision;
81,146✔
450
         arg.precision = va_arg(ap, int);
81,146✔
451
         goto again;
81,146✔
452
      case 'd':
368,670✔
453
      case 'i':
454
      case 'x':
455
      case 'u':
456
      case 'o':
457
         if (z_mod) {
368,670✔
458
            arg.value.z = va_arg(ap, size_t);
159,979✔
459
            arg.fn = format_z;
159,979✔
460
         }
461
         else if (l_mod >= 2) {
208,691✔
UNCOV
462
            arg.value.ll = va_arg(ap, long long);
×
UNCOV
463
            arg.fn = format_ll;
×
464
         }
465
         else if (l_mod == 1) {
208,691✔
466
            arg.value.l = va_arg(ap, long);
45,657✔
467
            arg.fn = format_l;
45,657✔
468
         }
469
         else {
470
            arg.value.i = va_arg(ap, int);
163,034✔
471
            arg.fn = format_i;
163,034✔
472
         }
473
         break;
474
      case 'c':
1,466✔
475
         arg.value.i = va_arg(ap, int);
1,466✔
476
         arg.fn = format_i;
1,466✔
477
         break;
1,466✔
478
      case 'e':
1,107✔
479
      case 'f':
480
      case 'g':
481
         arg.value.f = va_arg(ap, double);
1,107✔
482
         arg.fn = format_f;
1,107✔
483
         break;
1,107✔
484
      case 's':
553,700✔
485
         arg.value.p = va_arg(ap, char *);
553,700✔
486
         arg.fn = format_s;
553,700✔
487
         break;
553,700✔
488
      case 'p':
911✔
489
         arg.value.p = va_arg(ap, void *);
911✔
490
         if ((arg.fn = get_pointer_formatter(p[1])))
911✔
491
            p++;
906✔
492
         else
493
            arg.fn = format_p;
494
         break;
495
      case '%':
62,867✔
496
         p++;
62,867✔
497
         continue;
62,867✔
UNCOV
498
      default:
×
499
         fatal_trace("unhandled character '%c' in format", *p);
500
      }
501

502
      arg.len = ++p - arg.start;
925,854✔
503

504
      if (state.nargs == MAX_ARGS)
925,854✔
505
         fatal_trace("maximum of %d printf arguments supported", MAX_ARGS);
506
      else
507
         state.args[state.nargs++] = arg;
925,854✔
508
   }
509

510
   return printf_interpret(os, &state, fmt, end);
1,404,284✔
511
}
512

513
int nvc_vprintf(const char *fmt, va_list ap)
8✔
514
{
515
   return nvc_vfprintf(nvc_stdout(), fmt, ap);
8✔
516
}
517

518
int nvc_printf(const char *fmt, ...)
8✔
519
{
520
   va_list ap;
8✔
521
   va_start(ap, fmt);
8✔
522
   const int nchars = nvc_vprintf(fmt, ap);
8✔
523
   va_end(ap);
8✔
524
   return nchars;
8✔
525
}
526

527
int nvc_fprintf(ostream_t *os, const char *fmt, ...)
179,626✔
528
{
529
   va_list ap;
179,626✔
530
   va_start(ap, fmt);
179,626✔
531
   const int nchars = nvc_vfprintf(os, fmt, ap);
179,626✔
532
   va_end(ap);
179,626✔
533
   return nchars;
179,626✔
534
}
535

536
void stdio_ostream_write(const char *buf, size_t len, void *ctx)
1,223,695✔
537
{
538
   FILE *f = ctx;
1,223,695✔
539
   fwrite(buf, len, 1, f);
1,223,695✔
540
}
1,223,695✔
541

542
static void init_terminal_ostream(ostream_t *os, FILE *f)
7,520✔
543
{
544
   os->callback = stdio_ostream_write;
7,520✔
545
   os->context = f;
7,520✔
546
   os->flags = 0;
7,520✔
547

548
   const int saved_errno = errno;
7,520✔
549

550
   if (isatty(fileno(f))) {
7,520✔
UNCOV
551
      os->charset = utf8_terminal() ? CHARSET_UTF8 : CHARSET_ISO88591;
×
UNCOV
552
      os->flags |= OS_TERMINAL;
×
553
   }
554
   else
555
      os->charset = CHARSET_ISO88591;
7,520✔
556

557
   if (color_terminal())
7,520✔
558
      os->flags |= OS_COLOR;
4✔
559

560
   errno = saved_errno;
7,520✔
561
}
7,520✔
562

563
ostream_t *nvc_stdout(void)
7,074✔
564
{
565
   static ostream_t os;
7,074✔
566
   INIT_ONCE(init_terminal_ostream(&os, stdout));
7,074✔
567
   return &os;
7,074✔
568
}
569

570
ostream_t *nvc_stderr(void)
74,491✔
571
{
572
   static ostream_t os;
74,491✔
573
   INIT_ONCE(init_terminal_ostream(&os, stderr));
74,491✔
574
   return &os;
74,491✔
575
}
576

577
char *color_asprintf(const char *fmt, ...)
31✔
578
{
579
   LOCAL_TEXT_BUF tb = tb_new();
62✔
580
   ostream_t os = { tb_ostream_write, tb, CHARSET_ISO88591, OS_COLOR };
31✔
581

582
   va_list ap;
31✔
583
   va_start(ap, fmt);
31✔
584
   nvc_vfprintf(&os, fmt, ap);
31✔
585
   va_end(ap);
31✔
586

587
   return tb_claim(tb);
31✔
588
}
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