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