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` **Recursion** ✅ → `55`
```ludic ```ludic
game P { game P {
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) }
system B phase Start { print_int(fib(10)) quit() } 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 { fn itoa(v: int, buf: ptr) -> int {
let n = 0 let n = 0
let x = v 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) 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 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) mem_free(tmp)
return n return n
} }
@ -905,10 +905,17 @@ file_close(f)
Each is a spelling the language accepts; the point is that the *alternative* Each is a spelling the language accepts; the point is that the *alternative*
spelling is equally legal (§5.3). 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 ```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() } } 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` **R2 — commas omitted throughout** → `7`
```ludic ```ludic
@ -954,7 +961,7 @@ parenthesisation) while `ludic-fmt` returns the input **unchanged**.
**Verified-missing** — each a compile error today: **Verified-missing** — each a compile error today:
```ludic ```ludic
# doc-check: expect-error — every line here is a compile error by design # 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') struct Node { k: int, a: ptr } # expected declaration (got 'struct')
var t: [int; 8] # expected identifier (got '[') var t: [int; 8] # expected identifier (got '[')
var h: fn = a # unknown type 'fn' for var h 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' print_err("x") # unknown function 'print_err'
let p = mem_realloc(ptr_null(), 10) # unknown function 'mem_realloc' let p = mem_realloc(ptr_null(), 10) # unknown function 'mem_realloc'
print_str("ab" + "cd") # passes front-end, invalid IR 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() } if ui_clicked(UI_Quit) { quit() }
ui_set_int(UI_HpLabel, hp) # poke dynamic values by id 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. 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` · `for (…) in query […] { }` · `break` · `continue` · `return` · `spawn` ·
`despawn` · `match` · `machine`. `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 `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 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 `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: the spec and the compiler, then unifies the grammar around two rules. Scope:
**full redesign (Phases 0–5)**. Named-field direction: **colon everywhere**. **full redesign (Phases 0–5)**. Named-field direction: **colon everywhere**.
> Status: **Phase 1 landed** (see below). Phases 2→5 are proposals. The phases > Status: **Phases 1 & 2 landed** (see below). Phases 3→5 are proposals. The
> are ordered so the documentation never describes syntax the compiler rejects, > phases are ordered so the documentation never describes syntax the compiler
> and every phase ends with the compiler still self-hosting to a fixpoint > rejects, and every phase ends with the compiler still self-hosting to a
> (`./test.sh`). > fixpoint (`./test.sh`).
> >
> Coordinated with the toolchain agent (CLI front-end / `ludicc`+`ludic` > Coordinated with the toolchain agent (CLI front-end / `ludicc`+`ludic`
> binaries) via serialized reseeds of `selfhost/ludicc.seed.ll`; Phase 1 rode in > 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 compile yet — a positive test would fail; the header note + LANGUAGE.md warning
cover the drift instead). cover the drift instead).
### Phase 2 — Statement separation (Rule B) ### Phase 2 — Statement separation (Rule B) ✅ DONE
- Enforce a separator in `block()`/`stmt()` ([parse.ludic:130-199](selfhost/parse.ludic)): Landed on branch `syntax-redesign-phase2` (baseline committed on `main` first).
after a statement, require `TK_NL` or `}`. - ✅ **Parser enforces a separator** — `block()` requires a newline or `;` after
- Teach `ludic-fmt` to normalize one-statement-per-line and insert `;` where two each statement, else `expected newline or ';' between statements`
share a line. Reformat the whole corpus with it. ([parse.ludic](selfhost/parse.ludic)). Also fixed `if`-without-`else` swallowing
- Risk: the self-host sources themselves use space-juxtaposed statements heavily its trailing separator (it now peeks for `else` and restores if absent).
— reformat them in the same commit, re-seed, re-verify fixpoint. - ✅ **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) ### Phase 3 — Named-field unification (Rule A)
- Migrate spawn/record init (`= {…=…}` → `{…:…}`) and ui props (`key=value` → - Migrate spawn/record init (`= {…=…}` → `{…:…}`) and ui props (`key=value` →

View file

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

View file

@ -7,7 +7,7 @@
game SceneDemo { game SceneDemo {
const R_N: int = 0 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 { scene Title start {
on enter { print_int(1) } on enter { print_int(1) }
@ -22,7 +22,7 @@ game SceneDemo {
} }
scene Play { 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) } on exit { print_int(4) }
layer World { layer World {
system Step phase Update { system Step phase Update {

View file

@ -26,9 +26,9 @@ game Snake {
fn reset(x: int) -> int { fn reset(x: int) -> int {
for (p, s) in query [Pos, Seg] { despawn self() } for (p, s) in query [Pos, Seg] { despawn self() }
spawn S0 { Seg = { order = 0 } Pos = { x = 10, y = 7 } } spawn S0 { Seg = { order = 0 }; Pos = { x = 10, y = 7 } }
spawn S1 { Seg = { order = 1 } Pos = { x = 9, y = 7 } } spawn S1 { Seg = { order = 1 }; Pos = { x = 9, y = 7 } }
spawn S2 { Seg = { order = 2 } Pos = { x = 8, y = 7 } } spawn S2 { Seg = { order = 2 }; Pos = { x = 8, y = 7 } }
setreg(R_DIR, 3) setreg(R_DIR, 3)
setreg(R_LEN, 3) setreg(R_LEN, 3)
setreg(R_TICK, 0) setreg(R_TICK, 0)
@ -62,7 +62,7 @@ game Snake {
setreg(R_TICK, 0) setreg(R_TICK, 0)
let len = reg(R_LEN) let len = reg(R_LEN)
for (pt, stg) in query [Pos, Seg] { 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 { for step in 0 .. len {
let i = len - 1 - step let i = len - 1 - step
@ -70,7 +70,7 @@ game Snake {
for (pa, sa) in query [Pos, Seg] { for (pa, sa) in query [Pos, Seg] {
if sa.order == i { if sa.order == i {
for (pb, sb) in query [Pos, Seg] { 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 { if reg(R_ATE) == 1 {
setreg(R_SCORE, reg(R_SCORE) + 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_LEN, len + 1)
setreg(R_FX, rng_range(0, GW - 1)) setreg(R_FX, rng_range(0, GW - 1))
setreg(R_FY, rng_range(0, GH - 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) file_seek(f, 0, 2)
let n = file_tell(f) let n = file_tell(f)
file_seek(f, 0, 0) 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) let buf = mem_alloc(n + 8)
file_read(f, buf, n) file_read(f, buf, n)
file_close(f) file_close(f)
@ -70,9 +70,9 @@ fn rt_decode_png(path: str) -> bool {
let d = rt_read_file(path) let d = rt_read_file(path)
if ptr_is_null(d) { return false } if ptr_is_null(d) { return false }
let size = rt_file_len let size = rt_file_len
if size < 8 { 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, 0) != 137 { mem_free(d); return false }
if peek8(d, 1) != 80 { mem_free(d) return false } if peek8(d, 1) != 80 { mem_free(d); return false }
let w = 0 let w = 0
let h = 0 let h = 0
@ -119,8 +119,8 @@ fn rt_decode_png(path: str) -> bool {
} }
} }
if w <= 0 { mem_free(d) return false } if w <= 0 { mem_free(d); return false }
if h <= 0 { mem_free(d) return false } if h <= 0 { mem_free(d); return false }
let channels = 1 let channels = 1
if ct == 2 { channels = 3 } if ct == 2 { channels = 3 }
@ -133,7 +133,7 @@ fn rt_decode_png(path: str) -> bool {
let rawlen = h * (stride + 1) let rawlen = h * (stride + 1)
let raw = mem_alloc(rawlen + 8) let raw = mem_alloc(rawlen + 8)
if z_uncompress(idat, idlen, raw, rawlen) < 0 { 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 return false
} }
@ -378,7 +378,7 @@ fn rt_hexval(c: int) -> int {
fn rt_load_sprites(path: str) -> void { fn rt_load_sprites(path: str) -> void {
let d = rt_read_file(path) 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 size = rt_file_len
let pal = mem_alloc(128 * 4) let pal = mem_alloc(128 * 4)
mem_set(pal, 0, 128 * 4) mem_set(pal, 0, 128 * 4)
@ -391,7 +391,7 @@ fn rt_load_sprites(path: str) -> void {
let j = start let j = start
while j < size { while j < size {
let ch = peek8(d, j) 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) let c0 = peek8(d, start)
if c0 == 112 { # 'p' — "pal <char> <rrggbb>" 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) let r = peek8(d, p)
p = p + 1 p = p + 1
while r > 0 { 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 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 p = p + 1
if band(fl, 16) != 0 { xv = xv + dxv } else { xv = xv - dxv } if band(fl, 16) != 0 { xv = xv + dxv } else { xv = xv - dxv }
} else { } 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) 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 p = p + 1
if band(fl, 32) != 0 { yv = yv + dyv } else { yv = yv - dyv } if band(fl, 32) != 0 { yv = yv + dyv } else { yv = yv - dyv }
} else { } 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) 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 { fn buf_puts(b: Buf, s: ptr) -> void {
let i = 0 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_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)) } if fidx < 0 { perr(sconcat("no such field ", e.s)) }
g_addr_ty = field_type(s, e.s) g_addr_ty = field_type(s, e.s)
let r = nreg() let r = nreg()
emit(" ") emit(r) emit(" = getelementptr inbounds ") emit(layout_ty(base.ty)) 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(", ptr "); emit(base.code); emit(", i32 0, i32 "); emit(itoa(fidx)); emit("\n")
return r return r
} }
@ -26,13 +26,13 @@ fn emit_index_addr(e: Node) -> ptr {
g_addr_ty = el g_addr_ty = el
# load the data pointer from the slice header (field 0) # load the data pointer from the slice header (field 0)
let dp = nreg() let dp = nreg()
emit(" ") emit(dp) emit(" = getelementptr inbounds %LSlice, ptr ") emit(" "); emit(dp); emit(" = getelementptr inbounds %LSlice, ptr ")
emit(base.code) emit(", i32 0, i32 0\n") emit(base.code); emit(", i32 0, i32 0\n")
let data = nreg() 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 ix = emit_expr(e.b)
let r = nreg() let r = nreg()
emit(" ") emit(r) emit(" = getelementptr inbounds ") emit(llty(el)) emit(" "); emit(r); emit(" = getelementptr inbounds "); emit(llty(el))
emit(", ptr ") emit(data) emit(", i32 ") emit(ix.code) emit("\n") emit(", ptr "); emit(data); emit(", i32 "); emit(ix.code); emit("\n")
return r return r
} }

View file

@ -3,7 +3,7 @@
# structs and slices are references, so every non-scalar type lowers to `ptr`. # structs and slices are references, so every non-scalar type lowers to `ptr`.
struct Val { code: ptr = ptr_null(), ty: ptr = ptr_null() } 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 head: Buf # module-level: types, globals, string constants
var code: Buf # function bodies 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 # 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. # off its end), so they go into a per-function buffer spliced in at entry.
fn emit_alloca(llt: ptr) -> ptr { fn emit_alloca(llt: ptr) -> ptr {
let r = sconcat("%t", itoa(ll_t)) ll_t = ll_t + 1 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") buf_puts(falloc, " "); buf_puts(falloc, r); buf_puts(falloc, " = alloca "); buf_puts(falloc, llt); buf_puts(falloc, "\n")
return r return r
} }
# "%t<n>" fresh register # "%t<n>" fresh register
fn sconcat(a: ptr, b: ptr) -> ptr { 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 out = mem_alloc(la + lb + 1)
let i = 0 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 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) poke8(out, la + lb, 0)
return out return out
} }
fn nreg() -> ptr { let r = sconcat("%t", itoa(ll_t)) ll_t = ll_t + 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 } 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/ # Ludic type -> LLVM type. int/bool are i32; everything else (ptr/str/struct/
# slice) is a pointer; void is void. # 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 { fn find_arch(name: ptr) -> Node {
let i = 0 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() return ptr_null()
} }
fn find_comp(name: ptr) -> Node { fn find_comp(name: ptr) -> Node {
let i = 0 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() return ptr_null()
} }
# a struct or a component — both have %Str_/%Cmp_ layouts with named fields # a struct or a component — both have %Str_/%Cmp_ layouts with named fields
fn layout_node(name: ptr) -> Node { 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) return find_comp(name)
} }
fn layout_ty(name: ptr) -> ptr { fn layout_ty(name: ptr) -> ptr {
@ -99,12 +99,12 @@ fn layout_ty(name: ptr) -> ptr {
fn field_index(s: Node, fname: ptr) -> int { fn field_index(s: Node, fname: ptr) -> int {
let i = 0 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 return 0 - 1
} }
fn field_type(s: Node, fname: ptr) -> ptr { fn field_type(s: Node, fname: ptr) -> ptr {
let i = 0 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" return "int"
} }
@ -121,19 +121,19 @@ fn find_global(name: ptr) -> Node {
} }
fn find_fn(name: ptr) -> Node { fn find_fn(name: ptr) -> Node {
let i = 0 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() return ptr_null()
} }
# local variable environment # local variable environment
fn loc_reset() -> void { nloc = 0 } fn loc_reset() -> void { nloc = 0 }
fn loc_push(name: ptr, r: ptr, ty: ptr) -> void { 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 } 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) } else { push(loc_name, name); push(loc_reg, r); push(loc_ty, ty) }
nloc = nloc + 1 nloc = nloc + 1
} }
fn loc_find(name: ptr) -> int { fn loc_find(name: ptr) -> int {
let i = nloc - 1 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 return 0 - 1
} }

View file

@ -6,26 +6,26 @@ fn emit_params_sig(d: Node) -> void {
let i = 0 let i = 0
while i < len(d.kids) { while i < len(d.kids) {
if i > 0 { emit(", ") } 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 i = i + 1
} }
} }
fn emit_fn(d: Node) -> void { 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 ret_ty = d.ty
let fbody = buf_new() let fbody = buf_new()
falloc = buf_new() falloc = buf_new()
let saved = code let saved = code
code = fbody code = fbody
let rl = llty(ret_ty) 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 # params: store each incoming argument into a stack slot
let i = 0 let i = 0
while i < len(d.kids) { while i < len(d.kids) {
let p = d.kids[i] let p = d.kids[i]
let slot = emit_alloca(llty(p.ty)) 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) loc_push(p.s, slot, p.ty)
i = i + 1 i = i + 1
} }
@ -33,16 +33,16 @@ fn emit_fn(d: Node) -> void {
if not g_term { emit(" br label %ret\n") } if not g_term { emit(" br label %ret\n") }
emit("ret:\n") emit("ret:\n")
if streq(rl, "void") { emit(" ret void\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 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(falloc))
emit(buf_str(fbody)) emit(buf_str(fbody))
emit("}\n\n") emit("}\n\n")
} }
fn emit_main(d: Node) -> void { 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" ret_ty = "int"
let fbody = buf_new() let fbody = buf_new()
falloc = buf_new() falloc = buf_new()
@ -56,7 +56,7 @@ fn emit_main(d: Node) -> void {
if not g_term { emit(" br label %ret\n") } if not g_term { emit(" br label %ret\n") }
emit("ret:\n") emit("ret:\n")
let r = emit_bind("load i32, ptr %retval") let r = emit_bind("load i32, ptr %retval")
emit(" ret i32 ") emit(r) emit("\n") emit(" ret i32 "); emit(r); emit("\n")
code = saved code = saved
emit("define i32 @main(i32 %argc, ptr %argv) {\nentry:\n") emit("define i32 @main(i32 %argc, ptr %argv) {\nentry:\n")
emit(buf_str(falloc)) emit(buf_str(falloc))
@ -67,20 +67,20 @@ fn emit_main(d: Node) -> void {
fn emit_program() -> void { fn emit_program() -> void {
head = buf_new() head = buf_new()
code = buf_new() code = buf_new()
loc_name = new []ptr loc_reg = new []ptr loc_ty = new []ptr loc_name = new []ptr; loc_reg = new []ptr; loc_ty = new []ptr
brk_lbl = new []ptr cnt_lbl = new []ptr brk_lbl = new []ptr; cnt_lbl = new []ptr
self_stk = new []ptr self_stk = new []ptr
mach_stk = new []Node mach_stk = new []Node
emit_header() emit_header()
if has_ecs() { emit_ecs_storage() } if has_ecs() { emit_ecs_storage() }
let i = 0 let i = 0
while i < len(prog) { if prog[i].kind == N_FN { emit_fn(prog[i]) } i = i + 1 } 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_ecs() { emit_ecs_allocator(); emit_snapshot() }
if has_ui() { emit_ui_build() } if has_ui() { emit_ui_build() }
if has_systems() { emit_game_main() } if has_systems() { emit_game_main() }
else { else {
i = 0 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 { fn has_ecs() -> bool {
let i = 0 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 return false
} }
fn has_systems() -> bool { fn has_systems() -> bool {
let i = 0 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 return false
} }
@ -32,7 +32,7 @@ fn emit_ecs_storage() -> void {
emith(sconcat("%Cmp_", sconcat(c.s, " = type { "))) emith(sconcat("%Cmp_", sconcat(c.s, " = type { ")))
if len(c.kids) == 0 { emith("i32") } if len(c.kids) == 0 { emith("i32") }
let f = 0 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(" }\n")
emith(sconcat("@S_", sconcat(c.s, sconcat(" = internal global [", sconcat(me, sconcat(" x %Cmp_", sconcat(c.s, "] zeroinitializer\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"))))) 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) { while i < len(prog) {
if prog[i].kind == N_COMP { if prog[i].kind == N_COMP {
let hn = sconcat("%h", itoa(i)) 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(" "); 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(" store i8 0, ptr "); emit(hn); emit("\n")
} }
i = i + 1 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(" store i32 0, ptr %k\n ret void\n}\n\n")
emit("define i32 @L_alloc() {\nentry:\n") emit("define i32 @L_alloc() {\nentry:\n")
@ -64,7 +64,7 @@ fn emit_ecs_allocator() -> void {
emit("reuse:\n") emit("reuse:\n")
emit(" %fn1 = sub i32 %fn, 1\n") emit(" %fn1 = sub i32 %fn, 1\n")
emit(" store i32 %fn1, ptr @L_freen\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(" %re = load i32, ptr %fp\n")
emit(" br label %done\n") emit(" br label %done\n")
emit("fresh:\n") emit("fresh:\n")
@ -75,16 +75,16 @@ fn emit_ecs_allocator() -> void {
emit("done:\n") emit("done:\n")
emit(" %e = phi i32 [ %re, %reuse ], [ %ec, %fresh ]\n") emit(" %e = phi i32 [ %re, %reuse ], [ %ec, %fresh ]\n")
emit(" call void @L_reset(i32 %e)\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(" store i32 1, ptr %ap\n")
emit(" ret i32 %e\n}\n\n") emit(" ret i32 %e\n}\n\n")
emit("define void @L_free_entity(i32 %e) {\nentry:\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(" store i32 0, ptr %ap\n")
emit(" call void @L_reset(i32 %e)\n") emit(" call void @L_reset(i32 %e)\n")
emit(" %fn = load i32, ptr @L_freen\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(" store i32 %e, ptr %fp\n")
emit(" %fn1 = add i32 %fn, 1\n") emit(" %fn1 = add i32 %fn, 1\n")
emit(" store i32 %fn1, ptr @L_freen\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 la = emit_expr(e.a)
let lc = emit_bind(sconcat("icmp ne i32 ", sconcat(la.code, ", 0"))) let lc = emit_bind(sconcat("icmp ne i32 ", sconcat(la.code, ", 0")))
let lz = emit_bind(sconcat("zext i1 ", sconcat(lc, " to i32"))) let lz = emit_bind(sconcat("zext i1 ", sconcat(lc, " to i32")))
emit(" store i32 ") emit(lz) emit(", ptr ") emit(slot) emit("\n") emit(" store i32 "); emit(lz); emit(", ptr "); emit(slot); emit("\n")
let ev = lbl("sc") let done = lbl("scend") 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") } 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") } else { emit(" br i1 "); emit(lc); emit(", label %"); emit(done); emit(", label %"); emit(ev); emit("\n") }
emit(ev) emit(":\n") emit(ev); emit(":\n")
let rb = emit_expr(e.b) let rb = emit_expr(e.b)
let rc = emit_bind(sconcat("icmp ne i32 ", sconcat(rb.code, ", 0"))) let rc = emit_bind(sconcat("icmp ne i32 ", sconcat(rb.code, ", 0")))
let rz = emit_bind(sconcat("zext i1 ", sconcat(rc, " to i32"))) let rz = emit_bind(sconcat("zext i1 ", sconcat(rc, " to i32")))
emit(" store i32 ") emit(rz) emit(", ptr ") emit(slot) emit("\n") emit(" store i32 "); emit(rz); emit(", ptr "); emit(slot); emit("\n")
emit(" br label %") emit(done) emit("\n") emit(" br label %"); emit(done); emit("\n")
emit(done) emit(":\n") emit(done); emit(":\n")
return val(emit_bind(sconcat("load i32, ptr ", slot)), "bool") 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 b = emit_expr(e.b)
let fx = streq(a.ty, "fixed") or streq(b.ty, "fixed") let fx = streq(a.ty, "fixed") or streq(b.ty, "fixed")
if is_cmp(e.s) { if is_cmp(e.s) {
let ac = a.code let bc = b.code let ac = a.code; let bc = b.code
if fx { ac = to_fixed(a) bc = to_fixed(b) } 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)))))) 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") return val(emit_bind(sconcat("zext i1 ", sconcat(c, " to i32"))), "bool")
} }
if fx { 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, "*") { if streq(e.s, "*") {
let a64 = emit_bind(sconcat("sext i32 ", sconcat(af, " to i64"))) let a64 = emit_bind(sconcat("sext i32 ", sconcat(af, " to i64")))
let b64 = emit_bind(sconcat("sext i32 ", sconcat(bf, " 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 { fn emit_call(e: Node) -> Val {
let name = e.a.s 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, "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, "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, "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, "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, "len") { return emit_len(e) }
if streq(name, "push") { return emit_push(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, "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, "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_intrinsic(name) { return emit_intrinsic(name, e) }
if is_intrinsic2(name) { return emit_intrinsic2(name, e) } if is_intrinsic2(name) { return emit_intrinsic2(name, e) }
if is_math_builtin(name) { return emit_math_builtin(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 args = new []ptr
let atys = new []ptr let atys = new []ptr
let i = 0 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) let rl = llty(fn2.ty)
emit(" ") emit(" ")
let rreg = "0" let rreg = "0"
if not streq(rl, "void") { rreg = nreg() emit(rreg) emit(" = ") } if not streq(rl, "void") { rreg = nreg(); emit(rreg); emit(" = ") }
emit("call ") emit(rl) emit(" @fn_") emit(cname) emit("(") emit("call "); emit(rl); emit(" @fn_"); emit(cname); emit("(")
i = 0 i = 0
while i < len(args) { while i < len(args) {
if i > 0 { emit(", ") } if i > 0 { emit(", ") }
emit(llty(atys[i])) emit(" ") emit(args[i]) emit(llty(atys[i])); emit(" "); emit(args[i])
i = i + 1 i = i + 1
} }
emit(") emit(")\n")
")
return val(rreg, fn2.ty) 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") } if is_ui_ident(e.s) { return val(itoa(ui_index_of(e.s)), "int") }
perr(sconcat("unknown identifier ", e.s)) 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_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_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_CALL { return emit_call(e) }
if e.kind == E_BIN { return emit_bin(e) } if e.kind == E_BIN { return emit_bin(e) }
if e.kind == E_UN { if e.kind == E_UN {

View file

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

View file

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

View file

@ -19,9 +19,9 @@ fn is_intrinsic(name: ptr) -> bool {
return false return false
} }
# emit " <r> = <rest>\n" and return r # 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 { fn emit_intrinsic(name: ptr, e: Node) -> Val {
g_intrin_ok = true 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") return val(emit_bind(sconcat("call ptr @malloc(i64 ", sconcat(w, ")"))), "ptr")
} }
if streq(name, "mem_realloc") { 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"))) 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") return val(emit_bind(sconcat("call ptr @realloc(ptr ", sconcat(p, sconcat(", i64 ", sconcat(w, ")"))))), "ptr")
} }
if streq(name, "peek8") { 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 a = emit_gep_i8(p, i)
let b = emit_bind(sconcat("load i8, ptr ", a)) let b = emit_bind(sconcat("load i8, ptr ", a))
return val(emit_bind(sconcat("zext i8 ", sconcat(b, " to i32"))), "int") return val(emit_bind(sconcat("zext i8 ", sconcat(b, " to i32"))), "int")
} }
if streq(name, "poke8") { 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 a = emit_gep_i8(p, i)
let t = emit_bind(sconcat("trunc i32 ", sconcat(v, " to i8"))) 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") return val("0", "void")
} }
if streq(name, "ptr_is_null") { 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") return val(emit_bind(sconcat("zext i1 ", sconcat(c, " to i32"))), "bool")
} }
if streq(name, "file_open") { 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") 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") { 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 w = emit_bind(sconcat("zext i32 ", sconcat(n, " to i64")))
let fn2 = "@fread" let fn2 = "@fread"
if streq(name, "file_write") { fn2 = "@fwrite" } 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") return val(emit_bind(sconcat("trunc i64 ", sconcat(r, " to i32"))), "int")
} }
if streq(name, "file_seek") { 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"))) 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") 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") { if streq(name, "file_close") {
let f = arg_code(e, 0) 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") return val("0", "void")
} }
if streq(name, "print_str") { if streq(name, "print_str") {
let s = arg_code(e, 0) 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") return val("0", "void")
} }
if streq(name, "print_int") { if streq(name, "print_int") {
let n = arg_code(e, 0) 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") return val("0", "void")
} }
if streq(name, "os_argc") { return val(emit_bind("load i32, ptr @L_argc"), "int") } 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") { if streq(name, "os_exit") {
let n = arg_code(e, 0) 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") emit(" unreachable\n")
g_term = true g_term = true
return val("0", "void") return val("0", "void")
@ -114,9 +114,9 @@ fn emit_intrinsic(name: ptr, e: Node) -> Val {
let n = arg_code(e, 0) let n = arg_code(e, 0)
return val(emit_bind(sconcat("call ptr @getenv(ptr ", sconcat(n, ")"))), "str") 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") { 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" let opc = "shl"
if streq(name,"shr") { opc = "lshr" } if streq(name,"shr") { opc = "lshr" }
if streq(name,"band") { opc = "and" } 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") return val(emit_bind(sconcat(opc, sconcat(" i32 ", sconcat(a, sconcat(", ", b))))), "int")
} }
if streq(name, "peek32") { 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() 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") return val(emit_bind(sconcat("load i32, ptr ", g)), "int")
} }
if streq(name, "poke32") { 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() 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")
emit(" store i32 ") emit(v) emit(", ptr ") emit(g) emit("\n") emit(" store i32 "); emit(v); emit(", ptr "); emit(g); emit("\n")
return val("0", "void") return val("0", "void")
} }
g_intrin_ok = false g_intrin_ok = false

View file

@ -18,29 +18,29 @@ fn is_intrinsic2(name: ptr) -> bool {
fn emit_intrinsic2(name: ptr, e: Node) -> Val { fn emit_intrinsic2(name: ptr, e: Node) -> Val {
if streq(name, "mem_free") { 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") { 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"))) 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") return val("0", "void")
} }
if streq(name, "mem_set") { 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"))) 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") return val("0", "void")
} }
if streq(name, "ptr_add") { 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() 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") return val(g, "ptr")
} }
if streq(name, "read_byte") { return val(emit_bind("call i32 @getchar()"), "int") } if streq(name, "read_byte") { return val(emit_bind("call i32 @getchar()"), "int") }
if streq(name, "write_byte") { 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") { if streq(name, "str_len") {
let s = arg_code(e, 0) 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") return val(emit_bind(sconcat("trunc i64 ", sconcat(r, " to i32"))), "int")
} }
if streq(name, "peekf") { if streq(name, "peekf") {
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") 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") return val(emit_bind(sconcat("load i32, ptr ", g)), "fixed")
} }
if streq(name, "pokef") { if streq(name, "pokef") {
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") 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(" store i32 "); emit(v); emit(", ptr "); emit(g); emit("\n")
return val("0", "void") return val("0", "void")
} }
if streq(name, "peekp") { if streq(name, "peekp") {
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 ptr, ptr ") emit(p) emit(", i32 ") emit(i) emit("\n") 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") return val(emit_bind(sconcat("load ptr, ptr ", g)), "ptr")
} }
if streq(name, "pokep") { if streq(name, "pokep") {
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 ptr, ptr ") emit(p) emit(", i32 ") emit(i) emit("\n") 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") emit(" store ptr "); emit(v); emit(", ptr "); emit(g); emit("\n")
return val("0", "void") 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_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, "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, "is_windowed") { if g_windowed { return val("1", "bool") }; return val("0", "bool") }
if streq(name, "game_title") { return val("@.gametitle", "str") } if streq(name, "game_title") { return val("@.gametitle", "str") }
# windowing hooks — declared external; only reached on the is_windowed() branch # 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_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_running") { return val(emit_bind("call i32 @win_running()"), "bool") }
if streq(name, "win_open") { 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) 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") 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") return val("0", "void")
} }
if streq(name, "win_present") { if streq(name, "win_present") {
let a = arg_code(e, 0) let b = arg_code(e, 1) let c = arg_code(e, 2) 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") emit(" call void @win_present(ptr "); emit(a); emit(", i32 "); emit(b); emit(", i32 "); emit(c); emit(")\n")
return val("0", "void") 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") return val("0", "void")
} }

View file

@ -13,16 +13,16 @@ fn emit_machine(st: Node) -> void {
let state = st.kids[i] let state = st.kids[i]
let v = emit_expr(state.b) let v = emit_expr(state.b)
let c = emit_bind(sconcat("icmp eq i32 ", sconcat(s, sconcat(", ", v.code)))) let c = emit_bind(sconcat("icmp eq i32 ", sconcat(s, sconcat(", ", v.code))))
let body = lbl("sbody") let nxt = lbl("sarm") let body = lbl("sbody"); let nxt = lbl("sarm")
emit(" br i1 ") emit(c) emit(", label %") emit(body) emit(", label %") emit(nxt) emit("\n") emit(" br i1 "); emit(c); emit(", label %"); emit(body); emit(", label %"); emit(nxt); emit("\n")
emit(body) emit(":\n") g_term = false emit(body); emit(":\n"); g_term = false
emit_block(state.a) emit_block(state.a)
if not g_term { emit(" br label %") emit(endl) emit("\n") } if not g_term { emit(" br label %"); emit(endl); emit("\n") }
emit(nxt) emit(":\n") g_term = false emit(nxt); emit(":\n"); g_term = false
i = i + 1 i = i + 1
} }
if not g_term { emit(" br label %") emit(endl) emit("\n") } if not g_term { emit(" br label %"); emit(endl); emit("\n") }
emit(endl) emit(":\n") g_term = false emit(endl); emit(":\n"); g_term = false
nmach = nmach - 1 nmach = nmach - 1
} }
@ -31,9 +31,9 @@ fn emit_become(st: Node) -> void {
let m = mach_stk[nmach - 1] let m = mach_stk[nmach - 1]
let target: Node = ptr_null() let target: Node = ptr_null()
let i = 0 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)) } if ptr_is_null(target) { perr(sconcat("become: no state ", st.s)) }
let regv = emit_expr(m.a) let regv = emit_expr(m.a)
let sv = emit_expr(target.b) 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") 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") { 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" let op = "slt"
if streq(name, "max") { op = "sgt" } if streq(name, "max") { op = "sgt" }
let c = emit_bind(sconcat("icmp ", sconcat(op, sconcat(" i32 ", sconcat(a.code, sconcat(", ", b.code)))))) 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") 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)) # 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 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 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)))) 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) { while f < len(s.kids) {
let fd = s.kids[f] let fd = s.kids[f]
let addr = nreg() let addr = nreg()
emit(" ") emit(addr) emit(" = getelementptr inbounds %Str_") emit(name) emit(" "); emit(addr); emit(" = getelementptr inbounds %Str_"); emit(name)
emit(", ptr ") emit(obj) emit(", i32 0, i32 ") emit(itoa(f)) emit("\n") emit(", ptr "); emit(obj); emit(", i32 0, i32 "); emit(itoa(f)); emit("\n")
let lt = llty(fd.ty) let lt = llty(fd.ty)
let v = "0" let v = "0"
if streq(lt, "ptr") { v = "null" } if streq(lt, "ptr") { v = "null" }
if not ptr_is_null(fd.a) { let dv = emit_expr(fd.a) v = dv.code } 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") emit(" store "); emit(lt); emit(" "); emit(v); emit(", ptr "); emit(addr); emit("\n")
f = f + 1 f = f + 1
} }
return val(obj, name) return val(obj, name)
@ -31,19 +31,19 @@ fn emit_new_struct(name: ptr) -> Val {
fn emit_new_slice(ty: ptr) -> Val { fn emit_new_slice(ty: ptr) -> Val {
let sz = emit_sizeof("%LSlice") let sz = emit_sizeof("%LSlice")
let h = emit_bind(sconcat("call ptr @malloc(i64 ", sconcat(sz, ")"))) 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") 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") 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") 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") 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") 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") emit(" store i32 0, ptr "); emit(d2); emit("\n")
return val(h, ty) return val(h, ty)
} }
fn slice_field(h: ptr, i: int) -> ptr { fn slice_field(h: ptr, i: int) -> ptr {
let r = nreg() let r = nreg()
emit(" ") emit(r) emit(" = getelementptr inbounds %LSlice, ptr ") emit(h) emit(" "); emit(r); emit(" = getelementptr inbounds %LSlice, ptr "); emit(h)
emit(", i32 0, i32 ") emit(itoa(i)) emit("\n") emit(", i32 0, i32 "); emit(itoa(i)); emit("\n")
return r return r
} }
@ -58,13 +58,13 @@ fn emit_push(e: Node) -> Val {
let el = slice_elem(s.ty) let el = slice_elem(s.ty)
let elt = llty(el) let elt = llty(el)
let h = s.code 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 l = emit_bind(sconcat("load i32, ptr ", lp))
let c = emit_bind(sconcat("load i32, ptr ", cp)) let c = emit_bind(sconcat("load i32, ptr ", cp))
let full = emit_bind(sconcat("icmp sge i32 ", sconcat(l, sconcat(", ", c)))) let full = emit_bind(sconcat("icmp sge i32 ", sconcat(l, sconcat(", ", c))))
let grow = lbl("grow") let put = lbl("put") let grow = lbl("grow"); let put = lbl("put")
emit(" br i1 ") emit(full) emit(", label %") emit(grow) emit(", label %") emit(put) emit("\n") emit(" br i1 "); emit(full); emit(", label %"); emit(grow); emit(", label %"); emit(put); emit("\n")
emit(grow) emit(":\n") emit(grow); emit(":\n")
let dbl = emit_bind(sconcat("mul i32 ", sconcat(c, ", 2"))) let dbl = emit_bind(sconcat("mul i32 ", sconcat(c, ", 2")))
let isz = emit_bind(sconcat("icmp eq i32 ", sconcat(c, ", 0"))) 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)))) 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 bytes = emit_bind(sconcat("mul i64 ", sconcat(ncw, sconcat(", ", esz))))
let old = emit_bind(sconcat("load ptr, ptr ", dp)) let old = emit_bind(sconcat("load ptr, ptr ", dp))
let nd = emit_bind(sconcat("call ptr @realloc(ptr ", sconcat(old, sconcat(", i64 ", sconcat(bytes, ")"))))) 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 ptr "); emit(nd); emit(", ptr "); emit(dp); emit("\n")
emit(" store i32 ") emit(nc) emit(", ptr ") emit(cp) emit("\n") emit(" store i32 "); emit(nc); emit(", ptr "); emit(cp); emit("\n")
emit(" br label %") emit(put) emit("\n") emit(" br label %"); emit(put); emit("\n")
emit(put) emit(":\n") emit(put); emit(":\n")
let v = emit_expr(e.kids[1]) let v = emit_expr(e.kids[1])
let data = emit_bind(sconcat("load ptr, ptr ", dp)) let data = emit_bind(sconcat("load ptr, ptr ", dp))
let slot = nreg() let slot = nreg()
emit(" ") emit(slot) emit(" = getelementptr inbounds ") emit(elt) emit(", ptr ") emit(data) emit(", i32 ") emit(l) 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") emit(" store "); emit(elt); emit(" "); emit(v.code); emit(", ptr "); emit(slot); emit("\n")
let l1 = emit_bind(sconcat("add i32 ", sconcat(l, ", 1"))) 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") return val("0", "void")
} }

View file

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

View file

@ -6,8 +6,8 @@
var g_iok: int = 0 var g_iok: int = 0
fn emit_io(fn2: ptr, p: ptr, bytes: ptr) -> void { fn emit_io(fn2: ptr, p: ptr, bytes: ptr) -> void {
let r = sconcat("%io", itoa(g_iok)) g_iok = g_iok + 1 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") 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 { fn emit_snapshot_blocks(fn2: ptr) -> void {
@ -15,19 +15,19 @@ fn emit_snapshot_blocks(fn2: ptr) -> void {
let me = itoa(MAX_ENT) let me = itoa(MAX_ENT)
emit_io(fn2, "@L_entc", "4") emit_io(fn2, "@L_entc", "4")
emit_io(fn2, "@L_freen", "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_alive", "%nalive")
emit_io(fn2, "@L_freelist", "%nalive") emit_io(fn2, "@L_freelist", "%nalive")
emit_io(fn2, "@L_kind", "%nalive") emit_io(fn2, "@L_kind", "%nalive")
let i = 0 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 let ci = 0
i = 0 i = 0
while i < len(prog) { while i < len(prog) {
if prog[i].kind == N_COMP { if prog[i].kind == N_COMP {
let c = prog[i].s let c = prog[i].s
let csz = sconcat("%csz", itoa(ci)) 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("@S_", c), csz)
emit_io(fn2, sconcat("@H_", c), me) emit_io(fn2, sconcat("@H_", c), me)
ci = ci + 1 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 me = itoa(MAX_ENT)
let c = find_comp(comp) let c = find_comp(comp)
if ptr_is_null(c) { perr(sconcat("spawn: unknown component ", 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") 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") emit(" store i8 1, ptr "); emit(hp); emit("\n")
let slot = nreg() 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 # defaults
let f = 0 let f = 0
while f < len(c.kids) { while f < len(c.kids) {
let fd = c.kids[f] let fd = c.kids[f]
let addr = nreg() 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 lt = llty(fd.ty)
let v = "0" let v = "0"
if streq(lt, "ptr") { v = "null" } if streq(lt, "ptr") { v = "null" }
if not ptr_is_null(fd.a) { let dv = emit_expr(fd.a) v = dv.code } 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") emit(" store "); emit(lt); emit(" "); emit(v); emit(", ptr "); emit(addr); emit("\n")
f = f + 1 f = f + 1
} }
# per-spawn overrides # 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) let fidx = field_index(c, fi.s)
if fidx >= 0 { if fidx >= 0 {
let addr = nreg() 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) 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 j = j + 1
} }
@ -44,25 +44,25 @@ fn emit_spawn(st: Node) -> void {
let ak = find_arch_id(st.s) let ak = find_arch_id(st.s)
if ak > 0 { if ak > 0 {
let me = itoa(MAX_ENT) 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") 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") emit(" store i32 "); emit(itoa(ak)); emit(", ptr "); emit(kp); emit("\n")
let arch = find_arch(st.s) let arch = find_arch(st.s)
let c = 0 let c = 0
while c < len(arch.kids) { while c < len(arch.kids) {
let cn = arch.kids[c].s let cn = arch.kids[c].s
let rec = ptr_null() let rec = ptr_null()
let i = 0 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) emit_init_component(e, cn, rec)
c = c + 1 c = c + 1
} }
} else { } else {
let i = 0 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 { fn emit_despawn(st: Node) -> void {
let v = emit_expr(st.a) 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` # store `val` (llvm type `lt`) into address `addr`
fn store_at(lt: ptr, v: ptr, addr: ptr) -> void { 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 { fn emit_assign(st: Node) -> void {
@ -22,15 +22,15 @@ fn emit_assign(st: Node) -> void {
let ty = "int" let ty = "int"
if t.kind == E_ID { if t.kind == E_ID {
let li = loc_find(t.s) 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 { else {
let g = find_global(t.s) let g = find_global(t.s)
if ptr_is_null(g) { perr(sconcat("assign to unknown ", 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 { } else {
if t.kind == E_MEMBER { addr = emit_member_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 { if t.kind == E_INDEX { addr = emit_index_addr(t); ty = g_addr_ty }
else { perr("bad assignment target") } } else { perr("bad assignment target") } }
} }
let lt = llty(ty) let lt = llty(ty)
@ -51,34 +51,34 @@ fn emit_if(st: Node) -> void {
let c = emit_expr(st.a) let c = emit_expr(st.a)
let cc = emit_bind(sconcat("icmp ne i32 ", sconcat(c.code, ", 0"))) let cc = emit_bind(sconcat("icmp ne i32 ", sconcat(c.code, ", 0")))
let has_else = not ptr_is_null(st.c) let has_else = not ptr_is_null(st.c)
let tl = lbl("then") let el = lbl("else") let en = lbl("ifend") 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") } 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") } else { emit(" br i1 "); emit(cc); emit(", label %"); emit(tl); emit(", label %"); emit(en); emit("\n") }
emit(tl) emit(":\n") g_term = false emit(tl); emit(":\n"); g_term = false
emit_block(st.b) 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 { 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 # 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 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 { fn emit_while(st: Node) -> void {
let cl = lbl("wcond") let bl = lbl("wbody") let en = lbl("wend") let cl = lbl("wcond"); let bl = lbl("wbody"); let en = lbl("wend")
emit(" br label %") emit(cl) emit("\n") emit(" br label %"); emit(cl); emit("\n")
emit(cl) emit(":\n") emit(cl); emit(":\n")
let c = emit_expr(st.a) let c = emit_expr(st.a)
let cc = emit_bind(sconcat("icmp ne i32 ", sconcat(c.code, ", 0"))) 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(" br i1 "); emit(cc); emit(", label %"); emit(bl); emit(", label %"); emit(en); emit("\n")
emit(bl) emit(":\n") g_term = false emit(bl); emit(":\n"); g_term = false
loop_push(cl, en) loop_push(cl, en)
emit_block(st.b) emit_block(st.b)
loop_pop() loop_pop()
if not g_term { emit(" br label %") emit(cl) emit("\n") } if not g_term { emit(" br label %"); emit(cl); emit("\n") }
emit(en) emit(":\n") g_term = false emit(en); emit(":\n"); g_term = false
} }
fn emit_for(st: Node) -> void { fn emit_for(st: Node) -> void {
@ -86,24 +86,24 @@ fn emit_for(st: Node) -> void {
let lo = emit_expr(st.a) let lo = emit_expr(st.a)
store_at("i32", lo.code, slot) store_at("i32", lo.code, slot)
loc_push(st.s, slot, "int") loc_push(st.s, slot, "int")
let cl = lbl("fcond") let bl = lbl("fbody") let ct = lbl("fcont") let en = lbl("fend") let cl = lbl("fcond"); let bl = lbl("fbody"); let ct = lbl("fcont"); let en = lbl("fend")
emit(" br label %") emit(cl) emit("\n") emit(" br label %"); emit(cl); emit("\n")
emit(cl) emit(":\n") emit(cl); emit(":\n")
let iv = emit_bind(sconcat("load i32, ptr ", slot)) let iv = emit_bind(sconcat("load i32, ptr ", slot))
let hi = emit_expr(st.b) let hi = emit_expr(st.b)
let cc = emit_bind(sconcat("icmp slt i32 ", sconcat(iv, sconcat(", ", hi.code)))) 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(" br i1 "); emit(cc); emit(", label %"); emit(bl); emit(", label %"); emit(en); emit("\n")
emit(bl) emit(":\n") g_term = false emit(bl); emit(":\n"); g_term = false
loop_push(ct, en) loop_push(ct, en)
emit_block(st.c) emit_block(st.c)
loop_pop() loop_pop()
if not g_term { emit(" br label %") emit(ct) emit("\n") } if not g_term { emit(" br label %"); emit(ct); emit("\n") }
emit(ct) emit(":\n") emit(ct); emit(":\n")
let i2 = emit_bind(sconcat("load i32, ptr ", slot)) let i2 = emit_bind(sconcat("load i32, ptr ", slot))
let i3 = emit_bind(sconcat("add i32 ", sconcat(i2, ", 1"))) let i3 = emit_bind(sconcat("add i32 ", sconcat(i2, ", 1")))
store_at("i32", i3, slot) store_at("i32", i3, slot)
emit(" br label %") emit(cl) emit("\n") emit(" br label %"); emit(cl); emit("\n")
emit(en) emit(":\n") g_term = false emit(en); emit(":\n"); g_term = false
} }
fn emit_return(st: Node) -> void { fn emit_return(st: Node) -> void {
@ -117,7 +117,7 @@ fn emit_return(st: Node) -> void {
fn arm_is_default(arm: Node) -> bool { fn arm_is_default(arm: Node) -> bool {
let p = 0 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 return false
} }
@ -136,58 +136,58 @@ fn emit_match(st: Node) -> void {
while p < len(arm.kids) { while p < len(arm.kids) {
let pv = emit_expr(arm.kids[p]) let pv = emit_expr(arm.kids[p])
let c = emit_bind(sconcat("icmp eq i32 ", sconcat(sv.code, sconcat(", ", pv.code)))) 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)))) } else { acc = emit_bind(sconcat("or i1 ", sconcat(acc, sconcat(", ", c)))) }
p = p + 1 p = p + 1
} }
let bodyl = lbl("mbody") let nextl = lbl("marm") let bodyl = lbl("mbody"); let nextl = lbl("marm")
emit(" br i1 ") emit(acc) emit(", label %") emit(bodyl) emit(", label %") emit(nextl) emit("\n") emit(" br i1 "); emit(acc); emit(", label %"); emit(bodyl); emit(", label %"); emit(nextl); emit("\n")
emit(bodyl) emit(":\n") g_term = false emit(bodyl); emit(":\n"); g_term = false
emit_block(arm.a) emit_block(arm.a)
if not g_term { emit(" br label %") emit(endl) emit("\n") } if not g_term { emit(" br label %"); emit(endl); emit("\n") }
emit(nextl) emit(":\n") g_term = false emit(nextl); emit(":\n"); g_term = false
} }
i = i + 1 i = i + 1
} }
if not ptr_is_null(deflt) { emit_block(deflt.a) } if not ptr_is_null(deflt) { emit_block(deflt.a) }
if not g_term { emit(" br label %") emit(endl) emit("\n") } if not g_term { emit(" br label %"); emit(endl); emit("\n") }
emit(endl) emit(":\n") g_term = false emit(endl); emit(":\n"); g_term = false
} }
fn emit_stmt(st: Node) -> void { fn emit_stmt(st: Node) -> void {
if st.kind == S_LET { if st.kind == S_LET {
let ty = st.ty let ty = st.ty
if ptr_is_null(ty) { let v0 = emit_expr(st.a) ty = v0.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 } 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)) let slot = emit_alloca(llty(ty))
if ptr_is_null(st.a) { if ptr_is_null(st.a) {
let z = "0" let z = "0"
if streq(llty(ty), "ptr") { z = "null" } if streq(llty(ty), "ptr") { z = "null" }
store_at(llty(ty), z, slot) 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) loc_push(st.s, slot, ty)
return return
} }
if st.kind == S_ASSIGN { emit_assign(st) return } if st.kind == S_ASSIGN { emit_assign(st); return }
if st.kind == S_IF { emit_if(st) return } if st.kind == S_IF { emit_if(st); return }
if st.kind == S_WHILE { emit_while(st) return } if st.kind == S_WHILE { emit_while(st); return }
if st.kind == S_FOR { emit_for(st) return } if st.kind == S_FOR { emit_for(st); return }
if st.kind == S_RETURN { emit_return(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_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_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_MATCH { emit_match(st); return }
if st.kind == S_QUERY { emit_query(st) return } if st.kind == S_QUERY { emit_query(st); return }
if st.kind == S_SPAWN { emit_spawn(st) return } if st.kind == S_SPAWN { emit_spawn(st); return }
if st.kind == S_DESPAWN { emit_despawn(st) return } if st.kind == S_DESPAWN { emit_despawn(st); return }
if st.kind == S_MACHINE { emit_machine(st) return } if st.kind == S_MACHINE { emit_machine(st); return }
if st.kind == S_BECOME { emit_become(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_EXPR { let v = emit_expr(st.a); return }
perr("cannot emit statement") perr("cannot emit statement")
} }
fn loop_push(cont: ptr, brk: ptr) -> void { fn loop_push(cont: ptr, brk: ptr) -> void {
if nloop < len(cnt_lbl) { cnt_lbl[nloop] = cont brk_lbl[nloop] = brk } if nloop < len(cnt_lbl) { cnt_lbl[nloop] = cont; brk_lbl[nloop] = brk }
else { push(cnt_lbl, cont) push(brk_lbl, brk) } else { push(cnt_lbl, cont); push(brk_lbl, brk) }
nloop = nloop + 1 nloop = nloop + 1
} }
fn loop_pop() -> void { 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 { fn ui_wtype(w: Node) -> int {
let s = w.s 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, "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, "label") { return 3 }; if streq(s, "button") { return 4 }
if streq(s, "image") { return 5 } if streq(s, "spacer") { return 6 } if streq(s, "image") { return 5 }; if streq(s, "spacer") { return 6 }
return 0 return 0
} }
fn ui_prop(w: Node, key: ptr) -> Node { fn ui_prop(w: Node, key: ptr) -> Node {
let i = 0 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() return ptr_null()
} }
fn ui_flatten(w: Node, parent: int) -> void { fn ui_flatten(w: Node, parent: int) -> void {
let idx = len(uiw) let idx = len(uiw)
push(uiw, w) push(uiw_parent, parent) push(uiw, w); push(uiw_parent, parent)
let i = 0 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 { 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 let i = 0
while i < len(prog) { 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 i = i + 1
} }
} }
fn has_ui() -> bool { fn has_ui() -> bool {
let i = 0 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 return false
} }
# index of a UI_<name>: a ui block's root, or a widget's id= # index of a UI_<name>: a ui block's root, or a widget's id=
fn ui_index_of(nm: ptr) -> int { fn ui_index_of(nm: ptr) -> int {
let s = substr(nm, 3, slen(nm) - 3) # strip "UI_" 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 let i = 0
while i < len(prog) { 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 = i + 1
} }
i = 0 i = 0
@ -61,24 +61,24 @@ fn is_ui_ident(nm: ptr) -> bool {
} }
fn ll_ui_set(idx: int, key: int, val: ptr) -> void { 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 { 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,"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,"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,"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,"inset") { return 16 }; if streq(k,"focus") { return 17 }
return 0 - 1 return 0 - 1
} }
fn emit_ui_build() -> void { fn emit_ui_build() -> void {
ui_flatten_all() 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() let fbody = buf_new()
falloc = buf_new() falloc = buf_new()
let saved = code let saved = code
code = fbody 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 let i = 0
while i < len(uiw) { while i < len(uiw) {
let w = uiw[i] let w = uiw[i]
@ -91,10 +91,10 @@ fn emit_ui_build() -> void {
let pr = w.b.kids[p] let pr = w.b.kids[p]
let k = pr.s let k = pr.s
let v = pr.a let v = pr.a
if streq(k, "id") { p = p + 1 continue } if streq(k, "id") { p = p + 1; continue }
if streq(k, "text") { if streq(k, "text") {
let sv = emit_expr(v) 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") { } else { if streq(k, "skin") or streq(k, "image") {
let sv = emit_expr(v) let sv = emit_expr(v)
let r = emit_bind(sconcat("call i32 @fn_rt_image_load(ptr ", sconcat(sv.code, ")"))) 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") { } else { if streq(k, "align") {
if v.kind == E_ID { if v.kind == E_ID {
let a = 0 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)) 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 { } else {
let key = ui_prop_key(k) let key = ui_prop_key(k)
if key >= 0 { if key >= 0 {

View file

@ -16,7 +16,7 @@ var toks: []Tok
fn tok_push(kind: int, text: ptr, ival: int, line: int) -> void { fn tok_push(kind: int, text: ptr, ival: int, line: int) -> void {
let t = new Tok 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) push(toks, t)
} }
@ -42,8 +42,8 @@ fn lex(src: ptr) -> void {
let n = slen(src) let n = slen(src)
while i < n { while i < n {
let c = peek8(src, i) 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 == 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 == 32 or c == 9 or c == 13 { i = i + 1; continue }
if c == 35 { # '#' comment to end of line if c == 35 { # '#' comment to end of line
while i < n and peek8(src, i) != 10 { i = i + 1 } while i < n and peek8(src, i) != 10 { i = i + 1 }
continue continue
@ -60,8 +60,8 @@ fn lex(src: ptr) -> void {
if e == 110 { r = 10 } if e == 110 { r = 10 }
if e == 116 { r = 9 } if e == 116 { r = 9 }
if e == 48 { r = 0 } if e == 48 { r = 0 }
poke8(out, j, r) j = j + 1 i = i + 2 poke8(out, j, r); j = j + 1; i = i + 2
} else { poke8(out, j, peek8(src, i)) j = j + 1 i = i + 1 } } else { poke8(out, j, peek8(src, i)); j = j + 1; i = i + 1 }
} }
i = i + 1 i = i + 1
poke8(out, j, 0) poke8(out, j, 0)
@ -77,7 +77,7 @@ fn lex(src: ptr) -> void {
if e == 116 { v = 9 } if e == 116 { v = 9 }
if e == 48 { v = 0 } if e == 48 { v = 0 }
i = i + 2 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 } if peek8(src, i) == 39 { i = i + 1 }
tok_push(TK_INT, ptr_null(), v, line) tok_push(TK_INT, ptr_null(), v, line)
continue continue
@ -99,12 +99,12 @@ fn lex(src: ptr) -> void {
continue continue
} }
let v = 0 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 # 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)) { if i < n and peek8(src, i) == 46 and char_is_digit(peek8(src, i + 1)) {
i = i + 1 i = i + 1
let fnum = 0 let fden = 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 } 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 let bits = shl(v, 16) + shl(fnum, 16) / fden
tok_push(TK_FLOAT, ptr_null(), bits, line) tok_push(TK_FLOAT, ptr_null(), bits, line)
continue continue
@ -118,20 +118,20 @@ fn lex(src: ptr) -> void {
tok_push(TK_ID, substr(src, start, i - start), 0, line) tok_push(TK_ID, substr(src, start, i - start), 0, line)
continue 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 # 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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 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 is_op1(c) { tok_push(TK_OP, substr(src, i, 1), 0, line); i = i + 1; continue }
i = i + 1 # skip anything unrecognised i = i + 1 # skip anything unrecognised
} }
tok_push(TK_EOF, ptr_null(), 0, line) 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 { fn base_name(path: ptr) -> ptr {
let last = 0 - 1 let last = 0 - 1
let i = 0 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)) return substr(path, last + 1, i - (last + 1))
} }
@ -72,7 +72,7 @@ main {
else { if streq(a, "--fmt") { fmt = true } else { if streq(a, "--fmt") { fmt = true }
else { if streq(a, "--save-temps") { save = true } else { if streq(a, "--save-temps") { save = true }
else { if streq(a, "--run") { run = 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 { else {
# an unknown flag is ignored (with a note) rather than mistaken for the # an unknown flag is ignored (with a note) rather than mistaken for the
# input file — '-' is ASCII 45 # input file — '-' is ASCII 45
@ -112,8 +112,8 @@ main {
emit_program() emit_program()
# --emit-llvm, or no binary target: emit IR and stop (stdout when out is null). # --emit-llvm, or no binary target: emit IR and stop (stdout when out is null).
if emit_ir { ir_flush(out) return } if emit_ir { ir_flush(out); return }
if not has_target { ir_flush(ptr_null()) return } if not has_target { ir_flush(ptr_null()); return }
# a run with no explicit -o lands in a temp file # a run with no explicit -o lands in a temp file
if run and ptr_is_null(out) { 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 cur() -> Tok { return toks[pi] }
fn pk(o: int) -> Tok { return toks[pi + o] } 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_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_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 is_kw(v: ptr) -> bool { return is_id(v) }
fn perr(msg: ptr) -> void { fn perr(msg: ptr) -> void {
@ -20,7 +20,7 @@ fn perr(msg: ptr) -> void {
os_exit(1) 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 { fn eat_id() -> ptr {
let t = toks[pi] let t = toks[pi]
if t.kind != TK_ID { perr("expected identifier") } if t.kind != TK_ID { perr("expected identifier") }
@ -36,9 +36,9 @@ fn ptype() -> ptr {
eat_op("]") eat_op("]")
let el = ptype() let el = ptype()
let out = mem_alloc(slen(el) + 3) 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 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) poke8(out, 2 + i, 0)
return out return out
} }
@ -49,23 +49,23 @@ fn ptype() -> ptr {
fn expr() -> Node { return p_or() } fn expr() -> Node { return p_or() }
fn args_call(call: Node) -> void { fn args_call(call: Node) -> void {
eat_op("(") skipnl() eat_op("("); skipnl()
while not is_op(")") { push(call.kids, expr()) skipnl() if is_op(",") { pi = pi + 1 skipnl() } } while not is_op(")") { push(call.kids, expr()); skipnl(); if is_op(",") { pi = pi + 1; skipnl() } }
eat_op(")") eat_op(")")
} }
fn p_primary() -> Node { fn p_primary() -> Node {
let t = toks[pi] 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_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_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_STR { let n = node(E_STR); n.s = t.text; pi = pi + 1; return n }
if t.kind == TK_ID { 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, "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, "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 } 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 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") perr("expected expression")
return node(E_INT) return node(E_INT)
} }
@ -73,46 +73,46 @@ fn p_primary() -> Node {
fn p_postfix() -> Node { fn p_postfix() -> Node {
let e = p_primary() let e = p_primary()
while true { while true {
if is_op(".") { pi = pi + 1 let m = node(E_MEMBER) m.a = e m.s = eat_id() e = m } 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("[") { 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 } } } else { if is_op("(") { let c = node(E_CALL); c.a = e; args_call(c); e = c } else { break } } }
} }
return e return e
} }
fn p_unary() -> Node { 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_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_id("not") { pi = pi + 1; let n = node(E_UN); n.s = "not"; n.a = p_unary(); return n }
return p_postfix() 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 { fn p_mul() -> Node {
let l = p_unary() 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 return l
} }
fn p_add() -> Node { fn p_add() -> Node {
let l = p_mul() 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 return l
} }
fn p_cmp() -> Node { fn p_cmp() -> Node {
let l = p_add() let l = p_add()
while is_op("<") or is_op("<=") or is_op(">") or is_op(">=") or is_op("==") or is_op("!=") { 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 return l
} }
fn p_and() -> Node { fn p_and() -> Node {
let l = p_cmp() 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 return l
} }
fn p_or() -> Node { fn p_or() -> Node {
let l = p_and() 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 return l
} }
@ -120,17 +120,30 @@ fn p_or() -> Node {
fn record() -> Node { fn record() -> Node {
eat_op("{") eat_op("{")
let r = node(E_REC) let r = node(E_REC)
while true { skipnl() if is_op("}") { break } 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) let fi = node(E_FINIT); fi.s = eat_id(); eat_op("="); fi.a = expr(); push(r.kids, fi)
if is_op(",") { pi = pi + 1 } } if is_op(",") { pi = pi + 1 } }
eat_op("}") return r eat_op("}"); return r
} }
# ---- statements ------------------------------------------------------------ # ---- statements ------------------------------------------------------------
fn block() -> Node { fn block() -> Node {
skipnl() eat_op("{") skipnl(); eat_op("{")
let b = node(N_BLOCK) 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("}") eat_op("}")
return b return b
} }
@ -139,106 +152,108 @@ fn stmt() -> Node {
let t = toks[pi] let t = toks[pi]
if t.kind == TK_ID { if t.kind == TK_ID {
if streq(t.text, "let") { if streq(t.text, "let") {
pi = pi + 1 let n = node(S_LET) n.s = eat_id() 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.ty = ptype() }
if is_op("=") { pi = pi + 1 n.a = expr() } if is_op("=") { pi = pi + 1; n.a = expr() }
return n return n
} }
if streq(t.text, "return") { 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() } if toks[pi].kind != TK_NL and not is_op("}") { n.a = expr() }
return n return n
} }
if streq(t.text, "if") { if streq(t.text, "if") {
pi = pi + 1 let n = node(S_IF) n.a = expr() n.b = block() skipnl() pi = pi + 1; let n = node(S_IF); n.a = expr(); n.b = block()
if is_id("else") { pi = pi + 1 skipnl() 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() } } 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 return n
} }
if streq(t.text, "when") { # `when c { }` — an if with no else 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 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") { if streq(t.text, "for") {
pi = pi + 1 pi = pi + 1
if is_op("(") { return parse_query_for() } 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' 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 return n
} }
if streq(t.text, "spawn") { return parse_spawn() } if streq(t.text, "spawn") { return parse_spawn() }
if streq(t.text, "machine") { if streq(t.text, "machine") {
pi = pi + 1 let n = node(S_MACHINE) n.a = expr() skipnl() eat_op("{") pi = pi + 1; let n = node(S_MACHINE); n.a = expr(); skipnl(); eat_op("{")
while true { skipnl() if is_op("}") { break } while true { skipnl(); if is_op("}") { break }
let stkw = eat_id() # 'state' 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) } 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, "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, "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, "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, "break") { pi = pi + 1; return node(S_BREAK) }
if streq(t.text, "continue") { pi = pi + 1 return node(S_CONTINUE) } if streq(t.text, "continue") { pi = pi + 1; return node(S_CONTINUE) }
if streq(t.text, "match") { 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 { while true {
skipnl() if is_op("}") { break } skipnl(); if is_op("}") { break }
let arm = node(S_MARM) let arm = node(S_MARM)
while true { push(arm.kids, expr()) if is_op(",") { pi = pi + 1 skipnl() continue } break } while true { push(arm.kids, expr()); if is_op(",") { pi = pi + 1; skipnl(); continue }; break }
eat_op("=>") skipnl() eat_op("=>"); skipnl()
if is_op("{") { arm.a = block() } else { let b = node(N_BLOCK) push(b.kids, stmt()) arm.a = b } if is_op("{") { arm.a = block() } else { let b = node(N_BLOCK); push(b.kids, stmt()); arm.a = b }
push(n.kids, arm) push(n.kids, arm)
} }
eat_op("}") return n eat_op("}"); return n
} }
} }
let e = expr() let e = expr()
if is_op("=") or is_op("+=") or is_op("-=") or is_op("*=") or is_op("/=") { 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 ---------------------------------------------------------- # ---- declarations ----------------------------------------------------------
fn parse_struct() -> Node { fn parse_struct() -> Node {
pi = pi + 1 let n = node(N_STRUCT) n.s = eat_id() skipnl() eat_op("{") pi = pi + 1; let n = node(N_STRUCT); n.s = eat_id(); skipnl(); eat_op("{")
while true { skipnl() if is_op("}") { break } while true { skipnl(); if is_op("}") { break }
let f = node(N_FIELD) f.s = eat_id() eat_op(":") f.ty = ptype() let f = node(N_FIELD); f.s = eat_id(); eat_op(":"); f.ty = ptype()
if is_op("=") { pi = pi + 1 f.a = expr() } if is_op("=") { pi = pi + 1; f.a = expr() }
push(n.kids, f) if is_op(",") { pi = pi + 1 } } push(n.kids, f); if is_op(",") { pi = pi + 1 } }
eat_op("}") return n eat_op("}"); return n
} }
fn parse_var() -> Node { fn parse_var() -> Node {
pi = pi + 1 let n = node(N_VAR) n.s = eat_id() eat_op(":") n.ty = ptype() 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() } if is_op("=") { pi = pi + 1; n.a = expr() }
return n return n
} }
fn parse_const() -> Node { 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 return n
} }
fn parse_fn() -> Node { 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(")") { 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 } if is_op(",") { pi = pi + 1 }
} }
eat_op(")") eat_op(")")
n.ty = "void" n.ty = "void"
if is_op("->") { pi = pi + 1 n.ty = ptype() } if is_op("->") { pi = pi + 1; n.ty = ptype() }
n.a = block() n.a = block()
return n 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 # directory part of a path, including the trailing '/', or "" if none
fn dir_of(path: ptr) -> ptr { fn dir_of(path: ptr) -> ptr {
let last = 0 - 1 let last = 0 - 1
let i = 0 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 "" } if last < 0 { return "" }
return substr(path, 0, last + 1) return substr(path, 0, last + 1)
} }
@ -252,7 +267,7 @@ var cur_dir: ptr
fn already_loaded(full: ptr) -> bool { fn already_loaded(full: ptr) -> bool {
let i = 0 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 return false
} }
@ -261,21 +276,21 @@ fn parse_one_decl() -> void {
if is_id("import") { pi = pi + 1 if is_id("import") { pi = pi + 1
let t = toks[pi] let t = toks[pi]
if t.kind != TK_STR { perr("expected \"path\" after import") } 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) do_import(rel)
return return
} }
if is_id("struct") { push(prog, parse_struct()) return } if is_id("struct") { push(prog, parse_struct()); return }
if is_id("component") { push(prog, parse_component()) return } if is_id("component") { push(prog, parse_component()); return }
if is_id("archetype") { push(prog, parse_archetype()) 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("system") or is_id("edge") { push(prog, parse_system()); return }
if is_id("ui") { push(prog, parse_ui()) return } if is_id("ui") { push(prog, parse_ui()); return }
if is_id("var") { push(prog, parse_var()) return } if is_id("var") { push(prog, parse_var()); return }
if is_id("const") { push(prog, parse_const()) 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("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("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("extern") { push(prog, parse_extern()); return }
if is_id("main") { push(prog, parse_main()) return } if is_id("main") { push(prog, parse_main()); return }
perr("expected declaration") perr("expected declaration")
} }
@ -286,13 +301,13 @@ fn do_import(rel: ptr) -> void {
push(loaded_paths, full) push(loaded_paths, full)
let src = read_file(full) let src = read_file(full)
if ptr_is_null(src) { perr(sconcat("cannot open import ", 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) cur_dir = dir_of(full)
lex(src) # resets the global token stream lex(src) # resets the global token stream
pi = 0 pi = 0
skipnl() skipnl()
while toks[pi].kind != TK_EOF { parse_one_decl() skipnl() } while toks[pi].kind != TK_EOF { parse_one_decl(); skipnl() }
toks = saved_toks pi = saved_pi cur_dir = saved_dir toks = saved_toks; pi = saved_pi; cur_dir = saved_dir
} }
# a game (has systems/components) links the Ludic runtime; auto-splice it the # a game (has systems/components) links the Ludic runtime; auto-splice it the
@ -311,8 +326,8 @@ fn parse_program() -> void {
skipnl() skipnl()
g_game_name = "Ludic" g_game_name = "Ludic"
# imports may precede the game block # 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() } 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("{") } if is_id("game") or is_id("module") { pi = pi + 1; g_game_name = eat_id(); skipnl(); eat_op("{") }
while true { while true {
skipnl() skipnl()
if toks[pi].kind == TK_EOF { break } if toks[pi].kind == TK_EOF { break }

View file

@ -3,25 +3,25 @@
# game-construct parsing in compiler/front/parse.c. # game-construct parsing in compiler/front/parse.c.
fn parse_component() -> Node { fn parse_component() -> Node {
pi = pi + 1 let n = node(N_COMP) n.s = eat_id() skipnl() eat_op("{") pi = pi + 1; let n = node(N_COMP); n.s = eat_id(); skipnl(); eat_op("{")
while true { skipnl() if is_op("}") { break } while true { skipnl(); if is_op("}") { break }
let f = node(N_FIELD) f.s = eat_id() eat_op(":") f.ty = ptype() let f = node(N_FIELD); f.s = eat_id(); eat_op(":"); f.ty = ptype()
if is_op("=") { pi = pi + 1 f.a = expr() } if is_op("=") { pi = pi + 1; f.a = expr() }
push(n.kids, f) if is_op(",") { pi = pi + 1 } } push(n.kids, f); if is_op(",") { pi = pi + 1 } }
eat_op("}") return n eat_op("}"); return n
} }
# skip an @annotation or a reads/writes/query/... clause we don't model yet # skip an @annotation or a reads/writes/query/... clause we don't model yet
fn skip_clause() -> void { fn skip_clause() -> void {
if is_op("[") { let depth = 0 if is_op("[") { let depth = 0
while true { if is_op("[") { depth = depth + 1 } if is_op("]") { depth = depth - 1 } while true { if is_op("[") { depth = depth + 1 }; if is_op("]") { depth = depth - 1 }
pi = pi + 1 if depth == 0 { break } } pi = pi + 1; if depth == 0 { break } }
} }
} }
fn parse_system() -> Node { fn parse_system() -> Node {
if is_id("edge") { pi = pi + 1 } # optional `edge` modifier before `system` 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 (..) [..] # clauses: @anno, phase X, reads/writes/needs/uses [..], query (..) [..]
# A `query (vars) [terms] where c` clause desugars to a body wrapped in one # 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 # `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) let qn = node(S_QUERY)
while true { while true {
skipnl() # clauses may span several lines 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 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 } } } 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 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("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("reads") or is_id("writes") or is_id("needs") or is_id("uses") { pi = pi + 1; skip_clause(); continue }
if is_id("query") { 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 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(")") } eat_op(")") }
qn.c = parse_query_tail() qn.c = parse_query_tail()
qn.b = qn.c.a # where-expr (or null) qn.b = qn.c.a # where-expr (or null)
@ -50,7 +50,7 @@ fn parse_system() -> Node {
let body = block() let body = block()
if has_q { if has_q {
qn.a = body qn.a = body
let wrap = node(N_BLOCK) push(wrap.kids, qn) let wrap = node(N_BLOCK); push(wrap.kids, qn)
n.a = wrap n.a = wrap
} else { n.a = body } } else { n.a = body }
return n return n
@ -62,12 +62,12 @@ fn parse_query_tail() -> Node {
eat_op("[") eat_op("[")
let q = node(N_BLOCK) let q = node(N_BLOCK)
while not is_op("]") { 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("}") } 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) } else { let t = node(E_ID); t.s = eat_id(); t.ival = 0; push(q.kids, t) }
if is_op(",") { pi = pi + 1 } if is_op(",") { pi = pi + 1 }
} }
eat_op("]") eat_op("]")
if is_id("where") { pi = pi + 1 q.a = expr() } if is_id("where") { pi = pi + 1; q.a = expr() }
return q return q
} }
@ -75,7 +75,7 @@ fn parse_query_tail() -> Node {
fn parse_query_for() -> Node { fn parse_query_for() -> Node {
let n = node(S_QUERY) let n = node(S_QUERY)
eat_op("(") 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(")") eat_op(")")
let inkw = eat_id() # 'in' let inkw = eat_id() # 'in'
let qkw = eat_id() # 'query' let qkw = eat_id() # 'query'
@ -86,59 +86,59 @@ fn parse_query_for() -> Node {
} }
fn parse_spawn() -> 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 { while true {
skipnl() if is_op("}") { break } skipnl(); if is_op("}") { break }
let ci = node(E_FINIT) ci.s = eat_id() eat_op("=") let ci = node(E_FINIT); ci.s = eat_id(); eat_op("=")
ci.a = record() # { field = val, ... } ci.a = record() # { field = val, ... }
push(n.kids, ci) push(n.kids, ci)
if is_op(",") { pi = pi + 1 } 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) # archetype Name { CompA, CompB } — a named entity kind (bundle of components)
fn parse_archetype() -> Node { fn parse_archetype() -> Node {
pi = pi + 1 let n = node(N_ARCH) n.s = eat_id() skipnl() eat_op("{") pi = pi + 1; let n = node(N_ARCH); n.s = eat_id(); skipnl(); eat_op("{")
while true { skipnl() if is_op("}") { break } while true { skipnl(); if is_op("}") { break }
let c = node(E_ID) c.s = eat_id() push(n.kids, c) let c = node(E_ID); c.s = eat_id(); push(n.kids, c)
if is_op(",") { pi = pi + 1 } skipnl() } if is_op(",") { pi = pi + 1 }; skipnl() }
eat_op("}") return n eat_op("}"); return n
} }
# extern fn name(params) -> T = "symbol" # extern fn name(params) -> T = "symbol"
fn parse_extern() -> Node { fn parse_extern() -> Node {
pi = pi + 1 # 'extern' pi = pi + 1 # 'extern'
let fnkw = eat_id() # 'fn' let fnkw = eat_id() # 'fn'
let n = node(N_EXTERN) n.s = eat_id() eat_op("(") 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) 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 } } if is_op(",") { pi = pi + 1 } }
eat_op(")") eat_op(")")
n.ty = "void" n.ty = "void"
if is_op("->") { pi = pi + 1 n.ty = ptype() } if is_op("->") { pi = pi + 1; n.ty = ptype() }
eat_op("=") eat_op("=")
let t = toks[pi] # "symbol" 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 return n
} }
# ui Name { widget-tree } — parsed into a widget node tree (emitted later) # ui Name { widget-tree } — parsed into a widget node tree (emitted later)
fn parse_widget() -> Node { 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) 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, "=") { 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() skipnl()
if is_op("{") { pi = pi + 1 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("}") } eat_op("}") }
return w return w
} }
fn parse_ui() -> Node { 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 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() skipnl(); eat_op("{"); skipnl()
n.a = parse_widget() n.a = parse_widget()
skipnl() eat_op("}") skipnl(); eat_op("}")
return n return n
} }

View file

@ -24,7 +24,7 @@ fn slen(s: ptr) -> int {
fn substr(src: ptr, start: int, n: int) -> ptr { fn substr(src: ptr, start: int, n: int) -> ptr {
let b = mem_alloc(n + 1) let b = mem_alloc(n + 1)
let i = 0 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) poke8(b, n, 0)
return b 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 # integer -> fresh decimal string
fn itoa(v: int) -> ptr { 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 neg = false
let x = v 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 tmp = mem_alloc(16)
let n = 0 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 let total = n
if neg { total = total + 1 } if neg { total = total + 1 }
let out = mem_alloc(total + 1) let out = mem_alloc(total + 1)
let k = 0 let k = 0
if neg { poke8(out, 0, 45) k = 1 } if neg { poke8(out, 0, 45); k = 1 }
let i = 0 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) poke8(out, total, 0)
return out return out
} }

View file

@ -1,12 +1,12 @@
game T { 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 { main {
print_int(fib(10)) # 55 print_int(fib(10)) # 55
let s = 0 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 print_int(s) # 1+3 = 4
let i = 0 let t = 0 let i = 0; let t = 0
while true { i = i + 1 if i > 5 { break } t = t + i } while true { i = i + 1; if i > 5 { break }; t = t + i }
print_int(t) # 15 print_int(t) # 15
if false and (1/0 == 0) { print_int(999) } else { print_int(1) } # short-circuit: no div by zero 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 { game T {
fn classify(c: int) -> int { 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 return 0
} }
main { 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;
}