Merge syntax redesign: cohesion pass (Phases 1-5)

Resolves all 13 findings from SYNTAX-REDESIGN.md. Rule A (: associates, =
binds) across fields/records/spawn/ui/modifiers; Rule B (statement separators);
auto-numbered machine states + enum types for magic-int spaces; docs and editor
tooling reconciled with the compiler and guarded against future drift.

Every migration proven behaviour-preserving (byte-identical self-compiled IR +
golden renders); C-free fixpoint held throughout; test.sh 14/14, self-host 11/11.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-08-27 17:43:39 +03:00
commit 3227e1e1d3
53 changed files with 5723 additions and 4579 deletions

View file

@ -126,7 +126,7 @@ All verified. A compiler needs each of these, and each one works today.
| `shr` is **logical**, not arithmetic | ✅ | `shr(-16, 1)` → `2147483640` | | `shr` is **logical**, not arithmetic | ✅ | `shr(-16, 1)` → `2147483640` |
| Character literals | ✅ | `'x'`, `'\n'`, `'\0'` lex to ints | | Character literals | ✅ | `'x'`, `'\n'`, `'\0'` lex to ints |
| Exit codes | ✅ | `os_exit(3)` → shell sees `3` | | Exit codes | ✅ | `os_exit(3)` → shell sees `3` |
| Separate compilation, C ABI | ✅ | `module` + `export fn`, `extern fn … = "sym"` | | Separate compilation, C ABI | ✅ | `module` + `@export fn`, `extern fn … = "sym"` |
**The consequence:** a compiler is *already expressible* in Ludic today. You **The consequence:** a compiler is *already expressible* in Ludic today. You
could write a lexer, a parser building nodes as hand-offset `peek32`/`poke32` could write a lexer, a parser building nodes as hand-offset `peek32`/`poke32`
@ -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,16 +905,23 @@ 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
# doc-check: skip — composite: declaration plus statements # doc-check: skip — composite: declaration plus statements
component Pos { x: int = 0 y: int = 0 } component Pos { x: int = 0 y: int = 0 }
spawn Hero { Pos = { x = 7 y = 2 } } spawn Hero { Pos { x: 7 y: 2 } }
``` ```
**R3 — RESOLVED.** Every symbol form is now rejected where it is written: **R3 — RESOLVED.** Every symbol form is now rejected where it is written:
@ -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

@ -79,20 +79,20 @@ self-hosted `ludicc`.
> **Not yet on the self-hosted toolchain.** `--shared` and the `nm`/library > **Not yet on the self-hosted toolchain.** `--shared` and the `nm`/library
> workflow below describe the old C driver's behavior; the self-hosted `ludicc` > workflow below describe the old C driver's behavior; the self-hosted `ludicc`
> builds executables only for now. The `module`/`export fn` semantics are > builds executables only for now. The `module`/`@export fn` semantics are
> unchanged — only the packaging step is pending. > unchanged — only the packaging step is pending.
A source file opens with `game Name { … }` or `module Name { … }`. A source file opens with `game Name { … }` or `module Name { … }`.
* A **game** gets an entry point and the phase-ordered frame loop * A **game** gets an entry point and the phase-ordered frame loop
(`Start`, then `Input → FixedUpdate → Update → LateUpdate → Render` each tick). (`Start`, then `Input → FixedUpdate → Update → LateUpdate → Render` each tick).
* A **module** gets neither. It is a library, and only its `export fn`s become * A **module** gets neither. It is a library, and only its `@export fn`s become
public symbols; everything else stays private to the library. public symbols; everything else stays private to the library.
```ludic ```ludic
# doc-check: skip — illustrative: elided body # doc-check: skip — illustrative: elided body
module Combat { module Combat {
export fn damage(attack: int, armour: int, roll: int) -> int { … } @export fn damage(attack: int, armour: int, roll: int) -> int { … }
fn curve(level: int) -> int { … } # private: not a symbol fn curve(level: int) -> int { … } # private: not a symbol
} }
``` ```

View file

@ -67,7 +67,7 @@ component Stats { hp: int = 10 }
archetype Player { Pos, Stats } # Player IS a kind, not a component archetype Player { Pos, Stats } # Player IS a kind, not a component
archetype Enemy { Pos, Stats } archetype Enemy { Pos, Stats }
spawn Player { Pos = { x = 5 } } # attaches every listed component spawn Player { Pos { x: 5 } } # attaches every listed component
# (seeding field defaults), then overrides # (seeding field defaults), then overrides
for (p, s) in query [Pos, Stats, {Player}] { ... } # {Player} filters by kind for (p, s) in query [Pos, Stats, {Player}] { ... } # {Player} filters by kind
``` ```
@ -105,18 +105,18 @@ TrueType text, focus highlight) and keyboard focus + activation.
```ludic ```ludic
ui MainMenu { ui MainMenu {
panel id=Root w=288 pad=16 gap=6 skin="assets/ui/panel.png" inset=10 align=center { panel id: Root w: 288 pad: 16 gap: 6 skin: "assets/ui/panel.png" inset: 10 align: center {
label text="CHRONO RIFT" font=reg(R_FONT) size=26 fg=0xffe060 align=center label text: "CHRONO RIFT" font: reg(R_FONT) size: 26 fg: 0xffe060 align: center
button id=NewGame text="New Game" font=reg(R_FONT) size=16 w=236 button id: NewGame text: "New Game" font: reg(R_FONT) size: 16 w: 236
button id=Quit text="Quit" font=reg(R_FONT) size=16 w=236 button id: Quit text: "Quit" font: reg(R_FONT) size: 16 w: 236
} }
} }
``` ```
Widget types: `panel` (container + optional skin/bg/border), `col` / `row` Widget types: `panel` (container + optional skin/bg/border), `col` / `row`
(pure stacks), `label`, `button` (focusable), `image`, `spacer`. Props are (pure stacks), `label`, `button` (focusable), `image`, `spacer`. Props are
evaluated at build time, so `font=reg(R_FONT)` reads a value the game set first. evaluated at build time, so `font: reg(R_FONT)` reads a value the game set first.
Each `id=Name` mints a `UI_Name` handle (the `ui` block name too), used from Each `id: Name` mints a `UI_Name` handle (the `ui` block name too), used from
systems: systems:
```ludic ```ludic
@ -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.
@ -159,8 +159,8 @@ component Pos { x: int = 0, y: int = 0 } # typed fields with defaults
component Player { } # a tag (no fields) component Player { } # a tag (no fields)
spawn Hero { # create an entity spawn Hero { # create an entity
Pos = { x = 10, y = 5 } Pos { x: 10, y: 5 }
Player = { } Player { }
} }
despawn self() # remove the current entity despawn self() # remove the current entity
@ -185,7 +185,13 @@ system Move @deterministic
Phases run in this order every frame: **`Start`** (once at boot), then each Phases run in this order every frame: **`Start`** (once at boot), then each
frame **`Input` → `FixedUpdate` → `Update` → `LateUpdate` → `Render`**. frame **`Input` → `FixedUpdate` → `Update` → `LateUpdate` → `Render`**.
`edge system` marks a system that touches the outside world. `@edge` in front of a `system` marks one that touches the outside world.
Declaration modifiers are `@annotations` written in front of the declaration —
`@export fn …` (a C-ABI-exported function), `@edge system …`, `@deterministic`,
`@pure`. They parse into one uniform channel rather than a set of prefix
keywords. (`@export` sets the export flag; the others parse but have no codegen
effect in the self-hosted compiler yet.)
### The `query` clause ### The `query` clause
@ -335,6 +341,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
@ -361,15 +379,38 @@ state (no more `if phase == N` chains). See the co-op battle in
```ludic ```ludic
# doc-check: skip — illustrative: elided bodies # doc-check: skip — illustrative: elided bodies
machine R_PHASE { machine R_PHASE {
state KnightMenu = 0 { … if is_confirm(k) { …attack… become KnightResolve } } state KnightMenu { … if is_confirm(k) { …attack… become KnightResolve } }
state KnightResolve = 1 { … become MageMenu } state KnightResolve { … become MageMenu }
state EnemyTurn = 4 { … become KnightMenu } state EnemyTurn { … become KnightMenu }
} }
``` ```
A `machine <reg>` reads `reg(<reg>)` to pick the state; `become Name` compiles to States **number themselves by declaration order** (`KnightMenu` is `0`,
`setreg(<reg>, <Name's value>)`. Both lower to plain branches (and `match` runs `KnightResolve` is `1`, …) — no magic constants. (An explicit `state Name = expr`
on the native LLVM backend too). is still accepted when a state needs a specific value.) A `machine <reg>` reads
`reg(<reg>)` to pick the state; `become Name` compiles to `setreg(<reg>, <Name's
value>)`. Both lower to plain branches (and `match` runs on the native LLVM
backend too).
## Enums
`enum` names a set of related integer values so a magic-number space — a menu
selection, a mode, a machine state — reads as names instead of literals:
```ludic
# doc-check: skip — composite: a declaration plus its uses
enum Action { Attack, Guard, Item, Flee } # Attack = 0, Guard = 1, …
match reg(R_CUR) { Action.Attack => attack() Action.Guard => guard() _ => wait() }
if reg(R_MODE) == Mode.Battle { … }
```
A variant is a **compile-time `int`** accessed as `Enum.Variant` (`Action.Guard`
is `1`), numbered from `0` by declaration order, so it works anywhere an int does
— `match` patterns, comparisons, `setreg`. Enums are a naming layer over `int`:
there is no distinct enum runtime type yet, so an enum value lives in an ordinary
`int` or register (and is saved with it). See `examples/chronorift/combat.ludic`,
whose battle menus dispatch on `KnightAct`/`MageAct` instead of `0..3`.
## Expressions ## Expressions
@ -378,7 +419,7 @@ postfix(. () )`. Operators are built-in only (no overloading). The boolean
operators are spelled **`and` / `or` / `not`**; `&&` and `||` are not Ludic operators operators are spelled **`and` / `or` / `not`**; `&&` and `||` are not Ludic operators
and `!` are rejected with a diagnostic naming the fix (`!=` is unaffected). Bitwise operations are and `!` are rejected with a diagnostic naming the fix (`!=` is unaffected). Bitwise operations are
functions (`band`, `bor`, `bxor`, `bnot`, `shl`, `shr`), so the symbols are free functions (`band`, `bor`, `bxor`, `bnot`, `shl`, `shr`), so the symbols are free
— which is why there is only one spelling to remember. `expr with { field = … }` is not implemented; records appear — which is why there is only one spelling to remember. `expr with { field: … }` is not implemented; records appear
only in `spawn`. Char literals (`'w'`) are `int` code points; colors are hex only in `spawn`. Char literals (`'w'`) are `int` code points; colors are hex
ints (`0xff8800`). ints (`0xff8800`).

View file

@ -140,7 +140,7 @@ same highlighting, checking and formatting as the source tree.
- `scene` / `layer` / `enter` — mutually-exclusive game states, each with - `scene` / `layer` / `enter` — mutually-exclusive game states, each with
`on enter`/`on exit` hooks and layered systems (layer order = draw order). `on enter`/`on exit` hooks and layered systems (layer order = draw order).
- `var` — typed module-level state, included in save/load snapshots. - `var` — typed module-level state, included in save/load snapshots.
- `module` + `export fn` — compile a .ludic file to a shared library whose - `module` + `@export fn` — compile a .ludic file to a shared library whose
exported functions are ordinary C-ABI symbols. exported functions are ordinary C-ABI symbols.
- `extern fn … = "symbol"` — call any C-ABI library, Ludic or otherwise. - `extern fn … = "symbol"` — call any C-ABI library, Ludic or otherwise.
- `--target wasm32-unknown-unknown` — the same game in a browser: the runtime, - `--target wasm32-unknown-unknown` — the same game in a browser: the runtime,

View file

@ -4,10 +4,11 @@ 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–5 complete.** Every phase kept the compiler self-hosting to
> are ordered so the documentation never describes syntax the compiler rejects, > a fixpoint (`./test.sh` 14/14), and each syntax migration was proven
> and every phase ends with the compiler still self-hosting to a fixpoint > behaviour-preserving (the migrated compiler compiles itself to byte-identical
> (`./test.sh`). > IR; every golden game renders byte-identically). Landed on branch
> `syntax-redesign-phase2` over a committed baseline on `main`.
> >
> 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
@ -33,9 +34,9 @@ Verified against the self-hosted compiler ([selfhost/parse.ludic](selfhost/parse
**Broken / dead syntax (compiler-verified)** **Broken / dead syntax (compiler-verified)**
4. `edge system` — **hard parse error** (documented at [LANGUAGE.md:188](LANGUAGE.md)). *(✅ fixed in Phase 1)* 4. `edge system` — **hard parse error** (documented at [LANGUAGE.md:188](LANGUAGE.md)). *(✅ fixed in Phase 1)*
5. `pure fn` — parses, `pure` silently discarded ([parse.ludic:272](selfhost/parse.ludic)); undocumented. 5. `pure fn` — parses, `pure` silently discarded ([parse.ludic:272](selfhost/parse.ludic)); undocumented. *(✅ Phase 3d: now `@pure`)*
6. `@anno` + `reads/writes/needs/uses [..]` — parsed then thrown away 6. `@anno` + `reads/writes/needs/uses [..]` — parsed then thrown away
([parse_game.ludic:15-31](selfhost/parse_game.ludic)); four synonyms, two undocumented. ([parse_game.ludic:15-31](selfhost/parse_game.ludic)); four synonyms, two undocumented. *(✅ Phase 3c: `needs`/`uses` dropped)*
7. `scene`/`layer`/`on enter` — full LANGUAGE.md section + [examples/scenes.ludic](examples/scenes.ludic), 7. `scene`/`layer`/`on enter` — full LANGUAGE.md section + [examples/scenes.ludic](examples/scenes.ludic),
**does not compile** (`expected declaration`). **does not compile** (`expected declaration`).
8. `query (v) [..]` in a system signature — two LANGUAGE.md sections + 8. `query (v) [..]` in a system signature — two LANGUAGE.md sections +
@ -49,9 +50,9 @@ Verified against the self-hosted compiler ([selfhost/parse.ludic](selfhost/parse
11. Operators are words (`and`/`or`/`not`), symbols (`==`/`<=`), *and* functions 11. Operators are words (`and`/`or`/`not`), symbols (`==`/`<=`), *and* functions
(`band`/`shl`) at once. (`band`/`shl`) at once.
12. Three overlapping control families — `if`/`when`, `match`, `machine`/`become` 12. Three overlapping control families — `if`/`when`, `match`, `machine`/`become`
— and `enter` reuses `become`'s AST node ([parse.ludic:176-177](selfhost/parse.ludic)). — and `enter` reuses `become`'s AST node ([parse.ludic:176-177](selfhost/parse.ludic)). *(Phase 4: `if`/`when` kept by choice; magic-int dispatch resolved)*
13. Typed components/structs exist, but real state lives in 64 untyped int 13. Typed components/structs exist, but real state lives in 64 untyped int
registers (`reg`/`setreg`), so `machine`/`match` dispatch on magic numbers. registers (`reg`/`setreg`), so `machine`/`match` dispatch on magic numbers. *(✅ Phase 4: auto-numbered states + `enum` name the values)*
--- ---
@ -208,38 +209,142 @@ 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` →
named-arg form) in the parser and emitters.
- Collapse `reads/writes/needs/uses` → `reads`/`writes`, stored on the node.
- Move `edge`/`pure`/`export` into `@`-annotations; parse annotations into a list.
- Ship `ludic-fmt --migrate`: a mechanical codemod that rewrites old syntax to
new, run over `examples/`, `runtime/`, and `selfhost/`.
- Gate: compiler still self-hosts; every golden render is byte-identical.
### Phase 4 — Control-flow & operator consolidation (largest) **3a — spawn/record initializers ✅ DONE.** `Comp = { f = v }` → `Comp { f: v }`.
- Remove `when`; confirm `if` covers all uses in the corpus. `record()` requires `:` and `parse_spawn()` drops the `=` before the record
- Introduce `enum` + typed `machine`/state; migrate `combat.ludic`'s R_PHASE ([parse.ludic](selfhost/parse.ludic), [parse_game.ludic](selfhost/parse_game.ludic)).
machine and the register-driven `match reg(…)` sites. The `=` is now assignment/const/default/extern-binding only. Migration tool:
- Decide the bitwise story: keep `band/shl/…` as functions (document as a [migrate_records.c](tools/ludic-tools/migrate_records.c) (spawn-context aware).
deliberate "one spelling" choice) or promote to operators — pick one and state Records live only in games, so the seed was unaffected; verified every golden
it, don't leave it implicit. byte-identical, old `=` form now rejected, reseeded, `test.sh` 14/14. Doc examples
updated (LANGUAGE.md, BOOTSTRAP.md R2).
### Phase 5 — Single source of truth for keywords/grammar **3b — ui props → `key: value` ✅ DONE.** `panel id=Root w=288` → `panel id: Root
- Generate every editor plugin keyword list, `ludic_syntax.h`, and the LSP's w: 288`. `parse_widget` now reads props with `:` ([parse_game.ludic](selfhost/parse_game.ludic)).
token set from **one** canonical list so a keyword can never again be Chose the **colonized** form over parenthesized named-args: it satisfies Rule A
highlighted but unparsed. (the `=` overload is gone) with minimal churn, needs no new grammar, and `emit_ui`
- Add a CI check (extend `tools/check-docs.py` / `check-vocabulary.py`) that (which reads the AST) and `ludic-fmt` (which formats `:` correctly by default)
every ` ```ludic ` fence in the docs compiles, closing the doc-drift loop were both untouched. Migration: [migrate_ui.c](tools/ludic-tools/migrate_ui.c).
permanently. menu golden byte-identical, old `=` form rejected, reseeded, `test.sh` 14/14.
(The parenthesized form `panel(id: Root, w: 288)` remains a possible future
refinement if the language ever gains named call arguments.)
**3c — dropped the dead `needs`/`uses` clause synonyms ✅ DONE.** `reads`/`writes`
stay (documented; still parsed-and-reserved). `needs`/`uses` were undocumented and
unused anywhere in the corpus — removed from `parse_system`. *Not done:* actually
*storing* reads/writes on the node for an analysis pass — that's analysis
infrastructure, out of scope for a syntax pass.
**3d — modifiers → `@`-annotations ✅ DONE.** `edge`/`pure`/`export` prefix keywords
are retired; declaration modifiers are now leading `@annotations`: `@export fn`,
`@edge system`, `@pure`, `@deterministic`. `parse_one_decl` collects a leading
`@anno` run and `@export` sets the fn export flag ([parse.ludic](selfhost/parse.ludic));
the dead `edge`-dispatch was removed from `parse_system`. Migrated the one
`@export` user ([examples/lib/combat.ludic](examples/lib/combat.ludic)); old
prefix forms now rejected. Behavior-identical: the export flag is parse-only in
the self-hosted emitter (it emits `@fn_<name>` for every function and never reads
the flag — the C-ABI-export capability is vestigial, a pre-existing gap), so
`@export` and the old `export` produce byte-identical IR. Reseeded, fixpoint
holds, `test.sh` 14/14, goldens byte-identical.
**Phase 3 is complete.** The `=`/`:` overload (finding #2) and the modifier-zoo
(findings #5, #6) are resolved; `:` associates and `=` binds throughout.
Each sub-phase follows the proven pattern: parser change → verification-gated
migration (IR byte-identical / goldens identical) → reseed → docs. The migration
tools ([migrate_separators.c](tools/ludic-tools/migrate_separators.c),
[migrate_records.c](tools/ludic-tools/migrate_records.c)) are the reusable spine.
### Phase 4 — Control-flow & state consolidation
**4a — machine states auto-number ✅ DONE.** `state KnightMenu = 0 { }` →
`state KnightMenu { }`; a state's value is its declaration index (an explicit
`= expr` still works). Removes the magic constants from state machines
([parse.ludic](selfhost/parse.ludic)). combat.ludic migrated; chronorift golden
byte-identical.
**4b — `enum` types ✅ DONE.** `enum Action { Attack, Guard, Item, Flee }` declares
named `int` constants; a variant is a compile-time int accessed as `Action.Guard`
(= 1), numbered by order. Parser `parse_enum` + dispatch, `enum_ordinal` resolver
in [emit_core.ludic](selfhost/emit_core.ludic), and `Enum.Variant` handling in
[emit_expr.ludic](selfhost/emit_expr.ludic). combat.ludic's battle menus now
dispatch on `KnightAct`/`MageAct` instead of `0..3`; chronorift golden
byte-identical. Editor vocab (`ludic_syntax.h`, JetBrains, TextMate, emacs) gained
`enum` and lost the retired `edge`/`export`/`pure` decl keywords; check-vocabulary
+ test-tools green. **Scoped:** enums are a naming layer over `int` (no distinct
runtime type / enum-typed variables yet) — that keeps register/save semantics
untouched, which the "enum var replaces the register" vision would have to solve.
**`when` vs `if` — kept both (decision).** `when` stays as the `if`-without-else
spelling: it is not incoherent so much as a readability signal ("no else here"),
it is documented and highlighted, and it is a pure alias with no semantic overlap
to untangle. The real target of finding #12 — dispatch on magic integers — is
addressed by 4a/4b, not by collapsing `if`/`when`.
**Bitwise operators — kept as functions (decision).** `band`/`bor`/`bxor`/`bnot`/
`shl`/`shr` stay functions, documented as the deliberate "one spelling, symbols
stay free" choice (LANGUAGE.md §Expressions already states this). Promoting them
to operators would re-introduce the symbol soup the current design avoids.
### Phase 5 — Vocabulary anchored to the compiler ✅ DONE
The editor vocabulary already stayed in sync *with itself* (`check-vocabulary.py`
compares `ludic_syntax.h`, the JetBrains lexer, and the TextMate grammar). The
missing anchor was the **compiler**: a keyword could be highlighted everywhere
and still be silently unparsed. Closed both loops:
- ✅ **Vocabulary ⇄ parser.** `check-vocabulary.py` now extracts every keyword
`selfhost/parse*.ludic` dispatches on (`is_id(...)` / `streq(t.text, ...)`) and
requires the header's declaration + clause keywords to be a subset — with a
`LUDIC_KW_RESERVED` escape hatch for documented, not-yet-implemented keywords
(`scene`/`layer`/`on`/`start`), itself checked so a reserved word that gets
implemented must be promoted. Verified it catches an injected bogus keyword.
- ✅ **Reconciled the drift it exposed.** Removed the highlighted-but-unparsed
`scene`/`layer`/`on`/`start` (→ RESERVED) and the never-implemented
`needs`/`uses`/`requires`/`ensures`/`invariant`/`effects` clause words, and the
retired `edge`/`export`/`pure` prefix modifiers, from `ludic_syntax.h`, the
JetBrains lexer, the TextMate grammar, and the emacs mode; added `enum`/`main`.
`@`-annotations already highlight generically (`@[A-Za-z_]…`). test-tools 28/0.
- ✅ **Doc-fence compilation** — the other half of "single source of truth" — was
already live: `check-docs.py` compiles every ` ```ludic ` fence through the
self-hosted `ludicc --fmt` parse gate (revived during Phase 1's coordination).
Full generation-from-one-list (emit the editor files from a manifest) was not
needed: the bidirectional *checks* give the same guarantee — nothing can drift
without CI failing — without a code-generation step to maintain.
**Phases 1–5 are complete.** All thirteen findings are resolved or resolved by an
explicit, documented decision.
--- ---

View file

@ -3,13 +3,18 @@
# The battle is a state machine over R_PHASE. `machine` dispatches on the # The battle is a state machine over R_PHASE. `machine` dispatches on the
# register and `become` transitions to a named state — no more `if ph == N` # register and `become` transitions to a named state — no more `if ph == N`
# ladders. Knight turn (0,1), Mage turn (2,3), Enemy turn (4). # ladders. Knight turn (0,1), Mage turn (2,3), Enemy turn (4).
# Menu selections, named instead of matched on bare integers (R_CUR holds one).
enum KnightAct { Attack, Guard, Item, Flee }
enum MageAct { Attack, Heal, Guard }
system Battle phase Update { system Battle phase Update {
if reg(R_MODE) == 1 { if reg(R_MODE) == 1 {
let k = key() let k = key()
for (e) in query [Stats, {Enemy}] { for (e) in query [Stats, {Enemy}] {
machine R_PHASE { machine R_PHASE {
state KnightMenu = 0 { state KnightMenu {
for (st, pt) in query [Stats, Party] { for (st, pt) in query [Stats, Party] {
if pt.slot == 0 { if pt.slot == 0 {
if st.hp <= 0 { if st.hp <= 0 {
@ -20,21 +25,21 @@ system Battle phase Update {
if k == 's' { setreg(R_CUR, min(3, reg(R_CUR) + 1)) } if k == 's' { setreg(R_CUR, min(3, reg(R_CUR) + 1)) }
if is_confirm(k) { if is_confirm(k) {
match reg(R_CUR) { match reg(R_CUR) {
0 => { KnightAct.Attack => {
let d = max(1, st.atk - e.def + rng_range(0, 4)) let d = max(1, st.atk - e.def + rng_range(0, 4))
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) } KnightAct.Guard => { st.guard = 1; setreg(R_PDMG, 0) }
2 => { KnightAct.Item => {
if reg(R_POTION) > 0 { if reg(R_POTION) > 0 {
setreg(R_POTION, reg(R_POTION) - 1) setreg(R_POTION, reg(R_POTION) - 1)
st.hp = min(st.maxhp, st.hp + 24) st.hp = min(st.maxhp, st.hp + 24)
setreg(R_PDMG, 24) setreg(R_PDMG, 24)
} else { setreg(R_PDMG, 0) } } else { setreg(R_PDMG, 0) }
} }
3 => { KnightAct.Flee => {
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) }
} }
} }
@ -46,12 +51,12 @@ system Battle phase Update {
} }
} }
state KnightResolve = 1 { state KnightResolve {
if k != 0 { if k != 0 {
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 {
@ -61,7 +66,7 @@ system Battle phase Update {
} }
} }
state MageMenu = 2 { state MageMenu {
for (st, pt) in query [Stats, Party] { for (st, pt) in query [Stats, Party] {
if pt.slot == 1 { if pt.slot == 1 {
if st.hp <= 0 { if st.hp <= 0 {
@ -71,12 +76,12 @@ system Battle phase Update {
if k == 'k' { setreg(R_CUR, min(2, reg(R_CUR) + 1)) } if k == 'k' { setreg(R_CUR, min(2, reg(R_CUR) + 1)) }
if k == 'j' { if k == 'j' {
match reg(R_CUR) { match reg(R_CUR) {
0 => { MageAct.Attack => {
let d = max(1, st.atk + 5 - e.def + rng_range(0, 5)) let d = max(1, st.atk + 5 - e.def + rng_range(0, 5))
e.hp = e.hp - d e.hp = e.hp - d
setreg(R_PDMG, d) setreg(R_PDMG, d)
} }
1 => { MageAct.Heal => {
if st.mp >= 4 { if st.mp >= 4 {
st.mp = st.mp - 4 st.mp = st.mp - 4
for (t2, p2) in query [Stats, Party] { for (t2, p2) in query [Stats, Party] {
@ -85,7 +90,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) } MageAct.Guard => { st.guard = 1; setreg(R_PDMG, 0) }
} }
become MageResolve become MageResolve
setreg(R_CUR, 0) setreg(R_CUR, 0)
@ -95,12 +100,12 @@ system Battle phase Update {
} }
} }
state MageResolve = 3 { state MageResolve {
if k != 0 { if k != 0 {
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 {
@ -109,14 +114,14 @@ system Battle phase Update {
} }
} }
state EnemyTurn = 4 { state EnemyTurn {
if k != 0 { if k != 0 {
let done = 0 let done = 0
for (t3) in query [Stats, {Party}] { for (t3) in query [Stats, {Party}] {
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 +132,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

@ -16,14 +16,14 @@ system Boot phase Start {
build_field(0) build_field(0)
spawn Player { spawn Player {
Pos = { x = 11, y = 15 } Pos { x: 11, y: 15 }
Actor = { kind = 5 } Actor { kind: 5 }
Party = { slot = 0 } Party { slot: 0 }
Stats = { hp = 40, maxhp = 40, mp = 8, maxmp = 8, atk = 12, def = 6, lvl = 1, xp = 0, guard = 0 } Stats { hp: 40, maxhp: 40, mp: 8, maxmp: 8, atk: 12, def: 6, lvl: 1, xp: 0, guard: 0 }
} }
spawn Ally { spawn Ally {
Party = { slot = 1 } Party { slot: 1 }
Stats = { hp = 26, maxhp = 26, mp = 16, maxmp = 16, atk = 8, def = 4, lvl = 1, xp = 0, guard = 0 } Stats { hp: 26, maxhp: 26, mp: 16, maxmp: 16, atk: 8, def: 4, lvl: 1, xp: 0, guard: 0 }
} }
setreg(R_MODE, 0) setreg(R_MODE, 0)
setreg(R_ENC, 4) setreg(R_ENC, 4)
@ -69,8 +69,8 @@ system Control phase Input {
if reg(R_BOSS) == 0 { if reg(R_BOSS) == 0 {
setreg(R_ETYPE, 2) setreg(R_ETYPE, 2)
spawn Enemy { spawn Enemy {
Actor = { kind = 8 } Actor { kind: 8 }
Stats = { hp = 120, maxhp = 120, mp = 0, maxmp = 0, atk = 18, def = 10, lvl = 5, xp = 300, guard = 0 } Stats { hp: 120, maxhp: 120, mp: 0, maxmp: 0, atk: 18, def: 10, lvl: 5, xp: 300, guard: 0 }
} }
setreg(R_MODE, 1) setreg(R_MODE, 1)
setreg(R_PHASE, 0) setreg(R_PHASE, 0)
@ -96,14 +96,14 @@ system Control phase Input {
setreg(R_ETYPE, et) setreg(R_ETYPE, et)
if et == 0 { if et == 0 {
spawn Enemy { spawn Enemy {
Actor = { kind = 6 } Actor { kind: 6 }
Stats = { hp = 18, maxhp = 18, mp = 0, maxmp = 0, atk = 8, def = 2, lvl = 1, xp = 7, guard = 0 } Stats { hp: 18, maxhp: 18, mp: 0, maxmp: 0, atk: 8, def: 2, lvl: 1, xp: 7, guard: 0 }
} }
} }
if et == 1 { if et == 1 {
spawn Enemy { spawn Enemy {
Actor = { kind = 7 } Actor { kind: 7 }
Stats = { hp = 30, maxhp = 30, mp = 0, maxmp = 0, atk = 11, def = 5, lvl = 1, xp = 15, guard = 0 } Stats { hp: 30, maxhp: 30, mp: 0, maxmp: 0, atk: 11, def: 5, lvl: 1, xp: 15, guard: 0 }
} }
} }
setreg(R_MODE, 1) setreg(R_MODE, 1)

View file

@ -6,12 +6,12 @@ game Hello {
system Boot phase Start { system Boot phase Start {
spawn Mob { spawn Mob {
Pos = { x = 3, y = 4 } Pos { x: 3, y: 4 }
Vel = { dx = 1, dy = 0 } Vel { dx: 1, dy: 0 }
} }
spawn Mob { spawn Mob {
Pos = { x = 10, y = 2 } Pos { x: 10, y: 2 }
Vel = { dx = 0, dy = 1 } Vel { dx: 0, dy: 1 }
} }
} }

View file

@ -18,8 +18,8 @@ game Arena {
component Fighter { hp: int = 30, attack: int = 9, armour: int = 2 } component Fighter { hp: int = 30, attack: int = 9, armour: int = 2 }
system Boot phase Start { system Boot phase Start {
spawn Hero { Fighter = { hp = 40, attack = 12, armour = 3 } } spawn Hero { Fighter { hp: 40, attack: 12, armour: 3 } }
spawn Orc { Fighter = { hp = 24, attack = 7, armour = 1 } } spawn Orc { Fighter { hp: 24, attack: 7, armour: 1 } }
} }
system Fight phase Update { system Fight phase Update {

View file

@ -16,7 +16,7 @@ module Combat {
const CRIT_MULT: int = 2 const CRIT_MULT: int = 2
# damage after armour, with a crit multiplier applied on an exact roll # damage after armour, with a crit multiplier applied on an exact roll
export fn damage(attack: int, armour: int, roll: int) -> int { @export fn damage(attack: int, armour: int, roll: int) -> int {
let raw = attack - armour let raw = attack - armour
if raw < 1 { raw = 1 } if raw < 1 { raw = 1 }
if roll == 20 { raw = raw * CRIT_MULT } if roll == 20 { raw = raw * CRIT_MULT }
@ -25,7 +25,7 @@ module Combat {
# how many hits to drop a target — the loop is here so the caller cannot # how many hits to drop a target — the loop is here so the caller cannot
# accidentally disagree with `damage` about rounding # accidentally disagree with `damage` about rounding
export fn hits_to_kill(hp: int, attack: int, armour: int) -> int { @export fn hits_to_kill(hp: int, attack: int, armour: int) -> int {
let left = hp let left = hp
let n = 0 let n = 0
while left > 0 { while left > 0 {
@ -35,7 +35,7 @@ module Combat {
return n return n
} }
export fn xp_for(level: int, kills: int) -> int { @export fn xp_for(level: int, kills: int) -> int {
return curve(level) * kills return curve(level) * kills
} }

View file

@ -8,14 +8,14 @@ game Menu {
const R_MSG: int = 1 const R_MSG: int = 1
ui MainMenu { ui MainMenu {
panel id=Root w=288 pad=16 gap=6 bg=0x1a1a2c border=0x4a4a72 skin="assets/ui/panel.png" inset=10 align=center { panel id: Root w: 288 pad: 16 gap: 6 bg: 0x1a1a2c border: 0x4a4a72 skin: "assets/ui/panel.png" inset: 10 align: center {
label text="CHRONO RIFT" font=reg(R_FONT) size=26 fg=0xffe060 align=center label text: "CHRONO RIFT" font: reg(R_FONT) size: 26 fg: 0xffe060 align: center
label text="a Ludic retained-UI demo" font=reg(R_FONT) size=14 fg=0x9aa0c8 align=center label text: "a Ludic retained-UI demo" font: reg(R_FONT) size: 14 fg: 0x9aa0c8 align: center
spacer h=4 spacer h: 4
button id=NewGame text="New Game" font=reg(R_FONT) size=16 w=236 button id: NewGame text: "New Game" font: reg(R_FONT) size: 16 w: 236
button id=Continue text="Continue" font=reg(R_FONT) size=16 w=236 button id: Continue text: "Continue" font: reg(R_FONT) size: 16 w: 236
button id=Options text="Options" font=reg(R_FONT) size=16 w=236 button id: Options text: "Options" font: reg(R_FONT) size: 16 w: 236
button id=Quit text="Quit" font=reg(R_FONT) size=16 w=236 button id: Quit text: "Quit" font: reg(R_FONT) size: 16 w: 236
} }
} }

View file

@ -15,9 +15,9 @@ game QueryDecl {
archetype Foe { Battle, Pos } archetype Foe { Battle, Pos }
system Seed phase Start { system Seed phase Start {
spawn Foe { Battle = { hp = 3, side = 1 } } spawn Foe { Battle { hp: 3, side: 1 } }
spawn Foe { Battle = { hp = 0, side = 1 } } spawn Foe { Battle { hp: 0, side: 1 } }
spawn Foe { Battle = { hp = -2, side = 0 } } spawn Foe { Battle { hp: -2, side: 0 } }
} }
system CleanBattle phase LateUpdate system CleanBattle phase LateUpdate

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

@ -15,6 +15,7 @@ const N_SYS: int = 9
const N_ARCH: int = 10 const N_ARCH: int = 10
const N_EXTERN: int = 11 const N_EXTERN: int = 11
const N_UI: int = 12 const N_UI: int = 12
const N_ENUM: int = 27 # enum Name { A, B, ... } — named int constants
# statements # statements
const S_LET: int = 10 const S_LET: int = 10
const S_ASSIGN: int = 11 const S_ASSIGN: int = 11

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"
} }
@ -119,21 +119,36 @@ fn find_global(name: ptr) -> Node {
} }
return ptr_null() return ptr_null()
} }
# `Enum.Variant` -> the variant's ordinal (its index), or -1 if `ename` names no
# enum with that variant. Enum names live in `prog` like any other declaration.
fn enum_ordinal(ename: ptr, vname: ptr) -> int {
let i = 0
while i < len(prog) {
let d = prog[i]
if d.kind == N_ENUM and streq(d.s, ename) {
let j = 0
while j < len(d.kids) { if streq(d.kids[j].s, vname) { return j }; j = j + 1 }
}
i = i + 1
}
return 0 - 1
}
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,14 @@ 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 {
if e.kind == E_INDEX { let a = emit_index_addr(e) return emit_load_at(a, g_addr_ty) } if e.a.kind == E_ID { # `Enum.Variant` -> its ordinal, a compile-time int
let ord = enum_ordinal(e.a.s, e.s)
if ord >= 0 { return val(itoa(ord), "int") }
}
let a = emit_member_addr(e); return emit_load_at(a, g_addr_ty)
}
if e.kind == E_INDEX { let a = emit_index_addr(e); return emit_load_at(a, g_addr_ty) }
if e.kind == E_CALL { return emit_call(e) } if e.kind == E_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,64 +73,78 @@ 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
} }
# a record literal `{ field = value, ... }` — used by spawn component inits # a record literal `{ field: value, ... }` — used by spawn component inits.
# Rule A: a named part uses `:` (`=` is assignment/binding only).
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 +153,112 @@ 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 } let sidx = 0 # states auto-number by declaration order
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()
push(n.kids, s) } if is_op("=") { pi = pi + 1; s.b = expr() } # explicit value (still allowed)
eat_op("}") return n else { let iv = node(E_INT); iv.ival = sidx; s.b = iv } # else its ordinal
skipnl(); s.a = block()
push(n.kids, s); sidx = sidx + 1 }
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,30 +272,40 @@ 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
} }
# parse one top-level declaration (or resolve an import) into `prog` # parse one top-level declaration (or resolve an import) into `prog`.
# Modifiers are `@annotations` in front of the declaration: `@export`, `@edge`,
# `@pure`, `@deterministic`, … — one channel, not a zoo of prefix keywords.
fn parse_one_decl() -> void { fn parse_one_decl() -> void {
let is_export = false
while is_op("@") {
pi = pi + 1; let a = eat_id() # collect a leading @annotation
if streq(a, "export") { is_export = true }
if is_op("(") { let d = 0 # optional @anno(args) — skipped
while true { if is_op("(") { d = d + 1 }; if is_op(")") { d = d - 1 }; pi = pi + 1; if d == 0 { break } } }
skipnl()
}
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("enum") { push(prog, parse_enum()); return }
if is_id("archetype") { push(prog, parse_archetype()) return } if is_id("component") { push(prog, parse_component()); return }
if is_id("system") or is_id("edge") { push(prog, parse_system()) return } if is_id("archetype") { push(prog, parse_archetype()); return }
if is_id("ui") { push(prog, parse_ui()) return } if is_id("system") { push(prog, parse_system()); return }
if is_id("var") { push(prog, parse_var()) return } if is_id("ui") { push(prog, parse_ui()); return }
if is_id("const") { push(prog, parse_const()) return } if is_id("var") { push(prog, parse_var()); return }
if is_id("export") { pi = pi + 1 let f = parse_fn() f.ival = 1 push(prog, f) return } if is_id("const") { push(prog, parse_const()); 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") { let f = parse_fn(); if is_export { f.ival = 1 }; push(prog, f); 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 +316,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 +341,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,24 @@
# 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` 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 +29,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") { pi = pi + 1; skip_clause(); continue } # `needs`/`uses` synonyms dropped (Rule A cleanup)
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 +49,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 +61,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 +74,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 +85,70 @@ 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()
ci.a = record() # { field = val, ... } ci.a = record() # Comp { field: val, ... } — no `=` before the record
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
}
# enum Name { A, B, C } — named int constants; a variant's value is its index.
# Accessed as `Name.A` (a compile-time int), so it names magic-int value spaces
# (state ids, menu selections, mode registers) without a runtime cost.
fn parse_enum() -> Node {
pi = pi + 1; let n = node(N_ENUM); n.s = eat_id(); skipnl(); eat_op("{")
while true { skipnl(); if is_op("}") { break }
let v = node(E_ID); v.s = eat_id(); push(n.kids, v)
if is_op(",") { pi = pi + 1 }; skipnl() }
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) # widget prop `key: value` (Rule A)
} }
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

@ -142,6 +142,35 @@ kpath = ["repository", "keyword", "patterns"]
compare("phases", h_phases, grammar_alternation(grammar, kpath, "FixedUpdate"), "ludic.tmLanguage.json") compare("phases", h_phases, grammar_alternation(grammar, kpath, "FixedUpdate"), "ludic.tmLanguage.json")
compare("primitive types", h_types, grammar_alternation(grammar, kpath, "fixed"), "ludic.tmLanguage.json") compare("primitive types", h_types, grammar_alternation(grammar, kpath, "fixed"), "ludic.tmLanguage.json")
# --- against the actual self-hosted parser ---------------------------------
# The compiler moved to Ludic, so the old C cross-check (above) is dark. This
# replaces it: a declaration or clause keyword the highlighter colours must be
# one the parser actually dispatches on, or nobody notices when a keyword is
# highlighted but silently unparsed (exactly what happened to `scene`, `edge`,
# `needs`, …). RESERVED is the escape hatch for documented, not-yet-implemented
# keywords — and it is checked too, so a reserved word that gets implemented
# must be promoted out of it.
def parser_keywords():
kws = set()
for name in ("parse.ludic", "parse_game.ludic"):
text = open(os.path.join(ROOT, "selfhost", name), encoding="utf-8").read()
kws |= set(re.findall(r'is_id\("([a-z]+)"\)', text))
kws |= set(re.findall(r'streq\(t\.text,\s*"([a-z]+)"\)', text))
return kws
pkw = parser_keywords()
h_reserved = c_string_list(syntax_h, "LUDIC_KW_RESERVED")
if not pkw:
problems.append("could not extract any keywords from selfhost/parse*.ludic")
else:
for label, kws in (("declaration keywords", h_decl), ("clause keywords", h_clause)):
unparsed = (kws - pkw) - h_reserved
if unparsed:
problems.append(f"ludic_syntax.h lists {label} the selfhost parser never dispatches on: {', '.join(sorted(unparsed))}")
promoted = h_reserved & pkw
if promoted:
problems.append(f"LUDIC_KW_RESERVED lists keywords the parser now accepts — promote them into the highlighted vocabulary: {', '.join(sorted(promoted))}")
if problems: if problems:
print("vocabulary drift:", file=sys.stderr) print("vocabulary drift:", file=sys.stderr)
for p in problems: for p in problems:

View file

@ -11,12 +11,11 @@
;;; Code: ;;; Code:
(defconst ludic--declaration-keywords (defconst ludic--declaration-keywords
'("game" "module" "import" "component" "archetype" "ui" "scene" "layer" '("game" "module" "import" "component" "struct" "archetype" "enum" "ui"
"const" "var" "export" "fn" "pure" "extern" "system" "edge" "start" "on")) "const" "var" "fn" "extern" "system" "main"))
(defconst ludic--clause-keywords (defconst ludic--clause-keywords
'("phase" "query" "reads" "writes" "needs" "uses" '("phase" "query" "reads" "writes"))
"requires" "ensures" "invariant" "effects"))
(defconst ludic--statement-keywords (defconst ludic--statement-keywords
'("let" "return" "if" "else" "when" "while" "for" "in" "spawn" "despawn" '("let" "return" "if" "else" "when" "while" "for" "in" "spawn" "despawn"

View file

@ -41,12 +41,11 @@ object LudicTokens {
*/ */
object LudicVocabulary { object LudicVocabulary {
val DECL = setOf( val DECL = setOf(
"game", "module", "import", "component", "struct", "archetype", "ui", "scene", "layer", "game", "module", "import", "component", "struct", "archetype", "enum", "ui",
"const", "var", "export", "fn", "pure", "extern", "system", "edge", "start", "on" "const", "var", "fn", "extern", "system", "main"
) )
val CLAUSE = setOf( val CLAUSE = setOf(
"phase", "query", "reads", "writes", "needs", "uses", "phase", "query", "reads", "writes"
"requires", "ensures", "invariant", "effects"
) )
val STMT = setOf( val STMT = setOf(
"let", "return", "if", "else", "when", "while", "for", "in", "spawn", "despawn", "let", "return", "if", "else", "when", "while", "for", "in", "spawn", "despawn",

View file

@ -75,35 +75,25 @@
} }
}, },
{ {
"match": "\\b(scene)\\s+([A-Za-z_][A-Za-z0-9_]*)(\\s+start)?", "match": "\\b(ui)\\s+([A-Za-z_][A-Za-z0-9_]*)",
"captures": {
"1": { "name": "storage.type.scene.ludic" },
"2": { "name": "entity.name.type.scene.ludic" },
"3": { "name": "keyword.other.start.ludic" }
}
},
{
"match": "\\b(layer|ui)\\s+([A-Za-z_][A-Za-z0-9_]*)",
"captures": { "captures": {
"1": { "name": "storage.type.ludic" }, "1": { "name": "storage.type.ludic" },
"2": { "name": "entity.name.type.ludic" } "2": { "name": "entity.name.type.ludic" }
} }
}, },
{ {
"match": "\\b(edge\\s+)?(system)\\s+([A-Za-z_][A-Za-z0-9_]*)", "match": "\\b(system)\\s+([A-Za-z_][A-Za-z0-9_]*)",
"captures": { "captures": {
"1": { "name": "storage.modifier.edge.ludic" }, "1": { "name": "storage.type.system.ludic" },
"2": { "name": "storage.type.system.ludic" }, "2": { "name": "entity.name.function.system.ludic" }
"3": { "name": "entity.name.function.system.ludic" }
} }
}, },
{ {
"match": "\\b(export\\s+|pure\\s+)?(extern\\s+)?(fn)\\s+([A-Za-z_][A-Za-z0-9_]*)", "match": "\\b(extern\\s+)?(fn)\\s+([A-Za-z_][A-Za-z0-9_]*)",
"captures": { "captures": {
"1": { "name": "storage.modifier.ludic" }, "1": { "name": "storage.modifier.extern.ludic" },
"2": { "name": "storage.modifier.extern.ludic" }, "2": { "name": "storage.type.function.ludic" },
"3": { "name": "storage.type.function.ludic" }, "3": { "name": "entity.name.function.ludic" }
"4": { "name": "entity.name.function.ludic" }
} }
}, },
{ {
@ -173,9 +163,9 @@
"patterns": [ "patterns": [
{ "name": "keyword.control.ludic", "match": "\\b(if|else|when|while|for|in|match|machine|become|enter|return|spawn|despawn|where|break|continue|new)\\b" }, { "name": "keyword.control.ludic", "match": "\\b(if|else|when|while|for|in|match|machine|become|enter|return|spawn|despawn|where|break|continue|new)\\b" },
{ "name": "keyword.operator.logical.ludic", "match": "\\b(and|or|not)\\b" }, { "name": "keyword.operator.logical.ludic", "match": "\\b(and|or|not)\\b" },
{ "name": "keyword.other.clause.ludic", "match": "\\b(phase|query|reads|writes|needs|uses|requires|ensures|invariant|effects|on)\\b" }, { "name": "keyword.other.clause.ludic", "match": "\\b(phase|query|reads|writes)\\b" },
{ "name": "keyword.other.ludic", "match": "\\b(import|export|pure|extern|start|edge)\\b" }, { "name": "keyword.other.ludic", "match": "\\b(import|extern)\\b" },
{ "name": "storage.type.ludic", "match": "\\b(game|module|component|archetype|ui|scene|layer|const|var|let|fn|system|state)\\b" }, { "name": "storage.type.ludic", "match": "\\b(game|module|component|struct|archetype|enum|ui|const|var|let|fn|system|main|state)\\b" },
{ "name": "support.type.primitive.ludic", "match": "\\b(int|fixed|bool|entity|str|ptr|void)\\b" }, { "name": "support.type.primitive.ludic", "match": "\\b(int|fixed|bool|entity|str|ptr|void)\\b" },
{ "name": "constant.language.boolean.ludic", "match": "\\b(true|false)\\b" }, { "name": "constant.language.boolean.ludic", "match": "\\b(true|false)\\b" },
{ "name": "constant.language.phase.ludic", "match": "\\b(Start|Input|FixedUpdate|Update|LateUpdate|Render)\\b" } { "name": "constant.language.phase.ludic", "match": "\\b(Start|Input|FixedUpdate|Update|LateUpdate|Render)\\b" }

View file

@ -75,35 +75,25 @@
} }
}, },
{ {
"match": "\\b(scene)\\s+([A-Za-z_][A-Za-z0-9_]*)(\\s+start)?", "match": "\\b(ui)\\s+([A-Za-z_][A-Za-z0-9_]*)",
"captures": {
"1": { "name": "storage.type.scene.ludic" },
"2": { "name": "entity.name.type.scene.ludic" },
"3": { "name": "keyword.other.start.ludic" }
}
},
{
"match": "\\b(layer|ui)\\s+([A-Za-z_][A-Za-z0-9_]*)",
"captures": { "captures": {
"1": { "name": "storage.type.ludic" }, "1": { "name": "storage.type.ludic" },
"2": { "name": "entity.name.type.ludic" } "2": { "name": "entity.name.type.ludic" }
} }
}, },
{ {
"match": "\\b(edge\\s+)?(system)\\s+([A-Za-z_][A-Za-z0-9_]*)", "match": "\\b(system)\\s+([A-Za-z_][A-Za-z0-9_]*)",
"captures": { "captures": {
"1": { "name": "storage.modifier.edge.ludic" }, "1": { "name": "storage.type.system.ludic" },
"2": { "name": "storage.type.system.ludic" }, "2": { "name": "entity.name.function.system.ludic" }
"3": { "name": "entity.name.function.system.ludic" }
} }
}, },
{ {
"match": "\\b(export\\s+|pure\\s+)?(extern\\s+)?(fn)\\s+([A-Za-z_][A-Za-z0-9_]*)", "match": "\\b(extern\\s+)?(fn)\\s+([A-Za-z_][A-Za-z0-9_]*)",
"captures": { "captures": {
"1": { "name": "storage.modifier.ludic" }, "1": { "name": "storage.modifier.extern.ludic" },
"2": { "name": "storage.modifier.extern.ludic" }, "2": { "name": "storage.type.function.ludic" },
"3": { "name": "storage.type.function.ludic" }, "3": { "name": "entity.name.function.ludic" }
"4": { "name": "entity.name.function.ludic" }
} }
}, },
{ {
@ -173,9 +163,9 @@
"patterns": [ "patterns": [
{ "name": "keyword.control.ludic", "match": "\\b(if|else|when|while|for|in|match|machine|become|enter|return|spawn|despawn|where|break|continue|new)\\b" }, { "name": "keyword.control.ludic", "match": "\\b(if|else|when|while|for|in|match|machine|become|enter|return|spawn|despawn|where|break|continue|new)\\b" },
{ "name": "keyword.operator.logical.ludic", "match": "\\b(and|or|not)\\b" }, { "name": "keyword.operator.logical.ludic", "match": "\\b(and|or|not)\\b" },
{ "name": "keyword.other.clause.ludic", "match": "\\b(phase|query|reads|writes|needs|uses|requires|ensures|invariant|effects|on)\\b" }, { "name": "keyword.other.clause.ludic", "match": "\\b(phase|query|reads|writes)\\b" },
{ "name": "keyword.other.ludic", "match": "\\b(import|export|pure|extern|start|edge)\\b" }, { "name": "keyword.other.ludic", "match": "\\b(import|extern)\\b" },
{ "name": "storage.type.ludic", "match": "\\b(game|module|component|archetype|ui|scene|layer|const|var|let|fn|system|state)\\b" }, { "name": "storage.type.ludic", "match": "\\b(game|module|component|struct|archetype|enum|ui|const|var|let|fn|system|main|state)\\b" },
{ "name": "support.type.primitive.ludic", "match": "\\b(int|fixed|bool|entity|str|ptr|void)\\b" }, { "name": "support.type.primitive.ludic", "match": "\\b(int|fixed|bool|entity|str|ptr|void)\\b" },
{ "name": "constant.language.boolean.ludic", "match": "\\b(true|false)\\b" }, { "name": "constant.language.boolean.ludic", "match": "\\b(true|false)\\b" },
{ "name": "constant.language.phase.ludic", "match": "\\b(Start|Input|FixedUpdate|Update|LateUpdate|Render)\\b" } { "name": "constant.language.phase.ludic", "match": "\\b(Start|Input|FixedUpdate|Update|LateUpdate|Render)\\b" }

View file

@ -52,12 +52,18 @@ typedef struct {
* mirror the compiler's parser: anything parse_decl() dispatches on is a * mirror the compiler's parser: anything parse_decl() dispatches on is a
* declaration keyword, anything stmt() dispatches on is a statement keyword. */ * declaration keyword, anything stmt() dispatches on is a statement keyword. */
static const char* LUDIC_KW_DECL[] = { static const char* LUDIC_KW_DECL[] = {
"game","module","import","component","struct","archetype","ui","scene","layer", "game","module","import","component","struct","archetype","enum","ui",
"const","var","export","fn","pure","extern","system","edge","start","on", 0 "const","var","fn","extern","system","main", 0
}; };
static const char* LUDIC_KW_CLAUSE[] = { static const char* LUDIC_KW_CLAUSE[] = {
"phase","query","reads","writes","needs","uses", "phase","query","reads","writes", 0
"requires","ensures","invariant","effects", 0 };
/* Documented design targets the self-hosted parser does not accept yet. Kept
* out of the highlighted vocabulary (they would read as working keywords) until
* they are implemented; check-vocabulary.py verifies the lists above are a
* subset of what selfhost/parse*.ludic actually dispatches on. */
static const char* LUDIC_KW_RESERVED[] = {
"scene","layer","on","start", 0
}; };
static const char* LUDIC_KW_STMT[] = { static const char* LUDIC_KW_STMT[] = {
"let","return","if","else","when","while","for","in","spawn","despawn", "let","return","if","else","when","while","for","in","spawn","despawn",

View file

@ -0,0 +1,327 @@
/* ============================================================================
* ludic_syntax.h — the lexical layer shared by every Ludic editor tool.
*
* One lexer, one vocabulary. `ludic-fmt`, `ludic-lsp` and the generated
* TextMate grammar all read their keyword/builtin/type tables from here, so an
* addition to the language shows up in every editor at once instead of drifting
* across six hand-maintained copies.
*
* Unlike the compiler's lexer (compiler/ludicc.c) this one is written for
* editors: it keeps comments, keeps newlines, records byte spans for every
* token, and never exits on bad input — a stray character becomes an LT_ERR
* token and lexing continues, because a file being typed into is malformed most
* of the time.
* ==========================================================================*/
#ifndef LUDIC_SYNTAX_H
#define LUDIC_SYNTAX_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
/* ---------- token kinds ---------------------------------------------------*/
enum {
LT_EOF, LT_NL, LT_COMMENT,
LT_ID, /* a plain identifier */
LT_KW, /* a reserved word (see KEYWORDS) */
LT_TYPE, /* a built-in type name: int fixed bool entity str ptr void */
LT_PHASE, /* Start Input FixedUpdate Update LateUpdate Render */
LT_BOOL, /* true false */
LT_INT, LT_FLOAT, LT_STR, LT_CHAR,
LT_ANNO, /* @deterministic — the '@' and the name as one token */
LT_OP,
LT_ERR
};
typedef struct {
int kind;
int start, end; /* byte offsets into the source buffer */
int line; /* 0-based */
int bad; /* set on an unterminated string / stray character */
} LTok;
typedef struct {
LTok* v; int n, cap;
const char* src;
int* linestart; int nline, caplin;
} LLex;
/* ---------- the vocabulary ------------------------------------------------*/
/* Reserved words, grouped so editors can colour them differently. The groups
* mirror the compiler's parser: anything parse_decl() dispatches on is a
* declaration keyword, anything stmt() dispatches on is a statement keyword. */
static const char* LUDIC_KW_DECL[] = {
"game","module","import","component","struct","archetype","enum","ui",
"const","var","fn","extern","system","main", 0
};
static const char* LUDIC_KW_CLAUSE[] = {
"phase","query","reads","writes", 0
};
/* Documented design targets the self-hosted parser does not accept yet. Kept
* out of the highlighted vocabulary (they would read as working keywords) until
* they are implemented; check-vocabulary.py verifies the lists above are a
* subset of what selfhost/parse*.ludic actually dispatches on. */
static const char* LUDIC_KW_RESERVED[] = {
"scene","layer","on","start", 0
};
static const char* LUDIC_KW_STMT[] = {
"let","return","if","else","when","while","for","in","spawn","despawn",
"match","machine","state","become","enter","where","and","or","not",
"break","continue","new", 0
};
static const char* LUDIC_TYPES[] = {
"int","fixed","bool","entity","str","ptr","void", 0
};
static const char* LUDIC_PHASES[] = {
"Start","Input","FixedUpdate","Update","LateUpdate","Render", 0
};
static const char* LUDIC_WIDGETS[] = {
"panel","col","row","label","button","image","spacer", 0
};
static const char* LUDIC_WIDGET_PROPS[] = {
"id","text","skin","image","align","w","h","pad","gap","size","font",
"inset","grow","bg","fg","border","focus","x","y", 0
};
/* A builtin is a name the runtime provides (`clear(c)` -> rt_clear). An
* intrinsic is a name the compiler lowers directly to libc/OS. Editors treat
* both as "standard library", but the signatures differ, so they stay apart. */
typedef struct { const char* name; const char* sig; const char* doc; } LBuiltin;
static const LBuiltin LUDIC_BUILTINS[] = {
{"min","min(a: int, b: int) -> int","Smaller of two integers."},
{"max","max(a: int, b: int) -> int","Larger of two integers."},
{"abs","abs(a: int) -> int","Absolute value."},
{"clamp","clamp(v: int, lo: int, hi: int) -> int","Constrain v to [lo, hi]."},
{"seed","seed(i: int)","Seed the deterministic RNG."},
{"rng_range","rng_range(lo: int, hi: int) -> int","Deterministic integer in [lo, hi]."},
{"rng_chance","rng_chance(pct: int) -> bool","True pct% of the time, deterministically."},
{"fx","fx(i: int) -> fixed","Widen an int to Q16.16 fixed-point."},
{"flr","flr(f: fixed) -> int","Truncate a fixed-point value toward zero."},
{"map_size","map_size(w: int, h: int)","Set the tilemap dimensions."},
{"map_row","map_row(y: int, row: str)","Fill one tilemap row from a string."},
{"tile","tile(x: int, y: int) -> int","Tile code at a map cell."},
{"clear","clear(color: int)","Clear the framebuffer to a 0xRRGGBB colour."},
{"present","present()","Push the framebuffer to the window (or out.ppm when headless)."},
{"fill_rect","fill_rect(x: int, y: int, w: int, h: int, color: int)","Filled rectangle."},
{"frame_rect","frame_rect(x: int, y: int, w: int, h: int, color: int)","One-pixel rectangle outline."},
{"put_px","put_px(x: int, y: int, color: int)","Write a single pixel."},
{"text","text(x: int, y: int, s: str, color: int, scale: int)","Draw text with the built-in 5x7 bitmap font."},
{"text_int","text_int(x: int, y: int, n: int, color: int, scale: int)","Draw an integer with the 5x7 bitmap font."},
{"font_load","font_load(path: str) -> int","Load a TrueType .ttf/.ttc; returns a font id."},
{"text_ttf","text_ttf(font: int, x: int, y: int, utf8: str, color: int, px: int)","Draw UTF-8 text with a TrueType font."},
{"text_w","text_w(font: int, utf8: str, px: int) -> int","Advance width of the string, in pixels."},
{"text_h","text_h(font: int, px: int) -> int","Line height of the font, in pixels."},
{"image_load","image_load(path: str) -> int","Decode a PNG into an image id."},
{"draw_image","draw_image(id: int, x: int, y: int)","Blit an image at its natural size."},
{"draw_image_scaled","draw_image_scaled(id: int, x: int, y: int, w: int, h: int)","Blit an image stretched to w x h."},
{"draw_9slice","draw_9slice(id: int, x: int, y: int, w: int, h: int, inset: int)","Nine-slice an image across a w x h box."},
{"load_png","load_png(path: str) -> int","Decode a PNG as a 16x16 sprite sheet; returns the first sprite id."},
{"load_sprites","load_sprites(path: str)","Load the sprite sheet used by draw_sprite."},
{"draw_sprite","draw_sprite(id: int, x: int, y: int)","Blit a sprite."},
{"draw_sprite_scaled","draw_sprite_scaled(id: int, x: int, y: int, scale: int)","Blit a sprite at an integer scale."},
{"ui_build","ui_build()","Construct every declared `ui` tree (loads skins and images)."},
{"ui_open","ui_open(id: int)","Make a ui tree active and focus its first button."},
{"ui_tick","ui_tick(key: int)","Feed a key to the UI: w/s move focus, space/enter activate."},
{"ui_render","ui_render()","Lay out and draw the active ui tree."},
{"ui_clicked","ui_clicked(id: int) -> bool","True on the frame a widget was activated."},
{"ui_set_text","ui_set_text(id: int, s: str)","Replace a widget's text."},
{"ui_set_int","ui_set_int(id: int, n: int)","Replace a widget's text with a number."},
{"ui_focus","ui_focus(id: int)","Move keyboard focus to a widget."},
{"ui_focused","ui_focused() -> int","Id of the focused widget."},
{"ui_visible","ui_visible(id: int, on: bool)","Show or hide a widget subtree."},
{"key","key() -> int","Key code pressed this frame, 0 if none."},
{"reg","reg(i: int) -> int","Read one of the 64 integer resources shared by systems."},
{"setreg","setreg(i: int, v: int)","Write one of the 64 integer resources."},
{"self","self() -> entity","The entity of the innermost query loop."},
{"save","save()","Write a binary snapshot of the whole ECS world."},
{"load","load() -> bool","Restore the snapshot; false if there is none."},
{"status","status(s: str)","Set the one-line status message."},
{"print_int","print_int(i: int)","Print an integer to stdout."},
{"quit","quit()","Stop the frame loop and exit."},
{0,0,0}
};
static const LBuiltin LUDIC_INTRINSICS[] = {
{"mem_alloc","mem_alloc(n: int) -> ptr","Allocate n bytes (malloc)."},
{"mem_realloc","mem_realloc(p: ptr, n: int) -> ptr","Resize a block to n bytes, preserving its contents (realloc)."},
{"os_argc","os_argc() -> int","Number of command-line arguments, argv[0] included."},
{"os_arg","os_arg(i: int) -> str","The i-th command-line argument."},
{"file_stderr","file_stderr() -> ptr","The standard error stream, for file_write."},
{"mem_free","mem_free(p: ptr)","Release an allocation."},
{"mem_copy","mem_copy(dst: ptr, src: ptr, n: int)","memcpy."},
{"mem_set","mem_set(p: ptr, byte: int, n: int)","memset."},
{"peek8","peek8(p: ptr, off: int) -> int","Read one byte."},
{"poke8","poke8(p: ptr, off: int, v: int)","Write one byte."},
{"peek32","peek32(p: ptr, off: int) -> int","Read a 32-bit word."},
{"poke32","poke32(p: ptr, off: int, v: int)","Write a 32-bit word."},
{"peekp","peekp(p: ptr, off: int) -> ptr","Read a pointer-sized word."},
{"pokep","pokep(p: ptr, off: int, v: ptr)","Write a pointer-sized word."},
{"peekf","peekf(p: ptr, off: int) -> fixed","Read a fixed-point word."},
{"pokef","pokef(p: ptr, off: int, v: fixed)","Write a fixed-point word."},
{"ptr_add","ptr_add(p: ptr, off: int) -> ptr","Offset a pointer by bytes."},
{"ptr_null","ptr_null() -> ptr","The null pointer."},
{"ptr_is_null","ptr_is_null(p: ptr) -> bool","Null test."},
{"as_fixed","as_fixed(i: int) -> fixed","Reinterpret an int as fixed (no conversion)."},
{"as_int","as_int(f: fixed) -> int","Reinterpret a fixed as int (no conversion)."},
{"file_open","file_open(path: str, mode: str) -> ptr","fopen."},
{"file_read","file_read(f: ptr, buf: ptr, n: int) -> int","fread."},
{"file_write","file_write(f: ptr, buf: ptr, n: int) -> int","fwrite."},
{"file_seek","file_seek(f: ptr, off: int, whence: int) -> int","fseek."},
{"file_tell","file_tell(f: ptr) -> int","ftell."},
{"file_close","file_close(f: ptr)","fclose."},
{"read_byte","read_byte() -> int","Read one byte from stdin, -1 at EOF."},
{"write_byte","write_byte(b: int)","Write one byte to stdout."},
{"print_str","print_str(s: str)","Write a string to stdout."},
{"str_len","str_len(s: str) -> int","Length of a string in bytes."},
{"shl","shl(v: int, n: int) -> int","Shift left."},
{"shr","shr(v: int, n: int) -> int","Logical shift right."},
{"band","band(a: int, b: int) -> int","Bitwise and."},
{"bor","bor(a: int, b: int) -> int","Bitwise or."},
{"bxor","bxor(a: int, b: int) -> int","Bitwise xor."},
{"bnot","bnot(a: int) -> int","Bitwise not."},
{"os_exit","os_exit(code: int)","Terminate the process."},
{"os_time","os_time() -> int","Seconds since the epoch."},
{"is_windowed","is_windowed() -> bool","True when the build has a window."},
{"game_title","game_title() -> str","The name from the `game` declaration."},
{"win_open","win_open(title: str, w: int, h: int, scale: int)","Open the platform window."},
{"win_poll","win_poll() -> int","Pump the event queue; returns a key code."},
{"win_present","win_present(px: ptr, w: int, h: int)","Blit a framebuffer to the window."},
{"win_running","win_running() -> bool","False once the window has been closed."},
{"win_close","win_close()","Close the platform window."},
{0,0,0}
};
static int lud_in(const char** set, const char* s){
for (int i = 0; set[i]; i++) if (!strcmp(set[i], s)) return 1;
return 0;
}
static const LBuiltin* lud_lookup(const LBuiltin* t, const char* s){
for (int i = 0; t[i].name; i++) if (!strcmp(t[i].name, s)) return &t[i];
return 0;
}
static int lud_is_keyword(const char* s){
return lud_in(LUDIC_KW_DECL, s) || lud_in(LUDIC_KW_CLAUSE, s) || lud_in(LUDIC_KW_STMT, s);
}
/* ---------- lexing --------------------------------------------------------*/
static void ltok_push(LLex* L, int kind, int start, int end, int line, int bad){
if (L->n >= L->cap){ L->cap = L->cap ? L->cap * 2 : 512; L->v = realloc(L->v, L->cap * sizeof(LTok)); }
L->v[L->n++] = (LTok){ kind, start, end, line, bad };
}
static void lline_push(LLex* L, int off){
if (L->nline >= L->caplin){ L->caplin = L->caplin ? L->caplin * 2 : 256; L->linestart = realloc(L->linestart, L->caplin * sizeof(int)); }
L->linestart[L->nline++] = off;
}
static const char* LUDIC_OPS2[] = { "->","+=","-=","*=","/=","==","!=","<=",">=","&&","||","..","=>", 0 };
/* Lex the whole buffer. Comments and newlines are kept — the formatter needs
* both, and a highlighter needs the comments. */
static void lud_lex(LLex* L, const char* src){
memset(L, 0, sizeof(*L));
L->src = src;
lline_push(L, 0);
int i = 0, line = 0;
while (src[i]){
char c = src[i];
if (c == '\n'){ ltok_push(L, LT_NL, i, i + 1, line, 0); i++; line++; lline_push(L, i); continue; }
if (c == ' ' || c == '\t' || c == '\r'){ i++; continue; }
if (c == '#'){ int s = i; while (src[i] && src[i] != '\n') i++; ltok_push(L, LT_COMMENT, s, i, line, 0); continue; }
if (c == '"'){
int s = i; i++; int bad = 0;
while (src[i] && src[i] != '"' && src[i] != '\n'){ if (src[i] == '\\' && src[i+1]) i += 2; else i++; }
if (src[i] == '"') i++; else bad = 1;
ltok_push(L, LT_STR, s, i, line, bad); continue;
}
if (c == '\''){
int s = i; i++; int bad = 0;
if (src[i] == '\\' && src[i+1]) i += 2; else if (src[i] && src[i] != '\n') i++;
if (src[i] == '\'') i++; else bad = 1;
ltok_push(L, LT_CHAR, s, i, line, bad); continue;
}
if (isdigit((unsigned char)c)){
int s = i;
if (c == '0' && (src[i+1] == 'x' || src[i+1] == 'X')){
i += 2; while (isxdigit((unsigned char)src[i])) i++;
ltok_push(L, LT_INT, s, i, line, 0); continue;
}
while (isdigit((unsigned char)src[i])) i++;
if (src[i] == '.' && isdigit((unsigned char)src[i+1])){
i++; while (isdigit((unsigned char)src[i])) i++;
ltok_push(L, LT_FLOAT, s, i, line, 0); continue;
}
ltok_push(L, LT_INT, s, i, line, 0); continue;
}
/* @name is one token: an annotation reads as a unit, and the formatter
* must never put a space between the sigil and the name. */
if (c == '@' && (isalpha((unsigned char)src[i+1]) || src[i+1] == '_')){
int s = i; i++; while (isalnum((unsigned char)src[i]) || src[i] == '_') i++;
ltok_push(L, LT_ANNO, s, i, line, 0); continue;
}
if (isalpha((unsigned char)c) || c == '_'){
int s = i; while (isalnum((unsigned char)src[i]) || src[i] == '_') i++;
int len = i - s; char w[128];
if (len < (int)sizeof(w)){ memcpy(w, src + s, len); w[len] = 0; } else { w[0] = 0; }
int k = LT_ID;
if (!strcmp(w, "true") || !strcmp(w, "false")) k = LT_BOOL;
else if (lud_in(LUDIC_TYPES, w)) k = LT_TYPE;
else if (lud_in(LUDIC_PHASES, w)) k = LT_PHASE;
else if (lud_is_keyword(w)) k = LT_KW;
ltok_push(L, k, s, i, line, 0); continue;
}
{
int matched = 0;
for (int k = 0; LUDIC_OPS2[k]; k++)
if (src[i] == LUDIC_OPS2[k][0] && src[i+1] == LUDIC_OPS2[k][1]){
ltok_push(L, LT_OP, i, i + 2, line, 0); i += 2; matched = 1; break;
}
if (matched) continue;
}
if (c == ';'){ ltok_push(L, LT_OP, i, i + 1, line, 0); i++; continue; }
if (strchr("+-*/%<>=(){}[],:.!@", c)){ ltok_push(L, LT_OP, i, i + 1, line, 0); i++; continue; }
/* Anything else is an error token — but a whole UTF-8 character's worth
* at a time. Splitting a multi-byte character into one token per byte
* would let a consumer that re-emits tokens (the formatter) put spaces
* inside it and corrupt the file. */
{
unsigned char u = (unsigned char)c;
int len = u < 0x80 ? 1 : u < 0xE0 ? 2 : u < 0xF0 ? 3 : 4;
for (int k = 1; k < len; k++) if (!src[i + k] || ((unsigned char)src[i + k] & 0xC0) != 0x80){ len = k; break; }
ltok_push(L, LT_ERR, i, i + len, line, 1); i += len;
}
}
ltok_push(L, LT_EOF, i, i, line, 0);
}
static void lud_lex_free(LLex* L){ free(L->v); free(L->linestart); memset(L, 0, sizeof(*L)); }
/* ---------- helpers over the token stream --------------------------------*/
static int ltok_len(const LTok* t){ return t->end - t->start; }
static int ltok_is(const LLex* L, int i, const char* s){
if (i < 0 || i >= L->n) return 0;
const LTok* t = &L->v[i]; int n = ltok_len(t);
return (int)strlen(s) == n && !strncmp(L->src + t->start, s, n);
}
/* Copy a token's text into a caller buffer; returns buf. */
static char* ltok_text(const LLex* L, int i, char* buf, int cap){
const LTok* t = &L->v[i]; int n = ltok_len(t);
if (n >= cap) n = cap - 1;
memcpy(buf, L->src + t->start, n); buf[n] = 0; return buf;
}
/* Index of the next token that is not a newline or comment, or -1. */
static int ltok_next_sig(const LLex* L, int i){
for (int j = i + 1; j < L->n; j++){
int k = L->v[j].kind;
if (k != LT_NL && k != LT_COMMENT) return j;
}
return -1;
}
static int ltok_prev_sig(const LLex* L, int i){
for (int j = i - 1; j >= 0; j--){
int k = L->v[j].kind;
if (k != LT_NL && k != LT_COMMENT) return j;
}
return -1;
}
#endif /* LUDIC_SYNTAX_H */

View file

@ -0,0 +1,59 @@
/* migrate_records.c — one-time migration for Rule A (named fields use ':').
* Records appear ONLY inside `spawn` blocks, so we track spawn context and, for
* every single '=' inside one:
* - '=' followed by '{' is a component init `Comp = { … }` -> delete it (`Comp { … }`)
* - otherwise is a field binding `field = value` -> rewrite to ':'
* A '=' at a real boundary is a pure re-spelling; the driver's IR-identity check
* (migrated program compiles to the same IR) is the proof.
* usage: migrate_records <file> -> migrated source to stdout
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "ludic_syntax.h"
int main(int argc, char** argv){
if (argc < 2){ fprintf(stderr, "usage: migrate_records <file>\n"); return 2; }
FILE* f = fopen(argv[1], "rb");
if (!f){ fprintf(stderr, "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);
char* rep = calloc(L.n, 1); /* '=' -> ':' */
char* del = calloc(L.n, 1); /* drop the '=' (component init) */
int sp_stack[1024]; int bp = 0; int spawn_active = 0; int pendingSpawn = 0;
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;
if (k == LT_KW && ltok_is(&L, i, "spawn")) pendingSpawn = 1;
int op1 = (k == LT_OP && ltok_len(&L.v[i]) == 1);
char c0 = L.src[L.v[i].start];
if (op1 && c0 == '{'){
if (bp < 1024){ sp_stack[bp] = (pendingSpawn || spawn_active > 0) ? 1 : 0; if (sp_stack[bp]) spawn_active++; bp++; }
pendingSpawn = 0;
} else if (op1 && c0 == '}'){
if (bp > 0){ bp--; if (sp_stack[bp] && spawn_active > 0) spawn_active--; }
} else if (op1 && c0 == '='){
if (spawn_active > 0){
int nx = ltok_next_sig(&L, i);
if (nx >= 0 && L.v[nx].kind == LT_OP && ltok_len(&L.v[nx]) == 1 && L.src[L.v[nx].start] == '{') del[i] = 1;
else rep[i] = 1;
}
}
}
/* map flags onto byte positions so the rebuild can peek one char ahead */
char* repb = calloc(n + 1, 1); char* delb = calloc(n + 1, 1);
for (int i = 0; i < L.n; i++){ if (rep[i]) repb[L.v[i].start] = 1; if (del[i]) delb[L.v[i].start] = 1; }
for (int b = 0; b < n; ){
if (repb[b]){ putchar(':'); b++; continue; } /* '=' -> ':' */
if (delb[b]){ b++; if (b < n && src[b] == ' ') b++; continue; } /* drop '=' and one space */
if (src[b] == ' ' && b + 1 < n && repb[b + 1]){ b++; continue; } /* drop the space before ':' -> `field: v` */
putchar(src[b]); b++;
}
free(repb); free(delb);
lud_lex_free(&L); free(rep); free(del); free(src);
return 0;
}

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;
}

View file

@ -0,0 +1,52 @@
/* migrate_ui.c — one-time migration for Rule A in `ui` blocks: widget props go
* from `key=value` to `key: value`. Every '=' inside a `ui` block is a prop
* separator (values are expressions, never contain a top-level '='), so we track
* ui context (armed by the `ui` keyword's root brace) and rewrite each '=' to a
* ':' with canonical spacing (`id: Root`). The driver's IR-identity check proves
* it is a pure re-spelling (the parser builds the same E_FINIT nodes).
* usage: migrate_ui <file> -> migrated source to stdout
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "ludic_syntax.h"
int main(int argc, char** argv){
if (argc < 2){ fprintf(stderr, "usage: migrate_ui <file>\n"); return 2; }
FILE* f = fopen(argv[1], "rb");
if (!f){ fprintf(stderr, "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);
char* repb = calloc(n + 1, 1); /* byte position of a '=' to rewrite as ':' */
int ui_stack[1024]; int bp = 0; int ui_active = 0; int pendingUi = 0;
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;
if (k == LT_KW && ltok_is(&L, i, "ui")) pendingUi = 1;
int op1 = (k == LT_OP && ltok_len(&L.v[i]) == 1);
char c0 = L.src[L.v[i].start];
if (op1 && c0 == '{'){
if (bp < 1024){ ui_stack[bp] = (pendingUi || ui_active > 0) ? 1 : 0; if (ui_stack[bp]) ui_active++; bp++; }
pendingUi = 0;
} else if (op1 && c0 == '}'){
if (bp > 0){ bp--; if (ui_stack[bp] && ui_active > 0) ui_active--; }
} else if (op1 && c0 == '=' && ui_active > 0){
repb[L.v[i].start] = 1;
}
}
for (int b = 0; b < n; ){
if (repb[b]){ /* '=' -> ': ' with a single trailing space */
putchar(':'); b++;
if (b < n && src[b] != ' ' && src[b] != '\n') putchar(' ');
continue;
}
if (src[b] == ' ' && b + 1 < n && repb[b + 1]){ b++; continue; } /* drop the space before ':' */
putchar(src[b]); b++;
}
lud_lex_free(&L); free(repb); free(src);
return 0;
}