Line data Source code
1 : // wok_sexpr -- see wok_sexpr.h for the format and the out_src contract.
2 : //
3 : // Every traversal here walks wok_node_desc[]; the only per-node-CLASS code
4 : // is the seven-way switch on WokFieldClass (NODE/OPT/SEQ/NAME/TEXT/INT/FLAG),
5 : // which is exactly the "generic" part of "generic dump and reader" -- adding
6 : // a wok AST node never touches this file, only adding a new field CLASS
7 : // would.
8 :
9 : // This include a string buffer here.
10 :
11 : // The prelude comes FIRST: it carries the POSIX feature-test macros, which
12 : // have no effect once a system header has been read. wok_base.h hard-errors
13 : // if it is reached too late.
14 : #include "wok_base.h"
15 :
16 : #include "wok_sexpr.h"
17 :
18 : #include <inttypes.h>
19 : #include <stdint.h>
20 : #include <string.h>
21 :
22 : // Real C call-stack depth, in both directions, is bounded by this: a node
23 : // recurses into its NODE/OPT/SEQ fields, which recurse into their nodes, and
24 : // so on. Past this depth we report rather than risk overflowing the stack.
25 : #define WOK_SEXPR_MAX_DEPTH 256
26 :
27 : // =========================================================================
28 : // dump
29 : // =========================================================================
30 :
31 : // Both public dump entry points write through this so they are, by
32 : // construction, the same algorithm: one to a FILE (stdio does its own
33 : // buffering), one to an arena-backed growable buffer (doubling, like
34 : // WokNodeBuf in wok_ast.c -- there is no realloc in an arena, so old copies
35 : // are simply abandoned garbage, which is what an arena is for).
36 : typedef struct {
37 : bool to_file;
38 : FILE *file;
39 : WokArena *arena;
40 : char *buf;
41 : usize len, cap;
42 : } WokSexprSink;
43 :
44 14211926 : static void sink_reserve(WokSexprSink *s, usize extra) {
45 14211926 : if (s->to_file) return;
46 14211926 : if (s->len + extra <= s->cap) return;
47 88864 : usize new_cap = s->cap ? s->cap * 2 : 128;
48 88864 : while (new_cap < s->len + extra) new_cap *= 2;
49 88864 : char *grown = WOK_NEW_N(s->arena, char, new_cap);
50 88864 : if (s->len) memcpy(grown, s->buf, s->len);
51 88864 : s->buf = grown;
52 88864 : s->cap = new_cap;
53 : }
54 :
55 4900523 : static void sink_put_char(WokSexprSink *s, char c) {
56 4900523 : if (s->to_file) {
57 209 : (void)fputc(c, s->file);
58 209 : return;
59 : }
60 4900314 : sink_reserve(s, 1);
61 4900314 : s->buf[s->len++] = c;
62 : }
63 :
64 9284164 : static void sink_put_str(WokSexprSink *s, const char *str, usize n) {
65 9284164 : if (s->to_file) {
66 363 : (void)fwrite(str, 1, n, s->file);
67 363 : return;
68 : }
69 9283801 : sink_reserve(s, n);
70 9283801 : memcpy(s->buf + s->len, str, n);
71 9283801 : s->len += n;
72 : }
73 :
74 : static void render_node(WokSexprSink *sink, const char *src, const WokNode *n,
75 : u32 depth);
76 :
77 675637 : static bool tag_is_block(WokTag t) {
78 675637 : const WokNodeDesc *d = &wok_node_desc[t];
79 1210606 : for (u16 i = 0; i < d->nfields; i++) {
80 965251 : WokFieldClass c = d->fields[i].cls;
81 965251 : if (c == WFC_NODE || c == WFC_OPT || c == WFC_SEQ) return true;
82 : }
83 : return false;
84 : }
85 :
86 1204150 : static void emit_indent(WokSexprSink *sink, u32 depth) {
87 9210283 : for (u32 i = 0; i < depth; i++) sink_put_str(sink, " ", 2);
88 1204150 : }
89 :
90 360092 : static void render_string(WokSexprSink *sink, const char *src, WokSpan span) {
91 360092 : sink_put_char(sink, '"');
92 1334084 : for (u32 i = 0; i < span.len; i++) {
93 973992 : unsigned char c = (unsigned char)src[span.off + i];
94 973992 : switch (c) {
95 7475 : case '\\':
96 7475 : sink_put_str(sink, "\\\\", 2);
97 7475 : break;
98 25370 : case '"':
99 25370 : sink_put_str(sink, "\\\"", 2);
100 25370 : break;
101 2 : case '\n':
102 2 : sink_put_str(sink, "\\n", 2);
103 2 : break;
104 2 : case '\t':
105 2 : sink_put_str(sink, "\\t", 2);
106 2 : break;
107 0 : case '\r':
108 0 : sink_put_str(sink, "\\r", 2);
109 0 : break;
110 941143 : default:
111 941143 : if (c < 0x20) {
112 2 : char buf[5];
113 2 : (void)snprintf(buf, sizeof buf, "\\x%02X", (unsigned)c);
114 2 : sink_put_str(sink, buf, 4);
115 : } else {
116 941141 : sink_put_char(sink, (char)c);
117 : }
118 : break;
119 : }
120 : }
121 360092 : sink_put_char(sink, '"');
122 360092 : }
123 :
124 46735 : static void render_uint(WokSexprSink *sink, u64 v) {
125 46735 : char buf[32];
126 46735 : int len = snprintf(buf, sizeof buf, "%" PRIu64, v);
127 46735 : sink_put_str(sink, buf, (usize)len);
128 46735 : }
129 :
130 : // The one place a schema violation (a required NODE field left null) is
131 : // caught: it cannot be dumped correctly, so this reports it and emits a
132 : // placeholder that will, correctly, fail to read back.
133 351385 : static void render_node_or_null(WokSexprSink *sink, const char *src,
134 : const char *parent_tag, const char *field,
135 : const WokNode *child, u32 depth) {
136 351385 : if (child == nullptr) {
137 0 : (void)fprintf(stderr,
138 : "wok_sexpr: dump error: %s.%s is a null NODE field\n",
139 : parent_tag, field);
140 0 : sink_put_str(sink, "(!null)", 7);
141 0 : return;
142 : }
143 351385 : render_node(sink, src, child, depth);
144 : }
145 :
146 22281 : static void render_opt(WokSexprSink *sink, const char *src,
147 : const WokNode *child, u32 depth) {
148 22281 : if (child == nullptr) {
149 7975 : sink_put_str(sink, "(none)", 6);
150 7975 : return;
151 : }
152 14306 : sink_put_str(sink, "(some", 5);
153 14306 : sink_put_char(sink, '\n');
154 14306 : emit_indent(sink, depth + 1);
155 14306 : render_node(sink, src, child, depth + 1);
156 14306 : sink_put_char(sink, ')');
157 : }
158 :
159 273403 : static void render_seq(WokSexprSink *sink, const char *src, WokSeq seq,
160 : u32 depth) {
161 273403 : if (seq.n == 0) {
162 72427 : sink_put_str(sink, "(seq)", 5);
163 72427 : return;
164 : }
165 200976 : sink_put_str(sink, "(seq", 4);
166 516795 : for (u32 i = 0; i < seq.n; i++) {
167 315819 : sink_put_char(sink, '\n');
168 315819 : emit_indent(sink, depth + 1);
169 315819 : render_node(sink, src, seq.items[i], depth + 1);
170 : }
171 200976 : sink_put_char(sink, ')');
172 : }
173 :
174 1247334 : static void render_field(WokSexprSink *sink, const char *src,
175 : const char *parent_tag, const char *field_name,
176 : WokFieldClass cls, WokSlot slot, u32 depth) {
177 1247334 : switch (cls) {
178 351385 : case WFC_NODE:
179 351385 : render_node_or_null(sink, src, parent_tag, field_name, slot.node,
180 : depth);
181 351385 : return;
182 22281 : case WFC_OPT:
183 22281 : render_opt(sink, src, slot.node, depth);
184 22281 : return;
185 273403 : case WFC_SEQ:
186 273403 : render_seq(sink, src, wok_seq_unpack(slot.seq), depth);
187 273403 : return;
188 360092 : case WFC_NAME:
189 : case WFC_TEXT:
190 360092 : render_string(sink, src, slot.span);
191 360092 : return;
192 46735 : case WFC_INT:
193 46735 : render_uint(sink, slot.num);
194 46735 : return;
195 193438 : case WFC_FLAG:
196 193438 : sink_put_str(sink, slot.flag ? "#t" : "#f", 2);
197 193438 : return;
198 : case WOK_FIELD_CLASS_COUNT:
199 : break;
200 : }
201 0 : WOK_UNREACHABLE();
202 : }
203 :
204 709323 : static void render_node(WokSexprSink *sink, const char *src, const WokNode *n,
205 : u32 depth) {
206 709323 : if (depth > WOK_SEXPR_MAX_DEPTH) {
207 0 : (void)fprintf(stderr,
208 : "wok_sexpr: dump error: nesting exceeds %d at tag %s\n",
209 0 : WOK_SEXPR_MAX_DEPTH, wok_node_desc[n->tag].tag);
210 0 : sink_put_str(sink, "(!depth)", 8);
211 0 : return;
212 : }
213 :
214 709323 : const WokNodeDesc *d = &wok_node_desc[n->tag];
215 709323 : sink_put_char(sink, '(');
216 709323 : sink_put_str(sink, d->tag, strlen(d->tag));
217 :
218 709323 : if (d->nfields == 0) {
219 33686 : sink_put_char(sink, ')');
220 33686 : return;
221 : }
222 :
223 675637 : if (!tag_is_block(n->tag)) {
224 618664 : for (u16 i = 0; i < d->nfields; i++) {
225 373309 : sink_put_char(sink, ' ');
226 373309 : render_field(sink, src, d->tag, d->fields[i].name, d->fields[i].cls,
227 : n->slot[i], depth);
228 : }
229 245355 : sink_put_char(sink, ')');
230 245355 : return;
231 : }
232 :
233 1304307 : for (u16 i = 0; i < d->nfields; i++) {
234 874025 : sink_put_char(sink, '\n');
235 874025 : emit_indent(sink, depth + 1);
236 874025 : render_field(sink, src, d->tag, d->fields[i].name, d->fields[i].cls,
237 : n->slot[i], depth + 1);
238 : }
239 430282 : sink_put_char(sink, ')');
240 : }
241 :
242 2 : void wok_sexpr_dump(const WokNode *node, const char *src, FILE *out) {
243 2 : WokSexprSink sink = {.to_file = true, .file = out};
244 2 : render_node(&sink, src, node, 1);
245 2 : (void)fputc('\n', out);
246 2 : }
247 :
248 27811 : char *wok_sexpr_dump_string(const WokNode *node, const char *src,
249 : WokArena *arena) {
250 27811 : WokSexprSink sink = {.to_file = false, .arena = arena};
251 27811 : render_node(&sink, src, node, 1);
252 27811 : sink_put_char(&sink, '\n');
253 27811 : sink_reserve(&sink, 1);
254 27811 : sink.buf[sink.len] = '\0';
255 27811 : return sink.buf;
256 : }
257 :
258 : // =========================================================================
259 : // read
260 : // =========================================================================
261 :
262 : // The decoded-text pool: NAME/TEXT spans in the returned tree index this,
263 : // not the dump text (see wok_sexpr.h). Same doubling-in-the-arena growth as
264 : // the dump-side sink above; kept separate because it holds decoded bytes
265 : // under a different lifetime story (it outlives the read, the sink does
266 : // not).
267 : typedef struct {
268 : WokArena *arena;
269 : char *data;
270 : usize len, cap;
271 : } WokByteBuf;
272 :
273 350068 : static void bytebuf_reserve(WokByteBuf *b, usize extra) {
274 350068 : if (b->len + extra <= b->cap) return;
275 11630 : usize new_cap = b->cap ? b->cap * 2 : 128;
276 11630 : while (new_cap < b->len + extra) new_cap *= 2;
277 11630 : char *grown = WOK_NEW_N(b->arena, char, new_cap);
278 11630 : if (b->len) memcpy(grown, b->data, b->len);
279 11630 : b->data = grown;
280 11630 : b->cap = new_cap;
281 : }
282 :
283 339115 : static void bytebuf_put(WokByteBuf *b, char c) {
284 339115 : bytebuf_reserve(b, 1);
285 339115 : b->data[b->len++] = c;
286 339115 : }
287 :
288 : typedef struct {
289 : const char *text;
290 : usize n;
291 : usize pos;
292 : WokDiagSink *diag;
293 : WokArena *arena;
294 : WokByteBuf pool;
295 : } WokReader;
296 :
297 13468110 : static bool at_eof(const WokReader *r) { return r->pos >= r->n; }
298 10946352 : static bool is_ws(char c) { return c == ' ' || c == '\t' || c == '\n' || c == '\r'; }
299 2617859 : static bool is_delim(char c) { return c == '(' || c == ')' || c == '"' || is_ws(c); }
300 :
301 2304686 : static void skip_ws(WokReader *r) {
302 8439773 : while (!at_eof(r) && is_ws(r->text[r->pos])) r->pos++;
303 2304686 : }
304 :
305 892039 : static bool peek_is(WokReader *r, char c) {
306 892039 : skip_ws(r);
307 892039 : return !at_eof(r) && r->text[r->pos] == c;
308 : }
309 :
310 486142 : static bool expect_char(WokReader *r, char c, const char *what) {
311 486142 : skip_ws(r);
312 486142 : if (at_eof(r)) {
313 0 : wok_diag_add(r->diag, WOK_E_LEX_UNTERMINATED, (u32)r->pos, 0,
314 : "unterminated %s: expected `%c`, found end of input", what,
315 : c);
316 0 : return false;
317 : }
318 486142 : if (r->text[r->pos] != c) {
319 1 : wok_diag_add(r->diag, WOK_E_PARSE, (u32)r->pos, 0,
320 : "expected `%c`, found `%c`", c, r->text[r->pos]);
321 1 : return false;
322 : }
323 486141 : r->pos++;
324 486141 : return true;
325 : }
326 :
327 : // A maximal run of non-delimiter characters: a tag, `#t`/`#f`, `seq`,
328 : // `some`, `none`, or a decimal literal. Returns false (no diagnostic) when
329 : // the next character is itself a delimiter or EOF -- the caller knows the
330 : // context and reports accordingly.
331 437125 : static bool read_word(WokReader *r, usize *out_off, usize *out_len) {
332 437125 : skip_ws(r);
333 437125 : usize start = r->pos;
334 2617860 : while (!at_eof(r) && !is_delim(r->text[r->pos])) r->pos++;
335 437125 : usize len = r->pos - start;
336 437125 : if (len == 0) return false;
337 437125 : *out_off = start;
338 437125 : *out_len = len;
339 437125 : return true;
340 : }
341 :
342 211415 : static bool word_eq(const WokReader *r, usize off, usize len,
343 : const char *lit) {
344 211415 : usize litlen = strlen(lit);
345 211415 : return len == litlen && memcmp(r->text + off, lit, litlen) == 0;
346 : }
347 :
348 2 : static int hex_digit(char c) {
349 2 : if (c >= '0' && c <= '9') return c - '0';
350 0 : if (c >= 'a' && c <= 'f') return c - 'a' + 10;
351 0 : if (c >= 'A' && c <= 'F') return c - 'A' + 10;
352 : return -1;
353 : }
354 :
355 : // Decodes one quoted string literal at the current position, inverting
356 : // render_string exactly. When pool is non-null the decoded bytes are
357 : // appended there and *out_off/*out_len give the span into it; when pool is
358 : // null the literal is only validated and skipped (used to count extra
359 : // fields for a wrong-field-count diagnostic, where the decoded value is
360 : // thrown away).
361 124954 : static bool read_string_lit(WokReader *r, WokByteBuf *pool, u32 *out_off,
362 : u32 *out_len) {
363 124954 : skip_ws(r);
364 124954 : usize start = r->pos;
365 124954 : if (!expect_char(r, '"', "string literal")) return false;
366 :
367 124954 : usize pool_start = pool ? pool->len : 0;
368 464069 : for (;;) {
369 464069 : if (at_eof(r)) {
370 1 : wok_diag_add(r->diag, WOK_E_LEX_UNTERMINATED, (u32)start, 0,
371 : "unterminated string literal");
372 1 : return false;
373 : }
374 464068 : char c = r->text[r->pos];
375 464068 : if (c == '"') {
376 124953 : r->pos++;
377 124953 : break;
378 : }
379 339115 : if (c == '\\') {
380 10974 : r->pos++;
381 10974 : if (at_eof(r)) {
382 0 : wok_diag_add(r->diag, WOK_E_LEX_UNTERMINATED, (u32)start, 0,
383 : "unterminated string literal");
384 0 : return false;
385 : }
386 10974 : char e = r->text[r->pos];
387 10974 : char decoded;
388 10974 : switch (e) {
389 2491 : case '\\':
390 2491 : decoded = '\\';
391 2491 : r->pos++;
392 2491 : break;
393 8480 : case '"':
394 8480 : decoded = '"';
395 8480 : r->pos++;
396 8480 : break;
397 1 : case 'n':
398 1 : decoded = '\n';
399 1 : r->pos++;
400 1 : break;
401 1 : case 't':
402 1 : decoded = '\t';
403 1 : r->pos++;
404 1 : break;
405 0 : case 'r':
406 0 : decoded = '\r';
407 0 : r->pos++;
408 0 : break;
409 1 : case 'x': {
410 1 : r->pos++;
411 1 : if (r->pos + 2 > r->n) {
412 0 : wok_diag_add(r->diag, WOK_E_LEX_UNTERMINATED, (u32)start, 0,
413 : "unterminated \\x escape in string literal");
414 0 : return false;
415 : }
416 1 : int hi = hex_digit(r->text[r->pos]);
417 1 : int lo = hex_digit(r->text[r->pos + 1]);
418 1 : if (hi < 0 || lo < 0) {
419 0 : wok_diag_add(r->diag, WOK_E_PARSE, (u32)r->pos, 0,
420 : "invalid \\x escape in string literal");
421 0 : return false;
422 : }
423 1 : decoded = (char)((hi << 4) | lo);
424 1 : r->pos += 2;
425 1 : break;
426 : }
427 0 : default:
428 0 : wok_diag_add(r->diag, WOK_E_PARSE, (u32)r->pos, 0,
429 : "unknown escape `\\%c` in string literal", e);
430 0 : return false;
431 : }
432 10974 : if (pool) bytebuf_put(pool, decoded);
433 10974 : continue;
434 : }
435 328141 : if (pool) bytebuf_put(pool, c);
436 328141 : r->pos++;
437 : }
438 :
439 124953 : if (out_off) *out_off = (u32)pool_start;
440 124953 : if (out_len) *out_len = pool ? (u32)(pool->len - pool_start) : 0;
441 : return true;
442 : }
443 :
444 16167 : static bool read_uint_field(WokReader *r, u64 *out) {
445 16167 : usize off, len;
446 16167 : if (!read_word(r, &off, &len)) {
447 0 : if (at_eof(r)) {
448 0 : wok_diag_add(r->diag, WOK_E_LEX_UNTERMINATED, (u32)r->pos, 0,
449 : "unterminated node: expected an integer field");
450 : } else {
451 0 : wok_diag_add(r->diag, WOK_E_PARSE, (u32)r->pos, 0,
452 0 : "expected an integer field, found `%c`", r->text[r->pos]);
453 : }
454 0 : return false;
455 : }
456 : u64 v = 0;
457 41713 : for (usize i = 0; i < len; i++) {
458 25546 : char c = r->text[off + i];
459 25546 : if (c < '0' || c > '9') {
460 0 : wok_diag_add(r->diag, WOK_E_PARSE, (u32)off, (u32)len,
461 : "expected a decimal integer, found `%.*s`", (int)len,
462 : r->text + off);
463 0 : return false;
464 : }
465 25546 : u64 d = (u64)(c - '0');
466 25546 : if (v > (UINT64_MAX - d) / 10) {
467 0 : wok_diag_add(r->diag, WOK_E_LEX_INT_RANGE, (u32)off, (u32)len,
468 : "integer literal `%.*s` out of range", (int)len,
469 : r->text + off);
470 0 : return false;
471 : }
472 25546 : v = v * 10 + d;
473 : }
474 16167 : *out = v;
475 16167 : return true;
476 : }
477 :
478 67486 : static bool read_flag_field(WokReader *r, bool *out) {
479 67486 : usize off, len;
480 67486 : if (!read_word(r, &off, &len)) {
481 0 : if (at_eof(r)) {
482 0 : wok_diag_add(r->diag, WOK_E_LEX_UNTERMINATED, (u32)r->pos, 0,
483 : "unterminated node: expected `#t` or `#f`");
484 : } else {
485 0 : wok_diag_add(r->diag, WOK_E_PARSE, (u32)r->pos, 0,
486 0 : "expected `#t` or `#f`, found `%c`", r->text[r->pos]);
487 : }
488 0 : return false;
489 : }
490 67486 : if (word_eq(r, off, len, "#t")) {
491 33175 : *out = true;
492 33175 : return true;
493 : }
494 34311 : if (word_eq(r, off, len, "#f")) {
495 34311 : *out = false;
496 34311 : return true;
497 : }
498 0 : wok_diag_add(r->diag, WOK_E_PARSE, (u32)off, (u32)len,
499 0 : "expected `#t` or `#f`, found `%.*s`", (int)len, r->text + off);
500 0 : return false;
501 : }
502 :
503 248676 : static bool lookup_tag(const WokReader *r, usize off, usize len,
504 : WokTag *out) {
505 7318603 : for (int t = 0; t < WOK_TAG_COUNT; t++) {
506 7318602 : const char *name = wok_node_desc[t].tag;
507 7318602 : usize nl = strlen(name);
508 7318602 : if (nl == len && memcmp(r->text + off, name, len) == 0) {
509 248675 : *out = (WokTag)t;
510 248675 : return true;
511 : }
512 : }
513 : return false;
514 : }
515 :
516 : static WokNode *parse_node(WokReader *r, u32 depth);
517 :
518 : // Discards one opaque atom or balanced list, used only to keep counting
519 : // fields after a node has already overrun its declared arity, so the
520 : // "expected N, got M" diagnostic can name a real M instead of guessing.
521 1 : static bool skip_one_form(WokReader *r) {
522 1 : skip_ws(r);
523 1 : if (at_eof(r)) {
524 0 : wok_diag_add(r->diag, WOK_E_LEX_UNTERMINATED, (u32)r->pos, 0,
525 : "unterminated list");
526 0 : return false;
527 : }
528 1 : char c = r->text[r->pos];
529 1 : if (c == '"') return read_string_lit(r, nullptr, nullptr, nullptr);
530 1 : if (c == '(') {
531 0 : usize start = r->pos;
532 0 : r->pos++;
533 0 : int nesting = 1;
534 0 : while (nesting > 0) {
535 0 : if (at_eof(r)) {
536 0 : wok_diag_add(r->diag, WOK_E_LEX_UNTERMINATED, (u32)start, 0,
537 : "unterminated list");
538 0 : return false;
539 : }
540 0 : char cc = r->text[r->pos];
541 0 : if (cc == '"') {
542 0 : if (!read_string_lit(r, nullptr, nullptr, nullptr)) return false;
543 0 : continue;
544 : }
545 0 : if (cc == '(') nesting++;
546 0 : else if (cc == ')') nesting--;
547 0 : r->pos++;
548 : }
549 : return true;
550 : }
551 1 : if (c == ')') return false;
552 1 : usize off, len;
553 1 : return read_word(r, &off, &len);
554 : }
555 :
556 7716 : static bool parse_opt(WokReader *r, u32 depth, WokNode **out) {
557 7716 : skip_ws(r);
558 7716 : usize off = r->pos;
559 7716 : if (!expect_char(r, '(', "OPT field")) return false;
560 :
561 7716 : usize woff, wlen;
562 7716 : if (!read_word(r, &woff, &wlen)) {
563 0 : if (at_eof(r)) {
564 0 : wok_diag_add(r->diag, WOK_E_LEX_UNTERMINATED, (u32)off, 0,
565 : "unterminated OPT field: expected `none` or `some`");
566 : } else {
567 0 : wok_diag_add(r->diag, WOK_E_PARSE, (u32)r->pos, 0,
568 : "expected `none` or `some`");
569 : }
570 0 : return false;
571 : }
572 7716 : if (word_eq(r, woff, wlen, "none")) {
573 2893 : if (!expect_char(r, ')', "none")) return false;
574 2893 : *out = nullptr;
575 2893 : return true;
576 : }
577 4823 : if (word_eq(r, woff, wlen, "some")) {
578 4823 : WokNode *child = parse_node(r, depth + 1);
579 4823 : if (!child) return false;
580 4823 : if (!expect_char(r, ')', "some")) return false;
581 4823 : *out = child;
582 4823 : return true;
583 : }
584 0 : wok_diag_add(r->diag, WOK_E_PARSE, (u32)woff, (u32)wlen,
585 : "expected `none` or `some`, found `%.*s`", (int)wlen,
586 0 : r->text + woff);
587 0 : return false;
588 : }
589 :
590 97080 : static bool parse_seq(WokReader *r, u32 depth, WokSeq *out) {
591 97080 : skip_ws(r);
592 97080 : usize off = r->pos;
593 97080 : if (!expect_char(r, '(', "SEQ field")) return false;
594 :
595 97079 : usize woff, wlen;
596 97079 : if (!read_word(r, &woff, &wlen)) {
597 0 : if (at_eof(r)) {
598 0 : wok_diag_add(r->diag, WOK_E_LEX_UNTERMINATED, (u32)off, 0,
599 : "unterminated SEQ field: expected `seq`");
600 : } else {
601 0 : wok_diag_add(r->diag, WOK_E_PARSE, (u32)r->pos, 0,
602 : "expected `seq`");
603 : }
604 0 : return false;
605 : }
606 97079 : if (!word_eq(r, woff, wlen, "seq")) {
607 0 : wok_diag_add(r->diag, WOK_E_PARSE, (u32)woff, (u32)wlen,
608 0 : "expected `seq`, found `%.*s`", (int)wlen, r->text + woff);
609 0 : return false;
610 : }
611 :
612 97079 : WokNodeBuf buf;
613 97079 : wok_buf_init(&buf, r->arena);
614 109852 : for (;;) {
615 206931 : if (peek_is(r, ')')) {
616 97076 : r->pos++;
617 97076 : break;
618 : }
619 109855 : if (at_eof(r)) {
620 0 : wok_diag_add(r->diag, WOK_E_LEX_UNTERMINATED, (u32)r->pos, 0,
621 : "unterminated seq");
622 0 : return false;
623 : }
624 109855 : WokNode *child = parse_node(r, depth + 1);
625 109855 : if (!child) return false;
626 109852 : wok_buf_push(&buf, child);
627 : }
628 97076 : *out = wok_buf_seq(&buf);
629 97076 : return true;
630 : }
631 :
632 : // THE FAMILY CHECK. The schema says which kind of child a field demands, so a
633 : // dump holding a type where an expression belongs is REPORTED here rather than
634 : // read back as a different program. The class check alone let it through: it
635 : // asks whether a child is present, never what the child IS.
636 237711 : static bool family_ok(WokReader *r, const WokNode *child, WokFamily want,
637 : const char *field) {
638 237711 : WokFamily got = wok_node_desc[child->tag].family;
639 237711 : if (wok_family_accepts(want, got)) return true;
640 3 : wok_diag_add(r->diag, WOK_E_PARSE, (u32)r->pos, 0,
641 : "field `%s` takes %s, found %s, which is %s", field,
642 3 : wok_family_name(want), wok_node_desc[child->tag].tag,
643 : wok_family_name(got));
644 3 : return false;
645 : }
646 :
647 436440 : static bool parse_field(WokReader *r, WokNode *node, u16 i,
648 : const WokFieldDesc *desc, u32 depth) {
649 436440 : WokFieldClass cls = desc->cls;
650 436440 : switch (cls) {
651 123037 : case WFC_NODE: {
652 123037 : WokNode *child = parse_node(r, depth + 1);
653 123037 : if (!child) return false;
654 123036 : if (!family_ok(r, child, desc->family, desc->name)) return false;
655 123034 : node->slot[i] = WOK_MK_NODE(child);
656 123034 : return true;
657 : }
658 7716 : case WFC_OPT: {
659 7716 : WokNode *child = nullptr;
660 7716 : if (!parse_opt(r, depth, &child)) return false;
661 7716 : if (child && !family_ok(r, child, desc->family, desc->name)) return false;
662 7716 : node->slot[i] = WOK_MK_OPT(child);
663 7716 : return true;
664 : }
665 97080 : case WFC_SEQ: {
666 97080 : WokSeq seq;
667 97080 : if (!parse_seq(r, depth, &seq)) return false;
668 206927 : for (u32 k = 0; k < seq.n; k++)
669 109852 : if (!family_ok(r, seq.items[k], desc->family, desc->name)) return false;
670 97075 : node->slot[i] = WOK_MK_SEQ(seq);
671 97075 : return true;
672 : }
673 117930 : case WFC_NAME: {
674 117930 : u32 off, len;
675 117930 : if (!read_string_lit(r, &r->pool, &off, &len)) return false;
676 117930 : node->slot[i] = WOK_MK_NAME(wok_span(off, len));
677 117930 : return true;
678 : }
679 7024 : case WFC_TEXT: {
680 7024 : u32 off, len;
681 7024 : if (!read_string_lit(r, &r->pool, &off, &len)) return false;
682 7023 : node->slot[i] = WOK_MK_TEXT(wok_span(off, len));
683 7023 : return true;
684 : }
685 16167 : case WFC_INT: {
686 16167 : u64 v;
687 16167 : if (!read_uint_field(r, &v)) return false;
688 16167 : node->slot[i] = WOK_MK_INT(v);
689 16167 : return true;
690 : }
691 67486 : case WFC_FLAG: {
692 67486 : bool v;
693 67486 : if (!read_flag_field(r, &v)) return false;
694 67486 : node->slot[i] = WOK_MK_FLAG(v);
695 67486 : return true;
696 : }
697 : case WOK_FIELD_CLASS_COUNT:
698 : break;
699 : }
700 0 : WOK_UNREACHABLE();
701 : }
702 :
703 248676 : static WokNode *parse_node(WokReader *r, u32 depth) {
704 248676 : if (depth > WOK_SEXPR_MAX_DEPTH) {
705 0 : wok_diag_add(r->diag, WOK_E_DEPTH, (u32)r->pos, 0,
706 : "s-expression nesting exceeds %d levels", WOK_SEXPR_MAX_DEPTH);
707 0 : return nullptr;
708 : }
709 :
710 248676 : skip_ws(r);
711 248676 : usize open_off = r->pos;
712 248676 : if (!expect_char(r, '(', "node")) return nullptr;
713 :
714 248676 : usize tag_off, tag_len;
715 248676 : if (!read_word(r, &tag_off, &tag_len)) {
716 0 : if (at_eof(r)) {
717 0 : wok_diag_add(r->diag, WOK_E_LEX_UNTERMINATED, (u32)open_off, 0,
718 : "unterminated node: missing tag");
719 : } else {
720 0 : wok_diag_add(r->diag, WOK_E_PARSE, (u32)r->pos, 0,
721 : "expected a tag name after `(`");
722 : }
723 0 : return nullptr;
724 : }
725 :
726 248676 : WokTag tag;
727 248676 : if (!lookup_tag(r, tag_off, tag_len, &tag)) {
728 1 : wok_diag_add(r->diag, WOK_E_PARSE, (u32)tag_off, (u32)tag_len,
729 1 : "unknown tag `%.*s`", (int)tag_len, r->text + tag_off);
730 1 : return nullptr;
731 : }
732 :
733 248675 : const WokNodeDesc *desc = &wok_node_desc[tag];
734 248675 : WokNode *node = wok_node(r->arena, tag, 0, 0);
735 :
736 685106 : for (u16 i = 0; i < desc->nfields; i++) {
737 436440 : if (peek_is(r, ')')) {
738 0 : wok_diag_add(r->diag, WOK_E_PARSE, (u32)r->pos, 0,
739 0 : "%s: expected %u fields, got %u", desc->tag,
740 : (unsigned)desc->nfields, (unsigned)i);
741 0 : return nullptr;
742 : }
743 436440 : if (at_eof(r)) {
744 0 : wok_diag_add(r->diag, WOK_E_LEX_UNTERMINATED, (u32)r->pos, 0,
745 0 : "unterminated node `%s`", desc->tag);
746 0 : return nullptr;
747 : }
748 436440 : if (!parse_field(r, node, i, &desc->fields[i], depth)) return nullptr;
749 : }
750 :
751 248666 : if (!peek_is(r, ')')) {
752 2 : if (at_eof(r)) {
753 1 : wok_diag_add(r->diag, WOK_E_LEX_UNTERMINATED, (u32)r->pos, 0,
754 1 : "unterminated node `%s`: missing `)`", desc->tag);
755 1 : return nullptr;
756 : }
757 : u16 extra = 0;
758 2 : while (!peek_is(r, ')') && !at_eof(r)) {
759 1 : if (!skip_one_form(r)) return nullptr;
760 1 : extra++;
761 : }
762 1 : if (at_eof(r)) {
763 0 : wok_diag_add(r->diag, WOK_E_LEX_UNTERMINATED, (u32)r->pos, 0,
764 0 : "unterminated node `%s`: missing `)`", desc->tag);
765 0 : return nullptr;
766 : }
767 1 : wok_diag_add(r->diag, WOK_E_PARSE, (u32)r->pos, 0,
768 1 : "%s: expected %u fields, got %u", desc->tag,
769 1 : (unsigned)desc->nfields, (unsigned)(desc->nfields + extra));
770 1 : return nullptr;
771 : }
772 248664 : r->pos++; // consume ')'
773 248664 : return node;
774 : }
775 :
776 10961 : WokNode *wok_sexpr_read(const char *text, usize n, WokArena *arena,
777 : WokDiagSink *diag, const char **out_src) {
778 10961 : WokReader r = {.text = text,
779 : .n = n,
780 : .pos = 0,
781 : .diag = diag,
782 : .arena = arena,
783 : .pool = {.arena = arena}};
784 :
785 10961 : WokNode *root = parse_node(&r, 1);
786 10961 : if (root) {
787 10953 : skip_ws(&r);
788 10953 : if (!at_eof(&r)) {
789 0 : wok_diag_add(diag, WOK_E_PARSE, (u32)r.pos, 0,
790 : "trailing content after the top-level form");
791 0 : root = nullptr;
792 : }
793 : }
794 :
795 0 : if (!root) {
796 8 : if (out_src) *out_src = nullptr;
797 8 : return nullptr;
798 : }
799 :
800 10953 : bytebuf_reserve(&r.pool, 1);
801 10953 : r.pool.data[r.pool.len] = '\0';
802 10953 : if (out_src) *out_src = r.pool.data;
803 : return root;
804 : }
|