Graphviz 16.1.0~dev.20260902.0144
Loading...
Searching...
No Matches
gvpr.c
Go to the documentation of this file.
1/*************************************************************************
2 * Copyright (c) 2011 AT&T Intellectual Property
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v2.0
5 * which accompanies this distribution, and is available at
6 * https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.html
7 *
8 * Contributors: Details at https://graphviz.org
9 *************************************************************************/
10
11/*
12 * gpr: graph pattern recognizer
13 *
14 * Written by Emden Gansner
15 */
16
17#include "config.h"
18
19#include "builddate.h"
20#include <assert.h>
21#include <ast/error.h>
22#include <cgraph/cgraph.h>
23#include <cgraph/ingraphs.h>
24#include <common/globals.h>
25#include <getopt.h>
26#include <gvpr/actions.h>
27#include <gvpr/compile.h>
28#include <gvpr/gprstate.h>
29#include <gvpr/gvpr.h>
30#include <setjmp.h>
31#include <stdbool.h>
32#include <stddef.h>
33#include <stdio.h>
34#include <string.h>
35#include <time.h>
36#include <unistd.h>
37#include <util/agxbuf.h>
38#include <util/alloc.h>
39#include <util/exit.h>
40#include <util/gv_ctype.h>
41#include <util/gv_find_me.h>
42#include <util/gv_fopen.h>
43#include <util/list.h>
44#include <util/path.h>
45#include <util/strview.h>
46#include <util/unreachable.h>
47
48static char *Info[] = {
49 "gvpr", /* Program */
50 PACKAGE_VERSION, /* Version */
51 BUILDDATE /* Build Date */
52};
53
54static const char *usage =
55 " [-o <ofile>] [-a <args>] ([-f <prog>] | 'prog') [files]\n\
56 -c - use source graph for output\n\
57 -f <pfile> - find program in file <pfile>\n\
58 -i - create node induced subgraph\n\
59 -a <args> - string arguments available as ARGV[0..]\n\
60 -o <ofile> - write output to <ofile>; stdout by default\n\
61 -n - no read-ahead of input graphs\n\
62 -q - turn off warning messages\n\
63 -v - enable verbose messages\n\
64 -V - print version info\n\
65 -? - print usage info\n\
66If no files are specified, stdin is used\n";
67
68typedef struct {
69 char *cmdName; /* command name */
70 FILE *outFile; /* output stream; stdout default */
71 char *program; /* program source */
72 int useFile; /* true if program comes from a file */
75 char **inFiles;
76 strviews_t args;
77 int state; /* > 0 : continue; <= 0 finish */
79} options;
80
81static clock_t start_timer(void) { return clock(); }
82
83static double elapsed_sec(clock_t start) {
84 const clock_t end = clock();
85 return (end - start) / (double)CLOCKS_PER_SEC;
86}
87
88static FILE *openOut(char *name) {
89 FILE *const outs = gv_fopen(name, "w");
90 if (outs == 0) {
91 error(ERROR_ERROR, "could not open %s for writing", name);
92 }
93 return outs;
94}
95
96/* Tokenize a string. Tokens consist of either a non-empty string
97 * of non-space characters, or all characters between a pair of
98 * single or double quotes. As usual, we map
99 * \c -> c
100 * for all c
101 * Return next argument token, returning NULL if none.
102 * sp is updated to point to next character to be processed.
103 * NB. There must be white space between tokens. Otherwise, they
104 * are concatenated.
105 */
106static char *gettok(char **sp) {
107 char *s = *sp;
108 char *ws = s;
109 char *rs = s;
110 char c;
111 char q = '\0'; /* if non-0, in quote mode with quote char q */
112
113 while (gv_isspace(*rs))
114 rs++;
115 if (*rs == '\0')
116 return NULL;
117 while ((c = *rs)) {
118 if (q && q == c) { /* end quote */
119 q = '\0';
120 } else if (!q && (c == '"' || c == '\'')) {
121 q = c;
122 } else if (c == '\\') {
123 rs++;
124 c = *rs;
125 if (c)
126 *ws++ = c;
127 else {
129 "backslash in argument followed by no character - ignored");
130 rs--;
131 }
132 } else if (q || !gv_isspace(c))
133 *ws++ = c;
134 else
135 break;
136 rs++;
137 }
138 if (*rs)
139 rs++;
140 else if (q)
141 error(ERROR_WARNING, "no closing quote for argument %s", s);
142 *sp = rs;
143 *ws = '\0';
144 return s;
145}
146
147/* Split s into whitespace separated tokens, allowing quotes.
148 * Append tokens to argument list and return new number of arguments.
149 *
150 * @param arg [in,out] The current arguments
151 */
152static void parseArgs(char *s, strviews_t *arg) {
153 char *t;
154
155 while ((t = gettok(&s))) {
156 LIST_APPEND(arg, strview(t, '\0'));
157 }
158}
159
160#if defined(_WIN32) && !defined(__MINGW32__)
161#define LISTSEP ';'
162#else
163#define LISTSEP ':'
164#endif
165
166static char *concat(char *pfx, char *sfx) {
167 agxbuf sp = {0};
168 agxbprint(&sp, "%s%s", pfx, sfx);
169 return agxbdisown(&sp);
170}
171
179static char *dflt_gvprpath(void) {
180
181 // find our containing executable
182 char *const exe = gv_find_me();
183 if (exe == NULL) {
184 return NULL;
185 }
186
187 // assume it is of the form …/bin/foo[.exe] and construct
188 // .:…/share/graphviz/gvpr
189
190 char *slash = strrchr(exe, PATH_SEPARATOR);
191 if (slash == NULL) {
192 free(exe);
193 return NULL;
194 }
195
196 *slash = '\0';
197 slash = strrchr(exe, PATH_SEPARATOR);
198 if (slash == NULL) {
199 free(exe);
200 return NULL;
201 }
202
203 *slash = '\0';
204 const size_t share_len =
205 strlen(".:") + strlen(exe) + strlen("/share/graphviz/gvpr") + 1;
206 char *const share = malloc(share_len);
207 if (share == NULL) {
208 free(exe);
209 return NULL;
210 }
211 snprintf(share, share_len, ".%c%s%cshare%cgraphviz%cgvpr", LISTSEP, exe,
213 free(exe);
214
215 return share;
216}
217
218/* Translate -f arg parameter into a pathname.
219 * If arg contains '/', return arg.
220 * Else search directories in GVPRPATH for arg.
221 * Return NULL on error.
222 */
223static char *resolve(char *arg, int verbose) {
224 char *path;
225 char *s;
226 char *cp;
227 char c;
228 char *fname = 0;
229 char *pathp = NULL;
230 size_t sz;
231
232 if (strchr(arg, PATH_SEPARATOR))
233 return gv_strdup(arg);
234
235 char *const dflt = dflt_gvprpath();
236 if (dflt == NULL) {
237 error(ERROR_ERROR, "Could not determine DFLT_GVPRPATH");
238 }
239
240 path = getenv("GVPRPATH");
241 if (!path)
242 path = getenv("GPRPATH"); // deprecated
243 if (dflt == NULL) {
244 // do not use `dflt` at all
245 } else if (path && (c = *path)) {
246 if (c == LISTSEP) {
247 pathp = path = concat(dflt, path);
248 } else if (path[strlen(path) - 1] == LISTSEP) {
249 pathp = path = concat(path, dflt);
250 }
251 } else
252 path = dflt;
253 if (verbose)
254 fprintf(stderr, "PATH: %s\n", path);
255 agxbuf fp = {0};
256
257 while (*path && !fname) {
258 if (*path == LISTSEP) { /* skip colons */
259 path++;
260 continue;
261 }
262 cp = strchr(path, LISTSEP);
263 if (cp) {
264 sz = (size_t)(cp - path);
265 agxbput_n(&fp, path, sz);
266 path = cp + 1; /* skip past current colon */
267 } else {
268 sz = agxbput(&fp, path);
269 path += sz;
270 }
271 agxbprint(&fp, "%c%s", PATH_SEPARATOR, arg);
272 s = agxbuse(&fp);
273
274 if (access(s, R_OK) == 0) {
275 fname = gv_strdup(s);
276 }
277 }
278
279 free(dflt);
280
281 if (!fname)
282 error(ERROR_ERROR, "Could not find file \"%s\" in GVPRPATH", arg);
283
284 agxbfree(&fp);
285 free(pathp);
286 if (verbose)
287 fprintf(stderr, "file %s resolved to %s\n", arg,
288 fname == NULL ? "<null>" : fname);
289 return fname;
290}
291
292static char *getOptarg(int c, char **argp, int *argip, int argc, char **argv) {
293 char *rv;
294 char *arg = *argp;
295 int argi = *argip;
296
297 if (*arg) {
298 rv = arg;
299 while (*arg)
300 arg++;
301 *argp = arg;
302 } else if (argi < argc) {
303 rv = argv[argi++];
304 *argip = argi;
305 } else {
306 rv = NULL;
307 error(ERROR_WARNING, "missing argument for option -%c", c);
308 }
309 return rv;
310}
311
312/* Process a command-line argument starting with a '-'.
313 * argi is the index of the next available item in argv[].
314 * argc has its usual meaning.
315 *
316 * return > 0 given next argi value
317 * = 0 for exit with 0
318 * < 0 for error
319 */
320static int doFlags(char *arg, int argi, int argc, char **argv, options *opts) {
321 int c;
322
323 while ((c = *arg++)) {
324 switch (c) {
325 case 'c':
326 opts->compflags.srcout = true;
327 break;
328 case 'C':
329 opts->compflags.srcout = true;
330 opts->compflags.clone = true;
331 break;
332 case 'f':
333 if ((optarg = getOptarg(c, &arg, &argi, argc, argv)) &&
334 (opts->program = resolve(optarg, opts->verbose))) {
335 opts->useFile = 1;
336 } else
337 return -1;
338 break;
339 case 'i':
340 opts->compflags.induce = true;
341 break;
342 case 'n':
343 opts->readAhead = 0;
344 break;
345 case 'a':
346 if ((optarg = getOptarg(c, &arg, &argi, argc, argv))) {
347 parseArgs(optarg, &opts->args);
348 } else
349 return -1;
350 break;
351 case 'o':
352 if (!(optarg = getOptarg(c, &arg, &argi, argc, argv)) ||
353 !(opts->outFile = openOut(optarg)))
354 return -1;
355 break;
356 case 'q':
357 setTraceLevel(ERROR_ERROR); /* Don't emit warning messages */
358 break;
359 case 'v':
360 opts->verbose = 1;
361 break;
362 case 'V':
363 fprintf(stderr, "%s version %s (%s)\n", Info[0], Info[1], Info[2]);
364 return 0;
365 case '?':
366 if (optopt == '\0' || optopt == '?')
367 fprintf(stderr, "Usage: gvpr%s", usage);
368 else {
370 }
371 return 0;
372 default:
373 error(ERROR_WARNING, "option -%c unrecognized", c);
374 break;
375 }
376 }
377 return argi;
378}
379
380static void freeOpts(options opts) {
381 if (opts.outFile != NULL && opts.outFile != stdout)
382 fclose(opts.outFile);
383 free(opts.inFiles);
384 if (opts.useFile)
385 free(opts.program);
386 LIST_FREE(&opts.args);
387}
388
390static options scanArgs(int argc, char **argv) {
391 char *arg;
392 options opts = {0};
393
394 opts.cmdName = argv[0];
395 opts.state = 1;
396 opts.readAhead = 1;
397 setErrorId(opts.cmdName);
398 opts.verbose = 0;
399
400 LIST(char *) input_filenames = {0};
401
402 /* loop over arguments */
403 for (int i = 1; i < argc;) {
404 arg = argv[i++];
405 if (*arg == '-') {
406 i = doFlags(arg + 1, i, argc, argv, &opts);
407 if (i <= 0) {
408 opts.state = i;
409 goto opts_done;
410 }
411 } else if (arg)
412 LIST_APPEND(&input_filenames, arg);
413 }
414
415 /* Handle additional semantics */
416 if (opts.useFile == 0) {
417 if (LIST_IS_EMPTY(&input_filenames)) {
418 error(ERROR_ERROR, "No program supplied via argument or -f option");
419 opts.state = -1;
420 } else {
421 opts.program = LIST_POP_FRONT(&input_filenames);
422 }
423 }
424 if (LIST_IS_EMPTY(&input_filenames)) {
425 opts.inFiles = 0;
426 LIST_FREE(&input_filenames);
427 } else {
428 LIST_APPEND(&input_filenames, NULL);
429 LIST_DETACH(&input_filenames, &opts.inFiles, NULL);
430 }
431
432 if (!opts.outFile)
433 opts.outFile = stdout;
434
435opts_done:
436 if (opts.state <= 0) {
437 if (opts.state < 0)
439 LIST_FREE(&input_filenames);
440 }
441
442 return opts;
443}
444
445static Agobj_t *evalEdge(Gpr_t *state, Expr_t *prog, comp_block *xprog,
446 Agedge_t *e) {
447 case_stmt *cs;
448 bool okay;
449
450 state->curobj = (Agobj_t *)e;
451 for (size_t i = 0; i < xprog->n_estmts; i++) {
452 cs = xprog->edge_stmts + i;
453 if (cs->guard)
454 okay = exeval(prog, cs->guard, state).integer != 0;
455 else
456 okay = true;
457 if (okay) {
458 if (cs->action)
459 exeval(prog, cs->action, state);
460 else
461 agsubedge(state->target, e, 1);
462 }
463 }
464 return state->curobj;
465}
466
467static Agobj_t *evalNode(Gpr_t *state, Expr_t *prog, comp_block *xprog,
468 Agnode_t *n) {
469 case_stmt *cs;
470 bool okay;
471
472 state->curobj = (Agobj_t *)n;
473 for (size_t i = 0; i < xprog->n_nstmts; i++) {
474 cs = xprog->node_stmts + i;
475 if (cs->guard)
476 okay = exeval(prog, cs->guard, state).integer != 0;
477 else
478 okay = true;
479 if (okay) {
480 if (cs->action)
481 exeval(prog, cs->action, state);
482 else
483 agsubnode(state->target, n, 1);
484 }
485 }
486 return (state->curobj);
487}
488
493
494static Agnode_t *nextNode(Gpr_t *state, nodestream *nodes) {
495 Agnode_t *np;
496
497 if (state->tvroot != nodes->oldroot) {
498 np = nodes->oldroot = state->tvroot;
499 } else if (state->flags & GV_NEXT_SET) {
500 np = nodes->oldroot = state->tvroot = state->tvnext;
501 state->flags &= ~GV_NEXT_SET;
502 } else if (nodes->prev) {
503 np = nodes->prev = agnxtnode(state->curgraph, nodes->prev);
504 } else {
505 np = nodes->prev = agfstnode(state->curgraph);
506 }
507 return np;
508}
509
510#define MARKED(x) (((x)->iu.integer) & 1)
511#define MARK(x) (((x)->iu.integer) = 1)
512#define ONSTACK(x) (((x)->iu.integer) & 2)
513#define PUSH(x, e) (((x)->iu.integer) |= 2, (x)->ine = (e))
514#define POP(x) (((x)->iu.integer) &= (~2))
515
516typedef Agedge_t *(*fstedgefn_t)(Agraph_t *, Agnode_t *);
517typedef Agedge_t *(*nxttedgefn_t)(Agraph_t *, Agedge_t *, Agnode_t *);
518
519#define PRE_VISIT 1
520#define POST_VISIT 2
521
522typedef struct {
525 unsigned char undirected;
526 unsigned char visit;
527} trav_fns;
528
530static Agedge_t *agnxtout_(Agraph_t *g, Agedge_t *e, Agnode_t *ignored) {
531 (void)ignored;
532 return agnxtout(g, e);
533}
534
536static Agedge_t *agnxtin_(Agraph_t *g, Agedge_t *e, Agnode_t *ignored) {
537 (void)ignored;
538 return agnxtin(g, e);
539}
540
543static trav_fns REVfns = {agfstin, agnxtin_, 0, 0};
544
545static void travBFS(Gpr_t *state, Expr_t *prog, comp_block *xprog) {
546 LIST(Agnode_t *) q = {0};
547 Agnode_t *n;
548 Agraph_t *g = state->curgraph;
549 const size_t nodeseq_limit = aggetseq(g, AGNODE);
550 const size_t edgeseq_limit = aggetseq(g, AGEDGE);
551
552 for (nodestream nodes = {0}; (n = nextNode(state, &nodes));) {
553 if (AGSEQ(n) > nodeseq_limit) {
554 continue;
555 }
556 ndata *nd = nData(n);
557 if (MARKED(nd))
558 continue;
559 PUSH(nd, 0);
560 LIST_PUSH_BACK(&q, n);
561 while (!LIST_IS_EMPTY(&q)) {
562 n = LIST_POP_FRONT(&q);
563 nd = nData(n);
564 MARK(nd);
565 POP(nd);
566 state->tvedge = nd->ine;
567 if (!evalNode(state, prog, xprog, n))
568 continue;
569 for (Agedge_t *current = agfstedge(g, n), *next; current != NULL;
570 current = next) {
571 next = agnxtedge(g, current, n);
572 if (AGSEQ(current) > edgeseq_limit) {
573 continue;
574 }
575 nd = nData(current->node);
576 if (MARKED(nd))
577 continue;
578 if (!evalEdge(state, prog, xprog, current))
579 continue;
580 if (!ONSTACK(nd)) {
581 LIST_PUSH_BACK(&q, current->node);
582 PUSH(nd, current);
583 }
584 }
585 }
586 }
587 state->tvedge = 0;
588 LIST_FREE(&q);
589}
590
591static void travDFS(Gpr_t *state, Expr_t *prog, comp_block *xprog,
592 trav_fns *fns) {
593 Agnode_t *n;
594 LIST(Agedge_t *) stk = {0};
595 const size_t nodeseq_limit = aggetseq(state->curgraph, AGNODE);
596 const size_t edgeseq_limit = aggetseq(state->curgraph, AGEDGE);
597
598 for (nodestream nodes = {0}; (n = nextNode(state, &nodes));) {
599 ndata *nd = nData(n);
600 if (MARKED(nd))
601 continue;
602 if (AGSEQ(n) > nodeseq_limit) {
603 continue;
604 }
605 Agedgepair_t seed = {.out = {.node = n}};
606 Agnode_t *curn = n;
607 Agedge_t *entry = &seed.out;
608 state->tvedge = NULL;
609 MARK(nd);
610 PUSH(nd, 0);
611 if (fns->visit & PRE_VISIT)
612 evalNode(state, prog, xprog, n);
613 bool more = true;
614 for (Agedge_t *current = NULL; more;) {
615 if (current != NULL)
616 current = fns->nxtedge(state->curgraph, current, curn);
617 else
618 current = fns->fstedge(state->curgraph, curn);
619 if (current) {
620 if (AGSEQ(current) > edgeseq_limit) {
621 continue;
622 }
623 if (entry == agopp(current)) // skip edge used to get here
624 continue;
625 nd = nData(current->node);
626 if (MARKED(nd)) {
627 /* For undirected DFS, visit an edge only if its head
628 * is on the stack, to avoid visiting it twice.
629 * This is no problem in directed DFS.
630 */
631 if (fns->undirected) {
632 if (ONSTACK(nd))
633 evalEdge(state, prog, xprog, current);
634 } else
635 evalEdge(state, prog, xprog, current);
636 } else {
637 evalEdge(state, prog, xprog, current);
638 LIST_PUSH_BACK(&stk, entry);
639 state->tvedge = entry = current;
640 curn = current->node;
641 current = NULL;
642 if (fns->visit & PRE_VISIT)
643 evalNode(state, prog, xprog, curn);
644 MARK(nd);
645 PUSH(nd, entry);
646 }
647 } else {
648 if (fns->visit & POST_VISIT)
649 evalNode(state, prog, xprog, curn);
650 nd = nData(curn);
651 POP(nd);
652 current = entry;
653 entry = LIST_IS_EMPTY(&stk) ? NULL : LIST_POP_BACK(&stk);
654 if (entry == &seed.out)
655 state->tvedge = 0;
656 else
657 state->tvedge = entry;
658 if (entry)
659 curn = entry->node;
660 else
661 more = false;
662 }
663 }
664 }
665 state->tvedge = 0;
666 LIST_FREE(&stk);
667}
668
669static void travNodes(Gpr_t *state, Expr_t *prog, comp_block *xprog) {
670 Agnode_t *n;
671 Agnode_t *next;
672 Agraph_t *g = state->curgraph;
673 for (n = agfstnode(g); n; n = next) {
674 next = agnxtnode(g, n);
675 evalNode(state, prog, xprog, n);
676 }
677}
678
679static void travEdges(Gpr_t *state, Expr_t *prog, comp_block *xprog) {
680 Agnode_t *n;
681 Agnode_t *next;
682 Agedge_t *e;
683 Agedge_t *nexte;
684 Agraph_t *g = state->curgraph;
685 for (n = agfstnode(g); n; n = next) {
686 next = agnxtnode(g, n);
687 for (e = agfstout(g, n); e; e = nexte) {
688 nexte = agnxtout(g, e);
689 evalEdge(state, prog, xprog, e);
690 }
691 }
692}
693
694static void travFlat(Gpr_t *state, Expr_t *prog, comp_block *xprog) {
695 Agnode_t *n;
696 Agnode_t *next;
697 Agedge_t *e;
698 Agedge_t *nexte;
699 Agraph_t *g = state->curgraph;
700 const size_t nodeseq_limit = aggetseq(g, AGNODE);
701 const size_t edgeseq_limit = aggetseq(g, AGEDGE);
702 for (n = agfstnode(g); n; n = next) {
703 next = agnxtnode(g, n);
704 if (AGSEQ(n) > nodeseq_limit) {
705 continue;
706 }
707 if (!evalNode(state, prog, xprog, n))
708 continue;
709 if (xprog->n_estmts > 0) {
710 for (e = agfstout(g, n); e; e = nexte) {
711 nexte = agnxtout(g, e);
712 if (AGSEQ(e) > edgeseq_limit) {
713 continue;
714 }
715 evalEdge(state, prog, xprog, e);
716 }
717 }
718 }
719}
720
722static void doCleanup(Agraph_t *g) {
723 Agnode_t *n;
724 ndata *nd;
725
726 for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
727 nd = nData(n);
728 nd->ine = NULL;
729 nd->iu.integer = 0;
730 }
731}
732
734static bool traverse(Gpr_t *state, Expr_t *prog, comp_block *bp, bool cleanup) {
735 if (!state->target) {
736 char *target;
737 agxbuf tmp = {0};
738
739 if (state->name_used) {
740 agxbprint(&tmp, "%s%d", state->tgtname, state->name_used);
741 target = agxbuse(&tmp);
742 } else
743 target = state->tgtname;
744 state->name_used++;
745 /* make sure target subgraph does not exist */
746 while (agsubg(state->curgraph, target, 0)) {
747 state->name_used++;
748 agxbprint(&tmp, "%s%d", state->tgtname, state->name_used);
749 target = agxbuse(&tmp);
750 }
751 state->target = openSubg(state->curgraph, target);
752 agxbfree(&tmp);
753 }
754 if (!state->outgraph)
755 state->outgraph = state->target;
756
757 switch (state->tvt) {
758 case TV_flat:
759 travFlat(state, prog, bp);
760 break;
761 case TV_bfs:
762 if (cleanup)
763 doCleanup(state->curgraph);
764 travBFS(state, prog, bp);
765 cleanup = true;
766 break;
767 case TV_dfs:
768 if (cleanup)
769 doCleanup(state->curgraph);
771 travDFS(state, prog, bp, &DFSfns);
772 cleanup = true;
773 break;
774 case TV_fwd:
775 if (cleanup)
776 doCleanup(state->curgraph);
778 travDFS(state, prog, bp, &FWDfns);
779 cleanup = true;
780 break;
781 case TV_rev:
782 if (cleanup)
783 doCleanup(state->curgraph);
785 travDFS(state, prog, bp, &REVfns);
786 cleanup = true;
787 break;
788 case TV_postdfs:
789 if (cleanup)
790 doCleanup(state->curgraph);
792 travDFS(state, prog, bp, &DFSfns);
793 cleanup = true;
794 break;
795 case TV_postfwd:
796 if (cleanup)
797 doCleanup(state->curgraph);
799 travDFS(state, prog, bp, &FWDfns);
800 cleanup = true;
801 break;
802 case TV_postrev:
803 if (cleanup)
804 doCleanup(state->curgraph);
806 travDFS(state, prog, bp, &REVfns);
807 cleanup = true;
808 break;
809 case TV_prepostdfs:
810 if (cleanup)
811 doCleanup(state->curgraph);
813 travDFS(state, prog, bp, &DFSfns);
814 cleanup = true;
815 break;
816 case TV_prepostfwd:
817 if (cleanup)
818 doCleanup(state->curgraph);
820 travDFS(state, prog, bp, &FWDfns);
821 cleanup = true;
822 break;
823 case TV_prepostrev:
824 if (cleanup)
825 doCleanup(state->curgraph);
827 travDFS(state, prog, bp, &REVfns);
828 cleanup = true;
829 break;
830 case TV_ne:
831 travNodes(state, prog, bp);
832 travEdges(state, prog, bp);
833 break;
834 case TV_en:
835 travEdges(state, prog, bp);
836 travNodes(state, prog, bp);
837 break;
838 default:
839 UNREACHABLE();
840 }
841 return cleanup;
842}
843
844/* Append output graph to option struct.
845 * We know uopts and state->outgraph are non-NULL.
846 */
847static void addOutputGraph(Gpr_t *state, gvpropts *uopts) {
848 Agraph_t *g = state->outgraph;
849
850 if ((agroot(g) == state->curgraph) && !uopts->ingraphs)
851 g = (Agraph_t *)cloneO(0, &g->base);
852
853 uopts->outgraphs = gv_recalloc(uopts->outgraphs, uopts->n_outgraphs,
854 uopts->n_outgraphs + 1, sizeof(Agraph_t *));
855 uopts->n_outgraphs++;
856 uopts->outgraphs[uopts->n_outgraphs - 1] = g;
857}
858
859static void chkClose(Agraph_t *g) {
860 gdata *data;
861
862 data = gData(g);
863 if (data->lock.locked)
864 data->lock.zombie = true;
865 else
866 agclose(g);
867}
868
869static Agraph_t *ing_read(const char *filename, void *fp) {
870 Agraph_t *g = agconcat(NULL, filename, fp, NULL);
871 if (g) {
872 aginit(g, AGRAPH, UDATA, sizeof(gdata), false);
873 aginit(g, AGNODE, UDATA, sizeof(ndata), false);
874 aginit(g, AGEDGE, UDATA, sizeof(edata), false);
875 }
876 return g;
877}
878
887
888/* Only used if GV_USE_EXIT not set during exeval.
889 * This implies setjmp/longjmp set up.
890 */
891static void gvexitf(void *env, int v) {
892 gvpr_state_t *st = env;
893
894 longjmp(st->state->jbuf, v);
895}
896
897static void gverrorf(const char *prefix, void *user_state, int level,
898 const char *fmt, ...) {
899 assert(user_state != NULL);
900
901 va_list ap;
902
903 va_start(ap, fmt);
904 errorv(prefix, level, fmt, ap);
905 va_end(ap);
906
907 if (level >= ERROR_ERROR) {
908 Gpr_t *state = user_state;
909 if (state->flags & GV_USE_EXIT)
910 graphviz_exit(1);
911 else if (state->flags & GV_USE_JUMP)
912 longjmp(state->jbuf, 1);
913 }
914}
915
916/* Return 0 on success; non-zero on error.
917 *
918 * FIX/TODO:
919 * - close non-source/non-output graphs
920 * - flag to clone target graph?
921 * - remove assignment in boolean warning if wrapped in ()
922 * - do automatic cast for array indices if type is known
923 * - array initialization
924 */
925static int gvpr_core(int argc, char *argv[], gvpropts *uopts,
926 gvpr_state_t *gs) {
928 int rv = 0;
929
931
932 gs->opts = scanArgs(argc, argv);
933 if (gs->opts.state <= 0) {
934 return gs->opts.state;
935 }
936
937 clock_t start = start_timer();
938 gs->prog = parseProg(gs->opts.program, gs->opts.useFile);
939 if (gs->prog == NULL) {
940 return 1;
941 }
942 info.outFile = gs->opts.outFile;
943 info.args = gs->opts.args;
944 info.errf = gverrorf;
945 info.flags = uopts->flags;
946 if (uopts->flags & GV_USE_EXIT)
947 info.exitf = 0;
948 else
949 info.exitf = gvexitf;
950 gs->state = openGPRState(&info);
951 if (gs->state == NULL) {
952 return 1;
953 }
954 if (uopts->bindings)
955 addBindings(gs->state, uopts->bindings);
956 gs->xprog = compileProg(gs->prog, gs->state, gs->opts.compflags);
957 if (gs->xprog == NULL) {
958 return 1;
959 }
960
961 initGPRState(gs->state);
962
963 if (uopts->flags & GV_USE_OUTGRAPH) {
964 uopts->outgraphs = 0;
965 uopts->n_outgraphs = 0;
966 }
967
968 if (!(uopts->flags & GV_USE_EXIT)) {
969 gs->state->flags |= GV_USE_JUMP;
970 if ((rv = setjmp(gs->state->jbuf))) {
971 return rv;
972 }
973 }
974
975 bool incoreGraphs = uopts->ingraphs != NULL;
976
977 if (gs->opts.verbose)
978 fprintf(stderr, "Parse/compile/init: %.2f secs.\n", elapsed_sec(start));
979 /* do begin */
980 if (gs->xprog->begin_stmt != NULL)
981 exeval(gs->xprog->prog, gs->xprog->begin_stmt, gs->state);
982
983 /* if program is not null */
984 if (gs->xprog->uses_graph) {
985 if (uopts->ingraphs)
986 gs->ing = newIngGraphs(0, uopts->ingraphs, ing_read);
987 else
988 gs->ing = newIng(0, gs->opts.inFiles, ing_read);
989
990 start = start_timer();
991 Agraph_t *nextg = NULL;
992 for (gs->state->curgraph = nextGraph(gs->ing); gs->state->curgraph;
993 gs->state->curgraph = nextg) {
994 if (gs->opts.verbose)
995 fprintf(stderr, "Read graph: %.2f secs.\n", elapsed_sec(start));
996 gs->state->infname = fileName(gs->ing);
997 if (gs->opts.readAhead)
998 nextg = gs->state->nextgraph = nextGraph(gs->ing);
999 bool cleanup = false;
1000
1001 for (size_t i = 0; i < gs->xprog->n_blocks; i++) {
1002 comp_block *bp = gs->xprog->blocks + i;
1003
1004 /* begin graph */
1005 if (incoreGraphs && gs->opts.compflags.clone)
1006 gs->state->curgraph =
1007 (Agraph_t *)cloneO(0, &gs->state->curgraph->base);
1008 gs->state->curobj = &gs->state->curgraph->base;
1009 gs->state->tvroot = 0;
1010 if (bp->begg_stmt)
1011 exeval(gs->xprog->prog, bp->begg_stmt, gs->state);
1012
1013 /* walk graph */
1014 if (bp->does_walk_graph) {
1015 cleanup = traverse(gs->state, gs->xprog->prog, bp, cleanup);
1016 }
1017 }
1018
1019 /* end graph */
1020 gs->state->curobj = &gs->state->curgraph->base;
1021 if (gs->xprog->endg_stmt != NULL)
1022 exeval(gs->xprog->prog, gs->xprog->endg_stmt, gs->state);
1023 if (gs->opts.verbose)
1024 fprintf(stderr, "Finish graph: %.2f secs.\n", elapsed_sec(start));
1025
1026 /* if $O == $G and $T is empty, delete $T */
1027 if (gs->state->outgraph == gs->state->curgraph &&
1028 gs->state->target != NULL && !agnnodes(gs->state->target))
1029 agdelete(gs->state->curgraph, gs->state->target);
1030
1031 /* output graph, if necessary
1032 * For this, the outgraph must be defined, and either
1033 * be non-empty or the -c option was used.
1034 */
1035 if (gs->state->outgraph != NULL &&
1036 (agnnodes(gs->state->outgraph) || gs->opts.compflags.srcout)) {
1037 if (uopts->flags & GV_USE_OUTGRAPH)
1038 addOutputGraph(gs->state, uopts);
1039 else
1040 sfioWrite(gs->state->outgraph, gs->opts.outFile);
1041 }
1042
1043 if (!incoreGraphs)
1044 chkClose(gs->state->curgraph);
1045 gs->state->target = 0;
1046 gs->state->outgraph = 0;
1047
1048 start = start_timer();
1049 if (!gs->opts.readAhead)
1050 nextg = nextGraph(gs->ing);
1051 if (gs->opts.verbose && nextg != NULL) {
1052 fprintf(stderr, "Read graph: %.2f secs.\n", elapsed_sec(start));
1053 }
1054 }
1055 }
1056
1057 /* do end */
1058 gs->state->curgraph = 0;
1059 gs->state->curobj = 0;
1060 if (gs->xprog->end_stmt != NULL)
1061 exeval(gs->xprog->prog, gs->xprog->end_stmt, gs->state);
1062
1063 return 0;
1064}
1065
1070int gvpr(int argc, char *argv[], gvpropts *uopts) {
1071 gvpr_state_t gvpr_state = {0};
1072
1073 // initialize opts to something that makes freeOpts() a no-op if we fail early
1074 gvpr_state.opts.outFile = stdout;
1075
1076 gvpropts DEFAULT_OPTS = {0};
1077 if (uopts == NULL) {
1078 uopts = &DEFAULT_OPTS;
1079 }
1080
1081 int rv = gvpr_core(argc, argv, uopts, &gvpr_state);
1082
1083 // free all allocated resources
1084 freeParseProg(gvpr_state.prog);
1085 freeCompileProg(gvpr_state.xprog);
1086 closeGPRState(gvpr_state.state);
1087 if (gvpr_state.ing != NULL) {
1088 closeIngraph(gvpr_state.ing);
1089 }
1090 freeOpts(gvpr_state.opts);
1091
1092 return rv;
1093}
1094
Agobj_t * cloneO(Agraph_t *g, Agobj_t *obj)
Definition actions.c:329
int sfioWrite(Agraph_t *g, FILE *fp)
Definition actions.c:515
Dynamically expanding string buffers.
static void agxbfree(agxbuf *xb)
free any malloced resources
Definition agxbuf.h:97
static size_t agxbput_n(agxbuf *xb, const char *s, size_t ssz)
append string s of length ssz into xb
Definition agxbuf.h:268
static int agxbprint(agxbuf *xb, const char *fmt,...)
Printf-style output to an agxbuf.
Definition agxbuf.h:252
static WUR char * agxbuse(agxbuf *xb)
Definition agxbuf.h:325
static char * agxbdisown(agxbuf *xb)
Definition agxbuf.h:345
Memory allocation wrappers that exit on failure.
static void * gv_recalloc(void *ptr, size_t old_nmemb, size_t new_nmemb, size_t size)
Definition alloc.h:73
static char * gv_strdup(const char *original)
Definition alloc.h:101
int verbose
Definition bcomps.c:62
abstract graph C library, Cgraph API
comp_prog * compileProg(parse_prog *inp, Gpr_t *state, compflags_t flags)
Definition compile.c:2358
Agraph_t * openSubg(Agraph_t *g, char *name)
Definition compile.c:2475
void freeCompileProg(comp_prog *p)
Definition compile.c:2437
#define nData(n)
Definition compile.h:57
#define UDATA
Definition compile.h:30
#define gData(g)
Definition compile.h:58
static char * fname
void setTraceLevel(int i)
Definition error.c:34
void errorv(const char *id, int level, const char *s, va_list ap)
Definition error.c:36
void setErrorId(char *id)
Definition error.c:31
void setErrorErrors(int errors)
Definition error.c:32
#define ERROR_WARNING
Definition error.h:35
#define ERROR_ERROR
Definition error.h:36
#define ERROR_USAGE
Definition error.h:41
Extype_t exeval(Expr_t *ex, Exnode_t *exnode, void *env)
Definition exeval.c:1949
static long seed
Definition exeval.c:1010
static NORETURN void graphviz_exit(int status)
Definition exit.h:23
static void cleanup(void)
Definition gmlparse.c:130
void * malloc(YYSIZE_T)
void free(void *)
require define api prefix
Definition gmlparse.y:17
void initGPRState(Gpr_t *state)
Definition gprstate.c:34
void addBindings(Gpr_t *state, gvprbinding *bindings)
Definition gprstate.c:94
Gpr_t * openGPRState(gpr_info *info)
Definition gprstate.c:38
void closeGPRState(Gpr_t *state)
Definition gprstate.c:122
@ TV_flat
Definition gprstate.h:32
@ TV_fwd
Definition gprstate.h:34
@ TV_rev
Definition gprstate.h:34
@ TV_postdfs
Definition gprstate.h:35
@ TV_dfs
Definition gprstate.h:34
@ TV_prepostfwd
Definition gprstate.h:36
@ TV_en
Definition gprstate.h:32
@ TV_prepostrev
Definition gprstate.h:36
@ TV_prepostdfs
Definition gprstate.h:36
@ TV_postfwd
Definition gprstate.h:35
@ TV_bfs
Definition gprstate.h:33
@ TV_ne
Definition gprstate.h:32
@ TV_postrev
Definition gprstate.h:35
node NULL
Definition grammar.y:181
int agnnodes(Agraph_t *g)
Definition graph.c:163
#define agopp(e)
opposite edge: flip Agedgepair_s.out ⇄ Agedgepair_s.in/*#end#*‍/
Definition cgraph.h:984
Agedge_t * agsubedge(Agraph_t *g, Agedge_t *e, int createflag)
Definition edge.c:350
Agedge_t * agnxtin(Agraph_t *g, Agedge_t *e)
Definition edge.c:73
Agedge_t * agfstout(Agraph_t *g, Agnode_t *n)
Definition edge.c:28
Agedge_t * agnxtedge(Agraph_t *g, Agedge_t *e, Agnode_t *n)
Definition edge.c:98
Agedge_t * agnxtout(Agraph_t *g, Agedge_t *e)
Definition edge.c:43
Agedge_t * agfstedge(Agraph_t *g, Agnode_t *n)
Definition edge.c:89
Agedge_t * agfstin(Agraph_t *g, Agnode_t *n)
Definition edge.c:59
int agclose(Agraph_t *g)
deletes a graph, freeing its associated storage
Definition graph.c:97
Agraph_t * agconcat(Agraph_t *g, const char *filename, void *chan, Agdisc_t *disc)
merges the file contents with a pre-existing graph
Definition grammar.c:2030
Agnode_t * agnxtnode(Agraph_t *g, Agnode_t *n)
Definition node.c:50
Agnode_t * agfstnode(Agraph_t *g)
Definition node.c:43
Agnode_t * agsubnode(Agraph_t *g, Agnode_t *n, int createflag)
Definition node.c:254
uint64_t aggetseq(const Agraph_t *g, int objtype)
returns highest sequence number allocated so far, for given object type
Definition graph.c:152
int agdelete(Agraph_t *g, void *obj)
deletes object. Equivalent to agclose, agdelnode, and agdeledge for obj being a graph,...
Definition obj.c:22
Agraph_t * agroot(void *obj)
Definition obj.c:170
#define AGSEQ(obj)
Definition cgraph.h:225
@ AGEDGE
Definition cgraph.h:207
@ AGNODE
Definition cgraph.h:207
@ AGRAPH
Definition cgraph.h:207
void aginit(Agraph_t *g, int kind, const char *rec_name, int rec_size, int move_to_front)
attach new records to objects of specified kind
Definition rec.c:172
Agraph_t * agsubg(Agraph_t *g, char *name, int cflag)
Definition subg.c:52
replacements for ctype.h functions
static bool gv_isspace(int c)
Definition gv_ctype.h:55
char * gv_find_me(void)
Definition gv_find_me.c:69
platform abstraction for finding the path to yourself
FILE * gv_fopen(const char *filename, const char *mode)
Definition gv_fopen.c:34
wrapper around fopen for internal library usage
agxbput(xb, staging)
static opts_t opts
Definition gvgen.c:415
static void freeOpts(options opts)
Definition gvpr.c:380
static char * concat(char *pfx, char *sfx)
Definition gvpr.c:166
#define MARKED(x)
Definition gvpr.c:510
static char * resolve(char *arg, int verbose)
Definition gvpr.c:223
static char * Info[]
Definition gvpr.c:48
static Agobj_t * evalNode(Gpr_t *state, Expr_t *prog, comp_block *xprog, Agnode_t *n)
Definition gvpr.c:467
static void gvexitf(void *env, int v)
Definition gvpr.c:891
int gvpr(int argc, char *argv[], gvpropts *uopts)
Definition gvpr.c:1070
static void chkClose(Agraph_t *g)
Definition gvpr.c:859
static void travNodes(Gpr_t *state, Expr_t *prog, comp_block *xprog)
Definition gvpr.c:669
static void doCleanup(Agraph_t *g)
reset node traversal data
Definition gvpr.c:722
#define ONSTACK(x)
Definition gvpr.c:512
static char * gettok(char **sp)
Definition gvpr.c:106
#define PRE_VISIT
Definition gvpr.c:519
static FILE * openOut(char *name)
Definition gvpr.c:88
static bool traverse(Gpr_t *state, Expr_t *prog, comp_block *bp, bool cleanup)
return true if traversal requires cleanup
Definition gvpr.c:734
#define MARK(x)
Definition gvpr.c:511
static Agedge_t * agnxtin_(Agraph_t *g, Agedge_t *e, Agnode_t *ignored)
agnxtin wrapper to tweak calling convention
Definition gvpr.c:536
#define LISTSEP
Definition gvpr.c:163
Agedge_t *(* nxttedgefn_t)(Agraph_t *, Agedge_t *, Agnode_t *)
Definition gvpr.c:517
static void gverrorf(const char *prefix, void *user_state, int level, const char *fmt,...)
Definition gvpr.c:897
static options scanArgs(int argc, char **argv)
parse command line options
Definition gvpr.c:390
static trav_fns DFSfns
Definition gvpr.c:541
static void travDFS(Gpr_t *state, Expr_t *prog, comp_block *xprog, trav_fns *fns)
Definition gvpr.c:591
static trav_fns REVfns
Definition gvpr.c:543
#define POST_VISIT
Definition gvpr.c:520
static trav_fns FWDfns
Definition gvpr.c:542
static void travBFS(Gpr_t *state, Expr_t *prog, comp_block *xprog)
Definition gvpr.c:545
static void travFlat(Gpr_t *state, Expr_t *prog, comp_block *xprog)
Definition gvpr.c:694
static clock_t start_timer(void)
Definition gvpr.c:81
static char * dflt_gvprpath(void)
Definition gvpr.c:179
static void travEdges(Gpr_t *state, Expr_t *prog, comp_block *xprog)
Definition gvpr.c:679
static Agraph_t * ing_read(const char *filename, void *fp)
Definition gvpr.c:869
static Agnode_t * nextNode(Gpr_t *state, nodestream *nodes)
Definition gvpr.c:494
static void addOutputGraph(Gpr_t *state, gvpropts *uopts)
Definition gvpr.c:847
static int gvpr_core(int argc, char *argv[], gvpropts *uopts, gvpr_state_t *gs)
Definition gvpr.c:925
static int doFlags(char *arg, int argi, int argc, char **argv, options *opts)
Definition gvpr.c:320
Agedge_t *(* fstedgefn_t)(Agraph_t *, Agnode_t *)
Definition gvpr.c:516
static Agedge_t * agnxtout_(Agraph_t *g, Agedge_t *e, Agnode_t *ignored)
agnxtout wrapper to tweak calling convention
Definition gvpr.c:530
static const char * usage
Definition gvpr.c:54
static Agobj_t * evalEdge(Gpr_t *state, Expr_t *prog, comp_block *xprog, Agedge_t *e)
Definition gvpr.c:445
#define PUSH(x, e)
Definition gvpr.c:513
static void parseArgs(char *s, strviews_t *arg)
Definition gvpr.c:152
#define POP(x)
Definition gvpr.c:514
static char * getOptarg(int c, char **argp, int *argip, int argc, char **argv)
Definition gvpr.c:292
graph pattern scanning and processing language API, main function gvpr
#define GV_USE_OUTGRAPH
Definition gvpr.h:49
#define GV_USE_EXIT
Definition gvpr.h:47
#define GV_NEXT_SET
Definition gvpr.h:53
#define GV_USE_JUMP
Definition gvpr.h:51
table Syntax error
Definition htmlparse.y:288
char * fileName(ingraph_state *sp)
Return name of current file being processed.
Definition ingraphs.c:156
void closeIngraph(ingraph_state *sp)
Definition ingraphs.c:147
Agraph_t * nextGraph(ingraph_state *sp)
Definition ingraphs.c:61
ingraph_state * newIng(ingraph_state *sp, char **files, Agraph_t *(*readf)(const char *, void *))
Definition ingraphs.c:120
ingraph_state * newIngGraphs(ingraph_state *sp, Agraph_t **graphs, Agraph_t *(*readf)(const char *, void *))
Definition ingraphs.c:128
supports user-supplied data
type-generic dynamically expanding list
#define LIST_DETACH(list, datap, sizep)
Definition list.h:427
#define LIST_PUSH_BACK(list,...)
Definition list.h:368
#define LIST_APPEND(list,...)
Definition list.h:124
#define LIST(type)
Definition list.h:55
#define LIST_POP_FRONT(list)
Definition list.h:378
#define LIST_FREE(list)
Definition list.h:350
#define LIST_POP_BACK(list)
Definition list.h:391
#define LIST_IS_EMPTY(list)
Definition list.h:90
void freeParseProg(parse_prog *prog)
Definition parse.c:509
parse_prog * parseProg(char *input, int isFile)
parses input into gpr sections
Definition parse.c:391
File system path helpers.
#define PATH_SEPARATOR
character for separating directory components in a file system path
Definition path.h:10
Agnode_t * node
Definition cgraph.h:272
Agedge_t out
Definition cgraph.h:276
a generic header of Agraph_s, Agnode_s and Agedge_s
Definition cgraph.h:210
graph or subgraph
Definition cgraph.h:424
Agobj_t base
Definition cgraph.h:425
Definition expr.h:207
Agedge_t * tvedge
Definition gprstate.h:54
char * tgtname
Definition gprstate.h:48
Agobj_t * curobj
Definition gprstate.h:44
Agraph_t * target
Definition gprstate.h:42
Agraph_t * curgraph
Definition gprstate.h:40
int flags
Definition gprstate.h:57
Agraph_t * outgraph
Definition gprstate.h:43
Agnode_t * tvroot
Definition gprstate.h:52
Agnode_t * tvnext
Definition gprstate.h:53
char * infname
Definition gprstate.h:49
jmp_buf jbuf
Definition gprstate.h:60
Agraph_t * nextgraph
Definition gprstate.h:41
int name_used
Definition gprstate.h:55
trav_type tvt
Definition gprstate.h:51
Exnode_t * guard
Definition compile.h:26
Exnode_t * action
Definition compile.h:27
case_stmt * node_stmts
Definition compile.h:71
Exnode_t * begg_stmt
Definition compile.h:67
size_t n_nstmts
Definition compile.h:69
size_t n_estmts
Definition compile.h:70
case_stmt * edge_stmts
Definition compile.h:72
bool does_walk_graph
does this block have a node or edge statement?
Definition compile.h:68
Exnode_t * begin_stmt
Definition compile.h:78
comp_block * blocks
Definition compile.h:80
Expr_t * prog
Definition compile.h:77
size_t n_blocks
Definition compile.h:79
bool uses_graph
does this program use the input graph?
Definition compile.h:76
Exnode_t * endg_stmt
Definition compile.h:81
Exnode_t * end_stmt
Definition compile.h:82
bool srcout
Definition compile.h:61
bool clone
Definition compile.h:63
gvlock_t lock
Definition compile.h:45
bool locked
is the lock currently taken?
Definition compile.h:39
bool zombie
was a deletion request recorded while locked?
Definition compile.h:40
collective managed state used in gvpr_core
Definition gvpr.c:880
parse_prog * prog
Definition gvpr.c:881
comp_prog * xprog
Definition gvpr.c:883
options opts
Definition gvpr.c:885
ingraph_state * ing
Definition gvpr.c:882
Gpr_t * state
Definition gvpr.c:884
size_t n_outgraphs
if GV_USE_OUTGRAPH set, output graphs
Definition gvpr.h:65
Agraph_t ** outgraphs
Definition gvpr.h:66
gvprbinding * bindings
Definition gvpr.h:70
int flags
Definition gvpr.h:69
Agraph_t ** ingraphs
Definition gvpr.h:64
Agnode_t * prev
Definition gvpr.c:491
Agnode_t * oldroot
Definition gvpr.c:490
Definition gvpr.c:68
compflags_t compflags
Definition gvpr.c:73
char * program
Definition gvpr.c:71
strviews_t args
Definition gvpr.c:76
int readAhead
Definition gvpr.c:74
int verbose
Definition gvpr.c:78
char * cmdName
Definition gvpr.c:69
FILE * outFile
Definition gvpr.c:70
int useFile
Definition gvpr.c:72
int state
Definition gvpr.c:77
char ** inFiles
Definition gvpr.c:75
Definition types.h:81
unsigned char visit
Definition gvpr.c:526
nxttedgefn_t nxtedge
Definition gvpr.c:524
unsigned char undirected
Definition gvpr.c:525
fstedgefn_t fstedge
Definition gvpr.c:523
Non-owning string references.
static strview_t strview(const char *referent, char terminator)
create a string reference
Definition strview.h:26
double elapsed_sec(void)
Definition timing.c:23
long long integer
Definition exparse.h:240
Definition grammar.c:90
#define UNREACHABLE()
Definition unreachable.h:30