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
```

View file

@ -130,7 +130,7 @@ system Nav phase Update {
if ui_clicked(UI_Quit) { quit() }
ui_set_int(UI_HpLabel, hp) # poke dynamic values by id
}
system Draw phase Render { clear(0x0e0e16) ui_render() present() }
system Draw phase Render { clear(0x0e0e16); ui_render(); present() }
```
See `examples/menu.ludic` for a complete title screen.
@ -335,6 +335,18 @@ another `.ludic` file (see `examples/lib/`).
`for (…) in query […] { }` · `break` · `continue` · `return` · `spawn` ·
`despawn` · `match` · `machine`.
**Statements are separated by a newline or `;`** (both lex to the same separator
token). Two statements may not sit adjacent with only spaces between them — the
compiler reports `expected newline or ';' between statements`. Write one
statement per line, or, to pack several onto a line, separate them with `;`:
```ludic
# doc-check: skip — a bare statement block, not a whole declaration
let x = 1
x = x + 1 # one per line, the usual form
let y = 1; y = y + 1 # or `;`-separated on one line
```
`break` and `continue` apply to the innermost enclosing loop, and work in all
three loop forms — `while`, the numeric `for`, and the ECS query loop, where
`continue` advances to the next matching entity. Using either outside a loop is

View file

@ -4,10 +4,10 @@ A plan to make Ludic's syntax internally consistent. It fixes the drift between
the spec and the compiler, then unifies the grammar around two rules. Scope:
**full redesign (Phases 0–5)**. Named-field direction: **colon everywhere**.
> Status: **Phase 1 landed** (see below). Phases 2→5 are proposals. The phases
> are ordered so the documentation never describes syntax the compiler rejects,
> and every phase ends with the compiler still self-hosting to a fixpoint
> (`./test.sh`).
> Status: **Phases 1 & 2 landed** (see below). Phases 3→5 are proposals. The
> phases are ordered so the documentation never describes syntax the compiler
> rejects, and every phase ends with the compiler still self-hosting to a
> fixpoint (`./test.sh`).
>
> Coordinated with the toolchain agent (CLI front-end / `ludicc`+`ludic`
> binaries) via serialized reseeds of `selfhost/ludicc.seed.ll`; Phase 1 rode in
@ -208,13 +208,36 @@ Made spec ⇄ compiler agree **before** any grammar change. What landed:
compile yet — a positive test would fail; the header note + LANGUAGE.md warning
cover the drift instead).
### Phase 2 — Statement separation (Rule B)
- Enforce a separator in `block()`/`stmt()` ([parse.ludic:130-199](selfhost/parse.ludic)):
after a statement, require `TK_NL` or `}`.
- Teach `ludic-fmt` to normalize one-statement-per-line and insert `;` where two
share a line. Reformat the whole corpus with it.
- Risk: the self-host sources themselves use space-juxtaposed statements heavily
— reformat them in the same commit, re-seed, re-verify fixpoint.
### Phase 2 — Statement separation (Rule B) ✅ DONE
Landed on branch `syntax-redesign-phase2` (baseline committed on `main` first).
- ✅ **Parser enforces a separator** — `block()` requires a newline or `;` after
each statement, else `expected newline or ';' between statements`
([parse.ludic](selfhost/parse.ludic)). Also fixed `if`-without-`else` swallowing
its trailing separator (it now peeks for `else` and restores if absent).
- ✅ **Interpretation chosen:** *require a separator*, not *reflow to one-per-line*.
The migration **inserts `;` at statement boundaries** and leaves lines intact —
comment-safe, minimal-diff, and it makes boundaries visible without an
opinionated reflow. One-per-line stays the recommended hand-written form.
- ✅ **Migration tool** ([tools/ludic-tools/migrate_separators.c](tools/ludic-tools/migrate_separators.c),
reuses the toolchain lexer) with a
**verification oracle**: a `;` inserted at a real boundary is a semantic no-op,
proven by the migrated compiler compiling itself to **IR byte-identical to the
seed** and every golden game rendering identically. ~1100 boundaries across the
corpus (examples, runtime, and the 25 self-host fragments).
- ✅ **Reseeded** to the strict compiler (19557 lines); C-free bootstrap fixpoint
holds; `./test.sh` 14/14; all goldens byte-identical; qdecl runs correctly.
- ✅ **Docs updated** — Rule B documented in LANGUAGE.md §Statements; BOOTSTRAP.md
R1 (which advertised no-separator juxtaposition as legal) and its stale code
fences updated; `check-docs` (now a live strict parse gate) green across all docs.
**Bug found & fixed en route:** a multi-line string literal in
[emit_expr.ludic](selfhost/emit_expr.ludic) (`emit(")<newline>")`) lexed fine in
the self-host lexer but the **C toolchain lexer** (`ludic_syntax.h`, shared by
sepfix, `ludic-fmt`, and the LSP) stops strings at newline — so it mis-lexed and
`ludic-fmt` would corrupt such a file. Converted it to the byte-identical `\n`
escape. **Open follow-up:** align the C lexer to allow newlines in strings, or
forbid literal newlines in string literals language-wide (the two lexers should
agree). Flagged to the toolchain owners.
### Phase 3 — Named-field unification (Rule A)
- Migrate spawn/record init (`= {…=…}` → `{…:…}`) and ui props (`key=value` →

View file

@ -25,7 +25,7 @@ system Battle phase Update {
e.hp = e.hp - d
setreg(R_PDMG, d)
}
1 => { st.guard = 1 setreg(R_PDMG, 0) }
1 => { st.guard = 1; setreg(R_PDMG, 0) }
2 => {
if reg(R_POTION) > 0 {
setreg(R_POTION, reg(R_POTION) - 1)
@ -34,7 +34,7 @@ system Battle phase Update {
} else { setreg(R_PDMG, 0) }
}
3 => {
if rng_chance(50) { e.hp = 0 setreg(R_MODE, 0) }
if rng_chance(50) { e.hp = 0; setreg(R_MODE, 0) }
else { setreg(R_PDMG, 0) }
}
}
@ -51,7 +51,7 @@ system Battle phase Update {
if e.hp <= 0 {
grant_xp(e.xp)
setreg(R_GOLD, reg(R_GOLD) + e.xp)
if reg(R_ETYPE) == 2 { setreg(R_BOSS, 1) setreg(R_MODE, 3) setreg(R_ACK, 0) }
if reg(R_ETYPE) == 2 { setreg(R_BOSS, 1); setreg(R_MODE, 3); setreg(R_ACK, 0) }
else { setreg(R_MODE, 0) }
become KnightMenu
} else {
@ -85,7 +85,7 @@ system Battle phase Update {
setreg(R_PDMG, 16)
} else { setreg(R_PDMG, 0) }
}
2 => { st.guard = 1 setreg(R_PDMG, 0) }
2 => { st.guard = 1; setreg(R_PDMG, 0) }
}
become MageResolve
setreg(R_CUR, 0)
@ -100,7 +100,7 @@ system Battle phase Update {
if e.hp <= 0 {
grant_xp(e.xp)
setreg(R_GOLD, reg(R_GOLD) + e.xp)
if reg(R_ETYPE) == 2 { setreg(R_BOSS, 1) setreg(R_MODE, 3) setreg(R_ACK, 0) }
if reg(R_ETYPE) == 2 { setreg(R_BOSS, 1); setreg(R_MODE, 3); setreg(R_ACK, 0) }
else { setreg(R_MODE, 0) }
become KnightMenu
} else {
@ -116,7 +116,7 @@ system Battle phase Update {
if done == 0 {
if t3.hp > 0 {
let ed = max(1, e.atk - t3.def + rng_range(0, 4))
if t3.guard == 1 { ed = ed / 2 t3.guard = 0 }
if t3.guard == 1 { ed = ed / 2; t3.guard = 0 }
t3.hp = t3.hp - ed
setreg(R_EDMG, ed)
done = 1
@ -127,7 +127,7 @@ system Battle phase Update {
for (t4) in query [Stats, {Party}] {
if t4.hp > 0 { alive = alive + 1 }
}
if alive == 0 { setreg(R_MODE, 2) setreg(R_ACK, 0) }
if alive == 0 { setreg(R_MODE, 2); setreg(R_ACK, 0) }
else {
become KnightMenu
setreg(R_CUR, 0)

View file

@ -7,7 +7,7 @@
game SceneDemo {
const R_N: int = 0
system Boot phase Start { setreg(R_N, 0) print_int(1000) }
system Boot phase Start { setreg(R_N, 0); print_int(1000) }
scene Title start {
on enter { print_int(1) }
@ -22,7 +22,7 @@ game SceneDemo {
}
scene Play {
on enter { print_int(3) setreg(R_N, 0) }
on enter { print_int(3); setreg(R_N, 0) }
on exit { print_int(4) }
layer World {
system Step phase Update {

View file

@ -26,9 +26,9 @@ game Snake {
fn reset(x: int) -> int {
for (p, s) in query [Pos, Seg] { despawn self() }
spawn S0 { Seg = { order = 0 } Pos = { x = 10, y = 7 } }
spawn S1 { Seg = { order = 1 } Pos = { x = 9, y = 7 } }
spawn S2 { Seg = { order = 2 } Pos = { x = 8, y = 7 } }
spawn S0 { Seg = { order = 0 }; Pos = { x = 10, y = 7 } }
spawn S1 { Seg = { order = 1 }; Pos = { x = 9, y = 7 } }
spawn S2 { Seg = { order = 2 }; Pos = { x = 8, y = 7 } }
setreg(R_DIR, 3)
setreg(R_LEN, 3)
setreg(R_TICK, 0)
@ -62,7 +62,7 @@ game Snake {
setreg(R_TICK, 0)
let len = reg(R_LEN)
for (pt, stg) in query [Pos, Seg] {
if stg.order == len - 1 { setreg(R_TX, pt.x) setreg(R_TY, pt.y) }
if stg.order == len - 1 { setreg(R_TX, pt.x); setreg(R_TY, pt.y) }
}
for step in 0 .. len {
let i = len - 1 - step
@ -70,7 +70,7 @@ game Snake {
for (pa, sa) in query [Pos, Seg] {
if sa.order == i {
for (pb, sb) in query [Pos, Seg] {
if sb.order == i - 1 { pa.x = pb.x pa.y = pb.y }
if sb.order == i - 1 { pa.x = pb.x; pa.y = pb.y }
}
}
}
@ -101,7 +101,7 @@ game Snake {
}
if reg(R_ATE) == 1 {
setreg(R_SCORE, reg(R_SCORE) + 1)
spawn Body { Seg = { order = len } Pos = { x = reg(R_TX), y = reg(R_TY) } }
spawn Body { Seg = { order = len }; Pos = { x = reg(R_TX), y = reg(R_TY) } }
setreg(R_LEN, len + 1)
setreg(R_FX, rng_range(0, GW - 1))
setreg(R_FY, rng_range(0, GH - 1))

View file

@ -49,7 +49,7 @@ fn rt_read_file(path: str) -> ptr {
file_seek(f, 0, 2)
let n = file_tell(f)
file_seek(f, 0, 0)
if n <= 0 { file_close(f) return ptr_null() }
if n <= 0 { file_close(f); return ptr_null() }
let buf = mem_alloc(n + 8)
file_read(f, buf, n)
file_close(f)
@ -70,9 +70,9 @@ fn rt_decode_png(path: str) -> bool {
let d = rt_read_file(path)
if ptr_is_null(d) { return false }
let size = rt_file_len
if size < 8 { mem_free(d) return false }
if peek8(d, 0) != 137 { mem_free(d) return false }
if peek8(d, 1) != 80 { mem_free(d) return false }
if size < 8 { mem_free(d); return false }
if peek8(d, 0) != 137 { mem_free(d); return false }
if peek8(d, 1) != 80 { mem_free(d); return false }
let w = 0
let h = 0
@ -119,8 +119,8 @@ fn rt_decode_png(path: str) -> bool {
}
}
if w <= 0 { mem_free(d) return false }
if h <= 0 { mem_free(d) return false }
if w <= 0 { mem_free(d); return false }
if h <= 0 { mem_free(d); return false }
let channels = 1
if ct == 2 { channels = 3 }
@ -133,7 +133,7 @@ fn rt_decode_png(path: str) -> bool {
let rawlen = h * (stride + 1)
let raw = mem_alloc(rawlen + 8)
if z_uncompress(idat, idlen, raw, rawlen) < 0 {
mem_free(d) mem_free(raw) mem_free(idat)
mem_free(d); mem_free(raw); mem_free(idat)
return false
}
@ -378,7 +378,7 @@ fn rt_hexval(c: int) -> int {
fn rt_load_sprites(path: str) -> void {
let d = rt_read_file(path)
if ptr_is_null(d) { spr_n = 0 return }
if ptr_is_null(d) { spr_n = 0; return }
let size = rt_file_len
let pal = mem_alloc(128 * 4)
mem_set(pal, 0, 128 * 4)
@ -391,7 +391,7 @@ fn rt_load_sprites(path: str) -> void {
let j = start
while j < size {
let ch = peek8(d, j)
if ch == 10 { j = size } else { len = len + 1 j = j + 1 }
if ch == 10 { j = size } else { len = len + 1; j = j + 1 }
}
let c0 = peek8(d, start)
if c0 == 112 { # 'p' — "pal <char> <rrggbb>"

View file

@ -322,7 +322,7 @@ fn tt_load_outline(id: int, gid: int, a: fixed, b: fixed, c: fixed, e: fixed, dx
let r = peek8(d, p)
p = p + 1
while r > 0 {
if i < npts { poke8(flags, i, fl) i = i + 1 }
if i < npts { poke8(flags, i, fl); i = i + 1 }
r = r - 1
}
}
@ -338,7 +338,7 @@ fn tt_load_outline(id: int, gid: int, a: fixed, b: fixed, c: fixed, e: fixed, dx
p = p + 1
if band(fl, 16) != 0 { xv = xv + dxv } else { xv = xv - dxv }
} else {
if band(fl, 16) == 0 { xv = xv + tt_i16(d, p) p = p + 2 }
if band(fl, 16) == 0 { xv = xv + tt_i16(d, p); p = p + 2 }
}
poke32(xs, k, xv)
}
@ -350,7 +350,7 @@ fn tt_load_outline(id: int, gid: int, a: fixed, b: fixed, c: fixed, e: fixed, dx
p = p + 1
if band(fl, 32) != 0 { yv = yv + dyv } else { yv = yv - dyv }
} else {
if band(fl, 32) == 0 { yv = yv + tt_i16(d, p) p = p + 2 }
if band(fl, 32) == 0 { yv = yv + tt_i16(d, p); p = p + 2 }
}
poke32(ys, k, yv)
}

View file

@ -23,7 +23,7 @@ fn buf_putc(b: Buf, c: int) -> void {
}
fn buf_puts(b: Buf, s: ptr) -> void {
let i = 0
while peek8(s, i) != 0 { buf_putc(b, peek8(s, i)) i = i + 1 }
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 }
fn buf_str(b: Buf) -> ptr { poke8(b.data, b.len, 0); return b.data }

View file

@ -13,8 +13,8 @@ fn emit_member_addr(e: Node) -> ptr {
if fidx < 0 { perr(sconcat("no such field ", e.s)) }
g_addr_ty = field_type(s, e.s)
let r = nreg()
emit(" ") emit(r) emit(" = getelementptr inbounds ") emit(layout_ty(base.ty))
emit(", ptr ") emit(base.code) emit(", i32 0, i32 ") emit(itoa(fidx)) emit("\n")
emit(" "); emit(r); emit(" = getelementptr inbounds "); emit(layout_ty(base.ty))
emit(", ptr "); emit(base.code); emit(", i32 0, i32 "); emit(itoa(fidx)); emit("\n")
return r
}
@ -26,13 +26,13 @@ fn emit_index_addr(e: Node) -> ptr {
g_addr_ty = el
# load the data pointer from the slice header (field 0)
let dp = nreg()
emit(" ") emit(dp) emit(" = getelementptr inbounds %LSlice, ptr ")
emit(base.code) emit(", i32 0, i32 0\n")
emit(" "); emit(dp); emit(" = getelementptr inbounds %LSlice, ptr ")
emit(base.code); emit(", i32 0, i32 0\n")
let data = nreg()
emit(" ") emit(data) emit(" = load ptr, ptr ") emit(dp) emit("\n")
emit(" "); emit(data); emit(" = load ptr, ptr "); emit(dp); emit("\n")
let ix = emit_expr(e.b)
let r = nreg()
emit(" ") emit(r) emit(" = getelementptr inbounds ") emit(llty(el))
emit(", ptr ") emit(data) emit(", i32 ") emit(ix.code) emit("\n")
emit(" "); emit(r); emit(" = getelementptr inbounds "); emit(llty(el))
emit(", ptr "); emit(data); emit(", i32 "); emit(ix.code); emit("\n")
return r
}

View file

@ -3,7 +3,7 @@
# structs and slices are references, so every non-scalar type lowers to `ptr`.
struct Val { code: ptr = ptr_null(), ty: ptr = ptr_null() }
fn val(code: ptr, ty: ptr) -> Val { let v = new Val v.code = code v.ty = ty return v }
fn val(code: ptr, ty: ptr) -> Val { let v = new Val; v.code = code; v.ty = ty; return v }
var head: Buf # module-level: types, globals, string constants
var code: Buf # function bodies
@ -36,24 +36,24 @@ fn emith(s: ptr) -> void { buf_puts(head, s) }
# stack slots MUST live in the entry block (an alloca in a loop walks the stack
# off its end), so they go into a per-function buffer spliced in at entry.
fn emit_alloca(llt: ptr) -> ptr {
let r = sconcat("%t", itoa(ll_t)) ll_t = ll_t + 1
buf_puts(falloc, " ") buf_puts(falloc, r) buf_puts(falloc, " = alloca ") buf_puts(falloc, llt) buf_puts(falloc, "\n")
let r = sconcat("%t", itoa(ll_t)); ll_t = ll_t + 1
buf_puts(falloc, " "); buf_puts(falloc, r); buf_puts(falloc, " = alloca "); buf_puts(falloc, llt); buf_puts(falloc, "\n")
return r
}
# "%t<n>" fresh register
fn sconcat(a: ptr, b: ptr) -> ptr {
let la = slen(a) let lb = slen(b)
let la = slen(a); let lb = slen(b)
let out = mem_alloc(la + lb + 1)
let i = 0
while i < la { poke8(out, i, peek8(a, i)) i = i + 1 }
while i < la { poke8(out, i, peek8(a, i)); i = i + 1 }
let j = 0
while j < lb { poke8(out, la + j, peek8(b, j)) j = j + 1 }
while j < lb { poke8(out, la + j, peek8(b, j)); j = j + 1 }
poke8(out, la + lb, 0)
return out
}
fn nreg() -> ptr { let r = sconcat("%t", itoa(ll_t)) ll_t = ll_t + 1 return r }
fn lbl(pfx: ptr) -> ptr { let r = sconcat(pfx, itoa(ll_lbl)) ll_lbl = ll_lbl + 1 return r }
fn nreg() -> ptr { let r = sconcat("%t", itoa(ll_t)); ll_t = ll_t + 1; return r }
fn lbl(pfx: ptr) -> ptr { let r = sconcat(pfx, itoa(ll_lbl)); ll_lbl = ll_lbl + 1; return r }
# Ludic type -> LLVM type. int/bool are i32; everything else (ptr/str/struct/
# slice) is a pointer; void is void.
@ -79,17 +79,17 @@ fn is_struct_ty(t: ptr) -> bool { return not ptr_is_null(find_struct(t)) }
fn find_arch(name: ptr) -> Node {
let i = 0
while i < len(prog) { let d = prog[i] if d.kind == N_ARCH and streq(d.s, name) { return d } i = i + 1 }
while i < len(prog) { let d = prog[i]; if d.kind == N_ARCH and streq(d.s, name) { return d }; i = i + 1 }
return ptr_null()
}
fn find_comp(name: ptr) -> Node {
let i = 0
while i < len(prog) { let d = prog[i] if d.kind == N_COMP and streq(d.s, name) { return d } i = i + 1 }
while i < len(prog) { let d = prog[i]; if d.kind == N_COMP and streq(d.s, name) { return d }; i = i + 1 }
return ptr_null()
}
# a struct or a component — both have %Str_/%Cmp_ layouts with named fields
fn layout_node(name: ptr) -> Node {
let s = find_struct(name) if not ptr_is_null(s) { return s }
let s = find_struct(name); if not ptr_is_null(s) { return s }
return find_comp(name)
}
fn layout_ty(name: ptr) -> ptr {
@ -99,12 +99,12 @@ fn layout_ty(name: ptr) -> ptr {
fn field_index(s: Node, fname: ptr) -> int {
let i = 0
while i < len(s.kids) { if streq(s.kids[i].s, fname) { return i } i = i + 1 }
while i < len(s.kids) { if streq(s.kids[i].s, fname) { return i }; i = i + 1 }
return 0 - 1
}
fn field_type(s: Node, fname: ptr) -> ptr {
let i = 0
while i < len(s.kids) { if streq(s.kids[i].s, fname) { return s.kids[i].ty } i = i + 1 }
while i < len(s.kids) { if streq(s.kids[i].s, fname) { return s.kids[i].ty }; i = i + 1 }
return "int"
}
@ -121,19 +121,19 @@ fn find_global(name: ptr) -> Node {
}
fn find_fn(name: ptr) -> Node {
let i = 0
while i < len(prog) { let d = prog[i] if d.kind == N_FN and streq(d.s, name) { return d } i = i + 1 }
while i < len(prog) { let d = prog[i]; if d.kind == N_FN and streq(d.s, name) { return d }; i = i + 1 }
return ptr_null()
}
# local variable environment
fn loc_reset() -> void { nloc = 0 }
fn loc_push(name: ptr, r: ptr, ty: ptr) -> void {
if nloc < len(loc_name) { loc_name[nloc] = name loc_reg[nloc] = r loc_ty[nloc] = ty }
else { push(loc_name, name) push(loc_reg, r) push(loc_ty, ty) }
if nloc < len(loc_name) { loc_name[nloc] = name; loc_reg[nloc] = r; loc_ty[nloc] = ty }
else { push(loc_name, name); push(loc_reg, r); push(loc_ty, ty) }
nloc = nloc + 1
}
fn loc_find(name: ptr) -> int {
let i = nloc - 1
while i >= 0 { if streq(loc_name[i], name) { return i } i = i - 1 }
while i >= 0 { if streq(loc_name[i], name) { return i }; i = i - 1 }
return 0 - 1
}

View file

@ -6,26 +6,26 @@ fn emit_params_sig(d: Node) -> void {
let i = 0
while i < len(d.kids) {
if i > 0 { emit(", ") }
emit(llty(d.kids[i].ty)) emit(" %arg_") emit(d.kids[i].s)
emit(llty(d.kids[i].ty)); emit(" %arg_"); emit(d.kids[i].s)
i = i + 1
}
}
fn emit_fn(d: Node) -> void {
ll_t = 0 ll_lbl = 0 g_term = false loc_reset() nloop = 0
ll_t = 0; ll_lbl = 0; g_term = false; loc_reset(); nloop = 0
ret_ty = d.ty
let fbody = buf_new()
falloc = buf_new()
let saved = code
code = fbody
let rl = llty(ret_ty)
if not streq(rl, "void") { buf_puts(falloc, " %retval = alloca ") buf_puts(falloc, rl) buf_puts(falloc, "\n") }
if not streq(rl, "void") { buf_puts(falloc, " %retval = alloca "); buf_puts(falloc, rl); buf_puts(falloc, "\n") }
# params: store each incoming argument into a stack slot
let i = 0
while i < len(d.kids) {
let p = d.kids[i]
let slot = emit_alloca(llty(p.ty))
emit(" store ") emit(llty(p.ty)) emit(" %arg_") emit(p.s) emit(", ptr ") emit(slot) emit("\n")
emit(" store "); emit(llty(p.ty)); emit(" %arg_"); emit(p.s); emit(", ptr "); emit(slot); emit("\n")
loc_push(p.s, slot, p.ty)
i = i + 1
}
@ -33,16 +33,16 @@ fn emit_fn(d: Node) -> void {
if not g_term { emit(" br label %ret\n") }
emit("ret:\n")
if streq(rl, "void") { emit(" ret void\n") }
else { let r = emit_bind(sconcat("load ", sconcat(rl, ", ptr %retval"))) emit(" ret ") emit(rl) emit(" ") emit(r) emit("\n") }
else { let r = emit_bind(sconcat("load ", sconcat(rl, ", ptr %retval"))); emit(" ret "); emit(rl); emit(" "); emit(r); emit("\n") }
code = saved
emit("define ") emit(rl) emit(" @fn_") emit(d.s) emit("(") emit_params_sig(d) emit(") {\nentry:\n")
emit("define "); emit(rl); emit(" @fn_"); emit(d.s); emit("("); emit_params_sig(d); emit(") {\nentry:\n")
emit(buf_str(falloc))
emit(buf_str(fbody))
emit("}\n\n")
}
fn emit_main(d: Node) -> void {
ll_t = 0 ll_lbl = 0 g_term = false loc_reset() nloop = 0
ll_t = 0; ll_lbl = 0; g_term = false; loc_reset(); nloop = 0
ret_ty = "int"
let fbody = buf_new()
falloc = buf_new()
@ -56,7 +56,7 @@ fn emit_main(d: Node) -> void {
if not g_term { emit(" br label %ret\n") }
emit("ret:\n")
let r = emit_bind("load i32, ptr %retval")
emit(" ret i32 ") emit(r) emit("\n")
emit(" ret i32 "); emit(r); emit("\n")
code = saved
emit("define i32 @main(i32 %argc, ptr %argv) {\nentry:\n")
emit(buf_str(falloc))
@ -67,20 +67,20 @@ fn emit_main(d: Node) -> void {
fn emit_program() -> void {
head = buf_new()
code = buf_new()
loc_name = new []ptr loc_reg = new []ptr loc_ty = new []ptr
brk_lbl = new []ptr cnt_lbl = new []ptr
loc_name = new []ptr; loc_reg = new []ptr; loc_ty = new []ptr
brk_lbl = new []ptr; cnt_lbl = new []ptr
self_stk = new []ptr
mach_stk = new []Node
emit_header()
if has_ecs() { emit_ecs_storage() }
let i = 0
while i < len(prog) { if prog[i].kind == N_FN { emit_fn(prog[i]) } i = i + 1 }
if has_ecs() { emit_ecs_allocator() emit_snapshot() }
while i < len(prog) { if prog[i].kind == N_FN { emit_fn(prog[i]) }; i = i + 1 }
if has_ecs() { emit_ecs_allocator(); emit_snapshot() }
if has_ui() { emit_ui_build() }
if has_systems() { emit_game_main() }
else {
i = 0
while i < len(prog) { if prog[i].kind == N_MAIN { emit_main(prog[i]) } i = i + 1 }
while i < len(prog) { if prog[i].kind == N_MAIN { emit_main(prog[i]) }; i = i + 1 }
}
}

View file

@ -7,12 +7,12 @@ const MAX_ENT: int = 1024
fn has_ecs() -> bool {
let i = 0
while i < len(prog) { let k = prog[i].kind if k == N_COMP or k == N_SYS { return true } i = i + 1 }
while i < len(prog) { let k = prog[i].kind; if k == N_COMP or k == N_SYS { return true }; i = i + 1 }
return false
}
fn has_systems() -> bool {
let i = 0
while i < len(prog) { if prog[i].kind == N_SYS { return true } i = i + 1 }
while i < len(prog) { if prog[i].kind == N_SYS { return true }; i = i + 1 }
return false
}
@ -32,7 +32,7 @@ fn emit_ecs_storage() -> void {
emith(sconcat("%Cmp_", sconcat(c.s, " = type { ")))
if len(c.kids) == 0 { emith("i32") }
let f = 0
while f < len(c.kids) { if f > 0 { emith(", ") } emith(llty(c.kids[f].ty)) f = f + 1 }
while f < len(c.kids) { if f > 0 { emith(", ") }; emith(llty(c.kids[f].ty)); f = f + 1 }
emith(" }\n")
emith(sconcat("@S_", sconcat(c.s, sconcat(" = internal global [", sconcat(me, sconcat(" x %Cmp_", sconcat(c.s, "] zeroinitializer\n")))))))
emith(sconcat("@H_", sconcat(c.s, sconcat(" = internal global [", sconcat(me, " x i8] zeroinitializer\n")))))
@ -49,12 +49,12 @@ fn emit_ecs_allocator() -> void {
while i < len(prog) {
if prog[i].kind == N_COMP {
let hn = sconcat("%h", itoa(i))
emit(" ") emit(hn) emit(" = getelementptr inbounds [") emit(me) emit(" x i8], ptr @H_") emit(prog[i].s) emit(", i32 0, i32 %e\n")
emit(" store i8 0, ptr ") emit(hn) emit("\n")
emit(" "); emit(hn); emit(" = getelementptr inbounds ["); emit(me); emit(" x i8], ptr @H_"); emit(prog[i].s); emit(", i32 0, i32 %e\n")
emit(" store i8 0, ptr "); emit(hn); emit("\n")
}
i = i + 1
}
emit(" %k = getelementptr inbounds [") emit(me) emit(" x i32], ptr @L_kind, i32 0, i32 %e\n")
emit(" %k = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_kind, i32 0, i32 %e\n")
emit(" store i32 0, ptr %k\n ret void\n}\n\n")
emit("define i32 @L_alloc() {\nentry:\n")
@ -64,7 +64,7 @@ fn emit_ecs_allocator() -> void {
emit("reuse:\n")
emit(" %fn1 = sub i32 %fn, 1\n")
emit(" store i32 %fn1, ptr @L_freen\n")
emit(" %fp = getelementptr inbounds [") emit(me) emit(" x i32], ptr @L_freelist, i32 0, i32 %fn1\n")
emit(" %fp = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_freelist, i32 0, i32 %fn1\n")
emit(" %re = load i32, ptr %fp\n")
emit(" br label %done\n")
emit("fresh:\n")
@ -75,16 +75,16 @@ fn emit_ecs_allocator() -> void {
emit("done:\n")
emit(" %e = phi i32 [ %re, %reuse ], [ %ec, %fresh ]\n")
emit(" call void @L_reset(i32 %e)\n")
emit(" %ap = getelementptr inbounds [") emit(me) emit(" x i32], ptr @L_alive, i32 0, i32 %e\n")
emit(" %ap = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_alive, i32 0, i32 %e\n")
emit(" store i32 1, ptr %ap\n")
emit(" ret i32 %e\n}\n\n")
emit("define void @L_free_entity(i32 %e) {\nentry:\n")
emit(" %ap = getelementptr inbounds [") emit(me) emit(" x i32], ptr @L_alive, i32 0, i32 %e\n")
emit(" %ap = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_alive, i32 0, i32 %e\n")
emit(" store i32 0, ptr %ap\n")
emit(" call void @L_reset(i32 %e)\n")
emit(" %fn = load i32, ptr @L_freen\n")
emit(" %fp = getelementptr inbounds [") emit(me) emit(" x i32], ptr @L_freelist, i32 0, i32 %fn\n")
emit(" %fp = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_freelist, i32 0, i32 %fn\n")
emit(" store i32 %e, ptr %fp\n")
emit(" %fn1 = add i32 %fn, 1\n")
emit(" store i32 %fn1, ptr @L_freen\n")

View file

@ -12,17 +12,17 @@ fn emit_logic(e: Node) -> Val {
let la = emit_expr(e.a)
let lc = emit_bind(sconcat("icmp ne i32 ", sconcat(la.code, ", 0")))
let lz = emit_bind(sconcat("zext i1 ", sconcat(lc, " to i32")))
emit(" store i32 ") emit(lz) emit(", ptr ") emit(slot) emit("\n")
let ev = lbl("sc") let done = lbl("scend")
if streq(e.s, "and") { emit(" br i1 ") emit(lc) emit(", label %") emit(ev) emit(", label %") emit(done) emit("\n") }
else { emit(" br i1 ") emit(lc) emit(", label %") emit(done) emit(", label %") emit(ev) emit("\n") }
emit(ev) emit(":\n")
emit(" store i32 "); emit(lz); emit(", ptr "); emit(slot); emit("\n")
let ev = lbl("sc"); let done = lbl("scend")
if streq(e.s, "and") { emit(" br i1 "); emit(lc); emit(", label %"); emit(ev); emit(", label %"); emit(done); emit("\n") }
else { emit(" br i1 "); emit(lc); emit(", label %"); emit(done); emit(", label %"); emit(ev); emit("\n") }
emit(ev); emit(":\n")
let rb = emit_expr(e.b)
let rc = emit_bind(sconcat("icmp ne i32 ", sconcat(rb.code, ", 0")))
let rz = emit_bind(sconcat("zext i1 ", sconcat(rc, " to i32")))
emit(" store i32 ") emit(rz) emit(", ptr ") emit(slot) emit("\n")
emit(" br label %") emit(done) emit("\n")
emit(done) emit(":\n")
emit(" store i32 "); emit(rz); emit(", ptr "); emit(slot); emit("\n")
emit(" br label %"); emit(done); emit("\n")
emit(done); emit(":\n")
return val(emit_bind(sconcat("load i32, ptr ", slot)), "bool")
}
@ -57,13 +57,13 @@ fn emit_bin(e: Node) -> Val {
let b = emit_expr(e.b)
let fx = streq(a.ty, "fixed") or streq(b.ty, "fixed")
if is_cmp(e.s) {
let ac = a.code let bc = b.code
if fx { ac = to_fixed(a) bc = to_fixed(b) }
let ac = a.code; let bc = b.code
if fx { ac = to_fixed(a); bc = to_fixed(b) }
let c = emit_bind(sconcat("icmp ", sconcat(cmp_code(e.s), sconcat(" i32 ", sconcat(ac, sconcat(", ", bc))))))
return val(emit_bind(sconcat("zext i1 ", sconcat(c, " to i32"))), "bool")
}
if fx {
let af = to_fixed(a) let bf = to_fixed(b)
let af = to_fixed(a); let bf = to_fixed(b)
if streq(e.s, "*") {
let a64 = emit_bind(sconcat("sext i32 ", sconcat(af, " to i64")))
let b64 = emit_bind(sconcat("sext i32 ", sconcat(bf, " to i64")))
@ -87,16 +87,16 @@ fn emit_bin(e: Node) -> Val {
fn emit_call(e: Node) -> Val {
let name = e.a.s
if streq(name, "self") { if nself == 0 { return val("0", "entity") } return val(emit_bind(sconcat("load i32, ptr ", self_stk[nself - 1])), "entity") }
if streq(name, "self") { if nself == 0 { return val("0", "entity") }; return val(emit_bind(sconcat("load i32, ptr ", self_stk[nself - 1])), "entity") }
if streq(name, "key") { return val(emit_bind("load i32, ptr @L_key"), "int") }
if streq(name, "save") { emit(" call void @L_save()\n") return val("0", "void") }
if streq(name, "ui_build") { emit(" call void @ui_build()\n") return val("0", "void") }
if streq(name, "save") { emit(" call void @L_save()\n"); return val("0", "void") }
if streq(name, "ui_build") { emit(" call void @ui_build()\n"); return val("0", "void") }
if streq(name, "load") { return val(emit_bind("call i32 @L_load()"), "bool") }
if streq(name, "quit") { emit(" store i32 0, ptr @L_running\n") return val("0", "void") }
if streq(name, "quit") { emit(" store i32 0, ptr @L_running\n"); return val("0", "void") }
if streq(name, "len") { return emit_len(e) }
if streq(name, "push") { return emit_push(e) }
if streq(name, "fx") { let a = emit_expr(e.kids[0]) return val(emit_bind(sconcat("shl i32 ", sconcat(a.code, ", 16"))), "fixed") }
if streq(name, "flr") { let a = emit_expr(e.kids[0]) return val(emit_bind(sconcat("ashr i32 ", sconcat(a.code, ", 16"))), "int") }
if streq(name, "fx") { let a = emit_expr(e.kids[0]); return val(emit_bind(sconcat("shl i32 ", sconcat(a.code, ", 16"))), "fixed") }
if streq(name, "flr") { let a = emit_expr(e.kids[0]); return val(emit_bind(sconcat("ashr i32 ", sconcat(a.code, ", 16"))), "int") }
if is_intrinsic(name) { return emit_intrinsic(name, e) }
if is_intrinsic2(name) { return emit_intrinsic2(name, e) }
if is_math_builtin(name) { return emit_math_builtin(name, e) }
@ -113,20 +113,19 @@ fn emit_call(e: Node) -> Val {
let args = new []ptr
let atys = new []ptr
let i = 0
while i < len(e.kids) { let v = emit_expr(e.kids[i]) push(args, v.code) push(atys, v.ty) i = i + 1 }
while i < len(e.kids) { let v = emit_expr(e.kids[i]); push(args, v.code); push(atys, v.ty); i = i + 1 }
let rl = llty(fn2.ty)
emit(" ")
let rreg = "0"
if not streq(rl, "void") { rreg = nreg() emit(rreg) emit(" = ") }
emit("call ") emit(rl) emit(" @fn_") emit(cname) emit("(")
if not streq(rl, "void") { rreg = nreg(); emit(rreg); emit(" = ") }
emit("call "); emit(rl); emit(" @fn_"); emit(cname); emit("(")
i = 0
while i < len(args) {
if i > 0 { emit(", ") }
emit(llty(atys[i])) emit(" ") emit(args[i])
emit(llty(atys[i])); emit(" "); emit(args[i])
i = i + 1
}
emit(")
")
emit(")\n")
return val(rreg, fn2.ty)
}
@ -153,8 +152,8 @@ fn emit_expr(e: Node) -> Val {
if is_ui_ident(e.s) { return val(itoa(ui_index_of(e.s)), "int") }
perr(sconcat("unknown identifier ", e.s))
}
if e.kind == E_MEMBER { let a = emit_member_addr(e) return emit_load_at(a, g_addr_ty) }
if e.kind == E_INDEX { let a = emit_index_addr(e) return emit_load_at(a, g_addr_ty) }
if e.kind == E_MEMBER { let a = emit_member_addr(e); return emit_load_at(a, g_addr_ty) }
if e.kind == E_INDEX { let a = emit_index_addr(e); return emit_load_at(a, g_addr_ty) }
if e.kind == E_CALL { return emit_call(e) }
if e.kind == E_BIN { return emit_bin(e) }
if e.kind == E_UN {

View file

@ -4,7 +4,7 @@
# called only when the runtime defines them.
fn emit_system_fn(sys: Node) -> void {
ll_t = 0 ll_lbl = 0 g_term = false loc_reset() nloop = 0 nself = 0
ll_t = 0; ll_lbl = 0; g_term = false; loc_reset(); nloop = 0; nself = 0
ret_ty = "void"
let fbody = buf_new()
falloc = buf_new()
@ -14,7 +14,7 @@ fn emit_system_fn(sys: Node) -> void {
if not g_term { emit(" br label %ret\n") }
emit("ret:\n ret void\n")
code = saved
emit("define void @sys_") emit(sys.s) emit("() {\nentry:\n")
emit("define void @sys_"); emit(sys.s); emit("() {\nentry:\n")
emit(buf_str(falloc))
emit(buf_str(fbody))
emit("}\n\n")
@ -24,7 +24,7 @@ fn emit_calls_for_phase(phase: ptr) -> void {
let i = 0
while i < len(prog) {
let d = prog[i]
if d.kind == N_SYS and streq(d.ty, phase) { emit(" call void @sys_") emit(d.s) emit("()\n") }
if d.kind == N_SYS and streq(d.ty, phase) { emit(" call void @sys_"); emit(d.s); emit("()\n") }
i = i + 1
}
}
@ -32,7 +32,7 @@ fn emit_calls_for_phase(phase: ptr) -> void {
fn emit_game_main() -> void {
# every system becomes a function first
let i = 0
while i < len(prog) { if prog[i].kind == N_SYS { emit_system_fn(prog[i]) } i = i + 1 }
while i < len(prog) { if prog[i].kind == N_SYS { emit_system_fn(prog[i]) }; i = i + 1 }
emit("define i32 @main(i32 %argc, ptr %argv) {\nentry:\n")
emit(" store i32 %argc, ptr @L_argc\n")
@ -47,14 +47,14 @@ fn emit_game_main() -> void {
let pr = emit_bind("call i32 @fn_rt_running()")
let pc = emit_bind(sconcat("icmp ne i32 ", sconcat(pr, ", 0")))
let go = emit_bind(sconcat("and i1 ", sconcat(rc, sconcat(", ", pc))))
emit(" br i1 ") emit(go) emit(", label %body, label %done\n")
emit(" br i1 "); emit(go); emit(", label %body, label %done\n")
} else {
emit(" br i1 ") emit(rc) emit(", label %body, label %done\n")
emit(" br i1 "); emit(rc); emit(", label %body, label %done\n")
}
emit("body:\n")
if not ptr_is_null(find_fn("rt_poll")) {
let k = emit_bind("call i32 @fn_rt_poll()")
emit(" store i32 ") emit(k) emit(", ptr @L_key\n")
emit(" store i32 "); emit(k); emit(", ptr @L_key\n")
}
emit_calls_for_phase("Input")
emit_calls_for_phase("FixedUpdate")

View file

@ -1,15 +1,15 @@
# emit_head.ludic — string constants and the module header (libc declarations,
# the slice header type, struct layouts, globals, argv, format strings).
fn hexdig(n: int) -> int { if n < 10 { return 48 + n } return 55 + n } # 0-9 A-F
fn hexdig(n: int) -> int { if n < 10 { return 48 + n }; return 55 + n } # 0-9 A-F
# emit `@.strN = ... c"escaped\00"` and return its name; % and non-print -> \XX
fn emit_str_const(s: ptr) -> ptr {
let name = sconcat("@.str", itoa(ll_str))
ll_str = ll_str + 1
let n = slen(s)
emith(name) emith(" = private unnamed_addr constant [")
emith(itoa(n + 1)) emith(" x i8] c\"")
emith(name); emith(" = private unnamed_addr constant [")
emith(itoa(n + 1)); emith(" x i8] c\"")
let i = 0
while i < n {
let c = peek8(s, i)
@ -27,14 +27,14 @@ fn emit_str_const(s: ptr) -> ptr {
# a fresh SSA register bound to a `getelementptr`, returned as its name
fn emit_gep_i8(base: ptr, idx: ptr) -> ptr {
let r = nreg()
emit(" ") emit(r) emit(" = getelementptr inbounds i8, ptr ")
emit(base) emit(", i32 ") emit(idx) emit("\n")
emit(" "); emit(r); emit(" = getelementptr inbounds i8, ptr ")
emit(base); emit(", i32 "); emit(idx); emit("\n")
return r
}
# the constant initializer for a global var: a literal, or 0/null
fn global_init(d: Node) -> ptr {
if ptr_is_null(d.a) { if streq(llty(d.ty), "ptr") { return "null" } return "0" }
if ptr_is_null(d.a) { if streq(llty(d.ty), "ptr") { return "null" }; return "0" }
let e = d.a
if e.kind == E_INT or e.kind == E_FLOAT or e.kind == E_BOOL { return itoa(e.ival) }
if e.kind == E_UN and streq(e.s, "-") and e.a.kind == E_INT { return sconcat("-", itoa(e.a.ival)) }
@ -74,14 +74,14 @@ fn emit_header() -> void {
emith("@L_argc = internal global i32 0\n")
emith("@L_argv = internal global ptr null\n")
emith("@.gametitle = private unnamed_addr constant [")
emith(itoa(slen(g_game_name) + 1)) emith(" x i8] c\"") emith(g_game_name) emith("\\00\"\n")
emith(itoa(slen(g_game_name) + 1)); emith(" x i8] c\""); emith(g_game_name); emith("\\00\"\n")
emith("%LSlice = type { ptr, i32, i32 }\n")
# struct layouts
let i = 0
while i < len(prog) {
let d = prog[i]
if d.kind == N_STRUCT {
emith("%Str_") emith(d.s) emith(" = type { ")
emith("%Str_"); emith(d.s); emith(" = type { ")
if len(d.kids) == 0 { emith("i32") }
let f = 0
while f < len(d.kids) {
@ -98,8 +98,8 @@ fn emit_header() -> void {
while i < len(prog) {
let d = prog[i]
if d.kind == N_VAR {
emith("@g_") emith(d.s) emith(" = internal global ")
emith(llty(d.ty)) emith(" ")
emith("@g_"); emith(d.s); emith(" = internal global ")
emith(llty(d.ty)); emith(" ")
emith(global_init(d))
emith("\n")
}

View file

@ -19,9 +19,9 @@ fn is_intrinsic(name: ptr) -> bool {
return false
}
# emit " <r> = <rest>\n" and return r
fn emit_bind(rest: ptr) -> ptr { let r = nreg() emit(" ") emit(r) emit(" = ") emit(rest) emit("\n") return r }
fn emit_bind(rest: ptr) -> ptr { let r = nreg(); emit(" "); emit(r); emit(" = "); emit(rest); emit("\n"); return r }
fn arg_code(e: Node, i: int) -> ptr { let v = emit_expr(e.kids[i]) return v.code }
fn arg_code(e: Node, i: int) -> ptr { let v = emit_expr(e.kids[i]); return v.code }
fn emit_intrinsic(name: ptr, e: Node) -> Val {
g_intrin_ok = true
@ -32,21 +32,21 @@ fn emit_intrinsic(name: ptr, e: Node) -> Val {
return val(emit_bind(sconcat("call ptr @malloc(i64 ", sconcat(w, ")"))), "ptr")
}
if streq(name, "mem_realloc") {
let p = arg_code(e, 0) let n = arg_code(e, 1)
let p = arg_code(e, 0); let n = arg_code(e, 1)
let w = emit_bind(sconcat("zext i32 ", sconcat(n, " to i64")))
return val(emit_bind(sconcat("call ptr @realloc(ptr ", sconcat(p, sconcat(", i64 ", sconcat(w, ")"))))), "ptr")
}
if streq(name, "peek8") {
let p = arg_code(e, 0) let i = arg_code(e, 1)
let p = arg_code(e, 0); let i = arg_code(e, 1)
let a = emit_gep_i8(p, i)
let b = emit_bind(sconcat("load i8, ptr ", a))
return val(emit_bind(sconcat("zext i8 ", sconcat(b, " to i32"))), "int")
}
if streq(name, "poke8") {
let p = arg_code(e, 0) let i = arg_code(e, 1) let v = arg_code(e, 2)
let p = arg_code(e, 0); let i = arg_code(e, 1); let v = arg_code(e, 2)
let a = emit_gep_i8(p, i)
let t = emit_bind(sconcat("trunc i32 ", sconcat(v, " to i8")))
emit(" store i8 ") emit(t) emit(", ptr ") emit(a) emit("\n")
emit(" store i8 "); emit(t); emit(", ptr "); emit(a); emit("\n")
return val("0", "void")
}
if streq(name, "ptr_is_null") {
@ -55,11 +55,11 @@ fn emit_intrinsic(name: ptr, e: Node) -> Val {
return val(emit_bind(sconcat("zext i1 ", sconcat(c, " to i32"))), "bool")
}
if streq(name, "file_open") {
let p = arg_code(e, 0) let m = arg_code(e, 1)
let p = arg_code(e, 0); let m = arg_code(e, 1)
return val(emit_bind(sconcat("call ptr @fopen(ptr ", sconcat(p, sconcat(", ptr ", sconcat(m, ")"))))), "ptr")
}
if streq(name, "file_read") or streq(name, "file_write") {
let f = arg_code(e, 0) let b = arg_code(e, 1) let n = arg_code(e, 2)
let f = arg_code(e, 0); let b = arg_code(e, 1); let n = arg_code(e, 2)
let w = emit_bind(sconcat("zext i32 ", sconcat(n, " to i64")))
let fn2 = "@fread"
if streq(name, "file_write") { fn2 = "@fwrite" }
@ -67,7 +67,7 @@ fn emit_intrinsic(name: ptr, e: Node) -> Val {
return val(emit_bind(sconcat("trunc i64 ", sconcat(r, " to i32"))), "int")
}
if streq(name, "file_seek") {
let f = arg_code(e, 0) let off = arg_code(e, 1) let wh = arg_code(e, 2)
let f = arg_code(e, 0); let off = arg_code(e, 1); let wh = arg_code(e, 2)
let o = emit_bind(sconcat("sext i32 ", sconcat(off, " to i64")))
return val(emit_bind(sconcat("call i32 @fseek(ptr ", sconcat(f, sconcat(", i64 ", sconcat(o, sconcat(", i32 ", sconcat(wh, ")"))))))), "int")
}
@ -78,17 +78,17 @@ fn emit_intrinsic(name: ptr, e: Node) -> Val {
}
if streq(name, "file_close") {
let f = arg_code(e, 0)
emit(" call i32 @fclose(ptr ") emit(f) emit(")\n")
emit(" call i32 @fclose(ptr "); emit(f); emit(")\n")
return val("0", "void")
}
if streq(name, "print_str") {
let s = arg_code(e, 0)
emit(" call i32 (ptr, ...) @printf(ptr @.fmt_str, ptr ") emit(s) emit(")\n")
emit(" call i32 (ptr, ...) @printf(ptr @.fmt_str, ptr "); emit(s); emit(")\n")
return val("0", "void")
}
if streq(name, "print_int") {
let n = arg_code(e, 0)
emit(" call i32 (ptr, ...) @printf(ptr @.fmt_int, i32 ") emit(n) emit(")\n")
emit(" call i32 (ptr, ...) @printf(ptr @.fmt_int, i32 "); emit(n); emit(")\n")
return val("0", "void")
}
if streq(name, "os_argc") { return val(emit_bind("load i32, ptr @L_argc"), "int") }
@ -100,7 +100,7 @@ fn emit_intrinsic(name: ptr, e: Node) -> Val {
}
if streq(name, "os_exit") {
let n = arg_code(e, 0)
emit(" call void @exit(i32 ") emit(n) emit(")\n")
emit(" call void @exit(i32 "); emit(n); emit(")\n")
emit(" unreachable\n")
g_term = true
return val("0", "void")
@ -114,9 +114,9 @@ fn emit_intrinsic(name: ptr, e: Node) -> Val {
let n = arg_code(e, 0)
return val(emit_bind(sconcat("call ptr @getenv(ptr ", sconcat(n, ")"))), "str")
}
if streq(name, "bnot") { let a = arg_code(e, 0) return val(emit_bind(sconcat("xor i32 ", sconcat(a, ", -1"))), "int") }
if streq(name, "bnot") { let a = arg_code(e, 0); return val(emit_bind(sconcat("xor i32 ", sconcat(a, ", -1"))), "int") }
if streq(name,"shl") or streq(name,"shr") or streq(name,"band") or streq(name,"bor") or streq(name,"bxor") {
let a = arg_code(e, 0) let b = arg_code(e, 1)
let a = arg_code(e, 0); let b = arg_code(e, 1)
let opc = "shl"
if streq(name,"shr") { opc = "lshr" }
if streq(name,"band") { opc = "and" }
@ -125,16 +125,16 @@ fn emit_intrinsic(name: ptr, e: Node) -> Val {
return val(emit_bind(sconcat(opc, sconcat(" i32 ", sconcat(a, sconcat(", ", b))))), "int")
}
if streq(name, "peek32") {
let p = arg_code(e, 0) let i = arg_code(e, 1)
let p = arg_code(e, 0); let i = arg_code(e, 1)
let g = nreg()
emit(" ") emit(g) emit(" = getelementptr inbounds i32, ptr ") emit(p) emit(", i32 ") emit(i) emit("\n")
emit(" "); emit(g); emit(" = getelementptr inbounds i32, ptr "); emit(p); emit(", i32 "); emit(i); emit("\n")
return val(emit_bind(sconcat("load i32, ptr ", g)), "int")
}
if streq(name, "poke32") {
let p = arg_code(e, 0) let i = arg_code(e, 1) let v = arg_code(e, 2)
let p = arg_code(e, 0); let i = arg_code(e, 1); let v = arg_code(e, 2)
let g = nreg()
emit(" ") emit(g) emit(" = getelementptr inbounds i32, ptr ") emit(p) emit(", i32 ") emit(i) emit("\n")
emit(" store i32 ") emit(v) emit(", ptr ") emit(g) emit("\n")
emit(" "); emit(g); emit(" = getelementptr inbounds i32, ptr "); emit(p); emit(", i32 "); emit(i); emit("\n")
emit(" store i32 "); emit(v); emit(", ptr "); emit(g); emit("\n")
return val("0", "void")
}
g_intrin_ok = false

View file

@ -18,29 +18,29 @@ fn is_intrinsic2(name: ptr) -> bool {
fn emit_intrinsic2(name: ptr, e: Node) -> Val {
if streq(name, "mem_free") {
let p = arg_code(e, 0) emit(" call void @free(ptr ") emit(p) emit(")\n") return val("0", "void")
let p = arg_code(e, 0); emit(" call void @free(ptr "); emit(p); emit(")\n"); return val("0", "void")
}
if streq(name, "mem_copy") {
let d = arg_code(e, 0) let s = arg_code(e, 1) let n = arg_code(e, 2)
let d = arg_code(e, 0); let s = arg_code(e, 1); let n = arg_code(e, 2)
let w = emit_bind(sconcat("zext i32 ", sconcat(n, " to i64")))
emit(" call ptr @memcpy(ptr ") emit(d) emit(", ptr ") emit(s) emit(", i64 ") emit(w) emit(")\n")
emit(" call ptr @memcpy(ptr "); emit(d); emit(", ptr "); emit(s); emit(", i64 "); emit(w); emit(")\n")
return val("0", "void")
}
if streq(name, "mem_set") {
let p = arg_code(e, 0) let v = arg_code(e, 1) let n = arg_code(e, 2)
let p = arg_code(e, 0); let v = arg_code(e, 1); let n = arg_code(e, 2)
let w = emit_bind(sconcat("zext i32 ", sconcat(n, " to i64")))
emit(" call ptr @memset(ptr ") emit(p) emit(", i32 ") emit(v) emit(", i64 ") emit(w) emit(")\n")
emit(" call ptr @memset(ptr "); emit(p); emit(", i32 "); emit(v); emit(", i64 "); emit(w); emit(")\n")
return val("0", "void")
}
if streq(name, "ptr_add") {
let p = arg_code(e, 0) let n = arg_code(e, 1)
let p = arg_code(e, 0); let n = arg_code(e, 1)
let g = nreg()
emit(" ") emit(g) emit(" = getelementptr inbounds i8, ptr ") emit(p) emit(", i32 ") emit(n) emit("\n")
emit(" "); emit(g); emit(" = getelementptr inbounds i8, ptr "); emit(p); emit(", i32 "); emit(n); emit("\n")
return val(g, "ptr")
}
if streq(name, "read_byte") { return val(emit_bind("call i32 @getchar()"), "int") }
if streq(name, "write_byte") {
let v = arg_code(e, 0) emit(" call i32 @putchar(i32 ") emit(v) emit(")\n") return val("0", "void")
let v = arg_code(e, 0); emit(" call i32 @putchar(i32 "); emit(v); emit(")\n"); return val("0", "void")
}
if streq(name, "str_len") {
let s = arg_code(e, 0)
@ -52,44 +52,44 @@ fn emit_intrinsic2(name: ptr, e: Node) -> Val {
return val(emit_bind(sconcat("trunc i64 ", sconcat(r, " to i32"))), "int")
}
if streq(name, "peekf") {
let p = arg_code(e, 0) let i = arg_code(e, 1)
let g = nreg() emit(" ") emit(g) emit(" = getelementptr inbounds i32, ptr ") emit(p) emit(", i32 ") emit(i) emit("\n")
let p = arg_code(e, 0); let i = arg_code(e, 1)
let g = nreg(); emit(" "); emit(g); emit(" = getelementptr inbounds i32, ptr "); emit(p); emit(", i32 "); emit(i); emit("\n")
return val(emit_bind(sconcat("load i32, ptr ", g)), "fixed")
}
if streq(name, "pokef") {
let p = arg_code(e, 0) let i = arg_code(e, 1) let v = arg_code(e, 2)
let g = nreg() emit(" ") emit(g) emit(" = getelementptr inbounds i32, ptr ") emit(p) emit(", i32 ") emit(i) emit("\n")
emit(" store i32 ") emit(v) emit(", ptr ") emit(g) emit("\n")
let p = arg_code(e, 0); let i = arg_code(e, 1); let v = arg_code(e, 2)
let g = nreg(); emit(" "); emit(g); emit(" = getelementptr inbounds i32, ptr "); emit(p); emit(", i32 "); emit(i); emit("\n")
emit(" store i32 "); emit(v); emit(", ptr "); emit(g); emit("\n")
return val("0", "void")
}
if streq(name, "peekp") {
let p = arg_code(e, 0) let i = arg_code(e, 1)
let g = nreg() emit(" ") emit(g) emit(" = getelementptr inbounds ptr, ptr ") emit(p) emit(", i32 ") emit(i) emit("\n")
let p = arg_code(e, 0); let i = arg_code(e, 1)
let g = nreg(); emit(" "); emit(g); emit(" = getelementptr inbounds ptr, ptr "); emit(p); emit(", i32 "); emit(i); emit("\n")
return val(emit_bind(sconcat("load ptr, ptr ", g)), "ptr")
}
if streq(name, "pokep") {
let p = arg_code(e, 0) let i = arg_code(e, 1) let v = arg_code(e, 2)
let g = nreg() emit(" ") emit(g) emit(" = getelementptr inbounds ptr, ptr ") emit(p) emit(", i32 ") emit(i) emit("\n")
emit(" store ptr ") emit(v) emit(", ptr ") emit(g) emit("\n")
let p = arg_code(e, 0); let i = arg_code(e, 1); let v = arg_code(e, 2)
let g = nreg(); emit(" "); emit(g); emit(" = getelementptr inbounds ptr, ptr "); emit(p); emit(", i32 "); emit(i); emit("\n")
emit(" store ptr "); emit(v); emit(", ptr "); emit(g); emit("\n")
return val("0", "void")
}
if streq(name, "as_fixed") { let a = emit_expr(e.kids[0]) return val(a.code, "fixed") }
if streq(name, "as_int") { let a = emit_expr(e.kids[0]) return val(a.code, "int") }
if streq(name, "is_windowed") { if g_windowed { return val("1", "bool") } return val("0", "bool") }
if streq(name, "as_fixed") { let a = emit_expr(e.kids[0]); return val(a.code, "fixed") }
if streq(name, "as_int") { let a = emit_expr(e.kids[0]); return val(a.code, "int") }
if streq(name, "is_windowed") { if g_windowed { return val("1", "bool") }; return val("0", "bool") }
if streq(name, "game_title") { return val("@.gametitle", "str") }
# windowing hooks — declared external; only reached on the is_windowed() branch
if streq(name, "win_poll") { return val(emit_bind("call i32 @win_poll()"), "int") }
if streq(name, "win_running") { return val(emit_bind("call i32 @win_running()"), "bool") }
if streq(name, "win_open") {
let a = arg_code(e, 0) let b = arg_code(e, 1) let c = arg_code(e, 2) let d = arg_code(e, 3)
emit(" call void @win_open(i32 ") emit(a) emit(", i32 ") emit(b) emit(", i32 ") emit(c) emit(", ptr ") emit(d) emit(")\n")
let a = arg_code(e, 0); let b = arg_code(e, 1); let c = arg_code(e, 2); let d = arg_code(e, 3)
emit(" call void @win_open(i32 "); emit(a); emit(", i32 "); emit(b); emit(", i32 "); emit(c); emit(", ptr "); emit(d); emit(")\n")
return val("0", "void")
}
if streq(name, "win_present") {
let a = arg_code(e, 0) let b = arg_code(e, 1) let c = arg_code(e, 2)
emit(" call void @win_present(ptr ") emit(a) emit(", i32 ") emit(b) emit(", i32 ") emit(c) emit(")\n")
let a = arg_code(e, 0); let b = arg_code(e, 1); let c = arg_code(e, 2)
emit(" call void @win_present(ptr "); emit(a); emit(", i32 "); emit(b); emit(", i32 "); emit(c); emit(")\n")
return val("0", "void")
}
if streq(name, "win_close") { emit(" call void @win_close()\n") return val("0", "void") }
if streq(name, "win_close") { emit(" call void @win_close()\n"); return val("0", "void") }
return val("0", "void")
}

View file

@ -13,16 +13,16 @@ fn emit_machine(st: Node) -> void {
let state = st.kids[i]
let v = emit_expr(state.b)
let c = emit_bind(sconcat("icmp eq i32 ", sconcat(s, sconcat(", ", v.code))))
let body = lbl("sbody") let nxt = lbl("sarm")
emit(" br i1 ") emit(c) emit(", label %") emit(body) emit(", label %") emit(nxt) emit("\n")
emit(body) emit(":\n") g_term = false
let body = lbl("sbody"); let nxt = lbl("sarm")
emit(" br i1 "); emit(c); emit(", label %"); emit(body); emit(", label %"); emit(nxt); emit("\n")
emit(body); emit(":\n"); g_term = false
emit_block(state.a)
if not g_term { emit(" br label %") emit(endl) emit("\n") }
emit(nxt) emit(":\n") g_term = false
if not g_term { emit(" br label %"); emit(endl); emit("\n") }
emit(nxt); emit(":\n"); g_term = false
i = i + 1
}
if not g_term { emit(" br label %") emit(endl) emit("\n") }
emit(endl) emit(":\n") g_term = false
if not g_term { emit(" br label %"); emit(endl); emit("\n") }
emit(endl); emit(":\n"); g_term = false
nmach = nmach - 1
}
@ -31,9 +31,9 @@ fn emit_become(st: Node) -> void {
let m = mach_stk[nmach - 1]
let target: Node = ptr_null()
let i = 0
while i < len(m.kids) { if streq(m.kids[i].s, st.s) { target = m.kids[i] } i = i + 1 }
while i < len(m.kids) { if streq(m.kids[i].s, st.s) { target = m.kids[i] }; i = i + 1 }
if ptr_is_null(target) { perr(sconcat("become: no state ", st.s)) }
let regv = emit_expr(m.a)
let sv = emit_expr(target.b)
emit(" call void @fn_rt_setreg(i32 ") emit(regv.code) emit(", i32 ") emit(sv.code) emit(")\n")
emit(" call void @fn_rt_setreg(i32 "); emit(regv.code); emit(", i32 "); emit(sv.code); emit(")\n")
}

View file

@ -14,14 +14,14 @@ fn emit_math_builtin(name: ptr, e: Node) -> Val {
return val(emit_bind(sconcat("select i1 ", sconcat(c, sconcat(", i32 ", sconcat(n, sconcat(", i32 ", a.code)))))), "int")
}
if streq(name, "min") or streq(name, "max") {
let a = emit_expr(e.kids[0]) let b = emit_expr(e.kids[1])
let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1])
let op = "slt"
if streq(name, "max") { op = "sgt" }
let c = emit_bind(sconcat("icmp ", sconcat(op, sconcat(" i32 ", sconcat(a.code, sconcat(", ", b.code))))))
return val(emit_bind(sconcat("select i1 ", sconcat(c, sconcat(", i32 ", sconcat(a.code, sconcat(", i32 ", b.code)))))), "int")
}
# clamp(v, lo, hi) = max(lo, min(v, hi))
let v = emit_expr(e.kids[0]) let lo = emit_expr(e.kids[1]) let hi = emit_expr(e.kids[2])
let v = emit_expr(e.kids[0]); let lo = emit_expr(e.kids[1]); let hi = emit_expr(e.kids[2])
let c1 = emit_bind(sconcat("icmp slt i32 ", sconcat(v.code, sconcat(", ", hi.code))))
let t = emit_bind(sconcat("select i1 ", sconcat(c1, sconcat(", i32 ", sconcat(v.code, sconcat(", i32 ", hi.code))))))
let c2 = emit_bind(sconcat("icmp sgt i32 ", sconcat(lo.code, sconcat(", ", t))))

View file

@ -16,13 +16,13 @@ fn emit_new_struct(name: ptr) -> Val {
while f < len(s.kids) {
let fd = s.kids[f]
let addr = nreg()
emit(" ") emit(addr) emit(" = getelementptr inbounds %Str_") emit(name)
emit(", ptr ") emit(obj) emit(", i32 0, i32 ") emit(itoa(f)) emit("\n")
emit(" "); emit(addr); emit(" = getelementptr inbounds %Str_"); emit(name)
emit(", ptr "); emit(obj); emit(", i32 0, i32 "); emit(itoa(f)); emit("\n")
let lt = llty(fd.ty)
let v = "0"
if streq(lt, "ptr") { v = "null" }
if not ptr_is_null(fd.a) { let dv = emit_expr(fd.a) v = dv.code }
emit(" store ") emit(lt) emit(" ") emit(v) emit(", ptr ") emit(addr) emit("\n")
if not ptr_is_null(fd.a) { let dv = emit_expr(fd.a); v = dv.code }
emit(" store "); emit(lt); emit(" "); emit(v); emit(", ptr "); emit(addr); emit("\n")
f = f + 1
}
return val(obj, name)
@ -31,19 +31,19 @@ fn emit_new_struct(name: ptr) -> Val {
fn emit_new_slice(ty: ptr) -> Val {
let sz = emit_sizeof("%LSlice")
let h = emit_bind(sconcat("call ptr @malloc(i64 ", sconcat(sz, ")")))
let d0 = nreg() emit(" ") emit(d0) emit(" = getelementptr inbounds %LSlice, ptr ") emit(h) emit(", i32 0, i32 0\n")
emit(" store ptr null, ptr ") emit(d0) emit("\n")
let d1 = nreg() emit(" ") emit(d1) emit(" = getelementptr inbounds %LSlice, ptr ") emit(h) emit(", i32 0, i32 1\n")
emit(" store i32 0, ptr ") emit(d1) emit("\n")
let d2 = nreg() emit(" ") emit(d2) emit(" = getelementptr inbounds %LSlice, ptr ") emit(h) emit(", i32 0, i32 2\n")
emit(" store i32 0, ptr ") emit(d2) emit("\n")
let d0 = nreg(); emit(" "); emit(d0); emit(" = getelementptr inbounds %LSlice, ptr "); emit(h); emit(", i32 0, i32 0\n")
emit(" store ptr null, ptr "); emit(d0); emit("\n")
let d1 = nreg(); emit(" "); emit(d1); emit(" = getelementptr inbounds %LSlice, ptr "); emit(h); emit(", i32 0, i32 1\n")
emit(" store i32 0, ptr "); emit(d1); emit("\n")
let d2 = nreg(); emit(" "); emit(d2); emit(" = getelementptr inbounds %LSlice, ptr "); emit(h); emit(", i32 0, i32 2\n")
emit(" store i32 0, ptr "); emit(d2); emit("\n")
return val(h, ty)
}
fn slice_field(h: ptr, i: int) -> ptr {
let r = nreg()
emit(" ") emit(r) emit(" = getelementptr inbounds %LSlice, ptr ") emit(h)
emit(", i32 0, i32 ") emit(itoa(i)) emit("\n")
emit(" "); emit(r); emit(" = getelementptr inbounds %LSlice, ptr "); emit(h)
emit(", i32 0, i32 "); emit(itoa(i)); emit("\n")
return r
}
@ -58,13 +58,13 @@ fn emit_push(e: Node) -> Val {
let el = slice_elem(s.ty)
let elt = llty(el)
let h = s.code
let lp = slice_field(h, 1) let cp = slice_field(h, 2) let dp = slice_field(h, 0)
let lp = slice_field(h, 1); let cp = slice_field(h, 2); let dp = slice_field(h, 0)
let l = emit_bind(sconcat("load i32, ptr ", lp))
let c = emit_bind(sconcat("load i32, ptr ", cp))
let full = emit_bind(sconcat("icmp sge i32 ", sconcat(l, sconcat(", ", c))))
let grow = lbl("grow") let put = lbl("put")
emit(" br i1 ") emit(full) emit(", label %") emit(grow) emit(", label %") emit(put) emit("\n")
emit(grow) emit(":\n")
let grow = lbl("grow"); let put = lbl("put")
emit(" br i1 "); emit(full); emit(", label %"); emit(grow); emit(", label %"); emit(put); emit("\n")
emit(grow); emit(":\n")
let dbl = emit_bind(sconcat("mul i32 ", sconcat(c, ", 2")))
let isz = emit_bind(sconcat("icmp eq i32 ", sconcat(c, ", 0")))
let nc = emit_bind(sconcat("select i1 ", sconcat(isz, sconcat(", i32 8, i32 ", dbl))))
@ -73,16 +73,16 @@ fn emit_push(e: Node) -> Val {
let bytes = emit_bind(sconcat("mul i64 ", sconcat(ncw, sconcat(", ", esz))))
let old = emit_bind(sconcat("load ptr, ptr ", dp))
let nd = emit_bind(sconcat("call ptr @realloc(ptr ", sconcat(old, sconcat(", i64 ", sconcat(bytes, ")")))))
emit(" store ptr ") emit(nd) emit(", ptr ") emit(dp) emit("\n")
emit(" store i32 ") emit(nc) emit(", ptr ") emit(cp) emit("\n")
emit(" br label %") emit(put) emit("\n")
emit(put) emit(":\n")
emit(" store ptr "); emit(nd); emit(", ptr "); emit(dp); emit("\n")
emit(" store i32 "); emit(nc); emit(", ptr "); emit(cp); emit("\n")
emit(" br label %"); emit(put); emit("\n")
emit(put); emit(":\n")
let v = emit_expr(e.kids[1])
let data = emit_bind(sconcat("load ptr, ptr ", dp))
let slot = nreg()
emit(" ") emit(slot) emit(" = getelementptr inbounds ") emit(elt) emit(", ptr ") emit(data) emit(", i32 ") emit(l) emit("\n")
emit(" store ") emit(elt) emit(" ") emit(v.code) emit(", ptr ") emit(slot) emit("\n")
emit(" "); emit(slot); emit(" = getelementptr inbounds "); emit(elt); emit(", ptr "); emit(data); emit(", i32 "); emit(l); emit("\n")
emit(" store "); emit(elt); emit(" "); emit(v.code); emit(", ptr "); emit(slot); emit("\n")
let l1 = emit_bind(sconcat("add i32 ", sconcat(l, ", 1")))
emit(" store i32 ") emit(l1) emit(", ptr ") emit(lp) emit("\n")
emit(" store i32 "); emit(l1); emit(", ptr "); emit(lp); emit("\n")
return val("0", "void")
}

View file

@ -3,8 +3,8 @@
# the body once per match. Mirrors ll_query in compiler/back/ir_ecs.c.
fn find_arch_id(name: ptr) -> int {
let i = 0 let n = 1
while i < len(prog) { if prog[i].kind == N_ARCH { if streq(prog[i].s, name) { return n } n = n + 1 } i = i + 1 }
let i = 0; let n = 1
while i < len(prog) { if prog[i].kind == N_ARCH { if streq(prog[i].s, name) { return n }; n = n + 1 }; i = i + 1 }
return 0
}
@ -15,22 +15,22 @@ fn emit_query(st: Node) -> void {
if nself < len(self_stk) { self_stk[nself] = ip } else { push(self_stk, ip) }
nself = nself + 1
let cond = lbl("qcond") let body = lbl("qbody") let nxt = lbl("qnext") let endl = lbl("qend")
emit(" br label %") emit(cond) emit("\n")
emit(cond) emit(":\n")
let cond = lbl("qcond"); let body = lbl("qbody"); let nxt = lbl("qnext"); let endl = lbl("qend")
emit(" br label %"); emit(cond); emit("\n")
emit(cond); emit(":\n")
let i0 = emit_bind(sconcat("load i32, ptr ", ip))
let ec = emit_bind("load i32, ptr @L_entc")
let lt = emit_bind(sconcat("icmp slt i32 ", sconcat(i0, sconcat(", ", ec))))
emit(" br i1 ") emit(lt) emit(", label %") emit(body) emit(", label %") emit(endl) emit("\n")
emit(body) emit(":\n") g_term = false
emit(" br i1 "); emit(lt); emit(", label %"); emit(body); emit(", label %"); emit(endl); emit("\n")
emit(body); emit(":\n"); g_term = false
let i1 = emit_bind(sconcat("load i32, ptr ", ip))
# alive?
let ap = nreg() emit(" ") emit(ap) emit(" = getelementptr inbounds [") emit(me) emit(" x i32], ptr @L_alive, i32 0, i32 ") emit(i1) emit("\n")
let ap = nreg(); emit(" "); emit(ap); emit(" = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_alive, i32 0, i32 "); emit(i1); emit("\n")
let al = emit_bind(sconcat("load i32, ptr ", ap))
let alc = emit_bind(sconcat("icmp ne i32 ", sconcat(al, ", 0")))
let ka = lbl("qa")
emit(" br i1 ") emit(alc) emit(", label %") emit(ka) emit(", label %") emit(nxt) emit("\n")
emit(ka) emit(":\n")
emit(" br i1 "); emit(alc); emit(", label %"); emit(ka); emit(", label %"); emit(nxt); emit("\n")
emit(ka); emit(":\n")
let terms = st.c
# component / archetype filters
@ -40,17 +40,17 @@ fn emit_query(st: Node) -> void {
let ak = find_arch_id(tm.s)
let ok = "0"
if ak > 0 {
let kp = nreg() emit(" ") emit(kp) emit(" = getelementptr inbounds [") emit(me) emit(" x i32], ptr @L_kind, i32 0, i32 ") emit(i1) emit("\n")
let kp = nreg(); emit(" "); emit(kp); emit(" = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_kind, i32 0, i32 "); emit(i1); emit("\n")
let kv = emit_bind(sconcat("load i32, ptr ", kp))
ok = emit_bind(sconcat("icmp eq i32 ", sconcat(kv, sconcat(", ", itoa(ak)))))
} else {
let hp = nreg() emit(" ") emit(hp) emit(" = getelementptr inbounds [") emit(me) emit(" x i8], ptr @H_") emit(tm.s) emit(", i32 0, i32 ") emit(i1) emit("\n")
let hp = nreg(); emit(" "); emit(hp); emit(" = getelementptr inbounds ["); emit(me); emit(" x i8], ptr @H_"); emit(tm.s); emit(", i32 0, i32 "); emit(i1); emit("\n")
let hv = emit_bind(sconcat("load i8, ptr ", hp))
ok = emit_bind(sconcat("icmp ne i8 ", sconcat(hv, ", 0")))
}
let keep = lbl("qt")
emit(" br i1 ") emit(ok) emit(", label %") emit(keep) emit(", label %") emit(nxt) emit("\n")
emit(keep) emit(":\n")
emit(" br i1 "); emit(ok); emit(", label %"); emit(keep); emit(", label %"); emit(nxt); emit("\n")
emit(keep); emit(":\n")
t = t + 1
}
@ -63,9 +63,9 @@ fn emit_query(st: Node) -> void {
if tm.ival == 0 and find_arch_id(tm.s) == 0 {
if vi < len(st.kids) {
let slot = nreg()
emit(" ") emit(slot) emit(" = getelementptr inbounds [") emit(me) emit(" x %Cmp_") emit(tm.s) emit("], ptr @S_") emit(tm.s) emit(", i32 0, i32 ") emit(i1) emit("\n")
emit(" "); emit(slot); emit(" = getelementptr inbounds ["); emit(me); emit(" x %Cmp_"); emit(tm.s); emit("], ptr @S_"); emit(tm.s); emit(", i32 0, i32 "); emit(i1); emit("\n")
let vslot = emit_alloca("ptr")
emit(" store ptr ") emit(slot) emit(", ptr ") emit(vslot) emit("\n")
emit(" store ptr "); emit(slot); emit(", ptr "); emit(vslot); emit("\n")
loc_push(st.kids[vi].s, vslot, tm.s)
vi = vi + 1
}
@ -78,21 +78,21 @@ fn emit_query(st: Node) -> void {
let w = emit_expr(st.b)
let wc = emit_bind(sconcat("icmp ne i32 ", sconcat(w.code, ", 0")))
let kw = lbl("qw")
emit(" br i1 ") emit(wc) emit(", label %") emit(kw) emit(", label %") emit(nxt) emit("\n")
emit(kw) emit(":\n")
emit(" br i1 "); emit(wc); emit(", label %"); emit(kw); emit(", label %"); emit(nxt); emit("\n")
emit(kw); emit(":\n")
}
loop_push(nxt, endl)
emit_block(st.a)
loop_pop()
nloc = save
if not g_term { emit(" br label %") emit(nxt) emit("\n") }
if not g_term { emit(" br label %"); emit(nxt); emit("\n") }
emit(nxt) emit(":\n")
emit(nxt); emit(":\n")
let i2 = emit_bind(sconcat("load i32, ptr ", ip))
let i3 = emit_bind(sconcat("add i32 ", sconcat(i2, ", 1")))
store_at("i32", i3, ip)
emit(" br label %") emit(cond) emit("\n")
emit(endl) emit(":\n") g_term = false
emit(" br label %"); emit(cond); emit("\n")
emit(endl); emit(":\n"); g_term = false
nself = nself - 1
}

View file

@ -6,8 +6,8 @@
var g_iok: int = 0
fn emit_io(fn2: ptr, p: ptr, bytes: ptr) -> void {
let r = sconcat("%io", itoa(g_iok)) g_iok = g_iok + 1
emit(" ") emit(r) emit(" = call i64 @") emit(fn2) emit("(ptr ") emit(p) emit(", i64 1, i64 ") emit(bytes) emit(", ptr %f)\n")
let r = sconcat("%io", itoa(g_iok)); g_iok = g_iok + 1
emit(" "); emit(r); emit(" = call i64 @"); emit(fn2); emit("(ptr "); emit(p); emit(", i64 1, i64 "); emit(bytes); emit(", ptr %f)\n")
}
fn emit_snapshot_blocks(fn2: ptr) -> void {
@ -15,19 +15,19 @@ fn emit_snapshot_blocks(fn2: ptr) -> void {
let me = itoa(MAX_ENT)
emit_io(fn2, "@L_entc", "4")
emit_io(fn2, "@L_freen", "4")
emit(" %nalive = mul i64 ") emit(me) emit(", 4\n")
emit(" %nalive = mul i64 "); emit(me); emit(", 4\n")
emit_io(fn2, "@L_alive", "%nalive")
emit_io(fn2, "@L_freelist", "%nalive")
emit_io(fn2, "@L_kind", "%nalive")
let i = 0
while i < len(prog) { if prog[i].kind == N_VAR { emit_io(fn2, sconcat("@g_", prog[i].s), "4") } i = i + 1 }
while i < len(prog) { if prog[i].kind == N_VAR { emit_io(fn2, sconcat("@g_", prog[i].s), "4") }; i = i + 1 }
let ci = 0
i = 0
while i < len(prog) {
if prog[i].kind == N_COMP {
let c = prog[i].s
let csz = sconcat("%csz", itoa(ci))
emit(" ") emit(csz) emit(" = ptrtoint ptr getelementptr (%Cmp_") emit(c) emit(", ptr null, i32 ") emit(me) emit(") to i64\n")
emit(" "); emit(csz); emit(" = ptrtoint ptr getelementptr (%Cmp_"); emit(c); emit(", ptr null, i32 "); emit(me); emit(") to i64\n")
emit_io(fn2, sconcat("@S_", c), csz)
emit_io(fn2, sconcat("@H_", c), me)
ci = ci + 1

View file

@ -5,21 +5,21 @@ fn emit_init_component(e: ptr, comp: ptr, rec: Node) -> void {
let me = itoa(MAX_ENT)
let c = find_comp(comp)
if ptr_is_null(c) { perr(sconcat("spawn: unknown component ", comp)) }
let hp = nreg() emit(" ") emit(hp) emit(" = getelementptr inbounds [") emit(me) emit(" x i8], ptr @H_") emit(comp) emit(", i32 0, i32 ") emit(e) emit("\n")
emit(" store i8 1, ptr ") emit(hp) emit("\n")
let hp = nreg(); emit(" "); emit(hp); emit(" = getelementptr inbounds ["); emit(me); emit(" x i8], ptr @H_"); emit(comp); emit(", i32 0, i32 "); emit(e); emit("\n")
emit(" store i8 1, ptr "); emit(hp); emit("\n")
let slot = nreg()
emit(" ") emit(slot) emit(" = getelementptr inbounds [") emit(me) emit(" x %Cmp_") emit(comp) emit("], ptr @S_") emit(comp) emit(", i32 0, i32 ") emit(e) emit("\n")
emit(" "); emit(slot); emit(" = getelementptr inbounds ["); emit(me); emit(" x %Cmp_"); emit(comp); emit("], ptr @S_"); emit(comp); emit(", i32 0, i32 "); emit(e); emit("\n")
# defaults
let f = 0
while f < len(c.kids) {
let fd = c.kids[f]
let addr = nreg()
emit(" ") emit(addr) emit(" = getelementptr inbounds %Cmp_") emit(comp) emit(", ptr ") emit(slot) emit(", i32 0, i32 ") emit(itoa(f)) emit("\n")
emit(" "); emit(addr); emit(" = getelementptr inbounds %Cmp_"); emit(comp); emit(", ptr "); emit(slot); emit(", i32 0, i32 "); emit(itoa(f)); emit("\n")
let lt = llty(fd.ty)
let v = "0"
if streq(lt, "ptr") { v = "null" }
if not ptr_is_null(fd.a) { let dv = emit_expr(fd.a) v = dv.code }
emit(" store ") emit(lt) emit(" ") emit(v) emit(", ptr ") emit(addr) emit("\n")
if not ptr_is_null(fd.a) { let dv = emit_expr(fd.a); v = dv.code }
emit(" store "); emit(lt); emit(" "); emit(v); emit(", ptr "); emit(addr); emit("\n")
f = f + 1
}
# per-spawn overrides
@ -30,9 +30,9 @@ fn emit_init_component(e: ptr, comp: ptr, rec: Node) -> void {
let fidx = field_index(c, fi.s)
if fidx >= 0 {
let addr = nreg()
emit(" ") emit(addr) emit(" = getelementptr inbounds %Cmp_") emit(comp) emit(", ptr ") emit(slot) emit(", i32 0, i32 ") emit(itoa(fidx)) emit("\n")
emit(" "); emit(addr); emit(" = getelementptr inbounds %Cmp_"); emit(comp); emit(", ptr "); emit(slot); emit(", i32 0, i32 "); emit(itoa(fidx)); emit("\n")
let dv = emit_expr(fi.a)
emit(" store ") emit(llty(field_type(c, fi.s))) emit(" ") emit(dv.code) emit(", ptr ") emit(addr) emit("\n")
emit(" store "); emit(llty(field_type(c, fi.s))); emit(" "); emit(dv.code); emit(", ptr "); emit(addr); emit("\n")
}
j = j + 1
}
@ -44,25 +44,25 @@ fn emit_spawn(st: Node) -> void {
let ak = find_arch_id(st.s)
if ak > 0 {
let me = itoa(MAX_ENT)
let kp = nreg() emit(" ") emit(kp) emit(" = getelementptr inbounds [") emit(me) emit(" x i32], ptr @L_kind, i32 0, i32 ") emit(e) emit("\n")
emit(" store i32 ") emit(itoa(ak)) emit(", ptr ") emit(kp) emit("\n")
let kp = nreg(); emit(" "); emit(kp); emit(" = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_kind, i32 0, i32 "); emit(e); emit("\n")
emit(" store i32 "); emit(itoa(ak)); emit(", ptr "); emit(kp); emit("\n")
let arch = find_arch(st.s)
let c = 0
while c < len(arch.kids) {
let cn = arch.kids[c].s
let rec = ptr_null()
let i = 0
while i < len(st.kids) { if streq(st.kids[i].s, cn) { rec = st.kids[i].a } i = i + 1 }
while i < len(st.kids) { if streq(st.kids[i].s, cn) { rec = st.kids[i].a }; i = i + 1 }
emit_init_component(e, cn, rec)
c = c + 1
}
} else {
let i = 0
while i < len(st.kids) { emit_init_component(e, st.kids[i].s, st.kids[i].a) i = i + 1 }
while i < len(st.kids) { emit_init_component(e, st.kids[i].s, st.kids[i].a); i = i + 1 }
}
}
fn emit_despawn(st: Node) -> void {
let v = emit_expr(st.a)
emit(" call void @L_free_entity(i32 ") emit(v.code) emit(")\n")
emit(" call void @L_free_entity(i32 "); emit(v.code); emit(")\n")
}

View file

@ -12,7 +12,7 @@ fn emit_block(b: Node) -> void {
# 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")
emit(" store "); emit(lt); emit(" "); emit(v); emit(", ptr "); emit(addr); emit("\n")
}
fn emit_assign(st: Node) -> void {
@ -22,15 +22,15 @@ fn emit_assign(st: Node) -> void {
let ty = "int"
if t.kind == E_ID {
let li = loc_find(t.s)
if li >= 0 { addr = loc_reg[li] ty = loc_ty[li] }
if li >= 0 { 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
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 }
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)
@ -51,34 +51,34 @@ 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
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 not g_term { emit(" br label %"); emit(en); emit("\n") }
if has_else {
emit(el) emit(":\n") g_term = false
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") }
if not g_term { emit(" br label %"); emit(en); emit("\n") }
}
emit(en) emit(":\n") g_term = false
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 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
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
if not g_term { emit(" br label %"); emit(cl); emit("\n") }
emit(en); emit(":\n"); g_term = false
}
fn emit_for(st: Node) -> void {
@ -86,24 +86,24 @@ fn emit_for(st: Node) -> void {
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 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
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")
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
emit(" br label %"); emit(cl); emit("\n")
emit(en); emit(":\n"); g_term = false
}
fn emit_return(st: Node) -> void {
@ -117,7 +117,7 @@ fn emit_return(st: Node) -> void {
fn arm_is_default(arm: Node) -> bool {
let 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 }
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
}
@ -136,58 +136,58 @@ fn emit_match(st: Node) -> void {
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 }
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
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
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
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 {
let 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) return }
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); return }
let slot = emit_alloca(llty(ty))
if ptr_is_null(st.a) {
let 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) }
} else { let v = emit_expr(st.a); store_at(llty(ty), v.code, slot) }
loc_push(st.s, slot, ty)
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_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 }
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_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) }
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 }

View file

@ -9,43 +9,43 @@ var ui_roots: []int # first-widget index of each ui block
fn ui_wtype(w: Node) -> int {
let s = w.s
if streq(s, "panel") { return 0 } if streq(s, "col") { return 1 } if streq(s, "row") { return 2 }
if streq(s, "label") { return 3 } if streq(s, "button") { return 4 }
if streq(s, "image") { return 5 } if streq(s, "spacer") { return 6 }
if streq(s, "panel") { return 0 }; if streq(s, "col") { return 1 }; if streq(s, "row") { return 2 }
if streq(s, "label") { return 3 }; if streq(s, "button") { return 4 }
if streq(s, "image") { return 5 }; if streq(s, "spacer") { return 6 }
return 0
}
fn ui_prop(w: Node, key: ptr) -> Node {
let i = 0
while i < len(w.b.kids) { if streq(w.b.kids[i].s, key) { return w.b.kids[i].a } i = i + 1 }
while i < len(w.b.kids) { if streq(w.b.kids[i].s, key) { return w.b.kids[i].a }; i = i + 1 }
return ptr_null()
}
fn ui_flatten(w: Node, parent: int) -> void {
let idx = len(uiw)
push(uiw, w) push(uiw_parent, parent)
push(uiw, w); push(uiw_parent, parent)
let i = 0
while i < len(w.kids) { ui_flatten(w.kids[i], idx) i = i + 1 }
while i < len(w.kids) { ui_flatten(w.kids[i], idx); i = i + 1 }
}
fn ui_flatten_all() -> void {
uiw = new []Node uiw_parent = new []int ui_roots = new []int
uiw = new []Node; uiw_parent = new []int; ui_roots = new []int
let i = 0
while i < len(prog) {
if prog[i].kind == N_UI and prog[i].ival == 1 { push(ui_roots, len(uiw)) ui_flatten(prog[i].a, 0 - 1) }
if prog[i].kind == N_UI and prog[i].ival == 1 { push(ui_roots, len(uiw)); ui_flatten(prog[i].a, 0 - 1) }
i = i + 1
}
}
fn has_ui() -> bool {
let i = 0
while i < len(prog) { if prog[i].kind == N_UI and prog[i].ival == 1 { return true } i = i + 1 }
while i < len(prog) { if prog[i].kind == N_UI and prog[i].ival == 1 { return true }; i = i + 1 }
return false
}
# index of a UI_<name>: a ui block's root, or a widget's id=
fn ui_index_of(nm: ptr) -> int {
let s = substr(nm, 3, slen(nm) - 3) # strip "UI_"
let u = 0 let bi = 0
let u = 0; let bi = 0
let i = 0
while i < len(prog) {
if prog[i].kind == N_UI and prog[i].ival == 1 { if streq(prog[i].s, s) { return ui_roots[bi] } bi = bi + 1 }
if prog[i].kind == N_UI and prog[i].ival == 1 { if streq(prog[i].s, s) { return ui_roots[bi] }; bi = bi + 1 }
i = i + 1
}
i = 0
@ -61,24 +61,24 @@ fn is_ui_ident(nm: ptr) -> bool {
}
fn ll_ui_set(idx: int, key: int, val: ptr) -> void {
emit(" call void @fn_rt_ui_set(i32 ") emit(itoa(idx)) emit(", i32 ") emit(itoa(key)) emit(", i32 ") emit(val) emit(")\n")
emit(" call void @fn_rt_ui_set(i32 "); emit(itoa(idx)); emit(", i32 "); emit(itoa(key)); emit(", i32 "); emit(val); emit(")\n")
}
fn ui_prop_key(k: ptr) -> int {
if streq(k,"w") { return 2 } if streq(k,"h") { return 3 } if streq(k,"x") { return 4 } if streq(k,"y") { return 5 }
if streq(k,"pad") { return 7 } if streq(k,"gap") { return 8 } if streq(k,"bg") { return 9 } if streq(k,"fg") { return 10 }
if streq(k,"border") { return 11 } if streq(k,"grow") { return 13 } if streq(k,"font") { return 14 } if streq(k,"size") { return 15 }
if streq(k,"inset") { return 16 } if streq(k,"focus") { return 17 }
if streq(k,"w") { return 2 }; if streq(k,"h") { return 3 }; if streq(k,"x") { return 4 }; if streq(k,"y") { return 5 }
if streq(k,"pad") { return 7 }; if streq(k,"gap") { return 8 }; if streq(k,"bg") { return 9 }; if streq(k,"fg") { return 10 }
if streq(k,"border") { return 11 }; if streq(k,"grow") { return 13 }; if streq(k,"font") { return 14 }; if streq(k,"size") { return 15 }
if streq(k,"inset") { return 16 }; if streq(k,"focus") { return 17 }
return 0 - 1
}
fn emit_ui_build() -> void {
ui_flatten_all()
ll_t = 0 ll_lbl = 0 g_term = false loc_reset() nloop = 0
ll_t = 0; ll_lbl = 0; g_term = false; loc_reset(); nloop = 0
let fbody = buf_new()
falloc = buf_new()
let saved = code
code = fbody
emit(" call void @fn_rt_ui_reset(i32 ") emit(itoa(len(uiw))) emit(")\n")
emit(" call void @fn_rt_ui_reset(i32 "); emit(itoa(len(uiw))); emit(")\n")
let i = 0
while i < len(uiw) {
let w = uiw[i]
@ -91,10 +91,10 @@ fn emit_ui_build() -> void {
let pr = w.b.kids[p]
let k = pr.s
let v = pr.a
if streq(k, "id") { p = p + 1 continue }
if streq(k, "id") { p = p + 1; continue }
if streq(k, "text") {
let sv = emit_expr(v)
emit(" call void @fn_rt_ui_static_text(i32 ") emit(itoa(i)) emit(", ptr ") emit(sv.code) emit(")\n")
emit(" call void @fn_rt_ui_static_text(i32 "); emit(itoa(i)); emit(", ptr "); emit(sv.code); emit(")\n")
} else { if streq(k, "skin") or streq(k, "image") {
let sv = emit_expr(v)
let r = emit_bind(sconcat("call i32 @fn_rt_image_load(ptr ", sconcat(sv.code, ")")))
@ -102,9 +102,9 @@ fn emit_ui_build() -> void {
} else { if streq(k, "align") {
if v.kind == E_ID {
let a = 0
if streq(v.s, "center") { a = 1 } if streq(v.s, "end") { a = 2 }
if streq(v.s, "center") { a = 1 }; if streq(v.s, "end") { a = 2 }
ll_ui_set(i, 12, itoa(a))
} else { let vv = emit_expr(v) ll_ui_set(i, 12, vv.code) }
} else { let vv = emit_expr(v); ll_ui_set(i, 12, vv.code) }
} else {
let key = ui_prop_key(k)
if key >= 0 {

View file

@ -16,7 +16,7 @@ var toks: []Tok
fn tok_push(kind: int, text: ptr, ival: int, line: int) -> void {
let t = new Tok
t.kind = kind t.text = text t.ival = ival t.line = line
t.kind = kind; t.text = text; t.ival = ival; t.line = line
push(toks, t)
}
@ -42,8 +42,8 @@ fn lex(src: ptr) -> void {
let n = slen(src)
while i < n {
let c = peek8(src, i)
if c == 10 { tok_push(TK_NL, ptr_null(), 0, line) line = line + 1 i = i + 1 continue }
if c == 32 or c == 9 or c == 13 { i = i + 1 continue }
if c == 10 { tok_push(TK_NL, ptr_null(), 0, line); line = line + 1; i = i + 1; continue }
if c == 32 or c == 9 or c == 13 { i = i + 1; continue }
if c == 35 { # '#' comment to end of line
while i < n and peek8(src, i) != 10 { i = i + 1 }
continue
@ -60,8 +60,8 @@ fn lex(src: ptr) -> void {
if e == 110 { r = 10 }
if e == 116 { r = 9 }
if e == 48 { r = 0 }
poke8(out, j, r) j = j + 1 i = i + 2
} else { poke8(out, j, peek8(src, i)) j = j + 1 i = i + 1 }
poke8(out, j, r); j = j + 1; i = i + 2
} else { poke8(out, j, peek8(src, i)); j = j + 1; i = i + 1 }
}
i = i + 1
poke8(out, j, 0)
@ -77,7 +77,7 @@ fn lex(src: ptr) -> void {
if e == 116 { v = 9 }
if e == 48 { v = 0 }
i = i + 2
} else { v = peek8(src, i) i = i + 1 }
} else { v = peek8(src, i); i = i + 1 }
if peek8(src, i) == 39 { i = i + 1 }
tok_push(TK_INT, ptr_null(), v, line)
continue
@ -99,12 +99,12 @@ fn lex(src: ptr) -> void {
continue
}
let v = 0
while i < n and char_is_digit(peek8(src, i)) { v = v * 10 + (peek8(src, i) - 48) i = i + 1 }
while i < n and char_is_digit(peek8(src, i)) { v = v * 10 + (peek8(src, i) - 48); i = i + 1 }
# a fractional part makes it a Q16.16 fixed literal
if i < n and peek8(src, i) == 46 and char_is_digit(peek8(src, i + 1)) {
i = i + 1
let fnum = 0 let fden = 1
while i < n and char_is_digit(peek8(src, i)) { fnum = fnum * 10 + (peek8(src, i) - 48) fden = fden * 10 i = i + 1 }
let fnum = 0; let fden = 1
while i < n and char_is_digit(peek8(src, i)) { fnum = fnum * 10 + (peek8(src, i) - 48); fden = fden * 10; i = i + 1 }
let bits = shl(v, 16) + shl(fnum, 16) / fden
tok_push(TK_FLOAT, ptr_null(), bits, line)
continue
@ -118,20 +118,20 @@ fn lex(src: ptr) -> void {
tok_push(TK_ID, substr(src, start, i - start), 0, line)
continue
}
if c == 59 { tok_push(TK_NL, ptr_null(), 0, line) i = i + 1 continue } # ';'
if c == 59 { tok_push(TK_NL, ptr_null(), 0, line); i = i + 1; continue } # ';'
# two-character operators
if two_at(src, i, 45, 62) { tok_push(TK_OP, substr(src, i, 2), 0, line) i = i + 2 continue } # ->
if two_at(src, i, 61, 62) { tok_push(TK_OP, substr(src, i, 2), 0, line) i = i + 2 continue } # =>
if two_at(src, i, 61, 61) { tok_push(TK_OP, substr(src, i, 2), 0, line) i = i + 2 continue } # ==
if two_at(src, i, 33, 61) { tok_push(TK_OP, substr(src, i, 2), 0, line) i = i + 2 continue } # !=
if two_at(src, i, 60, 61) { tok_push(TK_OP, substr(src, i, 2), 0, line) i = i + 2 continue } # <=
if two_at(src, i, 62, 61) { tok_push(TK_OP, substr(src, i, 2), 0, line) i = i + 2 continue } # >=
if two_at(src, i, 43, 61) { tok_push(TK_OP, substr(src, i, 2), 0, line) i = i + 2 continue } # +=
if two_at(src, i, 45, 61) { tok_push(TK_OP, substr(src, i, 2), 0, line) i = i + 2 continue } # -=
if two_at(src, i, 42, 61) { tok_push(TK_OP, substr(src, i, 2), 0, line) i = i + 2 continue } # *=
if two_at(src, i, 47, 61) { tok_push(TK_OP, substr(src, i, 2), 0, line) i = i + 2 continue } # /=
if two_at(src, i, 46, 46) { tok_push(TK_OP, substr(src, i, 2), 0, line) i = i + 2 continue } # ..
if is_op1(c) { tok_push(TK_OP, substr(src, i, 1), 0, line) i = i + 1 continue }
if two_at(src, i, 45, 62) { tok_push(TK_OP, substr(src, i, 2), 0, line); i = i + 2; continue } # ->
if two_at(src, i, 61, 62) { tok_push(TK_OP, substr(src, i, 2), 0, line); i = i + 2; continue } # =>
if two_at(src, i, 61, 61) { tok_push(TK_OP, substr(src, i, 2), 0, line); i = i + 2; continue } # ==
if two_at(src, i, 33, 61) { tok_push(TK_OP, substr(src, i, 2), 0, line); i = i + 2; continue } # !=
if two_at(src, i, 60, 61) { tok_push(TK_OP, substr(src, i, 2), 0, line); i = i + 2; continue } # <=
if two_at(src, i, 62, 61) { tok_push(TK_OP, substr(src, i, 2), 0, line); i = i + 2; continue } # >=
if two_at(src, i, 43, 61) { tok_push(TK_OP, substr(src, i, 2), 0, line); i = i + 2; continue } # +=
if two_at(src, i, 45, 61) { tok_push(TK_OP, substr(src, i, 2), 0, line); i = i + 2; continue } # -=
if two_at(src, i, 42, 61) { tok_push(TK_OP, substr(src, i, 2), 0, line); i = i + 2; continue } # *=
if two_at(src, i, 47, 61) { tok_push(TK_OP, substr(src, i, 2), 0, line); i = i + 2; continue } # /=
if two_at(src, i, 46, 46) { tok_push(TK_OP, substr(src, i, 2), 0, line); i = i + 2; continue } # ..
if is_op1(c) { tok_push(TK_OP, substr(src, i, 1), 0, line); i = i + 1; continue }
i = i + 1 # skip anything unrecognised
}
tok_push(TK_EOF, ptr_null(), 0, line)

File diff suppressed because it is too large Load diff

View file

@ -18,7 +18,7 @@
fn base_name(path: ptr) -> ptr {
let last = 0 - 1
let i = 0
while peek8(path, i) != 0 { if peek8(path, i) == 47 { last = i } i = i + 1 }
while peek8(path, i) != 0 { if peek8(path, i) == 47 { last = i }; i = i + 1 }
return substr(path, last + 1, i - (last + 1))
}
@ -72,7 +72,7 @@ main {
else { if streq(a, "--fmt") { fmt = true }
else { if streq(a, "--save-temps") { save = true }
else { if streq(a, "--run") { run = true }
else { if streq(a, "-o") { ai = ai + 1 if ai < os_argc() { out = os_arg(ai) } }
else { if streq(a, "-o") { ai = ai + 1; if ai < os_argc() { out = os_arg(ai) } }
else {
# an unknown flag is ignored (with a note) rather than mistaken for the
# input file — '-' is ASCII 45
@ -112,8 +112,8 @@ main {
emit_program()
# --emit-llvm, or no binary target: emit IR and stop (stdout when out is null).
if emit_ir { ir_flush(out) return }
if not has_target { ir_flush(ptr_null()) return }
if emit_ir { ir_flush(out); return }
if not has_target { ir_flush(ptr_null()); return }
# a run with no explicit -o lands in a temp file
if run and ptr_is_null(out) {

View file

@ -8,8 +8,8 @@ var g_game_name: ptr # the `game`/`module` name
fn cur() -> Tok { return toks[pi] }
fn pk(o: int) -> Tok { return toks[pi + o] }
fn is_op(v: ptr) -> bool { let t = toks[pi] return t.kind == TK_OP and streq(t.text, v) }
fn is_id(v: ptr) -> bool { let t = toks[pi] return t.kind == TK_ID and streq(t.text, v) }
fn is_op(v: ptr) -> bool { let t = toks[pi]; return t.kind == TK_OP and streq(t.text, v) }
fn is_id(v: ptr) -> bool { let t = toks[pi]; return t.kind == TK_ID and streq(t.text, v) }
fn is_kw(v: ptr) -> bool { return is_id(v) }
fn perr(msg: ptr) -> void {
@ -20,7 +20,7 @@ fn perr(msg: ptr) -> void {
os_exit(1)
}
fn eat_op(v: ptr) -> void { if not is_op(v) { perr(v) } pi = pi + 1 }
fn eat_op(v: ptr) -> void { if not is_op(v) { perr(v) }; pi = pi + 1 }
fn eat_id() -> ptr {
let t = toks[pi]
if t.kind != TK_ID { perr("expected identifier") }
@ -36,9 +36,9 @@ fn ptype() -> ptr {
eat_op("]")
let el = ptype()
let out = mem_alloc(slen(el) + 3)
poke8(out, 0, 91) poke8(out, 1, 93) # "[]"
poke8(out, 0, 91); poke8(out, 1, 93) # "[]"
let i = 0
while peek8(el, i) != 0 { poke8(out, 2 + i, peek8(el, i)) i = i + 1 }
while peek8(el, i) != 0 { poke8(out, 2 + i, peek8(el, i)); i = i + 1 }
poke8(out, 2 + i, 0)
return out
}
@ -49,23 +49,23 @@ fn ptype() -> ptr {
fn expr() -> Node { return p_or() }
fn args_call(call: Node) -> void {
eat_op("(") skipnl()
while not is_op(")") { push(call.kids, expr()) skipnl() if is_op(",") { pi = pi + 1 skipnl() } }
eat_op("("); skipnl()
while not is_op(")") { push(call.kids, expr()); skipnl(); if is_op(",") { pi = pi + 1; skipnl() } }
eat_op(")")
}
fn p_primary() -> Node {
let t = toks[pi]
if t.kind == TK_INT { let n = node(E_INT) n.ival = t.ival pi = pi + 1 return n }
if t.kind == TK_FLOAT { let n = node(E_FLOAT) n.ival = t.ival pi = pi + 1 return n }
if t.kind == TK_STR { let n = node(E_STR) n.s = t.text pi = pi + 1 return n }
if t.kind == TK_INT { let n = node(E_INT); n.ival = t.ival; pi = pi + 1; return n }
if t.kind == TK_FLOAT { let n = node(E_FLOAT); n.ival = t.ival; pi = pi + 1; return n }
if t.kind == TK_STR { let n = node(E_STR); n.s = t.text; pi = pi + 1; return n }
if t.kind == TK_ID {
if streq(t.text, "true") { let n = node(E_BOOL) n.ival = 1 pi = pi + 1 return n }
if streq(t.text, "false") { let n = node(E_BOOL) n.ival = 0 pi = pi + 1 return n }
if streq(t.text, "new") { pi = pi + 1 let n = node(E_NEW) n.s = ptype() return n }
let n = node(E_ID) n.s = t.text pi = pi + 1 return n
if streq(t.text, "true") { let n = node(E_BOOL); n.ival = 1; pi = pi + 1; return n }
if streq(t.text, "false") { let n = node(E_BOOL); n.ival = 0; pi = pi + 1; return n }
if streq(t.text, "new") { pi = pi + 1; let n = node(E_NEW); n.s = ptype(); return n }
let n = node(E_ID); n.s = t.text; pi = pi + 1; return n
}
if is_op("(") { pi = pi + 1 skipnl() let e = expr() skipnl() eat_op(")") return e }
if is_op("(") { pi = pi + 1; skipnl(); let e = expr(); skipnl(); eat_op(")"); return e }
perr("expected expression")
return node(E_INT)
}
@ -73,46 +73,46 @@ fn p_primary() -> Node {
fn p_postfix() -> Node {
let e = p_primary()
while true {
if is_op(".") { pi = pi + 1 let m = node(E_MEMBER) m.a = e m.s = eat_id() e = m }
else { if is_op("[") { pi = pi + 1 let ix = node(E_INDEX) ix.a = e ix.b = expr() eat_op("]") e = ix }
else { if is_op("(") { let c = node(E_CALL) c.a = e args_call(c) e = c } else { break } } }
if is_op(".") { pi = pi + 1; let m = node(E_MEMBER); m.a = e; m.s = eat_id(); e = m }
else { if is_op("[") { pi = pi + 1; let ix = node(E_INDEX); ix.a = e; ix.b = expr(); eat_op("]"); e = ix }
else { if is_op("(") { let c = node(E_CALL); c.a = e; args_call(c); e = c } else { break } } }
}
return e
}
fn p_unary() -> Node {
if is_op("-") { pi = pi + 1 let n = node(E_UN) n.s = "-" n.a = p_unary() return n }
if is_id("not") { pi = pi + 1 let n = node(E_UN) n.s = "not" n.a = p_unary() return n }
if is_op("-") { pi = pi + 1; let n = node(E_UN); n.s = "-"; n.a = p_unary(); return n }
if is_id("not") { pi = pi + 1; let n = node(E_UN); n.s = "not"; n.a = p_unary(); return n }
return p_postfix()
}
fn mkbin(op: ptr, l: Node, r: Node) -> Node { let b = node(E_BIN) b.s = op b.a = l b.b = r return b }
fn mkbin(op: ptr, l: Node, r: Node) -> Node { let b = node(E_BIN); b.s = op; b.a = l; b.b = r; return b }
fn p_mul() -> Node {
let l = p_unary()
while is_op("*") or is_op("/") or is_op("%") { let op = toks[pi].text pi = pi + 1 l = mkbin(op, l, p_unary()) }
while is_op("*") or is_op("/") or is_op("%") { let op = toks[pi].text; pi = pi + 1; l = mkbin(op, l, p_unary()) }
return l
}
fn p_add() -> Node {
let l = p_mul()
while is_op("+") or is_op("-") { let op = toks[pi].text pi = pi + 1 l = mkbin(op, l, p_mul()) }
while is_op("+") or is_op("-") { let op = toks[pi].text; pi = pi + 1; l = mkbin(op, l, p_mul()) }
return l
}
fn p_cmp() -> Node {
let l = p_add()
while is_op("<") or is_op("<=") or is_op(">") or is_op(">=") or is_op("==") or is_op("!=") {
let op = toks[pi].text pi = pi + 1 l = mkbin(op, l, p_add())
let op = toks[pi].text; pi = pi + 1; l = mkbin(op, l, p_add())
}
return l
}
fn p_and() -> Node {
let l = p_cmp()
while is_id("and") { pi = pi + 1 l = mkbin("and", l, p_cmp()) }
while is_id("and") { pi = pi + 1; l = mkbin("and", l, p_cmp()) }
return l
}
fn p_or() -> Node {
let l = p_and()
while is_id("or") { pi = pi + 1 l = mkbin("or", l, p_and()) }
while is_id("or") { pi = pi + 1; l = mkbin("or", l, p_and()) }
return l
}
@ -120,17 +120,30 @@ fn p_or() -> Node {
fn record() -> Node {
eat_op("{")
let r = node(E_REC)
while true { skipnl() if is_op("}") { break }
let fi = node(E_FINIT) fi.s = eat_id() eat_op("=") fi.a = expr() push(r.kids, fi)
while true { skipnl(); if is_op("}") { break }
let fi = node(E_FINIT); fi.s = eat_id(); eat_op("="); fi.a = expr(); push(r.kids, fi)
if is_op(",") { pi = pi + 1 } }
eat_op("}") return r
eat_op("}"); return r
}
# ---- statements ------------------------------------------------------------
fn block() -> Node {
skipnl() eat_op("{")
skipnl(); eat_op("{")
let b = node(N_BLOCK)
while true { skipnl() if is_op("}") { break } push(b.kids, stmt()) }
while true { skipnl(); if is_op("}") { break }
push(b.kids, stmt())
# Rule B: statements are separated by a newline or ';' (both lex to TK_NL).
# After a statement the next token must be that separator or the block's end —
# two statements may not sit adjacent with only spaces between them.
let sep = toks[pi].kind == TK_NL
if is_op("}") { sep = true }
if not sep {
let ez = file_stderr(); file_write(ez, "cur=", 4)
if not ptr_is_null(toks[pi].text) { file_write(ez, toks[pi].text, slen(toks[pi].text)) }
file_write(ez, "\n", 1); print_int(7770000 + toks[pi].line)
perr("expected newline or ';' between statements")
}
}
eat_op("}")
return b
}
@ -139,106 +152,108 @@ fn stmt() -> Node {
let t = toks[pi]
if t.kind == TK_ID {
if streq(t.text, "let") {
pi = pi + 1 let n = node(S_LET) n.s = eat_id()
if is_op(":") { pi = pi + 1 n.ty = ptype() }
if is_op("=") { pi = pi + 1 n.a = expr() }
pi = pi + 1; let n = node(S_LET); n.s = eat_id()
if is_op(":") { pi = pi + 1; n.ty = ptype() }
if is_op("=") { pi = pi + 1; n.a = expr() }
return n
}
if streq(t.text, "return") {
pi = pi + 1 let n = node(S_RETURN)
pi = pi + 1; let n = node(S_RETURN)
if toks[pi].kind != TK_NL and not is_op("}") { n.a = expr() }
return n
}
if streq(t.text, "if") {
pi = pi + 1 let n = node(S_IF) n.a = expr() n.b = block() skipnl()
if is_id("else") { pi = pi + 1 skipnl()
pi = pi + 1; let n = node(S_IF); n.a = expr(); n.b = block()
let save = pi; skipnl() # peek past newlines for a trailing `else`
if is_id("else") { pi = pi + 1; skipnl()
if is_id("if") { n.c = stmt() } else { n.c = block() } }
else { pi = save } # no else: keep the separator for block()'s Rule-B check
return n
}
if streq(t.text, "when") { # `when c { }` — an if with no else
pi = pi + 1 let n = node(S_IF) n.a = expr() n.b = block()
pi = pi + 1; let n = node(S_IF); n.a = expr(); n.b = block()
return n
}
if streq(t.text, "while") { pi = pi + 1 let n = node(S_WHILE) n.a = expr() n.b = block() return n }
if streq(t.text, "while") { pi = pi + 1; let n = node(S_WHILE); n.a = expr(); n.b = block(); return n }
if streq(t.text, "for") {
pi = pi + 1
if is_op("(") { return parse_query_for() }
let n = node(S_FOR) n.s = eat_id()
let n = node(S_FOR); n.s = eat_id()
let inkw = eat_id() # 'in'
n.a = expr() eat_op("..") n.b = expr() n.c = block()
n.a = expr(); eat_op(".."); n.b = expr(); n.c = block()
return n
}
if streq(t.text, "spawn") { return parse_spawn() }
if streq(t.text, "machine") {
pi = pi + 1 let n = node(S_MACHINE) n.a = expr() skipnl() eat_op("{")
while true { skipnl() if is_op("}") { break }
pi = pi + 1; let n = node(S_MACHINE); n.a = expr(); skipnl(); eat_op("{")
while true { skipnl(); if is_op("}") { break }
let stkw = eat_id() # 'state'
let s = node(S_STATE) s.s = eat_id() eat_op("=") s.b = expr() skipnl() s.a = block()
let s = node(S_STATE); s.s = eat_id(); eat_op("="); s.b = expr(); skipnl(); s.a = block()
push(n.kids, s) }
eat_op("}") return n
eat_op("}"); return n
}
if streq(t.text, "become") { pi = pi + 1 let n = node(S_BECOME) n.s = eat_id() return n }
if streq(t.text, "enter") { pi = pi + 1 let n = node(S_BECOME) n.s = eat_id() n.ival = 1 return n }
if streq(t.text, "despawn") { pi = pi + 1 let n = node(S_DESPAWN) n.a = expr() return n }
if streq(t.text, "break") { pi = pi + 1 return node(S_BREAK) }
if streq(t.text, "continue") { pi = pi + 1 return node(S_CONTINUE) }
if streq(t.text, "become") { pi = pi + 1; let n = node(S_BECOME); n.s = eat_id(); return n }
if streq(t.text, "enter") { pi = pi + 1; let n = node(S_BECOME); n.s = eat_id(); n.ival = 1; return n }
if streq(t.text, "despawn") { pi = pi + 1; let n = node(S_DESPAWN); n.a = expr(); return n }
if streq(t.text, "break") { pi = pi + 1; return node(S_BREAK) }
if streq(t.text, "continue") { pi = pi + 1; return node(S_CONTINUE) }
if streq(t.text, "match") {
pi = pi + 1 let n = node(S_MATCH) n.a = expr() skipnl() eat_op("{")
pi = pi + 1; let n = node(S_MATCH); n.a = expr(); skipnl(); eat_op("{")
while true {
skipnl() if is_op("}") { break }
skipnl(); if is_op("}") { break }
let arm = node(S_MARM)
while true { push(arm.kids, expr()) if is_op(",") { pi = pi + 1 skipnl() continue } break }
eat_op("=>") skipnl()
if is_op("{") { arm.a = block() } else { let b = node(N_BLOCK) push(b.kids, stmt()) arm.a = b }
while true { push(arm.kids, expr()); if is_op(",") { pi = pi + 1; skipnl(); continue }; break }
eat_op("=>"); skipnl()
if is_op("{") { arm.a = block() } else { let b = node(N_BLOCK); push(b.kids, stmt()); arm.a = b }
push(n.kids, arm)
}
eat_op("}") return n
eat_op("}"); return n
}
}
let e = expr()
if is_op("=") or is_op("+=") or is_op("-=") or is_op("*=") or is_op("/=") {
let n = node(S_ASSIGN) n.s = toks[pi].text pi = pi + 1 n.a = e n.b = expr() return n
let n = node(S_ASSIGN); n.s = toks[pi].text; pi = pi + 1; n.a = e; n.b = expr(); return n
}
let n = node(S_EXPR) n.a = e return n
let n = node(S_EXPR); n.a = e; return n
}
# ---- declarations ----------------------------------------------------------
fn parse_struct() -> Node {
pi = pi + 1 let n = node(N_STRUCT) n.s = eat_id() skipnl() eat_op("{")
while true { skipnl() if is_op("}") { break }
let f = node(N_FIELD) f.s = eat_id() eat_op(":") f.ty = ptype()
if is_op("=") { pi = pi + 1 f.a = expr() }
push(n.kids, f) if is_op(",") { pi = pi + 1 } }
eat_op("}") return n
pi = pi + 1; let n = node(N_STRUCT); n.s = eat_id(); skipnl(); eat_op("{")
while true { skipnl(); if is_op("}") { break }
let f = node(N_FIELD); f.s = eat_id(); eat_op(":"); f.ty = ptype()
if is_op("=") { pi = pi + 1; f.a = expr() }
push(n.kids, f); if is_op(",") { pi = pi + 1 } }
eat_op("}"); return n
}
fn parse_var() -> Node {
pi = pi + 1 let n = node(N_VAR) n.s = eat_id() eat_op(":") n.ty = ptype()
if is_op("=") { pi = pi + 1 n.a = expr() }
pi = pi + 1; let n = node(N_VAR); n.s = eat_id(); eat_op(":"); n.ty = ptype()
if is_op("=") { pi = pi + 1; n.a = expr() }
return n
}
fn parse_const() -> Node {
pi = pi + 1 let n = node(N_CONST) n.s = eat_id() eat_op(":") n.ty = ptype() eat_op("=") n.a = expr()
pi = pi + 1; let n = node(N_CONST); n.s = eat_id(); eat_op(":"); n.ty = ptype(); eat_op("="); n.a = expr()
return n
}
fn parse_fn() -> Node {
pi = pi + 1 let n = node(N_FN) n.s = eat_id() eat_op("(")
pi = pi + 1; let n = node(N_FN); n.s = eat_id(); eat_op("(")
while not is_op(")") {
let p = node(N_PARAM) p.s = eat_id() eat_op(":") p.ty = ptype() push(n.kids, p)
let p = node(N_PARAM); p.s = eat_id(); eat_op(":"); p.ty = ptype(); push(n.kids, p)
if is_op(",") { pi = pi + 1 }
}
eat_op(")")
n.ty = "void"
if is_op("->") { pi = pi + 1 n.ty = ptype() }
if is_op("->") { pi = pi + 1; n.ty = ptype() }
n.a = block()
return n
}
fn parse_main() -> Node { pi = pi + 1 let n = node(N_MAIN) n.a = block() return n }
fn parse_main() -> Node { pi = pi + 1; let n = node(N_MAIN); n.a = block(); return n }
# directory part of a path, including the trailing '/', or "" if none
fn dir_of(path: ptr) -> ptr {
let last = 0 - 1
let i = 0
while peek8(path, i) != 0 { if peek8(path, i) == 47 { last = i } i = i + 1 }
while peek8(path, i) != 0 { if peek8(path, i) == 47 { last = i }; i = i + 1 }
if last < 0 { return "" }
return substr(path, 0, last + 1)
}
@ -252,7 +267,7 @@ var cur_dir: ptr
fn already_loaded(full: ptr) -> bool {
let i = 0
while i < len(loaded_paths) { if streq(loaded_paths[i], full) { return true } i = i + 1 }
while i < len(loaded_paths) { if streq(loaded_paths[i], full) { return true }; i = i + 1 }
return false
}
@ -261,21 +276,21 @@ fn parse_one_decl() -> void {
if is_id("import") { pi = pi + 1
let t = toks[pi]
if t.kind != TK_STR { perr("expected \"path\" after import") }
let rel = t.text pi = pi + 1
let rel = t.text; pi = pi + 1
do_import(rel)
return
}
if is_id("struct") { push(prog, parse_struct()) return }
if is_id("component") { push(prog, parse_component()) return }
if is_id("archetype") { push(prog, parse_archetype()) return }
if is_id("system") or is_id("edge") { push(prog, parse_system()) return }
if is_id("ui") { push(prog, parse_ui()) return }
if is_id("var") { push(prog, parse_var()) return }
if is_id("const") { push(prog, parse_const()) return }
if is_id("export") { pi = pi + 1 let f = parse_fn() f.ival = 1 push(prog, f) return }
if is_id("fn") or is_id("pure") { if is_id("pure") { pi = pi + 1 } push(prog, parse_fn()) return }
if is_id("extern") { push(prog, parse_extern()) return }
if is_id("main") { push(prog, parse_main()) return }
if is_id("struct") { push(prog, parse_struct()); return }
if is_id("component") { push(prog, parse_component()); return }
if is_id("archetype") { push(prog, parse_archetype()); return }
if is_id("system") or is_id("edge") { push(prog, parse_system()); return }
if is_id("ui") { push(prog, parse_ui()); return }
if is_id("var") { push(prog, parse_var()); return }
if is_id("const") { push(prog, parse_const()); return }
if is_id("export") { pi = pi + 1; let f = parse_fn(); f.ival = 1; push(prog, f); return }
if is_id("fn") or is_id("pure") { if is_id("pure") { pi = pi + 1 }; push(prog, parse_fn()); return }
if is_id("extern") { push(prog, parse_extern()); return }
if is_id("main") { push(prog, parse_main()); return }
perr("expected declaration")
}
@ -286,13 +301,13 @@ fn do_import(rel: ptr) -> void {
push(loaded_paths, full)
let src = read_file(full)
if ptr_is_null(src) { perr(sconcat("cannot open import ", full)) }
let saved_toks = toks let saved_pi = pi let saved_dir = cur_dir
let saved_toks = toks; let saved_pi = pi; let saved_dir = cur_dir
cur_dir = dir_of(full)
lex(src) # resets the global token stream
pi = 0
skipnl()
while toks[pi].kind != TK_EOF { parse_one_decl() skipnl() }
toks = saved_toks pi = saved_pi cur_dir = saved_dir
while toks[pi].kind != TK_EOF { parse_one_decl(); skipnl() }
toks = saved_toks; pi = saved_pi; cur_dir = saved_dir
}
# a game (has systems/components) links the Ludic runtime; auto-splice it the
@ -311,8 +326,8 @@ fn parse_program() -> void {
skipnl()
g_game_name = "Ludic"
# imports may precede the game block
while is_id("import") { pi = pi + 1 let t = toks[pi] let rel = t.text pi = pi + 1 do_import(rel) skipnl() }
if is_id("game") or is_id("module") { pi = pi + 1 g_game_name = eat_id() skipnl() eat_op("{") }
while is_id("import") { pi = pi + 1; let t = toks[pi]; let rel = t.text; pi = pi + 1; do_import(rel); skipnl() }
if is_id("game") or is_id("module") { pi = pi + 1; g_game_name = eat_id(); skipnl(); eat_op("{") }
while true {
skipnl()
if toks[pi].kind == TK_EOF { break }

View file

@ -3,25 +3,25 @@
# game-construct parsing in compiler/front/parse.c.
fn parse_component() -> Node {
pi = pi + 1 let n = node(N_COMP) n.s = eat_id() skipnl() eat_op("{")
while true { skipnl() if is_op("}") { break }
let f = node(N_FIELD) f.s = eat_id() eat_op(":") f.ty = ptype()
if is_op("=") { pi = pi + 1 f.a = expr() }
push(n.kids, f) if is_op(",") { pi = pi + 1 } }
eat_op("}") return n
pi = pi + 1; let n = node(N_COMP); n.s = eat_id(); skipnl(); eat_op("{")
while true { skipnl(); if is_op("}") { break }
let f = node(N_FIELD); f.s = eat_id(); eat_op(":"); f.ty = ptype()
if is_op("=") { pi = pi + 1; f.a = expr() }
push(n.kids, f); if is_op(",") { pi = pi + 1 } }
eat_op("}"); return n
}
# skip an @annotation or a reads/writes/query/... clause we don't model yet
fn skip_clause() -> void {
if is_op("[") { let depth = 0
while true { if is_op("[") { depth = depth + 1 } if is_op("]") { depth = depth - 1 }
pi = pi + 1 if depth == 0 { break } }
while true { if is_op("[") { depth = depth + 1 }; if is_op("]") { depth = depth - 1 }
pi = pi + 1; if depth == 0 { break } }
}
}
fn parse_system() -> Node {
if is_id("edge") { pi = pi + 1 } # optional `edge` modifier before `system`
pi = pi + 1 let n = node(N_SYS) n.s = eat_id() n.ty = "Update"
pi = pi + 1; let n = node(N_SYS); n.s = eat_id(); n.ty = "Update"
# clauses: @anno, phase X, reads/writes/needs/uses [..], query (..) [..]
# A `query (vars) [terms] where c` clause desugars to a body wrapped in one
# `for (vars) in query [terms] where c { ... }` — the same S_QUERY node the
@ -30,15 +30,15 @@ fn parse_system() -> Node {
let qn = node(S_QUERY)
while true {
skipnl() # clauses may span several lines
if is_op("@") { pi = pi + 1 let a = eat_id() if is_op("(") { let d = 0 # @anno, one per turn so a
while true { if is_op("(") { d = d + 1 } if is_op(")") { d = d - 1 } pi = pi + 1 if d == 0 { break } } }
if is_op("@") { pi = pi + 1; let a = eat_id(); if is_op("(") { let d = 0 # @anno, one per turn so a
while true { if is_op("(") { d = d + 1 }; if is_op(")") { d = d - 1 }; pi = pi + 1; if d == 0 { break } } }
continue } # newline-separated @anno re-skips at the loop top
if is_id("phase") { pi = pi + 1 n.ty = eat_id() continue }
if is_id("reads") or is_id("writes") or is_id("needs") or is_id("uses") { pi = pi + 1 skip_clause() continue }
if is_id("phase") { pi = pi + 1; n.ty = eat_id(); continue }
if is_id("reads") or is_id("writes") or is_id("needs") or is_id("uses") { pi = pi + 1; skip_clause(); continue }
if is_id("query") {
pi = pi + 1 has_q = true
pi = pi + 1; has_q = true
if is_op("(") { eat_op("(") # optional (vars); omitted when nothing binds
while not is_op(")") { let v = node(E_ID) v.s = eat_id() push(qn.kids, v) if is_op(",") { pi = pi + 1 } }
while not is_op(")") { let v = node(E_ID); v.s = eat_id(); push(qn.kids, v); if is_op(",") { pi = pi + 1 } }
eat_op(")") }
qn.c = parse_query_tail()
qn.b = qn.c.a # where-expr (or null)
@ -50,7 +50,7 @@ fn parse_system() -> Node {
let body = block()
if has_q {
qn.a = body
let wrap = node(N_BLOCK) push(wrap.kids, qn)
let wrap = node(N_BLOCK); push(wrap.kids, qn)
n.a = wrap
} else { n.a = body }
return n
@ -62,12 +62,12 @@ fn parse_query_tail() -> Node {
eat_op("[")
let q = node(N_BLOCK)
while not is_op("]") {
if is_op("{") { pi = pi + 1 let t = node(E_ID) t.s = eat_id() t.ival = 1 push(q.kids, t) eat_op("}") }
else { let t = node(E_ID) t.s = eat_id() t.ival = 0 push(q.kids, t) }
if is_op("{") { pi = pi + 1; let t = node(E_ID); t.s = eat_id(); t.ival = 1; push(q.kids, t); eat_op("}") }
else { let t = node(E_ID); t.s = eat_id(); t.ival = 0; push(q.kids, t) }
if is_op(",") { pi = pi + 1 }
}
eat_op("]")
if is_id("where") { pi = pi + 1 q.a = expr() }
if is_id("where") { pi = pi + 1; q.a = expr() }
return q
}
@ -75,7 +75,7 @@ fn parse_query_tail() -> Node {
fn parse_query_for() -> Node {
let n = node(S_QUERY)
eat_op("(")
while not is_op(")") { let v = node(E_ID) v.s = eat_id() push(n.kids, v) if is_op(",") { pi = pi + 1 } }
while not is_op(")") { let v = node(E_ID); v.s = eat_id(); push(n.kids, v); if is_op(",") { pi = pi + 1 } }
eat_op(")")
let inkw = eat_id() # 'in'
let qkw = eat_id() # 'query'
@ -86,59 +86,59 @@ fn parse_query_for() -> Node {
}
fn parse_spawn() -> Node {
pi = pi + 1 let n = node(S_SPAWN) n.s = eat_id() skipnl() eat_op("{")
pi = pi + 1; let n = node(S_SPAWN); n.s = eat_id(); skipnl(); eat_op("{")
while true {
skipnl() if is_op("}") { break }
let ci = node(E_FINIT) ci.s = eat_id() eat_op("=")
skipnl(); if is_op("}") { break }
let ci = node(E_FINIT); ci.s = eat_id(); eat_op("=")
ci.a = record() # { field = val, ... }
push(n.kids, ci)
if is_op(",") { pi = pi + 1 }
}
eat_op("}") return n
eat_op("}"); return n
}
# archetype Name { CompA, CompB } — a named entity kind (bundle of components)
fn parse_archetype() -> Node {
pi = pi + 1 let n = node(N_ARCH) n.s = eat_id() skipnl() eat_op("{")
while true { skipnl() if is_op("}") { break }
let c = node(E_ID) c.s = eat_id() push(n.kids, c)
if is_op(",") { pi = pi + 1 } skipnl() }
eat_op("}") return n
pi = pi + 1; let n = node(N_ARCH); n.s = eat_id(); skipnl(); eat_op("{")
while true { skipnl(); if is_op("}") { break }
let c = node(E_ID); c.s = eat_id(); push(n.kids, c)
if is_op(",") { pi = pi + 1 }; skipnl() }
eat_op("}"); return n
}
# extern fn name(params) -> T = "symbol"
fn parse_extern() -> Node {
pi = pi + 1 # 'extern'
let fnkw = eat_id() # 'fn'
let n = node(N_EXTERN) n.s = eat_id() eat_op("(")
while not is_op(")") { let p = node(N_PARAM) p.s = eat_id() eat_op(":") p.ty = ptype() push(n.kids, p)
let n = node(N_EXTERN); n.s = eat_id(); eat_op("(")
while not is_op(")") { let p = node(N_PARAM); p.s = eat_id(); eat_op(":"); p.ty = ptype(); push(n.kids, p)
if is_op(",") { pi = pi + 1 } }
eat_op(")")
n.ty = "void"
if is_op("->") { pi = pi + 1 n.ty = ptype() }
if is_op("->") { pi = pi + 1; n.ty = ptype() }
eat_op("=")
let t = toks[pi] # "symbol"
n.a = node(E_STR) n.a.s = t.text pi = pi + 1
n.a = node(E_STR); n.a.s = t.text; pi = pi + 1
return n
}
# ui Name { widget-tree } — parsed into a widget node tree (emitted later)
fn parse_widget() -> Node {
let w = node(N_UI) w.s = eat_id() # widget type name
let w = node(N_UI); w.s = eat_id() # widget type name
w.b = node(N_BLOCK) # b.kids = props (E_FINIT)
while toks[pi].kind == TK_ID and toks[pi + 1].kind == TK_OP and streq(toks[pi + 1].text, "=") {
let pr = node(E_FINIT) pr.s = eat_id() eat_op("=") pr.a = expr() push(w.b.kids, pr)
let pr = node(E_FINIT); pr.s = eat_id(); eat_op("="); pr.a = expr(); push(w.b.kids, pr)
}
skipnl()
if is_op("{") { pi = pi + 1
while true { skipnl() if is_op("}") { break } push(w.kids, parse_widget()) }
while true { skipnl(); if is_op("}") { break }; push(w.kids, parse_widget()) }
eat_op("}") }
return w
}
fn parse_ui() -> Node {
pi = pi + 1 let n = node(N_UI) n.s = eat_id() n.ival = 1 # ival=1 marks the top ui block
skipnl() eat_op("{") skipnl()
pi = pi + 1; let n = node(N_UI); n.s = eat_id(); n.ival = 1 # ival=1 marks the top ui block
skipnl(); eat_op("{"); skipnl()
n.a = parse_widget()
skipnl() eat_op("}")
skipnl(); eat_op("}")
return n
}

View file

@ -24,7 +24,7 @@ fn slen(s: ptr) -> int {
fn substr(src: ptr, start: int, n: int) -> ptr {
let b = mem_alloc(n + 1)
let i = 0
while i < n { poke8(b, i, peek8(src, start + i)) i = i + 1 }
while i < n { poke8(b, i, peek8(src, start + i)); i = i + 1 }
poke8(b, n, 0)
return b
}
@ -39,20 +39,20 @@ fn char_is_alnum(c: int) -> bool { return char_is_alpha(c) or char_is_digit(c) }
# integer -> fresh decimal string
fn itoa(v: int) -> ptr {
if v == 0 { let z = mem_alloc(2) poke8(z, 0, 48) poke8(z, 1, 0) return z }
if v == 0 { let z = mem_alloc(2); poke8(z, 0, 48); poke8(z, 1, 0); return z }
let neg = false
let x = v
if x < 0 { neg = true x = 0 - x }
if x < 0 { neg = true; x = 0 - x }
let tmp = mem_alloc(16)
let n = 0
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 total = n
if neg { total = total + 1 }
let out = mem_alloc(total + 1)
let k = 0
if neg { poke8(out, 0, 45) k = 1 }
if neg { poke8(out, 0, 45); k = 1 }
let i = 0
while i < n { poke8(out, k + i, peek8(tmp, n - 1 - i)) i = i + 1 }
while i < n { poke8(out, k + i, peek8(tmp, n - 1 - i)); i = i + 1 }
poke8(out, total, 0)
return out
}

View file

@ -1,12 +1,12 @@
game T {
fn fib(n: int) -> int { if n < 2 { return n } return fib(n-1) + fib(n-2) }
fn fib(n: int) -> int { if n < 2 { return n }; return fib(n-1) + fib(n-2) }
main {
print_int(fib(10)) # 55
let s = 0
for i in 0 .. 10 { if i == 5 { break } if i % 2 == 0 { continue } s = s + i }
for i in 0 .. 10 { if i == 5 { break }; if i % 2 == 0 { continue }; s = s + i }
print_int(s) # 1+3 = 4
let i = 0 let t = 0
while true { i = i + 1 if i > 5 { break } t = t + i }
let i = 0; let t = 0
while true { i = i + 1; if i > 5 { break }; t = t + i }
print_int(t) # 15
if false and (1/0 == 0) { print_int(999) } else { print_int(1) } # short-circuit: no div by zero
}

View file

@ -1,6 +1,6 @@
game T {
fn classify(c: int) -> int {
match c { 65, 66 => { return 1 } 67 => { return 2 } _ => { return 9 } }
match c { 65, 66 => { return 1 }; 67 => { return 2 }; _ => { return 9 } }
return 0
}
main {

View file

@ -0,0 +1,119 @@
/* sepfix.c — one-time migration for Rule B: insert `;` at statement boundaries
* that are currently spelled with only whitespace. Reuses the toolchain lexer.
*
* A boundary is inserted between two significant, same-line tokens prev,cur when
* - we are at paren/bracket depth 0 (not inside a call's args or an index), AND
* - we are NOT inside a `ui` block (widget props are `k=v`, space-separated,
* and the parser does not skip newlines between them), AND
* - prev can END an operand/statement, AND cur can START a statement.
* Inserting `;` (which the lexer maps to a newline token) at a REAL boundary is
* a semantic no-op under today's permissive parser — the IR-identity check in
* the driver is the proof. Wrong insertions change the IR and are rejected.
*
* usage: sepfix <in.ludic> -> writes migrated source to stdout
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "ludic_syntax.h"
static const char* STMT_START[] = {
"let","return","if","when","while","for","spawn","despawn",
"match","machine","become","enter","break","continue", 0
};
static int ender(const LLex* L, int i){
const LTok* t = &L->v[i];
switch (t->kind){
case LT_ID: case LT_INT: case LT_FLOAT: case LT_STR: case LT_CHAR:
case LT_BOOL: case LT_TYPE: case LT_PHASE: return 1;
case LT_OP: {
if (ltok_len(t) != 1) return 0;
char c = L->src[t->start];
if (c == ']'){
/* `]` closing an empty `[]` is a slice-TYPE marker (`[]Node`),
* not an operand end — the type name that follows continues it. */
int p = ltok_prev_sig(L, i);
if (p >= 0 && L->v[p].kind == LT_OP && ltok_len(&L->v[p]) == 1 && L->src[L->v[p].start] == '[') return 0;
return 1;
}
return (c == ')' || c == '}'); }
case LT_KW: { char b[32]; ltok_text(L, i, b, sizeof b);
return !strcmp(b, "break") || !strcmp(b, "continue"); }
default: return 0;
}
}
static int starter(const LLex* L, int i){
const LTok* t = &L->v[i];
if (t->kind == LT_ID || t->kind == LT_INT || t->kind == LT_FLOAT ||
t->kind == LT_STR || t->kind == LT_CHAR || t->kind == LT_BOOL) return 1;
if (t->kind == LT_KW){ char b[32]; ltok_text(L, i, b, sizeof b); return lud_in(STMT_START, b); }
return 0; /* an operator never starts a statement here */
}
int main(int argc, char** argv){
if (argc < 2){ fprintf(stderr, "usage: sepfix <file>\n"); return 2; }
FILE* f = fopen(argv[1], "rb");
if (!f){ fprintf(stderr, "sepfix: cannot open %s\n", argv[1]); return 2; }
fseek(f, 0, SEEK_END); long n = ftell(f); fseek(f, 0, SEEK_SET);
char* src = malloc(n + 1); fread(src, 1, n, f); src[n] = 0; fclose(f);
LLex L; lud_lex(&L, src);
/* insert[k] = 1 means: emit a ';' immediately after token k's bytes */
char* insert = calloc(L.n, 1);
int paren = 0; /* unclosed ( or [ */
/* ui suppression: brace depth stack, marking which open braces are ui/widget */
int ui_stack[512]; int bp = 0; int ui_active = 0;
int pendingUiOpen = 0; /* saw `ui` or a widget word; the next { is a ui brace */
int prev = -1; /* previous significant token index */
for (int i = 0; i < L.n; i++){
int k = L.v[i].kind;
if (k == LT_EOF) break;
if (k == LT_NL || k == LT_COMMENT) continue;
char c0 = L.src[L.v[i].start];
int op1 = (k == LT_OP && ltok_len(&L.v[i]) == 1);
/* track ui context by the `ui` KEYWORD only — it brackets the whole
* widget tree via its root brace, so every nested widget brace is
* already inside ui_active. (Do NOT key off widget words like col/row/
* image/label: those are also ordinary variable names, and matching them
* would wrongly suppress real statement boundaries.) */
if (k == LT_KW && ltok_is(&L, i, "ui")) pendingUiOpen = 1;
if (op1 && c0 == '{'){
if (bp < 512){ ui_stack[bp] = pendingUiOpen; bp++; if (pendingUiOpen) ui_active++; }
pendingUiOpen = 0;
} else if (op1 && c0 == '}'){
if (bp > 0){ bp--; if (ui_stack[bp]) { if (ui_active) ui_active--; } }
} else if (op1 && (c0 == '(' || c0 == '[')){ paren++; if (getenv("SEPFIX_TRACE")) fprintf(stderr, " L%d '%c' -> paren=%d\n", L.v[i].line + 1, c0, paren); }
else if (op1 && (c0 == ')' || c0 == ']')){ if (paren > 0) paren--; if (getenv("SEPFIX_TRACE")) fprintf(stderr, " L%d '%c' -> paren=%d\n", L.v[i].line + 1, c0, paren); }
/* candidate boundary between prev and this token */
if (prev >= 0 && paren == 0 && !ui_active &&
L.v[prev].line == L.v[i].line &&
ender(&L, prev) && starter(&L, i)){
insert[prev] = 1;
}
prev = i;
}
if (getenv("SEPFIX_DEBUG")) fprintf(stderr, "END STATE %s: paren=%d ui_active=%d bp=%d\n", argv[1], paren, ui_active, bp);
/* rebuild: copy bytes, dropping in ';' right after any token flagged */
for (int i = 0, t = 0; i < n; ){
/* find if a token ends exactly at i and is flagged */
putchar(src[i]);
i++;
/* emit ';' after the last byte of a flagged token */
for (; t < L.n; t++){
if (L.v[t].end == i && insert[t]){ putchar(';'); break; }
if (L.v[t].end > i) break;
}
}
lud_lex_free(&L); free(insert); free(src);
return 0;
}