Graphviz 14.1.1~dev.20251208.1034
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 parse_blocks_t blocklist = {0};
390 case_infos_t edgelist = {.dtor = free_case_info};
391 case_infos_t nodelist = {.dtor = free_case_info};
392 int l_beging = 0;
393
394 lineno = col0 = startLine = kwLine = 1;
395 parse_prog *prog = calloc(1, sizeof(parse_prog));
396 if (!prog) {
397 error(ERROR_ERROR, "parseProg: out of memory");
398 return NULL;
399 }
400
401 if (isFile) {
402 str = fopen(input, "r");
403 prog->source = input;
404
405 } else {
406 str = tmpfile();
407 if (str != NULL) {
408 fputs(input, str);
409 rewind(str);
410 }
411 prog->source = NULL; /* command line */
412 }
413
414 if (!str) {
415 if (isFile)
416 error(ERROR_ERROR, "could not open %s for reading", input);
417 else
418 error(ERROR_ERROR, "parseProg : unable to create sfio stream");
419 free(prog);
420 return NULL;
421 }
422
423 char *begg_stmt = NULL;
424 for (bool more = true; more;) {
425 char *guard = NULL;
426 int gline = 0;
427 char *action = NULL;
428 int line = 0;
429 switch (parseCase(str, &guard, &gline, &action, &line)) {
430 case Begin:
431 bindAction(Begin, action, line, &prog->begin_stmt, &prog->l_begin);
432 action = NULL;
433 break;
434 case BeginG:
435 if (action && (begg_stmt || !LIST_IS_EMPTY(&nodelist) ||
436 !LIST_IS_EMPTY(&edgelist))) { // non-empty block
437 addBlock(&blocklist, begg_stmt, l_beging, nodelist, edgelist);
438
439 /* reset values */
440 edgelist = (case_infos_t){.dtor = free_case_info};
441 nodelist = (case_infos_t){.dtor = free_case_info};
442 begg_stmt = NULL;
443 }
444 bindAction(BeginG, action, line, &begg_stmt, &l_beging);
445 action = NULL;
446 break;
447 case End:
448 bindAction(End, action, line, &prog->end_stmt, &prog->l_end);
449 action = NULL;
450 break;
451 case EndG:
452 bindAction(EndG, action, line, &prog->endg_stmt, &prog->l_endg);
453 action = NULL;
454 break;
455 case Eof:
456 more = false;
457 break;
458 case Node:
459 addCase(&nodelist, guard, gline, action, line);
460 guard = NULL;
461 action = NULL;
462 break;
463 case Edge:
464 addCase(&edgelist, guard, gline, action, line);
465 guard = NULL;
466 action = NULL;
467 break;
468 case Error: /* to silence warnings */
469 more = false;
470 break;
471 default:
472 UNREACHABLE();
473 }
474 free(guard);
475 free(action);
476 }
477
478 if (begg_stmt || !LIST_IS_EMPTY(&nodelist) ||
479 !LIST_IS_EMPTY(&edgelist)) { // non-empty block
480 addBlock(&blocklist, begg_stmt, l_beging, nodelist, edgelist);
481 }
482
483 prog->blocks = blocklist;
484
485 fclose(str);
486
487 if (getErrorErrors()) {
488 freeParseProg(prog);
489 prog = NULL;
490 }
491
492 return prog;
493}
494
495static void freeBlocks(parse_blocks_t *ip) {
496 for (size_t i = 0; i < LIST_SIZE(ip); ++i) {
497 parse_block p = LIST_GET(ip, i);
498 free(p.begg_stmt);
501 }
502 LIST_FREE(ip);
503}
504
506 if (!prog)
507 return;
508 free(prog->begin_stmt);
509 freeBlocks(&prog->blocks);
510 free(prog->endg_stmt);
511 free(prog->end_stmt);
512 free(prog);
513}
Dynamically expanding string buffers.
static void agxbfree(agxbuf *xb)
free any malloced resources
Definition agxbuf.h:97
static int agxbputc(agxbuf *xb, char c)
add character to buffer
Definition agxbuf.h:295
static char * agxbdisown(agxbuf *xb)
Definition agxbuf.h:345
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:120
#define LIST_FREE(list)
Definition list.h:370
#define LIST_IS_EMPTY(list)
Definition list.h:90
#define LIST_GET(list, index)
Definition list.h:155
static char * parseGuard(FILE *str, agxbuf *buf)
Definition parse.c:284
void freeParseProg(parse_prog *prog)
Definition parse.c:505
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:495
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:23
Definition node.h:24
char * guard
Definition parse.h:24
char * action
Definition parse.h:26
Definition cdt.h:98
Definition utils.c:750
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