Graphviz 14.1.2~dev.20260118.1035
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 "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 [inout] 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 nodestream nodes;
547 LIST(Agnode_t *) q = {0};
548 ndata *nd;
549 Agnode_t *n;
550 Agedge_t *cure;
551 Agedge_t *nxte;
552 Agraph_t *g = state->curgraph;
553
554 nodes.oldroot = 0;
555 nodes.prev = 0;
556 while ((n = nextNode(state, &nodes))) {
557 nd = nData(n);
558 if (MARKED(nd))
559 continue;
560 PUSH(nd, 0);
561 LIST_PUSH_BACK(&q, n);
562 while (!LIST_IS_EMPTY(&q)) {
563 n = LIST_POP_FRONT(&q);
564 nd = nData(n);
565 MARK(nd);
566 POP(nd);
567 state->tvedge = nd->ine;
568 if (!evalNode(state, prog, xprog, n))
569 continue;
570 for (cure = agfstedge(g, n); cure; cure = nxte) {
571 nxte = agnxtedge(g, cure, n);
572 nd = nData(cure->node);
573 if (MARKED(nd))
574 continue;
575 if (!evalEdge(state, prog, xprog, cure))
576 continue;
577 if (!ONSTACK(nd)) {
578 LIST_PUSH_BACK(&q, cure->node);
579 PUSH(nd, cure);
580 }
581 }
582 }
583 }
584 state->tvedge = 0;
585 LIST_FREE(&q);
586}
587
588static void travDFS(Gpr_t *state, Expr_t *prog, comp_block *xprog,
589 trav_fns *fns) {
590 Agnode_t *n;
591 LIST(Agedge_t *) stk = {0};
592 Agnode_t *curn;
593 Agedge_t *cure;
594 Agedge_t *entry;
595 int more;
596 ndata *nd;
597 nodestream nodes;
599
600 nodes.oldroot = 0;
601 nodes.prev = 0;
602 while ((n = nextNode(state, &nodes))) {
603 nd = nData(n);
604 if (MARKED(nd))
605 continue;
606 seed.out.node = n;
607 seed.in.node = 0;
608 curn = n;
609 entry = &seed.out;
610 state->tvedge = cure = 0;
611 MARK(nd);
612 PUSH(nd, 0);
613 if (fns->visit & PRE_VISIT)
614 evalNode(state, prog, xprog, n);
615 more = 1;
616 while (more) {
617 if (cure)
618 cure = fns->nxtedge(state->curgraph, cure, curn);
619 else
620 cure = fns->fstedge(state->curgraph, curn);
621 if (cure) {
622 if (entry == agopp(cure)) /* skip edge used to get here */
623 continue;
624 nd = nData(cure->node);
625 if (MARKED(nd)) {
626 /* For undirected DFS, visit an edge only if its head
627 * is on the stack, to avoid visiting it twice.
628 * This is no problem in directed DFS.
629 */
630 if (fns->undirected) {
631 if (ONSTACK(nd))
632 evalEdge(state, prog, xprog, cure);
633 } else
634 evalEdge(state, prog, xprog, cure);
635 } else {
636 evalEdge(state, prog, xprog, cure);
637 LIST_PUSH_BACK(&stk, entry);
638 state->tvedge = entry = cure;
639 curn = cure->node;
640 cure = 0;
641 if (fns->visit & PRE_VISIT)
642 evalNode(state, prog, xprog, curn);
643 MARK(nd);
644 PUSH(nd, entry);
645 }
646 } else {
647 if (fns->visit & POST_VISIT)
648 evalNode(state, prog, xprog, curn);
649 nd = nData(curn);
650 POP(nd);
651 cure = entry;
652 entry = LIST_IS_EMPTY(&stk) ? NULL : LIST_POP_BACK(&stk);
653 if (entry == &seed.out)
654 state->tvedge = 0;
655 else
656 state->tvedge = entry;
657 if (entry)
658 curn = entry->node;
659 else
660 more = 0;
661 }
662 }
663 }
664 state->tvedge = 0;
665 LIST_FREE(&stk);
666}
667
668static void travNodes(Gpr_t *state, Expr_t *prog, comp_block *xprog) {
669 Agnode_t *n;
670 Agnode_t *next;
671 Agraph_t *g = state->curgraph;
672 for (n = agfstnode(g); n; n = next) {
673 next = agnxtnode(g, n);
674 evalNode(state, prog, xprog, n);
675 }
676}
677
678static void travEdges(Gpr_t *state, Expr_t *prog, comp_block *xprog) {
679 Agnode_t *n;
680 Agnode_t *next;
681 Agedge_t *e;
682 Agedge_t *nexte;
683 Agraph_t *g = state->curgraph;
684 for (n = agfstnode(g); n; n = next) {
685 next = agnxtnode(g, n);
686 for (e = agfstout(g, n); e; e = nexte) {
687 nexte = agnxtout(g, e);
688 evalEdge(state, prog, xprog, e);
689 }
690 }
691}
692
693static void travFlat(Gpr_t *state, Expr_t *prog, comp_block *xprog) {
694 Agnode_t *n;
695 Agnode_t *next;
696 Agedge_t *e;
697 Agedge_t *nexte;
698 Agraph_t *g = state->curgraph;
699 for (n = agfstnode(g); n; n = next) {
700 next = agnxtnode(g, n);
701 if (!evalNode(state, prog, xprog, n))
702 continue;
703 if (xprog->n_estmts > 0) {
704 for (e = agfstout(g, n); e; e = nexte) {
705 nexte = agnxtout(g, e);
706 evalEdge(state, prog, xprog, e);
707 }
708 }
709 }
710}
711
713static void doCleanup(Agraph_t *g) {
714 Agnode_t *n;
715 ndata *nd;
716
717 for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
718 nd = nData(n);
719 nd->ine = NULL;
720 nd->iu.integer = 0;
721 }
722}
723
725static bool traverse(Gpr_t *state, Expr_t *prog, comp_block *bp, bool cleanup) {
726 if (!state->target) {
727 char *target;
728 agxbuf tmp = {0};
729
730 if (state->name_used) {
731 agxbprint(&tmp, "%s%d", state->tgtname, state->name_used);
732 target = agxbuse(&tmp);
733 } else
734 target = state->tgtname;
735 state->name_used++;
736 /* make sure target subgraph does not exist */
737 while (agsubg(state->curgraph, target, 0)) {
738 state->name_used++;
739 agxbprint(&tmp, "%s%d", state->tgtname, state->name_used);
740 target = agxbuse(&tmp);
741 }
742 state->target = openSubg(state->curgraph, target);
743 agxbfree(&tmp);
744 }
745 if (!state->outgraph)
746 state->outgraph = state->target;
747
748 switch (state->tvt) {
749 case TV_flat:
750 travFlat(state, prog, bp);
751 break;
752 case TV_bfs:
753 if (cleanup)
754 doCleanup(state->curgraph);
755 travBFS(state, prog, bp);
756 cleanup = true;
757 break;
758 case TV_dfs:
759 if (cleanup)
760 doCleanup(state->curgraph);
762 travDFS(state, prog, bp, &DFSfns);
763 cleanup = true;
764 break;
765 case TV_fwd:
766 if (cleanup)
767 doCleanup(state->curgraph);
769 travDFS(state, prog, bp, &FWDfns);
770 cleanup = true;
771 break;
772 case TV_rev:
773 if (cleanup)
774 doCleanup(state->curgraph);
776 travDFS(state, prog, bp, &REVfns);
777 cleanup = true;
778 break;
779 case TV_postdfs:
780 if (cleanup)
781 doCleanup(state->curgraph);
783 travDFS(state, prog, bp, &DFSfns);
784 cleanup = true;
785 break;
786 case TV_postfwd:
787 if (cleanup)
788 doCleanup(state->curgraph);
790 travDFS(state, prog, bp, &FWDfns);
791 cleanup = true;
792 break;
793 case TV_postrev:
794 if (cleanup)
795 doCleanup(state->curgraph);
797 travDFS(state, prog, bp, &REVfns);
798 cleanup = true;
799 break;
800 case TV_prepostdfs:
801 if (cleanup)
802 doCleanup(state->curgraph);
804 travDFS(state, prog, bp, &DFSfns);
805 cleanup = true;
806 break;
807 case TV_prepostfwd:
808 if (cleanup)
809 doCleanup(state->curgraph);
811 travDFS(state, prog, bp, &FWDfns);
812 cleanup = true;
813 break;
814 case TV_prepostrev:
815 if (cleanup)
816 doCleanup(state->curgraph);
818 travDFS(state, prog, bp, &REVfns);
819 cleanup = true;
820 break;
821 case TV_ne:
822 travNodes(state, prog, bp);
823 travEdges(state, prog, bp);
824 break;
825 case TV_en:
826 travEdges(state, prog, bp);
827 travNodes(state, prog, bp);
828 break;
829 default:
830 UNREACHABLE();
831 }
832 return cleanup;
833}
834
835/* Append output graph to option struct.
836 * We know uopts and state->outgraph are non-NULL.
837 */
838static void addOutputGraph(Gpr_t *state, gvpropts *uopts) {
839 Agraph_t *g = state->outgraph;
840
841 if ((agroot(g) == state->curgraph) && !uopts->ingraphs)
842 g = (Agraph_t *)cloneO(0, &g->base);
843
844 uopts->outgraphs = gv_recalloc(uopts->outgraphs, uopts->n_outgraphs,
845 uopts->n_outgraphs + 1, sizeof(Agraph_t *));
846 uopts->n_outgraphs++;
847 uopts->outgraphs[uopts->n_outgraphs - 1] = g;
848}
849
850static void chkClose(Agraph_t *g) {
851 gdata *data;
852
853 data = gData(g);
854 if (data->lock.locked)
855 data->lock.zombie = true;
856 else
857 agclose(g);
858}
859
860static Agraph_t *ing_read(const char *filename, void *fp) {
861 Agraph_t *g = agconcat(NULL, filename, fp, NULL);
862 if (g) {
863 aginit(g, AGRAPH, UDATA, sizeof(gdata), false);
864 aginit(g, AGNODE, UDATA, sizeof(ndata), false);
865 aginit(g, AGEDGE, UDATA, sizeof(edata), false);
866 }
867 return g;
868}
869
878
879/* Only used if GV_USE_EXIT not set during exeval.
880 * This implies setjmp/longjmp set up.
881 */
882static void gvexitf(void *env, int v) {
883 gvpr_state_t *st = env;
884
885 longjmp(st->state->jbuf, v);
886}
887
888static void gverrorf(Expr_t *handle, Exdisc_t *discipline, int level,
889 const char *fmt, ...) {
890 va_list ap;
891
892 va_start(ap, fmt);
893 errorv((discipline && handle) ? *((char **)handle) : (char *)handle, level,
894 fmt, ap);
895 va_end(ap);
896
897 if (level >= ERROR_ERROR) {
898 Gpr_t *state = discipline->user;
899 if (state->flags & GV_USE_EXIT)
900 graphviz_exit(1);
901 else if (state->flags & GV_USE_JUMP)
902 longjmp(state->jbuf, 1);
903 }
904}
905
906/* Return 0 on success; non-zero on error.
907 *
908 * FIX/TODO:
909 * - close non-source/non-output graphs
910 * - flag to clone target graph?
911 * - remove assignment in boolean warning if wrapped in ()
912 * - do automatic cast for array indices if type is known
913 * - array initialization
914 */
915static int gvpr_core(int argc, char *argv[], gvpropts *uopts,
916 gvpr_state_t *gs) {
918 int rv = 0;
919
921
922 gs->opts = scanArgs(argc, argv);
923 if (gs->opts.state <= 0) {
924 return gs->opts.state;
925 }
926
927 clock_t start = start_timer();
928 gs->prog = parseProg(gs->opts.program, gs->opts.useFile);
929 if (gs->prog == NULL) {
930 return 1;
931 }
932 info.outFile = gs->opts.outFile;
933 info.args = gs->opts.args;
934 info.errf = gverrorf;
935 info.flags = uopts->flags;
936 if (uopts->flags & GV_USE_EXIT)
937 info.exitf = 0;
938 else
939 info.exitf = gvexitf;
940 gs->state = openGPRState(&info);
941 if (gs->state == NULL) {
942 return 1;
943 }
944 if (uopts->bindings)
945 addBindings(gs->state, uopts->bindings);
946 gs->xprog = compileProg(gs->prog, gs->state, gs->opts.compflags);
947 if (gs->xprog == NULL) {
948 return 1;
949 }
950
951 initGPRState(gs->state);
952
953 if (uopts->flags & GV_USE_OUTGRAPH) {
954 uopts->outgraphs = 0;
955 uopts->n_outgraphs = 0;
956 }
957
958 if (!(uopts->flags & GV_USE_EXIT)) {
959 gs->state->flags |= GV_USE_JUMP;
960 if ((rv = setjmp(gs->state->jbuf))) {
961 return rv;
962 }
963 }
964
965 bool incoreGraphs = uopts->ingraphs;
966
967 if (gs->opts.verbose)
968 fprintf(stderr, "Parse/compile/init: %.2f secs.\n", elapsed_sec(start));
969 /* do begin */
970 if (gs->xprog->begin_stmt != NULL)
971 exeval(gs->xprog->prog, gs->xprog->begin_stmt, gs->state);
972
973 /* if program is not null */
974 if (gs->xprog->uses_graph) {
975 if (uopts->ingraphs)
976 gs->ing = newIngGraphs(0, uopts->ingraphs, ing_read);
977 else
978 gs->ing = newIng(0, gs->opts.inFiles, ing_read);
979
980 start = start_timer();
981 Agraph_t *nextg = NULL;
982 for (gs->state->curgraph = nextGraph(gs->ing); gs->state->curgraph;
983 gs->state->curgraph = nextg) {
984 if (gs->opts.verbose)
985 fprintf(stderr, "Read graph: %.2f secs.\n", elapsed_sec(start));
986 gs->state->infname = fileName(gs->ing);
987 if (gs->opts.readAhead)
988 nextg = gs->state->nextgraph = nextGraph(gs->ing);
989 bool cleanup = false;
990
991 for (size_t i = 0; i < gs->xprog->n_blocks; i++) {
992 comp_block *bp = gs->xprog->blocks + i;
993
994 /* begin graph */
995 if (incoreGraphs && gs->opts.compflags.clone)
996 gs->state->curgraph =
997 (Agraph_t *)cloneO(0, &gs->state->curgraph->base);
998 gs->state->curobj = &gs->state->curgraph->base;
999 gs->state->tvroot = 0;
1000 if (bp->begg_stmt)
1001 exeval(gs->xprog->prog, bp->begg_stmt, gs->state);
1002
1003 /* walk graph */
1004 if (bp->does_walk_graph) {
1005 cleanup = traverse(gs->state, gs->xprog->prog, bp, cleanup);
1006 }
1007 }
1008
1009 /* end graph */
1010 gs->state->curobj = &gs->state->curgraph->base;
1011 if (gs->xprog->endg_stmt != NULL)
1012 exeval(gs->xprog->prog, gs->xprog->endg_stmt, gs->state);
1013 if (gs->opts.verbose)
1014 fprintf(stderr, "Finish graph: %.2f secs.\n", elapsed_sec(start));
1015
1016 /* if $O == $G and $T is empty, delete $T */
1017 if (gs->state->outgraph == gs->state->curgraph &&
1018 gs->state->target != NULL && !agnnodes(gs->state->target))
1019 agdelete(gs->state->curgraph, gs->state->target);
1020
1021 /* output graph, if necessary
1022 * For this, the outgraph must be defined, and either
1023 * be non-empty or the -c option was used.
1024 */
1025 if (gs->state->outgraph != NULL &&
1026 (agnnodes(gs->state->outgraph) || gs->opts.compflags.srcout)) {
1027 if (uopts->flags & GV_USE_OUTGRAPH)
1028 addOutputGraph(gs->state, uopts);
1029 else
1030 sfioWrite(gs->state->outgraph, gs->opts.outFile);
1031 }
1032
1033 if (!incoreGraphs)
1034 chkClose(gs->state->curgraph);
1035 gs->state->target = 0;
1036 gs->state->outgraph = 0;
1037
1038 start = start_timer();
1039 if (!gs->opts.readAhead)
1040 nextg = nextGraph(gs->ing);
1041 if (gs->opts.verbose && nextg != NULL) {
1042 fprintf(stderr, "Read graph: %.2f secs.\n", elapsed_sec(start));
1043 }
1044 }
1045 }
1046
1047 /* do end */
1048 gs->state->curgraph = 0;
1049 gs->state->curobj = 0;
1050 if (gs->xprog->end_stmt != NULL)
1051 exeval(gs->xprog->prog, gs->xprog->end_stmt, gs->state);
1052
1053 return 0;
1054}
1055
1060int gvpr(int argc, char *argv[], gvpropts *uopts) {
1061 gvpr_state_t gvpr_state = {0};
1062
1063 // initialize opts to something that makes freeOpts() a no-op if we fail early
1064 gvpr_state.opts.outFile = stdout;
1065
1066 gvpropts DEFAULT_OPTS = {0};
1067 if (uopts == NULL) {
1068 uopts = &DEFAULT_OPTS;
1069 }
1070
1071 int rv = gvpr_core(argc, argv, uopts, &gvpr_state);
1072
1073 // free all allocated resources
1074 freeParseProg(gvpr_state.prog);
1075 freeCompileProg(gvpr_state.xprog);
1076 closeGPRState(gvpr_state.state);
1077 if (gvpr_state.ing != NULL) {
1078 closeIngraph(gvpr_state.ing);
1079 }
1080 freeOpts(gvpr_state.opts);
1081
1082 return rv;
1083}
1084
Agobj_t * cloneO(Agraph_t *g, Agobj_t *obj)
Definition actions.c:344
int sfioWrite(Agraph_t *g, FILE *fp)
Definition actions.c:538
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: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: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:1936
static long seed
Definition exeval.c:1008
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:32
void addBindings(Gpr_t *state, gvprbinding *bindings)
Definition gprstate.c:90
Gpr_t * openGPRState(gpr_info *info)
Definition gprstate.c:36
void closeGPRState(Gpr_t *state)
Definition gprstate.c:118
@ 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:157
#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: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
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
@ 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:55
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:882
int gvpr(int argc, char *argv[], gvpropts *uopts)
Definition gvpr.c:1060
static void chkClose(Agraph_t *g)
Definition gvpr.c:850
static void travNodes(Gpr_t *state, Expr_t *prog, comp_block *xprog)
Definition gvpr.c:668
static void doCleanup(Agraph_t *g)
reset node traversal data
Definition gvpr.c:713
#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:725
#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 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:588
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:693
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:678
static Agraph_t * ing_read(const char *filename, void *fp)
Definition gvpr.c:860
static Agnode_t * nextNode(Gpr_t *state, nodestream *nodes)
Definition gvpr.c:494
static void addOutputGraph(Gpr_t *state, gvpropts *uopts)
Definition gvpr.c:838
static int gvpr_core(int argc, char *argv[], gvpropts *uopts, gvpr_state_t *gs)
Definition gvpr.c:915
static int doFlags(char *arg, int argi, int argc, char **argv, options *opts)
Definition gvpr.c:320
static void gverrorf(Expr_t *handle, Exdisc_t *discipline, int level, const char *fmt,...)
Definition gvpr.c:888
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: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: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
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
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:871
parse_prog * prog
Definition gvpr.c:872
comp_prog * xprog
Definition gvpr.c:874
options opts
Definition gvpr.c:876
ingraph_state * ing
Definition gvpr.c:873
Gpr_t * state
Definition gvpr.c:875
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