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