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