← Commit history

parser: add AST and Java-like declaration, statement and expression parsing

lennoxrose committed on Oct 17, 2026 at 16:46 UTC

5164bafparent995e2a2

15 files changed+1555−0

front/src/jdriver/D/.gitkeepadded

+0−0

No content changes. View the file on GitHub

front/src/jdriver/O/.gitkeepadded

+0−0

No content changes. View the file on GitHub

front/src/jlower/D/.gitkeepadded

+0−0

No content changes. View the file on GitHub

front/src/jlower/O/.gitkeepadded

+0−0

No content changes. View the file on GitHub

front/src/jparse/C/jparse_decl.cadded

+476−0
@@ -0,0 +1,476 @@
1+#include <stdarg.h>
2+#include <stdio.h>
3+#include <stdlib.h>
4+
5+#include "jparse/H/jparse.h"
6+#include "jparse/H/jparse_internal.h"
7+
8+Node *node_new(NodeKind kind, const char *path, int line, int col) {
9+ Node *n = j_alloc(sizeof(Node));
10+ n->kind = kind;
11+ n->path = path;
12+ n->line = line;
13+ n->col = col;
14+ return n;
15+}
16+
17+Node *p_node(Parser *p, NodeKind k) {
18+ JToken *t = p_peek(p);
19+ return node_new(k, p->t.path, t->line, t->col);
20+}
21+
22+void p_fail(Parser *p, const char *fmt, ...) {
23+ JToken *t = p_peek(p);
24+ va_list ap;
25+ va_start(ap, fmt);
26+ char *msg = NULL;
27+ int n = vsnprintf(NULL, 0, fmt, ap);
28+ va_end(ap);
29+ msg = j_alloc((size_t)n + 1);
30+ va_start(ap, fmt);
31+ vsnprintf(msg, (size_t)n + 1, fmt, ap);
32+ va_end(ap);
33+ j_error(p->t.path, t->line, t->col, "%s", msg);
34+ abort();
35+}
36+
37+JToken *p_next(Parser *p) {
38+ JToken *t = p_peek(p);
39+ if (t->kind != JT_EOF) p->pos++;
40+ return t;
41+}
42+
43+int p_accept(Parser *p, JTokKind k) {
44+ if (p_is(p, k)) {
45+ p->pos++;
46+ return 1;
47+ }
48+ return 0;
49+}
50+
51+JToken *p_expect(Parser *p, JTokKind k, const char *what) {
52+ if (!p_is(p, k)) {
53+ JToken *t = p_peek(p);
54+ p_fail(p, "expected %s but found %s%s%s", what ? what : jtok_name(k),
55+ t->kind == JT_EOF ? "end of file" : "'", t->kind == JT_EOF ? "" : t->text,
56+ t->kind == JT_EOF ? "" : "'");
57+ }
58+ return p_next(p);
59+}
60+
61+char *p_ident(Parser *p, const char *what) {
62+ JToken *t = p_expect(p, JT_IDENT, what ? what : "an identifier");
63+ return (char *)t->text;
64+}
65+
66+int p_is_prim(JTokKind k) {
67+ return k == JT_INT || k == JT_LONG || k == JT_FLOAT || k == JT_DOUBLE || k == JT_BOOL ||
68+ k == JT_CHAR || k == JT_BYTE || k == JT_SHORT || k == JT_VOID;
69+}
70+
71+/* ---------------------------------------------------------------- types */
72+
73+static Node *type_args_opt(Parser *p, Node *t);
74+
75+static Node *type_arg(Parser *p) {
76+ if (p_is(p, JT_QUESTION)) {
77+ Node *w = p_node(p, N_WILDCARD);
78+ p_next(p);
79+ w->name = "";
80+ if (p_is(p, JT_EXTENDS) || p_is(p, JT_SUPER)) {
81+ w->name = p_peek(p)->kind == JT_EXTENDS ? "extends" : "super";
82+ p_next(p);
83+ w->a = p_type_opt(p);
84+ if (!w->a) return NULL;
85+ }
86+ return w;
87+ }
88+ return p_type_opt(p);
89+}
90+
91+static Node *type_args_opt(Parser *p, Node *t) {
92+ if (!p_is(p, JT_LT)) return t;
93+ p_next(p);
94+ if (p_is(p, JT_GT)) { /* diamond */
95+ p_next(p);
96+ t->flags |= 0x10000;
97+ return t;
98+ }
99+ for (;;) {
100+ Node *a = type_arg(p);
101+ if (!a) return NULL;
102+ j_list_push(&t->l1, a);
103+ if (p_accept(p, JT_COMMA)) continue;
104+ if (p_accept(p, JT_GT)) break;
105+ return NULL;
106+ }
107+ return t;
108+}
109+
110+Node *p_type_opt(Parser *p) {
111+ Node *t = p_node(p, N_TYPE);
112+ JToken *tok = p_peek(p);
113+ if (p_is_prim(tok->kind)) {
114+ t->name = tok->text;
115+ p_next(p);
116+ } else if (tok->kind == JT_IDENT) {
117+ JBuf name = {0};
118+ j_buf_add(&name, tok->text);
119+ p_next(p);
120+ t = type_args_opt(p, t);
121+ if (!t) return NULL;
122+ while (p_is(p, JT_DOT) && p_peek_at(p, 1)->kind == JT_IDENT) {
123+ p_next(p);
124+ j_buf_addc(&name, '.');
125+ j_buf_add(&name, p_peek(p)->text);
126+ p_next(p);
127+ t = type_args_opt(p, t);
128+ if (!t) return NULL;
129+ }
130+ t->name = name.data;
131+ } else {
132+ return NULL;
133+ }
134+ while (p_is(p, JT_LBRACKET) && p_peek_at(p, 1)->kind == JT_RBRACKET) {
135+ p_next(p);
136+ p_next(p);
137+ t->flags++;
138+ }
139+ /* `T?` nullable: only when what follows cannot continue an expression like `a ? b : c`
140+ * in a way that makes the `?` a ternary; callers backtrack when the parse fails. */
141+ if (p_is(p, JT_QUESTION) && !p->no_nullable) {
142+ JTokKind k = p_peek_at(p, 1)->kind;
143+ if (k == JT_IDENT || k == JT_RPAREN || k == JT_COMMA || k == JT_GT || k == JT_LBRACKET ||
144+ k == JT_ELLIPSIS || k == JT_SEMI || k == JT_ASSIGN || k == JT_LBRACE || k == JT_COLON) {
145+ p_next(p);
146+ t->nullable = 1;
147+ }
148+ }
149+ return t;
150+}
151+
152+Node *p_type(Parser *p) {
153+ Node *t = p_type_opt(p);
154+ if (!t) p_fail(p, "expected a type");
155+ return t;
156+}
157+
158+/* ---------------------------------------------------------------- modifiers */
159+
160+static Node *annotation(Parser *p) {
161+ Node *a = p_node(p, N_ANNO);
162+ p_expect(p, JT_AT, "'@'");
163+ a->name = p_ident(p, "an annotation name");
164+ if (p_accept(p, JT_LPAREN)) {
165+ if (p_is(p, JT_STRING_LIT)) {
166+ a->str = p_peek(p)->text;
167+ p_next(p);
168+ } else {
169+ while (!p_is(p, JT_RPAREN) && !p_is(p, JT_EOF)) p_next(p);
170+ }
171+ p_expect(p, JT_RPAREN, "')'");
172+ }
173+ return a;
174+}
175+
176+int p_parse_modifiers(Parser *p, JList *annos) {
177+ int mods = 0;
178+ for (;;) {
179+ JToken *t = p_peek(p);
180+ int m = 0;
181+ switch (t->kind) {
182+ case JT_PUBLIC: m = M_PUBLIC; break;
183+ case JT_PRIVATE: m = M_PRIVATE; break;
184+ case JT_PROTECTED: m = M_PROTECTED; break;
185+ case JT_STATIC: m = M_STATIC; break;
186+ case JT_FINAL: m = M_FINAL; break;
187+ case JT_ABSTRACT: m = M_ABSTRACT; break;
188+ case JT_NATIVE: m = M_NATIVE; break;
189+ case JT_CONST: m = M_CONST | M_STATIC | M_FINAL; break;
190+ case JT_DEFAULT:
191+ /* `default` here is the interface-method modifier, not a switch label */
192+ if (p_peek_at(p, 1)->kind == JT_COLON || p_peek_at(p, 1)->kind == JT_ARROW) return mods;
193+ m = M_DEFAULT;
194+ break;
195+ case JT_AT:
196+ if (p_peek_at(p, 1)->kind == JT_INTERFACE) return mods;
197+ j_list_push(annos, annotation(p));
198+ continue;
199+ case JT_IDENT:
200+ if (strcmp(t->text, "sealed") == 0 &&
201+ (p_peek_at(p, 1)->kind == JT_CLASS || p_peek_at(p, 1)->kind == JT_INTERFACE ||
202+ p_peek_at(p, 1)->kind == JT_ABSTRACT || p_peek_at(p, 1)->kind == JT_PUBLIC)) {
203+ m = M_SEALED;
204+ break;
205+ }
206+ return mods;
207+ default:
208+ return mods;
209+ }
210+ if (mods & m) p_fail(p, "repeated modifier '%s'", t->text);
211+ mods |= m;
212+ p_next(p);
213+ }
214+}
215+
216+/* ---------------------------------------------------------------- declarations */
217+
218+static void type_params_opt(Parser *p, JList *out) {
219+ if (!p_accept(p, JT_LT)) return;
220+ for (;;) {
221+ Node *tp = p_node(p, N_TYPEPARAM);
222+ tp->name = p_ident(p, "a type parameter name");
223+ if (p_accept(p, JT_EXTENDS)) {
224+ tp->a = p_type(p);
225+ while (p_accept(p, JT_AMP)) p_type(p); /* extra bounds are checked loosely */
226+ }
227+ j_list_push(out, tp);
228+ if (p_accept(p, JT_COMMA)) continue;
229+ p_expect(p, JT_GT, "'>'");
230+ break;
231+ }
232+}
233+
234+static Node *param(Parser *p) {
235+ Node *pa = p_node(p, N_PARAM);
236+ while (p_is(p, JT_FINAL) || p_is(p, JT_AT)) {
237+ if (p_accept(p, JT_FINAL)) pa->flags |= M_FINAL;
238+ else {
239+ JList dummy = {0};
240+ j_list_push(&dummy, annotation(p));
241+ }
242+ }
243+ pa->a = p_type(p);
244+ if (p_accept(p, JT_ELLIPSIS)) {
245+ pa->flags |= M_VARARGS;
246+ pa->a->flags++;
247+ }
248+ pa->name = p_ident(p, "a parameter name");
249+ return pa;
250+}
251+
252+void p_params(Parser *p, Node *m) {
253+ p_expect(p, JT_LPAREN, "'('");
254+ if (!p_is(p, JT_RPAREN)) {
255+ do {
256+ j_list_push(&m->l1, param(p));
257+ } while (p_accept(p, JT_COMMA));
258+ }
259+ p_expect(p, JT_RPAREN, "')'");
260+}
261+
262+static void throws_opt(Parser *p, Node *m) {
263+ if (!p_accept(p, JT_THROWS)) return;
264+ do {
265+ j_list_push(&m->l3, p_type(p));
266+ } while (p_accept(p, JT_COMMA));
267+}
268+
269+static void type_list(Parser *p, JList *out) {
270+ do {
271+ j_list_push(out, p_type(p));
272+ } while (p_accept(p, JT_COMMA));
273+}
274+
275+static Node *enum_const(Parser *p) {
276+ Node *c = p_node(p, N_ENUMCONST);
277+ while (p_is(p, JT_AT)) annotation(p);
278+ c->name = p_ident(p, "an enum constant name");
279+ if (p_accept(p, JT_LPAREN)) {
280+ if (!p_is(p, JT_RPAREN)) {
281+ do {
282+ j_list_push(&c->l1, p_assign_expr(p));
283+ } while (p_accept(p, JT_COMMA));
284+ }
285+ p_expect(p, JT_RPAREN, "')'");
286+ }
287+ return c;
288+}
289+
290+static void class_body(Parser *p, Node *cls) {
291+ p_expect(p, JT_LBRACE, "'{'");
292+ if (cls->kind == N_ENUM) {
293+ while (p_is(p, JT_IDENT) || p_is(p, JT_AT)) {
294+ j_list_push(&cls->l4, enum_const(p));
295+ if (!p_accept(p, JT_COMMA)) break;
296+ }
297+ if (!p_accept(p, JT_SEMI) && !p_is(p, JT_RBRACE)) p_fail(p, "expected ';' or '}' after enum constants");
298+ }
299+ while (!p_is(p, JT_RBRACE)) {
300+ if (p_is(p, JT_EOF)) p_fail(p, "unexpected end of file in class body");
301+ if (p_accept(p, JT_SEMI)) continue;
302+ j_list_push(&cls->l1, p_class_body_decl(p, cls));
303+ }
304+ p_expect(p, JT_RBRACE, "'}'");
305+}
306+
307+Node *p_type_decl(Parser *p, int mods, JList *annos) {
308+ JToken *kw = p_peek(p);
309+ NodeKind kind;
310+ if (kw->kind == JT_CLASS) kind = N_CLASS;
311+ else if (kw->kind == JT_INTERFACE) kind = N_INTERFACE;
312+ else if (kw->kind == JT_ENUM) kind = N_ENUM;
313+ else if (kw->kind == JT_IDENT && strcmp(kw->text, "record") == 0) kind = N_RECORD;
314+ else p_fail(p, "expected class, interface, enum or record declaration");
315+ Node *c = p_node(p, kind);
316+ p_next(p);
317+ c->flags = mods;
318+ c->l5 = (JList){0};
319+ c->name = p_ident(p, "a type name");
320+ (void)annos;
321+ type_params_opt(p, &c->l2);
322+ if (kind == N_RECORD) {
323+ p_expect(p, JT_LPAREN, "'('");
324+ if (!p_is(p, JT_RPAREN)) {
325+ do {
326+ j_list_push(&c->l4, param(p));
327+ } while (p_accept(p, JT_COMMA));
328+ }
329+ p_expect(p, JT_RPAREN, "')'");
330+ }
331+ if (p_accept(p, JT_EXTENDS)) {
332+ if (kind == N_INTERFACE) type_list(p, &c->l3);
333+ else c->a = p_type(p);
334+ }
335+ if (p_accept(p, JT_IMPLEMENTS)) type_list(p, &c->l3);
336+ if (p_is_word(p, "permits")) {
337+ p_next(p);
338+ type_list(p, &c->l5);
339+ }
340+ class_body(p, c);
341+ return c;
342+}
343+
344+Node *p_class_body_decl(Parser *p, Node *owner) {
345+ JList annos = {0};
346+ int mods = p_parse_modifiers(p, &annos);
347+ JToken *t = p_peek(p);
348+
349+ if (t->kind == JT_LBRACE && (mods & ~M_STATIC) == 0) { /* initializer block */
350+ Node *in = p_node(p, N_INIT);
351+ in->flags = mods;
352+ in->b = p_block(p);
353+ return in;
354+ }
355+ if (t->kind == JT_CLASS || t->kind == JT_INTERFACE || t->kind == JT_ENUM ||
356+ (t->kind == JT_IDENT && strcmp(t->text, "record") == 0 && p_peek_at(p, 1)->kind == JT_IDENT &&
357+ (p_peek_at(p, 2)->kind == JT_LPAREN || p_peek_at(p, 2)->kind == JT_LT))) {
358+ return p_type_decl(p, mods, &annos);
359+ }
360+
361+ Node *m = p_node(p, N_METHOD);
362+ m->flags = mods;
363+ m->l4 = annos;
364+ if (p_is(p, JT_LT)) type_params_opt(p, &m->l2);
365+
366+ /* constructor: Name '(' (or compact record constructor: Name '{') */
367+ if (p_is(p, JT_IDENT) && strcmp(p_peek(p)->text, owner->name) == 0 &&
368+ (p_peek_at(p, 1)->kind == JT_LPAREN ||
369+ (owner->kind == N_RECORD && p_peek_at(p, 1)->kind == JT_LBRACE))) {
370+ m->kind = N_CTOR;
371+ m->name = p_peek(p)->text;
372+ p_next(p);
373+ if (p_is(p, JT_LPAREN)) {
374+ p_params(p, m);
375+ } else {
376+ m->flags |= 0x20000; /* compact canonical constructor */
377+ }
378+ throws_opt(p, m);
379+ m->b = p_block(p);
380+ return m;
381+ }
382+
383+ Node *type = p_type(p);
384+ m->a = type;
385+ m->name = p_ident(p, "a member name");
386+ if (p_is(p, JT_LPAREN)) {
387+ p_params(p, m);
388+ /* legacy array dims after params are not supported */
389+ throws_opt(p, m);
390+ if (p_is(p, JT_LBRACE)) {
391+ m->b = p_block(p);
392+ } else {
393+ p_expect(p, JT_SEMI, "'{' or ';'");
394+ }
395+ return m;
396+ }
397+ /* field(s) */
398+ Node *first = NULL;
399+ Node *seq = NULL;
400+ for (;;) {
401+ Node *f = (first == NULL) ? m : node_new(N_FIELD, p->t.path, t->line, t->col);
402+ f->kind = N_FIELD;
403+ f->flags = mods;
404+ f->l1 = annos;
405+ f->a = type;
406+ if (first) f->name = p_ident(p, "a field name");
407+ if (p_accept(p, JT_ASSIGN)) {
408+ if (p_is(p, JT_LBRACE)) {
409+ f->b = p_array_init(p, type);
410+ } else {
411+ f->b = p_assign_expr(p);
412+ }
413+ }
414+ if (!first) {
415+ first = f;
416+ } else {
417+ if (!seq) {
418+ seq = p_node(p, N_SEQ);
419+ j_list_push(&seq->l1, first);
420+ }
421+ j_list_push(&seq->l1, f);
422+ }
423+ if (p_accept(p, JT_COMMA)) continue;
424+ break;
425+ }
426+ p_expect(p, JT_SEMI, "';'");
427+ return seq ? seq : first;
428+}
429+
430+/* ---------------------------------------------------------------- compilation unit */
431+
432+static char *dotted_name(Parser *p, int *wildcard) {
433+ JBuf b = {0};
434+ j_buf_add(&b, p_ident(p, "a name"));
435+ *wildcard = 0;
436+ while (p_accept(p, JT_DOT)) {
437+ if (p_accept(p, JT_STAR)) {
438+ *wildcard = 1;
439+ break;
440+ }
441+ j_buf_addc(&b, '.');
442+ j_buf_add(&b, p_ident(p, "a name"));
443+ }
444+ return b.data;
445+}
446+
447+Node *jparse_unit(const char *path, const char *src) {
448+ j_register_source(path, src);
449+ Parser p = {jlex_tokenize(path, src), 0, 0, 0};
450+ Node *u = p_node(&p, N_UNIT);
451+ u->name = "";
452+ if (p_is(&p, JT_PACKAGE)) {
453+ p_next(&p);
454+ int w;
455+ u->name = dotted_name(&p, &w);
456+ p_expect(&p, JT_SEMI, "';'");
457+ }
458+ while (p_is(&p, JT_IMPORT)) {
459+ Node *im = p_node(&p, N_IMPORT);
460+ p_next(&p);
461+ if (p_is(&p, JT_STATIC)) p_fail(&p, "static imports are not supported");
462+ int w;
463+ im->name = dotted_name(&p, &w);
464+ im->flags = w;
465+ if (p_is_word(&p, "as")) p_fail(&p, "import aliases are not supported (decision D1)");
466+ p_expect(&p, JT_SEMI, "';'");
467+ j_list_push(&u->l1, im);
468+ }
469+ while (!p_is(&p, JT_EOF)) {
470+ if (p_accept(&p, JT_SEMI)) continue;
471+ JList annos = {0};
472+ int mods = p_parse_modifiers(&p, &annos);
473+ j_list_push(&u->l2, p_type_decl(&p, mods, &annos));
474+ }
475+ return u;
476+}

front/src/jparse/C/jparse_expr.cadded

+427−0
@@ -0,0 +1,427 @@
1+#include <stdlib.h>
2+
3+#include "jparse/H/jparse.h"
4+#include "jparse/H/jparse_internal.h"
5+
6+static Node *unary(Parser *p);
7+static Node *ternary(Parser *p);
8+
9+static void args(Parser *p, JList *out) {
10+ p_expect(p, JT_LPAREN, "'('");
11+ if (!p_is(p, JT_RPAREN)) {
12+ do {
13+ j_list_push(out, p_assign_expr(p));
14+ } while (p_accept(p, JT_COMMA));
15+ }
16+ p_expect(p, JT_RPAREN, "')'");
17+}
18+
19+/* Index of the token after the ')' matching the '(' at offset `from`, or -1. */
20+static int after_matching_paren(Parser *p, int from) {
21+ int depth = 0;
22+ for (int i = from;; i++) {
23+ JTokKind k = p_peek_at(p, i)->kind;
24+ if (k == JT_EOF) return -1;
25+ if (k == JT_LPAREN) depth++;
26+ if (k == JT_RPAREN && --depth == 0) return i + 1;
27+ }
28+}
29+
30+static Node *lambda(Parser *p) {
31+ Node *l = p_node(p, N_LAMBDA);
32+ if (p_is(p, JT_IDENT)) {
33+ Node *pa = p_node(p, N_PARAM);
34+ pa->name = p_ident(p, NULL);
35+ j_list_push(&l->l1, pa);
36+ } else {
37+ p_expect(p, JT_LPAREN, "'('");
38+ if (!p_is(p, JT_RPAREN)) {
39+ do {
40+ Node *pa = p_node(p, N_PARAM);
41+ if (p_is(p, JT_FINAL)) p_next(p);
42+ if (p_is(p, JT_IDENT) && (p_peek_at(p, 1)->kind == JT_COMMA || p_peek_at(p, 1)->kind == JT_RPAREN)) {
43+ pa->name = p_ident(p, NULL);
44+ } else {
45+ pa->a = p_type(p);
46+ pa->name = p_ident(p, "a parameter name");
47+ }
48+ j_list_push(&l->l1, pa);
49+ } while (p_accept(p, JT_COMMA));
50+ }
51+ p_expect(p, JT_RPAREN, "')'");
52+ }
53+ p_expect(p, JT_ARROW, "'->'");
54+ if (p_is(p, JT_LBRACE)) l->b = p_block(p);
55+ else l->b = p_assign_expr(p);
56+ return l;
57+}
58+
59+static int lambda_ahead(Parser *p) {
60+ if (p->no_lambda) return 0;
61+ if (p_is(p, JT_IDENT) && p_peek_at(p, 1)->kind == JT_ARROW) return 1;
62+ if (p_is(p, JT_LPAREN)) {
63+ int after = after_matching_paren(p, 0);
64+ return after > 0 && p_peek_at(p, after)->kind == JT_ARROW;
65+ }
66+ return 0;
67+}
68+
69+static Node *array_init(Parser *p, Node *elem_type, int dims) {
70+ Node *na = p_node(p, N_NEWARRAY);
71+ na->a = elem_type;
72+ na->flags = dims;
73+ Node *init = p_node(p, N_ARRAYINIT);
74+ p_expect(p, JT_LBRACE, "'{'");
75+ while (!p_is(p, JT_RBRACE)) {
76+ if (p_is(p, JT_LBRACE)) j_list_push(&init->l1, array_init(p, elem_type, dims - 1));
77+ else j_list_push(&init->l1, p_assign_expr(p));
78+ if (!p_accept(p, JT_COMMA)) break;
79+ }
80+ p_expect(p, JT_RBRACE, "'}'");
81+ na->b = init;
82+ return na;
83+}
84+
85+static Node *new_expr(Parser *p) {
86+ Node *start = p_node(p, N_NEW);
87+ p_expect(p, JT_NEW, "'new'");
88+ Node *t = p_type(p);
89+ if (p_is(p, JT_LPAREN)) {
90+ start->a = t;
91+ args(p, &start->l1);
92+ if (p_is(p, JT_LBRACE)) p_fail(p, "anonymous classes are not supported; declare a named class or use a lambda");
93+ return start;
94+ }
95+ Node *na = p_node(p, N_NEWARRAY);
96+ na->line = start->line;
97+ na->col = start->col;
98+ na->a = t;
99+ int dims = t->flags;
100+ t->flags = 0;
101+ while (p_is(p, JT_LBRACKET)) {
102+ p_next(p);
103+ if (p_is(p, JT_RBRACKET)) {
104+ p_next(p);
105+ } else {
106+ j_list_push(&na->l1, p_expr(p));
107+ p_expect(p, JT_RBRACKET, "']'");
108+ }
109+ dims++;
110+ }
111+ if (dims == 0) p_fail(p, "expected '(' or '[' after 'new %s'", t->name);
112+ na->flags = dims;
113+ if (p_is(p, JT_LBRACE)) {
114+ Node *ai = array_init(p, t, dims);
115+ na->b = ai->b;
116+ } else if (na->l1.count == 0) {
117+ p_fail(p, "array creation needs a dimension or an initializer");
118+ }
119+ return na;
120+}
121+
122+static int starts_unary_not_plus_minus(JTokKind k) {
123+ switch (k) {
124+ case JT_IDENT: case JT_INT_LIT: case JT_LONG_LIT: case JT_FLOAT_LIT: case JT_DOUBLE_LIT:
125+ case JT_CHAR_LIT: case JT_STRING_LIT: case JT_LPAREN: case JT_BANG: case JT_TILDE:
126+ case JT_NEW: case JT_THIS: case JT_SUPER: case JT_TRUE: case JT_FALSE: case JT_NULL:
127+ case JT_SWITCH:
128+ return 1;
129+ default:
130+ return p_is_prim(k);
131+ }
132+}
133+
134+/* At '(': decide whether this is a cast; returns the type node (consuming `(Type)`) or NULL. */
135+static Node *try_cast(Parser *p) {
136+ int save = p->pos;
137+ p_next(p);
138+ Node *t = p_type_opt(p);
139+ if (t && p_is(p, JT_RPAREN)) {
140+ JTokKind after = p_peek_at(p, 1)->kind;
141+ int prim = p_is_prim(((JToken *)&p->t.toks[save + 1])->kind) && t->flags == 0;
142+ if (prim ? (starts_unary_not_plus_minus(after) || after == JT_PLUS || after == JT_MINUS ||
143+ after == JT_INC || after == JT_DEC)
144+ : starts_unary_not_plus_minus(after)) {
145+ p_next(p);
146+ return t;
147+ }
148+ }
149+ p->pos = save;
150+ return NULL;
151+}
152+
153+static Node *primary(Parser *p) {
154+ JToken *t = p_peek(p);
155+ Node *n;
156+ switch (t->kind) {
157+ case JT_INT_LIT: n = p_node(p, N_LIT_INT); n->name = t->text; p_next(p); return n;
158+ case JT_LONG_LIT: n = p_node(p, N_LIT_LONG); n->name = t->text; p_next(p); return n;
159+ case JT_FLOAT_LIT: n = p_node(p, N_LIT_FLOAT); n->name = t->text; p_next(p); return n;
160+ case JT_DOUBLE_LIT: n = p_node(p, N_LIT_DOUBLE); n->name = t->text; p_next(p); return n;
161+ case JT_CHAR_LIT: n = p_node(p, N_LIT_CHAR); n->name = t->text; p_next(p); return n;
162+ case JT_STRING_LIT: n = p_node(p, N_LIT_STRING); n->name = t->text; p_next(p); return n;
163+ case JT_TRUE: n = p_node(p, N_LIT_BOOL); n->name = "true"; p_next(p); return n;
164+ case JT_FALSE: n = p_node(p, N_LIT_BOOL); n->name = "false"; p_next(p); return n;
165+ case JT_NULL: n = p_node(p, N_LIT_NULL); n->name = "null"; p_next(p); return n;
166+ case JT_THIS:
167+ n = p_node(p, N_THIS);
168+ p_next(p);
169+ return n;
170+ case JT_SUPER:
171+ n = p_node(p, N_SUPER);
172+ p_next(p);
173+ return n;
174+ case JT_NEW:
175+ return new_expr(p);
176+ case JT_SWITCH:
177+ return p_switch(p, 1);
178+ case JT_IDENT:
179+ if (lambda_ahead(p)) return lambda(p);
180+ if (p_peek_at(p, 1)->kind == JT_LPAREN) {
181+ n = p_node(p, N_CALL);
182+ n->name = t->text;
183+ p_next(p);
184+ args(p, &n->l1);
185+ return n;
186+ }
187+ n = p_node(p, N_NAME);
188+ n->name = t->text;
189+ p_next(p);
190+ return n;
191+ case JT_LPAREN: {
192+ if (lambda_ahead(p)) return lambda(p);
193+ p_next(p);
194+ n = p_expr(p);
195+ p_expect(p, JT_RPAREN, "')'");
196+ return n;
197+ }
198+ default:
199+ if (p_is_prim(t->kind) && p_peek_at(p, 1)->kind == JT_COLONCOLON) {
200+ n = p_node(p, N_NAME);
201+ n->name = t->text;
202+ p_next(p);
203+ return n;
204+ }
205+ p_fail(p, "expected an expression but found %s",
206+ t->kind == JT_EOF ? "end of file" : j_fmt("'%s'", t->text));
207+ }
208+ return NULL;
209+}
210+
211+static Node *postfix(Parser *p) {
212+ Node *e = primary(p);
213+ for (;;) {
214+ if (p_is(p, JT_DOT)) {
215+ p_next(p);
216+ if (p_is(p, JT_LT)) p_fail(p, "explicit generic method type arguments are not supported");
217+ Node *n;
218+ char *name = p_ident(p, "a member name");
219+ if (p_is(p, JT_LPAREN)) {
220+ n = p_node(p, N_CALL);
221+ n->a = e;
222+ n->name = name;
223+ if (e->kind == N_SUPER) n->flags = 1;
224+ args(p, &n->l1);
225+ } else {
226+ n = p_node(p, N_FIELDACC);
227+ n->a = e;
228+ n->name = name;
229+ }
230+ n->line = e->line;
231+ n->col = e->col;
232+ e = n;
233+ } else if (p_is(p, JT_LBRACKET)) {
234+ Node *n = p_node(p, N_INDEX);
235+ p_next(p);
236+ n->a = e;
237+ n->b = p_expr(p);
238+ n->line = e->line;
239+ n->col = e->col;
240+ p_expect(p, JT_RBRACKET, "']'");
241+ e = n;
242+ } else if (p_is(p, JT_INC) || p_is(p, JT_DEC)) {
243+ Node *n = p_node(p, N_POSTFIX);
244+ n->name = p_peek(p)->text;
245+ p_next(p);
246+ n->a = e;
247+ n->line = e->line;
248+ n->col = e->col;
249+ e = n;
250+ } else if (p_is(p, JT_COLONCOLON)) {
251+ Node *n = p_node(p, N_METHODREF);
252+ p_next(p);
253+ n->a = e;
254+ n->name = p_is(p, JT_NEW) ? "new" : NULL;
255+ if (n->name) p_next(p);
256+ else n->name = p_ident(p, "a method name");
257+ n->line = e->line;
258+ n->col = e->col;
259+ e = n;
260+ } else {
261+ return e;
262+ }
263+ }
264+}
265+
266+static Node *unary(Parser *p) {
267+ JToken *t = p_peek(p);
268+ if (t->kind == JT_PLUS || t->kind == JT_MINUS || t->kind == JT_BANG || t->kind == JT_TILDE ||
269+ t->kind == JT_INC || t->kind == JT_DEC) {
270+ Node *n = p_node(p, N_UNARY);
271+ n->name = t->text;
272+ p_next(p);
273+ n->a = unary(p);
274+ return n;
275+ }
276+ if (t->kind == JT_LPAREN && !lambda_ahead(p)) {
277+ Node *line_node = p_node(p, N_CAST);
278+ Node *type = try_cast(p);
279+ if (type) {
280+ line_node->a = type;
281+ line_node->b = unary(p);
282+ return line_node;
283+ }
284+ }
285+ return postfix(p);
286+}
287+
288+/* Reads a binary operator at the cursor. Returns precedence (0 = none) and sets the
289+ * operator text and how many tokens it spans. `>` runs are merged into shifts here. */
290+static int peek_binop(Parser *p, const char **text, int *ntok) {
291+ JToken *t = p_peek(p);
292+ *ntok = 1;
293+ switch (t->kind) {
294+ case JT_OROR: *text = "||"; return 1;
295+ case JT_ANDAND: *text = "&&"; return 2;
296+ case JT_PIPE: *text = "|"; return 3;
297+ case JT_CARET: *text = "^"; return 4;
298+ case JT_AMP: *text = "&"; return 5;
299+ case JT_EQ: *text = "=="; return 6;
300+ case JT_NE: *text = "!="; return 6;
301+ case JT_LT: *text = "<"; return 7;
302+ case JT_LE: *text = "<="; return 7;
303+ case JT_GE: *text = ">="; return 7;
304+ case JT_INSTANCEOF: *text = "instanceof"; return 7;
305+ case JT_SHL: *text = "<<"; return 8;
306+ case JT_PLUS: *text = "+"; return 9;
307+ case JT_MINUS: *text = "-"; return 9;
308+ case JT_STAR: *text = "*"; return 10;
309+ case JT_SLASH: *text = "/"; return 10;
310+ case JT_PERCENT: *text = "%"; return 10;
311+ case JT_GT: {
312+ JToken *n1 = p_peek_at(p, 1);
313+ if (n1->kind == JT_GE && n1->adj) return 0; /* >>= */
314+ if (n1->kind == JT_GT && n1->adj) {
315+ JToken *n2 = p_peek_at(p, 2);
316+ if (n2->kind == JT_GE && n2->adj) return 0; /* >>>= */
317+ if (n2->kind == JT_GT && n2->adj) {
318+ *text = ">>>";
319+ *ntok = 3;
320+ return 8;
321+ }
322+ *text = ">>";
323+ *ntok = 2;
324+ return 8;
325+ }
326+ *text = ">";
327+ return 7;
328+ }
329+ default:
330+ return 0;
331+ }
332+}
333+
334+static Node *binary(Parser *p, int min_prec) {
335+ Node *lhs = unary(p);
336+ for (;;) {
337+ const char *op;
338+ int ntok;
339+ int prec = peek_binop(p, &op, &ntok);
340+ if (prec == 0 || prec < min_prec) return lhs;
341+ Node *n = p_node(p, N_BINARY);
342+ n->name = op;
343+ n->line = lhs->line;
344+ n->col = lhs->col;
345+ for (int i = 0; i < ntok; i++) p_next(p);
346+ if (strcmp(op, "instanceof") == 0) {
347+ n->kind = N_INSTANCEOF;
348+ n->a = lhs;
349+ if (p_is(p, JT_FINAL)) p_next(p);
350+ p->no_nullable++;
351+ n->b = p_type(p);
352+ p->no_nullable--;
353+ n->name = NULL;
354+ if (p_is(p, JT_IDENT)) n->name = p_ident(p, NULL);
355+ lhs = n;
356+ continue;
357+ }
358+ n->a = lhs;
359+ n->b = binary(p, prec + 1);
360+ lhs = n;
361+ }
362+}
363+
364+static Node *ternary(Parser *p) {
365+ Node *c = binary(p, 1);
366+ if (!p_is(p, JT_QUESTION)) return c;
367+ Node *n = p_node(p, N_COND);
368+ n->line = c->line;
369+ n->col = c->col;
370+ p_next(p);
371+ n->a = c;
372+ n->b = p_assign_expr(p);
373+ p_expect(p, JT_COLON, "':'");
374+ n->c = p_assign_expr(p);
375+ return n;
376+}
377+
378+static const char *assign_op(Parser *p, int *ntok) {
379+ *ntok = 1;
380+ switch (p_peek(p)->kind) {
381+ case JT_ASSIGN: return "=";
382+ case JT_PLUS_ASSIGN: return "+=";
383+ case JT_MINUS_ASSIGN: return "-=";
384+ case JT_STAR_ASSIGN: return "*=";
385+ case JT_SLASH_ASSIGN: return "/=";
386+ case JT_PERCENT_ASSIGN: return "%=";
387+ case JT_AMP_ASSIGN: return "&=";
388+ case JT_PIPE_ASSIGN: return "|=";
389+ case JT_CARET_ASSIGN: return "^=";
390+ case JT_SHL_ASSIGN: return "<<=";
391+ case JT_GT: {
392+ JToken *n1 = p_peek_at(p, 1);
393+ if (n1->kind == JT_GE && n1->adj) {
394+ *ntok = 2;
395+ return ">>=";
396+ }
397+ if (n1->kind == JT_GT && n1->adj) {
398+ JToken *n2 = p_peek_at(p, 2);
399+ if (n2->kind == JT_GE && n2->adj) {
400+ *ntok = 3;
401+ return ">>>=";
402+ }
403+ }
404+ return NULL;
405+ }
406+ default:
407+ return NULL;
408+ }
409+}
410+
411+Node *p_assign_expr(Parser *p) {
412+ if (lambda_ahead(p)) return lambda(p);
413+ Node *lhs = ternary(p);
414+ int ntok;
415+ const char *op = assign_op(p, &ntok);
416+ if (!op) return lhs;
417+ Node *n = p_node(p, N_ASSIGN);
418+ n->line = lhs->line;
419+ n->col = lhs->col;
420+ n->name = op;
421+ for (int i = 0; i < ntok; i++) p_next(p);
422+ n->a = lhs;
423+ n->b = p_assign_expr(p);
424+ return n;
425+}
426+
427+Node *p_expr(Parser *p) { return p_assign_expr(p); }

front/src/jparse/C/jparse_stmt.cadded

+339−0
@@ -0,0 +1,339 @@
1+#include <stdlib.h>
2+
3+#include "jparse/H/jparse.h"
4+#include "jparse/H/jparse_internal.h"
5+
6+Node *p_block(Parser *p) {
7+ Node *b = p_node(p, N_BLOCK);
8+ p_expect(p, JT_LBRACE, "'{'");
9+ while (!p_is(p, JT_RBRACE)) {
10+ if (p_is(p, JT_EOF)) p_fail(p, "unexpected end of file: missing '}'");
11+ j_list_push(&b->l1, p_stmt(p));
12+ }
13+ p_expect(p, JT_RBRACE, "'}'");
14+ return b;
15+}
16+
17+int p_looks_like_local_decl(Parser *p) {
18+ JToken *t = p_peek(p);
19+ if (t->kind == JT_FINAL) return 1;
20+ if (p_is_prim(t->kind) && t->kind != JT_VOID) {
21+ return p_peek_at(p, 1)->kind != JT_COLONCOLON && p_peek_at(p, 1)->kind != JT_DOT;
22+ }
23+ if (t->kind != JT_IDENT) return 0;
24+ if (strcmp(t->text, "var") == 0 && p_peek_at(p, 1)->kind == JT_IDENT) return 1;
25+ int save = p->pos;
26+ Node *ty = p_type_opt(p);
27+ int ok = 0;
28+ if (ty && p_is(p, JT_IDENT)) {
29+ JTokKind k = p_peek_at(p, 1)->kind;
30+ ok = (k == JT_ASSIGN || k == JT_SEMI || k == JT_COMMA || k == JT_COLON || k == JT_RPAREN);
31+ }
32+ p->pos = save;
33+ return ok;
34+}
35+
36+Node *p_array_init(Parser *p, Node *type) {
37+ Node *na = p_node(p, N_NEWARRAY);
38+ na->a = type;
39+ na->flags = type->flags;
40+ Node *init = p_node(p, N_ARRAYINIT);
41+ p_expect(p, JT_LBRACE, "'{'");
42+ while (!p_is(p, JT_RBRACE)) {
43+ if (p_is(p, JT_LBRACE)) {
44+ Node *inner = p_array_init(p, type);
45+ inner->flags = na->flags - 1;
46+ j_list_push(&init->l1, inner);
47+ } else {
48+ j_list_push(&init->l1, p_assign_expr(p));
49+ }
50+ if (!p_accept(p, JT_COMMA)) break;
51+ }
52+ p_expect(p, JT_RBRACE, "'}'");
53+ na->b = init;
54+ return na;
55+}
56+
57+Node *p_local_decl(Parser *p, int final_flag, int require_semi) {
58+ int flags = final_flag ? M_FINAL : 0;
59+ while (p_is(p, JT_FINAL)) {
60+ flags |= M_FINAL;
61+ p_next(p);
62+ }
63+ Node *type = NULL;
64+ if (p_is_word(p, "var") && p_peek_at(p, 1)->kind == JT_IDENT) {
65+ p_next(p);
66+ } else {
67+ type = p_type(p);
68+ }
69+ Node *first = NULL, *seq = NULL;
70+ for (;;) {
71+ Node *l = p_node(p, N_LOCAL);
72+ l->flags = flags;
73+ l->a = type;
74+ l->name = p_ident(p, "a variable name");
75+ if (p_accept(p, JT_ASSIGN)) {
76+ if (p_is(p, JT_LBRACE)) {
77+ if (!type) p_fail(p, "cannot infer the type of an array initializer with 'var'");
78+ l->b = p_array_init(p, type);
79+ } else {
80+ l->b = p_assign_expr(p);
81+ }
82+ }
83+ if (!first) first = l;
84+ else {
85+ if (!seq) {
86+ seq = p_node(p, N_SEQ);
87+ j_list_push(&seq->l1, first);
88+ }
89+ j_list_push(&seq->l1, l);
90+ }
91+ if (!p_accept(p, JT_COMMA)) break;
92+ }
93+ if (require_semi) p_expect(p, JT_SEMI, "';'");
94+ return seq ? seq : first;
95+}
96+
97+static Node *paren_cond(Parser *p) {
98+ p_expect(p, JT_LPAREN, "'('");
99+ Node *c = p_expr(p);
100+ p_expect(p, JT_RPAREN, "')'");
101+ return c;
102+}
103+
104+static Node *for_stmt(Parser *p) {
105+ Node *f = p_node(p, N_FOR);
106+ p_next(p);
107+ p_expect(p, JT_LPAREN, "'('");
108+ if (!p_is(p, JT_SEMI)) {
109+ if (p_looks_like_local_decl(p)) {
110+ /* foreach? `Type name :` */
111+ int save = p->pos;
112+ int is_each = 0;
113+ while (p_is(p, JT_FINAL)) p_next(p);
114+ Node *ty = NULL;
115+ if (p_is_word(p, "var") && p_peek_at(p, 1)->kind == JT_IDENT) p_next(p);
116+ else ty = p_type_opt(p);
117+ if ((ty || p->pos > save) && p_is(p, JT_IDENT) && p_peek_at(p, 1)->kind == JT_COLON) is_each = 1;
118+ if (is_each) {
119+ Node *e = p_node(p, N_FOREACH);
120+ e->line = f->line;
121+ e->col = f->col;
122+ e->a = ty;
123+ e->name = p_ident(p, NULL);
124+ p_expect(p, JT_COLON, "':'");
125+ e->b = p_expr(p);
126+ p_expect(p, JT_RPAREN, "')'");
127+ e->c = p_stmt(p);
128+ return e;
129+ }
130+ p->pos = save;
131+ j_list_push(&f->l1, p_local_decl(p, 0, 0));
132+ } else {
133+ do {
134+ Node *es = p_node(p, N_EXPRSTMT);
135+ es->a = p_expr(p);
136+ j_list_push(&f->l1, es);
137+ } while (p_accept(p, JT_COMMA));
138+ }
139+ }
140+ p_expect(p, JT_SEMI, "';'");
141+ if (!p_is(p, JT_SEMI)) f->a = p_expr(p);
142+ p_expect(p, JT_SEMI, "';'");
143+ if (!p_is(p, JT_RPAREN)) {
144+ do {
145+ j_list_push(&f->l2, p_expr(p));
146+ } while (p_accept(p, JT_COMMA));
147+ }
148+ p_expect(p, JT_RPAREN, "')'");
149+ f->b = p_stmt(p);
150+ return f;
151+}
152+
153+static Node *try_stmt(Parser *p) {
154+ Node *t = p_node(p, N_TRY);
155+ p_next(p);
156+ if (p_is(p, JT_LPAREN)) p_fail(p, "try-with-resources is not supported");
157+ t->b = p_block(p);
158+ while (p_is(p, JT_CATCH)) {
159+ Node *c = p_node(p, N_CATCH);
160+ p_next(p);
161+ p_expect(p, JT_LPAREN, "'('");
162+ while (p_accept(p, JT_FINAL)) {}
163+ do {
164+ j_list_push(&c->l1, p_type(p));
165+ } while (p_accept(p, JT_PIPE));
166+ c->name = p_ident(p, "an exception variable name");
167+ p_expect(p, JT_RPAREN, "')'");
168+ c->b = p_block(p);
169+ j_list_push(&t->l1, c);
170+ }
171+ if (p_accept(p, JT_FINALLY)) t->c = p_block(p);
172+ if (t->l1.count == 0 && !t->c) p_fail(p, "'try' needs at least one 'catch' or a 'finally'");
173+ return t;
174+}
175+
176+static Node *case_label(Parser *p) {
177+ /* type pattern: `Type name` followed by `->` or `:` */
178+ int save = p->pos;
179+ if (p_is(p, JT_IDENT)) {
180+ Node *ty = p_type_opt(p);
181+ if (ty && p_is(p, JT_IDENT) &&
182+ (p_peek_at(p, 1)->kind == JT_ARROW || p_peek_at(p, 1)->kind == JT_COLON)) {
183+ Node *tp = p_node(p, N_TYPEPATTERN);
184+ tp->a = ty;
185+ tp->name = p_ident(p, NULL);
186+ return tp;
187+ }
188+ }
189+ p->pos = save;
190+ p->no_lambda++;
191+ Node *e = p_assign_expr(p);
192+ p->no_lambda--;
193+ return e;
194+}
195+
196+Node *p_switch(Parser *p, int is_expr) {
197+ Node *sw = p_node(p, N_SWITCH);
198+ sw->flags = is_expr;
199+ p_next(p);
200+ sw->a = paren_cond(p);
201+ p_expect(p, JT_LBRACE, "'{'");
202+ Node *pending = NULL; /* stacked `case A: case B:` labels merge into one case */
203+ while (!p_is(p, JT_RBRACE)) {
204+ if (p_is(p, JT_EOF)) p_fail(p, "unexpected end of file in switch");
205+ Node *c = pending ? pending : p_node(p, N_CASE);
206+ pending = NULL;
207+ if (p_accept(p, JT_DEFAULT)) {
208+ c->flags = 1;
209+ } else {
210+ p_expect(p, JT_CASE, "'case' or 'default'");
211+ do {
212+ if (p_is(p, JT_NULL)) { /* case null */
213+ j_list_push(&c->l1, p_assign_expr(p));
214+ } else {
215+ j_list_push(&c->l1, case_label(p));
216+ }
217+ } while (p_accept(p, JT_COMMA));
218+ }
219+ if (p_accept(p, JT_ARROW)) {
220+ c->str = "->";
221+ Node *body;
222+ if (p_is(p, JT_LBRACE)) {
223+ body = p_block(p);
224+ } else if (p_is(p, JT_THROW)) {
225+ body = p_stmt(p);
226+ } else {
227+ body = p_node(p, N_EXPRSTMT);
228+ body->a = p_expr(p);
229+ p_expect(p, JT_SEMI, "';'");
230+ }
231+ c->b = body;
232+ } else {
233+ p_expect(p, JT_COLON, "':' or '->'");
234+ while (!p_is(p, JT_CASE) && !p_is(p, JT_DEFAULT) && !p_is(p, JT_RBRACE)) {
235+ if (p_is(p, JT_EOF)) p_fail(p, "unexpected end of file in switch");
236+ j_list_push(&c->l2, p_stmt(p));
237+ }
238+ if (c->l2.count == 0 && (p_is(p, JT_CASE) || p_is(p, JT_DEFAULT))) {
239+ if (p_is(p, JT_DEFAULT)) { /* `case A: default:` */
240+ c->flags = c->flags;
241+ }
242+ pending = c;
243+ continue;
244+ }
245+ }
246+ j_list_push(&sw->l1, c);
247+ }
248+ if (pending) j_list_push(&sw->l1, pending);
249+ p_expect(p, JT_RBRACE, "'}'");
250+ return sw;
251+}
252+
253+Node *p_stmt(Parser *p) {
254+ JToken *t = p_peek(p);
255+ Node *n;
256+ switch (t->kind) {
257+ case JT_LBRACE: return p_block(p);
258+ case JT_SEMI:
259+ n = p_node(p, N_EMPTY);
260+ p_next(p);
261+ return n;
262+ case JT_IF:
263+ n = p_node(p, N_IF);
264+ p_next(p);
265+ n->a = paren_cond(p);
266+ n->b = p_stmt(p);
267+ if (p_accept(p, JT_ELSE)) n->c = p_stmt(p);
268+ return n;
269+ case JT_WHILE:
270+ n = p_node(p, N_WHILE);
271+ p_next(p);
272+ n->a = paren_cond(p);
273+ n->b = p_stmt(p);
274+ return n;
275+ case JT_DO:
276+ n = p_node(p, N_DO);
277+ p_next(p);
278+ n->b = p_stmt(p);
279+ p_expect(p, JT_WHILE, "'while'");
280+ n->a = paren_cond(p);
281+ p_expect(p, JT_SEMI, "';'");
282+ return n;
283+ case JT_FOR: return for_stmt(p);
284+ case JT_RETURN:
285+ n = p_node(p, N_RETURN);
286+ p_next(p);
287+ if (!p_is(p, JT_SEMI)) n->a = p_expr(p);
288+ p_expect(p, JT_SEMI, "';'");
289+ return n;
290+ case JT_BREAK:
291+ n = p_node(p, N_BREAK);
292+ p_next(p);
293+ p_expect(p, JT_SEMI, "';'");
294+ return n;
295+ case JT_CONTINUE:
296+ n = p_node(p, N_CONTINUE);
297+ p_next(p);
298+ p_expect(p, JT_SEMI, "';'");
299+ return n;
300+ case JT_THROW:
301+ n = p_node(p, N_THROW);
302+ p_next(p);
303+ n->a = p_expr(p);
304+ p_expect(p, JT_SEMI, "';'");
305+ return n;
306+ case JT_TRY: return try_stmt(p);
307+ case JT_SWITCH: {
308+ Node *sw = p_switch(p, 0);
309+ return sw;
310+ }
311+ case JT_THIS:
312+ case JT_SUPER:
313+ if (p_peek_at(p, 1)->kind == JT_LPAREN) {
314+ n = p_node(p, N_CTORCALL);
315+ n->name = t->text;
316+ p_next(p);
317+ p_expect(p, JT_LPAREN, "'('");
318+ if (!p_is(p, JT_RPAREN)) {
319+ do {
320+ j_list_push(&n->l1, p_assign_expr(p));
321+ } while (p_accept(p, JT_COMMA));
322+ }
323+ p_expect(p, JT_RPAREN, "')'");
324+ p_expect(p, JT_SEMI, "';'");
325+ return n;
326+ }
327+ break;
328+ default:
329+ break;
330+ }
331+ if (t->kind == JT_CLASS || t->kind == JT_INTERFACE || t->kind == JT_ENUM) {
332+ p_fail(p, "local classes are not supported; declare the type at the top level or as a member");
333+ }
334+ if (p_looks_like_local_decl(p)) return p_local_decl(p, 0, 1);
335+ n = p_node(p, N_EXPRSTMT);
336+ n->a = p_expr(p);
337+ p_expect(p, JT_SEMI, "';'");
338+ return n;
339+}

front/src/jparse/D/.gitkeepadded

+0−0

No content changes. View the file on GitHub

front/src/jparse/H/jast.hadded

+100−0
@@ -0,0 +1,100 @@
1+#ifndef JAST_H
2+#define JAST_H
3+
4+#include "jcommon/H/jcommon.h"
5+
6+/* AST for new Ash. One generic node type keeps the checker and lowering compact.
7+ * Field conventions per kind are listed beside each enum entry. `type` and `sym`
8+ * are filled by the checker (jtypes) and read by lowering (jlower). */
9+
10+struct JType;
11+struct JClass;
12+struct JMember;
13+struct JLocal;
14+
15+enum {
16+ M_PUBLIC = 1, M_PRIVATE = 2, M_PROTECTED = 4, M_STATIC = 8, M_FINAL = 16,
17+ M_ABSTRACT = 32, M_NATIVE = 64, M_CONST = 128, M_SEALED = 256, M_DEFAULT = 512,
18+ M_VARARGS = 1024
19+};
20+
21+typedef enum {
22+ /* ---- types and declarations ---- */
23+ N_TYPE, /* name=qualified name or primitive, l1=type args, flags=dims, nullable=? */
24+ N_WILDCARD, /* a=bound or NULL, name="extends"/"super"/"" */
25+ N_TYPEPARAM, /* name, a=bound */
26+ N_UNIT, /* name=package, l1=imports, l2=type decls */
27+ N_IMPORT, /* name=dotted name, flags=1 for wildcard `.*` */
28+ N_CLASS, N_INTERFACE, N_ENUM, N_RECORD,
29+ /* name, flags=modifiers, l1=members, l2=type params, l3=extends(iface)/implements,
30+ * l4=enum constants / record components (N_PARAM), l5=permits, a=superclass type */
31+ N_FIELD, /* name, flags, a=type, b=init, l1=annotations */
32+ N_METHOD, /* name, flags, a=return type, l1=params, b=body, l2=type params, l3=throws, l4=annos */
33+ N_CTOR, /* like N_METHOD with a=NULL */
34+ N_INIT, /* static initializer: b=body, flags */
35+ N_PARAM, /* name, a=type (NULL for implicit lambda params), flags (M_FINAL, M_VARARGS) */
36+ N_ENUMCONST, /* name, l1=args */
37+ N_ANNO, /* name, str=optional argument */
38+ /* ---- statements ---- */
39+ N_BLOCK, /* l1 */
40+ N_SEQ, /* l1; declarations sharing a scope (int a, b;) */
41+ N_LOCAL, /* name, a=type (NULL for var), b=init, flags */
42+ N_EXPRSTMT, /* a */
43+ N_IF, /* a cond, b then, c else */
44+ N_WHILE, /* a cond, b body */
45+ N_DO, /* a cond, b body */
46+ N_FOR, /* l1 init, a cond, l2 update, b body */
47+ N_FOREACH, /* a=type (NULL for var), name, b=iterable, c=body */
48+ N_RETURN, /* a */
49+ N_BREAK, N_CONTINUE, N_EMPTY,
50+ N_THROW, /* a */
51+ N_TRY, /* b block, l1 catches, c finally */
52+ N_CATCH, /* l1 types, name, b block */
53+ N_SWITCH, /* a selector, l1 cases, flags=1 when used as expression */
54+ N_CASE, /* l1 labels (exprs / N_TYPEPATTERN), flags=1 default, str="->" for arrow form,
55+ * l2=body statements (colon form), b=arrow body (expr, block or throw) */
56+ N_TYPEPATTERN,/* a=type, name=binding */
57+ /* ---- expressions ---- */
58+ N_LIT_INT, N_LIT_LONG, N_LIT_FLOAT, N_LIT_DOUBLE, N_LIT_CHAR, N_LIT_STRING,
59+ N_LIT_BOOL, N_LIT_NULL, /* name=text */
60+ N_NAME, /* name */
61+ N_FIELDACC, /* a target, name */
62+ N_CALL, /* a target (NULL = unqualified), name, l1 args, flags=1 when target is `super` */
63+ N_CTORCALL, /* name="this"/"super", l1 args */
64+ N_NEW, /* a type, l1 args */
65+ N_NEWARRAY, /* a elem type, l1 dim exprs, flags=total dims, b=initializer */
66+ N_ARRAYINIT, /* l1 */
67+ N_INDEX, /* a, b */
68+ N_UNARY, /* name op, a */
69+ N_POSTFIX, /* name op, a */
70+ N_BINARY, /* name op, a, b */
71+ N_ASSIGN, /* name op ("=", "+=", ...), a, b */
72+ N_COND, /* a ? b : c */
73+ N_CAST, /* a type, b expr */
74+ N_INSTANCEOF, /* a expr, b type, name=binding or NULL */
75+ N_LAMBDA, /* l1 params, b body (expr or block) */
76+ N_METHODREF, /* a target, name */
77+ N_THIS,
78+ N_SUPER /* only as call/field target */
79+} NodeKind;
80+
81+typedef struct Node Node;
82+struct Node {
83+ NodeKind kind;
84+ int line, col;
85+ const char *name;
86+ const char *str;
87+ int flags;
88+ int nullable; /* N_TYPE: trailing `?` */
89+ Node *a, *b, *c;
90+ JList l1, l2, l3, l4, l5;
91+ const char *path;
92+ /* filled by the checker */
93+ struct JType *type;
94+ void *sym;
95+ void *sym2;
96+};
97+
98+Node *node_new(NodeKind kind, const char *path, int line, int col);
99+
100+#endif

front/src/jparse/H/jparse.hadded

+9−0
@@ -0,0 +1,9 @@
1+#ifndef JPARSE_H
2+#define JPARSE_H
3+
4+#include "jparse/H/jast.h"
5+
6+/* Parses one new-Ash source file into an N_UNIT. Syntax errors abort with a diagnostic. */
7+Node *jparse_unit(const char *path, const char *src);
8+
9+#endif

front/src/jparse/H/jparse_internal.hadded

+54−0
@@ -0,0 +1,54 @@
1+#ifndef JPARSE_INTERNAL_H
2+#define JPARSE_INTERNAL_H
3+
4+#include <string.h>
5+#include "jlex/H/jlex.h"
6+#include "jparse/H/jast.h"
7+
8+typedef struct {
9+ JTokens t;
10+ int pos;
11+ int no_nullable; /* set while parsing instanceof types (`x instanceof T ? a : b`) */
12+ int no_lambda; /* set while parsing case labels, where `X ->` is not a lambda */
13+} Parser;
14+
15+static inline JToken *p_peek(Parser *p) { return &p->t.toks[p->pos]; }
16+static inline JToken *p_peek_at(Parser *p, int n) {
17+ int i = p->pos + n;
18+ if (i >= p->t.count) i = p->t.count - 1;
19+ return &p->t.toks[i];
20+}
21+static inline int p_is(Parser *p, JTokKind k) { return p_peek(p)->kind == k; }
22+static inline int p_is_word(Parser *p, const char *w) {
23+ return p_peek(p)->kind == JT_IDENT && strcmp(p_peek(p)->text, w) == 0;
24+}
25+
26+JToken *p_next(Parser *p);
27+int p_accept(Parser *p, JTokKind k);
28+JToken *p_expect(Parser *p, JTokKind k, const char *what);
29+void p_fail(Parser *p, const char *fmt, ...) __attribute__((noreturn));
30+Node *p_node(Parser *p, NodeKind k);
31+char *p_ident(Parser *p, const char *what);
32+
33+/* types (jparse_decl.c) */
34+Node *p_type_opt(Parser *p); /* NULL when the tokens are not a type (no error) */
35+Node *p_type(Parser *p);
36+int p_is_prim(JTokKind k);
37+
38+/* declarations (jparse_decl.c) */
39+Node *p_class_body_decl(Parser *p, Node *owner);
40+int p_parse_modifiers(Parser *p, JList *annos);
41+Node *p_type_decl(Parser *p, int mods, JList *annos);
42+void p_params(Parser *p, Node *method);
43+
44+/* statements and expressions (jparse_stmt.c, jparse_expr.c) */
45+Node *p_block(Parser *p);
46+Node *p_array_init(Parser *p, Node *type); /* `{a, b}` initializer for an array of `type` */
47+Node *p_stmt(Parser *p);
48+Node *p_expr(Parser *p);
49+Node *p_assign_expr(Parser *p);
50+Node *p_switch(Parser *p, int is_expr);
51+Node *p_local_decl(Parser *p, int final_flag, int require_semi);
52+int p_looks_like_local_decl(Parser *p);
53+
54+#endif

front/src/jparse/O/.gitkeepadded

+0−0

No content changes. View the file on GitHub

front/src/jtypes/D/.gitkeepadded

+0−0

No content changes. View the file on GitHub

front/src/jtypes/O/.gitkeepadded

+0−0

No content changes. View the file on GitHub

front/tests/cases/syntax/KitchenSink.ashadded

+150−0
@@ -0,0 +1,150 @@
1+package demo.app;
2+
3+import ash.util.List;
4+import ash.util.*;
5+
6+/// A documentation comment.
7+public sealed interface Shape permits Circle, Square {
8+ double area();
9+ default String describe() {
10+ return "shape with area " + area();
11+ }
12+ static Shape unit() {
13+ return new Square(1.0);
14+ }
15+}
16+
17+record Circle(double radius) implements Shape {
18+ public double area() {
19+ return 3.14159 * radius * radius;
20+ }
21+}
22+
23+record Square(double side) implements Shape {
24+ Square {
25+ if (side < 0) {
26+ throw new IllegalArgumentException("negative side");
27+ }
28+ }
29+ public double area() {
30+ return side * side;
31+ }
32+}
33+
34+enum Color {
35+ RED(1), GREEN(2), BLUE(3);
36+
37+ private final int code;
38+
39+ Color(int code) {
40+ this.code = code;
41+ }
42+
43+ public int getCode() {
44+ return code;
45+ }
46+}
47+
48+abstract class Animal<T extends Comparable<T>> {
49+ protected final String name;
50+ private int legs = 4, tails = 1;
51+ static final int MAX = 10;
52+ const int LIMIT = 5;
53+
54+ protected Animal(String name) {
55+ this.name = name;
56+ }
57+
58+ public abstract String speak();
59+
60+ @Override
61+ public String toString() {
62+ return name + ":" + speak();
63+ }
64+}
65+
66+public class KitchenSink extends Animal<String> implements Runnable {
67+ private String? nickname = null;
68+ private int[] data = new int[10];
69+ private int[][] grid = {{1, 2}, {3, 4}};
70+
71+ static {
72+ System.out.println("static init");
73+ }
74+
75+ public KitchenSink() {
76+ super("sink");
77+ }
78+
79+ public String speak() {
80+ return "glug";
81+ }
82+
83+ public void run() {
84+ int x = 5;
85+ long y = 10L;
86+ var z = x + y * 2;
87+ final double d = 1.5e3;
88+ char c = 'a';
89+ bool flag = x > 3 && !(y < 2) || false;
90+ x += 3;
91+ x >>= 1;
92+ x = x >>> 2 << 1;
93+ List<Map<String, Integer>> nested = new ArrayList<>();
94+ int r = flag ? 1 : 2;
95+ String s = (String) null;
96+ int cast = (int) d;
97+ if (x > 3) {
98+ x++;
99+ } else if (x < 0) {
100+ x--;
101+ } else {
102+ x = 0;
103+ }
104+ for (int i = 0; i < 10; i++) {
105+ if (i == 3) { continue; }
106+ if (i == 7) { break; }
107+ }
108+ for (String item : nested.stream().map(m -> m.toString()).toList()) {
109+ System.out.println(item);
110+ }
111+ while (x < 100) { x *= 2; }
112+ do { x--; } while (x > 50);
113+ switch (x) {
114+ case 1:
115+ case 2:
116+ x = 3;
117+ break;
118+ default:
119+ x = 4;
120+ }
121+ String label = switch (Color.RED) {
122+ case RED -> "r";
123+ case GREEN, BLUE -> "gb";
124+ };
125+ Object o = label;
126+ if (o instanceof String str && str.length() > 0) {
127+ System.out.println(str);
128+ }
129+ try {
130+ throw new RuntimeException("x");
131+ } catch (IllegalArgumentException | IllegalStateException e) {
132+ System.out.println(e.getMessage());
133+ } catch (Exception e) {
134+ throw e;
135+ } finally {
136+ x = 0;
137+ }
138+ Function<Integer, Integer> f = (a) -> a + 1;
139+ Function<Integer, Integer> g = n -> { return n * 2; };
140+ Runnable rr = this::run;
141+ Supplier<List<String>> sup = ArrayList::new;
142+ nested.forEach(System.out::println);
143+ int[] arr = new int[]{1, 2, 3};
144+ arr[0] = arr[1] + arr[2];
145+ }
146+
147+ public static void main(String[] args) {
148+ new KitchenSink().run();
149+ }
150+}