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:
commit
3227e1e1d3
53 changed files with 5723 additions and 4579 deletions
27
BOOTSTRAP.md
27
BOOTSTRAP.md
|
|
@ -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` |
|
||||
| Character literals | ✅ | `'x'`, `'\n'`, `'\0'` lex to ints |
|
||||
| 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
|
||||
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`
|
||||
```ludic
|
||||
game P {
|
||||
fn fib(n: int) -> int { if n < 2 { return n } return fib(n-1) + fib(n-2) }
|
||||
system B phase Start { print_int(fib(10)) quit() }
|
||||
fn fib(n: int) -> int { if n < 2 { return n }; return fib(n-1) + fib(n-2) }
|
||||
system B phase Start { print_int(fib(10)); quit() }
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -878,11 +878,11 @@ fn streq(a: ptr, b: ptr) -> bool {
|
|||
fn itoa(v: int, buf: ptr) -> int {
|
||||
let n = 0
|
||||
let x = v
|
||||
if x == 0 { poke8(buf,0,48) return 1 }
|
||||
if x == 0 { poke8(buf,0,48); return 1 }
|
||||
let tmp = mem_alloc(16)
|
||||
while x > 0 { poke8(tmp, n, 48 + x % 10) x = x / 10 n = n + 1 }
|
||||
while x > 0 { poke8(tmp, n, 48 + x % 10); x = x / 10; n = n + 1 }
|
||||
let i = 0
|
||||
while i < n { poke8(buf, i, peek8(tmp, n-1-i)) i = i + 1 }
|
||||
while i < n { poke8(buf, i, peek8(tmp, n-1-i)); i = i + 1 }
|
||||
mem_free(tmp)
|
||||
return n
|
||||
}
|
||||
|
|
@ -905,16 +905,23 @@ file_close(f)
|
|||
Each is a spelling the language accepts; the point is that the *alternative*
|
||||
spelling is equally legal (§5.3).
|
||||
|
||||
**R1 — three statements on one line, no separators** → `2`
|
||||
**R1 — statements now require a separator (Rule B, syntax-redesign Phase 2)** → parse error
|
||||
```ludic
|
||||
# doc-check: skip — intentionally rejected under Rule B: needs a newline or ';'
|
||||
game P { system B phase Start { let x = 1 x = x + 1 print_int(x) quit() } }
|
||||
```
|
||||
Statements no longer sit adjacent with only spaces between them; the compiler
|
||||
reports `expected newline or ';' between statements`. Put each on its own line,
|
||||
or separate them with `;` (both lex to the same separator token):
|
||||
```ludic
|
||||
game P { system B phase Start { let x = 1; x = x + 1; print_int(x); quit() } }
|
||||
```
|
||||
|
||||
**R2 — commas omitted throughout** → `7`
|
||||
```ludic
|
||||
# doc-check: skip — composite: declaration plus statements
|
||||
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:
|
||||
|
|
@ -954,7 +961,7 @@ parenthesisation) while `ludic-fmt` returns the input **unchanged**.
|
|||
**Verified-missing** — each a compile error today:
|
||||
```ludic
|
||||
# doc-check: expect-error — every line here is a compile error by design
|
||||
while i < 10 { i = i + 1 if i == 3 { break } } # unknown identifier 'break'
|
||||
while i < 10 { i = i + 1; if i == 3 { break } } # unknown identifier 'break'
|
||||
struct Node { k: int, a: ptr } # expected declaration (got 'struct')
|
||||
var t: [int; 8] # expected identifier (got '[')
|
||||
var h: fn = a # unknown type 'fn' for var h
|
||||
|
|
@ -963,5 +970,5 @@ print_int(os_argc()) # unknown function 'os_argc'
|
|||
print_err("x") # unknown function 'print_err'
|
||||
let p = mem_realloc(ptr_null(), 10) # unknown function 'mem_realloc'
|
||||
print_str("ab" + "cd") # passes front-end, invalid IR
|
||||
let a = 100000 print_int(a*100000) # 1410065408 — i32 wrap
|
||||
let a = 100000; print_int(a*100000) # 1410065408 — i32 wrap
|
||||
```
|
||||
|
|
|
|||
|
|
@ -79,20 +79,20 @@ self-hosted `ludicc`.
|
|||
|
||||
> **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`
|
||||
> 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.
|
||||
|
||||
A source file opens with `game Name { … }` or `module Name { … }`.
|
||||
|
||||
* A **game** gets an entry point and the phase-ordered frame loop
|
||||
(`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.
|
||||
|
||||
```ludic
|
||||
# doc-check: skip — illustrative: elided body
|
||||
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
|
||||
}
|
||||
```
|
||||
|
|
|
|||
77
LANGUAGE.md
77
LANGUAGE.md
|
|
@ -67,7 +67,7 @@ component Stats { hp: int = 10 }
|
|||
archetype Player { Pos, Stats } # Player IS a kind, not a component
|
||||
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
|
||||
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
|
||||
ui MainMenu {
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Widget types: `panel` (container + optional skin/bg/border), `col` / `row`
|
||||
(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.
|
||||
Each `id=Name` mints a `UI_Name` handle (the `ui` block name too), used from
|
||||
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
|
||||
systems:
|
||||
|
||||
```ludic
|
||||
|
|
@ -130,7 +130,7 @@ system Nav phase Update {
|
|||
if ui_clicked(UI_Quit) { quit() }
|
||||
ui_set_int(UI_HpLabel, hp) # poke dynamic values by id
|
||||
}
|
||||
system Draw phase Render { clear(0x0e0e16) ui_render() present() }
|
||||
system Draw phase Render { clear(0x0e0e16); ui_render(); present() }
|
||||
```
|
||||
|
||||
See `examples/menu.ludic` for a complete title screen.
|
||||
|
|
@ -159,8 +159,8 @@ component Pos { x: int = 0, y: int = 0 } # typed fields with defaults
|
|||
component Player { } # a tag (no fields)
|
||||
|
||||
spawn Hero { # create an entity
|
||||
Pos = { x = 10, y = 5 }
|
||||
Player = { }
|
||||
Pos { x: 10, y: 5 }
|
||||
Player { }
|
||||
}
|
||||
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
|
||||
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
|
||||
|
||||
|
|
@ -335,6 +341,18 @@ another `.ludic` file (see `examples/lib/`).
|
|||
`for (…) in query […] { }` · `break` · `continue` · `return` · `spawn` ·
|
||||
`despawn` · `match` · `machine`.
|
||||
|
||||
**Statements are separated by a newline or `;`** (both lex to the same separator
|
||||
token). Two statements may not sit adjacent with only spaces between them — the
|
||||
compiler reports `expected newline or ';' between statements`. Write one
|
||||
statement per line, or, to pack several onto a line, separate them with `;`:
|
||||
|
||||
```ludic
|
||||
# doc-check: skip — a bare statement block, not a whole declaration
|
||||
let x = 1
|
||||
x = x + 1 # one per line, the usual form
|
||||
let y = 1; y = y + 1 # or `;`-separated on one line
|
||||
```
|
||||
|
||||
`break` and `continue` apply to the innermost enclosing loop, and work in all
|
||||
three loop forms — `while`, the numeric `for`, and the ECS query loop, where
|
||||
`continue` advances to the next matching entity. Using either outside a loop is
|
||||
|
|
@ -361,15 +379,38 @@ state (no more `if phase == N` chains). See the co-op battle in
|
|||
```ludic
|
||||
# doc-check: skip — illustrative: elided bodies
|
||||
machine R_PHASE {
|
||||
state KnightMenu = 0 { … if is_confirm(k) { …attack… become KnightResolve } }
|
||||
state KnightResolve = 1 { … become MageMenu }
|
||||
state EnemyTurn = 4 { … become KnightMenu }
|
||||
state KnightMenu { … if is_confirm(k) { …attack… become KnightResolve } }
|
||||
state KnightResolve { … become MageMenu }
|
||||
state EnemyTurn { … become KnightMenu }
|
||||
}
|
||||
```
|
||||
|
||||
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).
|
||||
States **number themselves by declaration order** (`KnightMenu` is `0`,
|
||||
`KnightResolve` is `1`, …) — no magic constants. (An explicit `state Name = expr`
|
||||
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
|
||||
|
||||
|
|
@ -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
|
||||
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
|
||||
— 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
|
||||
ints (`0xff8800`).
|
||||
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@ same highlighting, checking and formatting as the source tree.
|
|||
- `scene` / `layer` / `enter` — mutually-exclusive game states, each with
|
||||
`on enter`/`on exit` hooks and layered systems (layer order = draw order).
|
||||
- `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.
|
||||
- `extern fn … = "symbol"` — call any C-ABI library, Ludic or otherwise.
|
||||
- `--target wasm32-unknown-unknown` — the same game in a browser: the runtime,
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
**full redesign (Phases 0–5)**. Named-field direction: **colon everywhere**.
|
||||
|
||||
> Status: **Phase 1 landed** (see below). Phases 2→5 are proposals. The phases
|
||||
> are ordered so the documentation never describes syntax the compiler rejects,
|
||||
> and every phase ends with the compiler still self-hosting to a fixpoint
|
||||
> (`./test.sh`).
|
||||
> Status: **Phases 1–5 complete.** Every phase kept the compiler self-hosting to
|
||||
> a fixpoint (`./test.sh` 14/14), and each syntax migration was proven
|
||||
> behaviour-preserving (the migrated compiler compiles itself to byte-identical
|
||||
> 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`
|
||||
> 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)**
|
||||
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
|
||||
([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),
|
||||
**does not compile** (`expected declaration`).
|
||||
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
|
||||
(`band`/`shl`) at once.
|
||||
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
|
||||
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
|
||||
cover the drift instead).
|
||||
|
||||
### Phase 2 — Statement separation (Rule B)
|
||||
- Enforce a separator in `block()`/`stmt()` ([parse.ludic:130-199](selfhost/parse.ludic)):
|
||||
after a statement, require `TK_NL` or `}`.
|
||||
- Teach `ludic-fmt` to normalize one-statement-per-line and insert `;` where two
|
||||
share a line. Reformat the whole corpus with it.
|
||||
- Risk: the self-host sources themselves use space-juxtaposed statements heavily
|
||||
— reformat them in the same commit, re-seed, re-verify fixpoint.
|
||||
### Phase 2 — Statement separation (Rule B) ✅ DONE
|
||||
Landed on branch `syntax-redesign-phase2` (baseline committed on `main` first).
|
||||
- ✅ **Parser enforces a separator** — `block()` requires a newline or `;` after
|
||||
each statement, else `expected newline or ';' between statements`
|
||||
([parse.ludic](selfhost/parse.ludic)). Also fixed `if`-without-`else` swallowing
|
||||
its trailing separator (it now peeks for `else` and restores if absent).
|
||||
- ✅ **Interpretation chosen:** *require a separator*, not *reflow to one-per-line*.
|
||||
The migration **inserts `;` at statement boundaries** and leaves lines intact —
|
||||
comment-safe, minimal-diff, and it makes boundaries visible without an
|
||||
opinionated reflow. One-per-line stays the recommended hand-written form.
|
||||
- ✅ **Migration tool** ([tools/ludic-tools/migrate_separators.c](tools/ludic-tools/migrate_separators.c),
|
||||
reuses the toolchain lexer) with a
|
||||
**verification oracle**: a `;` inserted at a real boundary is a semantic no-op,
|
||||
proven by the migrated compiler compiling itself to **IR byte-identical to the
|
||||
seed** and every golden game rendering identically. ~1100 boundaries across the
|
||||
corpus (examples, runtime, and the 25 self-host fragments).
|
||||
- ✅ **Reseeded** to the strict compiler (19557 lines); C-free bootstrap fixpoint
|
||||
holds; `./test.sh` 14/14; all goldens byte-identical; qdecl runs correctly.
|
||||
- ✅ **Docs updated** — Rule B documented in LANGUAGE.md §Statements; BOOTSTRAP.md
|
||||
R1 (which advertised no-separator juxtaposition as legal) and its stale code
|
||||
fences updated; `check-docs` (now a live strict parse gate) green across all docs.
|
||||
|
||||
**Bug found & fixed en route:** a multi-line string literal in
|
||||
[emit_expr.ludic](selfhost/emit_expr.ludic) (`emit(")<newline>")`) lexed fine in
|
||||
the self-host lexer but the **C toolchain lexer** (`ludic_syntax.h`, shared by
|
||||
sepfix, `ludic-fmt`, and the LSP) stops strings at newline — so it mis-lexed and
|
||||
`ludic-fmt` would corrupt such a file. Converted it to the byte-identical `\n`
|
||||
escape. **Open follow-up:** align the C lexer to allow newlines in strings, or
|
||||
forbid literal newlines in string literals language-wide (the two lexers should
|
||||
agree). Flagged to the toolchain owners.
|
||||
|
||||
### Phase 3 — Named-field unification (Rule A)
|
||||
- Migrate spawn/record init (`= {…=…}` → `{…:…}`) and ui props (`key=value` →
|
||||
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)
|
||||
- Remove `when`; confirm `if` covers all uses in the corpus.
|
||||
- Introduce `enum` + typed `machine`/state; migrate `combat.ludic`'s R_PHASE
|
||||
machine and the register-driven `match reg(…)` sites.
|
||||
- Decide the bitwise story: keep `band/shl/…` as functions (document as a
|
||||
deliberate "one spelling" choice) or promote to operators — pick one and state
|
||||
it, don't leave it implicit.
|
||||
**3a — spawn/record initializers ✅ DONE.** `Comp = { f = v }` → `Comp { f: v }`.
|
||||
`record()` requires `:` and `parse_spawn()` drops the `=` before the record
|
||||
([parse.ludic](selfhost/parse.ludic), [parse_game.ludic](selfhost/parse_game.ludic)).
|
||||
The `=` is now assignment/const/default/extern-binding only. Migration tool:
|
||||
[migrate_records.c](tools/ludic-tools/migrate_records.c) (spawn-context aware).
|
||||
Records live only in games, so the seed was unaffected; verified every golden
|
||||
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
|
||||
- Generate every editor plugin keyword list, `ludic_syntax.h`, and the LSP's
|
||||
token set from **one** canonical list so a keyword can never again be
|
||||
highlighted but unparsed.
|
||||
- Add a CI check (extend `tools/check-docs.py` / `check-vocabulary.py`) that
|
||||
every ` ```ludic ` fence in the docs compiles, closing the doc-drift loop
|
||||
permanently.
|
||||
**3b — ui props → `key: value` ✅ DONE.** `panel id=Root w=288` → `panel id: Root
|
||||
w: 288`. `parse_widget` now reads props with `:` ([parse_game.ludic](selfhost/parse_game.ludic)).
|
||||
Chose the **colonized** form over parenthesized named-args: it satisfies Rule A
|
||||
(the `=` overload is gone) with minimal churn, needs no new grammar, and `emit_ui`
|
||||
(which reads the AST) and `ludic-fmt` (which formats `:` correctly by default)
|
||||
were both untouched. Migration: [migrate_ui.c](tools/ludic-tools/migrate_ui.c).
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -3,13 +3,18 @@
|
|||
# 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`
|
||||
# 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 {
|
||||
if reg(R_MODE) == 1 {
|
||||
let k = key()
|
||||
for (e) in query [Stats, {Enemy}] {
|
||||
machine R_PHASE {
|
||||
|
||||
state KnightMenu = 0 {
|
||||
state KnightMenu {
|
||||
for (st, pt) in query [Stats, Party] {
|
||||
if pt.slot == 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 is_confirm(k) {
|
||||
match reg(R_CUR) {
|
||||
0 => {
|
||||
KnightAct.Attack => {
|
||||
let d = max(1, st.atk - e.def + rng_range(0, 4))
|
||||
e.hp = e.hp - d
|
||||
setreg(R_PDMG, d)
|
||||
}
|
||||
1 => { st.guard = 1 setreg(R_PDMG, 0) }
|
||||
2 => {
|
||||
KnightAct.Guard => { st.guard = 1; setreg(R_PDMG, 0) }
|
||||
KnightAct.Item => {
|
||||
if reg(R_POTION) > 0 {
|
||||
setreg(R_POTION, reg(R_POTION) - 1)
|
||||
st.hp = min(st.maxhp, st.hp + 24)
|
||||
setreg(R_PDMG, 24)
|
||||
} else { setreg(R_PDMG, 0) }
|
||||
}
|
||||
3 => {
|
||||
if rng_chance(50) { e.hp = 0 setreg(R_MODE, 0) }
|
||||
KnightAct.Flee => {
|
||||
if rng_chance(50) { e.hp = 0; setreg(R_MODE, 0) }
|
||||
else { setreg(R_PDMG, 0) }
|
||||
}
|
||||
}
|
||||
|
|
@ -46,12 +51,12 @@ system Battle phase Update {
|
|||
}
|
||||
}
|
||||
|
||||
state KnightResolve = 1 {
|
||||
state KnightResolve {
|
||||
if k != 0 {
|
||||
if e.hp <= 0 {
|
||||
grant_xp(e.xp)
|
||||
setreg(R_GOLD, reg(R_GOLD) + e.xp)
|
||||
if reg(R_ETYPE) == 2 { setreg(R_BOSS, 1) setreg(R_MODE, 3) setreg(R_ACK, 0) }
|
||||
if reg(R_ETYPE) == 2 { setreg(R_BOSS, 1); setreg(R_MODE, 3); setreg(R_ACK, 0) }
|
||||
else { setreg(R_MODE, 0) }
|
||||
become KnightMenu
|
||||
} else {
|
||||
|
|
@ -61,7 +66,7 @@ system Battle phase Update {
|
|||
}
|
||||
}
|
||||
|
||||
state MageMenu = 2 {
|
||||
state MageMenu {
|
||||
for (st, pt) in query [Stats, Party] {
|
||||
if pt.slot == 1 {
|
||||
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 == 'j' {
|
||||
match reg(R_CUR) {
|
||||
0 => {
|
||||
MageAct.Attack => {
|
||||
let d = max(1, st.atk + 5 - e.def + rng_range(0, 5))
|
||||
e.hp = e.hp - d
|
||||
setreg(R_PDMG, d)
|
||||
}
|
||||
1 => {
|
||||
MageAct.Heal => {
|
||||
if st.mp >= 4 {
|
||||
st.mp = st.mp - 4
|
||||
for (t2, p2) in query [Stats, Party] {
|
||||
|
|
@ -85,7 +90,7 @@ system Battle phase Update {
|
|||
setreg(R_PDMG, 16)
|
||||
} else { setreg(R_PDMG, 0) }
|
||||
}
|
||||
2 => { st.guard = 1 setreg(R_PDMG, 0) }
|
||||
MageAct.Guard => { st.guard = 1; setreg(R_PDMG, 0) }
|
||||
}
|
||||
become MageResolve
|
||||
setreg(R_CUR, 0)
|
||||
|
|
@ -95,12 +100,12 @@ system Battle phase Update {
|
|||
}
|
||||
}
|
||||
|
||||
state MageResolve = 3 {
|
||||
state MageResolve {
|
||||
if k != 0 {
|
||||
if e.hp <= 0 {
|
||||
grant_xp(e.xp)
|
||||
setreg(R_GOLD, reg(R_GOLD) + e.xp)
|
||||
if reg(R_ETYPE) == 2 { setreg(R_BOSS, 1) setreg(R_MODE, 3) setreg(R_ACK, 0) }
|
||||
if reg(R_ETYPE) == 2 { setreg(R_BOSS, 1); setreg(R_MODE, 3); setreg(R_ACK, 0) }
|
||||
else { setreg(R_MODE, 0) }
|
||||
become KnightMenu
|
||||
} else {
|
||||
|
|
@ -109,14 +114,14 @@ system Battle phase Update {
|
|||
}
|
||||
}
|
||||
|
||||
state EnemyTurn = 4 {
|
||||
state EnemyTurn {
|
||||
if k != 0 {
|
||||
let done = 0
|
||||
for (t3) in query [Stats, {Party}] {
|
||||
if done == 0 {
|
||||
if t3.hp > 0 {
|
||||
let ed = max(1, e.atk - t3.def + rng_range(0, 4))
|
||||
if t3.guard == 1 { ed = ed / 2 t3.guard = 0 }
|
||||
if t3.guard == 1 { ed = ed / 2; t3.guard = 0 }
|
||||
t3.hp = t3.hp - ed
|
||||
setreg(R_EDMG, ed)
|
||||
done = 1
|
||||
|
|
@ -127,7 +132,7 @@ system Battle phase Update {
|
|||
for (t4) in query [Stats, {Party}] {
|
||||
if t4.hp > 0 { alive = alive + 1 }
|
||||
}
|
||||
if alive == 0 { setreg(R_MODE, 2) setreg(R_ACK, 0) }
|
||||
if alive == 0 { setreg(R_MODE, 2); setreg(R_ACK, 0) }
|
||||
else {
|
||||
become KnightMenu
|
||||
setreg(R_CUR, 0)
|
||||
|
|
|
|||
|
|
@ -16,14 +16,14 @@ system Boot phase Start {
|
|||
|
||||
build_field(0)
|
||||
spawn Player {
|
||||
Pos = { x = 11, y = 15 }
|
||||
Actor = { kind = 5 }
|
||||
Party = { slot = 0 }
|
||||
Stats = { hp = 40, maxhp = 40, mp = 8, maxmp = 8, atk = 12, def = 6, lvl = 1, xp = 0, guard = 0 }
|
||||
Pos { x: 11, y: 15 }
|
||||
Actor { kind: 5 }
|
||||
Party { slot: 0 }
|
||||
Stats { hp: 40, maxhp: 40, mp: 8, maxmp: 8, atk: 12, def: 6, lvl: 1, xp: 0, guard: 0 }
|
||||
}
|
||||
spawn Ally {
|
||||
Party = { slot = 1 }
|
||||
Stats = { hp = 26, maxhp = 26, mp = 16, maxmp = 16, atk = 8, def = 4, lvl = 1, xp = 0, guard = 0 }
|
||||
Party { slot: 1 }
|
||||
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_ENC, 4)
|
||||
|
|
@ -69,8 +69,8 @@ system Control phase Input {
|
|||
if reg(R_BOSS) == 0 {
|
||||
setreg(R_ETYPE, 2)
|
||||
spawn Enemy {
|
||||
Actor = { kind = 8 }
|
||||
Stats = { hp = 120, maxhp = 120, mp = 0, maxmp = 0, atk = 18, def = 10, lvl = 5, xp = 300, guard = 0 }
|
||||
Actor { kind: 8 }
|
||||
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_PHASE, 0)
|
||||
|
|
@ -96,14 +96,14 @@ system Control phase Input {
|
|||
setreg(R_ETYPE, et)
|
||||
if et == 0 {
|
||||
spawn Enemy {
|
||||
Actor = { kind = 6 }
|
||||
Stats = { hp = 18, maxhp = 18, mp = 0, maxmp = 0, atk = 8, def = 2, lvl = 1, xp = 7, guard = 0 }
|
||||
Actor { kind: 6 }
|
||||
Stats { hp: 18, maxhp: 18, mp: 0, maxmp: 0, atk: 8, def: 2, lvl: 1, xp: 7, guard: 0 }
|
||||
}
|
||||
}
|
||||
if et == 1 {
|
||||
spawn Enemy {
|
||||
Actor = { kind = 7 }
|
||||
Stats = { hp = 30, maxhp = 30, mp = 0, maxmp = 0, atk = 11, def = 5, lvl = 1, xp = 15, guard = 0 }
|
||||
Actor { kind: 7 }
|
||||
Stats { hp: 30, maxhp: 30, mp: 0, maxmp: 0, atk: 11, def: 5, lvl: 1, xp: 15, guard: 0 }
|
||||
}
|
||||
}
|
||||
setreg(R_MODE, 1)
|
||||
|
|
|
|||
|
|
@ -6,12 +6,12 @@ game Hello {
|
|||
|
||||
system Boot phase Start {
|
||||
spawn Mob {
|
||||
Pos = { x = 3, y = 4 }
|
||||
Vel = { dx = 1, dy = 0 }
|
||||
Pos { x: 3, y: 4 }
|
||||
Vel { dx: 1, dy: 0 }
|
||||
}
|
||||
spawn Mob {
|
||||
Pos = { x = 10, y = 2 }
|
||||
Vel = { dx = 0, dy = 1 }
|
||||
Pos { x: 10, y: 2 }
|
||||
Vel { dx: 0, dy: 1 }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@ game Arena {
|
|||
component Fighter { hp: int = 30, attack: int = 9, armour: int = 2 }
|
||||
|
||||
system Boot phase Start {
|
||||
spawn Hero { Fighter = { hp = 40, attack = 12, armour = 3 } }
|
||||
spawn Orc { Fighter = { hp = 24, attack = 7, armour = 1 } }
|
||||
spawn Hero { Fighter { hp: 40, attack: 12, armour: 3 } }
|
||||
spawn Orc { Fighter { hp: 24, attack: 7, armour: 1 } }
|
||||
}
|
||||
|
||||
system Fight phase Update {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ module Combat {
|
|||
const CRIT_MULT: int = 2
|
||||
|
||||
# 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
|
||||
if raw < 1 { raw = 1 }
|
||||
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
|
||||
# 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 n = 0
|
||||
while left > 0 {
|
||||
|
|
@ -35,7 +35,7 @@ module Combat {
|
|||
return n
|
||||
}
|
||||
|
||||
export fn xp_for(level: int, kills: int) -> int {
|
||||
@export fn xp_for(level: int, kills: int) -> int {
|
||||
return curve(level) * kills
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,14 +8,14 @@ game Menu {
|
|||
const R_MSG: int = 1
|
||||
|
||||
ui MainMenu {
|
||||
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="a Ludic retained-UI demo" font=reg(R_FONT) size=14 fg=0x9aa0c8 align=center
|
||||
spacer h=4
|
||||
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=Options text="Options" font=reg(R_FONT) size=16 w=236
|
||||
button id=Quit text="Quit" font=reg(R_FONT) size=16 w=236
|
||||
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: "a Ludic retained-UI demo" font: reg(R_FONT) size: 14 fg: 0x9aa0c8 align: center
|
||||
spacer h: 4
|
||||
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: Options text: "Options" font: reg(R_FONT) size: 16 w: 236
|
||||
button id: Quit text: "Quit" font: reg(R_FONT) size: 16 w: 236
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,9 +15,9 @@ game QueryDecl {
|
|||
archetype Foe { Battle, Pos }
|
||||
|
||||
system Seed phase Start {
|
||||
spawn Foe { Battle = { hp = 3, side = 1 } }
|
||||
spawn Foe { Battle = { hp = 0, side = 1 } }
|
||||
spawn Foe { Battle = { hp = -2, side = 0 } }
|
||||
spawn Foe { Battle { hp: 3, side: 1 } }
|
||||
spawn Foe { Battle { hp: 0, side: 1 } }
|
||||
spawn Foe { Battle { hp: -2, side: 0 } }
|
||||
}
|
||||
|
||||
system CleanBattle phase LateUpdate
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
game SceneDemo {
|
||||
const R_N: int = 0
|
||||
|
||||
system Boot phase Start { setreg(R_N, 0) print_int(1000) }
|
||||
system Boot phase Start { setreg(R_N, 0); print_int(1000) }
|
||||
|
||||
scene Title start {
|
||||
on enter { print_int(1) }
|
||||
|
|
@ -22,7 +22,7 @@ game SceneDemo {
|
|||
}
|
||||
|
||||
scene Play {
|
||||
on enter { print_int(3) setreg(R_N, 0) }
|
||||
on enter { print_int(3); setreg(R_N, 0) }
|
||||
on exit { print_int(4) }
|
||||
layer World {
|
||||
system Step phase Update {
|
||||
|
|
|
|||
|
|
@ -26,9 +26,9 @@ game Snake {
|
|||
|
||||
fn reset(x: int) -> int {
|
||||
for (p, s) in query [Pos, Seg] { despawn self() }
|
||||
spawn S0 { Seg = { order = 0 } Pos = { x = 10, y = 7 } }
|
||||
spawn S1 { Seg = { order = 1 } Pos = { x = 9, y = 7 } }
|
||||
spawn S2 { Seg = { order = 2 } Pos = { x = 8, y = 7 } }
|
||||
spawn S0 { Seg { order: 0 }; Pos { x: 10, y: 7 } }
|
||||
spawn S1 { Seg { order: 1 }; Pos { x: 9, y: 7 } }
|
||||
spawn S2 { Seg { order: 2 }; Pos { x: 8, y: 7 } }
|
||||
setreg(R_DIR, 3)
|
||||
setreg(R_LEN, 3)
|
||||
setreg(R_TICK, 0)
|
||||
|
|
@ -62,7 +62,7 @@ game Snake {
|
|||
setreg(R_TICK, 0)
|
||||
let len = reg(R_LEN)
|
||||
for (pt, stg) in query [Pos, Seg] {
|
||||
if stg.order == len - 1 { setreg(R_TX, pt.x) setreg(R_TY, pt.y) }
|
||||
if stg.order == len - 1 { setreg(R_TX, pt.x); setreg(R_TY, pt.y) }
|
||||
}
|
||||
for step in 0 .. len {
|
||||
let i = len - 1 - step
|
||||
|
|
@ -70,7 +70,7 @@ game Snake {
|
|||
for (pa, sa) in query [Pos, Seg] {
|
||||
if sa.order == i {
|
||||
for (pb, sb) in query [Pos, Seg] {
|
||||
if sb.order == i - 1 { pa.x = pb.x pa.y = pb.y }
|
||||
if sb.order == i - 1 { pa.x = pb.x; pa.y = pb.y }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -101,7 +101,7 @@ game Snake {
|
|||
}
|
||||
if reg(R_ATE) == 1 {
|
||||
setreg(R_SCORE, reg(R_SCORE) + 1)
|
||||
spawn Body { Seg = { order = len } Pos = { x = reg(R_TX), y = reg(R_TY) } }
|
||||
spawn Body { Seg { order: len }; Pos { x: reg(R_TX), y: reg(R_TY) } }
|
||||
setreg(R_LEN, len + 1)
|
||||
setreg(R_FX, rng_range(0, GW - 1))
|
||||
setreg(R_FY, rng_range(0, GH - 1))
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ fn rt_read_file(path: str) -> ptr {
|
|||
file_seek(f, 0, 2)
|
||||
let n = file_tell(f)
|
||||
file_seek(f, 0, 0)
|
||||
if n <= 0 { file_close(f) return ptr_null() }
|
||||
if n <= 0 { file_close(f); return ptr_null() }
|
||||
let buf = mem_alloc(n + 8)
|
||||
file_read(f, buf, n)
|
||||
file_close(f)
|
||||
|
|
@ -70,9 +70,9 @@ fn rt_decode_png(path: str) -> bool {
|
|||
let d = rt_read_file(path)
|
||||
if ptr_is_null(d) { return false }
|
||||
let size = rt_file_len
|
||||
if size < 8 { mem_free(d) return false }
|
||||
if peek8(d, 0) != 137 { mem_free(d) return false }
|
||||
if peek8(d, 1) != 80 { mem_free(d) return false }
|
||||
if size < 8 { mem_free(d); return false }
|
||||
if peek8(d, 0) != 137 { mem_free(d); return false }
|
||||
if peek8(d, 1) != 80 { mem_free(d); return false }
|
||||
|
||||
let w = 0
|
||||
let h = 0
|
||||
|
|
@ -119,8 +119,8 @@ fn rt_decode_png(path: str) -> bool {
|
|||
}
|
||||
}
|
||||
|
||||
if w <= 0 { mem_free(d) return false }
|
||||
if h <= 0 { mem_free(d) return false }
|
||||
if w <= 0 { mem_free(d); return false }
|
||||
if h <= 0 { mem_free(d); return false }
|
||||
|
||||
let channels = 1
|
||||
if ct == 2 { channels = 3 }
|
||||
|
|
@ -133,7 +133,7 @@ fn rt_decode_png(path: str) -> bool {
|
|||
let rawlen = h * (stride + 1)
|
||||
let raw = mem_alloc(rawlen + 8)
|
||||
if z_uncompress(idat, idlen, raw, rawlen) < 0 {
|
||||
mem_free(d) mem_free(raw) mem_free(idat)
|
||||
mem_free(d); mem_free(raw); mem_free(idat)
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
@ -378,7 +378,7 @@ fn rt_hexval(c: int) -> int {
|
|||
|
||||
fn rt_load_sprites(path: str) -> void {
|
||||
let d = rt_read_file(path)
|
||||
if ptr_is_null(d) { spr_n = 0 return }
|
||||
if ptr_is_null(d) { spr_n = 0; return }
|
||||
let size = rt_file_len
|
||||
let pal = mem_alloc(128 * 4)
|
||||
mem_set(pal, 0, 128 * 4)
|
||||
|
|
@ -391,7 +391,7 @@ fn rt_load_sprites(path: str) -> void {
|
|||
let j = start
|
||||
while j < size {
|
||||
let ch = peek8(d, j)
|
||||
if ch == 10 { j = size } else { len = len + 1 j = j + 1 }
|
||||
if ch == 10 { j = size } else { len = len + 1; j = j + 1 }
|
||||
}
|
||||
let c0 = peek8(d, start)
|
||||
if c0 == 112 { # 'p' — "pal <char> <rrggbb>"
|
||||
|
|
|
|||
|
|
@ -322,7 +322,7 @@ fn tt_load_outline(id: int, gid: int, a: fixed, b: fixed, c: fixed, e: fixed, dx
|
|||
let r = peek8(d, p)
|
||||
p = p + 1
|
||||
while r > 0 {
|
||||
if i < npts { poke8(flags, i, fl) i = i + 1 }
|
||||
if i < npts { poke8(flags, i, fl); i = i + 1 }
|
||||
r = r - 1
|
||||
}
|
||||
}
|
||||
|
|
@ -338,7 +338,7 @@ fn tt_load_outline(id: int, gid: int, a: fixed, b: fixed, c: fixed, e: fixed, dx
|
|||
p = p + 1
|
||||
if band(fl, 16) != 0 { xv = xv + dxv } else { xv = xv - dxv }
|
||||
} else {
|
||||
if band(fl, 16) == 0 { xv = xv + tt_i16(d, p) p = p + 2 }
|
||||
if band(fl, 16) == 0 { xv = xv + tt_i16(d, p); p = p + 2 }
|
||||
}
|
||||
poke32(xs, k, xv)
|
||||
}
|
||||
|
|
@ -350,7 +350,7 @@ fn tt_load_outline(id: int, gid: int, a: fixed, b: fixed, c: fixed, e: fixed, dx
|
|||
p = p + 1
|
||||
if band(fl, 32) != 0 { yv = yv + dyv } else { yv = yv - dyv }
|
||||
} else {
|
||||
if band(fl, 32) == 0 { yv = yv + tt_i16(d, p) p = p + 2 }
|
||||
if band(fl, 32) == 0 { yv = yv + tt_i16(d, p); p = p + 2 }
|
||||
}
|
||||
poke32(ys, k, yv)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ const N_SYS: int = 9
|
|||
const N_ARCH: int = 10
|
||||
const N_EXTERN: int = 11
|
||||
const N_UI: int = 12
|
||||
const N_ENUM: int = 27 # enum Name { A, B, ... } — named int constants
|
||||
# statements
|
||||
const S_LET: int = 10
|
||||
const S_ASSIGN: int = 11
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ fn buf_putc(b: Buf, c: int) -> void {
|
|||
}
|
||||
fn buf_puts(b: Buf, s: ptr) -> void {
|
||||
let i = 0
|
||||
while peek8(s, i) != 0 { buf_putc(b, peek8(s, i)) i = i + 1 }
|
||||
while peek8(s, i) != 0 { buf_putc(b, peek8(s, i)); i = i + 1 }
|
||||
}
|
||||
fn buf_puti(b: Buf, n: int) -> void { buf_puts(b, itoa(n)) }
|
||||
fn buf_str(b: Buf) -> ptr { poke8(b.data, b.len, 0) return b.data }
|
||||
fn buf_str(b: Buf) -> ptr { poke8(b.data, b.len, 0); return b.data }
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ fn emit_member_addr(e: Node) -> ptr {
|
|||
if fidx < 0 { perr(sconcat("no such field ", e.s)) }
|
||||
g_addr_ty = field_type(s, e.s)
|
||||
let r = nreg()
|
||||
emit(" ") emit(r) emit(" = getelementptr inbounds ") emit(layout_ty(base.ty))
|
||||
emit(", ptr ") emit(base.code) emit(", i32 0, i32 ") emit(itoa(fidx)) emit("\n")
|
||||
emit(" "); emit(r); emit(" = getelementptr inbounds "); emit(layout_ty(base.ty))
|
||||
emit(", ptr "); emit(base.code); emit(", i32 0, i32 "); emit(itoa(fidx)); emit("\n")
|
||||
return r
|
||||
}
|
||||
|
||||
|
|
@ -26,13 +26,13 @@ fn emit_index_addr(e: Node) -> ptr {
|
|||
g_addr_ty = el
|
||||
# load the data pointer from the slice header (field 0)
|
||||
let dp = nreg()
|
||||
emit(" ") emit(dp) emit(" = getelementptr inbounds %LSlice, ptr ")
|
||||
emit(base.code) emit(", i32 0, i32 0\n")
|
||||
emit(" "); emit(dp); emit(" = getelementptr inbounds %LSlice, ptr ")
|
||||
emit(base.code); emit(", i32 0, i32 0\n")
|
||||
let data = nreg()
|
||||
emit(" ") emit(data) emit(" = load ptr, ptr ") emit(dp) emit("\n")
|
||||
emit(" "); emit(data); emit(" = load ptr, ptr "); emit(dp); emit("\n")
|
||||
let ix = emit_expr(e.b)
|
||||
let r = nreg()
|
||||
emit(" ") emit(r) emit(" = getelementptr inbounds ") emit(llty(el))
|
||||
emit(", ptr ") emit(data) emit(", i32 ") emit(ix.code) emit("\n")
|
||||
emit(" "); emit(r); emit(" = getelementptr inbounds "); emit(llty(el))
|
||||
emit(", ptr "); emit(data); emit(", i32 "); emit(ix.code); emit("\n")
|
||||
return r
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
# structs and slices are references, so every non-scalar type lowers to `ptr`.
|
||||
|
||||
struct Val { code: ptr = ptr_null(), ty: ptr = ptr_null() }
|
||||
fn val(code: ptr, ty: ptr) -> Val { let v = new Val v.code = code v.ty = ty return v }
|
||||
fn val(code: ptr, ty: ptr) -> Val { let v = new Val; v.code = code; v.ty = ty; return v }
|
||||
|
||||
var head: Buf # module-level: types, globals, string constants
|
||||
var code: Buf # function bodies
|
||||
|
|
@ -36,24 +36,24 @@ fn emith(s: ptr) -> void { buf_puts(head, s) }
|
|||
# stack slots MUST live in the entry block (an alloca in a loop walks the stack
|
||||
# off its end), so they go into a per-function buffer spliced in at entry.
|
||||
fn emit_alloca(llt: ptr) -> ptr {
|
||||
let r = sconcat("%t", itoa(ll_t)) ll_t = ll_t + 1
|
||||
buf_puts(falloc, " ") buf_puts(falloc, r) buf_puts(falloc, " = alloca ") buf_puts(falloc, llt) buf_puts(falloc, "\n")
|
||||
let r = sconcat("%t", itoa(ll_t)); ll_t = ll_t + 1
|
||||
buf_puts(falloc, " "); buf_puts(falloc, r); buf_puts(falloc, " = alloca "); buf_puts(falloc, llt); buf_puts(falloc, "\n")
|
||||
return r
|
||||
}
|
||||
|
||||
# "%t<n>" fresh register
|
||||
fn sconcat(a: ptr, b: ptr) -> ptr {
|
||||
let la = slen(a) let lb = slen(b)
|
||||
let la = slen(a); let lb = slen(b)
|
||||
let out = mem_alloc(la + lb + 1)
|
||||
let i = 0
|
||||
while i < la { poke8(out, i, peek8(a, i)) i = i + 1 }
|
||||
while i < la { poke8(out, i, peek8(a, i)); i = i + 1 }
|
||||
let j = 0
|
||||
while j < lb { poke8(out, la + j, peek8(b, j)) j = j + 1 }
|
||||
while j < lb { poke8(out, la + j, peek8(b, j)); j = j + 1 }
|
||||
poke8(out, la + lb, 0)
|
||||
return out
|
||||
}
|
||||
fn nreg() -> ptr { let r = sconcat("%t", itoa(ll_t)) ll_t = ll_t + 1 return r }
|
||||
fn lbl(pfx: ptr) -> ptr { let r = sconcat(pfx, itoa(ll_lbl)) ll_lbl = ll_lbl + 1 return r }
|
||||
fn nreg() -> ptr { let r = sconcat("%t", itoa(ll_t)); ll_t = ll_t + 1; return r }
|
||||
fn lbl(pfx: ptr) -> ptr { let r = sconcat(pfx, itoa(ll_lbl)); ll_lbl = ll_lbl + 1; return r }
|
||||
|
||||
# Ludic type -> LLVM type. int/bool are i32; everything else (ptr/str/struct/
|
||||
# slice) is a pointer; void is void.
|
||||
|
|
@ -79,17 +79,17 @@ fn is_struct_ty(t: ptr) -> bool { return not ptr_is_null(find_struct(t)) }
|
|||
|
||||
fn find_arch(name: ptr) -> Node {
|
||||
let i = 0
|
||||
while i < len(prog) { let d = prog[i] if d.kind == N_ARCH and streq(d.s, name) { return d } i = i + 1 }
|
||||
while i < len(prog) { let d = prog[i]; if d.kind == N_ARCH and streq(d.s, name) { return d }; i = i + 1 }
|
||||
return ptr_null()
|
||||
}
|
||||
fn find_comp(name: ptr) -> Node {
|
||||
let i = 0
|
||||
while i < len(prog) { let d = prog[i] if d.kind == N_COMP and streq(d.s, name) { return d } i = i + 1 }
|
||||
while i < len(prog) { let d = prog[i]; if d.kind == N_COMP and streq(d.s, name) { return d }; i = i + 1 }
|
||||
return ptr_null()
|
||||
}
|
||||
# a struct or a component — both have %Str_/%Cmp_ layouts with named fields
|
||||
fn layout_node(name: ptr) -> Node {
|
||||
let s = find_struct(name) if not ptr_is_null(s) { return s }
|
||||
let s = find_struct(name); if not ptr_is_null(s) { return s }
|
||||
return find_comp(name)
|
||||
}
|
||||
fn layout_ty(name: ptr) -> ptr {
|
||||
|
|
@ -99,12 +99,12 @@ fn layout_ty(name: ptr) -> ptr {
|
|||
|
||||
fn field_index(s: Node, fname: ptr) -> int {
|
||||
let i = 0
|
||||
while i < len(s.kids) { if streq(s.kids[i].s, fname) { return i } i = i + 1 }
|
||||
while i < len(s.kids) { if streq(s.kids[i].s, fname) { return i }; i = i + 1 }
|
||||
return 0 - 1
|
||||
}
|
||||
fn field_type(s: Node, fname: ptr) -> ptr {
|
||||
let i = 0
|
||||
while i < len(s.kids) { if streq(s.kids[i].s, fname) { return s.kids[i].ty } i = i + 1 }
|
||||
while i < len(s.kids) { if streq(s.kids[i].s, fname) { return s.kids[i].ty }; i = i + 1 }
|
||||
return "int"
|
||||
}
|
||||
|
||||
|
|
@ -119,21 +119,36 @@ fn find_global(name: ptr) -> Node {
|
|||
}
|
||||
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 {
|
||||
let i = 0
|
||||
while i < len(prog) { let d = prog[i] if d.kind == N_FN and streq(d.s, name) { return d } i = i + 1 }
|
||||
while i < len(prog) { let d = prog[i]; if d.kind == N_FN and streq(d.s, name) { return d }; i = i + 1 }
|
||||
return ptr_null()
|
||||
}
|
||||
|
||||
# local variable environment
|
||||
fn loc_reset() -> void { nloc = 0 }
|
||||
fn loc_push(name: ptr, r: ptr, ty: ptr) -> void {
|
||||
if nloc < len(loc_name) { loc_name[nloc] = name loc_reg[nloc] = r loc_ty[nloc] = ty }
|
||||
else { push(loc_name, name) push(loc_reg, r) push(loc_ty, ty) }
|
||||
if nloc < len(loc_name) { loc_name[nloc] = name; loc_reg[nloc] = r; loc_ty[nloc] = ty }
|
||||
else { push(loc_name, name); push(loc_reg, r); push(loc_ty, ty) }
|
||||
nloc = nloc + 1
|
||||
}
|
||||
fn loc_find(name: ptr) -> int {
|
||||
let i = nloc - 1
|
||||
while i >= 0 { if streq(loc_name[i], name) { return i } i = i - 1 }
|
||||
while i >= 0 { if streq(loc_name[i], name) { return i }; i = i - 1 }
|
||||
return 0 - 1
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,26 +6,26 @@ fn emit_params_sig(d: Node) -> void {
|
|||
let i = 0
|
||||
while i < len(d.kids) {
|
||||
if i > 0 { emit(", ") }
|
||||
emit(llty(d.kids[i].ty)) emit(" %arg_") emit(d.kids[i].s)
|
||||
emit(llty(d.kids[i].ty)); emit(" %arg_"); emit(d.kids[i].s)
|
||||
i = i + 1
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_fn(d: Node) -> void {
|
||||
ll_t = 0 ll_lbl = 0 g_term = false loc_reset() nloop = 0
|
||||
ll_t = 0; ll_lbl = 0; g_term = false; loc_reset(); nloop = 0
|
||||
ret_ty = d.ty
|
||||
let fbody = buf_new()
|
||||
falloc = buf_new()
|
||||
let saved = code
|
||||
code = fbody
|
||||
let rl = llty(ret_ty)
|
||||
if not streq(rl, "void") { buf_puts(falloc, " %retval = alloca ") buf_puts(falloc, rl) buf_puts(falloc, "\n") }
|
||||
if not streq(rl, "void") { buf_puts(falloc, " %retval = alloca "); buf_puts(falloc, rl); buf_puts(falloc, "\n") }
|
||||
# params: store each incoming argument into a stack slot
|
||||
let i = 0
|
||||
while i < len(d.kids) {
|
||||
let p = d.kids[i]
|
||||
let slot = emit_alloca(llty(p.ty))
|
||||
emit(" store ") emit(llty(p.ty)) emit(" %arg_") emit(p.s) emit(", ptr ") emit(slot) emit("\n")
|
||||
emit(" store "); emit(llty(p.ty)); emit(" %arg_"); emit(p.s); emit(", ptr "); emit(slot); emit("\n")
|
||||
loc_push(p.s, slot, p.ty)
|
||||
i = i + 1
|
||||
}
|
||||
|
|
@ -33,16 +33,16 @@ fn emit_fn(d: Node) -> void {
|
|||
if not g_term { emit(" br label %ret\n") }
|
||||
emit("ret:\n")
|
||||
if streq(rl, "void") { emit(" ret void\n") }
|
||||
else { let r = emit_bind(sconcat("load ", sconcat(rl, ", ptr %retval"))) emit(" ret ") emit(rl) emit(" ") emit(r) emit("\n") }
|
||||
else { let r = emit_bind(sconcat("load ", sconcat(rl, ", ptr %retval"))); emit(" ret "); emit(rl); emit(" "); emit(r); emit("\n") }
|
||||
code = saved
|
||||
emit("define ") emit(rl) emit(" @fn_") emit(d.s) emit("(") emit_params_sig(d) emit(") {\nentry:\n")
|
||||
emit("define "); emit(rl); emit(" @fn_"); emit(d.s); emit("("); emit_params_sig(d); emit(") {\nentry:\n")
|
||||
emit(buf_str(falloc))
|
||||
emit(buf_str(fbody))
|
||||
emit("}\n\n")
|
||||
}
|
||||
|
||||
fn emit_main(d: Node) -> void {
|
||||
ll_t = 0 ll_lbl = 0 g_term = false loc_reset() nloop = 0
|
||||
ll_t = 0; ll_lbl = 0; g_term = false; loc_reset(); nloop = 0
|
||||
ret_ty = "int"
|
||||
let fbody = buf_new()
|
||||
falloc = buf_new()
|
||||
|
|
@ -56,7 +56,7 @@ fn emit_main(d: Node) -> void {
|
|||
if not g_term { emit(" br label %ret\n") }
|
||||
emit("ret:\n")
|
||||
let r = emit_bind("load i32, ptr %retval")
|
||||
emit(" ret i32 ") emit(r) emit("\n")
|
||||
emit(" ret i32 "); emit(r); emit("\n")
|
||||
code = saved
|
||||
emit("define i32 @main(i32 %argc, ptr %argv) {\nentry:\n")
|
||||
emit(buf_str(falloc))
|
||||
|
|
@ -67,20 +67,20 @@ fn emit_main(d: Node) -> void {
|
|||
fn emit_program() -> void {
|
||||
head = buf_new()
|
||||
code = buf_new()
|
||||
loc_name = new []ptr loc_reg = new []ptr loc_ty = new []ptr
|
||||
brk_lbl = new []ptr cnt_lbl = new []ptr
|
||||
loc_name = new []ptr; loc_reg = new []ptr; loc_ty = new []ptr
|
||||
brk_lbl = new []ptr; cnt_lbl = new []ptr
|
||||
self_stk = new []ptr
|
||||
mach_stk = new []Node
|
||||
emit_header()
|
||||
if has_ecs() { emit_ecs_storage() }
|
||||
let i = 0
|
||||
while i < len(prog) { if prog[i].kind == N_FN { emit_fn(prog[i]) } i = i + 1 }
|
||||
if has_ecs() { emit_ecs_allocator() emit_snapshot() }
|
||||
while i < len(prog) { if prog[i].kind == N_FN { emit_fn(prog[i]) }; i = i + 1 }
|
||||
if has_ecs() { emit_ecs_allocator(); emit_snapshot() }
|
||||
if has_ui() { emit_ui_build() }
|
||||
if has_systems() { emit_game_main() }
|
||||
else {
|
||||
i = 0
|
||||
while i < len(prog) { if prog[i].kind == N_MAIN { emit_main(prog[i]) } i = i + 1 }
|
||||
while i < len(prog) { if prog[i].kind == N_MAIN { emit_main(prog[i]) }; i = i + 1 }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@ const MAX_ENT: int = 1024
|
|||
|
||||
fn has_ecs() -> bool {
|
||||
let i = 0
|
||||
while i < len(prog) { let k = prog[i].kind if k == N_COMP or k == N_SYS { return true } i = i + 1 }
|
||||
while i < len(prog) { let k = prog[i].kind; if k == N_COMP or k == N_SYS { return true }; i = i + 1 }
|
||||
return false
|
||||
}
|
||||
fn has_systems() -> bool {
|
||||
let i = 0
|
||||
while i < len(prog) { if prog[i].kind == N_SYS { return true } i = i + 1 }
|
||||
while i < len(prog) { if prog[i].kind == N_SYS { return true }; i = i + 1 }
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
@ -32,7 +32,7 @@ fn emit_ecs_storage() -> void {
|
|||
emith(sconcat("%Cmp_", sconcat(c.s, " = type { ")))
|
||||
if len(c.kids) == 0 { emith("i32") }
|
||||
let f = 0
|
||||
while f < len(c.kids) { if f > 0 { emith(", ") } emith(llty(c.kids[f].ty)) f = f + 1 }
|
||||
while f < len(c.kids) { if f > 0 { emith(", ") }; emith(llty(c.kids[f].ty)); f = f + 1 }
|
||||
emith(" }\n")
|
||||
emith(sconcat("@S_", sconcat(c.s, sconcat(" = internal global [", sconcat(me, sconcat(" x %Cmp_", sconcat(c.s, "] zeroinitializer\n")))))))
|
||||
emith(sconcat("@H_", sconcat(c.s, sconcat(" = internal global [", sconcat(me, " x i8] zeroinitializer\n")))))
|
||||
|
|
@ -49,12 +49,12 @@ fn emit_ecs_allocator() -> void {
|
|||
while i < len(prog) {
|
||||
if prog[i].kind == N_COMP {
|
||||
let hn = sconcat("%h", itoa(i))
|
||||
emit(" ") emit(hn) emit(" = getelementptr inbounds [") emit(me) emit(" x i8], ptr @H_") emit(prog[i].s) emit(", i32 0, i32 %e\n")
|
||||
emit(" store i8 0, ptr ") emit(hn) emit("\n")
|
||||
emit(" "); emit(hn); emit(" = getelementptr inbounds ["); emit(me); emit(" x i8], ptr @H_"); emit(prog[i].s); emit(", i32 0, i32 %e\n")
|
||||
emit(" store i8 0, ptr "); emit(hn); emit("\n")
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
emit(" %k = getelementptr inbounds [") emit(me) emit(" x i32], ptr @L_kind, i32 0, i32 %e\n")
|
||||
emit(" %k = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_kind, i32 0, i32 %e\n")
|
||||
emit(" store i32 0, ptr %k\n ret void\n}\n\n")
|
||||
|
||||
emit("define i32 @L_alloc() {\nentry:\n")
|
||||
|
|
@ -64,7 +64,7 @@ fn emit_ecs_allocator() -> void {
|
|||
emit("reuse:\n")
|
||||
emit(" %fn1 = sub i32 %fn, 1\n")
|
||||
emit(" store i32 %fn1, ptr @L_freen\n")
|
||||
emit(" %fp = getelementptr inbounds [") emit(me) emit(" x i32], ptr @L_freelist, i32 0, i32 %fn1\n")
|
||||
emit(" %fp = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_freelist, i32 0, i32 %fn1\n")
|
||||
emit(" %re = load i32, ptr %fp\n")
|
||||
emit(" br label %done\n")
|
||||
emit("fresh:\n")
|
||||
|
|
@ -75,16 +75,16 @@ fn emit_ecs_allocator() -> void {
|
|||
emit("done:\n")
|
||||
emit(" %e = phi i32 [ %re, %reuse ], [ %ec, %fresh ]\n")
|
||||
emit(" call void @L_reset(i32 %e)\n")
|
||||
emit(" %ap = getelementptr inbounds [") emit(me) emit(" x i32], ptr @L_alive, i32 0, i32 %e\n")
|
||||
emit(" %ap = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_alive, i32 0, i32 %e\n")
|
||||
emit(" store i32 1, ptr %ap\n")
|
||||
emit(" ret i32 %e\n}\n\n")
|
||||
|
||||
emit("define void @L_free_entity(i32 %e) {\nentry:\n")
|
||||
emit(" %ap = getelementptr inbounds [") emit(me) emit(" x i32], ptr @L_alive, i32 0, i32 %e\n")
|
||||
emit(" %ap = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_alive, i32 0, i32 %e\n")
|
||||
emit(" store i32 0, ptr %ap\n")
|
||||
emit(" call void @L_reset(i32 %e)\n")
|
||||
emit(" %fn = load i32, ptr @L_freen\n")
|
||||
emit(" %fp = getelementptr inbounds [") emit(me) emit(" x i32], ptr @L_freelist, i32 0, i32 %fn\n")
|
||||
emit(" %fp = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_freelist, i32 0, i32 %fn\n")
|
||||
emit(" store i32 %e, ptr %fp\n")
|
||||
emit(" %fn1 = add i32 %fn, 1\n")
|
||||
emit(" store i32 %fn1, ptr @L_freen\n")
|
||||
|
|
|
|||
|
|
@ -12,17 +12,17 @@ fn emit_logic(e: Node) -> Val {
|
|||
let la = emit_expr(e.a)
|
||||
let lc = emit_bind(sconcat("icmp ne i32 ", sconcat(la.code, ", 0")))
|
||||
let lz = emit_bind(sconcat("zext i1 ", sconcat(lc, " to i32")))
|
||||
emit(" store i32 ") emit(lz) emit(", ptr ") emit(slot) emit("\n")
|
||||
let ev = lbl("sc") let done = lbl("scend")
|
||||
if streq(e.s, "and") { emit(" br i1 ") emit(lc) emit(", label %") emit(ev) emit(", label %") emit(done) emit("\n") }
|
||||
else { emit(" br i1 ") emit(lc) emit(", label %") emit(done) emit(", label %") emit(ev) emit("\n") }
|
||||
emit(ev) emit(":\n")
|
||||
emit(" store i32 "); emit(lz); emit(", ptr "); emit(slot); emit("\n")
|
||||
let ev = lbl("sc"); let done = lbl("scend")
|
||||
if streq(e.s, "and") { emit(" br i1 "); emit(lc); emit(", label %"); emit(ev); emit(", label %"); emit(done); emit("\n") }
|
||||
else { emit(" br i1 "); emit(lc); emit(", label %"); emit(done); emit(", label %"); emit(ev); emit("\n") }
|
||||
emit(ev); emit(":\n")
|
||||
let rb = emit_expr(e.b)
|
||||
let rc = emit_bind(sconcat("icmp ne i32 ", sconcat(rb.code, ", 0")))
|
||||
let rz = emit_bind(sconcat("zext i1 ", sconcat(rc, " to i32")))
|
||||
emit(" store i32 ") emit(rz) emit(", ptr ") emit(slot) emit("\n")
|
||||
emit(" br label %") emit(done) emit("\n")
|
||||
emit(done) emit(":\n")
|
||||
emit(" store i32 "); emit(rz); emit(", ptr "); emit(slot); emit("\n")
|
||||
emit(" br label %"); emit(done); emit("\n")
|
||||
emit(done); emit(":\n")
|
||||
return val(emit_bind(sconcat("load i32, ptr ", slot)), "bool")
|
||||
}
|
||||
|
||||
|
|
@ -57,13 +57,13 @@ fn emit_bin(e: Node) -> Val {
|
|||
let b = emit_expr(e.b)
|
||||
let fx = streq(a.ty, "fixed") or streq(b.ty, "fixed")
|
||||
if is_cmp(e.s) {
|
||||
let ac = a.code let bc = b.code
|
||||
if fx { ac = to_fixed(a) bc = to_fixed(b) }
|
||||
let ac = a.code; let bc = b.code
|
||||
if fx { ac = to_fixed(a); bc = to_fixed(b) }
|
||||
let c = emit_bind(sconcat("icmp ", sconcat(cmp_code(e.s), sconcat(" i32 ", sconcat(ac, sconcat(", ", bc))))))
|
||||
return val(emit_bind(sconcat("zext i1 ", sconcat(c, " to i32"))), "bool")
|
||||
}
|
||||
if fx {
|
||||
let af = to_fixed(a) let bf = to_fixed(b)
|
||||
let af = to_fixed(a); let bf = to_fixed(b)
|
||||
if streq(e.s, "*") {
|
||||
let a64 = emit_bind(sconcat("sext i32 ", sconcat(af, " to i64")))
|
||||
let b64 = emit_bind(sconcat("sext i32 ", sconcat(bf, " to i64")))
|
||||
|
|
@ -87,16 +87,16 @@ fn emit_bin(e: Node) -> Val {
|
|||
|
||||
fn emit_call(e: Node) -> Val {
|
||||
let name = e.a.s
|
||||
if streq(name, "self") { if nself == 0 { return val("0", "entity") } return val(emit_bind(sconcat("load i32, ptr ", self_stk[nself - 1])), "entity") }
|
||||
if streq(name, "self") { if nself == 0 { return val("0", "entity") }; return val(emit_bind(sconcat("load i32, ptr ", self_stk[nself - 1])), "entity") }
|
||||
if streq(name, "key") { return val(emit_bind("load i32, ptr @L_key"), "int") }
|
||||
if streq(name, "save") { emit(" call void @L_save()\n") return val("0", "void") }
|
||||
if streq(name, "ui_build") { emit(" call void @ui_build()\n") return val("0", "void") }
|
||||
if streq(name, "save") { emit(" call void @L_save()\n"); return val("0", "void") }
|
||||
if streq(name, "ui_build") { emit(" call void @ui_build()\n"); return val("0", "void") }
|
||||
if streq(name, "load") { return val(emit_bind("call i32 @L_load()"), "bool") }
|
||||
if streq(name, "quit") { emit(" store i32 0, ptr @L_running\n") return val("0", "void") }
|
||||
if streq(name, "quit") { emit(" store i32 0, ptr @L_running\n"); return val("0", "void") }
|
||||
if streq(name, "len") { return emit_len(e) }
|
||||
if streq(name, "push") { return emit_push(e) }
|
||||
if streq(name, "fx") { let a = emit_expr(e.kids[0]) return val(emit_bind(sconcat("shl i32 ", sconcat(a.code, ", 16"))), "fixed") }
|
||||
if streq(name, "flr") { let a = emit_expr(e.kids[0]) return val(emit_bind(sconcat("ashr i32 ", sconcat(a.code, ", 16"))), "int") }
|
||||
if streq(name, "fx") { let a = emit_expr(e.kids[0]); return val(emit_bind(sconcat("shl i32 ", sconcat(a.code, ", 16"))), "fixed") }
|
||||
if streq(name, "flr") { let a = emit_expr(e.kids[0]); return val(emit_bind(sconcat("ashr i32 ", sconcat(a.code, ", 16"))), "int") }
|
||||
if is_intrinsic(name) { return emit_intrinsic(name, e) }
|
||||
if is_intrinsic2(name) { return emit_intrinsic2(name, e) }
|
||||
if is_math_builtin(name) { return emit_math_builtin(name, e) }
|
||||
|
|
@ -113,20 +113,19 @@ fn emit_call(e: Node) -> Val {
|
|||
let args = new []ptr
|
||||
let atys = new []ptr
|
||||
let i = 0
|
||||
while i < len(e.kids) { let v = emit_expr(e.kids[i]) push(args, v.code) push(atys, v.ty) i = i + 1 }
|
||||
while i < len(e.kids) { let v = emit_expr(e.kids[i]); push(args, v.code); push(atys, v.ty); i = i + 1 }
|
||||
let rl = llty(fn2.ty)
|
||||
emit(" ")
|
||||
let rreg = "0"
|
||||
if not streq(rl, "void") { rreg = nreg() emit(rreg) emit(" = ") }
|
||||
emit("call ") emit(rl) emit(" @fn_") emit(cname) emit("(")
|
||||
if not streq(rl, "void") { rreg = nreg(); emit(rreg); emit(" = ") }
|
||||
emit("call "); emit(rl); emit(" @fn_"); emit(cname); emit("(")
|
||||
i = 0
|
||||
while i < len(args) {
|
||||
if i > 0 { emit(", ") }
|
||||
emit(llty(atys[i])) emit(" ") emit(args[i])
|
||||
emit(llty(atys[i])); emit(" "); emit(args[i])
|
||||
i = i + 1
|
||||
}
|
||||
emit(")
|
||||
")
|
||||
emit(")\n")
|
||||
return val(rreg, fn2.ty)
|
||||
}
|
||||
|
||||
|
|
@ -153,8 +152,14 @@ fn emit_expr(e: Node) -> Val {
|
|||
if is_ui_ident(e.s) { return val(itoa(ui_index_of(e.s)), "int") }
|
||||
perr(sconcat("unknown identifier ", e.s))
|
||||
}
|
||||
if e.kind == E_MEMBER { let a = emit_member_addr(e) return emit_load_at(a, g_addr_ty) }
|
||||
if e.kind == E_INDEX { let a = emit_index_addr(e) return emit_load_at(a, g_addr_ty) }
|
||||
if e.kind == E_MEMBER {
|
||||
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_BIN { return emit_bin(e) }
|
||||
if e.kind == E_UN {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
# called only when the runtime defines them.
|
||||
|
||||
fn emit_system_fn(sys: Node) -> void {
|
||||
ll_t = 0 ll_lbl = 0 g_term = false loc_reset() nloop = 0 nself = 0
|
||||
ll_t = 0; ll_lbl = 0; g_term = false; loc_reset(); nloop = 0; nself = 0
|
||||
ret_ty = "void"
|
||||
let fbody = buf_new()
|
||||
falloc = buf_new()
|
||||
|
|
@ -14,7 +14,7 @@ fn emit_system_fn(sys: Node) -> void {
|
|||
if not g_term { emit(" br label %ret\n") }
|
||||
emit("ret:\n ret void\n")
|
||||
code = saved
|
||||
emit("define void @sys_") emit(sys.s) emit("() {\nentry:\n")
|
||||
emit("define void @sys_"); emit(sys.s); emit("() {\nentry:\n")
|
||||
emit(buf_str(falloc))
|
||||
emit(buf_str(fbody))
|
||||
emit("}\n\n")
|
||||
|
|
@ -24,7 +24,7 @@ fn emit_calls_for_phase(phase: ptr) -> void {
|
|||
let i = 0
|
||||
while i < len(prog) {
|
||||
let d = prog[i]
|
||||
if d.kind == N_SYS and streq(d.ty, phase) { emit(" call void @sys_") emit(d.s) emit("()\n") }
|
||||
if d.kind == N_SYS and streq(d.ty, phase) { emit(" call void @sys_"); emit(d.s); emit("()\n") }
|
||||
i = i + 1
|
||||
}
|
||||
}
|
||||
|
|
@ -32,7 +32,7 @@ fn emit_calls_for_phase(phase: ptr) -> void {
|
|||
fn emit_game_main() -> void {
|
||||
# every system becomes a function first
|
||||
let i = 0
|
||||
while i < len(prog) { if prog[i].kind == N_SYS { emit_system_fn(prog[i]) } i = i + 1 }
|
||||
while i < len(prog) { if prog[i].kind == N_SYS { emit_system_fn(prog[i]) }; i = i + 1 }
|
||||
|
||||
emit("define i32 @main(i32 %argc, ptr %argv) {\nentry:\n")
|
||||
emit(" store i32 %argc, ptr @L_argc\n")
|
||||
|
|
@ -47,14 +47,14 @@ fn emit_game_main() -> void {
|
|||
let pr = emit_bind("call i32 @fn_rt_running()")
|
||||
let pc = emit_bind(sconcat("icmp ne i32 ", sconcat(pr, ", 0")))
|
||||
let go = emit_bind(sconcat("and i1 ", sconcat(rc, sconcat(", ", pc))))
|
||||
emit(" br i1 ") emit(go) emit(", label %body, label %done\n")
|
||||
emit(" br i1 "); emit(go); emit(", label %body, label %done\n")
|
||||
} else {
|
||||
emit(" br i1 ") emit(rc) emit(", label %body, label %done\n")
|
||||
emit(" br i1 "); emit(rc); emit(", label %body, label %done\n")
|
||||
}
|
||||
emit("body:\n")
|
||||
if not ptr_is_null(find_fn("rt_poll")) {
|
||||
let k = emit_bind("call i32 @fn_rt_poll()")
|
||||
emit(" store i32 ") emit(k) emit(", ptr @L_key\n")
|
||||
emit(" store i32 "); emit(k); emit(", ptr @L_key\n")
|
||||
}
|
||||
emit_calls_for_phase("Input")
|
||||
emit_calls_for_phase("FixedUpdate")
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
# emit_head.ludic — string constants and the module header (libc declarations,
|
||||
# the slice header type, struct layouts, globals, argv, format strings).
|
||||
|
||||
fn hexdig(n: int) -> int { if n < 10 { return 48 + n } return 55 + n } # 0-9 A-F
|
||||
fn hexdig(n: int) -> int { if n < 10 { return 48 + n }; return 55 + n } # 0-9 A-F
|
||||
|
||||
# emit `@.strN = ... c"escaped\00"` and return its name; % and non-print -> \XX
|
||||
fn emit_str_const(s: ptr) -> ptr {
|
||||
let name = sconcat("@.str", itoa(ll_str))
|
||||
ll_str = ll_str + 1
|
||||
let n = slen(s)
|
||||
emith(name) emith(" = private unnamed_addr constant [")
|
||||
emith(itoa(n + 1)) emith(" x i8] c\"")
|
||||
emith(name); emith(" = private unnamed_addr constant [")
|
||||
emith(itoa(n + 1)); emith(" x i8] c\"")
|
||||
let i = 0
|
||||
while i < n {
|
||||
let c = peek8(s, i)
|
||||
|
|
@ -27,14 +27,14 @@ fn emit_str_const(s: ptr) -> ptr {
|
|||
# a fresh SSA register bound to a `getelementptr`, returned as its name
|
||||
fn emit_gep_i8(base: ptr, idx: ptr) -> ptr {
|
||||
let r = nreg()
|
||||
emit(" ") emit(r) emit(" = getelementptr inbounds i8, ptr ")
|
||||
emit(base) emit(", i32 ") emit(idx) emit("\n")
|
||||
emit(" "); emit(r); emit(" = getelementptr inbounds i8, ptr ")
|
||||
emit(base); emit(", i32 "); emit(idx); emit("\n")
|
||||
return r
|
||||
}
|
||||
|
||||
# the constant initializer for a global var: a literal, or 0/null
|
||||
fn global_init(d: Node) -> ptr {
|
||||
if ptr_is_null(d.a) { if streq(llty(d.ty), "ptr") { return "null" } return "0" }
|
||||
if ptr_is_null(d.a) { if streq(llty(d.ty), "ptr") { return "null" }; return "0" }
|
||||
let e = d.a
|
||||
if e.kind == E_INT or e.kind == E_FLOAT or e.kind == E_BOOL { return itoa(e.ival) }
|
||||
if e.kind == E_UN and streq(e.s, "-") and e.a.kind == E_INT { return sconcat("-", itoa(e.a.ival)) }
|
||||
|
|
@ -74,14 +74,14 @@ fn emit_header() -> void {
|
|||
emith("@L_argc = internal global i32 0\n")
|
||||
emith("@L_argv = internal global ptr null\n")
|
||||
emith("@.gametitle = private unnamed_addr constant [")
|
||||
emith(itoa(slen(g_game_name) + 1)) emith(" x i8] c\"") emith(g_game_name) emith("\\00\"\n")
|
||||
emith(itoa(slen(g_game_name) + 1)); emith(" x i8] c\""); emith(g_game_name); emith("\\00\"\n")
|
||||
emith("%LSlice = type { ptr, i32, i32 }\n")
|
||||
# struct layouts
|
||||
let i = 0
|
||||
while i < len(prog) {
|
||||
let d = prog[i]
|
||||
if d.kind == N_STRUCT {
|
||||
emith("%Str_") emith(d.s) emith(" = type { ")
|
||||
emith("%Str_"); emith(d.s); emith(" = type { ")
|
||||
if len(d.kids) == 0 { emith("i32") }
|
||||
let f = 0
|
||||
while f < len(d.kids) {
|
||||
|
|
@ -98,8 +98,8 @@ fn emit_header() -> void {
|
|||
while i < len(prog) {
|
||||
let d = prog[i]
|
||||
if d.kind == N_VAR {
|
||||
emith("@g_") emith(d.s) emith(" = internal global ")
|
||||
emith(llty(d.ty)) emith(" ")
|
||||
emith("@g_"); emith(d.s); emith(" = internal global ")
|
||||
emith(llty(d.ty)); emith(" ")
|
||||
emith(global_init(d))
|
||||
emith("\n")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,9 +19,9 @@ fn is_intrinsic(name: ptr) -> bool {
|
|||
return false
|
||||
}
|
||||
# emit " <r> = <rest>\n" and return r
|
||||
fn emit_bind(rest: ptr) -> ptr { let r = nreg() emit(" ") emit(r) emit(" = ") emit(rest) emit("\n") return r }
|
||||
fn emit_bind(rest: ptr) -> ptr { let r = nreg(); emit(" "); emit(r); emit(" = "); emit(rest); emit("\n"); return r }
|
||||
|
||||
fn arg_code(e: Node, i: int) -> ptr { let v = emit_expr(e.kids[i]) return v.code }
|
||||
fn arg_code(e: Node, i: int) -> ptr { let v = emit_expr(e.kids[i]); return v.code }
|
||||
|
||||
fn emit_intrinsic(name: ptr, e: Node) -> Val {
|
||||
g_intrin_ok = true
|
||||
|
|
@ -32,21 +32,21 @@ fn emit_intrinsic(name: ptr, e: Node) -> Val {
|
|||
return val(emit_bind(sconcat("call ptr @malloc(i64 ", sconcat(w, ")"))), "ptr")
|
||||
}
|
||||
if streq(name, "mem_realloc") {
|
||||
let p = arg_code(e, 0) let n = arg_code(e, 1)
|
||||
let p = arg_code(e, 0); let n = arg_code(e, 1)
|
||||
let w = emit_bind(sconcat("zext i32 ", sconcat(n, " to i64")))
|
||||
return val(emit_bind(sconcat("call ptr @realloc(ptr ", sconcat(p, sconcat(", i64 ", sconcat(w, ")"))))), "ptr")
|
||||
}
|
||||
if streq(name, "peek8") {
|
||||
let p = arg_code(e, 0) let i = arg_code(e, 1)
|
||||
let p = arg_code(e, 0); let i = arg_code(e, 1)
|
||||
let a = emit_gep_i8(p, i)
|
||||
let b = emit_bind(sconcat("load i8, ptr ", a))
|
||||
return val(emit_bind(sconcat("zext i8 ", sconcat(b, " to i32"))), "int")
|
||||
}
|
||||
if streq(name, "poke8") {
|
||||
let p = arg_code(e, 0) let i = arg_code(e, 1) let v = arg_code(e, 2)
|
||||
let p = arg_code(e, 0); let i = arg_code(e, 1); let v = arg_code(e, 2)
|
||||
let a = emit_gep_i8(p, i)
|
||||
let t = emit_bind(sconcat("trunc i32 ", sconcat(v, " to i8")))
|
||||
emit(" store i8 ") emit(t) emit(", ptr ") emit(a) emit("\n")
|
||||
emit(" store i8 "); emit(t); emit(", ptr "); emit(a); emit("\n")
|
||||
return val("0", "void")
|
||||
}
|
||||
if streq(name, "ptr_is_null") {
|
||||
|
|
@ -55,11 +55,11 @@ fn emit_intrinsic(name: ptr, e: Node) -> Val {
|
|||
return val(emit_bind(sconcat("zext i1 ", sconcat(c, " to i32"))), "bool")
|
||||
}
|
||||
if streq(name, "file_open") {
|
||||
let p = arg_code(e, 0) let m = arg_code(e, 1)
|
||||
let p = arg_code(e, 0); let m = arg_code(e, 1)
|
||||
return val(emit_bind(sconcat("call ptr @fopen(ptr ", sconcat(p, sconcat(", ptr ", sconcat(m, ")"))))), "ptr")
|
||||
}
|
||||
if streq(name, "file_read") or streq(name, "file_write") {
|
||||
let f = arg_code(e, 0) let b = arg_code(e, 1) let n = arg_code(e, 2)
|
||||
let f = arg_code(e, 0); let b = arg_code(e, 1); let n = arg_code(e, 2)
|
||||
let w = emit_bind(sconcat("zext i32 ", sconcat(n, " to i64")))
|
||||
let fn2 = "@fread"
|
||||
if streq(name, "file_write") { fn2 = "@fwrite" }
|
||||
|
|
@ -67,7 +67,7 @@ fn emit_intrinsic(name: ptr, e: Node) -> Val {
|
|||
return val(emit_bind(sconcat("trunc i64 ", sconcat(r, " to i32"))), "int")
|
||||
}
|
||||
if streq(name, "file_seek") {
|
||||
let f = arg_code(e, 0) let off = arg_code(e, 1) let wh = arg_code(e, 2)
|
||||
let f = arg_code(e, 0); let off = arg_code(e, 1); let wh = arg_code(e, 2)
|
||||
let o = emit_bind(sconcat("sext i32 ", sconcat(off, " to i64")))
|
||||
return val(emit_bind(sconcat("call i32 @fseek(ptr ", sconcat(f, sconcat(", i64 ", sconcat(o, sconcat(", i32 ", sconcat(wh, ")"))))))), "int")
|
||||
}
|
||||
|
|
@ -78,17 +78,17 @@ fn emit_intrinsic(name: ptr, e: Node) -> Val {
|
|||
}
|
||||
if streq(name, "file_close") {
|
||||
let f = arg_code(e, 0)
|
||||
emit(" call i32 @fclose(ptr ") emit(f) emit(")\n")
|
||||
emit(" call i32 @fclose(ptr "); emit(f); emit(")\n")
|
||||
return val("0", "void")
|
||||
}
|
||||
if streq(name, "print_str") {
|
||||
let s = arg_code(e, 0)
|
||||
emit(" call i32 (ptr, ...) @printf(ptr @.fmt_str, ptr ") emit(s) emit(")\n")
|
||||
emit(" call i32 (ptr, ...) @printf(ptr @.fmt_str, ptr "); emit(s); emit(")\n")
|
||||
return val("0", "void")
|
||||
}
|
||||
if streq(name, "print_int") {
|
||||
let n = arg_code(e, 0)
|
||||
emit(" call i32 (ptr, ...) @printf(ptr @.fmt_int, i32 ") emit(n) emit(")\n")
|
||||
emit(" call i32 (ptr, ...) @printf(ptr @.fmt_int, i32 "); emit(n); emit(")\n")
|
||||
return val("0", "void")
|
||||
}
|
||||
if streq(name, "os_argc") { return val(emit_bind("load i32, ptr @L_argc"), "int") }
|
||||
|
|
@ -100,7 +100,7 @@ fn emit_intrinsic(name: ptr, e: Node) -> Val {
|
|||
}
|
||||
if streq(name, "os_exit") {
|
||||
let n = arg_code(e, 0)
|
||||
emit(" call void @exit(i32 ") emit(n) emit(")\n")
|
||||
emit(" call void @exit(i32 "); emit(n); emit(")\n")
|
||||
emit(" unreachable\n")
|
||||
g_term = true
|
||||
return val("0", "void")
|
||||
|
|
@ -114,9 +114,9 @@ fn emit_intrinsic(name: ptr, e: Node) -> Val {
|
|||
let n = arg_code(e, 0)
|
||||
return val(emit_bind(sconcat("call ptr @getenv(ptr ", sconcat(n, ")"))), "str")
|
||||
}
|
||||
if streq(name, "bnot") { let a = arg_code(e, 0) return val(emit_bind(sconcat("xor i32 ", sconcat(a, ", -1"))), "int") }
|
||||
if streq(name, "bnot") { let a = arg_code(e, 0); return val(emit_bind(sconcat("xor i32 ", sconcat(a, ", -1"))), "int") }
|
||||
if streq(name,"shl") or streq(name,"shr") or streq(name,"band") or streq(name,"bor") or streq(name,"bxor") {
|
||||
let a = arg_code(e, 0) let b = arg_code(e, 1)
|
||||
let a = arg_code(e, 0); let b = arg_code(e, 1)
|
||||
let opc = "shl"
|
||||
if streq(name,"shr") { opc = "lshr" }
|
||||
if streq(name,"band") { opc = "and" }
|
||||
|
|
@ -125,16 +125,16 @@ fn emit_intrinsic(name: ptr, e: Node) -> Val {
|
|||
return val(emit_bind(sconcat(opc, sconcat(" i32 ", sconcat(a, sconcat(", ", b))))), "int")
|
||||
}
|
||||
if streq(name, "peek32") {
|
||||
let p = arg_code(e, 0) let i = arg_code(e, 1)
|
||||
let p = arg_code(e, 0); let i = arg_code(e, 1)
|
||||
let g = nreg()
|
||||
emit(" ") emit(g) emit(" = getelementptr inbounds i32, ptr ") emit(p) emit(", i32 ") emit(i) emit("\n")
|
||||
emit(" "); emit(g); emit(" = getelementptr inbounds i32, ptr "); emit(p); emit(", i32 "); emit(i); emit("\n")
|
||||
return val(emit_bind(sconcat("load i32, ptr ", g)), "int")
|
||||
}
|
||||
if streq(name, "poke32") {
|
||||
let p = arg_code(e, 0) let i = arg_code(e, 1) let v = arg_code(e, 2)
|
||||
let p = arg_code(e, 0); let i = arg_code(e, 1); let v = arg_code(e, 2)
|
||||
let g = nreg()
|
||||
emit(" ") emit(g) emit(" = getelementptr inbounds i32, ptr ") emit(p) emit(", i32 ") emit(i) emit("\n")
|
||||
emit(" store i32 ") emit(v) emit(", ptr ") emit(g) emit("\n")
|
||||
emit(" "); emit(g); emit(" = getelementptr inbounds i32, ptr "); emit(p); emit(", i32 "); emit(i); emit("\n")
|
||||
emit(" store i32 "); emit(v); emit(", ptr "); emit(g); emit("\n")
|
||||
return val("0", "void")
|
||||
}
|
||||
g_intrin_ok = false
|
||||
|
|
|
|||
|
|
@ -18,29 +18,29 @@ fn is_intrinsic2(name: ptr) -> bool {
|
|||
|
||||
fn emit_intrinsic2(name: ptr, e: Node) -> Val {
|
||||
if streq(name, "mem_free") {
|
||||
let p = arg_code(e, 0) emit(" call void @free(ptr ") emit(p) emit(")\n") return val("0", "void")
|
||||
let p = arg_code(e, 0); emit(" call void @free(ptr "); emit(p); emit(")\n"); return val("0", "void")
|
||||
}
|
||||
if streq(name, "mem_copy") {
|
||||
let d = arg_code(e, 0) let s = arg_code(e, 1) let n = arg_code(e, 2)
|
||||
let d = arg_code(e, 0); let s = arg_code(e, 1); let n = arg_code(e, 2)
|
||||
let w = emit_bind(sconcat("zext i32 ", sconcat(n, " to i64")))
|
||||
emit(" call ptr @memcpy(ptr ") emit(d) emit(", ptr ") emit(s) emit(", i64 ") emit(w) emit(")\n")
|
||||
emit(" call ptr @memcpy(ptr "); emit(d); emit(", ptr "); emit(s); emit(", i64 "); emit(w); emit(")\n")
|
||||
return val("0", "void")
|
||||
}
|
||||
if streq(name, "mem_set") {
|
||||
let p = arg_code(e, 0) let v = arg_code(e, 1) let n = arg_code(e, 2)
|
||||
let p = arg_code(e, 0); let v = arg_code(e, 1); let n = arg_code(e, 2)
|
||||
let w = emit_bind(sconcat("zext i32 ", sconcat(n, " to i64")))
|
||||
emit(" call ptr @memset(ptr ") emit(p) emit(", i32 ") emit(v) emit(", i64 ") emit(w) emit(")\n")
|
||||
emit(" call ptr @memset(ptr "); emit(p); emit(", i32 "); emit(v); emit(", i64 "); emit(w); emit(")\n")
|
||||
return val("0", "void")
|
||||
}
|
||||
if streq(name, "ptr_add") {
|
||||
let p = arg_code(e, 0) let n = arg_code(e, 1)
|
||||
let p = arg_code(e, 0); let n = arg_code(e, 1)
|
||||
let g = nreg()
|
||||
emit(" ") emit(g) emit(" = getelementptr inbounds i8, ptr ") emit(p) emit(", i32 ") emit(n) emit("\n")
|
||||
emit(" "); emit(g); emit(" = getelementptr inbounds i8, ptr "); emit(p); emit(", i32 "); emit(n); emit("\n")
|
||||
return val(g, "ptr")
|
||||
}
|
||||
if streq(name, "read_byte") { return val(emit_bind("call i32 @getchar()"), "int") }
|
||||
if streq(name, "write_byte") {
|
||||
let v = arg_code(e, 0) emit(" call i32 @putchar(i32 ") emit(v) emit(")\n") return val("0", "void")
|
||||
let v = arg_code(e, 0); emit(" call i32 @putchar(i32 "); emit(v); emit(")\n"); return val("0", "void")
|
||||
}
|
||||
if streq(name, "str_len") {
|
||||
let s = arg_code(e, 0)
|
||||
|
|
@ -52,44 +52,44 @@ fn emit_intrinsic2(name: ptr, e: Node) -> Val {
|
|||
return val(emit_bind(sconcat("trunc i64 ", sconcat(r, " to i32"))), "int")
|
||||
}
|
||||
if streq(name, "peekf") {
|
||||
let p = arg_code(e, 0) let i = arg_code(e, 1)
|
||||
let g = nreg() emit(" ") emit(g) emit(" = getelementptr inbounds i32, ptr ") emit(p) emit(", i32 ") emit(i) emit("\n")
|
||||
let p = arg_code(e, 0); let i = arg_code(e, 1)
|
||||
let g = nreg(); emit(" "); emit(g); emit(" = getelementptr inbounds i32, ptr "); emit(p); emit(", i32 "); emit(i); emit("\n")
|
||||
return val(emit_bind(sconcat("load i32, ptr ", g)), "fixed")
|
||||
}
|
||||
if streq(name, "pokef") {
|
||||
let p = arg_code(e, 0) let i = arg_code(e, 1) let v = arg_code(e, 2)
|
||||
let g = nreg() emit(" ") emit(g) emit(" = getelementptr inbounds i32, ptr ") emit(p) emit(", i32 ") emit(i) emit("\n")
|
||||
emit(" store i32 ") emit(v) emit(", ptr ") emit(g) emit("\n")
|
||||
let p = arg_code(e, 0); let i = arg_code(e, 1); let v = arg_code(e, 2)
|
||||
let g = nreg(); emit(" "); emit(g); emit(" = getelementptr inbounds i32, ptr "); emit(p); emit(", i32 "); emit(i); emit("\n")
|
||||
emit(" store i32 "); emit(v); emit(", ptr "); emit(g); emit("\n")
|
||||
return val("0", "void")
|
||||
}
|
||||
if streq(name, "peekp") {
|
||||
let p = arg_code(e, 0) let i = arg_code(e, 1)
|
||||
let g = nreg() emit(" ") emit(g) emit(" = getelementptr inbounds ptr, ptr ") emit(p) emit(", i32 ") emit(i) emit("\n")
|
||||
let p = arg_code(e, 0); let i = arg_code(e, 1)
|
||||
let g = nreg(); emit(" "); emit(g); emit(" = getelementptr inbounds ptr, ptr "); emit(p); emit(", i32 "); emit(i); emit("\n")
|
||||
return val(emit_bind(sconcat("load ptr, ptr ", g)), "ptr")
|
||||
}
|
||||
if streq(name, "pokep") {
|
||||
let p = arg_code(e, 0) let i = arg_code(e, 1) let v = arg_code(e, 2)
|
||||
let g = nreg() emit(" ") emit(g) emit(" = getelementptr inbounds ptr, ptr ") emit(p) emit(", i32 ") emit(i) emit("\n")
|
||||
emit(" store ptr ") emit(v) emit(", ptr ") emit(g) emit("\n")
|
||||
let p = arg_code(e, 0); let i = arg_code(e, 1); let v = arg_code(e, 2)
|
||||
let g = nreg(); emit(" "); emit(g); emit(" = getelementptr inbounds ptr, ptr "); emit(p); emit(", i32 "); emit(i); emit("\n")
|
||||
emit(" store ptr "); emit(v); emit(", ptr "); emit(g); emit("\n")
|
||||
return val("0", "void")
|
||||
}
|
||||
if streq(name, "as_fixed") { let a = emit_expr(e.kids[0]) return val(a.code, "fixed") }
|
||||
if streq(name, "as_int") { let a = emit_expr(e.kids[0]) return val(a.code, "int") }
|
||||
if streq(name, "is_windowed") { if g_windowed { return val("1", "bool") } return val("0", "bool") }
|
||||
if streq(name, "as_fixed") { let a = emit_expr(e.kids[0]); return val(a.code, "fixed") }
|
||||
if streq(name, "as_int") { let a = emit_expr(e.kids[0]); return val(a.code, "int") }
|
||||
if streq(name, "is_windowed") { if g_windowed { return val("1", "bool") }; return val("0", "bool") }
|
||||
if streq(name, "game_title") { return val("@.gametitle", "str") }
|
||||
# windowing hooks — declared external; only reached on the is_windowed() branch
|
||||
if streq(name, "win_poll") { return val(emit_bind("call i32 @win_poll()"), "int") }
|
||||
if streq(name, "win_running") { return val(emit_bind("call i32 @win_running()"), "bool") }
|
||||
if streq(name, "win_open") {
|
||||
let a = arg_code(e, 0) let b = arg_code(e, 1) let c = arg_code(e, 2) let d = arg_code(e, 3)
|
||||
emit(" call void @win_open(i32 ") emit(a) emit(", i32 ") emit(b) emit(", i32 ") emit(c) emit(", ptr ") emit(d) emit(")\n")
|
||||
let a = arg_code(e, 0); let b = arg_code(e, 1); let c = arg_code(e, 2); let d = arg_code(e, 3)
|
||||
emit(" call void @win_open(i32 "); emit(a); emit(", i32 "); emit(b); emit(", i32 "); emit(c); emit(", ptr "); emit(d); emit(")\n")
|
||||
return val("0", "void")
|
||||
}
|
||||
if streq(name, "win_present") {
|
||||
let a = arg_code(e, 0) let b = arg_code(e, 1) let c = arg_code(e, 2)
|
||||
emit(" call void @win_present(ptr ") emit(a) emit(", i32 ") emit(b) emit(", i32 ") emit(c) emit(")\n")
|
||||
let a = arg_code(e, 0); let b = arg_code(e, 1); let c = arg_code(e, 2)
|
||||
emit(" call void @win_present(ptr "); emit(a); emit(", i32 "); emit(b); emit(", i32 "); emit(c); emit(")\n")
|
||||
return val("0", "void")
|
||||
}
|
||||
if streq(name, "win_close") { emit(" call void @win_close()\n") return val("0", "void") }
|
||||
if streq(name, "win_close") { emit(" call void @win_close()\n"); return val("0", "void") }
|
||||
return val("0", "void")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,16 +13,16 @@ fn emit_machine(st: Node) -> void {
|
|||
let state = st.kids[i]
|
||||
let v = emit_expr(state.b)
|
||||
let c = emit_bind(sconcat("icmp eq i32 ", sconcat(s, sconcat(", ", v.code))))
|
||||
let body = lbl("sbody") let nxt = lbl("sarm")
|
||||
emit(" br i1 ") emit(c) emit(", label %") emit(body) emit(", label %") emit(nxt) emit("\n")
|
||||
emit(body) emit(":\n") g_term = false
|
||||
let body = lbl("sbody"); let nxt = lbl("sarm")
|
||||
emit(" br i1 "); emit(c); emit(", label %"); emit(body); emit(", label %"); emit(nxt); emit("\n")
|
||||
emit(body); emit(":\n"); g_term = false
|
||||
emit_block(state.a)
|
||||
if not g_term { emit(" br label %") emit(endl) emit("\n") }
|
||||
emit(nxt) emit(":\n") g_term = false
|
||||
if not g_term { emit(" br label %"); emit(endl); emit("\n") }
|
||||
emit(nxt); emit(":\n"); g_term = false
|
||||
i = i + 1
|
||||
}
|
||||
if not g_term { emit(" br label %") emit(endl) emit("\n") }
|
||||
emit(endl) emit(":\n") g_term = false
|
||||
if not g_term { emit(" br label %"); emit(endl); emit("\n") }
|
||||
emit(endl); emit(":\n"); g_term = false
|
||||
nmach = nmach - 1
|
||||
}
|
||||
|
||||
|
|
@ -31,9 +31,9 @@ fn emit_become(st: Node) -> void {
|
|||
let m = mach_stk[nmach - 1]
|
||||
let target: Node = ptr_null()
|
||||
let i = 0
|
||||
while i < len(m.kids) { if streq(m.kids[i].s, st.s) { target = m.kids[i] } i = i + 1 }
|
||||
while i < len(m.kids) { if streq(m.kids[i].s, st.s) { target = m.kids[i] }; i = i + 1 }
|
||||
if ptr_is_null(target) { perr(sconcat("become: no state ", st.s)) }
|
||||
let regv = emit_expr(m.a)
|
||||
let sv = emit_expr(target.b)
|
||||
emit(" call void @fn_rt_setreg(i32 ") emit(regv.code) emit(", i32 ") emit(sv.code) emit(")\n")
|
||||
emit(" call void @fn_rt_setreg(i32 "); emit(regv.code); emit(", i32 "); emit(sv.code); emit(")\n")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,14 +14,14 @@ fn emit_math_builtin(name: ptr, e: Node) -> Val {
|
|||
return val(emit_bind(sconcat("select i1 ", sconcat(c, sconcat(", i32 ", sconcat(n, sconcat(", i32 ", a.code)))))), "int")
|
||||
}
|
||||
if streq(name, "min") or streq(name, "max") {
|
||||
let a = emit_expr(e.kids[0]) let b = emit_expr(e.kids[1])
|
||||
let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1])
|
||||
let op = "slt"
|
||||
if streq(name, "max") { op = "sgt" }
|
||||
let c = emit_bind(sconcat("icmp ", sconcat(op, sconcat(" i32 ", sconcat(a.code, sconcat(", ", b.code))))))
|
||||
return val(emit_bind(sconcat("select i1 ", sconcat(c, sconcat(", i32 ", sconcat(a.code, sconcat(", i32 ", b.code)))))), "int")
|
||||
}
|
||||
# clamp(v, lo, hi) = max(lo, min(v, hi))
|
||||
let v = emit_expr(e.kids[0]) let lo = emit_expr(e.kids[1]) let hi = emit_expr(e.kids[2])
|
||||
let v = emit_expr(e.kids[0]); let lo = emit_expr(e.kids[1]); let hi = emit_expr(e.kids[2])
|
||||
let c1 = emit_bind(sconcat("icmp slt i32 ", sconcat(v.code, sconcat(", ", hi.code))))
|
||||
let t = emit_bind(sconcat("select i1 ", sconcat(c1, sconcat(", i32 ", sconcat(v.code, sconcat(", i32 ", hi.code))))))
|
||||
let c2 = emit_bind(sconcat("icmp sgt i32 ", sconcat(lo.code, sconcat(", ", t))))
|
||||
|
|
|
|||
|
|
@ -16,13 +16,13 @@ fn emit_new_struct(name: ptr) -> Val {
|
|||
while f < len(s.kids) {
|
||||
let fd = s.kids[f]
|
||||
let addr = nreg()
|
||||
emit(" ") emit(addr) emit(" = getelementptr inbounds %Str_") emit(name)
|
||||
emit(", ptr ") emit(obj) emit(", i32 0, i32 ") emit(itoa(f)) emit("\n")
|
||||
emit(" "); emit(addr); emit(" = getelementptr inbounds %Str_"); emit(name)
|
||||
emit(", ptr "); emit(obj); emit(", i32 0, i32 "); emit(itoa(f)); emit("\n")
|
||||
let lt = llty(fd.ty)
|
||||
let v = "0"
|
||||
if streq(lt, "ptr") { v = "null" }
|
||||
if not ptr_is_null(fd.a) { let dv = emit_expr(fd.a) v = dv.code }
|
||||
emit(" store ") emit(lt) emit(" ") emit(v) emit(", ptr ") emit(addr) emit("\n")
|
||||
if not ptr_is_null(fd.a) { let dv = emit_expr(fd.a); v = dv.code }
|
||||
emit(" store "); emit(lt); emit(" "); emit(v); emit(", ptr "); emit(addr); emit("\n")
|
||||
f = f + 1
|
||||
}
|
||||
return val(obj, name)
|
||||
|
|
@ -31,19 +31,19 @@ fn emit_new_struct(name: ptr) -> Val {
|
|||
fn emit_new_slice(ty: ptr) -> Val {
|
||||
let sz = emit_sizeof("%LSlice")
|
||||
let h = emit_bind(sconcat("call ptr @malloc(i64 ", sconcat(sz, ")")))
|
||||
let d0 = nreg() emit(" ") emit(d0) emit(" = getelementptr inbounds %LSlice, ptr ") emit(h) emit(", i32 0, i32 0\n")
|
||||
emit(" store ptr null, ptr ") emit(d0) emit("\n")
|
||||
let d1 = nreg() emit(" ") emit(d1) emit(" = getelementptr inbounds %LSlice, ptr ") emit(h) emit(", i32 0, i32 1\n")
|
||||
emit(" store i32 0, ptr ") emit(d1) emit("\n")
|
||||
let d2 = nreg() emit(" ") emit(d2) emit(" = getelementptr inbounds %LSlice, ptr ") emit(h) emit(", i32 0, i32 2\n")
|
||||
emit(" store i32 0, ptr ") emit(d2) emit("\n")
|
||||
let d0 = nreg(); emit(" "); emit(d0); emit(" = getelementptr inbounds %LSlice, ptr "); emit(h); emit(", i32 0, i32 0\n")
|
||||
emit(" store ptr null, ptr "); emit(d0); emit("\n")
|
||||
let d1 = nreg(); emit(" "); emit(d1); emit(" = getelementptr inbounds %LSlice, ptr "); emit(h); emit(", i32 0, i32 1\n")
|
||||
emit(" store i32 0, ptr "); emit(d1); emit("\n")
|
||||
let d2 = nreg(); emit(" "); emit(d2); emit(" = getelementptr inbounds %LSlice, ptr "); emit(h); emit(", i32 0, i32 2\n")
|
||||
emit(" store i32 0, ptr "); emit(d2); emit("\n")
|
||||
return val(h, ty)
|
||||
}
|
||||
|
||||
fn slice_field(h: ptr, i: int) -> ptr {
|
||||
let r = nreg()
|
||||
emit(" ") emit(r) emit(" = getelementptr inbounds %LSlice, ptr ") emit(h)
|
||||
emit(", i32 0, i32 ") emit(itoa(i)) emit("\n")
|
||||
emit(" "); emit(r); emit(" = getelementptr inbounds %LSlice, ptr "); emit(h)
|
||||
emit(", i32 0, i32 "); emit(itoa(i)); emit("\n")
|
||||
return r
|
||||
}
|
||||
|
||||
|
|
@ -58,13 +58,13 @@ fn emit_push(e: Node) -> Val {
|
|||
let el = slice_elem(s.ty)
|
||||
let elt = llty(el)
|
||||
let h = s.code
|
||||
let lp = slice_field(h, 1) let cp = slice_field(h, 2) let dp = slice_field(h, 0)
|
||||
let lp = slice_field(h, 1); let cp = slice_field(h, 2); let dp = slice_field(h, 0)
|
||||
let l = emit_bind(sconcat("load i32, ptr ", lp))
|
||||
let c = emit_bind(sconcat("load i32, ptr ", cp))
|
||||
let full = emit_bind(sconcat("icmp sge i32 ", sconcat(l, sconcat(", ", c))))
|
||||
let grow = lbl("grow") let put = lbl("put")
|
||||
emit(" br i1 ") emit(full) emit(", label %") emit(grow) emit(", label %") emit(put) emit("\n")
|
||||
emit(grow) emit(":\n")
|
||||
let grow = lbl("grow"); let put = lbl("put")
|
||||
emit(" br i1 "); emit(full); emit(", label %"); emit(grow); emit(", label %"); emit(put); emit("\n")
|
||||
emit(grow); emit(":\n")
|
||||
let dbl = emit_bind(sconcat("mul i32 ", sconcat(c, ", 2")))
|
||||
let isz = emit_bind(sconcat("icmp eq i32 ", sconcat(c, ", 0")))
|
||||
let nc = emit_bind(sconcat("select i1 ", sconcat(isz, sconcat(", i32 8, i32 ", dbl))))
|
||||
|
|
@ -73,16 +73,16 @@ fn emit_push(e: Node) -> Val {
|
|||
let bytes = emit_bind(sconcat("mul i64 ", sconcat(ncw, sconcat(", ", esz))))
|
||||
let old = emit_bind(sconcat("load ptr, ptr ", dp))
|
||||
let nd = emit_bind(sconcat("call ptr @realloc(ptr ", sconcat(old, sconcat(", i64 ", sconcat(bytes, ")")))))
|
||||
emit(" store ptr ") emit(nd) emit(", ptr ") emit(dp) emit("\n")
|
||||
emit(" store i32 ") emit(nc) emit(", ptr ") emit(cp) emit("\n")
|
||||
emit(" br label %") emit(put) emit("\n")
|
||||
emit(put) emit(":\n")
|
||||
emit(" store ptr "); emit(nd); emit(", ptr "); emit(dp); emit("\n")
|
||||
emit(" store i32 "); emit(nc); emit(", ptr "); emit(cp); emit("\n")
|
||||
emit(" br label %"); emit(put); emit("\n")
|
||||
emit(put); emit(":\n")
|
||||
let v = emit_expr(e.kids[1])
|
||||
let data = emit_bind(sconcat("load ptr, ptr ", dp))
|
||||
let slot = nreg()
|
||||
emit(" ") emit(slot) emit(" = getelementptr inbounds ") emit(elt) emit(", ptr ") emit(data) emit(", i32 ") emit(l) emit("\n")
|
||||
emit(" store ") emit(elt) emit(" ") emit(v.code) emit(", ptr ") emit(slot) emit("\n")
|
||||
emit(" "); emit(slot); emit(" = getelementptr inbounds "); emit(elt); emit(", ptr "); emit(data); emit(", i32 "); emit(l); emit("\n")
|
||||
emit(" store "); emit(elt); emit(" "); emit(v.code); emit(", ptr "); emit(slot); emit("\n")
|
||||
let l1 = emit_bind(sconcat("add i32 ", sconcat(l, ", 1")))
|
||||
emit(" store i32 ") emit(l1) emit(", ptr ") emit(lp) emit("\n")
|
||||
emit(" store i32 "); emit(l1); emit(", ptr "); emit(lp); emit("\n")
|
||||
return val("0", "void")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@
|
|||
# the body once per match. Mirrors ll_query in compiler/back/ir_ecs.c.
|
||||
|
||||
fn find_arch_id(name: ptr) -> int {
|
||||
let i = 0 let n = 1
|
||||
while i < len(prog) { if prog[i].kind == N_ARCH { if streq(prog[i].s, name) { return n } n = n + 1 } i = i + 1 }
|
||||
let i = 0; let n = 1
|
||||
while i < len(prog) { if prog[i].kind == N_ARCH { if streq(prog[i].s, name) { return n }; n = n + 1 }; i = i + 1 }
|
||||
return 0
|
||||
}
|
||||
|
||||
|
|
@ -15,22 +15,22 @@ fn emit_query(st: Node) -> void {
|
|||
if nself < len(self_stk) { self_stk[nself] = ip } else { push(self_stk, ip) }
|
||||
nself = nself + 1
|
||||
|
||||
let cond = lbl("qcond") let body = lbl("qbody") let nxt = lbl("qnext") let endl = lbl("qend")
|
||||
emit(" br label %") emit(cond) emit("\n")
|
||||
emit(cond) emit(":\n")
|
||||
let cond = lbl("qcond"); let body = lbl("qbody"); let nxt = lbl("qnext"); let endl = lbl("qend")
|
||||
emit(" br label %"); emit(cond); emit("\n")
|
||||
emit(cond); emit(":\n")
|
||||
let i0 = emit_bind(sconcat("load i32, ptr ", ip))
|
||||
let ec = emit_bind("load i32, ptr @L_entc")
|
||||
let lt = emit_bind(sconcat("icmp slt i32 ", sconcat(i0, sconcat(", ", ec))))
|
||||
emit(" br i1 ") emit(lt) emit(", label %") emit(body) emit(", label %") emit(endl) emit("\n")
|
||||
emit(body) emit(":\n") g_term = false
|
||||
emit(" br i1 "); emit(lt); emit(", label %"); emit(body); emit(", label %"); emit(endl); emit("\n")
|
||||
emit(body); emit(":\n"); g_term = false
|
||||
let i1 = emit_bind(sconcat("load i32, ptr ", ip))
|
||||
# alive?
|
||||
let ap = nreg() emit(" ") emit(ap) emit(" = getelementptr inbounds [") emit(me) emit(" x i32], ptr @L_alive, i32 0, i32 ") emit(i1) emit("\n")
|
||||
let ap = nreg(); emit(" "); emit(ap); emit(" = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_alive, i32 0, i32 "); emit(i1); emit("\n")
|
||||
let al = emit_bind(sconcat("load i32, ptr ", ap))
|
||||
let alc = emit_bind(sconcat("icmp ne i32 ", sconcat(al, ", 0")))
|
||||
let ka = lbl("qa")
|
||||
emit(" br i1 ") emit(alc) emit(", label %") emit(ka) emit(", label %") emit(nxt) emit("\n")
|
||||
emit(ka) emit(":\n")
|
||||
emit(" br i1 "); emit(alc); emit(", label %"); emit(ka); emit(", label %"); emit(nxt); emit("\n")
|
||||
emit(ka); emit(":\n")
|
||||
|
||||
let terms = st.c
|
||||
# component / archetype filters
|
||||
|
|
@ -40,17 +40,17 @@ fn emit_query(st: Node) -> void {
|
|||
let ak = find_arch_id(tm.s)
|
||||
let ok = "0"
|
||||
if ak > 0 {
|
||||
let kp = nreg() emit(" ") emit(kp) emit(" = getelementptr inbounds [") emit(me) emit(" x i32], ptr @L_kind, i32 0, i32 ") emit(i1) emit("\n")
|
||||
let kp = nreg(); emit(" "); emit(kp); emit(" = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_kind, i32 0, i32 "); emit(i1); emit("\n")
|
||||
let kv = emit_bind(sconcat("load i32, ptr ", kp))
|
||||
ok = emit_bind(sconcat("icmp eq i32 ", sconcat(kv, sconcat(", ", itoa(ak)))))
|
||||
} else {
|
||||
let hp = nreg() emit(" ") emit(hp) emit(" = getelementptr inbounds [") emit(me) emit(" x i8], ptr @H_") emit(tm.s) emit(", i32 0, i32 ") emit(i1) emit("\n")
|
||||
let hp = nreg(); emit(" "); emit(hp); emit(" = getelementptr inbounds ["); emit(me); emit(" x i8], ptr @H_"); emit(tm.s); emit(", i32 0, i32 "); emit(i1); emit("\n")
|
||||
let hv = emit_bind(sconcat("load i8, ptr ", hp))
|
||||
ok = emit_bind(sconcat("icmp ne i8 ", sconcat(hv, ", 0")))
|
||||
}
|
||||
let keep = lbl("qt")
|
||||
emit(" br i1 ") emit(ok) emit(", label %") emit(keep) emit(", label %") emit(nxt) emit("\n")
|
||||
emit(keep) emit(":\n")
|
||||
emit(" br i1 "); emit(ok); emit(", label %"); emit(keep); emit(", label %"); emit(nxt); emit("\n")
|
||||
emit(keep); emit(":\n")
|
||||
t = t + 1
|
||||
}
|
||||
|
||||
|
|
@ -63,9 +63,9 @@ fn emit_query(st: Node) -> void {
|
|||
if tm.ival == 0 and find_arch_id(tm.s) == 0 {
|
||||
if vi < len(st.kids) {
|
||||
let slot = nreg()
|
||||
emit(" ") emit(slot) emit(" = getelementptr inbounds [") emit(me) emit(" x %Cmp_") emit(tm.s) emit("], ptr @S_") emit(tm.s) emit(", i32 0, i32 ") emit(i1) emit("\n")
|
||||
emit(" "); emit(slot); emit(" = getelementptr inbounds ["); emit(me); emit(" x %Cmp_"); emit(tm.s); emit("], ptr @S_"); emit(tm.s); emit(", i32 0, i32 "); emit(i1); emit("\n")
|
||||
let vslot = emit_alloca("ptr")
|
||||
emit(" store ptr ") emit(slot) emit(", ptr ") emit(vslot) emit("\n")
|
||||
emit(" store ptr "); emit(slot); emit(", ptr "); emit(vslot); emit("\n")
|
||||
loc_push(st.kids[vi].s, vslot, tm.s)
|
||||
vi = vi + 1
|
||||
}
|
||||
|
|
@ -78,21 +78,21 @@ fn emit_query(st: Node) -> void {
|
|||
let w = emit_expr(st.b)
|
||||
let wc = emit_bind(sconcat("icmp ne i32 ", sconcat(w.code, ", 0")))
|
||||
let kw = lbl("qw")
|
||||
emit(" br i1 ") emit(wc) emit(", label %") emit(kw) emit(", label %") emit(nxt) emit("\n")
|
||||
emit(kw) emit(":\n")
|
||||
emit(" br i1 "); emit(wc); emit(", label %"); emit(kw); emit(", label %"); emit(nxt); emit("\n")
|
||||
emit(kw); emit(":\n")
|
||||
}
|
||||
|
||||
loop_push(nxt, endl)
|
||||
emit_block(st.a)
|
||||
loop_pop()
|
||||
nloc = save
|
||||
if not g_term { emit(" br label %") emit(nxt) emit("\n") }
|
||||
if not g_term { emit(" br label %"); emit(nxt); emit("\n") }
|
||||
|
||||
emit(nxt) emit(":\n")
|
||||
emit(nxt); emit(":\n")
|
||||
let i2 = emit_bind(sconcat("load i32, ptr ", ip))
|
||||
let i3 = emit_bind(sconcat("add i32 ", sconcat(i2, ", 1")))
|
||||
store_at("i32", i3, ip)
|
||||
emit(" br label %") emit(cond) emit("\n")
|
||||
emit(endl) emit(":\n") g_term = false
|
||||
emit(" br label %"); emit(cond); emit("\n")
|
||||
emit(endl); emit(":\n"); g_term = false
|
||||
nself = nself - 1
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@
|
|||
var g_iok: int = 0
|
||||
|
||||
fn emit_io(fn2: ptr, p: ptr, bytes: ptr) -> void {
|
||||
let r = sconcat("%io", itoa(g_iok)) g_iok = g_iok + 1
|
||||
emit(" ") emit(r) emit(" = call i64 @") emit(fn2) emit("(ptr ") emit(p) emit(", i64 1, i64 ") emit(bytes) emit(", ptr %f)\n")
|
||||
let r = sconcat("%io", itoa(g_iok)); g_iok = g_iok + 1
|
||||
emit(" "); emit(r); emit(" = call i64 @"); emit(fn2); emit("(ptr "); emit(p); emit(", i64 1, i64 "); emit(bytes); emit(", ptr %f)\n")
|
||||
}
|
||||
|
||||
fn emit_snapshot_blocks(fn2: ptr) -> void {
|
||||
|
|
@ -15,19 +15,19 @@ fn emit_snapshot_blocks(fn2: ptr) -> void {
|
|||
let me = itoa(MAX_ENT)
|
||||
emit_io(fn2, "@L_entc", "4")
|
||||
emit_io(fn2, "@L_freen", "4")
|
||||
emit(" %nalive = mul i64 ") emit(me) emit(", 4\n")
|
||||
emit(" %nalive = mul i64 "); emit(me); emit(", 4\n")
|
||||
emit_io(fn2, "@L_alive", "%nalive")
|
||||
emit_io(fn2, "@L_freelist", "%nalive")
|
||||
emit_io(fn2, "@L_kind", "%nalive")
|
||||
let i = 0
|
||||
while i < len(prog) { if prog[i].kind == N_VAR { emit_io(fn2, sconcat("@g_", prog[i].s), "4") } i = i + 1 }
|
||||
while i < len(prog) { if prog[i].kind == N_VAR { emit_io(fn2, sconcat("@g_", prog[i].s), "4") }; i = i + 1 }
|
||||
let ci = 0
|
||||
i = 0
|
||||
while i < len(prog) {
|
||||
if prog[i].kind == N_COMP {
|
||||
let c = prog[i].s
|
||||
let csz = sconcat("%csz", itoa(ci))
|
||||
emit(" ") emit(csz) emit(" = ptrtoint ptr getelementptr (%Cmp_") emit(c) emit(", ptr null, i32 ") emit(me) emit(") to i64\n")
|
||||
emit(" "); emit(csz); emit(" = ptrtoint ptr getelementptr (%Cmp_"); emit(c); emit(", ptr null, i32 "); emit(me); emit(") to i64\n")
|
||||
emit_io(fn2, sconcat("@S_", c), csz)
|
||||
emit_io(fn2, sconcat("@H_", c), me)
|
||||
ci = ci + 1
|
||||
|
|
|
|||
|
|
@ -5,21 +5,21 @@ fn emit_init_component(e: ptr, comp: ptr, rec: Node) -> void {
|
|||
let me = itoa(MAX_ENT)
|
||||
let c = find_comp(comp)
|
||||
if ptr_is_null(c) { perr(sconcat("spawn: unknown component ", comp)) }
|
||||
let hp = nreg() emit(" ") emit(hp) emit(" = getelementptr inbounds [") emit(me) emit(" x i8], ptr @H_") emit(comp) emit(", i32 0, i32 ") emit(e) emit("\n")
|
||||
emit(" store i8 1, ptr ") emit(hp) emit("\n")
|
||||
let hp = nreg(); emit(" "); emit(hp); emit(" = getelementptr inbounds ["); emit(me); emit(" x i8], ptr @H_"); emit(comp); emit(", i32 0, i32 "); emit(e); emit("\n")
|
||||
emit(" store i8 1, ptr "); emit(hp); emit("\n")
|
||||
let slot = nreg()
|
||||
emit(" ") emit(slot) emit(" = getelementptr inbounds [") emit(me) emit(" x %Cmp_") emit(comp) emit("], ptr @S_") emit(comp) emit(", i32 0, i32 ") emit(e) emit("\n")
|
||||
emit(" "); emit(slot); emit(" = getelementptr inbounds ["); emit(me); emit(" x %Cmp_"); emit(comp); emit("], ptr @S_"); emit(comp); emit(", i32 0, i32 "); emit(e); emit("\n")
|
||||
# defaults
|
||||
let f = 0
|
||||
while f < len(c.kids) {
|
||||
let fd = c.kids[f]
|
||||
let addr = nreg()
|
||||
emit(" ") emit(addr) emit(" = getelementptr inbounds %Cmp_") emit(comp) emit(", ptr ") emit(slot) emit(", i32 0, i32 ") emit(itoa(f)) emit("\n")
|
||||
emit(" "); emit(addr); emit(" = getelementptr inbounds %Cmp_"); emit(comp); emit(", ptr "); emit(slot); emit(", i32 0, i32 "); emit(itoa(f)); emit("\n")
|
||||
let lt = llty(fd.ty)
|
||||
let v = "0"
|
||||
if streq(lt, "ptr") { v = "null" }
|
||||
if not ptr_is_null(fd.a) { let dv = emit_expr(fd.a) v = dv.code }
|
||||
emit(" store ") emit(lt) emit(" ") emit(v) emit(", ptr ") emit(addr) emit("\n")
|
||||
if not ptr_is_null(fd.a) { let dv = emit_expr(fd.a); v = dv.code }
|
||||
emit(" store "); emit(lt); emit(" "); emit(v); emit(", ptr "); emit(addr); emit("\n")
|
||||
f = f + 1
|
||||
}
|
||||
# per-spawn overrides
|
||||
|
|
@ -30,9 +30,9 @@ fn emit_init_component(e: ptr, comp: ptr, rec: Node) -> void {
|
|||
let fidx = field_index(c, fi.s)
|
||||
if fidx >= 0 {
|
||||
let addr = nreg()
|
||||
emit(" ") emit(addr) emit(" = getelementptr inbounds %Cmp_") emit(comp) emit(", ptr ") emit(slot) emit(", i32 0, i32 ") emit(itoa(fidx)) emit("\n")
|
||||
emit(" "); emit(addr); emit(" = getelementptr inbounds %Cmp_"); emit(comp); emit(", ptr "); emit(slot); emit(", i32 0, i32 "); emit(itoa(fidx)); emit("\n")
|
||||
let dv = emit_expr(fi.a)
|
||||
emit(" store ") emit(llty(field_type(c, fi.s))) emit(" ") emit(dv.code) emit(", ptr ") emit(addr) emit("\n")
|
||||
emit(" store "); emit(llty(field_type(c, fi.s))); emit(" "); emit(dv.code); emit(", ptr "); emit(addr); emit("\n")
|
||||
}
|
||||
j = j + 1
|
||||
}
|
||||
|
|
@ -44,25 +44,25 @@ fn emit_spawn(st: Node) -> void {
|
|||
let ak = find_arch_id(st.s)
|
||||
if ak > 0 {
|
||||
let me = itoa(MAX_ENT)
|
||||
let kp = nreg() emit(" ") emit(kp) emit(" = getelementptr inbounds [") emit(me) emit(" x i32], ptr @L_kind, i32 0, i32 ") emit(e) emit("\n")
|
||||
emit(" store i32 ") emit(itoa(ak)) emit(", ptr ") emit(kp) emit("\n")
|
||||
let kp = nreg(); emit(" "); emit(kp); emit(" = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_kind, i32 0, i32 "); emit(e); emit("\n")
|
||||
emit(" store i32 "); emit(itoa(ak)); emit(", ptr "); emit(kp); emit("\n")
|
||||
let arch = find_arch(st.s)
|
||||
let c = 0
|
||||
while c < len(arch.kids) {
|
||||
let cn = arch.kids[c].s
|
||||
let rec = ptr_null()
|
||||
let i = 0
|
||||
while i < len(st.kids) { if streq(st.kids[i].s, cn) { rec = st.kids[i].a } i = i + 1 }
|
||||
while i < len(st.kids) { if streq(st.kids[i].s, cn) { rec = st.kids[i].a }; i = i + 1 }
|
||||
emit_init_component(e, cn, rec)
|
||||
c = c + 1
|
||||
}
|
||||
} else {
|
||||
let i = 0
|
||||
while i < len(st.kids) { emit_init_component(e, st.kids[i].s, st.kids[i].a) i = i + 1 }
|
||||
while i < len(st.kids) { emit_init_component(e, st.kids[i].s, st.kids[i].a); i = i + 1 }
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_despawn(st: Node) -> void {
|
||||
let v = emit_expr(st.a)
|
||||
emit(" call void @L_free_entity(i32 ") emit(v.code) emit(")\n")
|
||||
emit(" call void @L_free_entity(i32 "); emit(v.code); emit(")\n")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ fn emit_block(b: Node) -> void {
|
|||
|
||||
# store `val` (llvm type `lt`) into address `addr`
|
||||
fn store_at(lt: ptr, v: ptr, addr: ptr) -> void {
|
||||
emit(" store ") emit(lt) emit(" ") emit(v) emit(", ptr ") emit(addr) emit("\n")
|
||||
emit(" store "); emit(lt); emit(" "); emit(v); emit(", ptr "); emit(addr); emit("\n")
|
||||
}
|
||||
|
||||
fn emit_assign(st: Node) -> void {
|
||||
|
|
@ -22,15 +22,15 @@ fn emit_assign(st: Node) -> void {
|
|||
let ty = "int"
|
||||
if t.kind == E_ID {
|
||||
let li = loc_find(t.s)
|
||||
if li >= 0 { addr = loc_reg[li] ty = loc_ty[li] }
|
||||
if li >= 0 { addr = loc_reg[li]; ty = loc_ty[li] }
|
||||
else {
|
||||
let g = find_global(t.s)
|
||||
if ptr_is_null(g) { perr(sconcat("assign to unknown ", t.s)) }
|
||||
addr = sconcat("@g_", t.s) ty = g.ty
|
||||
addr = sconcat("@g_", t.s); ty = g.ty
|
||||
}
|
||||
} else {
|
||||
if t.kind == E_MEMBER { addr = emit_member_addr(t) ty = g_addr_ty }
|
||||
else { if t.kind == E_INDEX { addr = emit_index_addr(t) ty = g_addr_ty }
|
||||
if t.kind == E_MEMBER { addr = emit_member_addr(t); ty = g_addr_ty }
|
||||
else { if t.kind == E_INDEX { addr = emit_index_addr(t); ty = g_addr_ty }
|
||||
else { perr("bad assignment target") } }
|
||||
}
|
||||
let lt = llty(ty)
|
||||
|
|
@ -51,34 +51,34 @@ fn emit_if(st: Node) -> void {
|
|||
let c = emit_expr(st.a)
|
||||
let cc = emit_bind(sconcat("icmp ne i32 ", sconcat(c.code, ", 0")))
|
||||
let has_else = not ptr_is_null(st.c)
|
||||
let tl = lbl("then") let el = lbl("else") let en = lbl("ifend")
|
||||
if has_else { emit(" br i1 ") emit(cc) emit(", label %") emit(tl) emit(", label %") emit(el) emit("\n") }
|
||||
else { emit(" br i1 ") emit(cc) emit(", label %") emit(tl) emit(", label %") emit(en) emit("\n") }
|
||||
emit(tl) emit(":\n") g_term = false
|
||||
let tl = lbl("then"); let el = lbl("else"); let en = lbl("ifend")
|
||||
if has_else { emit(" br i1 "); emit(cc); emit(", label %"); emit(tl); emit(", label %"); emit(el); emit("\n") }
|
||||
else { emit(" br i1 "); emit(cc); emit(", label %"); emit(tl); emit(", label %"); emit(en); emit("\n") }
|
||||
emit(tl); emit(":\n"); g_term = false
|
||||
emit_block(st.b)
|
||||
if not g_term { emit(" br label %") emit(en) emit("\n") }
|
||||
if not g_term { emit(" br label %"); emit(en); emit("\n") }
|
||||
if has_else {
|
||||
emit(el) emit(":\n") g_term = false
|
||||
emit(el); emit(":\n"); g_term = false
|
||||
# else is either a block or a nested if-statement
|
||||
if st.c.kind == S_IF { emit_stmt(st.c) } else { emit_block(st.c) }
|
||||
if not g_term { emit(" br label %") emit(en) emit("\n") }
|
||||
if not g_term { emit(" br label %"); emit(en); emit("\n") }
|
||||
}
|
||||
emit(en) emit(":\n") g_term = false
|
||||
emit(en); emit(":\n"); g_term = false
|
||||
}
|
||||
|
||||
fn emit_while(st: Node) -> void {
|
||||
let cl = lbl("wcond") let bl = lbl("wbody") let en = lbl("wend")
|
||||
emit(" br label %") emit(cl) emit("\n")
|
||||
emit(cl) emit(":\n")
|
||||
let cl = lbl("wcond"); let bl = lbl("wbody"); let en = lbl("wend")
|
||||
emit(" br label %"); emit(cl); emit("\n")
|
||||
emit(cl); emit(":\n")
|
||||
let c = emit_expr(st.a)
|
||||
let cc = emit_bind(sconcat("icmp ne i32 ", sconcat(c.code, ", 0")))
|
||||
emit(" br i1 ") emit(cc) emit(", label %") emit(bl) emit(", label %") emit(en) emit("\n")
|
||||
emit(bl) emit(":\n") g_term = false
|
||||
emit(" br i1 "); emit(cc); emit(", label %"); emit(bl); emit(", label %"); emit(en); emit("\n")
|
||||
emit(bl); emit(":\n"); g_term = false
|
||||
loop_push(cl, en)
|
||||
emit_block(st.b)
|
||||
loop_pop()
|
||||
if not g_term { emit(" br label %") emit(cl) emit("\n") }
|
||||
emit(en) emit(":\n") g_term = false
|
||||
if not g_term { emit(" br label %"); emit(cl); emit("\n") }
|
||||
emit(en); emit(":\n"); g_term = false
|
||||
}
|
||||
|
||||
fn emit_for(st: Node) -> void {
|
||||
|
|
@ -86,24 +86,24 @@ fn emit_for(st: Node) -> void {
|
|||
let lo = emit_expr(st.a)
|
||||
store_at("i32", lo.code, slot)
|
||||
loc_push(st.s, slot, "int")
|
||||
let cl = lbl("fcond") let bl = lbl("fbody") let ct = lbl("fcont") let en = lbl("fend")
|
||||
emit(" br label %") emit(cl) emit("\n")
|
||||
emit(cl) emit(":\n")
|
||||
let cl = lbl("fcond"); let bl = lbl("fbody"); let ct = lbl("fcont"); let en = lbl("fend")
|
||||
emit(" br label %"); emit(cl); emit("\n")
|
||||
emit(cl); emit(":\n")
|
||||
let iv = emit_bind(sconcat("load i32, ptr ", slot))
|
||||
let hi = emit_expr(st.b)
|
||||
let cc = emit_bind(sconcat("icmp slt i32 ", sconcat(iv, sconcat(", ", hi.code))))
|
||||
emit(" br i1 ") emit(cc) emit(", label %") emit(bl) emit(", label %") emit(en) emit("\n")
|
||||
emit(bl) emit(":\n") g_term = false
|
||||
emit(" br i1 "); emit(cc); emit(", label %"); emit(bl); emit(", label %"); emit(en); emit("\n")
|
||||
emit(bl); emit(":\n"); g_term = false
|
||||
loop_push(ct, en)
|
||||
emit_block(st.c)
|
||||
loop_pop()
|
||||
if not g_term { emit(" br label %") emit(ct) emit("\n") }
|
||||
emit(ct) emit(":\n")
|
||||
if not g_term { emit(" br label %"); emit(ct); emit("\n") }
|
||||
emit(ct); emit(":\n")
|
||||
let i2 = emit_bind(sconcat("load i32, ptr ", slot))
|
||||
let i3 = emit_bind(sconcat("add i32 ", sconcat(i2, ", 1")))
|
||||
store_at("i32", i3, slot)
|
||||
emit(" br label %") emit(cl) emit("\n")
|
||||
emit(en) emit(":\n") g_term = false
|
||||
emit(" br label %"); emit(cl); emit("\n")
|
||||
emit(en); emit(":\n"); g_term = false
|
||||
}
|
||||
|
||||
fn emit_return(st: Node) -> void {
|
||||
|
|
@ -117,7 +117,7 @@ fn emit_return(st: Node) -> void {
|
|||
|
||||
fn arm_is_default(arm: Node) -> bool {
|
||||
let p = 0
|
||||
while p < len(arm.kids) { if arm.kids[p].kind == E_ID and streq(arm.kids[p].s, "_") { return true } p = p + 1 }
|
||||
while p < len(arm.kids) { if arm.kids[p].kind == E_ID and streq(arm.kids[p].s, "_") { return true }; p = p + 1 }
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
@ -136,58 +136,58 @@ fn emit_match(st: Node) -> void {
|
|||
while p < len(arm.kids) {
|
||||
let pv = emit_expr(arm.kids[p])
|
||||
let c = emit_bind(sconcat("icmp eq i32 ", sconcat(sv.code, sconcat(", ", pv.code))))
|
||||
if first { acc = c first = false }
|
||||
if first { acc = c; first = false }
|
||||
else { acc = emit_bind(sconcat("or i1 ", sconcat(acc, sconcat(", ", c)))) }
|
||||
p = p + 1
|
||||
}
|
||||
let bodyl = lbl("mbody") let nextl = lbl("marm")
|
||||
emit(" br i1 ") emit(acc) emit(", label %") emit(bodyl) emit(", label %") emit(nextl) emit("\n")
|
||||
emit(bodyl) emit(":\n") g_term = false
|
||||
let bodyl = lbl("mbody"); let nextl = lbl("marm")
|
||||
emit(" br i1 "); emit(acc); emit(", label %"); emit(bodyl); emit(", label %"); emit(nextl); emit("\n")
|
||||
emit(bodyl); emit(":\n"); g_term = false
|
||||
emit_block(arm.a)
|
||||
if not g_term { emit(" br label %") emit(endl) emit("\n") }
|
||||
emit(nextl) emit(":\n") g_term = false
|
||||
if not g_term { emit(" br label %"); emit(endl); emit("\n") }
|
||||
emit(nextl); emit(":\n"); g_term = false
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
if not ptr_is_null(deflt) { emit_block(deflt.a) }
|
||||
if not g_term { emit(" br label %") emit(endl) emit("\n") }
|
||||
emit(endl) emit(":\n") g_term = false
|
||||
if not g_term { emit(" br label %"); emit(endl); emit("\n") }
|
||||
emit(endl); emit(":\n"); g_term = false
|
||||
}
|
||||
|
||||
fn emit_stmt(st: Node) -> void {
|
||||
if st.kind == S_LET {
|
||||
let ty = st.ty
|
||||
if ptr_is_null(ty) { let v0 = emit_expr(st.a) ty = v0.ty
|
||||
let slot = emit_alloca(llty(ty)) store_at(llty(ty), v0.code, slot) loc_push(st.s, slot, ty) return }
|
||||
if ptr_is_null(ty) { let v0 = emit_expr(st.a); ty = v0.ty
|
||||
let slot = emit_alloca(llty(ty)); store_at(llty(ty), v0.code, slot); loc_push(st.s, slot, ty); return }
|
||||
let slot = emit_alloca(llty(ty))
|
||||
if ptr_is_null(st.a) {
|
||||
let z = "0"
|
||||
if streq(llty(ty), "ptr") { z = "null" }
|
||||
store_at(llty(ty), z, slot)
|
||||
} else { let v = emit_expr(st.a) store_at(llty(ty), v.code, slot) }
|
||||
} else { let v = emit_expr(st.a); store_at(llty(ty), v.code, slot) }
|
||||
loc_push(st.s, slot, ty)
|
||||
return
|
||||
}
|
||||
if st.kind == S_ASSIGN { emit_assign(st) return }
|
||||
if st.kind == S_IF { emit_if(st) return }
|
||||
if st.kind == S_WHILE { emit_while(st) return }
|
||||
if st.kind == S_FOR { emit_for(st) return }
|
||||
if st.kind == S_RETURN { emit_return(st) return }
|
||||
if st.kind == S_BREAK { emit(" br label %") emit(brk_lbl[nloop - 1]) emit("\n") g_term = true return }
|
||||
if st.kind == S_CONTINUE { emit(" br label %") emit(cnt_lbl[nloop - 1]) emit("\n") g_term = true return }
|
||||
if st.kind == S_MATCH { emit_match(st) return }
|
||||
if st.kind == S_QUERY { emit_query(st) return }
|
||||
if st.kind == S_SPAWN { emit_spawn(st) return }
|
||||
if st.kind == S_DESPAWN { emit_despawn(st) return }
|
||||
if st.kind == S_MACHINE { emit_machine(st) return }
|
||||
if st.kind == S_BECOME { emit_become(st) return }
|
||||
if st.kind == S_EXPR { let v = emit_expr(st.a) return }
|
||||
if st.kind == S_ASSIGN { emit_assign(st); return }
|
||||
if st.kind == S_IF { emit_if(st); return }
|
||||
if st.kind == S_WHILE { emit_while(st); return }
|
||||
if st.kind == S_FOR { emit_for(st); return }
|
||||
if st.kind == S_RETURN { emit_return(st); return }
|
||||
if st.kind == S_BREAK { emit(" br label %"); emit(brk_lbl[nloop - 1]); emit("\n"); g_term = true; return }
|
||||
if st.kind == S_CONTINUE { emit(" br label %"); emit(cnt_lbl[nloop - 1]); emit("\n"); g_term = true; return }
|
||||
if st.kind == S_MATCH { emit_match(st); return }
|
||||
if st.kind == S_QUERY { emit_query(st); return }
|
||||
if st.kind == S_SPAWN { emit_spawn(st); return }
|
||||
if st.kind == S_DESPAWN { emit_despawn(st); return }
|
||||
if st.kind == S_MACHINE { emit_machine(st); return }
|
||||
if st.kind == S_BECOME { emit_become(st); return }
|
||||
if st.kind == S_EXPR { let v = emit_expr(st.a); return }
|
||||
perr("cannot emit statement")
|
||||
}
|
||||
|
||||
fn loop_push(cont: ptr, brk: ptr) -> void {
|
||||
if nloop < len(cnt_lbl) { cnt_lbl[nloop] = cont brk_lbl[nloop] = brk }
|
||||
else { push(cnt_lbl, cont) push(brk_lbl, brk) }
|
||||
if nloop < len(cnt_lbl) { cnt_lbl[nloop] = cont; brk_lbl[nloop] = brk }
|
||||
else { push(cnt_lbl, cont); push(brk_lbl, brk) }
|
||||
nloop = nloop + 1
|
||||
}
|
||||
fn loop_pop() -> void { nloop = nloop - 1 }
|
||||
|
|
|
|||
|
|
@ -9,43 +9,43 @@ var ui_roots: []int # first-widget index of each ui block
|
|||
|
||||
fn ui_wtype(w: Node) -> int {
|
||||
let s = w.s
|
||||
if streq(s, "panel") { return 0 } if streq(s, "col") { return 1 } if streq(s, "row") { return 2 }
|
||||
if streq(s, "label") { return 3 } if streq(s, "button") { return 4 }
|
||||
if streq(s, "image") { return 5 } if streq(s, "spacer") { return 6 }
|
||||
if streq(s, "panel") { return 0 }; if streq(s, "col") { return 1 }; if streq(s, "row") { return 2 }
|
||||
if streq(s, "label") { return 3 }; if streq(s, "button") { return 4 }
|
||||
if streq(s, "image") { return 5 }; if streq(s, "spacer") { return 6 }
|
||||
return 0
|
||||
}
|
||||
fn ui_prop(w: Node, key: ptr) -> Node {
|
||||
let i = 0
|
||||
while i < len(w.b.kids) { if streq(w.b.kids[i].s, key) { return w.b.kids[i].a } i = i + 1 }
|
||||
while i < len(w.b.kids) { if streq(w.b.kids[i].s, key) { return w.b.kids[i].a }; i = i + 1 }
|
||||
return ptr_null()
|
||||
}
|
||||
fn ui_flatten(w: Node, parent: int) -> void {
|
||||
let idx = len(uiw)
|
||||
push(uiw, w) push(uiw_parent, parent)
|
||||
push(uiw, w); push(uiw_parent, parent)
|
||||
let i = 0
|
||||
while i < len(w.kids) { ui_flatten(w.kids[i], idx) i = i + 1 }
|
||||
while i < len(w.kids) { ui_flatten(w.kids[i], idx); i = i + 1 }
|
||||
}
|
||||
fn ui_flatten_all() -> void {
|
||||
uiw = new []Node uiw_parent = new []int ui_roots = new []int
|
||||
uiw = new []Node; uiw_parent = new []int; ui_roots = new []int
|
||||
let i = 0
|
||||
while i < len(prog) {
|
||||
if prog[i].kind == N_UI and prog[i].ival == 1 { push(ui_roots, len(uiw)) ui_flatten(prog[i].a, 0 - 1) }
|
||||
if prog[i].kind == N_UI and prog[i].ival == 1 { push(ui_roots, len(uiw)); ui_flatten(prog[i].a, 0 - 1) }
|
||||
i = i + 1
|
||||
}
|
||||
}
|
||||
fn has_ui() -> bool {
|
||||
let i = 0
|
||||
while i < len(prog) { if prog[i].kind == N_UI and prog[i].ival == 1 { return true } i = i + 1 }
|
||||
while i < len(prog) { if prog[i].kind == N_UI and prog[i].ival == 1 { return true }; i = i + 1 }
|
||||
return false
|
||||
}
|
||||
|
||||
# index of a UI_<name>: a ui block's root, or a widget's id=
|
||||
fn ui_index_of(nm: ptr) -> int {
|
||||
let s = substr(nm, 3, slen(nm) - 3) # strip "UI_"
|
||||
let u = 0 let bi = 0
|
||||
let u = 0; let bi = 0
|
||||
let i = 0
|
||||
while i < len(prog) {
|
||||
if prog[i].kind == N_UI and prog[i].ival == 1 { if streq(prog[i].s, s) { return ui_roots[bi] } bi = bi + 1 }
|
||||
if prog[i].kind == N_UI and prog[i].ival == 1 { if streq(prog[i].s, s) { return ui_roots[bi] }; bi = bi + 1 }
|
||||
i = i + 1
|
||||
}
|
||||
i = 0
|
||||
|
|
@ -61,24 +61,24 @@ fn is_ui_ident(nm: ptr) -> bool {
|
|||
}
|
||||
|
||||
fn ll_ui_set(idx: int, key: int, val: ptr) -> void {
|
||||
emit(" call void @fn_rt_ui_set(i32 ") emit(itoa(idx)) emit(", i32 ") emit(itoa(key)) emit(", i32 ") emit(val) emit(")\n")
|
||||
emit(" call void @fn_rt_ui_set(i32 "); emit(itoa(idx)); emit(", i32 "); emit(itoa(key)); emit(", i32 "); emit(val); emit(")\n")
|
||||
}
|
||||
fn ui_prop_key(k: ptr) -> int {
|
||||
if streq(k,"w") { return 2 } if streq(k,"h") { return 3 } if streq(k,"x") { return 4 } if streq(k,"y") { return 5 }
|
||||
if streq(k,"pad") { return 7 } if streq(k,"gap") { return 8 } if streq(k,"bg") { return 9 } if streq(k,"fg") { return 10 }
|
||||
if streq(k,"border") { return 11 } if streq(k,"grow") { return 13 } if streq(k,"font") { return 14 } if streq(k,"size") { return 15 }
|
||||
if streq(k,"inset") { return 16 } if streq(k,"focus") { return 17 }
|
||||
if streq(k,"w") { return 2 }; if streq(k,"h") { return 3 }; if streq(k,"x") { return 4 }; if streq(k,"y") { return 5 }
|
||||
if streq(k,"pad") { return 7 }; if streq(k,"gap") { return 8 }; if streq(k,"bg") { return 9 }; if streq(k,"fg") { return 10 }
|
||||
if streq(k,"border") { return 11 }; if streq(k,"grow") { return 13 }; if streq(k,"font") { return 14 }; if streq(k,"size") { return 15 }
|
||||
if streq(k,"inset") { return 16 }; if streq(k,"focus") { return 17 }
|
||||
return 0 - 1
|
||||
}
|
||||
|
||||
fn emit_ui_build() -> void {
|
||||
ui_flatten_all()
|
||||
ll_t = 0 ll_lbl = 0 g_term = false loc_reset() nloop = 0
|
||||
ll_t = 0; ll_lbl = 0; g_term = false; loc_reset(); nloop = 0
|
||||
let fbody = buf_new()
|
||||
falloc = buf_new()
|
||||
let saved = code
|
||||
code = fbody
|
||||
emit(" call void @fn_rt_ui_reset(i32 ") emit(itoa(len(uiw))) emit(")\n")
|
||||
emit(" call void @fn_rt_ui_reset(i32 "); emit(itoa(len(uiw))); emit(")\n")
|
||||
let i = 0
|
||||
while i < len(uiw) {
|
||||
let w = uiw[i]
|
||||
|
|
@ -91,10 +91,10 @@ fn emit_ui_build() -> void {
|
|||
let pr = w.b.kids[p]
|
||||
let k = pr.s
|
||||
let v = pr.a
|
||||
if streq(k, "id") { p = p + 1 continue }
|
||||
if streq(k, "id") { p = p + 1; continue }
|
||||
if streq(k, "text") {
|
||||
let sv = emit_expr(v)
|
||||
emit(" call void @fn_rt_ui_static_text(i32 ") emit(itoa(i)) emit(", ptr ") emit(sv.code) emit(")\n")
|
||||
emit(" call void @fn_rt_ui_static_text(i32 "); emit(itoa(i)); emit(", ptr "); emit(sv.code); emit(")\n")
|
||||
} else { if streq(k, "skin") or streq(k, "image") {
|
||||
let sv = emit_expr(v)
|
||||
let r = emit_bind(sconcat("call i32 @fn_rt_image_load(ptr ", sconcat(sv.code, ")")))
|
||||
|
|
@ -102,9 +102,9 @@ fn emit_ui_build() -> void {
|
|||
} else { if streq(k, "align") {
|
||||
if v.kind == E_ID {
|
||||
let a = 0
|
||||
if streq(v.s, "center") { a = 1 } if streq(v.s, "end") { a = 2 }
|
||||
if streq(v.s, "center") { a = 1 }; if streq(v.s, "end") { a = 2 }
|
||||
ll_ui_set(i, 12, itoa(a))
|
||||
} else { let vv = emit_expr(v) ll_ui_set(i, 12, vv.code) }
|
||||
} else { let vv = emit_expr(v); ll_ui_set(i, 12, vv.code) }
|
||||
} else {
|
||||
let key = ui_prop_key(k)
|
||||
if key >= 0 {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ var toks: []Tok
|
|||
|
||||
fn tok_push(kind: int, text: ptr, ival: int, line: int) -> void {
|
||||
let t = new Tok
|
||||
t.kind = kind t.text = text t.ival = ival t.line = line
|
||||
t.kind = kind; t.text = text; t.ival = ival; t.line = line
|
||||
push(toks, t)
|
||||
}
|
||||
|
||||
|
|
@ -42,8 +42,8 @@ fn lex(src: ptr) -> void {
|
|||
let n = slen(src)
|
||||
while i < n {
|
||||
let c = peek8(src, i)
|
||||
if c == 10 { tok_push(TK_NL, ptr_null(), 0, line) line = line + 1 i = i + 1 continue }
|
||||
if c == 32 or c == 9 or c == 13 { i = i + 1 continue }
|
||||
if c == 10 { tok_push(TK_NL, ptr_null(), 0, line); line = line + 1; i = i + 1; continue }
|
||||
if c == 32 or c == 9 or c == 13 { i = i + 1; continue }
|
||||
if c == 35 { # '#' comment to end of line
|
||||
while i < n and peek8(src, i) != 10 { i = i + 1 }
|
||||
continue
|
||||
|
|
@ -60,8 +60,8 @@ fn lex(src: ptr) -> void {
|
|||
if e == 110 { r = 10 }
|
||||
if e == 116 { r = 9 }
|
||||
if e == 48 { r = 0 }
|
||||
poke8(out, j, r) j = j + 1 i = i + 2
|
||||
} else { poke8(out, j, peek8(src, i)) j = j + 1 i = i + 1 }
|
||||
poke8(out, j, r); j = j + 1; i = i + 2
|
||||
} else { poke8(out, j, peek8(src, i)); j = j + 1; i = i + 1 }
|
||||
}
|
||||
i = i + 1
|
||||
poke8(out, j, 0)
|
||||
|
|
@ -77,7 +77,7 @@ fn lex(src: ptr) -> void {
|
|||
if e == 116 { v = 9 }
|
||||
if e == 48 { v = 0 }
|
||||
i = i + 2
|
||||
} else { v = peek8(src, i) i = i + 1 }
|
||||
} else { v = peek8(src, i); i = i + 1 }
|
||||
if peek8(src, i) == 39 { i = i + 1 }
|
||||
tok_push(TK_INT, ptr_null(), v, line)
|
||||
continue
|
||||
|
|
@ -99,12 +99,12 @@ fn lex(src: ptr) -> void {
|
|||
continue
|
||||
}
|
||||
let v = 0
|
||||
while i < n and char_is_digit(peek8(src, i)) { v = v * 10 + (peek8(src, i) - 48) i = i + 1 }
|
||||
while i < n and char_is_digit(peek8(src, i)) { v = v * 10 + (peek8(src, i) - 48); i = i + 1 }
|
||||
# a fractional part makes it a Q16.16 fixed literal
|
||||
if i < n and peek8(src, i) == 46 and char_is_digit(peek8(src, i + 1)) {
|
||||
i = i + 1
|
||||
let fnum = 0 let fden = 1
|
||||
while i < n and char_is_digit(peek8(src, i)) { fnum = fnum * 10 + (peek8(src, i) - 48) fden = fden * 10 i = i + 1 }
|
||||
let fnum = 0; let fden = 1
|
||||
while i < n and char_is_digit(peek8(src, i)) { fnum = fnum * 10 + (peek8(src, i) - 48); fden = fden * 10; i = i + 1 }
|
||||
let bits = shl(v, 16) + shl(fnum, 16) / fden
|
||||
tok_push(TK_FLOAT, ptr_null(), bits, line)
|
||||
continue
|
||||
|
|
@ -118,20 +118,20 @@ fn lex(src: ptr) -> void {
|
|||
tok_push(TK_ID, substr(src, start, i - start), 0, line)
|
||||
continue
|
||||
}
|
||||
if c == 59 { tok_push(TK_NL, ptr_null(), 0, line) i = i + 1 continue } # ';'
|
||||
if c == 59 { tok_push(TK_NL, ptr_null(), 0, line); i = i + 1; continue } # ';'
|
||||
# two-character operators
|
||||
if two_at(src, i, 45, 62) { tok_push(TK_OP, substr(src, i, 2), 0, line) i = i + 2 continue } # ->
|
||||
if two_at(src, i, 61, 62) { tok_push(TK_OP, substr(src, i, 2), 0, line) i = i + 2 continue } # =>
|
||||
if two_at(src, i, 61, 61) { tok_push(TK_OP, substr(src, i, 2), 0, line) i = i + 2 continue } # ==
|
||||
if two_at(src, i, 33, 61) { tok_push(TK_OP, substr(src, i, 2), 0, line) i = i + 2 continue } # !=
|
||||
if two_at(src, i, 60, 61) { tok_push(TK_OP, substr(src, i, 2), 0, line) i = i + 2 continue } # <=
|
||||
if two_at(src, i, 62, 61) { tok_push(TK_OP, substr(src, i, 2), 0, line) i = i + 2 continue } # >=
|
||||
if two_at(src, i, 43, 61) { tok_push(TK_OP, substr(src, i, 2), 0, line) i = i + 2 continue } # +=
|
||||
if two_at(src, i, 45, 61) { tok_push(TK_OP, substr(src, i, 2), 0, line) i = i + 2 continue } # -=
|
||||
if two_at(src, i, 42, 61) { tok_push(TK_OP, substr(src, i, 2), 0, line) i = i + 2 continue } # *=
|
||||
if two_at(src, i, 47, 61) { tok_push(TK_OP, substr(src, i, 2), 0, line) i = i + 2 continue } # /=
|
||||
if two_at(src, i, 46, 46) { tok_push(TK_OP, substr(src, i, 2), 0, line) i = i + 2 continue } # ..
|
||||
if is_op1(c) { tok_push(TK_OP, substr(src, i, 1), 0, line) i = i + 1 continue }
|
||||
if two_at(src, i, 45, 62) { tok_push(TK_OP, substr(src, i, 2), 0, line); i = i + 2; continue } # ->
|
||||
if two_at(src, i, 61, 62) { tok_push(TK_OP, substr(src, i, 2), 0, line); i = i + 2; continue } # =>
|
||||
if two_at(src, i, 61, 61) { tok_push(TK_OP, substr(src, i, 2), 0, line); i = i + 2; continue } # ==
|
||||
if two_at(src, i, 33, 61) { tok_push(TK_OP, substr(src, i, 2), 0, line); i = i + 2; continue } # !=
|
||||
if two_at(src, i, 60, 61) { tok_push(TK_OP, substr(src, i, 2), 0, line); i = i + 2; continue } # <=
|
||||
if two_at(src, i, 62, 61) { tok_push(TK_OP, substr(src, i, 2), 0, line); i = i + 2; continue } # >=
|
||||
if two_at(src, i, 43, 61) { tok_push(TK_OP, substr(src, i, 2), 0, line); i = i + 2; continue } # +=
|
||||
if two_at(src, i, 45, 61) { tok_push(TK_OP, substr(src, i, 2), 0, line); i = i + 2; continue } # -=
|
||||
if two_at(src, i, 42, 61) { tok_push(TK_OP, substr(src, i, 2), 0, line); i = i + 2; continue } # *=
|
||||
if two_at(src, i, 47, 61) { tok_push(TK_OP, substr(src, i, 2), 0, line); i = i + 2; continue } # /=
|
||||
if two_at(src, i, 46, 46) { tok_push(TK_OP, substr(src, i, 2), 0, line); i = i + 2; continue } # ..
|
||||
if is_op1(c) { tok_push(TK_OP, substr(src, i, 1), 0, line); i = i + 1; continue }
|
||||
i = i + 1 # skip anything unrecognised
|
||||
}
|
||||
tok_push(TK_EOF, ptr_null(), 0, line)
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -18,7 +18,7 @@
|
|||
fn base_name(path: ptr) -> ptr {
|
||||
let last = 0 - 1
|
||||
let i = 0
|
||||
while peek8(path, i) != 0 { if peek8(path, i) == 47 { last = i } i = i + 1 }
|
||||
while peek8(path, i) != 0 { if peek8(path, i) == 47 { last = i }; i = i + 1 }
|
||||
return substr(path, last + 1, i - (last + 1))
|
||||
}
|
||||
|
||||
|
|
@ -72,7 +72,7 @@ main {
|
|||
else { if streq(a, "--fmt") { fmt = true }
|
||||
else { if streq(a, "--save-temps") { save = true }
|
||||
else { if streq(a, "--run") { run = true }
|
||||
else { if streq(a, "-o") { ai = ai + 1 if ai < os_argc() { out = os_arg(ai) } }
|
||||
else { if streq(a, "-o") { ai = ai + 1; if ai < os_argc() { out = os_arg(ai) } }
|
||||
else {
|
||||
# an unknown flag is ignored (with a note) rather than mistaken for the
|
||||
# input file — '-' is ASCII 45
|
||||
|
|
@ -112,8 +112,8 @@ main {
|
|||
emit_program()
|
||||
|
||||
# --emit-llvm, or no binary target: emit IR and stop (stdout when out is null).
|
||||
if emit_ir { ir_flush(out) return }
|
||||
if not has_target { ir_flush(ptr_null()) return }
|
||||
if emit_ir { ir_flush(out); return }
|
||||
if not has_target { ir_flush(ptr_null()); return }
|
||||
|
||||
# a run with no explicit -o lands in a temp file
|
||||
if run and ptr_is_null(out) {
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ var g_game_name: ptr # the `game`/`module` name
|
|||
|
||||
fn cur() -> Tok { return toks[pi] }
|
||||
fn pk(o: int) -> Tok { return toks[pi + o] }
|
||||
fn is_op(v: ptr) -> bool { let t = toks[pi] return t.kind == TK_OP and streq(t.text, v) }
|
||||
fn is_id(v: ptr) -> bool { let t = toks[pi] return t.kind == TK_ID and streq(t.text, v) }
|
||||
fn is_op(v: ptr) -> bool { let t = toks[pi]; return t.kind == TK_OP and streq(t.text, v) }
|
||||
fn is_id(v: ptr) -> bool { let t = toks[pi]; return t.kind == TK_ID and streq(t.text, v) }
|
||||
fn is_kw(v: ptr) -> bool { return is_id(v) }
|
||||
|
||||
fn perr(msg: ptr) -> void {
|
||||
|
|
@ -20,7 +20,7 @@ fn perr(msg: ptr) -> void {
|
|||
os_exit(1)
|
||||
}
|
||||
|
||||
fn eat_op(v: ptr) -> void { if not is_op(v) { perr(v) } pi = pi + 1 }
|
||||
fn eat_op(v: ptr) -> void { if not is_op(v) { perr(v) }; pi = pi + 1 }
|
||||
fn eat_id() -> ptr {
|
||||
let t = toks[pi]
|
||||
if t.kind != TK_ID { perr("expected identifier") }
|
||||
|
|
@ -36,9 +36,9 @@ fn ptype() -> ptr {
|
|||
eat_op("]")
|
||||
let el = ptype()
|
||||
let out = mem_alloc(slen(el) + 3)
|
||||
poke8(out, 0, 91) poke8(out, 1, 93) # "[]"
|
||||
poke8(out, 0, 91); poke8(out, 1, 93) # "[]"
|
||||
let i = 0
|
||||
while peek8(el, i) != 0 { poke8(out, 2 + i, peek8(el, i)) i = i + 1 }
|
||||
while peek8(el, i) != 0 { poke8(out, 2 + i, peek8(el, i)); i = i + 1 }
|
||||
poke8(out, 2 + i, 0)
|
||||
return out
|
||||
}
|
||||
|
|
@ -49,23 +49,23 @@ fn ptype() -> ptr {
|
|||
fn expr() -> Node { return p_or() }
|
||||
|
||||
fn args_call(call: Node) -> void {
|
||||
eat_op("(") skipnl()
|
||||
while not is_op(")") { push(call.kids, expr()) skipnl() if is_op(",") { pi = pi + 1 skipnl() } }
|
||||
eat_op("("); skipnl()
|
||||
while not is_op(")") { push(call.kids, expr()); skipnl(); if is_op(",") { pi = pi + 1; skipnl() } }
|
||||
eat_op(")")
|
||||
}
|
||||
|
||||
fn p_primary() -> Node {
|
||||
let t = toks[pi]
|
||||
if t.kind == TK_INT { let n = node(E_INT) n.ival = t.ival pi = pi + 1 return n }
|
||||
if t.kind == TK_FLOAT { let n = node(E_FLOAT) n.ival = t.ival pi = pi + 1 return n }
|
||||
if t.kind == TK_STR { let n = node(E_STR) n.s = t.text pi = pi + 1 return n }
|
||||
if t.kind == TK_INT { let n = node(E_INT); n.ival = t.ival; pi = pi + 1; return n }
|
||||
if t.kind == TK_FLOAT { let n = node(E_FLOAT); n.ival = t.ival; pi = pi + 1; return n }
|
||||
if t.kind == TK_STR { let n = node(E_STR); n.s = t.text; pi = pi + 1; return n }
|
||||
if t.kind == TK_ID {
|
||||
if streq(t.text, "true") { let n = node(E_BOOL) n.ival = 1 pi = pi + 1 return n }
|
||||
if streq(t.text, "false") { let n = node(E_BOOL) n.ival = 0 pi = pi + 1 return n }
|
||||
if streq(t.text, "new") { pi = pi + 1 let n = node(E_NEW) n.s = ptype() return n }
|
||||
let n = node(E_ID) n.s = t.text pi = pi + 1 return n
|
||||
if streq(t.text, "true") { let n = node(E_BOOL); n.ival = 1; pi = pi + 1; return n }
|
||||
if streq(t.text, "false") { let n = node(E_BOOL); n.ival = 0; pi = pi + 1; return n }
|
||||
if streq(t.text, "new") { pi = pi + 1; let n = node(E_NEW); n.s = ptype(); return n }
|
||||
let n = node(E_ID); n.s = t.text; pi = pi + 1; return n
|
||||
}
|
||||
if is_op("(") { pi = pi + 1 skipnl() let e = expr() skipnl() eat_op(")") return e }
|
||||
if is_op("(") { pi = pi + 1; skipnl(); let e = expr(); skipnl(); eat_op(")"); return e }
|
||||
perr("expected expression")
|
||||
return node(E_INT)
|
||||
}
|
||||
|
|
@ -73,64 +73,78 @@ fn p_primary() -> Node {
|
|||
fn p_postfix() -> Node {
|
||||
let e = p_primary()
|
||||
while true {
|
||||
if is_op(".") { pi = pi + 1 let m = node(E_MEMBER) m.a = e m.s = eat_id() e = m }
|
||||
else { if is_op("[") { pi = pi + 1 let ix = node(E_INDEX) ix.a = e ix.b = expr() eat_op("]") e = ix }
|
||||
else { if is_op("(") { let c = node(E_CALL) c.a = e args_call(c) e = c } else { break } } }
|
||||
if is_op(".") { pi = pi + 1; let m = node(E_MEMBER); m.a = e; m.s = eat_id(); e = m }
|
||||
else { if is_op("[") { pi = pi + 1; let ix = node(E_INDEX); ix.a = e; ix.b = expr(); eat_op("]"); e = ix }
|
||||
else { if is_op("(") { let c = node(E_CALL); c.a = e; args_call(c); e = c } else { break } } }
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
fn p_unary() -> Node {
|
||||
if is_op("-") { pi = pi + 1 let n = node(E_UN) n.s = "-" n.a = p_unary() return n }
|
||||
if is_id("not") { pi = pi + 1 let n = node(E_UN) n.s = "not" n.a = p_unary() return n }
|
||||
if is_op("-") { pi = pi + 1; let n = node(E_UN); n.s = "-"; n.a = p_unary(); return n }
|
||||
if is_id("not") { pi = pi + 1; let n = node(E_UN); n.s = "not"; n.a = p_unary(); return n }
|
||||
return p_postfix()
|
||||
}
|
||||
|
||||
fn mkbin(op: ptr, l: Node, r: Node) -> Node { let b = node(E_BIN) b.s = op b.a = l b.b = r return b }
|
||||
fn mkbin(op: ptr, l: Node, r: Node) -> Node { let b = node(E_BIN); b.s = op; b.a = l; b.b = r; return b }
|
||||
|
||||
fn p_mul() -> Node {
|
||||
let l = p_unary()
|
||||
while is_op("*") or is_op("/") or is_op("%") { let op = toks[pi].text pi = pi + 1 l = mkbin(op, l, p_unary()) }
|
||||
while is_op("*") or is_op("/") or is_op("%") { let op = toks[pi].text; pi = pi + 1; l = mkbin(op, l, p_unary()) }
|
||||
return l
|
||||
}
|
||||
fn p_add() -> Node {
|
||||
let l = p_mul()
|
||||
while is_op("+") or is_op("-") { let op = toks[pi].text pi = pi + 1 l = mkbin(op, l, p_mul()) }
|
||||
while is_op("+") or is_op("-") { let op = toks[pi].text; pi = pi + 1; l = mkbin(op, l, p_mul()) }
|
||||
return l
|
||||
}
|
||||
fn p_cmp() -> Node {
|
||||
let l = p_add()
|
||||
while is_op("<") or is_op("<=") or is_op(">") or is_op(">=") or is_op("==") or is_op("!=") {
|
||||
let op = toks[pi].text pi = pi + 1 l = mkbin(op, l, p_add())
|
||||
let op = toks[pi].text; pi = pi + 1; l = mkbin(op, l, p_add())
|
||||
}
|
||||
return l
|
||||
}
|
||||
fn p_and() -> Node {
|
||||
let l = p_cmp()
|
||||
while is_id("and") { pi = pi + 1 l = mkbin("and", l, p_cmp()) }
|
||||
while is_id("and") { pi = pi + 1; l = mkbin("and", l, p_cmp()) }
|
||||
return l
|
||||
}
|
||||
fn p_or() -> Node {
|
||||
let l = p_and()
|
||||
while is_id("or") { pi = pi + 1 l = mkbin("or", l, p_and()) }
|
||||
while is_id("or") { pi = pi + 1; l = mkbin("or", l, p_and()) }
|
||||
return l
|
||||
}
|
||||
|
||||
# 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 {
|
||||
eat_op("{")
|
||||
let r = node(E_REC)
|
||||
while true { skipnl() if is_op("}") { break }
|
||||
let fi = node(E_FINIT) fi.s = eat_id() eat_op("=") fi.a = expr() push(r.kids, fi)
|
||||
while true { skipnl(); if is_op("}") { break }
|
||||
let fi = node(E_FINIT); fi.s = eat_id(); eat_op(":"); fi.a = expr(); push(r.kids, fi)
|
||||
if is_op(",") { pi = pi + 1 } }
|
||||
eat_op("}") return r
|
||||
eat_op("}"); return r
|
||||
}
|
||||
|
||||
# ---- statements ------------------------------------------------------------
|
||||
fn block() -> Node {
|
||||
skipnl() eat_op("{")
|
||||
skipnl(); eat_op("{")
|
||||
let b = node(N_BLOCK)
|
||||
while true { skipnl() if is_op("}") { break } push(b.kids, stmt()) }
|
||||
while true { skipnl(); if is_op("}") { break }
|
||||
push(b.kids, stmt())
|
||||
# Rule B: statements are separated by a newline or ';' (both lex to TK_NL).
|
||||
# After a statement the next token must be that separator or the block's end —
|
||||
# two statements may not sit adjacent with only spaces between them.
|
||||
let sep = toks[pi].kind == TK_NL
|
||||
if is_op("}") { sep = true }
|
||||
if not sep {
|
||||
let ez = file_stderr(); file_write(ez, "cur=", 4)
|
||||
if not ptr_is_null(toks[pi].text) { file_write(ez, toks[pi].text, slen(toks[pi].text)) }
|
||||
file_write(ez, "\n", 1); print_int(7770000 + toks[pi].line)
|
||||
perr("expected newline or ';' between statements")
|
||||
}
|
||||
}
|
||||
eat_op("}")
|
||||
return b
|
||||
}
|
||||
|
|
@ -139,106 +153,112 @@ fn stmt() -> Node {
|
|||
let t = toks[pi]
|
||||
if t.kind == TK_ID {
|
||||
if streq(t.text, "let") {
|
||||
pi = pi + 1 let n = node(S_LET) n.s = eat_id()
|
||||
if is_op(":") { pi = pi + 1 n.ty = ptype() }
|
||||
if is_op("=") { pi = pi + 1 n.a = expr() }
|
||||
pi = pi + 1; let n = node(S_LET); n.s = eat_id()
|
||||
if is_op(":") { pi = pi + 1; n.ty = ptype() }
|
||||
if is_op("=") { pi = pi + 1; n.a = expr() }
|
||||
return n
|
||||
}
|
||||
if streq(t.text, "return") {
|
||||
pi = pi + 1 let n = node(S_RETURN)
|
||||
pi = pi + 1; let n = node(S_RETURN)
|
||||
if toks[pi].kind != TK_NL and not is_op("}") { n.a = expr() }
|
||||
return n
|
||||
}
|
||||
if streq(t.text, "if") {
|
||||
pi = pi + 1 let n = node(S_IF) n.a = expr() n.b = block() skipnl()
|
||||
if is_id("else") { pi = pi + 1 skipnl()
|
||||
pi = pi + 1; let n = node(S_IF); n.a = expr(); n.b = block()
|
||||
let save = pi; skipnl() # peek past newlines for a trailing `else`
|
||||
if is_id("else") { pi = pi + 1; skipnl()
|
||||
if is_id("if") { n.c = stmt() } else { n.c = block() } }
|
||||
else { pi = save } # no else: keep the separator for block()'s Rule-B check
|
||||
return n
|
||||
}
|
||||
if streq(t.text, "when") { # `when c { }` — an if with no else
|
||||
pi = pi + 1 let n = node(S_IF) n.a = expr() n.b = block()
|
||||
pi = pi + 1; let n = node(S_IF); n.a = expr(); n.b = block()
|
||||
return n
|
||||
}
|
||||
if streq(t.text, "while") { pi = pi + 1 let n = node(S_WHILE) n.a = expr() n.b = block() return n }
|
||||
if streq(t.text, "while") { pi = pi + 1; let n = node(S_WHILE); n.a = expr(); n.b = block(); return n }
|
||||
if streq(t.text, "for") {
|
||||
pi = pi + 1
|
||||
if is_op("(") { return parse_query_for() }
|
||||
let n = node(S_FOR) n.s = eat_id()
|
||||
let n = node(S_FOR); n.s = eat_id()
|
||||
let inkw = eat_id() # 'in'
|
||||
n.a = expr() eat_op("..") n.b = expr() n.c = block()
|
||||
n.a = expr(); eat_op(".."); n.b = expr(); n.c = block()
|
||||
return n
|
||||
}
|
||||
if streq(t.text, "spawn") { return parse_spawn() }
|
||||
if streq(t.text, "machine") {
|
||||
pi = pi + 1 let n = node(S_MACHINE) n.a = expr() skipnl() eat_op("{")
|
||||
while true { skipnl() if is_op("}") { break }
|
||||
pi = pi + 1; let n = node(S_MACHINE); n.a = expr(); skipnl(); eat_op("{")
|
||||
let sidx = 0 # states auto-number by declaration order
|
||||
while true { skipnl(); if is_op("}") { break }
|
||||
let stkw = eat_id() # 'state'
|
||||
let s = node(S_STATE) s.s = eat_id() eat_op("=") s.b = expr() skipnl() s.a = block()
|
||||
push(n.kids, s) }
|
||||
eat_op("}") return n
|
||||
let s = node(S_STATE); s.s = eat_id()
|
||||
if is_op("=") { pi = pi + 1; s.b = expr() } # explicit value (still allowed)
|
||||
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, "enter") { pi = pi + 1 let n = node(S_BECOME) n.s = eat_id() n.ival = 1 return n }
|
||||
if streq(t.text, "despawn") { pi = pi + 1 let n = node(S_DESPAWN) n.a = expr() return n }
|
||||
if streq(t.text, "break") { pi = pi + 1 return node(S_BREAK) }
|
||||
if streq(t.text, "continue") { pi = pi + 1 return node(S_CONTINUE) }
|
||||
if streq(t.text, "become") { pi = pi + 1; let n = node(S_BECOME); n.s = eat_id(); return n }
|
||||
if streq(t.text, "enter") { pi = pi + 1; let n = node(S_BECOME); n.s = eat_id(); n.ival = 1; return n }
|
||||
if streq(t.text, "despawn") { pi = pi + 1; let n = node(S_DESPAWN); n.a = expr(); return n }
|
||||
if streq(t.text, "break") { pi = pi + 1; return node(S_BREAK) }
|
||||
if streq(t.text, "continue") { pi = pi + 1; return node(S_CONTINUE) }
|
||||
if streq(t.text, "match") {
|
||||
pi = pi + 1 let n = node(S_MATCH) n.a = expr() skipnl() eat_op("{")
|
||||
pi = pi + 1; let n = node(S_MATCH); n.a = expr(); skipnl(); eat_op("{")
|
||||
while true {
|
||||
skipnl() if is_op("}") { break }
|
||||
skipnl(); if is_op("}") { break }
|
||||
let arm = node(S_MARM)
|
||||
while true { push(arm.kids, expr()) if is_op(",") { pi = pi + 1 skipnl() continue } break }
|
||||
eat_op("=>") skipnl()
|
||||
if is_op("{") { arm.a = block() } else { let b = node(N_BLOCK) push(b.kids, stmt()) arm.a = b }
|
||||
while true { push(arm.kids, expr()); if is_op(",") { pi = pi + 1; skipnl(); continue }; break }
|
||||
eat_op("=>"); skipnl()
|
||||
if is_op("{") { arm.a = block() } else { let b = node(N_BLOCK); push(b.kids, stmt()); arm.a = b }
|
||||
push(n.kids, arm)
|
||||
}
|
||||
eat_op("}") return n
|
||||
eat_op("}"); return n
|
||||
}
|
||||
}
|
||||
let e = expr()
|
||||
if is_op("=") or is_op("+=") or is_op("-=") or is_op("*=") or is_op("/=") {
|
||||
let n = node(S_ASSIGN) n.s = toks[pi].text pi = pi + 1 n.a = e n.b = expr() return n
|
||||
let n = node(S_ASSIGN); n.s = toks[pi].text; pi = pi + 1; n.a = e; n.b = expr(); return n
|
||||
}
|
||||
let n = node(S_EXPR) n.a = e return n
|
||||
let n = node(S_EXPR); n.a = e; return n
|
||||
}
|
||||
|
||||
# ---- declarations ----------------------------------------------------------
|
||||
fn parse_struct() -> Node {
|
||||
pi = pi + 1 let n = node(N_STRUCT) n.s = eat_id() skipnl() eat_op("{")
|
||||
while true { skipnl() if is_op("}") { break }
|
||||
let f = node(N_FIELD) f.s = eat_id() eat_op(":") f.ty = ptype()
|
||||
if is_op("=") { pi = pi + 1 f.a = expr() }
|
||||
push(n.kids, f) if is_op(",") { pi = pi + 1 } }
|
||||
eat_op("}") return n
|
||||
pi = pi + 1; let n = node(N_STRUCT); n.s = eat_id(); skipnl(); eat_op("{")
|
||||
while true { skipnl(); if is_op("}") { break }
|
||||
let f = node(N_FIELD); f.s = eat_id(); eat_op(":"); f.ty = ptype()
|
||||
if is_op("=") { pi = pi + 1; f.a = expr() }
|
||||
push(n.kids, f); if is_op(",") { pi = pi + 1 } }
|
||||
eat_op("}"); return n
|
||||
}
|
||||
fn parse_var() -> Node {
|
||||
pi = pi + 1 let n = node(N_VAR) n.s = eat_id() eat_op(":") n.ty = ptype()
|
||||
if is_op("=") { pi = pi + 1 n.a = expr() }
|
||||
pi = pi + 1; let n = node(N_VAR); n.s = eat_id(); eat_op(":"); n.ty = ptype()
|
||||
if is_op("=") { pi = pi + 1; n.a = expr() }
|
||||
return n
|
||||
}
|
||||
fn parse_const() -> Node {
|
||||
pi = pi + 1 let n = node(N_CONST) n.s = eat_id() eat_op(":") n.ty = ptype() eat_op("=") n.a = expr()
|
||||
pi = pi + 1; let n = node(N_CONST); n.s = eat_id(); eat_op(":"); n.ty = ptype(); eat_op("="); n.a = expr()
|
||||
return n
|
||||
}
|
||||
fn parse_fn() -> Node {
|
||||
pi = pi + 1 let n = node(N_FN) n.s = eat_id() eat_op("(")
|
||||
pi = pi + 1; let n = node(N_FN); n.s = eat_id(); eat_op("(")
|
||||
while not is_op(")") {
|
||||
let p = node(N_PARAM) p.s = eat_id() eat_op(":") p.ty = ptype() push(n.kids, p)
|
||||
let p = node(N_PARAM); p.s = eat_id(); eat_op(":"); p.ty = ptype(); push(n.kids, p)
|
||||
if is_op(",") { pi = pi + 1 }
|
||||
}
|
||||
eat_op(")")
|
||||
n.ty = "void"
|
||||
if is_op("->") { pi = pi + 1 n.ty = ptype() }
|
||||
if is_op("->") { pi = pi + 1; n.ty = ptype() }
|
||||
n.a = block()
|
||||
return n
|
||||
}
|
||||
fn parse_main() -> Node { pi = pi + 1 let n = node(N_MAIN) n.a = block() return n }
|
||||
fn parse_main() -> Node { pi = pi + 1; let n = node(N_MAIN); n.a = block(); return n }
|
||||
|
||||
# directory part of a path, including the trailing '/', or "" if none
|
||||
fn dir_of(path: ptr) -> ptr {
|
||||
let last = 0 - 1
|
||||
let i = 0
|
||||
while peek8(path, i) != 0 { if peek8(path, i) == 47 { last = i } i = i + 1 }
|
||||
while peek8(path, i) != 0 { if peek8(path, i) == 47 { last = i }; i = i + 1 }
|
||||
if last < 0 { return "" }
|
||||
return substr(path, 0, last + 1)
|
||||
}
|
||||
|
|
@ -252,30 +272,40 @@ var cur_dir: ptr
|
|||
|
||||
fn already_loaded(full: ptr) -> bool {
|
||||
let i = 0
|
||||
while i < len(loaded_paths) { if streq(loaded_paths[i], full) { return true } i = i + 1 }
|
||||
while i < len(loaded_paths) { if streq(loaded_paths[i], full) { return true }; i = i + 1 }
|
||||
return false
|
||||
}
|
||||
|
||||
# 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 {
|
||||
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
|
||||
let t = toks[pi]
|
||||
if t.kind != TK_STR { perr("expected \"path\" after import") }
|
||||
let rel = t.text pi = pi + 1
|
||||
let rel = t.text; pi = pi + 1
|
||||
do_import(rel)
|
||||
return
|
||||
}
|
||||
if is_id("struct") { push(prog, parse_struct()) return }
|
||||
if is_id("component") { push(prog, parse_component()) return }
|
||||
if is_id("archetype") { push(prog, parse_archetype()) return }
|
||||
if is_id("system") or is_id("edge") { push(prog, parse_system()) return }
|
||||
if is_id("ui") { push(prog, parse_ui()) return }
|
||||
if is_id("var") { push(prog, parse_var()) return }
|
||||
if is_id("const") { push(prog, parse_const()) return }
|
||||
if is_id("export") { pi = pi + 1 let f = parse_fn() f.ival = 1 push(prog, f) return }
|
||||
if is_id("fn") or is_id("pure") { if is_id("pure") { pi = pi + 1 } push(prog, parse_fn()) return }
|
||||
if is_id("extern") { push(prog, parse_extern()) return }
|
||||
if is_id("main") { push(prog, parse_main()) return }
|
||||
if is_id("struct") { push(prog, parse_struct()); return }
|
||||
if is_id("enum") { push(prog, parse_enum()); return }
|
||||
if is_id("component") { push(prog, parse_component()); return }
|
||||
if is_id("archetype") { push(prog, parse_archetype()); return }
|
||||
if is_id("system") { push(prog, parse_system()); return }
|
||||
if is_id("ui") { push(prog, parse_ui()); return }
|
||||
if is_id("var") { push(prog, parse_var()); return }
|
||||
if is_id("const") { push(prog, parse_const()); return }
|
||||
if is_id("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("main") { push(prog, parse_main()); return }
|
||||
perr("expected declaration")
|
||||
}
|
||||
|
||||
|
|
@ -286,13 +316,13 @@ fn do_import(rel: ptr) -> void {
|
|||
push(loaded_paths, full)
|
||||
let src = read_file(full)
|
||||
if ptr_is_null(src) { perr(sconcat("cannot open import ", full)) }
|
||||
let saved_toks = toks let saved_pi = pi let saved_dir = cur_dir
|
||||
let saved_toks = toks; let saved_pi = pi; let saved_dir = cur_dir
|
||||
cur_dir = dir_of(full)
|
||||
lex(src) # resets the global token stream
|
||||
pi = 0
|
||||
skipnl()
|
||||
while toks[pi].kind != TK_EOF { parse_one_decl() skipnl() }
|
||||
toks = saved_toks pi = saved_pi cur_dir = saved_dir
|
||||
while toks[pi].kind != TK_EOF { parse_one_decl(); skipnl() }
|
||||
toks = saved_toks; pi = saved_pi; cur_dir = saved_dir
|
||||
}
|
||||
|
||||
# a game (has systems/components) links the Ludic runtime; auto-splice it the
|
||||
|
|
@ -311,8 +341,8 @@ fn parse_program() -> void {
|
|||
skipnl()
|
||||
g_game_name = "Ludic"
|
||||
# imports may precede the game block
|
||||
while is_id("import") { pi = pi + 1 let t = toks[pi] let rel = t.text pi = pi + 1 do_import(rel) skipnl() }
|
||||
if is_id("game") or is_id("module") { pi = pi + 1 g_game_name = eat_id() skipnl() eat_op("{") }
|
||||
while is_id("import") { pi = pi + 1; let t = toks[pi]; let rel = t.text; pi = pi + 1; do_import(rel); skipnl() }
|
||||
if is_id("game") or is_id("module") { pi = pi + 1; g_game_name = eat_id(); skipnl(); eat_op("{") }
|
||||
while true {
|
||||
skipnl()
|
||||
if toks[pi].kind == TK_EOF { break }
|
||||
|
|
|
|||
|
|
@ -3,25 +3,24 @@
|
|||
# game-construct parsing in compiler/front/parse.c.
|
||||
|
||||
fn parse_component() -> Node {
|
||||
pi = pi + 1 let n = node(N_COMP) n.s = eat_id() skipnl() eat_op("{")
|
||||
while true { skipnl() if is_op("}") { break }
|
||||
let f = node(N_FIELD) f.s = eat_id() eat_op(":") f.ty = ptype()
|
||||
if is_op("=") { pi = pi + 1 f.a = expr() }
|
||||
push(n.kids, f) if is_op(",") { pi = pi + 1 } }
|
||||
eat_op("}") return n
|
||||
pi = pi + 1; let n = node(N_COMP); n.s = eat_id(); skipnl(); eat_op("{")
|
||||
while true { skipnl(); if is_op("}") { break }
|
||||
let f = node(N_FIELD); f.s = eat_id(); eat_op(":"); f.ty = ptype()
|
||||
if is_op("=") { pi = pi + 1; f.a = expr() }
|
||||
push(n.kids, f); if is_op(",") { pi = pi + 1 } }
|
||||
eat_op("}"); return n
|
||||
}
|
||||
|
||||
# skip an @annotation or a reads/writes/query/... clause we don't model yet
|
||||
fn skip_clause() -> void {
|
||||
if is_op("[") { let depth = 0
|
||||
while true { if is_op("[") { depth = depth + 1 } if is_op("]") { depth = depth - 1 }
|
||||
pi = pi + 1 if depth == 0 { break } }
|
||||
while true { if is_op("[") { depth = depth + 1 }; if is_op("]") { depth = depth - 1 }
|
||||
pi = pi + 1; if depth == 0 { break } }
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_system() -> Node {
|
||||
if is_id("edge") { pi = pi + 1 } # optional `edge` modifier before `system`
|
||||
pi = pi + 1 let n = node(N_SYS) n.s = eat_id() n.ty = "Update"
|
||||
pi = pi + 1; let n = node(N_SYS); n.s = eat_id(); n.ty = "Update"
|
||||
# clauses: @anno, phase X, reads/writes/needs/uses [..], query (..) [..]
|
||||
# A `query (vars) [terms] where c` clause desugars to a body wrapped in one
|
||||
# `for (vars) in query [terms] where c { ... }` — the same S_QUERY node the
|
||||
|
|
@ -30,15 +29,15 @@ fn parse_system() -> Node {
|
|||
let qn = node(S_QUERY)
|
||||
while true {
|
||||
skipnl() # clauses may span several lines
|
||||
if is_op("@") { pi = pi + 1 let a = eat_id() if is_op("(") { let d = 0 # @anno, one per turn so a
|
||||
while true { if is_op("(") { d = d + 1 } if is_op(")") { d = d - 1 } pi = pi + 1 if d == 0 { break } } }
|
||||
if is_op("@") { pi = pi + 1; let a = eat_id(); if is_op("(") { let d = 0 # @anno, one per turn so a
|
||||
while true { if is_op("(") { d = d + 1 }; if is_op(")") { d = d - 1 }; pi = pi + 1; if d == 0 { break } } }
|
||||
continue } # newline-separated @anno re-skips at the loop top
|
||||
if is_id("phase") { pi = pi + 1 n.ty = eat_id() continue }
|
||||
if is_id("reads") or is_id("writes") or is_id("needs") or is_id("uses") { pi = pi + 1 skip_clause() continue }
|
||||
if is_id("phase") { pi = pi + 1; n.ty = eat_id(); continue }
|
||||
if is_id("reads") or is_id("writes") { pi = pi + 1; skip_clause(); continue } # `needs`/`uses` synonyms dropped (Rule A cleanup)
|
||||
if is_id("query") {
|
||||
pi = pi + 1 has_q = true
|
||||
pi = pi + 1; has_q = true
|
||||
if is_op("(") { eat_op("(") # optional (vars); omitted when nothing binds
|
||||
while not is_op(")") { let v = node(E_ID) v.s = eat_id() push(qn.kids, v) if is_op(",") { pi = pi + 1 } }
|
||||
while not is_op(")") { let v = node(E_ID); v.s = eat_id(); push(qn.kids, v); if is_op(",") { pi = pi + 1 } }
|
||||
eat_op(")") }
|
||||
qn.c = parse_query_tail()
|
||||
qn.b = qn.c.a # where-expr (or null)
|
||||
|
|
@ -50,7 +49,7 @@ fn parse_system() -> Node {
|
|||
let body = block()
|
||||
if has_q {
|
||||
qn.a = body
|
||||
let wrap = node(N_BLOCK) push(wrap.kids, qn)
|
||||
let wrap = node(N_BLOCK); push(wrap.kids, qn)
|
||||
n.a = wrap
|
||||
} else { n.a = body }
|
||||
return n
|
||||
|
|
@ -62,12 +61,12 @@ fn parse_query_tail() -> Node {
|
|||
eat_op("[")
|
||||
let q = node(N_BLOCK)
|
||||
while not is_op("]") {
|
||||
if is_op("{") { pi = pi + 1 let t = node(E_ID) t.s = eat_id() t.ival = 1 push(q.kids, t) eat_op("}") }
|
||||
else { let t = node(E_ID) t.s = eat_id() t.ival = 0 push(q.kids, t) }
|
||||
if is_op("{") { pi = pi + 1; let t = node(E_ID); t.s = eat_id(); t.ival = 1; push(q.kids, t); eat_op("}") }
|
||||
else { let t = node(E_ID); t.s = eat_id(); t.ival = 0; push(q.kids, t) }
|
||||
if is_op(",") { pi = pi + 1 }
|
||||
}
|
||||
eat_op("]")
|
||||
if is_id("where") { pi = pi + 1 q.a = expr() }
|
||||
if is_id("where") { pi = pi + 1; q.a = expr() }
|
||||
return q
|
||||
}
|
||||
|
||||
|
|
@ -75,7 +74,7 @@ fn parse_query_tail() -> Node {
|
|||
fn parse_query_for() -> Node {
|
||||
let n = node(S_QUERY)
|
||||
eat_op("(")
|
||||
while not is_op(")") { let v = node(E_ID) v.s = eat_id() push(n.kids, v) if is_op(",") { pi = pi + 1 } }
|
||||
while not is_op(")") { let v = node(E_ID); v.s = eat_id(); push(n.kids, v); if is_op(",") { pi = pi + 1 } }
|
||||
eat_op(")")
|
||||
let inkw = eat_id() # 'in'
|
||||
let qkw = eat_id() # 'query'
|
||||
|
|
@ -86,59 +85,70 @@ fn parse_query_for() -> Node {
|
|||
}
|
||||
|
||||
fn parse_spawn() -> Node {
|
||||
pi = pi + 1 let n = node(S_SPAWN) n.s = eat_id() skipnl() eat_op("{")
|
||||
pi = pi + 1; let n = node(S_SPAWN); n.s = eat_id(); skipnl(); eat_op("{")
|
||||
while true {
|
||||
skipnl() if is_op("}") { break }
|
||||
let ci = node(E_FINIT) ci.s = eat_id() eat_op("=")
|
||||
ci.a = record() # { field = val, ... }
|
||||
skipnl(); if is_op("}") { break }
|
||||
let ci = node(E_FINIT); ci.s = eat_id()
|
||||
ci.a = record() # Comp { field: val, ... } — no `=` before the record
|
||||
push(n.kids, ci)
|
||||
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)
|
||||
fn parse_archetype() -> Node {
|
||||
pi = pi + 1 let n = node(N_ARCH) n.s = eat_id() skipnl() eat_op("{")
|
||||
while true { skipnl() if is_op("}") { break }
|
||||
let c = node(E_ID) c.s = eat_id() push(n.kids, c)
|
||||
if is_op(",") { pi = pi + 1 } skipnl() }
|
||||
eat_op("}") return n
|
||||
pi = pi + 1; let n = node(N_ARCH); n.s = eat_id(); skipnl(); eat_op("{")
|
||||
while true { skipnl(); if is_op("}") { break }
|
||||
let c = node(E_ID); c.s = eat_id(); push(n.kids, c)
|
||||
if is_op(",") { pi = pi + 1 }; skipnl() }
|
||||
eat_op("}"); return n
|
||||
}
|
||||
|
||||
# extern fn name(params) -> T = "symbol"
|
||||
fn parse_extern() -> Node {
|
||||
pi = pi + 1 # 'extern'
|
||||
let fnkw = eat_id() # 'fn'
|
||||
let n = node(N_EXTERN) n.s = eat_id() eat_op("(")
|
||||
while not is_op(")") { let p = node(N_PARAM) p.s = eat_id() eat_op(":") p.ty = ptype() push(n.kids, p)
|
||||
let n = node(N_EXTERN); n.s = eat_id(); eat_op("(")
|
||||
while not is_op(")") { let p = node(N_PARAM); p.s = eat_id(); eat_op(":"); p.ty = ptype(); push(n.kids, p)
|
||||
if is_op(",") { pi = pi + 1 } }
|
||||
eat_op(")")
|
||||
n.ty = "void"
|
||||
if is_op("->") { pi = pi + 1 n.ty = ptype() }
|
||||
if is_op("->") { pi = pi + 1; n.ty = ptype() }
|
||||
eat_op("=")
|
||||
let t = toks[pi] # "symbol"
|
||||
n.a = node(E_STR) n.a.s = t.text pi = pi + 1
|
||||
n.a = node(E_STR); n.a.s = t.text; pi = pi + 1
|
||||
return n
|
||||
}
|
||||
|
||||
# ui Name { widget-tree } — parsed into a widget node tree (emitted later)
|
||||
fn parse_widget() -> Node {
|
||||
let w = node(N_UI) w.s = eat_id() # widget type name
|
||||
let w = node(N_UI); w.s = eat_id() # widget type name
|
||||
w.b = node(N_BLOCK) # b.kids = props (E_FINIT)
|
||||
while toks[pi].kind == TK_ID and toks[pi + 1].kind == TK_OP and streq(toks[pi + 1].text, "=") {
|
||||
let pr = node(E_FINIT) pr.s = eat_id() eat_op("=") pr.a = expr() push(w.b.kids, pr)
|
||||
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) # widget prop `key: value` (Rule A)
|
||||
}
|
||||
skipnl()
|
||||
if is_op("{") { pi = pi + 1
|
||||
while true { skipnl() if is_op("}") { break } push(w.kids, parse_widget()) }
|
||||
while true { skipnl(); if is_op("}") { break }; push(w.kids, parse_widget()) }
|
||||
eat_op("}") }
|
||||
return w
|
||||
}
|
||||
fn parse_ui() -> Node {
|
||||
pi = pi + 1 let n = node(N_UI) n.s = eat_id() n.ival = 1 # ival=1 marks the top ui block
|
||||
skipnl() eat_op("{") skipnl()
|
||||
pi = pi + 1; let n = node(N_UI); n.s = eat_id(); n.ival = 1 # ival=1 marks the top ui block
|
||||
skipnl(); eat_op("{"); skipnl()
|
||||
n.a = parse_widget()
|
||||
skipnl() eat_op("}")
|
||||
skipnl(); eat_op("}")
|
||||
return n
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ fn slen(s: ptr) -> int {
|
|||
fn substr(src: ptr, start: int, n: int) -> ptr {
|
||||
let b = mem_alloc(n + 1)
|
||||
let i = 0
|
||||
while i < n { poke8(b, i, peek8(src, start + i)) i = i + 1 }
|
||||
while i < n { poke8(b, i, peek8(src, start + i)); i = i + 1 }
|
||||
poke8(b, n, 0)
|
||||
return b
|
||||
}
|
||||
|
|
@ -39,20 +39,20 @@ fn char_is_alnum(c: int) -> bool { return char_is_alpha(c) or char_is_digit(c) }
|
|||
|
||||
# integer -> fresh decimal string
|
||||
fn itoa(v: int) -> ptr {
|
||||
if v == 0 { let z = mem_alloc(2) poke8(z, 0, 48) poke8(z, 1, 0) return z }
|
||||
if v == 0 { let z = mem_alloc(2); poke8(z, 0, 48); poke8(z, 1, 0); return z }
|
||||
let neg = false
|
||||
let x = v
|
||||
if x < 0 { neg = true x = 0 - x }
|
||||
if x < 0 { neg = true; x = 0 - x }
|
||||
let tmp = mem_alloc(16)
|
||||
let n = 0
|
||||
while x > 0 { poke8(tmp, n, 48 + x % 10) x = x / 10 n = n + 1 }
|
||||
while x > 0 { poke8(tmp, n, 48 + x % 10); x = x / 10; n = n + 1 }
|
||||
let total = n
|
||||
if neg { total = total + 1 }
|
||||
let out = mem_alloc(total + 1)
|
||||
let k = 0
|
||||
if neg { poke8(out, 0, 45) k = 1 }
|
||||
if neg { poke8(out, 0, 45); k = 1 }
|
||||
let i = 0
|
||||
while i < n { poke8(out, k + i, peek8(tmp, n - 1 - i)) i = i + 1 }
|
||||
while i < n { poke8(out, k + i, peek8(tmp, n - 1 - i)); i = i + 1 }
|
||||
poke8(out, total, 0)
|
||||
return out
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
game T {
|
||||
fn fib(n: int) -> int { if n < 2 { return n } return fib(n-1) + fib(n-2) }
|
||||
fn fib(n: int) -> int { if n < 2 { return n }; return fib(n-1) + fib(n-2) }
|
||||
main {
|
||||
print_int(fib(10)) # 55
|
||||
let s = 0
|
||||
for i in 0 .. 10 { if i == 5 { break } if i % 2 == 0 { continue } s = s + i }
|
||||
for i in 0 .. 10 { if i == 5 { break }; if i % 2 == 0 { continue }; s = s + i }
|
||||
print_int(s) # 1+3 = 4
|
||||
let i = 0 let t = 0
|
||||
while true { i = i + 1 if i > 5 { break } t = t + i }
|
||||
let i = 0; let t = 0
|
||||
while true { i = i + 1; if i > 5 { break }; t = t + i }
|
||||
print_int(t) # 15
|
||||
if false and (1/0 == 0) { print_int(999) } else { print_int(1) } # short-circuit: no div by zero
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
game T {
|
||||
fn classify(c: int) -> int {
|
||||
match c { 65, 66 => { return 1 } 67 => { return 2 } _ => { return 9 } }
|
||||
match c { 65, 66 => { return 1 }; 67 => { return 2 }; _ => { return 9 } }
|
||||
return 0
|
||||
}
|
||||
main {
|
||||
|
|
|
|||
|
|
@ -142,6 +142,35 @@ kpath = ["repository", "keyword", "patterns"]
|
|||
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")
|
||||
|
||||
# --- 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:
|
||||
print("vocabulary drift:", file=sys.stderr)
|
||||
for p in problems:
|
||||
|
|
|
|||
|
|
@ -11,12 +11,11 @@
|
|||
;;; Code:
|
||||
|
||||
(defconst ludic--declaration-keywords
|
||||
'("game" "module" "import" "component" "archetype" "ui" "scene" "layer"
|
||||
"const" "var" "export" "fn" "pure" "extern" "system" "edge" "start" "on"))
|
||||
'("game" "module" "import" "component" "struct" "archetype" "enum" "ui"
|
||||
"const" "var" "fn" "extern" "system" "main"))
|
||||
|
||||
(defconst ludic--clause-keywords
|
||||
'("phase" "query" "reads" "writes" "needs" "uses"
|
||||
"requires" "ensures" "invariant" "effects"))
|
||||
'("phase" "query" "reads" "writes"))
|
||||
|
||||
(defconst ludic--statement-keywords
|
||||
'("let" "return" "if" "else" "when" "while" "for" "in" "spawn" "despawn"
|
||||
|
|
|
|||
|
|
@ -41,12 +41,11 @@ object LudicTokens {
|
|||
*/
|
||||
object LudicVocabulary {
|
||||
val DECL = setOf(
|
||||
"game", "module", "import", "component", "struct", "archetype", "ui", "scene", "layer",
|
||||
"const", "var", "export", "fn", "pure", "extern", "system", "edge", "start", "on"
|
||||
"game", "module", "import", "component", "struct", "archetype", "enum", "ui",
|
||||
"const", "var", "fn", "extern", "system", "main"
|
||||
)
|
||||
val CLAUSE = setOf(
|
||||
"phase", "query", "reads", "writes", "needs", "uses",
|
||||
"requires", "ensures", "invariant", "effects"
|
||||
"phase", "query", "reads", "writes"
|
||||
)
|
||||
val STMT = setOf(
|
||||
"let", "return", "if", "else", "when", "while", "for", "in", "spawn", "despawn",
|
||||
|
|
|
|||
|
|
@ -75,35 +75,25 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"match": "\\b(scene)\\s+([A-Za-z_][A-Za-z0-9_]*)(\\s+start)?",
|
||||
"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_]*)",
|
||||
"match": "\\b(ui)\\s+([A-Za-z_][A-Za-z0-9_]*)",
|
||||
"captures": {
|
||||
"1": { "name": "storage.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": {
|
||||
"1": { "name": "storage.modifier.edge.ludic" },
|
||||
"2": { "name": "storage.type.system.ludic" },
|
||||
"3": { "name": "entity.name.function.system.ludic" }
|
||||
"1": { "name": "storage.type.system.ludic" },
|
||||
"2": { "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": {
|
||||
"1": { "name": "storage.modifier.ludic" },
|
||||
"2": { "name": "storage.modifier.extern.ludic" },
|
||||
"3": { "name": "storage.type.function.ludic" },
|
||||
"4": { "name": "entity.name.function.ludic" }
|
||||
"1": { "name": "storage.modifier.extern.ludic" },
|
||||
"2": { "name": "storage.type.function.ludic" },
|
||||
"3": { "name": "entity.name.function.ludic" }
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -173,9 +163,9 @@
|
|||
"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.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.ludic", "match": "\\b(import|export|pure|extern|start|edge)\\b" },
|
||||
{ "name": "storage.type.ludic", "match": "\\b(game|module|component|archetype|ui|scene|layer|const|var|let|fn|system|state)\\b" },
|
||||
{ "name": "keyword.other.clause.ludic", "match": "\\b(phase|query|reads|writes)\\b" },
|
||||
{ "name": "keyword.other.ludic", "match": "\\b(import|extern)\\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": "constant.language.boolean.ludic", "match": "\\b(true|false)\\b" },
|
||||
{ "name": "constant.language.phase.ludic", "match": "\\b(Start|Input|FixedUpdate|Update|LateUpdate|Render)\\b" }
|
||||
|
|
|
|||
|
|
@ -75,35 +75,25 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"match": "\\b(scene)\\s+([A-Za-z_][A-Za-z0-9_]*)(\\s+start)?",
|
||||
"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_]*)",
|
||||
"match": "\\b(ui)\\s+([A-Za-z_][A-Za-z0-9_]*)",
|
||||
"captures": {
|
||||
"1": { "name": "storage.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": {
|
||||
"1": { "name": "storage.modifier.edge.ludic" },
|
||||
"2": { "name": "storage.type.system.ludic" },
|
||||
"3": { "name": "entity.name.function.system.ludic" }
|
||||
"1": { "name": "storage.type.system.ludic" },
|
||||
"2": { "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": {
|
||||
"1": { "name": "storage.modifier.ludic" },
|
||||
"2": { "name": "storage.modifier.extern.ludic" },
|
||||
"3": { "name": "storage.type.function.ludic" },
|
||||
"4": { "name": "entity.name.function.ludic" }
|
||||
"1": { "name": "storage.modifier.extern.ludic" },
|
||||
"2": { "name": "storage.type.function.ludic" },
|
||||
"3": { "name": "entity.name.function.ludic" }
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -173,9 +163,9 @@
|
|||
"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.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.ludic", "match": "\\b(import|export|pure|extern|start|edge)\\b" },
|
||||
{ "name": "storage.type.ludic", "match": "\\b(game|module|component|archetype|ui|scene|layer|const|var|let|fn|system|state)\\b" },
|
||||
{ "name": "keyword.other.clause.ludic", "match": "\\b(phase|query|reads|writes)\\b" },
|
||||
{ "name": "keyword.other.ludic", "match": "\\b(import|extern)\\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": "constant.language.boolean.ludic", "match": "\\b(true|false)\\b" },
|
||||
{ "name": "constant.language.phase.ludic", "match": "\\b(Start|Input|FixedUpdate|Update|LateUpdate|Render)\\b" }
|
||||
|
|
|
|||
|
|
@ -52,12 +52,18 @@ typedef struct {
|
|||
* 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","ui","scene","layer",
|
||||
"const","var","export","fn","pure","extern","system","edge","start","on", 0
|
||||
"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","needs","uses",
|
||||
"requires","ensures","invariant","effects", 0
|
||||
"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",
|
||||
|
|
|
|||
327
tools/ludic-tools/ludic_syntax.h.tmp
Normal file
327
tools/ludic-tools/ludic_syntax.h.tmp
Normal 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 */
|
||||
59
tools/ludic-tools/migrate_records.c
Normal file
59
tools/ludic-tools/migrate_records.c
Normal 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;
|
||||
}
|
||||
119
tools/ludic-tools/migrate_separators.c
Normal file
119
tools/ludic-tools/migrate_separators.c
Normal 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;
|
||||
}
|
||||
52
tools/ludic-tools/migrate_ui.c
Normal file
52
tools/ludic-tools/migrate_ui.c
Normal 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;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue