ww: hare-feature batch (_, const, [_]T, ..., size/offset, assert, for-else)
This commit is contained in:
13
lib/ww/CLAUDE.md
Normal file
13
lib/ww/CLAUDE.md
Normal file
@@ -0,0 +1,13 @@
|
||||
lib/ww — frontend in ww (lex, parse, sym, typ, ast). Mirrors `cmd/wcc/` (the C bootstrap frontend). Compiled by `cmd/w6c` against the wwstage cgen.
|
||||
|
||||
Style:
|
||||
- Hare-shaped syntax. Trailing `;`. `=` after fn/type signatures. `export` for visibility.
|
||||
- Plan 9 names: run-together lowercase (`parsefile`, `lexnext`, `mksym`). No snake_case. No CamelCase.
|
||||
- ww-side type names are lowercase (`node`, `sym`, `typ`); the C-side equivalents in `cmd/wcc/ww.h` are capitalized (`Node`, `Sym`, `Type`) per Plan 9 cc convention.
|
||||
|
||||
This directory is **not** a Hare-style stdlib module — it's a compiler-frontend port, so structural fidelity to `cmd/wcc/` beats Hare API mimicry. The "follow Hare APIs" rule from the root CLAUDE.md applies to peer `lib/*` modules (fmt, io, bufio, strings, ...), not here.
|
||||
|
||||
When porting from `cmd/wcc/*.c`:
|
||||
- Keep the same data layout. The selfhost goal is byte-compatible structures so dump/inspect tools work either way.
|
||||
- Read `cmd/wcc/ww.h` first for canonical field names and ordering.
|
||||
- See `selfhost/CLAUDE.md` for the wwstage cgen traps to avoid (two-level field write, `(scalar,str)` tuple return, `def : str`, undersized `amalloc`).
|
||||
@@ -444,10 +444,18 @@ fn lexident(l: *lex, start: *pos, out: *tok) void = {
|
||||
};
|
||||
let n: u64 = l.lpos - begin;
|
||||
let p: *u8 = l.src + begin;
|
||||
let k: i32 = kwlookup(p, n: i32);
|
||||
out.file = start.file;
|
||||
out.line = start.line;
|
||||
out.col = start.col;
|
||||
// Bare '_' is the discard marker. `_x`, `_1` are normal idents.
|
||||
if (n == 1u64) {
|
||||
if (p[0] == 95u8) {
|
||||
out.kind = TK_UNDER;
|
||||
out.text = astrndup(l.a, p, n);
|
||||
return;
|
||||
};
|
||||
};
|
||||
let k: i32 = kwlookup(p, n: i32);
|
||||
if (k != TK_NONE) {
|
||||
out.kind = k;
|
||||
} else {
|
||||
|
||||
@@ -48,62 +48,64 @@ def TK_FALSE: i32 = 28;
|
||||
def TK_AS: i32 = 29;
|
||||
def TK_STATIC: i32 = 30;
|
||||
def TK_MATCH: i32 = 31;
|
||||
def TK_CONST: i32 = 32;
|
||||
def TK_UNDER: i32 = 33;
|
||||
|
||||
def TK_LPAREN: i32 = 32;
|
||||
def TK_RPAREN: i32 = 33;
|
||||
def TK_LBRACE: i32 = 34;
|
||||
def TK_RBRACE: i32 = 35;
|
||||
def TK_LBRACK: i32 = 36;
|
||||
def TK_RBRACK: i32 = 37;
|
||||
def TK_COMMA: i32 = 38;
|
||||
def TK_SEMI: i32 = 39;
|
||||
def TK_COLON: i32 = 40;
|
||||
def TK_DOT: i32 = 41;
|
||||
def TK_ELLIPSIS: i32 = 42;
|
||||
def TK_DOTDOT: i32 = 43;
|
||||
def TK_AT: i32 = 44;
|
||||
def TK_QUESTION: i32 = 45;
|
||||
def TK_LPAREN: i32 = 34;
|
||||
def TK_RPAREN: i32 = 35;
|
||||
def TK_LBRACE: i32 = 36;
|
||||
def TK_RBRACE: i32 = 37;
|
||||
def TK_LBRACK: i32 = 38;
|
||||
def TK_RBRACK: i32 = 39;
|
||||
def TK_COMMA: i32 = 40;
|
||||
def TK_SEMI: i32 = 41;
|
||||
def TK_COLON: i32 = 42;
|
||||
def TK_DOT: i32 = 43;
|
||||
def TK_ELLIPSIS: i32 = 44;
|
||||
def TK_DOTDOT: i32 = 45;
|
||||
def TK_AT: i32 = 46;
|
||||
def TK_QUESTION: i32 = 47;
|
||||
|
||||
def TK_ASSIGN: i32 = 46;
|
||||
def TK_PLUSEQ: i32 = 47;
|
||||
def TK_MINUSEQ: i32 = 48;
|
||||
def TK_STAREQ: i32 = 49;
|
||||
def TK_SLASHEQ: i32 = 50;
|
||||
def TK_PERCENTEQ: i32 = 51;
|
||||
def TK_AMPEQ: i32 = 52;
|
||||
def TK_PIPEEQ: i32 = 53;
|
||||
def TK_CARETEQ: i32 = 54;
|
||||
def TK_LSHIFTEQ: i32 = 55;
|
||||
def TK_RSHIFTEQ: i32 = 56;
|
||||
def TK_ASSIGN: i32 = 48;
|
||||
def TK_PLUSEQ: i32 = 49;
|
||||
def TK_MINUSEQ: i32 = 50;
|
||||
def TK_STAREQ: i32 = 51;
|
||||
def TK_SLASHEQ: i32 = 52;
|
||||
def TK_PERCENTEQ: i32 = 53;
|
||||
def TK_AMPEQ: i32 = 54;
|
||||
def TK_PIPEEQ: i32 = 55;
|
||||
def TK_CARETEQ: i32 = 56;
|
||||
def TK_LSHIFTEQ: i32 = 57;
|
||||
def TK_RSHIFTEQ: i32 = 58;
|
||||
|
||||
def TK_PLUS: i32 = 57;
|
||||
def TK_MINUS: i32 = 58;
|
||||
def TK_STAR: i32 = 59;
|
||||
def TK_SLASH: i32 = 60;
|
||||
def TK_PERCENT: i32 = 61;
|
||||
def TK_AMP: i32 = 62;
|
||||
def TK_PIPE: i32 = 63;
|
||||
def TK_CARET: i32 = 64;
|
||||
def TK_TILDE: i32 = 65;
|
||||
def TK_LSHIFT: i32 = 66;
|
||||
def TK_RSHIFT: i32 = 67;
|
||||
def TK_PLUS: i32 = 59;
|
||||
def TK_MINUS: i32 = 60;
|
||||
def TK_STAR: i32 = 61;
|
||||
def TK_SLASH: i32 = 62;
|
||||
def TK_PERCENT: i32 = 63;
|
||||
def TK_AMP: i32 = 64;
|
||||
def TK_PIPE: i32 = 65;
|
||||
def TK_CARET: i32 = 66;
|
||||
def TK_TILDE: i32 = 67;
|
||||
def TK_LSHIFT: i32 = 68;
|
||||
def TK_RSHIFT: i32 = 69;
|
||||
|
||||
def TK_EQ: i32 = 68;
|
||||
def TK_NEQ: i32 = 69;
|
||||
def TK_LT: i32 = 70;
|
||||
def TK_LE: i32 = 71;
|
||||
def TK_GT: i32 = 72;
|
||||
def TK_GE: i32 = 73;
|
||||
def TK_EQ: i32 = 70;
|
||||
def TK_NEQ: i32 = 71;
|
||||
def TK_LT: i32 = 72;
|
||||
def TK_LE: i32 = 73;
|
||||
def TK_GT: i32 = 74;
|
||||
def TK_GE: i32 = 75;
|
||||
|
||||
def TK_AND: i32 = 74;
|
||||
def TK_OR: i32 = 75;
|
||||
def TK_NOT: i32 = 76;
|
||||
def TK_AND: i32 = 76;
|
||||
def TK_OR: i32 = 77;
|
||||
def TK_NOT: i32 = 78;
|
||||
|
||||
def TK_LARROW: i32 = 77;
|
||||
def TK_ARROW: i32 = 78;
|
||||
def TK_FATARROW: i32 = 79;
|
||||
def TK_LARROW: i32 = 79;
|
||||
def TK_ARROW: i32 = 80;
|
||||
def TK_FATARROW: i32 = 81;
|
||||
|
||||
def TK_LAST: i32 = 80;
|
||||
def TK_LAST: i32 = 82;
|
||||
|
||||
// ---- Pos / Tok --------------------------------------------------------
|
||||
//
|
||||
@@ -152,6 +154,7 @@ export fn kwlookup(p: *u8, n: i32) i32 = {
|
||||
if (streqn(p, "break", n)) { return TK_BREAK; };
|
||||
if (streqn(p, "case", n)) { return TK_CASE; };
|
||||
if (streqn(p, "chan", n)) { return TK_CHAN; };
|
||||
if (streqn(p, "const", n)) { return TK_CONST; };
|
||||
if (streqn(p, "continue", n)) { return TK_CONTINUE; };
|
||||
if (streqn(p, "def", n)) { return TK_DEF; };
|
||||
if (streqn(p, "defer", n)) { return TK_DEFER; };
|
||||
@@ -214,6 +217,8 @@ export fn tokname(k: i32) str = {
|
||||
if (k == TK_AS) { return "as"; };
|
||||
if (k == TK_STATIC) { return "static"; };
|
||||
if (k == TK_MATCH) { return "match"; };
|
||||
if (k == TK_CONST) { return "const"; };
|
||||
if (k == TK_UNDER) { return "_"; };
|
||||
|
||||
if (k == TK_LPAREN) { return "("; };
|
||||
if (k == TK_RPAREN) { return ")"; };
|
||||
|
||||
@@ -40,11 +40,15 @@ fn parselet(p: *parser, exported: i32) *node = {
|
||||
let pf: str = p.curfile;
|
||||
let pl: i32 = p.curline;
|
||||
let pc: i32 = p.curcol;
|
||||
advance(p); // past `let`
|
||||
// Accept `let` or `const`. Const-bound bindings are marked via
|
||||
// n.op = TK_CONST so the checker can reject reassignment.
|
||||
let is_const: i32 = 0;
|
||||
if (p.curkind == TK_CONST) { is_const = 1; };
|
||||
advance(p);
|
||||
let n: *node = newnode(p.a, N_LET, pf, pl, pc);
|
||||
n.module = p.l.module;
|
||||
let id: str;
|
||||
expectident(p, &id);
|
||||
expectbindname(p, &id);
|
||||
n.str = id;
|
||||
if (accepttok(p, TK_COLON)) {
|
||||
n.lhs = parsetype(p);
|
||||
@@ -54,6 +58,7 @@ fn parselet(p: *parser, exported: i32) *node = {
|
||||
};
|
||||
expecttok(p, TK_SEMI, "expected ';' after let");
|
||||
n.exported = exported;
|
||||
if (is_const != 0) { n.op = TK_CONST; };
|
||||
return n;
|
||||
};
|
||||
|
||||
@@ -89,10 +94,10 @@ fn parseparams(p: *parser) *node = {
|
||||
let pl: i32 = p.curline;
|
||||
let pc: i32 = p.curcol;
|
||||
let n: *node = newnode(p.a, N_PARAM, pf, pl, pc);
|
||||
// Param form: IDENT ':' type. Anonymous-type-only params (used
|
||||
// in fn type expressions) aren't yet wired here.
|
||||
// Param form: (IDENT|'_') ':' type. Anonymous-type-only params
|
||||
// (used in fn type expressions) aren't yet wired here.
|
||||
let id: str;
|
||||
expectident(p, &id);
|
||||
expectbindname(p, &id);
|
||||
n.str = id;
|
||||
expecttok(p, TK_COLON, "expected ':' in parameter");
|
||||
n.lhs = parsetype(p);
|
||||
|
||||
@@ -4,6 +4,18 @@ use os;
|
||||
use mem;
|
||||
use tok;
|
||||
|
||||
// streq_local — str-to-str compare. Inlined here to avoid a cross-
|
||||
// module `use sym;` for one call site.
|
||||
fn streq_local(a: str, b: str) bool = {
|
||||
if (a.len != b.len) { return false; };
|
||||
let i: i32 = 0;
|
||||
for (i < a.len) {
|
||||
if (a[i] != b[i]) { return false; };
|
||||
i += 1;
|
||||
};
|
||||
return true;
|
||||
};
|
||||
|
||||
fn parseprimary(p: *parser) *node = {
|
||||
let pf: str = p.curfile;
|
||||
let pl: i32 = p.curline;
|
||||
@@ -40,6 +52,43 @@ fn parseprimary(p: *parser) *node = {
|
||||
advance(p);
|
||||
return newnode(p.a, N_NIL, pf, pl, pc);
|
||||
};
|
||||
if (p.curkind == TK_UNDER) {
|
||||
// Bare `_` — valid only as a discard lvalue. Emit an N_IDENT
|
||||
// with empty str; the checker rejects it outside lvalue
|
||||
// positions.
|
||||
advance(p);
|
||||
let n: *node = newnode(p.a, N_IDENT, pf, pl, pc);
|
||||
let empty: str;
|
||||
n.str = empty;
|
||||
return n;
|
||||
};
|
||||
if (p.curkind == TK_LBRACK) {
|
||||
// Array literal `[a, b, c]` or `[v, w...]` (repeat suffix).
|
||||
// The repeat marker is an N_FIELD node with str = "..."
|
||||
// appended to the element list so cgen can detect it.
|
||||
advance(p);
|
||||
let n: *node = newnode(p.a, N_ARRLIT, pf, pl, pc);
|
||||
let head: *node = nil;
|
||||
let tail: *node = nil;
|
||||
for (p.curkind != TK_RBRACK) {
|
||||
if (p.curkind == TK_EOF) { break; };
|
||||
let e: *node = parseexpr(p);
|
||||
if (head == nil) { head = e; tail = e; }
|
||||
else { tail.next = e; tail = e; };
|
||||
if (accepttok(p, TK_ELLIPSIS)) {
|
||||
let rep: *node = newnode(p.a, N_FIELD,
|
||||
p.curfile, p.curline, p.curcol);
|
||||
rep.str = "...";
|
||||
tail.next = rep;
|
||||
tail = rep;
|
||||
break;
|
||||
};
|
||||
if (!accepttok(p, TK_COMMA)) { break; };
|
||||
};
|
||||
expecttok(p, TK_RBRACK, "expected ']' after array literal");
|
||||
n.list = head;
|
||||
return n;
|
||||
};
|
||||
if (p.curkind == TK_LPAREN) {
|
||||
advance(p);
|
||||
let e: *node = parseexpr(p);
|
||||
@@ -79,6 +128,13 @@ fn parseprimary(p: *parser) *node = {
|
||||
let tail: *node = nil;
|
||||
for (p.curkind != TK_RBRACE) {
|
||||
if (p.curkind == TK_EOF) { break; };
|
||||
// Trailing `...` autofill marker. Stash on s.op so
|
||||
// cgen can zero-fill the slot before per-field stores.
|
||||
if (p.curkind == TK_ELLIPSIS) {
|
||||
advance(p);
|
||||
s.op = TK_ELLIPSIS;
|
||||
break;
|
||||
};
|
||||
let fpf: str = p.curfile;
|
||||
let fpl: i32 = p.curline;
|
||||
let fpc: i32 = p.curcol;
|
||||
@@ -164,9 +220,20 @@ fn parsepostfix(p: *parser, lhs: *node) *node = {
|
||||
advance(p);
|
||||
let n: *node = newnode(p.a, N_CALL, pf, pl, pc);
|
||||
n.lhs = cur;
|
||||
let arghead: *node = nil;
|
||||
parsearglist(p, TK_RPAREN, &arghead);
|
||||
n.list = arghead;
|
||||
// size(T)/align(T): the single arg is a type expression,
|
||||
// not a regular expression. Special-case at the parser.
|
||||
let is_typeop: i32 = 0;
|
||||
if (cur.kind == N_IDENT) {
|
||||
if (streq_local(cur.str, "size")) { is_typeop = 1; };
|
||||
if (streq_local(cur.str, "align")) { is_typeop = 1; };
|
||||
};
|
||||
if (is_typeop != 0) {
|
||||
n.list = parsetype(p);
|
||||
} else {
|
||||
let arghead: *node = nil;
|
||||
parsearglist(p, TK_RPAREN, &arghead);
|
||||
n.list = arghead;
|
||||
};
|
||||
expecttok(p, TK_RPAREN, "expected ')' after args");
|
||||
cur = n;
|
||||
continue;
|
||||
|
||||
@@ -85,6 +85,19 @@ fn expectident(p: *parser, into: *str) bool = {
|
||||
return true;
|
||||
};
|
||||
|
||||
// expectbindname — like expectident but also accepts a bare `_`
|
||||
// discard marker. On `_`, returns "" so the checker skips
|
||||
// scope_define for the binding.
|
||||
fn expectbindname(p: *parser, into: *str) bool = {
|
||||
if (p.curkind == TK_UNDER) {
|
||||
let empty: str;
|
||||
*into = empty;
|
||||
advance(p);
|
||||
return true;
|
||||
};
|
||||
return expectident(p, into);
|
||||
};
|
||||
|
||||
// ---- type expressions ------------------------------------------------
|
||||
//
|
||||
// Currently: TNAME (single ident, no dotted path yet) and TPTR (`*T`).
|
||||
@@ -112,7 +125,14 @@ fn parsetype(p: *parser) *node = {
|
||||
return n;
|
||||
};
|
||||
let n: *node = newnode(p.a, N_TARRAY, pf, pl, pc);
|
||||
n.rhs = parseexpr(p);
|
||||
// `[_]T` — length inferred from initialiser. n.rhs stays nil
|
||||
// as the sentinel; the cgen path for N_LET fills it from the
|
||||
// array literal's element count.
|
||||
if (p.curkind == TK_UNDER) {
|
||||
advance(p);
|
||||
} else {
|
||||
n.rhs = parseexpr(p);
|
||||
};
|
||||
expecttok(p, TK_RBRACK, "expected ']' in array type");
|
||||
n.lhs = parsetype(p);
|
||||
return n;
|
||||
@@ -274,6 +294,8 @@ export fn parsefile(p: *parser) *node = {
|
||||
d = parsetypedecl(p, exported);
|
||||
} else { if (p.curkind == TK_LET) {
|
||||
d = parselet(p, exported);
|
||||
} else { if (p.curkind == TK_CONST) {
|
||||
d = parselet(p, exported);
|
||||
} else { if (p.curkind == TK_FN) {
|
||||
d = parsefn(p, exported, attrs);
|
||||
} else {
|
||||
@@ -300,7 +322,7 @@ export fn parsefile(p: *parser) *node = {
|
||||
advance(p);
|
||||
};
|
||||
if (p.curkind == TK_SEMI) { advance(p); };
|
||||
};};};};};
|
||||
};};};};};};
|
||||
|
||||
if (d != nil) {
|
||||
if (head == nil) {
|
||||
|
||||
@@ -8,10 +8,13 @@ fn parseletlocal(p: *parser) *node = {
|
||||
let pf: str = p.curfile;
|
||||
let pl: i32 = p.curline;
|
||||
let pc: i32 = p.curcol;
|
||||
advance(p); // past `let`
|
||||
// `let` or `const`. Const-bound locals are marked via n.op = TK_CONST.
|
||||
let is_const: i32 = 0;
|
||||
if (p.curkind == TK_CONST) { is_const = 1; };
|
||||
advance(p);
|
||||
let n: *node = newnode(p.a, N_LET, pf, pl, pc);
|
||||
let id: str;
|
||||
expectident(p, &id);
|
||||
expectbindname(p, &id);
|
||||
n.str = id;
|
||||
if (accepttok(p, TK_COLON)) {
|
||||
n.lhs = parsetype(p);
|
||||
@@ -20,6 +23,7 @@ fn parseletlocal(p: *parser) *node = {
|
||||
n.rhs = parseexpr(p);
|
||||
};
|
||||
expecttok(p, TK_SEMI, "expected ';' after let");
|
||||
if (is_const != 0) { n.op = TK_CONST; };
|
||||
return n;
|
||||
};
|
||||
|
||||
@@ -98,6 +102,11 @@ fn parsefor(p: *parser) *node = {
|
||||
};
|
||||
expecttok(p, TK_RPAREN, "expected ')' after for");
|
||||
n.body = parseblock(p);
|
||||
// Optional `else { ... }` — runs at normal cond-false exit; skipped
|
||||
// by break. Hare's "did the loop find it?" idiom.
|
||||
if (accepttok(p, TK_ELSE)) {
|
||||
n.els = parseblock(p);
|
||||
};
|
||||
return n;
|
||||
};
|
||||
|
||||
@@ -116,6 +125,7 @@ fn parsestmt(p: *parser) *node = {
|
||||
return b;
|
||||
};
|
||||
if (p.curkind == TK_LET) { return parseletlocal(p); };
|
||||
if (p.curkind == TK_CONST) { return parseletlocal(p); };
|
||||
if (p.curkind == TK_IF) {
|
||||
let n: *node = parseif(p);
|
||||
expecttok(p, TK_SEMI, "expected ';' after if");
|
||||
|
||||
@@ -24,6 +24,7 @@ type sym = struct {
|
||||
type_: *tinfo,
|
||||
decl: *node,
|
||||
exported: i32,
|
||||
is_const: i32, // const-bound (assignment rejected)
|
||||
snext: *sym, // iteration order
|
||||
hashnext: *sym, // hash bucket chain
|
||||
scope: *scope,
|
||||
|
||||
Reference in New Issue
Block a user