Line data Source code
1 : // wokparse -- the CLI.
2 : //
3 : // wokparse FILE.wok print the AST as an s-expression (any
4 : // parse-clean file dumps; stage-4 checks
5 : // belong to -check-only and -json)
6 : // wokparse -tokens FILE.wok the token stream, with positions
7 : // wokparse -layout FILE.wok the stream after the layout filter
8 : // wokparse -check-only FILE.wok parse, resolve, report faults; print nothing else
9 : // wokparse -json FILE.wok the same faults, one JSON object per line
10 : // wokparse -sexp -reorder FILE.wok
11 : // parse -> resolve -> reorder -> dump: the
12 : // s-expression carries precedence-RESOLVED
13 : // chains instead of flat ones. `-reorder`
14 : // is a modifier of `-sexp`, not a mode of
15 : // its own -- it is an error without it (see
16 : // docs/superpowers/specs/
17 : // 2026-08-07-sexp-reorder-pass.md).
18 : //
19 : // Human diagnostics are `file:line:col: message`, which every editor and CI
20 : // log already jumps to. A batch is printed, not the first fault.
21 :
22 : // The prelude comes FIRST: it carries the POSIX feature-test macros, which
23 : // have no effect once a system header has been read. wok_base.h hard-errors
24 : // if it is reached too late.
25 : #include "wok_base.h"
26 :
27 : #include <stdio.h>
28 : #include <stdlib.h>
29 : #include <string.h>
30 :
31 : #include "../wok_arena.h"
32 : #include "../wok_ast.h"
33 : #include "../wok_diag.h"
34 : #include "../wok_layout.h"
35 : #include "../wok_parse.h"
36 : #include "../wok_reorder.h"
37 : #include "../wok_resolve.h"
38 : #include "../wok_sexpr.h"
39 : #include "../wok_token.h"
40 :
41 : typedef enum { M_SEXP, M_TOKENS, M_LAYOUT, M_CHECK, M_JSON } Mode;
42 :
43 4 : static char *slurp(const char *path, usize *n) {
44 4 : FILE *fp = fopen(path, "rb");
45 4 : if (!fp) return nullptr;
46 4 : if (fseek(fp, 0, SEEK_END) != 0) {
47 0 : fclose(fp);
48 0 : return nullptr;
49 : }
50 4 : long sz = ftell(fp);
51 4 : if (sz < 0) {
52 0 : fclose(fp);
53 0 : return nullptr;
54 : }
55 4 : rewind(fp);
56 4 : char *buf = (char *)malloc((usize)sz + 1);
57 4 : if (!buf) {
58 0 : fclose(fp);
59 0 : return nullptr;
60 : }
61 4 : usize got = fread(buf, 1, (usize)sz, fp);
62 4 : buf[got] = '\0';
63 4 : *n = got;
64 4 : fclose(fp);
65 4 : return buf;
66 : }
67 :
68 0 : static void print_tokens(WokTokens t, const char *src, const WokDiagSink *d) {
69 0 : for (usize i = 0; i < t.n; i++) {
70 0 : u32 line, col;
71 0 : wok_diag_position(d, t.tok[i].off, &line, &col);
72 0 : const char *nm = wok_kind_name((WokKind)t.tok[i].kind);
73 0 : printf("%4u:%-3u %-16s", line, col, nm);
74 0 : if (t.tok[i].word != WW_NONE)
75 0 : printf(" word=%s", wok_word_text((WokWord)t.tok[i].word));
76 0 : if (t.tok[i].len > 0 && (WokKind)t.tok[i].kind != WT_EOF)
77 0 : printf(" `%.*s`", (int)t.tok[i].len, src + t.tok[i].off);
78 0 : if ((t.tok[i].flags & WOK_TF_FIRST_ON_LINE) != 0) printf(" [line-start]");
79 0 : putchar('\n');
80 : }
81 0 : }
82 :
83 5 : int main(int argc, char **argv) {
84 5 : Mode mode = M_SEXP;
85 5 : bool saw_sexp = false;
86 5 : bool reorder = false;
87 5 : int argi = 1;
88 11 : for (; argi < argc && argv[argi][0] == '-'; argi++) {
89 6 : if (strcmp(argv[argi], "-tokens") == 0) mode = M_TOKENS;
90 6 : else if (strcmp(argv[argi], "-layout") == 0) mode = M_LAYOUT;
91 6 : else if (strcmp(argv[argi], "-check-only") == 0) mode = M_CHECK;
92 5 : else if (strcmp(argv[argi], "-sexp") == 0) { mode = M_SEXP; saw_sexp = true; }
93 3 : else if (strcmp(argv[argi], "-json") == 0) mode = M_JSON;
94 3 : else if (strcmp(argv[argi], "-reorder") == 0) reorder = true;
95 : else {
96 0 : fprintf(stderr, "wokparse: unknown flag %s\n", argv[argi]);
97 0 : return 2;
98 : }
99 : }
100 5 : if (argi >= argc) {
101 0 : fprintf(stderr, "usage: wokparse [-tokens|-layout|-sexp[-reorder]|"
102 : "-check-only|-json] FILE.wok\n");
103 0 : return 2;
104 : }
105 : // `-reorder` reassociates the very chains `-sexp` dumps, so it means
106 : // nothing under any other mode -- and nothing implicitly either: -sexp is
107 : // the default mode, but a bare `wokparse -reorder FILE` did not ASK for
108 : // the dump `-reorder` modifies, so it is refused rather than guessed. The
109 : // MODE check (not just "was -sexp typed") is what catches `-sexp -json
110 : // -reorder`: a later mode flag overrides -sexp, and -reorder would
111 : // otherwise silently do nothing under the mode that won.
112 5 : if (reorder && (!saw_sexp || mode != M_SEXP)) {
113 1 : fprintf(stderr, "wokparse: -reorder requires -sexp\n");
114 1 : return 2;
115 : }
116 :
117 : int status = 0;
118 8 : for (; argi < argc; argi++) {
119 4 : usize n = 0;
120 4 : char *src = slurp(argv[argi], &n);
121 4 : if (!src) {
122 0 : fprintf(stderr, "wokparse: cannot read %s\n", argv[argi]);
123 0 : status = 2;
124 0 : continue;
125 : }
126 4 : WokArena *a = wok_arena_new(0);
127 4 : WokDiagSink *d = wok_diag_new(a, argv[argi], src, n);
128 :
129 4 : WokScanResult sr = wok_scan(src, n, a, d);
130 4 : WokTokens lay = wok_layout(sr.tokens, a, d);
131 :
132 4 : if (mode == M_TOKENS) {
133 0 : print_tokens(sr.tokens, src, d);
134 4 : } else if (mode == M_LAYOUT) {
135 0 : print_tokens(lay, src, d);
136 : } else {
137 4 : WokNode *file = wok_parse(lay, src, a, d);
138 4 : bool parsed = wok_diag_count(d) == 0;
139 : // Resolution runs only over a clean parse: a damaged tree has holes
140 : // where the names and counts belong, and the parse fault already said
141 : // the true thing about them. And it runs only in the CHECK modes, or
142 : // under -sexp -reorder where the reorder pass needs resolve's fixity
143 : // table -- plain -sexp stays the dump tool, and a stage-4 fault must
144 : // not cost the reader the very tree the fault is about.
145 4 : if (parsed && (mode == M_CHECK || mode == M_JSON)) {
146 1 : wok_resolve(file, src, a, d);
147 3 : } else if (parsed && mode == M_SEXP && reorder) {
148 2 : WokFixTable fix = {0};
149 2 : wok_resolve_fix(file, src, a, d, &fix);
150 2 : if (wok_diag_count(d) == 0)
151 2 : file = (WokNode *)wok_reorder(file, src, a, d, &fix);
152 : }
153 4 : if (mode == M_SEXP && parsed && wok_diag_count(d) == 0)
154 2 : wok_sexpr_dump(file, src, stdout);
155 : }
156 :
157 4 : if (wok_diag_count(d) > 0) {
158 2 : if (mode == M_JSON) wok_diag_render_jsonl(d, stdout);
159 2 : else wok_diag_render(d, stderr);
160 : status = 1;
161 : }
162 : // JSON Lines: no diagnostics means no output, not an empty container.
163 :
164 4 : wok_arena_free(a);
165 4 : free(src);
166 : }
167 : return status;
168 : }
|