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

nickg / nvc / 16693963086

02 Aug 2025 01:08PM UTC coverage: 92.586% (+0.2%) from 92.425%
16693963086

push

github

nickg
Use a dedicated Docker image for build/test

71633 of 77369 relevant lines covered (92.59%)

554188.44 hits per line

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

77.58
/src/rt/shell.c
1
//
2
//  Copyright (C) 2011-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 "common.h"
20
#include "diag.h"
21
#include "hash.h"
22
#include "printer.h"
23
#include "rt/assert.h"
24
#include "rt/model.h"
25
#include "rt/structs.h"
26
#include "shell.h"
27
#include "tree.h"
28
#include "type.h"
29

30
#include <assert.h>
31
#include <errno.h>
32
#include <inttypes.h>
33
#include <stdarg.h>
34
#include <stdio.h>
35
#include <stdlib.h>
36
#include <string.h>
37
#include <unistd.h>
38

39
#include <readline/readline.h>
40
#include <readline/history.h>
41

42
#undef DLLEXPORT
43
#include <tcl.h>
44

45
#define TIME_BUFSZ 32
46

47
#if TCL_MAJOR_VERSION < 9
48
typedef int Tcl_Size;
49
#endif
50

51
typedef struct {
52
   const char     *name;
53
   Tcl_ObjCmdProc *fn;
54
   const char     *help;
55
} shell_cmd_t;
56

57
typedef enum {
58
   SHELL_SIGNAL,
59
   SHELL_REGION,
60
} object_kind_t;
61

62
typedef struct {
63
   object_kind_t kind;
64
   ident_t       name;
65
   ident_t       path;
66
} shell_object_t;
67

68
typedef struct {
69
   shell_object_t  obj;
70
   rt_signal_t    *signal;
71
   print_func_t   *printer;
72
   rt_watch_t     *watch;
73
   tcl_shell_t    *owner;
74
} shell_signal_t;
75

76
typedef struct {
77
   shell_object_t  obj;
78
   rt_scope_t     *scope;
79
} shell_region_t;
80

81
typedef char *(*get_line_fn_t)(tcl_shell_t *);
82

83
typedef struct _tcl_shell {
84
   char            *prompt;
85
   Tcl_Interp      *interp;
86
   shell_cmd_t     *cmds;
87
   size_t           ncmds;
88
   size_t           cmdalloc;
89
   rt_model_t      *model;
90
   tree_t           top;
91
   rt_scope_t      *root;
92
   shell_signal_t  *signals;
93
   unsigned         nsignals;
94
   shell_region_t  *regions;
95
   unsigned         nregions;
96
   hash_t          *namemap;
97
   jit_t           *jit;
98
   int64_t          now_var;
99
   unsigned         deltas_var;
100
   printer_t       *printer;
101
   get_line_fn_t    getline;
102
   shell_handler_t  handler;
103
   bool             quit;
104
   char            *datadir;
105
} tcl_shell_t;
106

107
static __thread tcl_shell_t *rl_shell = NULL;
108

109
__attribute__((format(printf, 2, 3)))
110
static int tcl_error(tcl_shell_t *sh, const char *fmt, ...)
4✔
111
{
112
   va_list ap;
4✔
113
   va_start(ap, fmt);
4✔
114
   char *buf LOCAL = color_vasprintf(fmt, ap);
4✔
115
   va_end(ap);
4✔
116

117
   Tcl_SetObjResult(sh->interp, Tcl_NewStringObj(buf, -1));
4✔
118
   return TCL_ERROR;
4✔
119
}
120

121
static void tcl_dict_put(tcl_shell_t *sh, Tcl_Obj *dict, const char *key,
14✔
122
                         Tcl_Obj *value)
123
{
124
   Tcl_DictObjPut(sh->interp, dict, Tcl_NewStringObj(key, -1), value);
14✔
125
}
14✔
126

127
static Tcl_Obj *tcl_ident_string(ident_t ident)
36✔
128
{
129
   return Tcl_NewStringObj(istr(ident), ident_len(ident));
36✔
130
}
131

132
static int syntax_error(tcl_shell_t *sh, Tcl_Obj *const objv[])
×
133
{
134
   return tcl_error(sh, "syntax error, enter $bold$help %s$$ for usage",
×
135
                    Tcl_GetString(objv[0]));
136
}
137

138
__attribute__((format(printf, 2, 3)))
139
static void shell_printf(tcl_shell_t *sh, const char *fmt, ...)
11✔
140
{
141
   va_list ap;
11✔
142
   va_start(ap, fmt);
11✔
143

144
   if (sh->handler.stdout_write != NULL) {
11✔
145
      char *buf LOCAL = color_vasprintf(fmt, ap);
6✔
146
      (*sh->handler.stdout_write)(buf, strlen(buf), sh->handler.context);
3✔
147
   }
148
   else
149
      wrapped_vprintf(fmt, ap);
8✔
150

151
   va_end(ap);
11✔
152
}
11✔
153

154
static bool shell_has_model(tcl_shell_t *sh)
95✔
155
{
156
   if (sh->model == NULL) {
95✔
157
      tcl_error(sh, "no simulation loaded, elaborate first with the "
×
158
                "$bold$-e$$ command or pass the top-level design unit "
159
                "as a command line argument");
160
      return false;
×
161
   }
162

163
   return true;
164
}
165

166
static void shell_next_time_step(rt_model_t *m, void *user)
3✔
167
{
168
   tcl_shell_t *sh = user;
3✔
169
   assert(sh->handler.next_time_step != NULL);
3✔
170

171
   uint64_t now = model_now(m, NULL);
3✔
172
   (*sh->handler.next_time_step)(now, sh->handler.context);
3✔
173

174
   model_set_phase_cb(sh->model, NEXT_TIME_STEP, shell_next_time_step, sh);
3✔
175
}
3✔
176

177
static void shell_create_model(tcl_shell_t *sh)
24✔
178
{
179
   assert(sh->model == NULL);
24✔
180

181
   sh->model = model_new(sh->jit, NULL);
24✔
182
   create_scope(sh->model, sh->top, NULL);
24✔
183

184
   if (sh->handler.next_time_step != NULL)
24✔
185
      model_set_phase_cb(sh->model, NEXT_TIME_STEP, shell_next_time_step, sh);
1✔
186

187
   model_reset(sh->model);
24✔
188

189
   if ((sh->root = find_scope(sh->model, tree_stmt(sh->top, 0))) == NULL)
24✔
190
      fatal_trace("cannot find root scope");
191
}
24✔
192

193
static void shell_update_now(tcl_shell_t *sh)
50✔
194
{
195
   sh->now_var = model_now(sh->model, &sh->deltas_var);
50✔
196

197
   Tcl_UpdateLinkedVar(sh->interp, "now");
50✔
198
   Tcl_UpdateLinkedVar(sh->interp, "deltas");
50✔
199
}
50✔
200

201
static bool shell_get_printer(tcl_shell_t *sh, shell_signal_t *ss)
40✔
202
{
203
   if (ss->printer == NULL)
40✔
204
      ss->printer = printer_for(sh->printer, tree_type(ss->signal->where));
18✔
205

206
   if (ss->printer == NULL) {
40✔
207
      tcl_error(sh, "cannot display type %s",
×
208
                type_pp(tree_type(ss->signal->where)));
×
209
      return false;
×
210
   }
211

212
   return true;
213
}
214

215
static void shell_add_cmd(tcl_shell_t *sh, const char *name, Tcl_ObjCmdProc fn,
448✔
216
                          const char *help)
217
{
218
   shell_cmd_t cmd = { name, fn, help };
448✔
219

220
   if (sh->cmdalloc == sh->ncmds) {
448✔
221
      sh->cmdalloc = MAX(sh->cmdalloc * 2, 16);
28✔
222
      sh->cmds = xrealloc_array(sh->cmds, sh->cmdalloc, sizeof(shell_cmd_t));
28✔
223
   }
224

225
   sh->cmds[sh->ncmds++] = cmd;
448✔
226

227
   Tcl_CreateObjCommand(sh->interp, name, fn, sh, NULL);
448✔
228
}
448✔
229

230
static void shell_event_cb(uint64_t now, rt_signal_t *s, rt_watch_t *w,
4✔
231
                           void *user)
232
{
233
   shell_signal_t *ss = user;
4✔
234
   shell_handler_t *h = &(ss->owner->handler);
4✔
235

236
   if (h->signal_update != NULL) {
4✔
237
      const char *enc = print_signal(ss->printer, ss->signal, PRINT_F_ENCODE);
4✔
238
      (*h->signal_update)(ss->obj.path, now, s, enc, h->context);
4✔
239
   }
240
}
4✔
241

242
static void watch_signal(shell_signal_t *ss)
3✔
243
{
244
   ss->watch = watch_new(ss->owner->model, shell_event_cb, ss,
3✔
245
                         WATCH_POSTPONED, 1);
246
   model_set_event_cb(ss->owner->model, ss->signal, ss->watch);
3✔
247
}
3✔
248

249
static void recreate_objects(tcl_shell_t *sh, rt_scope_t *scope,
8✔
250
                             shell_signal_t **sptr, shell_region_t **rptr)
251
{
252
   shell_region_t *r = (*rptr)++;
8✔
253
   assert(r->obj.name == ident_downcase(tree_ident(scope->where)));
8✔
254
   r->scope = scope;
8✔
255

256
   for (int i = 0; i < scope->signals.count; i++) {
20✔
257
      shell_signal_t *ss = (*sptr)++;
12✔
258
      ss->signal = scope->signals.items[i];
12✔
259
      assert(ss->obj.name == ident_downcase(tree_ident(ss->signal->where)));
12✔
260

261
      if (ss->watch != NULL)
12✔
262
         watch_signal(ss);
×
263
   }
264

265
   for (int i = 0; i < scope->aliases.count; i++) {
12✔
266
      rt_alias_t *a = scope->aliases.items[i];
4✔
267

268
      shell_signal_t *ss = (*sptr)++;
4✔
269
      assert(ss->obj.name == ident_downcase(tree_ident(a->where)));
4✔
270
      ss->signal = a->signal;
4✔
271

272
      if (ss->watch != NULL)
4✔
273
         watch_signal(ss);
×
274
   }
275

276
   for (int i = 0; i < scope->children.count; i++)
12✔
277
      recreate_objects(sh, scope->children.items[i], sptr, rptr);
4✔
278
}
8✔
279

280
const char *next_option(int *pos, int objc, Tcl_Obj *const objv[])
62✔
281
{
282
   if (*pos >= objc)
62✔
283
      return NULL;
284

285
   const char *opt = Tcl_GetString(objv[*pos]);
61✔
286
   if (opt[0] != '-')
61✔
287
      return NULL;
288

289
   (*pos)++;
5✔
290
   return opt;
5✔
291
}
292

293
static shell_object_t *get_object(tcl_shell_t *sh, const char *name)
45✔
294
{
295
   shell_object_t *obj = hash_get(sh->namemap, ident_new(name));
45✔
296
   if (obj == NULL)
45✔
297
      tcl_error(sh, "cannot find name '%s'", name);
1✔
298

299
   return obj;
45✔
300
}
301

302
static shell_signal_t *get_signal(tcl_shell_t *sh, const char *name)
40✔
303
{
304
   shell_object_t *obj = get_object(sh, name);
40✔
305
   if (obj == NULL)
40✔
306
      return NULL;
307
   else if (obj->kind != SHELL_SIGNAL) {
40✔
308
      tcl_error(sh, "'%s' is not a signal", name);
×
309
      return NULL;
×
310
   }
311

312
   return container_of(obj, shell_signal_t, obj);
313
}
314

315
static const char restart_help[] =
316
   "Restart the simulation";
317

318
static int shell_cmd_restart(ClientData cd, Tcl_Interp *interp,
4✔
319
                             int objc, Tcl_Obj *const objv[])
320
{
321
   tcl_shell_t *sh = cd;
4✔
322

323
   if (!shell_has_model(sh))
4✔
324
      return TCL_ERROR;
325

326
   model_free(sh->model);
4✔
327
   sh->model = NULL;
4✔
328

329
   jit_reset(sh->jit);
4✔
330

331
   clear_vhdl_assert();
4✔
332
   for (vhdl_severity_t s = SEVERITY_NOTE; s <= SEVERITY_FAILURE; s++)
20✔
333
      set_vhdl_assert_enable(s, true);
16✔
334

335
   shell_create_model(sh);
4✔
336

337
   shell_signal_t *wptr = sh->signals;
4✔
338
   shell_region_t *rptr = sh->regions;
4✔
339
   recreate_objects(sh, sh->root, &wptr, &rptr);
4✔
340
   assert(wptr == sh->signals + sh->nsignals);
4✔
341
   assert(rptr == sh->regions + sh->nregions);
4✔
342

343
   shell_update_now(sh);
4✔
344

345
   if (sh->handler.restart_sim != NULL)
4✔
346
      (*sh->handler.restart_sim)(sh->handler.context);
×
347

348
   return TCL_OK;
349
}
350

351
static const char run_help[] =
352
   "Start or resume the simulation";
353

354
static int shell_cmd_run(ClientData cd, Tcl_Interp *interp,
26✔
355
                         int objc, Tcl_Obj *const objv[])
356
{
357
   tcl_shell_t *sh = cd;
26✔
358
   static bool sim_running = false;
26✔
359

360
   if (!shell_has_model(sh))
26✔
361
      return TCL_ERROR;
362
   else if (sim_running)
26✔
363
      return tcl_error(sh, "simulation already running");
×
364

365
   uint64_t stop_time = UINT64_MAX;
26✔
366
   if (objc == 3) {
26✔
367
      Tcl_WideInt base;
4✔
368
      int error = Tcl_GetWideIntFromObj(interp, objv[1], &base);
4✔
369
      if (error != TCL_OK || base <= 0)
4✔
370
         return tcl_error(sh, "invalid time");
×
371

372
      const char *unit = Tcl_GetString(objv[2]);
4✔
373

374
      uint64_t mult;
4✔
375
      if      (strcmp(unit, "fs") == 0) mult = 1;
4✔
376
      else if (strcmp(unit, "ps") == 0) mult = 1000;
4✔
377
      else if (strcmp(unit, "ns") == 0) mult = 1000000;
4✔
378
      else if (strcmp(unit, "us") == 0) mult = 1000000000;
×
379
      else if (strcmp(unit, "ms") == 0) mult = 1000000000000;
×
380
      else
381
         return tcl_error(sh, "invalid time unit %s", unit);
×
382

383
      stop_time = model_now(sh->model, NULL) + (base * mult);
4✔
384
   }
385
   else if (objc != 1)
22✔
386
      return tcl_error(sh, "usage: $bold$run [time units]$$");
×
387

388

389
   sim_running = true;
26✔
390
   model_run(sh->model, stop_time);
26✔
391
   sim_running = false;
26✔
392

393
   shell_update_now(sh);
26✔
394

395
   return TCL_OK;
26✔
396
}
397

398
static const char find_help[] =
399
   "Find signals and other objects in the design\n"
400
   "\n"
401
   "Syntax:\n"
402
   "  find signals [options] <pattern>\n"
403
   "  find regions [options] <pattern>\n"
404
   "\n"
405
   "Options:\n"
406
   "  -r, -recursive\tInclude subregions in wildcard search.\n"
407
   "\n"
408
   "Examples:\n"
409
   "  find signals -r /*\tList all signals in the design\n"
410
   "  find signals /uut/x*\tAll signals in instance UUT that start with X\n"
411
   "  find regions -r *\tList all regions in the design\n";
412

413
static int shell_cmd_find(ClientData cd, Tcl_Interp *interp,
16✔
414
                          int objc, Tcl_Obj *const objv[])
415
{
416
   tcl_shell_t *sh = cd;
16✔
417

418
   enum { SIGNALS, REGIONS } what;
16✔
419
   if (!shell_has_model(sh))
16✔
420
      return TCL_ERROR;
421
   else if (objc < 3)
16✔
422
      goto usage;
×
423
   else if (strcmp(Tcl_GetString(objv[1]), "signals") == 0)
16✔
424
      what = SIGNALS;
425
   else if (strcmp(Tcl_GetString(objv[1]), "regions") == 0)
×
426
      what = REGIONS;
427
   else
428
      goto usage;
×
429

430
   int pos = 2;
16✔
431
   for (const char *opt; (opt = next_option(&pos, objc, objv)); ) {
16✔
432
      if (strcmp(opt, "-recursive") == 0 || strcmp(opt, "-r") == 0) {
×
433
         // Always recursive for now...
434
      }
435
      else
436
         goto usage;
×
437
   }
438

439
   const char *glob = Tcl_GetString(objv[pos]);
16✔
440
   Tcl_Obj *result = Tcl_NewListObj(0, NULL);
16✔
441

442
   switch (what) {
16✔
443
   case SIGNALS:
444
      {
445
         for (int i = 0; i < sh->nsignals; i++) {
44✔
446
            if (!ident_glob(sh->signals[i].obj.path, glob, -1))
28✔
447
               continue;
×
448

449
            Tcl_Obj *obj = tcl_ident_string(sh->signals[i].obj.path);
28✔
450
            Tcl_ListObjAppendElement(interp, result, obj);
28✔
451
         }
452
      }
453
      break;
454

455
   case REGIONS:
456
      {
457
         for (int i = 0; i < sh->nregions; i++) {
×
458
            if (!ident_glob(sh->regions[i].obj.path, glob, -1))
×
459
               continue;
×
460

461
            Tcl_Obj *obj = tcl_ident_string(sh->regions[i].obj.path);
×
462
            Tcl_ListObjAppendElement(interp, result, obj);
×
463
         }
464
         break;
465
      }
466
      break;
467
   }
468

469
   Tcl_SetObjResult(interp, result);
16✔
470
   return TCL_OK;
16✔
471

472
 usage:
×
473
   return syntax_error(sh, objv);
×
474
}
475

476
static const char elaborate_help[] =
477
   "Obsolete command which does nothing.\n";
478

479
static int shell_cmd_elaborate(ClientData cd, Tcl_Interp *interp,
×
480
                               int objc, Tcl_Obj *const objv[])
481
{
482
   tcl_shell_t *sh = cd;
×
483
   return tcl_error(sh, "the elaborate/vsim command has been removed, "
×
484
                    "elaborate the design first with \"nvc -e top --do ...\" "
485
                    "or \"nvc -e top -i\"");
486
}
487

488
static const char examine_help[] =
489
   "Display current value of one of more signals\n"
490
   "\n"
491
   "Syntax:\n"
492
   "  examine [options] <name>...\n"
493
   "\n"
494
   "Note \"exa\" is an alias of this command.\n"
495
   "\n"
496
   "Options:\n"
497
   "  -radix <type>\tFormat as hexadecimal, decimal, or binary.\n"
498
   "  -<radix>\tAlias of \"-radix <radix>\".\n"
499
   "\n"
500
   "Examples:\n"
501
   "  examine /uut/foo\n"
502
   "  exa -hex sig\n";
503

504
static bool parse_radix(const char *str, print_flags_t *flags)
5✔
505
{
506
   if (strcmp(str, "binary") == 0 || strcmp(str, "bin") == 0
5✔
507
       || strcmp(str, "b") == 0) {
4✔
508
      *flags &= ~PRINT_F_RADIX;
1✔
509
      *flags |= PRINT_F_BIN;
1✔
510
      return true;
1✔
511
   }
512
   else if (strcmp(str, "-hexadecimal") == 0 || strcmp(str, "hex") == 0
4✔
513
            || strcmp(str, "h") == 0) {
1✔
514
      *flags &= ~PRINT_F_RADIX;
3✔
515
      *flags |= PRINT_F_HEX;
3✔
516
      return true;
3✔
517
   }
518
   else
519
      return false;
520
}
521

522
static int shell_cmd_examine(ClientData cd, Tcl_Interp *interp,
32✔
523
                             int objc, Tcl_Obj *const objv[])
524
{
525
   tcl_shell_t *sh = cd;
32✔
526

527
   if (!shell_has_model(sh))
32✔
528
      return TCL_ERROR;
529

530
   print_flags_t flags = 0;
32✔
531
   int pos = 1;
32✔
532
   for (const char *opt; (opt = next_option(&pos, objc, objv)); ) {
36✔
533
      if (parse_radix(opt + 1, &flags))
4✔
534
         continue;
3✔
535
      else if (strcmp(opt, "-radix") == 0 && pos + 1 < objc) {
1✔
536
         const char *arg = Tcl_GetString(objv[pos++]);
1✔
537
         if (!parse_radix(arg, &flags))
1✔
538
            goto usage;
×
539
      }
540
      else
541
         goto usage;
×
542
   }
543

544
   if (pos == objc)
32✔
545
      goto usage;
×
546

547
   const int count = objc - pos;
32✔
548
   Tcl_Obj *single[1], **result = single;
32✔
549

550
   if (count > 1)
32✔
551
      result = xmalloc_array(count, sizeof(Tcl_Obj *));
1✔
552

553
   for (int i = 0; pos < objc; pos++, i++) {
65✔
554
      const char *name = Tcl_GetString(objv[pos]);
33✔
555
      shell_signal_t *ss = get_signal(sh, name);
33✔
556
      if (ss == NULL)
33✔
557
         return TCL_ERROR;
558

559
      if (!shell_get_printer(sh, ss))
33✔
560
         return TCL_ERROR;
561

562
      const char *str = print_signal(ss->printer, ss->signal, flags);
33✔
563
      result[i] = Tcl_NewStringObj(str, -1);
33✔
564
   }
565

566
   if (count > 1) {
32✔
567
      Tcl_Obj *list = Tcl_NewListObj(count, result);
1✔
568
      Tcl_SetObjResult(interp, list);
1✔
569
      free(result);
1✔
570
   }
571
   else
572
      Tcl_SetObjResult(interp, result[0]);
31✔
573

574
   return TCL_OK;
575

576
 usage:
×
577
   return syntax_error(sh, objv);
×
578
}
579

580
static const char describe_help[] =
581
   "Return information about an object or region\n"
582
   "\n"
583
   "Syntax:\n"
584
   "  describe <name>...\n"
585
   "\n"
586
   "Examples:\n"
587
   "  describe /uut/foo\n"
588
   "  describe /uut\n";
589

590
static int shell_cmd_describe(ClientData cd, Tcl_Interp *interp,
5✔
591
                              int objc, Tcl_Obj *const objv[])
592
{
593
   tcl_shell_t *sh = cd;
5✔
594

595
   if (!shell_has_model(sh))
5✔
596
      return TCL_ERROR;
597

598
   int pos = 1;
5✔
599
   for (const char *opt; (opt = next_option(&pos, objc, objv)); ) {
5✔
600
      goto usage;
×
601
   }
602

603
   if (pos == objc)
5✔
604
      goto usage;
×
605

606
   const int count = objc - pos;
5✔
607
   Tcl_Obj *single[1], **result = single;
5✔
608

609
   if (count > 1)
5✔
610
      result = xmalloc_array(count, sizeof(Tcl_Obj *));
×
611

612
   for (int i = 0; pos < objc; pos++, i++) {
9✔
613
      const char *name = Tcl_GetString(objv[pos]);
5✔
614
      shell_object_t *obj = get_object(sh, name);
5✔
615
      if (obj == NULL)
5✔
616
         return TCL_ERROR;
617

618
      Tcl_Obj *d = result[i] = Tcl_NewDictObj();
4✔
619

620
      tcl_dict_put(sh, d, "name", tcl_ident_string(obj->name));
4✔
621
      tcl_dict_put(sh, d, "path", tcl_ident_string(obj->path));
4✔
622

623
      switch (obj->kind) {
4✔
624
      case SHELL_SIGNAL:
2✔
625
         {
626
            tcl_dict_put(sh, d, "kind", Tcl_NewStringObj("signal", -1));
2✔
627

628
            shell_signal_t *ss = container_of(obj, shell_signal_t, obj);
2✔
629
            type_t type = tree_type(ss->signal->where);
2✔
630
            tcl_dict_put(sh, d, "type", Tcl_NewStringObj(type_pp(type), -1));
2✔
631
         }
632
         break;
2✔
633
      case SHELL_REGION:
2✔
634
         tcl_dict_put(sh, d, "kind", Tcl_NewStringObj("region", -1));
2✔
635
         break;
2✔
636
      }
637
   }
638

639
   if (count > 1) {
4✔
640
      Tcl_Obj *list = Tcl_NewListObj(count, result);
×
641
      Tcl_SetObjResult(interp, list);
×
642
      free(result);
×
643
   }
644
   else
645
      Tcl_SetObjResult(interp, result[0]);
4✔
646

647
   return TCL_OK;
648

649
 usage:
×
650
   return syntax_error(sh, objv);
×
651
}
652

653
static const char force_help[] =
654
   "Force the value of a signal\n"
655
   "\n"
656
   "Syntax:\n"
657
   "  force [<signal> <value>]\n"
658
   "\n"
659
   "Value can be either an enumeration literal ('1', true), an integer "
660
   "(42, 0), or a bit string literal (\"10111\") and must be appropriate "
661
   "for the signal type. Without arguments lists all currently forced "
662
   "signals.\n"
663
   "\n"
664
   "Examples:\n"
665
   "  force /uut/foo '1'\n"
666
   "  force /bitvec \"10011\"\n";
667

668
static int shell_cmd_force(ClientData cd, Tcl_Interp *interp,
6✔
669
                           int objc, Tcl_Obj *const objv[])
670
{
671
   tcl_shell_t *sh = cd;
6✔
672

673
   if (!shell_has_model(sh))
6✔
674
      return TCL_ERROR;
675
   else if (objc != 3 && objc != 1)
6✔
676
      return syntax_error(sh, objv);
×
677

678
   if (objc == 1) {
6✔
679
      for (int i = 0; i < sh->nsignals; i++) {
4✔
680
         shell_signal_t *ss = &(sh->signals[i]);
3✔
681
         if (!(ss->signal->nexus.flags & NET_F_FORCED))
3✔
682
            continue;
×
683

684
         if (!shell_get_printer(sh, ss))
3✔
685
            return TCL_ERROR;
×
686

687
         const size_t nbytes = ss->signal->shared.size;
3✔
688
         uint8_t *value LOCAL = xmalloc(nbytes);
6✔
689
         get_forcing_value(ss->signal, value);
3✔
690

691
         shell_printf(sh, "force %s %s\n", istr(ss->obj.path),
3✔
692
                      print_raw(ss->printer, value, nbytes, 0));
693
      }
694

695
      return TCL_OK;
696
   }
697

698
   const char *signame = Tcl_GetString(objv[1]);
5✔
699
   const char *valstr = Tcl_GetString(objv[2]);
5✔
700

701
   shell_signal_t *ss = get_signal(sh, signame);
5✔
702
   if (ss == NULL)
5✔
703
      return TCL_ERROR;
704

705
   type_t type = tree_type(ss->signal->where);
5✔
706

707
   parsed_value_t value;
5✔
708
   if (!parse_value(type, valstr, &value))
5✔
709
      return tcl_error(sh, "value '%s' is not valid for type %s",
1✔
710
                       valstr, type_pp(type));
711

712
   if (type_is_scalar(type))
4✔
713
      force_signal(sh->model, ss->signal, &value.integer, 0, 1);
2✔
714
   else if (type_is_character_array(type)) {
2✔
715
      const int width = signal_width(ss->signal);
2✔
716
      if (value.enums->count != width) {
2✔
717
         tcl_error(sh, "expected %d elements for signal %s but have %d", width,
1✔
718
                   signame, value.enums->count);
719
         free(value.enums);
1✔
720
         return TCL_ERROR;
1✔
721
      }
722

723
      force_signal(sh->model, ss->signal, value.enums->values, 0, width);
1✔
724
      free(value.enums);
1✔
725
   }
726
   else
727
      return tcl_error(sh, "cannot force signals of type %s", type_pp(type));
×
728

729
   return TCL_OK;
730
}
731

732
static const char noforce_help[] =
733
   "Stop forcing the value of signals\n"
734
   "\n"
735
   "Syntax:\n"
736
   "  noforce <signal>...\n"
737
   "  noforce *\n"
738
   "\n"
739
   "The second form stops forcing all currently forced signals.\n"
740
   "\n"
741
   "Examples:\n"
742
   "  noforce /uut/foo /baz\n";
743

744
static int shell_cmd_noforce(ClientData cd, Tcl_Interp *interp,
3✔
745
                             int objc, Tcl_Obj *const objv[])
746
{
747
   tcl_shell_t *sh = cd;
3✔
748

749
   if (!shell_has_model(sh))
3✔
750
      return TCL_ERROR;
751
   else if (objc == 1)
3✔
752
      return syntax_error(sh, objv);
×
753

754
   for (int i = 1; i < objc; i++) {
5✔
755
      const char *signame = Tcl_GetString(objv[i]);
3✔
756
      if (strcmp(signame, "*") == 0) {
3✔
757
         for (int i = 0; i < sh->nsignals; i++) {
4✔
758
            shell_signal_t *ss = &(sh->signals[i]);
3✔
759
            if (ss->signal->nexus.flags & NET_F_FORCED)
3✔
760
               release_signal(sh->model, ss->signal, 0,
2✔
761
                              signal_width(ss->signal));
2✔
762
         }
763
      }
764
      else {
765
         shell_signal_t *ss = get_signal(sh, signame);
2✔
766
         if (ss == NULL)
2✔
767
            return TCL_ERROR;
768

769
         if (!(ss->signal->nexus.flags & NET_F_FORCED))
2✔
770
            return tcl_error(sh, "signal %s is not forced", signame);
1✔
771

772
         release_signal(sh->model, ss->signal, 0, signal_width(ss->signal));
1✔
773
      }
774
   }
775

776
   return TCL_OK;
777
}
778

779
static const char add_help[] =
780
   "Add signals and other objects to the display\n"
781
   "\n"
782
   "Syntax:\n"
783
   "  add wave [options] <name>...\n"
784
   "\n"
785
   "Options:\n"
786
   "  -r, -recursive\tInclude subregions in wildcard search.\n"
787
   "\n"
788
   "Examples:\n"
789
   "  add wave /*\tAdd all signals to waveform\n";
790

791
static int shell_cmd_add(ClientData cd, Tcl_Interp *interp,
3✔
792
                         int objc, Tcl_Obj *const objv[])
793
{
794
   tcl_shell_t *sh = cd;
3✔
795
   char **globs LOCAL = NULL;
6✔
796

797
   if (objc < 3 || strcmp(Tcl_GetString(objv[1]), "wave") != 0)
3✔
798
      goto usage;
×
799
   else if (!shell_has_model(sh))
3✔
800
      return TCL_ERROR;
801

802
   int pos = 2;
3✔
803
   for (const char *opt; (opt = next_option(&pos, objc, objv)); ) {
3✔
804
      if (strcmp(opt, "-recursive") == 0 || strcmp(opt, "-r") == 0) {
×
805
         // Always recursive for now...
806
      }
807
      else
808
         goto usage;
×
809
   }
810

811
   const int nglobs = objc - pos;
3✔
812
   globs = xmalloc_array(nglobs, sizeof(char *));
3✔
813
   for (int i = 0; i < nglobs; i++)
7✔
814
      globs[i] = Tcl_GetString(objv[pos++]);
4✔
815

816
   for (int i = 0; i < sh->nsignals; i++) {
12✔
817
      shell_signal_t *ss = &(sh->signals[i]);
9✔
818

819
      bool match = false;
9✔
820
      for (int j = 0; j < nglobs; j++)
21✔
821
         match |= ident_glob(ss->obj.path, globs[j], -1);
12✔
822

823
      if (!match || !shell_get_printer(sh, ss))
9✔
824
         continue;
5✔
825

826
      if (sh->handler.add_wave != NULL) {
4✔
827
         const char *enc =
4✔
828
            print_signal(ss->printer, ss->signal, PRINT_F_ENCODE);
4✔
829
         (*sh->handler.add_wave)(ss->obj.path, enc, sh->handler.context);
4✔
830
      }
831

832
      if (ss->watch == NULL)
4✔
833
         watch_signal(ss);
3✔
834
   }
835

836
   return TCL_OK;
837

838
 usage:
×
839
   return syntax_error(sh, objv);
×
840
}
841

842
static const char quit_help[] =
843
   "Obsolete command which does nothing.\n";
844

845
static int shell_cmd_quit(ClientData cd, Tcl_Interp *interp,
×
846
                          int objc, Tcl_Obj *const objv[])
847
{
848
   tcl_shell_t *sh = cd;
×
849
   return tcl_error(sh, "the quit command has been removed");
×
850
}
851

852
static const char exit_help[] =
853
   "Exit the simulator and return a status code\n"
854
   "\n"
855
   "Syntax:\n"
856
   "  exit [-code <integer>]\n"
857
   "\n"
858
   "Options:\n"
859
   "  -code <integer>\tStatus code to return to shell.\n";
860

861
static int shell_cmd_exit(ClientData cd, Tcl_Interp *interp,
1✔
862
                          int objc, Tcl_Obj *const objv[])
863
{
864
   tcl_shell_t *sh = cd;
1✔
865

866
   int pos = 1, status = EXIT_SUCCESS;
1✔
867
   for (const char *opt; (opt = next_option(&pos, objc, objv)); ) {
2✔
868
      if (strcmp(opt, "-code") == 0 && pos < objc)
1✔
869
         status = atoi(Tcl_GetString(objv[pos++]));
1✔
870
      else
871
         goto usage;
×
872
   }
873

874
   if (pos != objc)
1✔
875
      goto usage;
×
876

877
   if (sh->handler.exit != NULL)
1✔
878
      (*sh->handler.exit)(status, sh->handler.context);
1✔
879

880
   Tcl_Exit(status);
1✔
881

882
 usage:
×
883
   return syntax_error(sh, objv);
×
884
}
885

886
static const char help_help[] =
887
   "Display list of commands or detailed help\n"
888
   "\n"
889
   "Use $bold$help <command>$$ to display detailed usage of a particular\n"
890
   "command.\n";
891

892
static int shell_cmd_help(ClientData cd, Tcl_Interp *interp,
×
893
                          int objc, Tcl_Obj *const objv[])
894
{
895
   tcl_shell_t *sh = cd;
×
896

897
   if (objc == 2) {
×
898
      const char *which = Tcl_GetString(objv[1]);
×
899
      for (int i = 0; i < sh->ncmds; i++) {
×
900
         if (strcmp(sh->cmds[i].name, which) == 0) {
×
901
            shell_printf(sh, "%s", sh->cmds[i].help);
×
902
            return TCL_OK;
×
903
         }
904
      }
905

906
      return tcl_error(sh, "invalid command '%s'", which);
×
907
   }
908
   else if (objc != 1)
×
909
      return tcl_error(sh, "syntax error, try $bold$help$$");
×
910

911
   shell_printf(sh, "List of supported commands:\n");
×
912

913
   for (shell_cmd_t *c = sh->cmds; c < sh->cmds + sh->ncmds; c++) {
×
914
      const int linelen = strchrnul(c->help, '\n') - c->help;
×
915
      shell_printf(sh, "  $bold$%-16s$$%.*s\n", c->name, linelen, c->help);
×
916
   }
917

918
   shell_printf(sh, "\n");
×
919
   shell_printf(sh, "Use $bold$help <command>$$ for detailed usage "
×
920
                "of a particular command. Standard TCL commands are "
921
                "also accepted.\n");
922

923
   return TCL_OK;
×
924
}
925

926
static const char copyright_help[] = "Display copyright information";
927

928
static int shell_cmd_copyright(ClientData cd, Tcl_Interp *interp,
×
929
                               int objc, Tcl_Obj *const objv[])
930
{
931
   Tcl_Channel channel = Tcl_GetStdChannel(TCL_STDOUT);
×
932

933
   extern char copy_string[];
×
934
   Tcl_WriteChars(channel, copy_string, -1);
×
935
   Tcl_WriteChars(channel, "\n", 1);
×
936
   Tcl_Flush(channel);
×
937

938
   return TCL_OK;
×
939
}
940

941
static const char echo_help[] = "Display value of arguments";
942

943
static int shell_cmd_echo(ClientData cd, Tcl_Interp *interp,
1✔
944
                          int objc, Tcl_Obj *const objv[])
945
{
946
   Tcl_Channel channel = Tcl_GetStdChannel(TCL_STDOUT);
1✔
947

948
   for (int i = 1; i < objc; i++) {
3✔
949
      if (i > 1) Tcl_WriteChars(channel, " ", 1);
2✔
950
      Tcl_WriteObj(channel, objv[i]);
2✔
951
   }
952

953
   Tcl_WriteChars(channel, "\n", 1);
1✔
954
   Tcl_Flush(channel);
1✔
955

956
   return TCL_OK;
1✔
957
}
958

959
static char *shell_list_generator(const char *script, const char *text,
×
960
                                  int state, int prefix)
961
{
962
   static Tcl_Obj *list = NULL;
×
963
   static int index, len;
×
964
   static Tcl_Size max;
×
965

966
   if (!state) {
×
967
      if (Tcl_Eval(rl_shell->interp, script) != TCL_OK)
×
968
         return NULL;
969

970
      list = Tcl_GetObjResult(rl_shell->interp);
×
971

972
      if (Tcl_ListObjLength(rl_shell->interp, list, &max) != TCL_OK)
×
973
         return NULL;
974

975
      index = 0;
×
976
      len = strlen(text);
×
977
   }
978

979
   while (index < max) {
×
980
      Tcl_Obj *obj;
×
981
      if (Tcl_ListObjIndex(rl_shell->interp, list, index++, &obj) != TCL_OK)
×
982
         return NULL;
×
983

984
      const char *str = Tcl_GetString(obj);
×
985
      if (strncmp(str, text + prefix, len - prefix) == 0) {
×
986
         if (prefix == 0)
×
987
            return xstrdup(str);
×
988
         else {
989
            assert(len >= prefix);
×
990
            const size_t complen = strlen(str);
×
991
            char *buf = xmalloc(prefix + complen + 1);
×
992
            memcpy(buf, text, prefix);
×
993
            memcpy(buf + prefix, str, complen + 1);
×
994
            return buf;
×
995
         }
996
      }
997
   }
998

999
   return NULL;
1000
}
1001

1002
static char *shell_command_generator(const char *text, int state)
×
1003
{
1004
   return shell_list_generator("info commands", text, state, 0);
×
1005
}
1006

1007
static char *shell_variable_generator(const char *text, int state)
×
1008
{
1009
   return shell_list_generator("info vars", text, state, 1);
×
1010
}
1011

1012
static char **shell_tab_completion(const char *text, int start, int end)
×
1013
{
1014
   rl_attempted_completion_over = 0;
×
1015

1016
   if (text[0] == '$')
×
1017
      return rl_completion_matches(text, shell_variable_generator);
×
1018

1019
   // Determine if we are completing a TCL command or not
1020
   int pos = start - 1;
×
1021
   for (; pos >= 0 && isspace_iso88591(rl_line_buffer[pos]); pos--);
×
1022

1023
   if (pos == -1 || rl_line_buffer[pos] == '[')
×
1024
      return rl_completion_matches(text, shell_command_generator);
×
1025

1026
   return NULL;
1027
}
1028

1029
static char *shell_completing_get_line(tcl_shell_t *sh)
×
1030
{
1031
   rl_attempted_completion_function = shell_tab_completion;
×
1032
   rl_completer_quote_characters = "\"'";
×
1033
   rl_completer_word_break_characters = " \t\r\n[]{}";
×
1034
   rl_special_prefixes = "$";
×
1035
   rl_shell = sh;
×
1036

1037
   char *buf = readline(sh->prompt);
×
1038
   if ((buf != NULL) && (*buf != '\0'))
×
1039
      add_history(buf);
×
1040

1041
   rl_shell = NULL;
×
1042
   return buf;
×
1043
}
1044

1045

1046
static char *shell_raw_get_line(tcl_shell_t *sh)
92✔
1047
{
1048
   fputs(sh->prompt, stdout);
92✔
1049
   fflush(stdout);
92✔
1050

1051
   LOCAL_TEXT_BUF tb = tb_new();
184✔
1052

1053
   size_t off = 0;
92✔
1054
   for (;;) {
1,748✔
1055
      int ch = fgetc(stdin);
1,840✔
1056
      fputc(ch, stdout);
1,840✔
1057
      switch (ch) {
1,840✔
1058
      case EOF:
1059
         return (off > 0) ? tb_claim(tb) : NULL;
1060
      case '\n':
88✔
1061
         return tb_claim(tb);
88✔
1062
      default:
1,748✔
1063
         tb_append(tb, ch);
1,748✔
1064
      }
1065
   }
1066
}
1067

1068
void shell_print_banner(tcl_shell_t *sh)
4✔
1069
{
1070
   extern const char version_string[];
4✔
1071
   shell_printf(sh, "\n");
4✔
1072

1073
   if (sh->handler.stdout_write == NULL)
4✔
1074
      print_centred(version_string);
4✔
1075
   else
1076
      shell_printf(sh, "\t%s", version_string);
×
1077

1078
   static const char blurb[] =
4✔
1079
      "\n\nThis program comes with ABSOLUTELY NO WARRANTY. This is free "
1080
      "software, and you are welcome to redistribute it under certain "
1081
      "conditions; type $bold$copyright$$ for details.\n\n"
1082
      "Type $bold$help$$ for a list of supported commands.\n\n";
1083

1084
   shell_printf(sh, blurb);
4✔
1085
}
4✔
1086

1087
static int compare_shell_cmd(const void *a, const void *b)
1,652✔
1088
{
1089
   return strcmp(((shell_cmd_t *)a)->name, ((shell_cmd_t *)b)->name);
1,652✔
1090
}
1091

1092
tcl_shell_t *shell_new(jit_t *jit)
28✔
1093
{
1094
   tcl_shell_t *sh = xcalloc(sizeof(tcl_shell_t));
28✔
1095
#ifdef RL_VERSION_MAJOR
1096
   sh->prompt   = color_asprintf("\001$+cyan$\002%%\001$$\002 ");
28✔
1097
#else
1098
   sh->prompt   = color_asprintf("$+cyan$%%$$ ");
1099
#endif
1100
   sh->interp   = Tcl_CreateInterp();
28✔
1101
   sh->jit      = jit;
28✔
1102
   sh->printer  = printer_new();
28✔
1103

1104
   if (isatty(fileno(stdin)))
28✔
1105
      sh->getline = shell_completing_get_line;
×
1106
   else
1107
      sh->getline = shell_raw_get_line;
28✔
1108

1109
   if (Tcl_Init(sh->interp) != 0)
28✔
1110
      fatal("%s", Tcl_GetStringResult(sh->interp));
×
1111

1112
   Tcl_LinkVar(sh->interp, "now", (char *)&sh->now_var,
28✔
1113
               TCL_LINK_WIDE_INT | TCL_LINK_READ_ONLY);
1114
   Tcl_LinkVar(sh->interp, "deltas", (char *)&sh->deltas_var,
28✔
1115
               TCL_LINK_UINT | TCL_LINK_READ_ONLY);
1116

1117
   {
1118
      LOCAL_TEXT_BUF tb = tb_new();
28✔
1119
      get_data_dir(tb);
28✔
1120
      sh->datadir = tb_claim(tb);
28✔
1121
   }
1122
   Tcl_LinkVar(sh->interp, "nvc_dataDir", (char *)&sh->datadir,
28✔
1123
               TCL_LINK_READ_ONLY | TCL_LINK_STRING);
1124

1125
   atexit(Tcl_Finalize);
28✔
1126

1127
   Tcl_DeleteCommand(sh->interp, "exit");
28✔
1128

1129
   shell_add_cmd(sh, "help", shell_cmd_help, help_help);
28✔
1130
   shell_add_cmd(sh, "exit", shell_cmd_exit, exit_help);
28✔
1131
   shell_add_cmd(sh, "copyright", shell_cmd_copyright, copyright_help);
28✔
1132
   shell_add_cmd(sh, "find", shell_cmd_find, find_help);
28✔
1133
   shell_add_cmd(sh, "run", shell_cmd_run, run_help);
28✔
1134
   shell_add_cmd(sh, "restart", shell_cmd_restart, restart_help);
28✔
1135
   shell_add_cmd(sh, "elaborate", shell_cmd_elaborate, elaborate_help);
28✔
1136
   shell_add_cmd(sh, "vsim", shell_cmd_elaborate, elaborate_help);
28✔
1137
   shell_add_cmd(sh, "examine", shell_cmd_examine, examine_help);
28✔
1138
   shell_add_cmd(sh, "exa", shell_cmd_examine, examine_help);
28✔
1139
   shell_add_cmd(sh, "add", shell_cmd_add, add_help);
28✔
1140
   shell_add_cmd(sh, "quit", shell_cmd_quit, quit_help);
28✔
1141
   shell_add_cmd(sh, "force", shell_cmd_force, force_help);
28✔
1142
   shell_add_cmd(sh, "noforce", shell_cmd_noforce, noforce_help);
28✔
1143
   shell_add_cmd(sh, "echo", shell_cmd_echo, echo_help);
28✔
1144
   shell_add_cmd(sh, "describe", shell_cmd_describe, describe_help);
28✔
1145

1146
   qsort(sh->cmds, sh->ncmds, sizeof(shell_cmd_t), compare_shell_cmd);
28✔
1147

1148
   return sh;
28✔
1149
}
1150

1151
void shell_free(tcl_shell_t *sh)
27✔
1152
{
1153
   if (sh->model != NULL) {
27✔
1154
      model_free(sh->model);
20✔
1155
      hash_free(sh->namemap);
20✔
1156
      free(sh->signals);
20✔
1157
      free(sh->regions);
20✔
1158
   }
1159

1160
   printer_free(sh->printer);
27✔
1161
   Tcl_DeleteInterp(sh->interp);
27✔
1162

1163
   free(sh->datadir);
27✔
1164
   free(sh->prompt);
27✔
1165
   free(sh->cmds);
27✔
1166
   free(sh);
27✔
1167
}
27✔
1168

1169
bool shell_eval(tcl_shell_t *sh, const char *script, const char **result)
129✔
1170
{
1171
   const int code = Tcl_Eval(sh->interp, script);
129✔
1172

1173
   switch (code) {
128✔
1174
   case TCL_OK:
80✔
1175
      if (result != NULL)
80✔
1176
         *result = Tcl_GetStringResult(sh->interp);
80✔
1177
      return true;
1178
   case TCL_ERROR:
48✔
1179
      {
1180
         const char *info = Tcl_GetVar(sh->interp, "::errorInfo", 0);
48✔
1181
         if (info != NULL && *info != '\n')
48✔
1182
            errorf("%s", info);
48✔
1183

1184
         *result = Tcl_GetStringResult(sh->interp);
48✔
1185
         if (info == NULL && *result != NULL && **result != '\0')
48✔
1186
            errorf("%s", *result);
×
1187
      }
1188
      return false;
1189
   default:
×
1190
      warnf("Tcl_Eval returned unknown code %d", code);
×
1191
      return false;
×
1192
   }
1193
}
1194

1195
static void count_objects(rt_scope_t *scope, unsigned *nsignals,
26✔
1196
                          unsigned *nregions)
1197
{
1198
   *nsignals += scope->signals.count + scope->aliases.count;
26✔
1199
   *nregions += 1;
26✔
1200

1201
   for (int i = 0; i < scope->children.count;i ++)
32✔
1202
      count_objects(scope->children.items[i], nsignals, nregions);
6✔
1203
}
26✔
1204

1205
static void recurse_objects(tcl_shell_t *sh, rt_scope_t *scope,
26✔
1206
                            text_buf_t *path, shell_signal_t **sptr,
1207
                            shell_region_t **rptr)
1208
{
1209
   const int base = tb_len(path);
26✔
1210

1211
   shell_region_t *r = (*rptr)++;
26✔
1212
   r->scope = scope;
26✔
1213
   r->obj.kind = SHELL_REGION;
26✔
1214
   r->obj.name = ident_downcase(tree_ident(scope->where));
26✔
1215
   r->obj.path = ident_new(tb_get(path));
26✔
1216

1217
   hash_put(sh->namemap, r->obj.path, &(r->obj));
26✔
1218

1219
   for (int i = 0; i < scope->signals.count; i++) {
65✔
1220
      shell_signal_t *ss = (*sptr)++;
39✔
1221
      ss->signal = scope->signals.items[i];
39✔
1222
      ss->obj.kind = SHELL_SIGNAL;
39✔
1223
      ss->obj.name = ident_downcase(tree_ident(ss->signal->where));
39✔
1224
      ss->owner = sh;
39✔
1225

1226
      tb_istr(path, ss->obj.name);
39✔
1227
      ss->obj.path = ident_new(tb_get(path));
39✔
1228
      tb_trim(path, base);
39✔
1229

1230
      hash_put(sh->namemap, ss->obj.path, &(ss->obj));
39✔
1231
   }
1232

1233
   for (int i = 0; i < scope->aliases.count; i++) {
31✔
1234
      rt_alias_t *a = scope->aliases.items[i];
5✔
1235

1236
      shell_signal_t *ss = (*sptr)++;
5✔
1237
      ss->signal = a->signal;
5✔
1238
      ss->obj.kind = SHELL_SIGNAL;
5✔
1239
      ss->obj.name = ident_downcase(tree_ident(a->where));
5✔
1240
      ss->owner = sh;
5✔
1241

1242
      tb_istr(path, ss->obj.name);
5✔
1243
      ss->obj.path = ident_new(tb_get(path));
5✔
1244
      tb_trim(path, base);
5✔
1245

1246
      hash_put(sh->namemap, ss->obj.path, &(ss->obj));
5✔
1247
   }
1248

1249
   for (int i = 0; i < scope->children.count; i++) {
32✔
1250
      rt_scope_t *child = scope->children.items[i];
6✔
1251
      ident_t name = ident_downcase(tree_ident(child->where));
6✔
1252
      tb_istr(path, name);
6✔
1253
      tb_append(path, '/');
6✔
1254
      recurse_objects(sh, child, path, sptr, rptr);
6✔
1255
      tb_trim(path, base);
6✔
1256
   }
1257
}
26✔
1258

1259
void shell_reset(tcl_shell_t *sh, tree_t top)
20✔
1260
{
1261
   jit_reset(sh->jit);
20✔
1262

1263
   sh->top = top;
20✔
1264

1265
   shell_create_model(sh);
20✔
1266

1267
   sh->nsignals = sh->nregions = 0;
20✔
1268
   count_objects(sh->root, &sh->nsignals, &sh->nregions);
20✔
1269

1270
   sh->signals = xcalloc_array(sh->nsignals, sizeof(shell_signal_t));
20✔
1271
   sh->regions = xcalloc_array(sh->nregions, sizeof(shell_region_t));
20✔
1272
   sh->namemap = hash_new(1 + sh->nsignals * 2);
20✔
1273

1274
   text_buf_t *path = tb_new();
20✔
1275
   shell_signal_t *sptr = sh->signals;
20✔
1276
   shell_region_t *rptr = sh->regions;
20✔
1277
   tb_cat(path, "/");
20✔
1278
   recurse_objects(sh, sh->root, path, &sptr, &rptr);
20✔
1279
   assert(sptr == sh->signals + sh->nsignals);
20✔
1280
   assert(rptr == sh->regions + sh->nregions);
20✔
1281
   tb_free(path);
20✔
1282

1283
   shell_update_now(sh);
20✔
1284

1285
   if (sh->handler.start_sim != NULL)
20✔
1286
      (*sh->handler.start_sim)(tree_ident(top), sh->handler.context);
1✔
1287
}
20✔
1288

1289
void shell_interact(tcl_shell_t *sh)
4✔
1290
{
1291
   shell_print_banner(sh);
4✔
1292

1293
   char *line;
4✔
1294
   while (!sh->quit && (line = (*sh->getline)(sh))) {
92✔
1295
      const char *result = NULL;
88✔
1296
      if (shell_eval(sh, line, &result) && *result != '\0')
88✔
1297
         color_printf("$+black$%s$$\n", result);
8✔
1298

1299
      free(line);
88✔
1300
   }
1301
}
4✔
1302

1303
bool shell_do(tcl_shell_t *sh, const char *file)
16✔
1304
{
1305
   const int code = Tcl_EvalFile(sh->interp, file);
16✔
1306

1307
   switch (code) {
16✔
1308
   case TCL_OK:
1309
      return true;
1310
   case TCL_ERROR:
4✔
1311
      {
1312
         const char *info = Tcl_GetVar(sh->interp, "::errorInfo", 0);
4✔
1313
         if (info != NULL && *info != '\n')
4✔
1314
            errorf("%s", info);
4✔
1315
         else {
1316
            const char *str = Tcl_GetStringResult(sh->interp);
×
1317
            if (str != NULL && *str != '\0')
×
1318
               errorf("%s", str);
×
1319
         }
1320

1321
         return false;
1322
      }
1323
   default:
×
1324
      warnf("Tcl_Eval returned unknown code %d", code);
×
1325
      return false;
×
1326
   }
1327
}
1328

1329
static int shell_redirect_close(ClientData cd, Tcl_Interp *interp)
5✔
1330
{
1331
   return EINVAL;
5✔
1332
}
1333

1334
#if TCL_MAJOR_VERSION >= 9
1335
static int shell_redirect_close2(void *instanceData, Tcl_Interp *interp,
1336
                                 int flags)
1337
{
1338
   return EINVAL;
1339
}
1340
#endif
1341

1342
static void shell_redirect_watch(ClientData cd, int mask)
5✔
1343
{
1344
}
5✔
1345

1346
static int shell_redirect_output(ClientData cd, const char *buf, int nchars,
5✔
1347
                                 int *error)
1348
{
1349
   tcl_shell_t *sh = untag_pointer(cd, tcl_shell_t);
5✔
1350
   switch (pointer_tag(cd)) {
5✔
1351
   case 0:
2✔
1352
      (*sh->handler.stdout_write)(buf, nchars, sh->handler.context);
2✔
1353
      break;
2✔
1354
   case 1:
2✔
1355
      (*sh->handler.stderr_write)(buf, nchars, sh->handler.context);
2✔
1356
      break;
2✔
1357
   case 2:
1✔
1358
      (*sh->handler.backchannel_write)(buf, nchars, sh->handler.context);
1✔
1359
      break;
1✔
1360
   default:
×
1361
      fatal_trace("invalid channel number %"PRIiPTR, pointer_tag(cd));
1362
   }
1363

1364
   return nchars;
5✔
1365
}
1366

1367
static const Tcl_ChannelType redirect_funcs = {
1368
   .typeName = "redirect",
1369
   .version = TCL_CHANNEL_VERSION_5,
1370
   .closeProc = shell_redirect_close,
1371
#if TCL_MAJOR_VERSION >= 9
1372
   .close2Proc = shell_redirect_close2,
1373
#endif
1374
   .watchProc = shell_redirect_watch,
1375
   .outputProc = shell_redirect_output,
1376
};
1377

1378
void shell_set_handler(tcl_shell_t *sh, const shell_handler_t *h)
5✔
1379
{
1380
   sh->handler = *h;
5✔
1381

1382
   if (h->stdout_write != NULL) {
5✔
1383
      Tcl_Channel chan = Tcl_CreateChannel(&redirect_funcs, "redirect0",
9✔
1384
                                           tag_pointer(sh, 0), TCL_WRITABLE);
3✔
1385
      Tcl_SetChannelOption(NULL, chan, "-translation", "lf");
3✔
1386
      Tcl_SetChannelOption(NULL, chan, "-buffering", "line");
3✔
1387
      Tcl_SetChannelOption(NULL, chan, "-encoding", "utf-8");
3✔
1388

1389
      Tcl_RegisterChannel(sh->interp, chan);
3✔
1390
      Tcl_SetStdChannel(chan, TCL_STDOUT);
3✔
1391
   }
1392

1393
   if (h->stderr_write != NULL) {
5✔
1394
      Tcl_Channel chan = Tcl_CreateChannel(&redirect_funcs, "redirect1",
3✔
1395
                                           tag_pointer(sh, 1), TCL_WRITABLE);
1✔
1396
      Tcl_SetChannelOption(NULL, chan, "-translation", "lf");
1✔
1397
      Tcl_SetChannelOption(NULL, chan, "-buffering", "none");
1✔
1398
      Tcl_SetChannelOption(NULL, chan, "-encoding", "utf-8");
1✔
1399

1400
      Tcl_RegisterChannel(sh->interp, chan);
1✔
1401
      Tcl_SetStdChannel(chan, TCL_STDERR);
1✔
1402
   }
1403

1404
   if (h->backchannel_write != NULL) {
5✔
1405
      Tcl_Channel chan = Tcl_CreateChannel(&redirect_funcs, "backchannel",
3✔
1406
                                           tag_pointer(sh, 2), TCL_WRITABLE);
1✔
1407
      Tcl_SetChannelOption(NULL, chan, "-translation", "lf");
1✔
1408
      Tcl_SetChannelOption(NULL, chan, "-buffering", "full");
1✔
1409
      Tcl_SetChannelOption(NULL, chan, "-encoding", "utf-8");
1✔
1410

1411
      Tcl_RegisterChannel(sh->interp, chan);
1✔
1412
   }
1413
}
5✔
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