ww: hare-feature batch (_, const, [_]T, ..., size/offset, assert, for-else)
This commit is contained in:
25
selfhost/CLAUDE.md
Normal file
25
selfhost/CLAUDE.md
Normal file
@@ -0,0 +1,25 @@
|
||||
selfhost — ww reimplementation of the toolchain (wcc, w6c, w6a, w6l, ww, wwdump). Compiled by the C bootstrap (`../cmd/`); the goal is to eventually compile itself.
|
||||
|
||||
Identifiers: Plan 9 style. lowercase, words run together (`newbuf`, `tcpsock`, `parsefile`). No snake_case.
|
||||
|
||||
Syntax and idioms: Hare-shaped. Trailing `;`, `=` after fn/type signatures, `export` for visibility, `match`/`?`/`!` for tagged-union errors. Consult `ref/hare/` for canonical signatures and error-handling patterns before inventing your own.
|
||||
|
||||
The C bootstrap's cgen ("wwstage") has four known silent-miscompilation traps. They produce wrong runtime behavior, not compile errors. When porting C → ww here, default to the workarounds:
|
||||
|
||||
1. **Two-level field write through pointer field doesn't stick.**
|
||||
`r.sym.isdyn = 1` where `r.sym: *T` drops the write. Bind the inner pointer to a local first:
|
||||
```
|
||||
let sym: *lsym = r.sym;
|
||||
sym.isdyn = 1;
|
||||
```
|
||||
|
||||
2. **Tuple return `(scalar, str)` corrupts the str half.** Both ptr and len come back garbage. Split into two functions — one returns the scalar, another returns the str. Plain `str` returns are fine.
|
||||
|
||||
3. **`def NAME: str = "...";` is broken.** The `.len` picks up an unrelated accumulator. Wrap the literal in a nullary fn instead:
|
||||
```
|
||||
fn namestr() str = { return "..."; };
|
||||
```
|
||||
|
||||
4. **`amalloc(n)` with n < struct size silently corrupts neighbours.** No error — the bump arena hands out n bytes and field writes overflow into the next record. When introducing or growing a struct, audit every `amalloc(_, n)` call site and over-size (we routinely pass 48 for a 40-byte struct). Symptom: linked-list prepends lose all but the most recent entry.
|
||||
|
||||
If a port "should work" but the binary is wrong, suspect these first.
|
||||
@@ -488,62 +488,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 --------------------------------------------------------
|
||||
//
|
||||
@@ -592,6 +594,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; };
|
||||
@@ -654,6 +657,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 ")"; };
|
||||
@@ -1374,10 +1379,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 {
|
||||
@@ -1961,6 +1974,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;
|
||||
@@ -1997,6 +2022,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);
|
||||
@@ -2036,6 +2098,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;
|
||||
@@ -2121,9 +2190,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;
|
||||
@@ -2290,10 +2370,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);
|
||||
@@ -2302,6 +2385,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;
|
||||
};
|
||||
|
||||
@@ -2380,6 +2464,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;
|
||||
};
|
||||
|
||||
@@ -2398,6 +2487,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");
|
||||
@@ -2522,11 +2612,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);
|
||||
@@ -2536,6 +2630,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;
|
||||
};
|
||||
|
||||
@@ -2571,10 +2666,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);
|
||||
@@ -2722,6 +2817,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`).
|
||||
@@ -2749,7 +2857,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;
|
||||
@@ -2911,6 +3026,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 {
|
||||
@@ -2937,7 +3054,7 @@ export fn parsefile(p: *parser) *node = {
|
||||
advance(p);
|
||||
};
|
||||
if (p.curkind == TK_SEMI) { advance(p); };
|
||||
};};};};};
|
||||
};};};};};};
|
||||
|
||||
if (d != nil) {
|
||||
if (head == nil) {
|
||||
@@ -3311,6 +3428,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,
|
||||
@@ -4270,6 +4388,52 @@ fn primsize(name: str) i32 = {
|
||||
return 0;
|
||||
};
|
||||
|
||||
// letslotsize — slot size for a `let` binding. Like slotsize, but
|
||||
// detects `[_]T = arrlit;` (the type-AST has rhs == nil as the
|
||||
// length-inferred sentinel) and computes count × element-size from
|
||||
// the initialiser. Used by both scanlocals (prologue sizing) and
|
||||
// cglet (slot alloc) so they agree on the frame layout.
|
||||
export fn letslotsize(c: *cgen, n: *node) i32 = {
|
||||
// `[_]T = arrlit;` — inferred-length array. slotsize would
|
||||
// return elem_size * 1 (treating missing length as 1); intercept
|
||||
// and compute the real count first.
|
||||
if (n.lhs != nil) {
|
||||
if (n.lhs.kind == N_TARRAY) {
|
||||
if (n.lhs.rhs == nil) {
|
||||
if (n.rhs != nil) {
|
||||
if (n.rhs.kind == N_ARRLIT) {
|
||||
let elemn: *node = n.lhs.lhs;
|
||||
let esz: i32 = 8;
|
||||
if (elemn != nil) {
|
||||
if (elemn.kind == N_TNAME) {
|
||||
let ps: i32 = primsize(elemn.str);
|
||||
if (ps > 0) { esz = ps; };
|
||||
};
|
||||
};
|
||||
let cnt: i32 = 0;
|
||||
let e: *node = n.rhs.list;
|
||||
for (e != nil) {
|
||||
let adv: bool = true;
|
||||
if (e.kind == N_FIELD) {
|
||||
if (streq(e.str, "...")) {
|
||||
e = nil;
|
||||
adv = false;
|
||||
};
|
||||
};
|
||||
if (adv) {
|
||||
cnt += 1;
|
||||
e = e.next;
|
||||
};
|
||||
};
|
||||
return esz * cnt;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
return slotsize(c, n.lhs);
|
||||
};
|
||||
|
||||
fn slotsize(c: *cgen, typn: *node) i32 = {
|
||||
if (typn == nil) { return 8; };
|
||||
let k: i32 = typn.kind;
|
||||
@@ -5253,6 +5417,19 @@ fn cgcall(c: *cgen, n: *node) void = {
|
||||
|
||||
fn cgassign(c: *cgen, n: *node) void = {
|
||||
let lhs: *node = n.lhs;
|
||||
// Discard lvalue `_ = expr;` — evaluate rhs for side effects,
|
||||
// write nothing. Detected by lhs being an N_IDENT with empty str
|
||||
// (planted by parseprimary on the TK_UNDER token).
|
||||
if (lhs != nil) {
|
||||
if (lhs.kind == N_IDENT) {
|
||||
if (lhs.str.len == 0) {
|
||||
if (n.op == TK_ASSIGN) {
|
||||
cgexpr(c, n.rhs);
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
// `*p = v` — deref-assign. Element width comes from the
|
||||
// pointer's declared type. Mirrors C cgen: eval rhs (AX,
|
||||
// and BX if str), push, eval pointer, pop value, store.
|
||||
@@ -5746,7 +5923,7 @@ fn cgexprstmt(c: *cgen, n: *node) void = {
|
||||
|
||||
fn cglet(c: *cgen, n: *node) void = {
|
||||
let nm: str = n.str;
|
||||
let sz: i32 = slotsize(c, n.lhs);
|
||||
let sz: i32 = letslotsize(c, n);
|
||||
let off: i32 = localadd(c, nm, sz, n.lhs);
|
||||
if (n.rhs != nil) {
|
||||
let rhs: *node = n.rhs;
|
||||
@@ -5807,10 +5984,84 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
};
|
||||
// Array literal init: `let xs: [N]T = [a, b, c];` (or [_]T).
|
||||
// Walk elements in declaration order, store each at off + i*esz
|
||||
// using the right width for the element type. Trailing `...`
|
||||
// after the last value (an N_FIELD with str=="...") fills the
|
||||
// remaining slots up to the declared length with that value.
|
||||
if (rhs.kind == N_ARRLIT) {
|
||||
let elemn: *node = n.lhs.lhs;
|
||||
let esz: i32 = 8;
|
||||
if (elemn != nil) {
|
||||
if (elemn.kind == N_TNAME) {
|
||||
let ps: i32 = primsize(elemn.str);
|
||||
if (ps > 0) { esz = ps; };
|
||||
};
|
||||
};
|
||||
let mop: str = "MOVQ";
|
||||
if (esz == 1) { mop = "MOVB"; }
|
||||
else { if (esz == 4) { mop = "MOVL"; }; };
|
||||
let idx: i32 = 0;
|
||||
let repeat: bool = false;
|
||||
let e: *node = rhs.list;
|
||||
for (e != nil) {
|
||||
if (e.kind == N_FIELD) {
|
||||
if (streq(e.str, "...")) {
|
||||
repeat = true;
|
||||
e = nil;
|
||||
} else {
|
||||
cgexpr(c, e);
|
||||
emitline("\t");
|
||||
emitline(mop);
|
||||
emitline("\tAX, ");
|
||||
emitoff((off + idx * esz): i64);
|
||||
emitline("(BP)\n");
|
||||
idx += 1;
|
||||
e = e.next;
|
||||
};
|
||||
} else {
|
||||
cgexpr(c, e);
|
||||
emitline("\t");
|
||||
emitline(mop);
|
||||
emitline("\tAX, ");
|
||||
emitoff((off + idx * esz): i64);
|
||||
emitline("(BP)\n");
|
||||
idx += 1;
|
||||
e = e.next;
|
||||
};
|
||||
};
|
||||
// AX still holds the last stored value; fill remaining
|
||||
// slots up to the declared length with it.
|
||||
if (repeat) {
|
||||
let total: i32 = idx;
|
||||
if (n.lhs != nil) {
|
||||
if (n.lhs.kind == N_TARRAY) {
|
||||
if (n.lhs.rhs != nil) {
|
||||
if (n.lhs.rhs.kind == N_INTLIT) {
|
||||
total = n.lhs.rhs.uval: i32;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
for (idx < total) {
|
||||
emitline("\t");
|
||||
emitline(mop);
|
||||
emitline("\tAX, ");
|
||||
emitoff((off + idx * esz): i64);
|
||||
emitline("(BP)\n");
|
||||
idx += 1;
|
||||
};
|
||||
};
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
};
|
||||
// Struct literal init: `let p: point = point{x=..., y=...};`.
|
||||
// For each field in the lit, evaluate its value and store at
|
||||
// the field's offset within the slot. Field-name → offset
|
||||
// from the struct registry.
|
||||
// from the struct registry. When the literal carries
|
||||
// op == TK_ELLIPSIS (autofill marker from the parser), the
|
||||
// entire slot is zero-filled first so unmentioned fields
|
||||
// read as 0.
|
||||
if (rhs.kind == N_STRUCTLIT) {
|
||||
let trefn: *node = rhs.lhs;
|
||||
let sname: str;
|
||||
@@ -5821,6 +6072,29 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
};
|
||||
let si: *structinfo = structlookup(c, sname);
|
||||
if (si != nil) {
|
||||
if (rhs.op == TK_ELLIPSIS) {
|
||||
let total: i32 = si.totsize;
|
||||
emitline("\tXORQ\tAX, AX\n");
|
||||
let zi: i32 = 0;
|
||||
for (zi + 8 <= total) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((off + zi): i64);
|
||||
emitline("(BP)\n");
|
||||
zi += 8;
|
||||
};
|
||||
for (zi + 4 <= total) {
|
||||
emitline("\tMOVL\tAX, ");
|
||||
emitoff((off + zi): i64);
|
||||
emitline("(BP)\n");
|
||||
zi += 4;
|
||||
};
|
||||
for (zi < total) {
|
||||
emitline("\tMOVB\tAX, ");
|
||||
emitoff((off + zi): i64);
|
||||
emitline("(BP)\n");
|
||||
zi += 1;
|
||||
};
|
||||
};
|
||||
let fieldnode: *node = rhs.list;
|
||||
for (fieldnode != nil) {
|
||||
if (fieldnode.kind == N_FIELD) {
|
||||
@@ -5912,6 +6186,11 @@ fn cgfor(c: *cgen, n: *node) void = {
|
||||
// label when there's no post-expression.
|
||||
let topl: str = mklabel(c, "loop");
|
||||
let endl: str = mklabel(c, "endloop");
|
||||
// `else` runs at natural cond-false exit; break skips it. When
|
||||
// present, branch the cond-fail edge to a separate natural_exit
|
||||
// label so the else body sits between it and the break target.
|
||||
let naturall: str = endl;
|
||||
if (n.els != nil) { naturall = mklabel(c, "elseloop"); };
|
||||
|
||||
if (n.lhs != nil) { cgstmt(c, n.lhs); };
|
||||
|
||||
@@ -5919,7 +6198,7 @@ fn cgfor(c: *cgen, n: *node) void = {
|
||||
if (n.cond != nil) {
|
||||
cgexpr(c, n.cond);
|
||||
emitline("\tCMPQ\t$0, AX\n");
|
||||
emitline("\tJE\t"); emitline(endl); emitline("\n");
|
||||
emitline("\tJE\t"); emitline(naturall); emitline("\n");
|
||||
};
|
||||
|
||||
c.loopendbuf[c.looptop] = endl;
|
||||
@@ -5932,6 +6211,10 @@ fn cgfor(c: *cgen, n: *node) void = {
|
||||
|
||||
if (n.rhs != nil) { cgexpr(c, n.rhs); };
|
||||
emitline("\tJMP\t"); emitline(topl); emitline("\n");
|
||||
if (n.els != nil) {
|
||||
emitlabel(naturall);
|
||||
cgstmt(c, n.els);
|
||||
};
|
||||
emitlabel(endl);
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
@@ -6029,7 +6312,7 @@ fn scanlocals(c: *cgen, n: *node) i32 = {
|
||||
// of the caller. Same-name re-declarations share the first
|
||||
// slot (see scanseenmark / localadd).
|
||||
if (!scanseenmark(c, n.str)) {
|
||||
let sz: i32 = slotsize(c, n.lhs);
|
||||
let sz: i32 = letslotsize(c, n);
|
||||
if (sz < 8) { sz = 8; };
|
||||
if ((sz & 7) != 0) { sz = (sz + 7) & ~7; };
|
||||
total += sz;
|
||||
|
||||
@@ -33,7 +33,7 @@ fn scanlocals(c: *cgen, n: *node) i32 = {
|
||||
// of the caller. Same-name re-declarations share the first
|
||||
// slot (see scanseenmark / localadd).
|
||||
if (!scanseenmark(c, n.str)) {
|
||||
let sz: i32 = slotsize(c, n.lhs);
|
||||
let sz: i32 = letslotsize(c, n);
|
||||
if (sz < 8) { sz = 8; };
|
||||
if ((sz & 7) != 0) { sz = (sz + 7) & ~7; };
|
||||
total += sz;
|
||||
|
||||
@@ -736,6 +736,19 @@ fn cgcall(c: *cgen, n: *node) void = {
|
||||
|
||||
fn cgassign(c: *cgen, n: *node) void = {
|
||||
let lhs: *node = n.lhs;
|
||||
// Discard lvalue `_ = expr;` — evaluate rhs for side effects,
|
||||
// write nothing. Detected by lhs being an N_IDENT with empty str
|
||||
// (planted by parseprimary on the TK_UNDER token).
|
||||
if (lhs != nil) {
|
||||
if (lhs.kind == N_IDENT) {
|
||||
if (lhs.str.len == 0) {
|
||||
if (n.op == TK_ASSIGN) {
|
||||
cgexpr(c, n.rhs);
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
// `*p = v` — deref-assign. Element width comes from the
|
||||
// pointer's declared type. Mirrors C cgen: eval rhs (AX,
|
||||
// and BX if str), push, eval pointer, pop value, store.
|
||||
|
||||
@@ -126,7 +126,7 @@ fn cgexprstmt(c: *cgen, n: *node) void = {
|
||||
|
||||
fn cglet(c: *cgen, n: *node) void = {
|
||||
let nm: str = n.str;
|
||||
let sz: i32 = slotsize(c, n.lhs);
|
||||
let sz: i32 = letslotsize(c, n);
|
||||
let off: i32 = localadd(c, nm, sz, n.lhs);
|
||||
if (n.rhs != nil) {
|
||||
let rhs: *node = n.rhs;
|
||||
@@ -187,10 +187,84 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
};
|
||||
// Array literal init: `let xs: [N]T = [a, b, c];` (or [_]T).
|
||||
// Walk elements in declaration order, store each at off + i*esz
|
||||
// using the right width for the element type. Trailing `...`
|
||||
// after the last value (an N_FIELD with str=="...") fills the
|
||||
// remaining slots up to the declared length with that value.
|
||||
if (rhs.kind == N_ARRLIT) {
|
||||
let elemn: *node = n.lhs.lhs;
|
||||
let esz: i32 = 8;
|
||||
if (elemn != nil) {
|
||||
if (elemn.kind == N_TNAME) {
|
||||
let ps: i32 = primsize(elemn.str);
|
||||
if (ps > 0) { esz = ps; };
|
||||
};
|
||||
};
|
||||
let mop: str = "MOVQ";
|
||||
if (esz == 1) { mop = "MOVB"; }
|
||||
else { if (esz == 4) { mop = "MOVL"; }; };
|
||||
let idx: i32 = 0;
|
||||
let repeat: bool = false;
|
||||
let e: *node = rhs.list;
|
||||
for (e != nil) {
|
||||
if (e.kind == N_FIELD) {
|
||||
if (streq(e.str, "...")) {
|
||||
repeat = true;
|
||||
e = nil;
|
||||
} else {
|
||||
cgexpr(c, e);
|
||||
emitline("\t");
|
||||
emitline(mop);
|
||||
emitline("\tAX, ");
|
||||
emitoff((off + idx * esz): i64);
|
||||
emitline("(BP)\n");
|
||||
idx += 1;
|
||||
e = e.next;
|
||||
};
|
||||
} else {
|
||||
cgexpr(c, e);
|
||||
emitline("\t");
|
||||
emitline(mop);
|
||||
emitline("\tAX, ");
|
||||
emitoff((off + idx * esz): i64);
|
||||
emitline("(BP)\n");
|
||||
idx += 1;
|
||||
e = e.next;
|
||||
};
|
||||
};
|
||||
// AX still holds the last stored value; fill remaining
|
||||
// slots up to the declared length with it.
|
||||
if (repeat) {
|
||||
let total: i32 = idx;
|
||||
if (n.lhs != nil) {
|
||||
if (n.lhs.kind == N_TARRAY) {
|
||||
if (n.lhs.rhs != nil) {
|
||||
if (n.lhs.rhs.kind == N_INTLIT) {
|
||||
total = n.lhs.rhs.uval: i32;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
for (idx < total) {
|
||||
emitline("\t");
|
||||
emitline(mop);
|
||||
emitline("\tAX, ");
|
||||
emitoff((off + idx * esz): i64);
|
||||
emitline("(BP)\n");
|
||||
idx += 1;
|
||||
};
|
||||
};
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
};
|
||||
// Struct literal init: `let p: point = point{x=..., y=...};`.
|
||||
// For each field in the lit, evaluate its value and store at
|
||||
// the field's offset within the slot. Field-name → offset
|
||||
// from the struct registry.
|
||||
// from the struct registry. When the literal carries
|
||||
// op == TK_ELLIPSIS (autofill marker from the parser), the
|
||||
// entire slot is zero-filled first so unmentioned fields
|
||||
// read as 0.
|
||||
if (rhs.kind == N_STRUCTLIT) {
|
||||
let trefn: *node = rhs.lhs;
|
||||
let sname: str;
|
||||
@@ -201,6 +275,29 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
};
|
||||
let si: *structinfo = structlookup(c, sname);
|
||||
if (si != nil) {
|
||||
if (rhs.op == TK_ELLIPSIS) {
|
||||
let total: i32 = si.totsize;
|
||||
emitline("\tXORQ\tAX, AX\n");
|
||||
let zi: i32 = 0;
|
||||
for (zi + 8 <= total) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((off + zi): i64);
|
||||
emitline("(BP)\n");
|
||||
zi += 8;
|
||||
};
|
||||
for (zi + 4 <= total) {
|
||||
emitline("\tMOVL\tAX, ");
|
||||
emitoff((off + zi): i64);
|
||||
emitline("(BP)\n");
|
||||
zi += 4;
|
||||
};
|
||||
for (zi < total) {
|
||||
emitline("\tMOVB\tAX, ");
|
||||
emitoff((off + zi): i64);
|
||||
emitline("(BP)\n");
|
||||
zi += 1;
|
||||
};
|
||||
};
|
||||
let fieldnode: *node = rhs.list;
|
||||
for (fieldnode != nil) {
|
||||
if (fieldnode.kind == N_FIELD) {
|
||||
@@ -292,6 +389,11 @@ fn cgfor(c: *cgen, n: *node) void = {
|
||||
// label when there's no post-expression.
|
||||
let topl: str = mklabel(c, "loop");
|
||||
let endl: str = mklabel(c, "endloop");
|
||||
// `else` runs at natural cond-false exit; break skips it. When
|
||||
// present, branch the cond-fail edge to a separate natural_exit
|
||||
// label so the else body sits between it and the break target.
|
||||
let naturall: str = endl;
|
||||
if (n.els != nil) { naturall = mklabel(c, "elseloop"); };
|
||||
|
||||
if (n.lhs != nil) { cgstmt(c, n.lhs); };
|
||||
|
||||
@@ -299,7 +401,7 @@ fn cgfor(c: *cgen, n: *node) void = {
|
||||
if (n.cond != nil) {
|
||||
cgexpr(c, n.cond);
|
||||
emitline("\tCMPQ\t$0, AX\n");
|
||||
emitline("\tJE\t"); emitline(endl); emitline("\n");
|
||||
emitline("\tJE\t"); emitline(naturall); emitline("\n");
|
||||
};
|
||||
|
||||
c.loopendbuf[c.looptop] = endl;
|
||||
@@ -312,6 +414,10 @@ fn cgfor(c: *cgen, n: *node) void = {
|
||||
|
||||
if (n.rhs != nil) { cgexpr(c, n.rhs); };
|
||||
emitline("\tJMP\t"); emitline(topl); emitline("\n");
|
||||
if (n.els != nil) {
|
||||
emitlabel(naturall);
|
||||
cgstmt(c, n.els);
|
||||
};
|
||||
emitlabel(endl);
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
|
||||
@@ -619,6 +619,52 @@ fn primsize(name: str) i32 = {
|
||||
return 0;
|
||||
};
|
||||
|
||||
// letslotsize — slot size for a `let` binding. Like slotsize, but
|
||||
// detects `[_]T = arrlit;` (the type-AST has rhs == nil as the
|
||||
// length-inferred sentinel) and computes count × element-size from
|
||||
// the initialiser. Used by both scanlocals (prologue sizing) and
|
||||
// cglet (slot alloc) so they agree on the frame layout.
|
||||
export fn letslotsize(c: *cgen, n: *node) i32 = {
|
||||
// `[_]T = arrlit;` — inferred-length array. slotsize would
|
||||
// return elem_size * 1 (treating missing length as 1); intercept
|
||||
// and compute the real count first.
|
||||
if (n.lhs != nil) {
|
||||
if (n.lhs.kind == N_TARRAY) {
|
||||
if (n.lhs.rhs == nil) {
|
||||
if (n.rhs != nil) {
|
||||
if (n.rhs.kind == N_ARRLIT) {
|
||||
let elemn: *node = n.lhs.lhs;
|
||||
let esz: i32 = 8;
|
||||
if (elemn != nil) {
|
||||
if (elemn.kind == N_TNAME) {
|
||||
let ps: i32 = primsize(elemn.str);
|
||||
if (ps > 0) { esz = ps; };
|
||||
};
|
||||
};
|
||||
let cnt: i32 = 0;
|
||||
let e: *node = n.rhs.list;
|
||||
for (e != nil) {
|
||||
let adv: bool = true;
|
||||
if (e.kind == N_FIELD) {
|
||||
if (streq(e.str, "...")) {
|
||||
e = nil;
|
||||
adv = false;
|
||||
};
|
||||
};
|
||||
if (adv) {
|
||||
cnt += 1;
|
||||
e = e.next;
|
||||
};
|
||||
};
|
||||
return esz * cnt;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
return slotsize(c, n.lhs);
|
||||
};
|
||||
|
||||
fn slotsize(c: *cgen, typn: *node) i32 = {
|
||||
if (typn == nil) { return 8; };
|
||||
let k: i32 = typn.kind;
|
||||
|
||||
@@ -488,62 +488,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 --------------------------------------------------------
|
||||
//
|
||||
@@ -592,6 +594,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; };
|
||||
@@ -654,6 +657,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 ")"; };
|
||||
@@ -1374,10 +1379,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 {
|
||||
@@ -1961,6 +1974,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;
|
||||
@@ -1997,6 +2022,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);
|
||||
@@ -2036,6 +2098,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;
|
||||
@@ -2121,9 +2190,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;
|
||||
@@ -2290,10 +2370,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);
|
||||
@@ -2302,6 +2385,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;
|
||||
};
|
||||
|
||||
@@ -2380,6 +2464,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;
|
||||
};
|
||||
|
||||
@@ -2398,6 +2487,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");
|
||||
@@ -2522,11 +2612,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);
|
||||
@@ -2536,6 +2630,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;
|
||||
};
|
||||
|
||||
@@ -2571,10 +2666,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);
|
||||
@@ -2722,6 +2817,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`).
|
||||
@@ -2749,7 +2857,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;
|
||||
@@ -2911,6 +3026,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 {
|
||||
@@ -2937,7 +3054,7 @@ export fn parsefile(p: *parser) *node = {
|
||||
advance(p);
|
||||
};
|
||||
if (p.curkind == TK_SEMI) { advance(p); };
|
||||
};};};};};
|
||||
};};};};};};
|
||||
|
||||
if (d != nil) {
|
||||
if (head == nil) {
|
||||
@@ -3311,6 +3428,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,
|
||||
@@ -4270,6 +4388,52 @@ fn primsize(name: str) i32 = {
|
||||
return 0;
|
||||
};
|
||||
|
||||
// letslotsize — slot size for a `let` binding. Like slotsize, but
|
||||
// detects `[_]T = arrlit;` (the type-AST has rhs == nil as the
|
||||
// length-inferred sentinel) and computes count × element-size from
|
||||
// the initialiser. Used by both scanlocals (prologue sizing) and
|
||||
// cglet (slot alloc) so they agree on the frame layout.
|
||||
export fn letslotsize(c: *cgen, n: *node) i32 = {
|
||||
// `[_]T = arrlit;` — inferred-length array. slotsize would
|
||||
// return elem_size * 1 (treating missing length as 1); intercept
|
||||
// and compute the real count first.
|
||||
if (n.lhs != nil) {
|
||||
if (n.lhs.kind == N_TARRAY) {
|
||||
if (n.lhs.rhs == nil) {
|
||||
if (n.rhs != nil) {
|
||||
if (n.rhs.kind == N_ARRLIT) {
|
||||
let elemn: *node = n.lhs.lhs;
|
||||
let esz: i32 = 8;
|
||||
if (elemn != nil) {
|
||||
if (elemn.kind == N_TNAME) {
|
||||
let ps: i32 = primsize(elemn.str);
|
||||
if (ps > 0) { esz = ps; };
|
||||
};
|
||||
};
|
||||
let cnt: i32 = 0;
|
||||
let e: *node = n.rhs.list;
|
||||
for (e != nil) {
|
||||
let adv: bool = true;
|
||||
if (e.kind == N_FIELD) {
|
||||
if (streq(e.str, "...")) {
|
||||
e = nil;
|
||||
adv = false;
|
||||
};
|
||||
};
|
||||
if (adv) {
|
||||
cnt += 1;
|
||||
e = e.next;
|
||||
};
|
||||
};
|
||||
return esz * cnt;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
return slotsize(c, n.lhs);
|
||||
};
|
||||
|
||||
fn slotsize(c: *cgen, typn: *node) i32 = {
|
||||
if (typn == nil) { return 8; };
|
||||
let k: i32 = typn.kind;
|
||||
@@ -5253,6 +5417,19 @@ fn cgcall(c: *cgen, n: *node) void = {
|
||||
|
||||
fn cgassign(c: *cgen, n: *node) void = {
|
||||
let lhs: *node = n.lhs;
|
||||
// Discard lvalue `_ = expr;` — evaluate rhs for side effects,
|
||||
// write nothing. Detected by lhs being an N_IDENT with empty str
|
||||
// (planted by parseprimary on the TK_UNDER token).
|
||||
if (lhs != nil) {
|
||||
if (lhs.kind == N_IDENT) {
|
||||
if (lhs.str.len == 0) {
|
||||
if (n.op == TK_ASSIGN) {
|
||||
cgexpr(c, n.rhs);
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
// `*p = v` — deref-assign. Element width comes from the
|
||||
// pointer's declared type. Mirrors C cgen: eval rhs (AX,
|
||||
// and BX if str), push, eval pointer, pop value, store.
|
||||
@@ -5746,7 +5923,7 @@ fn cgexprstmt(c: *cgen, n: *node) void = {
|
||||
|
||||
fn cglet(c: *cgen, n: *node) void = {
|
||||
let nm: str = n.str;
|
||||
let sz: i32 = slotsize(c, n.lhs);
|
||||
let sz: i32 = letslotsize(c, n);
|
||||
let off: i32 = localadd(c, nm, sz, n.lhs);
|
||||
if (n.rhs != nil) {
|
||||
let rhs: *node = n.rhs;
|
||||
@@ -5807,10 +5984,84 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
};
|
||||
// Array literal init: `let xs: [N]T = [a, b, c];` (or [_]T).
|
||||
// Walk elements in declaration order, store each at off + i*esz
|
||||
// using the right width for the element type. Trailing `...`
|
||||
// after the last value (an N_FIELD with str=="...") fills the
|
||||
// remaining slots up to the declared length with that value.
|
||||
if (rhs.kind == N_ARRLIT) {
|
||||
let elemn: *node = n.lhs.lhs;
|
||||
let esz: i32 = 8;
|
||||
if (elemn != nil) {
|
||||
if (elemn.kind == N_TNAME) {
|
||||
let ps: i32 = primsize(elemn.str);
|
||||
if (ps > 0) { esz = ps; };
|
||||
};
|
||||
};
|
||||
let mop: str = "MOVQ";
|
||||
if (esz == 1) { mop = "MOVB"; }
|
||||
else { if (esz == 4) { mop = "MOVL"; }; };
|
||||
let idx: i32 = 0;
|
||||
let repeat: bool = false;
|
||||
let e: *node = rhs.list;
|
||||
for (e != nil) {
|
||||
if (e.kind == N_FIELD) {
|
||||
if (streq(e.str, "...")) {
|
||||
repeat = true;
|
||||
e = nil;
|
||||
} else {
|
||||
cgexpr(c, e);
|
||||
emitline("\t");
|
||||
emitline(mop);
|
||||
emitline("\tAX, ");
|
||||
emitoff((off + idx * esz): i64);
|
||||
emitline("(BP)\n");
|
||||
idx += 1;
|
||||
e = e.next;
|
||||
};
|
||||
} else {
|
||||
cgexpr(c, e);
|
||||
emitline("\t");
|
||||
emitline(mop);
|
||||
emitline("\tAX, ");
|
||||
emitoff((off + idx * esz): i64);
|
||||
emitline("(BP)\n");
|
||||
idx += 1;
|
||||
e = e.next;
|
||||
};
|
||||
};
|
||||
// AX still holds the last stored value; fill remaining
|
||||
// slots up to the declared length with it.
|
||||
if (repeat) {
|
||||
let total: i32 = idx;
|
||||
if (n.lhs != nil) {
|
||||
if (n.lhs.kind == N_TARRAY) {
|
||||
if (n.lhs.rhs != nil) {
|
||||
if (n.lhs.rhs.kind == N_INTLIT) {
|
||||
total = n.lhs.rhs.uval: i32;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
for (idx < total) {
|
||||
emitline("\t");
|
||||
emitline(mop);
|
||||
emitline("\tAX, ");
|
||||
emitoff((off + idx * esz): i64);
|
||||
emitline("(BP)\n");
|
||||
idx += 1;
|
||||
};
|
||||
};
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
};
|
||||
// Struct literal init: `let p: point = point{x=..., y=...};`.
|
||||
// For each field in the lit, evaluate its value and store at
|
||||
// the field's offset within the slot. Field-name → offset
|
||||
// from the struct registry.
|
||||
// from the struct registry. When the literal carries
|
||||
// op == TK_ELLIPSIS (autofill marker from the parser), the
|
||||
// entire slot is zero-filled first so unmentioned fields
|
||||
// read as 0.
|
||||
if (rhs.kind == N_STRUCTLIT) {
|
||||
let trefn: *node = rhs.lhs;
|
||||
let sname: str;
|
||||
@@ -5821,6 +6072,29 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
};
|
||||
let si: *structinfo = structlookup(c, sname);
|
||||
if (si != nil) {
|
||||
if (rhs.op == TK_ELLIPSIS) {
|
||||
let total: i32 = si.totsize;
|
||||
emitline("\tXORQ\tAX, AX\n");
|
||||
let zi: i32 = 0;
|
||||
for (zi + 8 <= total) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((off + zi): i64);
|
||||
emitline("(BP)\n");
|
||||
zi += 8;
|
||||
};
|
||||
for (zi + 4 <= total) {
|
||||
emitline("\tMOVL\tAX, ");
|
||||
emitoff((off + zi): i64);
|
||||
emitline("(BP)\n");
|
||||
zi += 4;
|
||||
};
|
||||
for (zi < total) {
|
||||
emitline("\tMOVB\tAX, ");
|
||||
emitoff((off + zi): i64);
|
||||
emitline("(BP)\n");
|
||||
zi += 1;
|
||||
};
|
||||
};
|
||||
let fieldnode: *node = rhs.list;
|
||||
for (fieldnode != nil) {
|
||||
if (fieldnode.kind == N_FIELD) {
|
||||
@@ -5912,6 +6186,11 @@ fn cgfor(c: *cgen, n: *node) void = {
|
||||
// label when there's no post-expression.
|
||||
let topl: str = mklabel(c, "loop");
|
||||
let endl: str = mklabel(c, "endloop");
|
||||
// `else` runs at natural cond-false exit; break skips it. When
|
||||
// present, branch the cond-fail edge to a separate natural_exit
|
||||
// label so the else body sits between it and the break target.
|
||||
let naturall: str = endl;
|
||||
if (n.els != nil) { naturall = mklabel(c, "elseloop"); };
|
||||
|
||||
if (n.lhs != nil) { cgstmt(c, n.lhs); };
|
||||
|
||||
@@ -5919,7 +6198,7 @@ fn cgfor(c: *cgen, n: *node) void = {
|
||||
if (n.cond != nil) {
|
||||
cgexpr(c, n.cond);
|
||||
emitline("\tCMPQ\t$0, AX\n");
|
||||
emitline("\tJE\t"); emitline(endl); emitline("\n");
|
||||
emitline("\tJE\t"); emitline(naturall); emitline("\n");
|
||||
};
|
||||
|
||||
c.loopendbuf[c.looptop] = endl;
|
||||
@@ -5932,6 +6211,10 @@ fn cgfor(c: *cgen, n: *node) void = {
|
||||
|
||||
if (n.rhs != nil) { cgexpr(c, n.rhs); };
|
||||
emitline("\tJMP\t"); emitline(topl); emitline("\n");
|
||||
if (n.els != nil) {
|
||||
emitlabel(naturall);
|
||||
cgstmt(c, n.els);
|
||||
};
|
||||
emitlabel(endl);
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
@@ -6029,7 +6312,7 @@ fn scanlocals(c: *cgen, n: *node) i32 = {
|
||||
// of the caller. Same-name re-declarations share the first
|
||||
// slot (see scanseenmark / localadd).
|
||||
if (!scanseenmark(c, n.str)) {
|
||||
let sz: i32 = slotsize(c, n.lhs);
|
||||
let sz: i32 = letslotsize(c, n);
|
||||
if (sz < 8) { sz = 8; };
|
||||
if ((sz & 7) != 0) { sz = (sz + 7) & ~7; };
|
||||
total += sz;
|
||||
|
||||
Reference in New Issue
Block a user