Phase 2: enforce statement separators (Rule B)

The parser now requires a newline or ';' between statements (block() in
selfhost/parse.ludic); two statements may no longer sit adjacent with only
spaces. Also fixed if-without-else swallowing its trailing separator.

Migration: tools/ludic-tools/migrate_separators.c inserts ';' at statement
boundaries corpus-wide (examples, runtime, 25 self-host fragments, ~1100
boundaries). Verified semantically identical — the migrated compiler compiles
itself to IR byte-identical to the pre-migration seed, and every golden game
renders identically. Reseeded to the strict compiler; C-free fixpoint holds;
test.sh 14/14.

Docs: Rule B documented in LANGUAGE.md; BOOTSTRAP.md R1 + stale fences updated;
check-docs green across all docs. Fixed a multi-line string literal in
emit_expr.ludic (byte-identical \n escape) that the C toolchain lexer mis-lexes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-08-27 15:41:29 +03:00
parent 985f9ad8f2
commit eac8f8f335
35 changed files with 3861 additions and 3588 deletions

View file

@ -853,8 +853,8 @@ current tree (`./test.sh` = 64/64).
**Recursion** ✅ → `55`
```ludic
game P {
fn fib(n: int) -> int { if n < 2 { return n } return fib(n-1) + fib(n-2) }
system B phase Start { print_int(fib(10)) quit() }
fn fib(n: int) -> int { if n < 2 { return n }; return fib(n-1) + fib(n-2) }
system B phase Start { print_int(fib(10)); quit() }
}
```
@ -878,11 +878,11 @@ fn streq(a: ptr, b: ptr) -> bool {
fn itoa(v: int, buf: ptr) -> int {
let n = 0
let x = v
if x == 0 { poke8(buf,0,48) return 1 }
if x == 0 { poke8(buf,0,48); return 1 }
let tmp = mem_alloc(16)
while x > 0 { poke8(tmp, n, 48 + x % 10) x = x / 10 n = n + 1 }
while x > 0 { poke8(tmp, n, 48 + x % 10); x = x / 10; n = n + 1 }
let i = 0
while i < n { poke8(buf, i, peek8(tmp, n-1-i)) i = i + 1 }
while i < n { poke8(buf, i, peek8(tmp, n-1-i)); i = i + 1 }
mem_free(tmp)
return n
}
@ -905,10 +905,17 @@ file_close(f)
Each is a spelling the language accepts; the point is that the *alternative*
spelling is equally legal (§5.3).
**R1 — three statements on one line, no separators** → `2`
**R1 — statements now require a separator (Rule B, syntax-redesign Phase 2)** → parse error
```ludic
# doc-check: skip — intentionally rejected under Rule B: needs a newline or ';'
game P { system B phase Start { let x = 1 x = x + 1 print_int(x) quit() } }
```
Statements no longer sit adjacent with only spaces between them; the compiler
reports `expected newline or ';' between statements`. Put each on its own line,
or separate them with `;` (both lex to the same separator token):
```ludic
game P { system B phase Start { let x = 1; x = x + 1; print_int(x); quit() } }
```
**R2 — commas omitted throughout** → `7`
```ludic
@ -954,7 +961,7 @@ parenthesisation) while `ludic-fmt` returns the input **unchanged**.
**Verified-missing** — each a compile error today:
```ludic
# doc-check: expect-error — every line here is a compile error by design
while i < 10 { i = i + 1 if i == 3 { break } } # unknown identifier 'break'
while i < 10 { i = i + 1; if i == 3 { break } } # unknown identifier 'break'
struct Node { k: int, a: ptr } # expected declaration (got 'struct')
var t: [int; 8] # expected identifier (got '[')
var h: fn = a # unknown type 'fn' for var h
@ -963,5 +970,5 @@ print_int(os_argc()) # unknown function 'os_argc'
print_err("x") # unknown function 'print_err'
let p = mem_realloc(ptr_null(), 10) # unknown function 'mem_realloc'
print_str("ab" + "cd") # passes front-end, invalid IR
let a = 100000 print_int(a*100000) # 1410065408 — i32 wrap
let a = 100000; print_int(a*100000) # 1410065408 — i32 wrap
```