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>
29 lines
930 B
Text
29 lines
930 B
Text
# buf.ludic — a growable byte buffer for building IR text. The emitter writes
|
|
# module-level material (types, globals, string constants) into one buffer and
|
|
# function bodies into another, then prints them in order.
|
|
|
|
property Buf { data: ptr = ptr_null(), len: int = 0, cap: int = 0 }
|
|
|
|
fn buf_new() -> Buf {
|
|
let b = new Buf
|
|
b.cap = 256
|
|
b.data = mem_alloc(b.cap)
|
|
b.len = 0
|
|
return b
|
|
}
|
|
fn buf_ensure(b: Buf, extra: int) -> void {
|
|
if b.len + extra + 1 <= b.cap { return }
|
|
while b.len + extra + 1 > b.cap { b.cap = b.cap * 2 }
|
|
b.data = mem_realloc(b.data, b.cap)
|
|
}
|
|
fn buf_putc(b: Buf, c: int) -> void {
|
|
buf_ensure(b, 1)
|
|
poke8(b.data, b.len, c)
|
|
b.len = b.len + 1
|
|
}
|
|
fn buf_puts(b: Buf, s: ptr) -> void {
|
|
var i = 0
|
|
while peek8(s, i) != 0 { buf_putc(b, peek8(s, i)); i = i + 1 }
|
|
}
|
|
fn buf_puti(b: Buf, n: int) -> void { buf_puts(b, itoa(n)) }
|
|
fn buf_str(b: Buf) -> ptr { poke8(b.data, b.len, 0); return b.data }
|