Phase 6f: let = immutable, var = mutable (binding-only)

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>
This commit is contained in:
Orkun ÇAKILKAYA 2026-08-27 23:52:59 +03:00
parent 573813609c
commit e1d7797e29
38 changed files with 2909 additions and 2778 deletions

View file

@ -79,10 +79,10 @@ fn rt_fill_rect(x: int, y: int, w: int, h: int, c: int) -> void {
let y0 = max(0, y)
let x1 = min(rt_fbw, x + w)
let y1 = min(rt_fbh, y + h)
let j = y0
var j = y0
while j < y1 {
let row = j * rt_fbw
let i = x0
var i = x0
while i < x1 {
poke32(rt_fb, row + i, c)
i = i + 1
@ -108,7 +108,7 @@ fn rt_present() -> void {
# ---- text -----------------------------------------------------------------
fn rt_glyph(x: int, y: int, ch: int, colour: int, sc: int) -> void {
let c = ch
var c = ch
if c >= 97 {
if c <= 122 { c = c - 32 }
}
@ -118,7 +118,7 @@ fn rt_glyph(x: int, y: int, ch: int, colour: int, sc: int) -> void {
let font = rt_font()
for row in 0 .. 7 {
let bits = peek8(font, base + row) - 48
let b = bits
var b = bits
for cc in 0 .. 5 {
let on = b / 16
if on == 1 {
@ -130,9 +130,9 @@ fn rt_glyph(x: int, y: int, ch: int, colour: int, sc: int) -> void {
}
fn rt_text(x: int, y: int, s: str, colour: int, sc: int) -> void {
let i = 0
let cx = x
let ch = peek8(s, 0)
var i = 0
var cx = x
var ch = peek8(s, 0)
while ch != 0 {
rt_glyph(cx, y, ch, colour, sc)
cx = cx + 6 * sc
@ -146,22 +146,22 @@ fn rt_text_int(x: int, y: int, n: int, colour: int, sc: int) -> void {
rt_glyph(x, y, 48, colour, sc)
return
}
let v = n
let cx = x
var v = n
var cx = x
if v < 0 {
rt_glyph(cx, y, 45, colour, sc)
cx = cx + 6 * sc
v = 0 - v
}
let digits = 0
let t = v
var digits = 0
var t = v
while t > 0 {
digits = digits + 1
t = t / 10
}
let p = digits
var p = digits
while p > 0 {
let div = 1
var div = 1
for k in 1 .. p {
div = div * 10
}
@ -196,7 +196,7 @@ fn rt_seed(s: int) -> void {
# xorshift32 (Marsaglia). Runs on the raw 32-bit pattern, so the sign bit is
# masked off only when a caller asks for a number.
fn rt_next_rand() -> int {
let x = rt_rng
var x = rt_rng
x = bxor(x, shl(x, 13))
x = bxor(x, shr(x, 17))
x = bxor(x, shl(x, 5))
@ -238,9 +238,9 @@ fn rt_running() -> bool {
# ---- writing the frame out ------------------------------------------------
fn rt_put_str(buf: ptr, at: int, s: str) -> int {
let i = 0
let n = at
let ch = peek8(s, 0)
var i = 0
var n = at
var ch = peek8(s, 0)
while ch != 0 {
poke8(buf, n, ch)
n = n + 1
@ -255,16 +255,16 @@ fn rt_put_int(buf: ptr, at: int, v: int) -> int {
poke8(buf, at, 48)
return at + 1
}
let digits = 0
let t = v
var digits = 0
var t = v
while t > 0 {
digits = digits + 1
t = t / 10
}
let n = at
let p = digits
var n = at
var p = digits
while p > 0 {
let div = 1
var div = 1
for k in 1 .. p {
div = div * 10
}
@ -280,7 +280,7 @@ fn rt_dump_ppm(path: str) -> void {
if ptr_is_null(f) { return }
let hdr = mem_alloc(64)
let n = rt_put_str(hdr, 0, "P6\n")
var n = rt_put_str(hdr, 0, "P6\n")
n = rt_put_int(hdr, n, rt_fbw)
n = rt_put_str(hdr, n, " ")
n = rt_put_int(hdr, n, rt_fbh)
@ -323,8 +323,8 @@ fn rt_map_size(w: int, h: int) -> void {
fn rt_map_row(y: int, s: str) -> void {
if y < 0 { return }
if y >= 64 { return }
let x = 0
let ch = peek8(s, 0)
var x = 0
var ch = peek8(s, 0)
while ch != 0 {
if x >= 96 { return }
poke8(rt_map, y * 96 + x, ch)
@ -347,8 +347,8 @@ fn rt_tile(x: int, y: int) -> int {
var rt_statusbuf: ptr = ptr_null()
fn rt_status(s: str) -> void {
let i = 0
let ch = peek8(s, 0)
var i = 0
var ch = peek8(s, 0)
while ch != 0 {
if i >= 95 { ch = 0 }
if ch != 0 {