Bindings now signal mutability the way Rust/Swift do, instead of `let` meaning
"local" and `var` meaning "module-level":
- `let x = e` -> immutable binding; a later `x = …` is a compile error
(`cannot assign to immutable 'x' … use var`).
- `var x = e` -> mutable binding, at local OR module scope (position decides
scope; the keyword decides mutability).
- `const` -> unchanged (compile-time).
Immutability is of the *binding*, not the object: `let n = new Node; n.kind = 1`
is fine (mutation through the reference); only rebinding `n` is rejected. The
check lives in emit_assign — a direct `name =` whose target is a `let` local
(loc_mut == 0) errors; field/index targets and `var`/param/loop bindings are
unaffected.
Delivered as three reseeds so the self-hosting compiler never had to compile
source its own rules would reject:
A) add `var` as a local statement + per-local mutability tracking (loc_mut),
no enforcement;
B) migrate every reassigned `let` -> `var` across the compiler, runtime and
examples (337 declarations), driven by a per-function, string/comment-aware
scan (binding targets only, never `x.f =` / `x[i] =`);
C) turn on the check. bootstrap-cfree (compiler vs its own source) and every
golden build (which splices the runtime) then proved zero reassigned `let`
was missed anywhere.
Also folded in: removed leftover debug instrumentation in block() (a `cur=` /
print_int(777…) trace on the separator-error path) and fixed parse.ludic's stale
header comment (no more `struct`). Reseeded (21711 lines); C-free fixpoint holds;
goldens identical; 17/17; vocab + doc-fences clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
200 lines
7.5 KiB
Text
200 lines
7.5 KiB
Text
# emit_stmt.ludic — lower statements. Terminators set g_term so the rest of a
|
|
# block is skipped until a new basic block opens.
|
|
|
|
fn emit_block(b: Node) -> void {
|
|
var i = 0
|
|
while i < len(b.kids) {
|
|
if g_term { return }
|
|
emit_stmt(b.kids[i])
|
|
i = i + 1
|
|
}
|
|
}
|
|
|
|
# store `val` (llvm type `lt`) into address `addr`
|
|
fn store_at(lt: ptr, v: ptr, addr: ptr) -> void {
|
|
emit(" store "); emit(lt); emit(" "); emit(v); emit(", ptr "); emit(addr); emit("\n")
|
|
}
|
|
|
|
fn emit_assign(st: Node) -> void {
|
|
# resolve the target's address and type
|
|
let t = st.a
|
|
var addr = "0"
|
|
var ty = "int"
|
|
if t.kind == E_ID {
|
|
let li = loc_find(t.s)
|
|
if li >= 0 {
|
|
# a `let` binding is immutable: rebinding it is an error (mutating THROUGH
|
|
# it — `x.field = …`, `x[i] = …` — is fine, and lands in the branches below).
|
|
if loc_mut[li] == 0 { perr(sconcat("cannot assign to immutable '", sconcat(t.s, sconcat("' (declared with let; use var) — line ", itoa(st.line))))) }
|
|
addr = loc_reg[li]; ty = loc_ty[li]
|
|
}
|
|
else {
|
|
let g = find_global(t.s)
|
|
if ptr_is_null(g) { perr(sconcat("assign to unknown ", t.s)) }
|
|
addr = sconcat("@g_", t.s); ty = g.ty
|
|
}
|
|
} else {
|
|
if t.kind == E_MEMBER { addr = emit_member_addr(t); ty = g_addr_ty }
|
|
else { if t.kind == E_INDEX { addr = emit_index_addr(t); ty = g_addr_ty }
|
|
else { perr("bad assignment target") } }
|
|
}
|
|
let lt = llty(ty)
|
|
let rv = emit_expr(st.b)
|
|
var v = rv.code
|
|
if not streq(st.s, "=") {
|
|
let cur = emit_bind(sconcat("load ", sconcat(lt, sconcat(", ptr ", addr))))
|
|
var opc = "add"
|
|
if streq(st.s, "-=") { opc = "sub" }
|
|
if streq(st.s, "*=") { opc = "mul" }
|
|
if streq(st.s, "/=") { opc = "sdiv" }
|
|
v = emit_bind(sconcat(opc, sconcat(" i32 ", sconcat(cur, sconcat(", ", v)))))
|
|
}
|
|
store_at(lt, v, addr)
|
|
}
|
|
|
|
fn emit_if(st: Node) -> void {
|
|
let c = emit_expr(st.a)
|
|
let cc = emit_bind(sconcat("icmp ne i32 ", sconcat(c.code, ", 0")))
|
|
let has_else = not ptr_is_null(st.c)
|
|
let tl = lbl("then"); let el = lbl("else"); let en = lbl("ifend")
|
|
if has_else { emit(" br i1 "); emit(cc); emit(", label %"); emit(tl); emit(", label %"); emit(el); emit("\n") }
|
|
else { emit(" br i1 "); emit(cc); emit(", label %"); emit(tl); emit(", label %"); emit(en); emit("\n") }
|
|
emit(tl); emit(":\n"); g_term = false
|
|
emit_block(st.b)
|
|
if not g_term { emit(" br label %"); emit(en); emit("\n") }
|
|
if has_else {
|
|
emit(el); emit(":\n"); g_term = false
|
|
# else is either a block or a nested if-statement
|
|
if st.c.kind == S_IF { emit_stmt(st.c) } else { emit_block(st.c) }
|
|
if not g_term { emit(" br label %"); emit(en); emit("\n") }
|
|
}
|
|
emit(en); emit(":\n"); g_term = false
|
|
}
|
|
|
|
fn emit_while(st: Node) -> void {
|
|
let cl = lbl("wcond"); let bl = lbl("wbody"); let en = lbl("wend")
|
|
emit(" br label %"); emit(cl); emit("\n")
|
|
emit(cl); emit(":\n")
|
|
let c = emit_expr(st.a)
|
|
let cc = emit_bind(sconcat("icmp ne i32 ", sconcat(c.code, ", 0")))
|
|
emit(" br i1 "); emit(cc); emit(", label %"); emit(bl); emit(", label %"); emit(en); emit("\n")
|
|
emit(bl); emit(":\n"); g_term = false
|
|
loop_push(cl, en)
|
|
emit_block(st.b)
|
|
loop_pop()
|
|
if not g_term { emit(" br label %"); emit(cl); emit("\n") }
|
|
emit(en); emit(":\n"); g_term = false
|
|
}
|
|
|
|
fn emit_for(st: Node) -> void {
|
|
let slot = emit_alloca("i32")
|
|
let lo = emit_expr(st.a)
|
|
store_at("i32", lo.code, slot)
|
|
loc_push(st.s, slot, "int")
|
|
let cl = lbl("fcond"); let bl = lbl("fbody"); let ct = lbl("fcont"); let en = lbl("fend")
|
|
emit(" br label %"); emit(cl); emit("\n")
|
|
emit(cl); emit(":\n")
|
|
let iv = emit_bind(sconcat("load i32, ptr ", slot))
|
|
let hi = emit_expr(st.b)
|
|
let cc = emit_bind(sconcat("icmp slt i32 ", sconcat(iv, sconcat(", ", hi.code))))
|
|
emit(" br i1 "); emit(cc); emit(", label %"); emit(bl); emit(", label %"); emit(en); emit("\n")
|
|
emit(bl); emit(":\n"); g_term = false
|
|
loop_push(ct, en)
|
|
emit_block(st.c)
|
|
loop_pop()
|
|
if not g_term { emit(" br label %"); emit(ct); emit("\n") }
|
|
emit(ct); emit(":\n")
|
|
let i2 = emit_bind(sconcat("load i32, ptr ", slot))
|
|
let i3 = emit_bind(sconcat("add i32 ", sconcat(i2, ", 1")))
|
|
store_at("i32", i3, slot)
|
|
emit(" br label %"); emit(cl); emit("\n")
|
|
emit(en); emit(":\n"); g_term = false
|
|
}
|
|
|
|
fn emit_return(st: Node) -> void {
|
|
if not ptr_is_null(st.a) {
|
|
let v = emit_expr(st.a)
|
|
store_at(llty(ret_ty), v.code, "%retval")
|
|
}
|
|
emit(" br label %ret\n")
|
|
g_term = true
|
|
}
|
|
|
|
fn arm_is_default(arm: Node) -> bool {
|
|
var p = 0
|
|
while p < len(arm.kids) { if arm.kids[p].kind == E_ID and streq(arm.kids[p].s, "_") { return true }; p = p + 1 }
|
|
return false
|
|
}
|
|
|
|
fn emit_match(st: Node) -> void {
|
|
let sv = emit_expr(st.a)
|
|
let endl = lbl("mend")
|
|
var deflt: Node = ptr_null()
|
|
var i = 0
|
|
while i < len(st.kids) {
|
|
let arm = st.kids[i]
|
|
if arm_is_default(arm) { deflt = arm }
|
|
else {
|
|
var acc = "0"
|
|
var first = true
|
|
var p = 0
|
|
while p < len(arm.kids) {
|
|
let pv = emit_expr(arm.kids[p])
|
|
let c = emit_bind(sconcat("icmp eq i32 ", sconcat(sv.code, sconcat(", ", pv.code))))
|
|
if first { acc = c; first = false }
|
|
else { acc = emit_bind(sconcat("or i1 ", sconcat(acc, sconcat(", ", c)))) }
|
|
p = p + 1
|
|
}
|
|
let bodyl = lbl("mbody"); let nextl = lbl("marm")
|
|
emit(" br i1 "); emit(acc); emit(", label %"); emit(bodyl); emit(", label %"); emit(nextl); emit("\n")
|
|
emit(bodyl); emit(":\n"); g_term = false
|
|
emit_block(arm.a)
|
|
if not g_term { emit(" br label %"); emit(endl); emit("\n") }
|
|
emit(nextl); emit(":\n"); g_term = false
|
|
}
|
|
i = i + 1
|
|
}
|
|
if not ptr_is_null(deflt) { emit_block(deflt.a) }
|
|
if not g_term { emit(" br label %"); emit(endl); emit("\n") }
|
|
emit(endl); emit(":\n"); g_term = false
|
|
}
|
|
|
|
fn emit_stmt(st: Node) -> void {
|
|
if st.kind == S_LET {
|
|
var ty = st.ty
|
|
if ptr_is_null(ty) { let v0 = emit_expr(st.a); ty = v0.ty
|
|
let slot = emit_alloca(llty(ty)); store_at(llty(ty), v0.code, slot); loc_push(st.s, slot, ty); loc_set_mut(st.ival); return }
|
|
let slot = emit_alloca(llty(ty))
|
|
if ptr_is_null(st.a) {
|
|
var z = "0"
|
|
if streq(llty(ty), "ptr") { z = "null" }
|
|
store_at(llty(ty), z, slot)
|
|
} else { let v = emit_expr(st.a); store_at(llty(ty), v.code, slot) }
|
|
loc_push(st.s, slot, ty)
|
|
loc_set_mut(st.ival)
|
|
return
|
|
}
|
|
if st.kind == S_ASSIGN { emit_assign(st); return }
|
|
if st.kind == S_IF { emit_if(st); return }
|
|
if st.kind == S_WHILE { emit_while(st); return }
|
|
if st.kind == S_FOR { emit_for(st); return }
|
|
if st.kind == S_RETURN { emit_return(st); return }
|
|
if st.kind == S_BREAK { emit(" br label %"); emit(brk_lbl[nloop - 1]); emit("\n"); g_term = true; return }
|
|
if st.kind == S_CONTINUE { emit(" br label %"); emit(cnt_lbl[nloop - 1]); emit("\n"); g_term = true; return }
|
|
if st.kind == S_MATCH { emit_match(st); return }
|
|
if st.kind == S_QUERY { emit_query(st); return }
|
|
if st.kind == S_SPAWN { emit_spawn(st); return }
|
|
if st.kind == S_DESPAWN { emit_despawn(st); return }
|
|
if st.kind == S_TOGGLE { emit_toggle(st); return }
|
|
if st.kind == S_MACHINE { emit_machine(st); return }
|
|
if st.kind == S_BECOME { emit_become(st); return }
|
|
if st.kind == S_EXPR { let v = emit_expr(st.a); return }
|
|
perr("cannot emit statement")
|
|
}
|
|
|
|
fn loop_push(cont: ptr, brk: ptr) -> void {
|
|
if nloop < len(cnt_lbl) { cnt_lbl[nloop] = cont; brk_lbl[nloop] = brk }
|
|
else { push(cnt_lbl, cont); push(brk_lbl, brk) }
|
|
nloop = nloop + 1
|
|
}
|
|
fn loop_pop() -> void { nloop = nloop - 1 }
|