← Commit history
parser: add AST and Java-like declaration, statement and expression parsing
lennoxrose committed on Oct 17, 2026 at 16:46 UTC
15 files changed+1555−0
front/src/jdriver/D/.gitkeepadded
+0−0No content changes. View the file on GitHub
front/src/jdriver/O/.gitkeepadded
+0−0No content changes. View the file on GitHub
front/src/jlower/D/.gitkeepadded
+0−0No content changes. View the file on GitHub
front/src/jlower/O/.gitkeepadded
+0−0No 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 the289
+ * 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−0No content changes. View the file on GitHub
front/src/jparse/H/jast.hadded
+100−0@@ -0,0 +1,100 @@1
+#ifndef JAST_H2
+#define JAST_H3
+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 = 102419
+};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
+#endiffront/src/jparse/H/jparse.hadded
+9−0@@ -0,0 +1,9 @@1
+#ifndef JPARSE_H2
+#define JPARSE_H3
+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
+#endiffront/src/jparse/H/jparse_internal.hadded
+54−0@@ -0,0 +1,54 @@1
+#ifndef JPARSE_INTERNAL_H2
+#define JPARSE_INTERNAL_H3
+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
+#endiffront/src/jparse/O/.gitkeepadded
+0−0No content changes. View the file on GitHub
front/src/jtypes/D/.gitkeepadded
+0−0No content changes. View the file on GitHub
front/src/jtypes/O/.gitkeepadded
+0−0No 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
+ @Override61
+ 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
+}