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

nickg / nvc / 6645772731

25 Oct 2023 08:25PM UTC coverage: 91.22% (+0.002%) from 91.218%
6645772731

push

github

nickg
Add TCL echo command

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

49640 of 54418 relevant lines covered (91.22%)

588130.41 hits per line

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

72.4
/src/rt/shell.c
1
//
2
//  Copyright (C) 2011-2023  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 "eval.h"
22
#include "hash.h"
23
#include "lib.h"
24
#include "lower.h"
25
#include "phase.h"
26
#include "printer.h"
27
#include "rt/model.h"
28
#include "rt/structs.h"
29
#include "scan.h"
30
#include "shell.h"
31
#include "tree.h"
32
#include "type.h"
33

34
#include <assert.h>
35
#include <errno.h>
36
#include <inttypes.h>
37
#include <stdarg.h>
38
#include <stdio.h>
39
#include <stdlib.h>
40
#include <string.h>
41
#include <unistd.h>
42

43
#include <readline/readline.h>
44
#include <readline/history.h>
45

46
#undef DLLEXPORT
47
#include <tcl.h>
48

49
#define TIME_BUFSZ 32
50

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

57
typedef struct {
58
   rt_signal_t  *signal;
59
   ident_t       name;
60
   ident_t       path;
61
   print_func_t *printer;
62
   rt_watch_t   *watch;
63
   tcl_shell_t  *owner;
64
} shell_signal_t;
65

66
typedef char *(*get_line_fn_t)(tcl_shell_t *);
67

68
typedef struct _tcl_shell {
69
   char            *prompt;
70
   Tcl_Interp      *interp;
71
   shell_cmd_t     *cmds;
72
   size_t           ncmds;
73
   size_t           cmdalloc;
74
   rt_model_t      *model;
75
   tree_t           top;
76
   rt_scope_t      *root;
77
   shell_signal_t  *signals;
78
   unsigned         nsignals;
79
   hash_t          *namemap;
80
   jit_t           *jit;
81
   int64_t          now_var;
82
   unsigned         deltas_var;
83
   printer_t       *printer;
84
   get_line_fn_t    getline;
85
   jit_factory_t    make_jit;
86
   unit_registry_t *registry;
87
   shell_handler_t  handler;
88
   bool             quit;
89
} tcl_shell_t;
90

91
static __thread tcl_shell_t *rl_shell = NULL;
92

93
__attribute__((format(printf, 2, 3)))
94
static int tcl_error(tcl_shell_t *sh, const char *fmt, ...)
3✔
95
{
96
   va_list ap;
3✔
97
   va_start(ap, fmt);
3✔
98
   char *buf LOCAL = color_vasprintf(fmt, ap);
3✔
99
   va_end(ap);
3✔
100

101
   Tcl_SetObjResult(sh->interp, Tcl_NewStringObj(buf, -1));
3✔
102
   return TCL_ERROR;
3✔
103
}
104

105
static int syntax_error(tcl_shell_t *sh, Tcl_Obj *const objv[])
106
{
107
   return tcl_error(sh, "syntax error, enter $bold$help %s$$ for usage",
108
                    Tcl_GetString(objv[0]));
109
}
110

111
__attribute__((format(printf, 2, 3)))
112
static void shell_printf(tcl_shell_t *sh, const char *fmt, ...)
3✔
113
{
114
   va_list ap;
3✔
115
   va_start(ap, fmt);
3✔
116

117
   if (sh->handler.stdout_write != NULL) {
3✔
118
      char *buf LOCAL = color_vasprintf(fmt, ap);
6✔
119
      (*sh->handler.stdout_write)(buf, strlen(buf), sh->handler.context);
3✔
120
   }
121
   else
122
      wrapped_vprintf(fmt, ap);
×
123

124
   va_end(ap);
3✔
125
}
3✔
126

127
static bool shell_has_model(tcl_shell_t *sh)
64✔
128
{
129
   if (sh->model == NULL) {
64✔
130
      tcl_error(sh, "no simulation loaded, try the $bold$elaborate$$ "
×
131
                "command first");
132
      return false;
×
133
   }
134

135
   return true;
136
}
137

138
static void shell_clear_model(tcl_shell_t *sh)
19✔
139
{
140
   if (sh->model == NULL)
19✔
141
      return;
142

143
   model_free(sh->model);
1✔
144
   hash_free(sh->namemap);
1✔
145

146
   sh->model = NULL;
1✔
147
   sh->namemap = NULL;
1✔
148

149
   if (sh->handler.quit_sim != NULL)
1✔
150
      (*sh->handler.quit_sim)(sh->handler.context);
1✔
151
}
152

153
static void shell_next_time_step(rt_model_t *m, void *user)
2✔
154
{
155
   tcl_shell_t *sh = user;
2✔
156
   assert(sh->handler.next_time_step != NULL);
2✔
157

158
   uint64_t now = model_now(m, NULL);
2✔
159
   (*sh->handler.next_time_step)(now, sh->handler.context);
2✔
160

161
   model_set_global_cb(sh->model, RT_NEXT_TIME_STEP, shell_next_time_step, sh);
2✔
162
}
2✔
163

164
static void shell_create_model(tcl_shell_t *sh)
12✔
165
{
166
   assert(sh->model == NULL);
12✔
167

168
   sh->model = model_new(sh->top, sh->jit);
12✔
169

170
   if (sh->handler.next_time_step != NULL)
12✔
171
      model_set_global_cb(sh->model, RT_NEXT_TIME_STEP,
1✔
172
                          shell_next_time_step, sh);
173

174
   model_reset(sh->model);
12✔
175

176
   if ((sh->root = find_scope(sh->model, tree_stmt(sh->top, 0))) == NULL)
12✔
177
      fatal_trace("cannot find root scope");
×
178
}
12✔
179

180
static void shell_update_now(tcl_shell_t *sh)
27✔
181
{
182
   sh->now_var = model_now(sh->model, &sh->deltas_var);
27✔
183

184
   Tcl_UpdateLinkedVar(sh->interp, "now");
27✔
185
   Tcl_UpdateLinkedVar(sh->interp, "deltas");
27✔
186
}
27✔
187

188
static bool shell_get_printer(tcl_shell_t *sh, shell_signal_t *ss)
189
{
190
   if (ss->printer == NULL)
191
      ss->printer = printer_for(sh->printer, tree_type(ss->signal->where));
192

193
   if (ss->printer == NULL) {
194
      tcl_error(sh, "cannot display type %s",
195
                type_pp(tree_type(ss->signal->where)));
196
      return false;
197
   }
198

199
   return true;
200
}
201

202
static void shell_add_cmd(tcl_shell_t *sh, const char *name, Tcl_ObjCmdProc fn,
238✔
203
                          const char *help)
204
{
205
   shell_cmd_t cmd = { name, fn, help };
238✔
206

207
   if (sh->cmdalloc == sh->ncmds) {
238✔
208
      sh->cmdalloc = MAX(sh->cmdalloc * 2, 16);
28✔
209
      sh->cmds = xrealloc_array(sh->cmds, sh->cmdalloc, sizeof(shell_cmd_t));
28✔
210
   }
211

212
   sh->cmds[sh->ncmds++] = cmd;
238✔
213

214
   Tcl_CreateObjCommand(sh->interp, name, fn, sh, NULL);
238✔
215
}
238✔
216

217
static void shell_event_cb(uint64_t now, rt_signal_t *s, rt_watch_t *w,
4✔
218
                           void *user)
219
{
220
   shell_signal_t *ss = user;
4✔
221
   shell_handler_t *h = &(ss->owner->handler);
4✔
222

223
   if (h->signal_update != NULL) {
4✔
224
      const char *enc = print_signal(ss->printer, ss->signal, PRINT_F_ENCODE);
4✔
225
      (*h->signal_update)(ss->path, now, s, enc, h->context);
4✔
226
   }
227
}
4✔
228

229
static void recreate_signals(tcl_shell_t *sh, rt_scope_t *scope,
6✔
230
                             shell_signal_t **wptr)
231
{
232
   for (list_iter(rt_signal_t *, s, scope->signals)) {
12✔
233
      shell_signal_t *ss = (*wptr)++;
6✔
234
      assert(ss->name == ident_downcase(tree_ident(s->where)));
6✔
235
      ss->signal = s;
6✔
236

237
      if (ss->watch != NULL)
6✔
238
         ss->watch = model_set_event_cb(ss->owner->model, ss->signal,
×
239
                                        shell_event_cb, ss, true);
240
   }
241

242
   for (list_iter(rt_alias_t *, a, scope->aliases)) {
12✔
243
      shell_signal_t *ss = (*wptr)++;
6✔
244
      assert(ss->name == ident_downcase(tree_ident(a->where)));
6✔
245
      ss->signal = a->signal;
6✔
246

247
      if (ss->watch != NULL)
6✔
248
         ss->watch = model_set_event_cb(sh->model, ss->signal,
×
249
                                        shell_event_cb, ss, true);
250
   }
251

252
   for (list_iter(rt_scope_t *, child, scope->children))
9✔
253
      recreate_signals(sh, child, wptr);
3✔
254
}
6✔
255

256
static const char restart_help[] =
257
   "Restart the simulation";
258

259
static int shell_cmd_restart(ClientData cd, Tcl_Interp *interp,
3✔
260
                             int objc, Tcl_Obj *const objv[])
261
{
262
   tcl_shell_t *sh = cd;
3✔
263

264
   if (!shell_has_model(sh))
3✔
265
      return TCL_ERROR;
266

267
   model_free(sh->model);
3✔
268
   sh->model = NULL;
3✔
269

270
   jit_reset(sh->jit);
3✔
271

272
   shell_create_model(sh);
3✔
273

274
   shell_signal_t *wptr = sh->signals;
3✔
275
   recreate_signals(sh, sh->root, &wptr);
3✔
276
   assert(wptr == sh->signals + sh->nsignals);
3✔
277

278
   shell_update_now(sh);
3✔
279

280
   if (sh->handler.restart_sim != NULL)
3✔
281
      (*sh->handler.restart_sim)(sh->handler.context);
×
282

283
   return TCL_OK;
284
}
285

286
static const char run_help[] =
287
   "Start or resume the simulation";
288

289
static int shell_cmd_run(ClientData cd, Tcl_Interp *interp,
15✔
290
                         int objc, Tcl_Obj *const objv[])
291
{
292
   tcl_shell_t *sh = cd;
15✔
293
   static bool sim_running = false;
15✔
294

295
   if (!shell_has_model(sh))
15✔
296
      return TCL_ERROR;
297
   else if (sim_running)
15✔
298
      return tcl_error(sh, "simulation already running");
×
299

300
   uint64_t stop_time = UINT64_MAX;
15✔
301
   if (objc == 3) {
15✔
302
      Tcl_WideInt base;
4✔
303
      int error = Tcl_GetWideIntFromObj(interp, objv[1], &base);
4✔
304
      if (error != TCL_OK || base <= 0)
4✔
305
         return tcl_error(sh, "invalid time");
×
306

307
      const char *unit = Tcl_GetString(objv[2]);
4✔
308

309
      uint64_t mult;
4✔
310
      if      (strcmp(unit, "fs") == 0) mult = 1;
4✔
311
      else if (strcmp(unit, "ps") == 0) mult = 1000;
4✔
312
      else if (strcmp(unit, "ns") == 0) mult = 1000000;
4✔
313
      else if (strcmp(unit, "us") == 0) mult = 1000000000;
×
314
      else if (strcmp(unit, "ms") == 0) mult = 1000000000000;
×
315
      else
316
         return tcl_error(sh, "invalid time unit %s", unit);
×
317

318
      stop_time = model_now(sh->model, NULL) + (base * mult);
4✔
319
   }
320
   else if (objc != 1)
11✔
321
      return tcl_error(sh, "usage: $bold$run [time units]$$");
×
322

323

324
   sim_running = true;
15✔
325
   model_run(sh->model, stop_time);
15✔
326
   sim_running = false;
15✔
327

328
   shell_update_now(sh);
15✔
329

330
   return TCL_OK;
15✔
331
}
332

333
static const char find_help[] =
334
   "Find signals and other objects in the design\n"
335
   "\n"
336
   "Syntax:\n"
337
   "  find signals <name>\n"
338
   "\n"
339
   "Examples:\n"
340
   "  find signals /*\tList all signals in the design\n"
341
   "  find signals /uut/x*\tAll signals in instance UUT that start with X\n";
342

343
static int shell_cmd_find(ClientData cd, Tcl_Interp *interp,
6✔
344
                          int objc, Tcl_Obj *const objv[])
345
{
346
   tcl_shell_t *sh = cd;
6✔
347

348
   if (objc != 3 || strcmp(Tcl_GetString(objv[1]), "signals") != 0)
6✔
349
      goto usage;
×
350
   else if (!shell_has_model(sh))
6✔
351
      return TCL_ERROR;
352

353
   const char *glob = Tcl_GetString(objv[2]);
6✔
354
   Tcl_Obj *result = Tcl_NewListObj(0, NULL);
6✔
355

356
   for (int i = 0; i < sh->nsignals; i++) {
21✔
357
      if (!ident_glob(sh->signals[i].path, glob, -1))
15✔
358
         continue;
×
359

360
      Tcl_Obj *obj = Tcl_NewStringObj(istr(sh->signals[i].path), -1);
15✔
361
      Tcl_ListObjAppendElement(interp, result, obj);
15✔
362
   }
363

364
   Tcl_SetObjResult(interp, result);
6✔
365
   return TCL_OK;
6✔
366

367
 usage:
×
368
   return syntax_error(sh, objv);
×
369
}
370

371
static const char analyse_help[] =
372
   "Analyse a VHDL source file\n"
373
   "\n"
374
   "Syntax:\n"
375
   "  analyse [options] <file> [<file> ...]\n"
376
   "\n"
377
   "Note \"vcom\" is an alias of this command.\n"
378
   "\n"
379
   "Examples:\n"
380
   "  analyse file.vhd\n"
381
   "  vcom file1.vhd file2.vhd\n";
382

383
static int shell_cmd_analyse(ClientData cd, Tcl_Interp *interp,
10✔
384
                             int objc, Tcl_Obj *const objv[])
385
{
386
   tcl_shell_t *sh = cd;
10✔
387

388
   int pos = 1;
10✔
389
   for (const char *opt; (opt = Tcl_GetString(objv[pos]))[0] == '-'; pos++)
10✔
390
      goto usage;
×
391

392
   if (pos == objc)
10✔
393
      goto usage;
×
394

395
   reset_error_count();
10✔
396

397
   for (; pos < objc; pos++) {
30✔
398
      const char *fname = Tcl_GetString(objv[pos]);
10✔
399

400
      file_info_t info;
10✔
401
      if (access(fname, R_OK) != 0 || !get_file_info(fname, &info))
10✔
402
         return tcl_error(sh, "cannot open %s: %s", fname, strerror(errno));
×
403
      else if (info.type == FILE_DIR)
10✔
404
         return tcl_error(sh, "%s is a directory", fname);
×
405
      else if (info.type != FILE_REGULAR)
10✔
406
         return tcl_error(sh, "%s is not a regular file", fname);
×
407

408
      analyse_file(fname, sh->jit, sh->registry);
10✔
409
   }
410

411
   return error_count() > 0 ? TCL_ERROR : TCL_OK;
10✔
412

413
 usage:
×
414
   return syntax_error(sh, objv);
×
415
}
416

417
static const char elaborate_help[] =
418
   "Elaborate a design hierarchy\n"
419
   "\n"
420
   "Syntax:\n"
421
   "  elaborate [options] <toplevel>\n"
422
   "\n"
423
   "Note \"vsim\" is an alias of this command.\n"
424
   "\n"
425
   "Options:\n"
426
   "\n"
427
   "Examples:\n"
428
   "  elaborate toplevel\n"
429
   "  vsim toplevel\n";
430

431
static int shell_cmd_elaborate(ClientData cd, Tcl_Interp *interp,
9✔
432
                               int objc, Tcl_Obj *const objv[])
433
{
434
   tcl_shell_t *sh = cd;
9✔
435
   LOCAL_TEXT_BUF tb = tb_new();
18✔
436

437
   int pos = 1;
9✔
438
   for (const char *opt; (opt = Tcl_GetString(objv[pos]))[0] == '-'; pos++)
9✔
439
      goto usage;
×
440

441
   if (pos + 1 != objc)
9✔
442
      goto usage;
×
443

444
   lib_t work = lib_work();
9✔
445

446
   tb_istr(tb, lib_name(work));
9✔
447
   tb_append(tb, '.');
9✔
448
   tb_cat(tb, Tcl_GetString(objv[pos]));
9✔
449
   tb_upcase(tb);
9✔
450

451
   tree_t unit = lib_get(lib_work(), ident_new(tb_get(tb)));
9✔
452
   if (unit == NULL)
9✔
453
      return tcl_error(sh, "cannot find unit %s in library %s",
×
454
                       Tcl_GetString(objv[pos]), istr(lib_name(work)));
455

456
   shell_clear_model(sh);
9✔
457

458
   reset_error_count();
9✔
459

460
   // Recreate the JIT instance and unit registry as it may have
461
   // references to stale code
462
   jit_free(sh->jit);
9✔
463
   unit_registry_free(sh->registry);
9✔
464
   sh->registry = unit_registry_new();
9✔
465
   sh->jit = (*sh->make_jit)(sh->registry);
9✔
466

467
   jit_enable_runtime(sh->jit, false);
9✔
468

469
   tree_t top = elab(unit, sh->jit, sh->registry, NULL);
9✔
470
   if (top == NULL)
9✔
471
      return TCL_ERROR;
472

473
   shell_reset(sh, top);
9✔
474
   return TCL_OK;
9✔
475

476
 usage:
×
477
   return syntax_error(sh, objv);
×
478
}
479

480
static const char examine_help[] =
481
   "Display current value of one of more signals\n"
482
   "\n"
483
   "Syntax:\n"
484
   "  examine [options] <name>...\n"
485
   "\n"
486
   "Note \"exa\" is an alias of this command.\n"
487
   "\n"
488
   "Options:\n"
489
   "  -radix <type>\tFormat as hexadecimal, decimal, or binary.\n"
490
   "  -<radix>\tAlias of \"-radix <radix>\".\n"
491
   "\n"
492
   "Examples:\n"
493
   "  examine /uut/foo\n"
494
   "  exa -hex sig\n";
495

496
static bool parse_radix(const char *str, print_flags_t *flags)
5✔
497
{
498
   if (strcmp(str, "binary") == 0 || strcmp(str, "bin") == 0
5✔
499
       || strcmp(str, "b") == 0) {
4✔
500
      *flags &= ~PRINT_F_RADIX;
1✔
501
      *flags |= PRINT_F_BIN;
1✔
502
      return true;
1✔
503
   }
504
   else if (strcmp(str, "-hexadecimal") == 0 || strcmp(str, "hex") == 0
4✔
505
            || strcmp(str, "h") == 0) {
1✔
506
      *flags &= ~PRINT_F_RADIX;
3✔
507
      *flags |= PRINT_F_HEX;
3✔
508
      return true;
3✔
509
   }
510
   else
511
      return false;
512
}
513

514
const char *next_option(int *pos, int objc, Tcl_Obj *const objv[])
35✔
515
{
516
   if (*pos >= objc)
35✔
517
      return NULL;
518

519
   const char *opt = Tcl_GetString(objv[*pos]);
33✔
520
   if (opt[0] != '-')
33✔
521
      return NULL;
522

523
   (*pos)++;
6✔
524
   return opt;
6✔
525
}
526

527
static int shell_cmd_examine(ClientData cd, Tcl_Interp *interp,
27✔
528
                             int objc, Tcl_Obj *const objv[])
529
{
530
   tcl_shell_t *sh = cd;
27✔
531

532
   if (!shell_has_model(sh))
27✔
533
      return TCL_ERROR;
534

535
   print_flags_t flags = 0;
27✔
536
   int pos = 1;
27✔
537
   for (const char *opt; (opt = next_option(&pos, objc, objv)); ) {
31✔
538
      if (parse_radix(opt + 1, &flags))
4✔
539
         continue;
3✔
540
      else if (strcmp(opt, "-radix") == 0 && pos + 1 < objc) {
1✔
541
         const char *arg = Tcl_GetString(objv[pos++]);
1✔
542
         if (!parse_radix(arg, &flags))
1✔
543
            goto usage;
×
544
      }
545
      else
546
         goto usage;
×
547
   }
548

549
   if (pos == objc)
27✔
550
      goto usage;
×
551

552
   const int count = objc - pos;
27✔
553
   Tcl_Obj *single[1], **result = single;
27✔
554

555
   if (count > 1)
27✔
556
      result = xmalloc_array(count, sizeof(Tcl_Obj *));
1✔
557

558
   for (int i = 0; pos < objc; pos++, i++) {
55✔
559
      const char *name = Tcl_GetString(objv[pos]);
28✔
560
      shell_signal_t *ss = hash_get(sh->namemap, ident_new(name));
28✔
561
      if (ss == NULL)
28✔
562
         return tcl_error(sh, "cannot find name '%s'", name);
×
563

564
      if (!shell_get_printer(sh, ss))
28✔
565
         return TCL_ERROR;
566

567
      const char *str = print_signal(ss->printer, ss->signal, flags);
28✔
568
      result[i] = Tcl_NewStringObj(str, -1);
28✔
569
   }
570

571
   if (count > 1) {
27✔
572
      Tcl_Obj *list = Tcl_NewListObj(count, result);
1✔
573
      Tcl_SetObjResult(interp, list);
1✔
574
      free(result);
1✔
575
   }
576
   else
577
      Tcl_SetObjResult(interp, result[0]);
26✔
578

579
   return TCL_OK;
580

581
 usage:
×
582
   return syntax_error(sh, objv);
×
583
}
584

585
static const char force_help[] =
586
   "Force the value of a signal\n"
587
   "\n"
588
   "Syntax:\n"
589
   "  force [<signal> <value>]\n"
590
   "\n"
591
   "Value can be either an enumeration literal ('1', true), an integer "
592
   "(42, 0), or a bit string literal (\"10111\") and must be appropriate "
593
   "for the signal type. Without arguments lists all currently forced "
594
   "signals.\n"
595
   "\n"
596
   "Examples:\n"
597
   "  force /uut/foo '1'\n"
598
   "  force /bitvec \"10011\"\n";
599

600
static int shell_cmd_force(ClientData cd, Tcl_Interp *interp,
6✔
601
                           int objc, Tcl_Obj *const objv[])
602
{
603
   tcl_shell_t *sh = cd;
6✔
604

605
   if (!shell_has_model(sh))
6✔
606
      return TCL_ERROR;
607
   else if (objc != 3 && objc != 1)
6✔
608
      return syntax_error(sh, objv);
×
609

610
   if (objc == 1) {
6✔
611
      for (int i = 0; i < sh->nsignals; i++) {
4✔
612
         shell_signal_t *ss = &(sh->signals[i]);
3✔
613
         if (!(ss->signal->nexus.flags & NET_F_FORCED))
3✔
614
            continue;
×
615

616
         if (!shell_get_printer(sh, ss))
3✔
617
            return TCL_ERROR;
×
618

619
         const size_t nbytes = ss->signal->shared.size;
3✔
620
         uint8_t *value LOCAL = xmalloc(nbytes);
6✔
621
         get_forcing_value(ss->signal, value);
3✔
622

623
         shell_printf(sh, "force %s %s\n", istr(ss->path),
3✔
624
                      print_raw(ss->printer, value, nbytes, 0));
625
      }
626

627
      return TCL_OK;
628
   }
629

630
   const char *signame = Tcl_GetString(objv[1]);
5✔
631
   const char *valstr = Tcl_GetString(objv[2]);
5✔
632

633
   shell_signal_t *ss = hash_get(sh->namemap, ident_new(signame));
5✔
634
   if (ss == NULL)
5✔
635
      return tcl_error(sh, "cannot find signal '%s'", signame);
×
636

637
   type_t type = tree_type(ss->signal->where);
5✔
638

639
   parsed_value_t value;
5✔
640
   if (!parse_value(type, valstr, &value))
5✔
641
      return tcl_error(sh, "value '%s' is not valid for type %s",
1✔
642
                       valstr, type_pp(type));
643

644
   if (type_is_scalar(type))
4✔
645
      force_signal(sh->model, ss->signal, &value.integer, 0, 1);
2✔
646
   else if (type_is_character_array(type)) {
2✔
647
      const int width = signal_width(ss->signal);
2✔
648
      if (value.enums->count != width) {
2✔
649
         tcl_error(sh, "expected %d elements for signal %s but have %d", width,
1✔
650
                   signame, value.enums->count);
651
         free(value.enums);
1✔
652
         return TCL_ERROR;
1✔
653
      }
654

655
      force_signal(sh->model, ss->signal, value.enums->values, 0, width);
1✔
656
      free(value.enums);
1✔
657
   }
658
   else
659
      return tcl_error(sh, "cannot force signals of type %s", type_pp(type));
×
660

661
   return TCL_OK;
662
}
663

664
static const char noforce_help[] =
665
   "Stop forcing the value of signals\n"
666
   "\n"
667
   "Syntax:\n"
668
   "  noforce <signal>...\n"
669
   "  noforce *\n"
670
   "\n"
671
   "The second form stops forcing all currently forced signals.\n"
672
   "\n"
673
   "Examples:\n"
674
   "  noforce /uut/foo /baz\n";
675

676
static int shell_cmd_noforce(ClientData cd, Tcl_Interp *interp,
3✔
677
                             int objc, Tcl_Obj *const objv[])
678
{
679
   tcl_shell_t *sh = cd;
3✔
680

681
   if (!shell_has_model(sh))
3✔
682
      return TCL_ERROR;
683
   else if (objc == 1)
3✔
684
      return syntax_error(sh, objv);
×
685

686
   for (int i = 1; i < objc; i++) {
5✔
687
      const char *signame = Tcl_GetString(objv[i]);
3✔
688
      if (strcmp(signame, "*") == 0) {
3✔
689
         for (int i = 0; i < sh->nsignals; i++) {
4✔
690
            shell_signal_t *ss = &(sh->signals[i]);
3✔
691
            if (ss->signal->nexus.flags & NET_F_FORCED)
3✔
692
               release_signal(sh->model, ss->signal, 0,
2✔
693
                              signal_width(ss->signal));
2✔
694
         }
695
      }
696
      else {
697
         shell_signal_t *ss = hash_get(sh->namemap, ident_new(signame));
2✔
698
         if (ss == NULL)
2✔
699
            return tcl_error(sh, "cannot find signal '%s'", signame);
×
700

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

704
         release_signal(sh->model, ss->signal, 0, signal_width(ss->signal));
1✔
705
      }
706
   }
707

708
   return TCL_OK;
709
}
710

711
static const char add_help[] =
712
   "Add signals and other objects to the display\n"
713
   "\n"
714
   "Syntax:\n"
715
   "  add wave <name>...\n"
716
   "\n"
717
   "Examples:\n"
718
   "  add wave /*\tAdd all signals to waveform\n";
719

720
static int shell_cmd_add(ClientData cd, Tcl_Interp *interp,
3✔
721
                         int objc, Tcl_Obj *const objv[])
722
{
723
   tcl_shell_t *sh = cd;
3✔
724
   char **globs LOCAL = NULL;
6✔
725

726
   if (objc < 3 || strcmp(Tcl_GetString(objv[1]), "wave") != 0)
3✔
727
      goto usage;
×
728
   else if (!shell_has_model(sh))
3✔
729
      return TCL_ERROR;
730

731
   const int nglobs = objc - 2;
3✔
732
   globs = xmalloc_array(nglobs, sizeof(char *));
3✔
733
   for (int i = 0; i < nglobs; i++)
7✔
734
      globs[i] = Tcl_GetString(objv[i + 2]);
4✔
735

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

739
      bool match = false;
9✔
740
      for (int j = 0; j < nglobs; j++)
21✔
741
         match |= ident_glob(ss->path, globs[j], -1);
12✔
742

743
      if (!match)
9✔
744
         continue;
5✔
745
      else if (!shell_get_printer(sh, ss))
4✔
746
         return TCL_ERROR;
747

748
      if (sh->handler.add_wave != NULL) {
4✔
749
         const char *enc =
4✔
750
            print_signal(ss->printer, ss->signal, PRINT_F_ENCODE);
4✔
751
         (*sh->handler.add_wave)(ss->path, enc, sh->handler.context);
4✔
752
      }
753

754
      if (ss->watch == NULL)
4✔
755
        ss->watch = model_set_event_cb(sh->model, ss->signal,
3✔
756
                                       shell_event_cb, ss, true);
757
   }
758

759
   return TCL_OK;
760

761
 usage:
×
762
   return syntax_error(sh, objv);
×
763
}
764

765
static const char quit_help[] =
766
   "Exit the simulator or unload the current design\n"
767
   "\n"
768
   "Syntax:\n"
769
   "  quit [-sim]\n"
770
   "\n"
771
   "Options:\n"
772
   "  -sim\t\tUnload the current simulation but do not exit the program.\n";
773

774
static int shell_cmd_quit(ClientData cd, Tcl_Interp *interp,
1✔
775
                          int objc, Tcl_Obj *const objv[])
776
{
777
   tcl_shell_t *sh = cd;
1✔
778

779
   bool quit_sim = false;
1✔
780
   int pos = 1;
1✔
781
   for (const char *opt; (opt = next_option(&pos, objc, objv)); ) {
2✔
782
      if (strcmp(opt, "-sim") == 0)
1✔
783
         quit_sim = true;
784
      else
785
         goto usage;
×
786
   }
787

788
   if (pos != objc)
1✔
789
      goto usage;
×
790

791
   if (quit_sim) {
1✔
792
      if (!shell_has_model(sh))
1✔
793
         return TCL_ERROR;
794
      else
795
         shell_clear_model(sh);
1✔
796
   }
797
   else {
798
      sh->quit = true;
×
799

800
      if (sh->handler.exit != NULL)
×
801
         (*sh->handler.exit)(0, sh->handler.context);
×
802
   }
803

804
   return TCL_OK;
805

806
 usage:
×
807
   return syntax_error(sh, objv);
×
808
}
809

810
static const char exit_help[] =
811
   "Exit the simulator and return a status code\n"
812
   "\n"
813
   "Syntax:\n"
814
   "  exit [-code <integer>]\n"
815
   "\n"
816
   "Options:\n"
817
   "  -code <integer>\tStatus code to return to shell.\n";
818

819
static int shell_cmd_exit(ClientData cd, Tcl_Interp *interp,
1✔
820
                          int objc, Tcl_Obj *const objv[])
821
{
822
   tcl_shell_t *sh = cd;
1✔
823

824
   int pos = 1, status = EXIT_SUCCESS;
1✔
825
   for (const char *opt; (opt = next_option(&pos, objc, objv)); ) {
2✔
826
      if (strcmp(opt, "-code") == 0 && pos < objc)
1✔
827
         status = atoi(Tcl_GetString(objv[pos++]));
1✔
828
      else
829
         goto usage;
×
830
   }
831

832
   if (pos != objc)
1✔
833
      goto usage;
×
834

835
   if (sh->handler.exit != NULL)
1✔
836
      (*sh->handler.exit)(status, sh->handler.context);
1✔
837

838
   Tcl_Exit(status);
1✔
839

840
 usage:
×
841
   return syntax_error(sh, objv);
×
842
}
843

844
static const char help_help[] =
845
   "Display list of commands or detailed help\n"
846
   "\n"
847
   "Use $bold$help <command>$$ to display detailed usage of a particular\n"
848
   "command.\n";
849

850
static int shell_cmd_help(ClientData cd, Tcl_Interp *interp,
×
851
                          int objc, Tcl_Obj *const objv[])
852
{
853
   tcl_shell_t *sh = cd;
×
854

855
   if (objc == 2) {
×
856
      const char *which = Tcl_GetString(objv[1]);
×
857
      for (int i = 0; i < sh->ncmds; i++) {
×
858
         if (strcmp(sh->cmds[i].name, which) == 0) {
×
859
            shell_printf(sh, "%s", sh->cmds[i].help);
×
860
            return TCL_OK;
×
861
         }
862
      }
863

864
      return tcl_error(sh, "invalid command '%s'", which);
×
865
   }
866
   else if (objc != 1)
×
867
      return tcl_error(sh, "syntax error, try $bold$help$$");
×
868

869
   shell_printf(sh, "List of supported commands:\n");
×
870

871
   for (shell_cmd_t *c = sh->cmds; c < sh->cmds + sh->ncmds; c++) {
×
872
      const int linelen = strchrnul(c->help, '\n') - c->help;
×
873
      shell_printf(sh, "  $bold$%-16s$$%.*s\n", c->name, linelen, c->help);
×
874
   }
875

876
   shell_printf(sh, "\n");
×
877
   shell_printf(sh, "Use $bold$help <command>$$ for detailed usage "
×
878
                "of a particular command. Standard TCL commands are "
879
                "also accepted.\n");
880

881
   return TCL_OK;
×
882
}
883

884
static const char copyright_help[] = "Display copyright information";
885

886
static int shell_cmd_copyright(ClientData cd, Tcl_Interp *interp,
×
887
                               int objc, Tcl_Obj *const objv[])
888
{
889
   Tcl_Channel channel = Tcl_GetStdChannel(TCL_STDOUT);
×
890

891
   extern char copy_string[];
×
892
   Tcl_WriteChars(channel, copy_string, -1);
×
893
   Tcl_WriteChars(channel, "\n", 1);
×
894
   Tcl_Flush(channel);
×
895

896
   return TCL_OK;
×
897
}
898

899
static const char echo_help[] = "Display value of arguments";
900

901
static int shell_cmd_echo(ClientData cd, Tcl_Interp *interp,
1✔
902
                          int objc, Tcl_Obj *const objv[])
903
{
904
   Tcl_Channel channel = Tcl_GetStdChannel(TCL_STDOUT);
1✔
905

906
   for (int i = 1; i < objc; i++) {
3✔
907
      if (i > 1) Tcl_WriteChars(channel, " ", 1);
2✔
908
      Tcl_WriteObj(channel, objv[i]);
2✔
909
   }
910

911
   Tcl_WriteChars(channel, "\n", 1);
1✔
912
   Tcl_Flush(channel);
1✔
913

914
   return TCL_OK;
1✔
915
}
916

917
static char *shell_list_generator(const char *script, const char *text,
×
918
                                  int state, int prefix)
919
{
920
   static Tcl_Obj *list = NULL;
×
921
   static int index, len, max;
×
922

923
   if (!state) {
×
924
      if (Tcl_Eval(rl_shell->interp, script) != TCL_OK)
×
925
         return NULL;
926

927
      list = Tcl_GetObjResult(rl_shell->interp);
×
928

929
      if (Tcl_ListObjLength(rl_shell->interp, list, &max) != TCL_OK)
×
930
         return NULL;
931

932
      index = 0;
×
933
      len = strlen(text);
×
934
   }
935

936
   while (index < max) {
×
937
      Tcl_Obj *obj;
×
938
      if (Tcl_ListObjIndex(rl_shell->interp, list, index++, &obj) != TCL_OK)
×
939
         return NULL;
×
940

941
      const char *str = Tcl_GetString(obj);
×
942
      if (strncmp(str, text + prefix, len - prefix) == 0) {
×
943
         if (prefix == 0)
×
944
            return xstrdup(str);
×
945
         else {
946
            assert(len >= prefix);
×
947
            const size_t complen = strlen(str);
×
948
            char *buf = xmalloc(prefix + complen + 1);
×
949
            memcpy(buf, text, prefix);
×
950
            memcpy(buf + prefix, str, complen + 1);
×
951
            return buf;
×
952
         }
953
      }
954
   }
955

956
   return NULL;
957
}
958

959
static char *shell_command_generator(const char *text, int state)
×
960
{
961
   return shell_list_generator("info commands", text, state, 0);
×
962
}
963

964
static char *shell_variable_generator(const char *text, int state)
×
965
{
966
   return shell_list_generator("info vars", text, state, 1);
×
967
}
968

969
static char **shell_tab_completion(const char *text, int start, int end)
×
970
{
971
   rl_attempted_completion_over = 0;
×
972

973
   if (text[0] == '$')
×
974
      return rl_completion_matches(text, shell_variable_generator);
×
975

976
   // Determine if we are completing a TCL command or not
977
   int pos = start - 1;
×
978
   for (; pos >= 0 && isspace_iso88591(rl_line_buffer[pos]); pos--);
×
979

980
   if (pos == -1 || rl_line_buffer[pos] == '[')
×
981
      return rl_completion_matches(text, shell_command_generator);
×
982

983
   return NULL;
984
}
985

986
static char *shell_completing_get_line(tcl_shell_t *sh)
×
987
{
988
   rl_attempted_completion_function = shell_tab_completion;
×
989
   rl_completer_quote_characters = "\"'";
×
990
   rl_completer_word_break_characters = " \t\r\n[]{}";
×
991
   rl_special_prefixes = "$";
×
992
   rl_shell = sh;
×
993

994
   char *buf = readline(sh->prompt);
×
995
   if ((buf != NULL) && (*buf != '\0'))
×
996
      add_history(buf);
×
997

998
   rl_shell = NULL;
×
999
   return buf;
×
1000
}
1001

1002

1003
static char *shell_raw_get_line(tcl_shell_t *sh)
×
1004
{
1005
   fputs(sh->prompt, stdout);
×
1006
   fflush(stdout);
×
1007

1008
   LOCAL_TEXT_BUF tb = tb_new();
×
1009

1010
   size_t off = 0;
×
1011
   for (;;) {
×
1012
      int ch = fgetc(stdin);
×
1013
      fputc(ch, stdout);
×
1014
      switch (ch) {
×
1015
      case EOF:
1016
         return (off > 0) ? tb_claim(tb) : NULL;
1017
      case '\n':
×
1018
         return tb_claim(tb);
×
1019
      default:
×
1020
         tb_append(tb, ch);
×
1021
      }
1022
   }
1023
}
1024

1025
void shell_print_banner(tcl_shell_t *sh)
×
1026
{
1027
   extern const char version_string[];
×
1028
   shell_printf(sh, "\n");
×
1029

1030
   if (sh->handler.stdout_write == NULL)
×
1031
      print_centred(version_string);
×
1032
   else
1033
      shell_printf(sh, "\t%s", version_string);
×
1034

1035
   static const char blurb[] =
×
1036
      "\n\nThis program comes with ABSOLUTELY NO WARRANTY. This is free "
1037
      "software, and you are welcome to redistribute it under certain "
1038
      "conditions; type $bold$copyright$$ for details.\n\n"
1039
      "Type $bold$help$$ for a list of supported commands.\n\n";
1040

1041
   shell_printf(sh, blurb);
×
1042
}
×
1043

1044
static int compare_shell_cmd(const void *a, const void *b)
728✔
1045
{
1046
   return strcmp(((shell_cmd_t *)a)->name, ((shell_cmd_t *)b)->name);
728✔
1047
}
1048

1049
tcl_shell_t *shell_new(jit_factory_t make_jit)
14✔
1050
{
1051
   tcl_shell_t *sh = xcalloc(sizeof(tcl_shell_t));
14✔
1052
#ifdef RL_VERSION_MAJOR
1053
   sh->prompt   = color_asprintf("\001$+cyan$\002%%\001$$\002 ");
14✔
1054
#else
1055
   sh->prompt   = color_asprintf("$+cyan$%%$$ ");
1056
#endif
1057
   sh->interp   = Tcl_CreateInterp();
14✔
1058
   sh->make_jit = make_jit;
14✔
1059
   sh->registry = unit_registry_new();
14✔
1060
   sh->jit      = make_jit ? (*make_jit)(sh->registry) : NULL;
14✔
1061
   sh->printer  = printer_new();
14✔
1062

1063
   if (isatty(fileno(stdin)))
14✔
1064
      sh->getline = shell_completing_get_line;
×
1065
   else
1066
      sh->getline = shell_raw_get_line;
14✔
1067

1068
   Tcl_LinkVar(sh->interp, "now", (char *)&sh->now_var,
14✔
1069
               TCL_LINK_WIDE_INT | TCL_LINK_READ_ONLY);
1070
   Tcl_LinkVar(sh->interp, "deltas", (char *)&sh->deltas_var,
14✔
1071
               TCL_LINK_UINT | TCL_LINK_READ_ONLY);
1072

1073
   atexit(Tcl_Finalize);
14✔
1074

1075
   Tcl_DeleteCommand(sh->interp, "exit");
14✔
1076

1077
   shell_add_cmd(sh, "help", shell_cmd_help, help_help);
14✔
1078
   shell_add_cmd(sh, "exit", shell_cmd_exit, exit_help);
14✔
1079
   shell_add_cmd(sh, "copyright", shell_cmd_copyright, copyright_help);
14✔
1080
   shell_add_cmd(sh, "find", shell_cmd_find, find_help);
14✔
1081
   shell_add_cmd(sh, "run", shell_cmd_run, run_help);
14✔
1082
   shell_add_cmd(sh, "restart", shell_cmd_restart, restart_help);
14✔
1083
   shell_add_cmd(sh, "analyse", shell_cmd_analyse, analyse_help);
14✔
1084
   shell_add_cmd(sh, "vcom", shell_cmd_analyse, analyse_help);
14✔
1085
   shell_add_cmd(sh, "elaborate", shell_cmd_elaborate, elaborate_help);
14✔
1086
   shell_add_cmd(sh, "vsim", shell_cmd_elaborate, elaborate_help);
14✔
1087
   shell_add_cmd(sh, "examine", shell_cmd_examine, examine_help);
14✔
1088
   shell_add_cmd(sh, "exa", shell_cmd_examine, examine_help);
14✔
1089
   shell_add_cmd(sh, "add", shell_cmd_add, add_help);
14✔
1090
   shell_add_cmd(sh, "quit", shell_cmd_quit, quit_help);
14✔
1091
   shell_add_cmd(sh, "force", shell_cmd_force, force_help);
14✔
1092
   shell_add_cmd(sh, "noforce", shell_cmd_noforce, noforce_help);
14✔
1093
   shell_add_cmd(sh, "echo", shell_cmd_echo, echo_help);
14✔
1094

1095
   qsort(sh->cmds, sh->ncmds, sizeof(shell_cmd_t), compare_shell_cmd);
14✔
1096

1097
   return sh;
14✔
1098
}
1099

1100
void shell_free(tcl_shell_t *sh)
13✔
1101
{
1102
   if (sh->model != NULL) {
13✔
1103
      model_free(sh->model);
8✔
1104
      hash_free(sh->namemap);
8✔
1105
      free(sh->signals);
8✔
1106
   }
1107

1108
   if (sh->jit != NULL)
13✔
1109
      jit_free(sh->jit);
10✔
1110

1111
   unit_registry_free(sh->registry);
13✔
1112
   printer_free(sh->printer);
13✔
1113
   Tcl_DeleteInterp(sh->interp);
13✔
1114

1115
   free(sh->prompt);
13✔
1116
   free(sh->cmds);
13✔
1117
   free(sh);
13✔
1118
}
13✔
1119

1120
bool shell_eval(tcl_shell_t *sh, const char *script, const char **result)
43✔
1121
{
1122
   const int code = Tcl_Eval(sh->interp, script);
43✔
1123
   const char *str = Tcl_GetStringResult(sh->interp);
42✔
1124

1125
   switch (code) {
42✔
1126
   case TCL_OK:
38✔
1127
      if (result != NULL)
38✔
1128
         *result = str;
38✔
1129
      return true;
1130
   case TCL_ERROR:
4✔
1131
      if (str != NULL && *str != '\0')
4✔
1132
         errorf("%s", str);
3✔
1133
      return false;
1134
   default:
×
1135
      warnf("Tcl_Eval returned unknown code %d", code);
×
1136
      return false;
×
1137
   }
1138
}
1139

1140
static int count_signals(rt_scope_t *scope)
13✔
1141
{
1142
   int total = list_size(scope->signals) + list_size(scope->aliases);
13✔
1143

1144
   list_foreach(rt_scope_t *, child, scope->children)
17✔
1145
      total += count_signals(child);
4✔
1146

1147
   return total;
13✔
1148
}
1149

1150
static void recurse_signals(tcl_shell_t *sh, rt_scope_t *scope,
13✔
1151
                            text_buf_t *path, shell_signal_t **wptr)
1152
{
1153
   const int base = tb_len(path);
13✔
1154

1155
   list_foreach(rt_signal_t *, s, scope->signals) {
35✔
1156
      shell_signal_t *ss = (*wptr)++;
22✔
1157
      ss->signal = s;
22✔
1158
      ss->name = ident_downcase(tree_ident(s->where));
22✔
1159
      ss->owner = sh;
22✔
1160

1161
      tb_istr(path, ss->name);
22✔
1162
      ss->path = ident_new(tb_get(path));
22✔
1163
      tb_trim(path, base);
22✔
1164

1165
      hash_put(sh->namemap, ss->path, ss);
22✔
1166
   }
1167

1168
   list_foreach(rt_alias_t *, a, scope->aliases) {
20✔
1169
      shell_signal_t *ss = (*wptr)++;
7✔
1170
      ss->signal = a->signal;
7✔
1171
      ss->name = ident_downcase(tree_ident(a->where));
7✔
1172
      ss->owner = sh;
7✔
1173

1174
      tb_istr(path, ss->name);
7✔
1175
      ss->path = ident_new(tb_get(path));
7✔
1176
      tb_trim(path, base);
7✔
1177

1178
      hash_put(sh->namemap, ss->path, ss);
7✔
1179
   }
1180

1181
   list_foreach(rt_scope_t *, child, scope->children) {
17✔
1182
      ident_t name = ident_downcase(tree_ident(child->where));
4✔
1183

1184
      tb_istr(path, name);
4✔
1185
      tb_append(path, '/');
4✔
1186
      recurse_signals(sh, child, path, wptr);
4✔
1187
      tb_trim(path, base);
4✔
1188
   }
1189
}
13✔
1190

1191
void shell_reset(tcl_shell_t *sh, tree_t top)
9✔
1192
{
1193
   shell_clear_model(sh);
9✔
1194

1195
   jit_reset(sh->jit);
9✔
1196
   jit_enable_runtime(sh->jit, true);
9✔
1197

1198
   sh->top = top;
9✔
1199

1200
   vcode_unit_t vu = lib_get_vcode(lib_work(), top);
9✔
1201
   if (vu != NULL)
9✔
1202
      unit_registry_put_all(sh->registry, vu);
×
1203

1204
   shell_create_model(sh);
9✔
1205

1206
   sh->nsignals = count_signals(sh->root);
9✔
1207
   sh->signals = xcalloc_array(sh->nsignals, sizeof(shell_signal_t));
9✔
1208
   sh->namemap = hash_new(sh->nsignals * 2);
9✔
1209

1210
   text_buf_t *path = tb_new();
9✔
1211
   shell_signal_t *wptr = sh->signals;
9✔
1212
   tb_cat(path, "/");
9✔
1213
   recurse_signals(sh, sh->root, path, &wptr);
9✔
1214
   assert(wptr == sh->signals + sh->nsignals);
9✔
1215
   tb_free(path);
9✔
1216

1217
   shell_update_now(sh);
9✔
1218

1219
   if (sh->handler.start_sim != NULL)
9✔
1220
      (*sh->handler.start_sim)(tree_ident(top), sh->handler.context);
1✔
1221
}
9✔
1222

1223
void shell_interact(tcl_shell_t *sh)
×
1224
{
1225
   shell_print_banner(sh);
×
1226

1227
   char *line;
×
1228
   while (!sh->quit && (line = (*sh->getline)(sh))) {
×
1229
      const char *result = NULL;
×
1230
      if (shell_eval(sh, line, &result) && *result != '\0')
×
1231
         color_printf("$+black$%s$$\n", result);
×
1232

1233
      free(line);
×
1234
   }
1235
}
×
1236

1237
bool shell_do(tcl_shell_t *sh, const char *file)
6✔
1238
{
1239
   const int code = Tcl_EvalFile(sh->interp, file);
6✔
1240

1241
   switch (code) {
6✔
1242
   case TCL_OK:
1243
      return true;
1244
   case TCL_ERROR:
×
1245
      {
1246
         const char *str = Tcl_GetStringResult(sh->interp);
×
1247
         if (str != NULL && *str != '\0')
×
1248
            errorf("%s", str);
×
1249
         return false;
1250
      }
1251
   default:
×
1252
      warnf("Tcl_Eval returned unknown code %d", code);
×
1253
      return false;
×
1254
   }
1255
}
1256

1257
static int shell_redirect_close(ClientData cd, Tcl_Interp *interp)
4✔
1258
{
1259
   return EINVAL;
4✔
1260
}
1261

1262
static void shell_redirect_watch(ClientData cd, int mask)
4✔
1263
{
1264
}
4✔
1265

1266
static int shell_redirect_output(ClientData cd, const char *buf, int nchars,
4✔
1267
                                 int *error)
1268
{
1269
   tcl_shell_t *sh = untag_pointer(cd, tcl_shell_t);
4✔
1270
   if (pointer_tag(cd) == 0)
4✔
1271
      (*sh->handler.stdout_write)(buf, nchars, sh->handler.context);
2✔
1272
   else
1273
      (*sh->handler.stderr_write)(buf, nchars, sh->handler.context);
2✔
1274

1275
   return nchars;
4✔
1276
}
1277

1278
static const Tcl_ChannelType redirect_funcs = {
1279
   .typeName = "redirect",
1280
   .version = TCL_CHANNEL_VERSION_4,
1281
   .closeProc = shell_redirect_close,
1282
   .watchProc = shell_redirect_watch,
1283
   .outputProc = shell_redirect_output,
1284
};
1285

1286
void shell_set_handler(tcl_shell_t *sh, const shell_handler_t *h)
5✔
1287
{
1288
   sh->handler = *h;
5✔
1289

1290
   if (h->stdout_write != NULL) {
5✔
1291
      Tcl_Channel chan = Tcl_CreateChannel(&redirect_funcs, "redirect0",
9✔
1292
                                           tag_pointer(sh, 0), TCL_WRITABLE);
3✔
1293
      Tcl_SetChannelOption(NULL, chan, "-translation", "lf");
3✔
1294
      Tcl_SetChannelOption(NULL, chan, "-buffering", "line");
3✔
1295
      Tcl_SetChannelOption(NULL, chan, "-encoding", "utf-8");
3✔
1296

1297
      Tcl_RegisterChannel(sh->interp, chan);
3✔
1298
      Tcl_SetStdChannel(chan, TCL_STDOUT);
3✔
1299
   }
1300

1301
   if (h->stderr_write != NULL) {
5✔
1302
      Tcl_Channel chan = Tcl_CreateChannel(&redirect_funcs, "redirect1",
3✔
1303
                                           tag_pointer(sh, 1), TCL_WRITABLE);
1✔
1304
      Tcl_SetChannelOption(NULL, chan, "-translation", "lf");
1✔
1305
      Tcl_SetChannelOption(NULL, chan, "-buffering", "none");
1✔
1306
      Tcl_SetChannelOption(NULL, chan, "-encoding", "utf-8");
1✔
1307

1308
      Tcl_RegisterChannel(sh->interp, chan);
1✔
1309
      Tcl_SetStdChannel(chan, TCL_STDERR);
1✔
1310
   }
1311
}
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