Graphviz 13.1.3~dev.20250829.0113
Loading...
Searching...
No Matches
parse.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 * Top-level parsing of gpr code into blocks
13 *
14 */
15
16#include <ast/ast.h>
17#include <ast/error.h>
18#include <gvpr/parse.h>
19#include <stdbool.h>
20#include <stdio.h>
21#include <stdlib.h>
22#include <string.h>
23#include <util/agxbuf.h>
24#include <util/alloc.h>
25#include <util/gv_ctype.h>
26#include <util/list.h>
27#include <util/unreachable.h>
28
29static int lineno = 1; /* current line number */
30static int col0 = 1; /* true if char ptr is at column 0 */
31static int startLine = 1; /* set to start line of bracketd content */
32static int kwLine = 1; /* set to line of keyword */
33
34static char *case_str[] = {
35 "BEGIN", "END", "BEG_G", "END_G", "N", "E", "EOF", "ERROR",
36};
37
39static char *caseStr(case_t cs) { return case_str[(int)cs]; }
40
42static int eol(FILE *str) {
43 int c;
44 while ((c = getc(str)) != '\n') {
45 if (c < 0)
46 return c;
47 }
48 lineno++;
49 col0 = 1;
50 return c;
51}
52
53/* return character from input stream
54 * while keeping track of line number.
55 * Strip out comments, and return space or newline.
56 * If a newline is seen in comment and ostr
57 * is non-null, add newline to ostr.
58 */
59static int readc(FILE *str, agxbuf *ostr) {
60 int c;
61 int cc;
62
63 switch (c = getc(str)) {
64 case '\n':
65 lineno++;
66 col0 = 1;
67 break;
68 case '#':
69 if (col0) { /* shell comment */
70 c = eol(str);
71 } else
72 col0 = 0;
73 break;
74 case '/':
75 cc = getc(str);
76 switch (cc) {
77 case '*': /* in C comment */
78 while (1) {
79 switch (c = getc(str)) {
80 case '\n':
81 lineno++;
82 if (ostr)
83 agxbputc(ostr, (char)c);
84 break;
85 case '*':
86 switch (cc = getc(str)) {
87 case -1:
88 return cc;
89 break;
90 case '\n':
91 lineno++;
92 if (ostr)
93 agxbputc(ostr, (char)cc);
94 break;
95 case '*':
96 ungetc(cc, str);
97 break;
98 case '/':
99 col0 = 0;
100 return ' ';
101 break;
102 }
103 }
104 }
105 break;
106 case '/': /* in C++ comment */
107 c = eol(str);
108 break;
109 default: /* not a comment */
110 if (cc >= '\0')
111 ungetc(cc, str);
112 break;
113 }
114 break;
115 default:
116 col0 = 0;
117 break;
118 }
119 return c;
120}
121
123static void unreadc(FILE *str, int c) {
124 ungetc(c, str);
125 if (c == '\n')
126 lineno--;
127}
128
129static int skipWS(FILE *str) {
130 int c;
131
132 while (true) {
133 c = readc(str, 0);
134 if (!gv_isspace(c)) {
135 return c;
136 }
137 }
138}
139
141static void parseID(FILE *str, int c, char *buf, size_t bsize) {
142 char *ptr = buf;
143 char *eptr = buf + (bsize - 1);
144
145 *ptr++ = (char)c;
146 while (true) {
147 c = readc(str, 0);
148 if (c < 0)
149 break;
150 if (gv_isalpha(c) || c == '_') {
151 if (ptr == eptr)
152 break;
153 *ptr++ = (char)c;
154 } else {
155 unreadc(str, c);
156 break;
157 }
158 }
159 *ptr = '\0';
160}
161
162#define BSIZE 8
163
164/* Look for keywords: BEGIN, END, BEG_G, END_G, N, E
165 * As side-effect, sets kwLine to line of keyword.
166 */
167static case_t parseKind(FILE *str) {
168 int c;
169 char buf[BSIZE];
170 case_t cs = Error;
171
172 c = skipWS(str);
173 if (c < 0)
174 return Eof;
175 if (!gv_isalpha(c)) {
176 error(ERROR_ERROR, "expected keyword BEGIN/END/N/E...; found '%c', line %d",
177 c, lineno);
178 return Error;
179 }
180
181 kwLine = lineno;
182 parseID(str, c, buf, BSIZE);
183 if (strcmp(buf, "BEGIN") == 0) {
184 cs = Begin;
185 } else if (strcmp(buf, "BEG_G") == 0) {
186 cs = BeginG;
187 } else if (strcmp(buf, "E") == 0) {
188 cs = Edge;
189 } else if (strcmp(buf, "END") == 0) {
190 cs = End;
191 } else if (strcmp(buf, "END_G") == 0) {
192 cs = EndG;
193 } else if (strcmp(buf, "N") == 0) {
194 cs = Node;
195 }
196 if (cs == Error)
197 error(ERROR_ERROR, "unexpected keyword \"%s\", line %d", buf, kwLine);
198 return cs;
199}
200
201/* eat characters from ins, putting them into outs,
202 * up to and including a terminating character ec
203 * that is not escaped with a back quote.
204 */
205static int endString(FILE *ins, agxbuf *outs, char ec) {
206 int sline = lineno;
207 int c;
208
209 while ((c = getc(ins)) != ec) {
210 if (c == '\\') {
211 agxbputc(outs, (char)c);
212 c = getc(ins);
213 }
214 if (c < 0) {
215 error(ERROR_ERROR, "unclosed string, start line %d", sline);
216 return c;
217 }
218 if (c == '\n')
219 lineno++;
220 agxbputc(outs, (char)c);
221 }
222 agxbputc(outs, (char)c);
223 return 0;
224}
225
226/* eat characters from ins, putting them into outs,
227 * up to a terminating character ec.
228 * Strings are treated as atomic units: any ec in them
229 * is ignored. Since matching bc-ec pairs might nest,
230 * the function is called recursively.
231 */
232static int endBracket(FILE *ins, agxbuf *outs, char bc, char ec) {
233 int c;
234
235 while (true) {
236 c = readc(ins, outs);
237 if (c < 0 || c == ec)
238 return c;
239 else if (c == bc) {
240 agxbputc(outs, (char)c);
241 c = endBracket(ins, outs, bc, ec);
242 if (c < 0)
243 return c;
244 else
245 agxbputc(outs, (char)c);
246 } else if (c == '\'' || c == '"') {
247 agxbputc(outs, (char)c);
248 if (endString(ins, outs, (char)c))
249 return -1;
250 } else
251 agxbputc(outs, (char)c);
252 }
253}
254
255/* parse paired expression : bc <string> ec
256 * returning <string>
257 * As a side-effect, set startLine to beginning of content.
258 */
259static char *parseBracket(FILE *str, agxbuf *buf, int bc, int ec) {
260 int c;
261
262 c = skipWS(str);
263 if (c < 0)
264 return 0;
265 if (c != bc) {
266 unreadc(str, c);
267 return 0;
268 }
270 c = endBracket(str, buf, bc, (char)ec);
271 if (c < 0) {
272 if (!getErrorErrors())
273 error(ERROR_ERROR, "unclosed bracket %c%c expression, start line %d", bc,
274 ec, startLine);
275 return 0;
276 } else
277 return agxbdisown(buf);
278}
279
280static char *parseAction(FILE *str, agxbuf *buf) {
281 return parseBracket(str, buf, '{', '}');
282}
283
284static char *parseGuard(FILE *str, agxbuf *buf) {
285 return parseBracket(str, buf, '[', ']');
286}
287
288/* Recognize
289 * BEGIN <optional action>
290 * END <optional action>
291 * BEG_G <optional action>
292 * END_G <optional action>
293 * N <optional guard> <optional action>
294 * E <optional guard> <optional action>
295 * where
296 * guard = '[' <expr> ']'
297 * action = '{' <expr> '}'
298 */
299static case_t parseCase(FILE *str, char **guard, int *gline, char **action,
300 int *aline) {
301 case_t kind;
302
303 agxbuf buf = {0};
304
305 kind = parseKind(str);
306 switch (kind) {
307 case Begin:
308 case BeginG:
309 case End:
310 case EndG:
311 *action = parseAction(str, &buf);
312 *aline = startLine;
313 if (getErrorErrors())
314 kind = Error;
315 break;
316 case Edge:
317 case Node:
318 *guard = parseGuard(str, &buf);
319 *gline = startLine;
320 if (!getErrorErrors()) {
321 *action = parseAction(str, &buf);
322 *aline = startLine;
323 }
324 if (getErrorErrors())
325 kind = Error;
326 break;
327 case Eof:
328 case Error: /* to silence warnings */
329 break;
330 default:
331 UNREACHABLE();
332 }
333
334 agxbfree(&buf);
335 return kind;
336}
337
339static void addBlock(parse_blocks_t *list, char *stmt, int line,
340 case_infos_t nodelist, case_infos_t edgelist) {
341 parse_block item = {0};
342
343 item.l_beging = line;
344 item.begg_stmt = stmt;
345 item.node_stmts = nodelist;
346 item.edge_stmts = edgelist;
347
348 LIST_APPEND(list, item);
349}
350
352static void addCase(case_infos_t *list, char *guard, int gline, char *action,
353 int line) {
354 if (!guard && !action) {
356 "Case with neither guard nor action, line %d - ignored", kwLine);
357 return;
358 }
359
360 case_info item = {.guard = guard, .action = action};
361 if (guard)
362 item.gstart = gline;
363 if (action)
364 item.astart = line;
365
366 LIST_APPEND(list, item);
367}
368
369static void bindAction(case_t cs, char *action, int aline, char **ap, int *lp) {
370 if (!action)
371 error(ERROR_WARNING, "%s with no action, line %d - ignored", caseStr(cs),
372 kwLine);
373 else if (*ap)
374 error(ERROR_ERROR, "additional %s section, line %d", caseStr(cs), kwLine);
375 else {
376 *ap = action;
377 *lp = aline;
378 }
379}
380
381static void free_case_info(case_info c) {
382 free(c.guard);
383 free(c.action);
384}
385
387parse_prog *parseProg(char *input, int isFile) {
388 FILE *str;
389 char *guard = NULL;
390 char *action = NULL;
391 bool more;
392 parse_blocks_t blocklist = {0};
393 case_infos_t edgelist = {.dtor = free_case_info};
394 case_infos_t nodelist = {.dtor = free_case_info};
395 int line = 0, gline = 0;
396 int l_beging = 0;
397 char *begg_stmt;
398
399 lineno = col0 = startLine = kwLine = 1;
400 parse_prog *prog = calloc(1, sizeof(parse_prog));
401 if (!prog) {
402 error(ERROR_ERROR, "parseProg: out of memory");
403 return NULL;
404 }
405
406 if (isFile) {
407 str = fopen(input, "r");
408 prog->source = input;
409
410 } else {
411 str = tmpfile();
412 if (str != NULL) {
413 fputs(input, str);
414 rewind(str);
415 }
416 prog->source = NULL; /* command line */
417 }
418
419 if (!str) {
420 if (isFile)
421 error(ERROR_ERROR, "could not open %s for reading", input);
422 else
423 error(ERROR_ERROR, "parseProg : unable to create sfio stream");
424 free(prog);
425 return NULL;
426 }
427
428 begg_stmt = NULL;
429 more = true;
430 while (more) {
431 switch (parseCase(str, &guard, &gline, &action, &line)) {
432 case Begin:
433 bindAction(Begin, action, line, &prog->begin_stmt, &prog->l_begin);
434 break;
435 case BeginG:
436 if (action && (begg_stmt || !LIST_IS_EMPTY(&nodelist) ||
437 !LIST_IS_EMPTY(&edgelist))) { // non-empty block
438 addBlock(&blocklist, begg_stmt, l_beging, nodelist, edgelist);
439
440 /* reset values */
441 edgelist = (case_infos_t){.dtor = free_case_info};
442 nodelist = (case_infos_t){.dtor = free_case_info};
443 begg_stmt = NULL;
444 }
445 bindAction(BeginG, action, line, &begg_stmt, &l_beging);
446 break;
447 case End:
448 bindAction(End, action, line, &prog->end_stmt, &prog->l_end);
449 break;
450 case EndG:
451 bindAction(EndG, action, line, &prog->endg_stmt, &prog->l_endg);
452 break;
453 case Eof:
454 more = false;
455 break;
456 case Node:
457 addCase(&nodelist, guard, gline, action, line);
458 break;
459 case Edge:
460 addCase(&edgelist, guard, gline, action, line);
461 break;
462 case Error: /* to silence warnings */
463 more = false;
464 break;
465 default:
466 UNREACHABLE();
467 }
468 }
469
470 if (begg_stmt || !LIST_IS_EMPTY(&nodelist) ||
471 !LIST_IS_EMPTY(&edgelist)) { // non-empty block
472 addBlock(&blocklist, begg_stmt, l_beging, nodelist, edgelist);
473 }
474
475 prog->blocks = blocklist;
476
477 fclose(str);
478
479 if (getErrorErrors()) {
480 freeParseProg(prog);
481 prog = NULL;
482 }
483
484 return prog;
485}
486
487static void freeBlocks(parse_blocks_t *ip) {
488 for (size_t i = 0; i < LIST_SIZE(ip); ++i) {
489 parse_block p = LIST_GET(ip, i);
490 free(p.begg_stmt);
493 }
494 LIST_FREE(ip);
495}
496
498 if (!prog)
499 return;
500 free(prog->begin_stmt);
501 freeBlocks(&prog->blocks);
502 free(prog->endg_stmt);
503 free(prog->end_stmt);
504 free(prog);
505}
static void agxbfree(agxbuf *xb)
free any malloced resources
Definition agxbuf.h:77
static int agxbputc(agxbuf *xb, char c)
add character to buffer
Definition agxbuf.h:276
static char * agxbdisown(agxbuf *xb)
Definition agxbuf.h:326
Memory allocation wrappers that exit on failure.
static void ins(Dict_t *d, Dtlink_t **set, Agedge_t *e)
Definition edge.c:149
Dt_t edgelist
Definition edgelist.h:24
int getErrorErrors(void)
Definition error.c:31
#define ERROR_WARNING
Definition error.h:35
#define ERROR_ERROR
Definition error.h:36
static snode guard
Definition fPQ.c:21
void free(void *)
node NULL
Definition grammar.y:181
replacements for ctype.h functions
static bool gv_isalpha(int c)
Definition gv_ctype.h:29
static bool gv_isspace(int c)
Definition gv_ctype.h:55
textitem scanner parser str
Definition htmlparse.y:218
table Syntax error
Definition htmlparse.y:288
type-generic dynamically expanding list
#define LIST_SIZE(list)
Definition list.h:80
#define LIST_APPEND(list, item)
Definition list.h:132
#define LIST_FREE(list)
Definition list.h:379
#define LIST_IS_EMPTY(list)
Definition list.h:90
#define LIST_GET(list, index)
Definition list.h:165
static char * parseGuard(FILE *str, agxbuf *buf)
Definition parse.c:284
void freeParseProg(parse_prog *prog)
Definition parse.c:497
static int col0
Definition parse.c:30
static char * parseBracket(FILE *str, agxbuf *buf, int bc, int ec)
Definition parse.c:259
static void addBlock(parse_blocks_t *list, char *stmt, int line, case_infos_t nodelist, case_infos_t edgelist)
create new block and append to list; return new item as tail
Definition parse.c:339
static void unreadc(FILE *str, int c)
push character back onto stream; if newline, reduce lineno
Definition parse.c:123
#define BSIZE
Definition parse.c:162
static void free_case_info(case_info c)
Definition parse.c:381
static case_t parseKind(FILE *str)
Definition parse.c:167
static int lineno
Definition parse.c:29
static int readc(FILE *str, agxbuf *ostr)
Definition parse.c:59
static void parseID(FILE *str, int c, char *buf, size_t bsize)
Put initial alpha in buffer; add additional alphas, up to buffer size.
Definition parse.c:141
static int endString(FILE *ins, agxbuf *outs, char ec)
Definition parse.c:205
static char * parseAction(FILE *str, agxbuf *buf)
Definition parse.c:280
static char * case_str[]
Definition parse.c:34
static case_t parseCase(FILE *str, char **guard, int *gline, char **action, int *aline)
Definition parse.c:299
static int eol(FILE *str)
eat characters until eol
Definition parse.c:42
static int startLine
Definition parse.c:31
static void addCase(case_infos_t *list, char *guard, int gline, char *action, int line)
create new case_info and append to list
Definition parse.c:352
static void freeBlocks(parse_blocks_t *ip)
Definition parse.c:487
static char * caseStr(case_t cs)
convert case_t to string
Definition parse.c:39
static int endBracket(FILE *ins, agxbuf *outs, char bc, char ec)
Definition parse.c:232
parse_prog * parseProg(char *input, int isFile)
parses input into gpr sections
Definition parse.c:387
static void bindAction(case_t cs, char *action, int aline, char **ap, int *lp)
Definition parse.c:369
static int kwLine
Definition parse.c:32
static int skipWS(FILE *str)
Definition parse.c:129
case_t
Definition parse.h:19
@ Error
Definition parse.h:20
@ End
Definition parse.h:20
@ Eof
Definition parse.h:20
@ Begin
Definition parse.h:19
@ BeginG
Definition parse.h:20
@ EndG
Definition parse.h:20
Definition edges.h:25
Definition node.h:24
char * guard
Definition parse.h:24
char * action
Definition parse.h:26
Definition cdt.h:100
Definition utils.c:751
case_infos_t node_stmts
Definition parse.h:34
case_infos_t edge_stmts
Definition parse.h:35
char * begg_stmt
Definition parse.h:33
int l_end
Definition parse.h:42
int l_begin
Definition parse.h:42
int l_endg
Definition parse.h:42
char * end_stmt
Definition parse.h:46
char * endg_stmt
Definition parse.h:45
char * begin_stmt
Definition parse.h:43
parse_blocks_t blocks
Definition parse.h:44
char * source
Definition parse.h:41
#define UNREACHABLE()
Definition unreachable.h:30