From 4c48077d68497c11ca2728d442bf48e6c58466ad Mon Sep 17 00:00:00 2001 From: Orkuncakilkaya Date: Sun, 30 Aug 2026 01:43:22 +0300 Subject: [PATCH] refactor(lang): rename the `fn` keyword to `function` Expand the function-declaration keyword to the full word across the whole language and toolchain: fn name(...) -> T { ... } -> function name(...) -> T { ... } Done as a self-hosting migration: teach the parser both spellings, reseed, rewrite every .ludic definition to `function`, then drop `fn`. The compiler now rejects `fn`. Touches the parser, all selfhost/tools/runtime/example/test sources, the grammars (TextMate shared+vscode, ludic_syntax.h, JetBrains LudicTokens.kt), the LSP and formatter, the Python doc/vocab tools (check-impl, check-docs, validate, palette, test-lsp), and the docs (fences, prose, kw-fn -> kw-function). Reseeded; C-free bootstrap fixpoint holds. All suites green (45 regression, 24 self-host, 29 tool); the docs site generates and check.py passes. Co-Authored-By: Claude Opus 4.8 --- BOOTSTRAP.md | 16 +- COMPILING.md | 6 +- LANGUAGE.md | 6 +- LUANTI-ROADMAP.md | 24 +- MOBILE-DESIGN.md | 8 +- NETWORKING-DESIGN.md | 2 +- SYNTAX-REDESIGN.md | 2 +- docs/language/annotations/annot-export.md | 6 +- docs/language/structure/kw-extern.md | 6 +- docs/language/structure/kw-fn.md | 38 -- docs/language/structure/kw-function.md | 38 ++ docs/language/structure/kw-return.md | 2 +- docs/language/types/type-ptr.md | 2 +- docs/language/types/type-void.md | 4 +- examples/chronorift/rules.ludic | 14 +- examples/lib/arena.ludic | 6 +- examples/lib/combat.ludic | 8 +- examples/net_rt.ludic | 4 +- examples/snake.ludic | 2 +- runtime/native/core.ludic | 78 ++--- runtime/native/image.ludic | 32 +- runtime/native/inflate.ludic | 30 +- runtime/native/truetype.ludic | 28 +- runtime/native/truetype_raster.ludic | 22 +- runtime/native/ui.ludic | 26 +- runtime/native/ui_draw.ludic | 28 +- selfhost/ast.ludic | 2 +- selfhost/buf.ludic | 12 +- selfhost/emit_addr.ludic | 4 +- selfhost/emit_collide.ludic | 8 +- selfhost/emit_color.ludic | 2 +- selfhost/emit_colorfn.ludic | 10 +- selfhost/emit_core.ludic | 96 ++--- selfhost/emit_decl.ludic | 10 +- selfhost/emit_ease.ludic | 6 +- selfhost/emit_ecs.ludic | 12 +- selfhost/emit_expr.ludic | 34 +- selfhost/emit_game.ludic | 24 +- selfhost/emit_hash.ludic | 10 +- selfhost/emit_head.ludic | 18 +- selfhost/emit_intrin.ludic | 8 +- selfhost/emit_intrin2.ludic | 4 +- selfhost/emit_list.ludic | 8 +- selfhost/emit_machine.ludic | 6 +- selfhost/emit_math.ludic | 18 +- selfhost/emit_mem.ludic | 4 +- selfhost/emit_net.ludic | 42 +-- selfhost/emit_new.ludic | 12 +- selfhost/emit_query.ludic | 4 +- selfhost/emit_save.ludic | 6 +- selfhost/emit_spawn.ludic | 14 +- selfhost/emit_stmt.ludic | 26 +- selfhost/emit_text.ludic | 8 +- selfhost/emit_time.ludic | 4 +- selfhost/emit_ui.ludic | 20 +- selfhost/io.ludic | 4 +- selfhost/lex.ludic | 8 +- selfhost/ludicc.seed.ll | 2 +- selfhost/main.ludic | 10 +- selfhost/parse.ludic | 84 ++--- selfhost/parse_game.ludic | 32 +- selfhost/str.ludic | 8 +- selfhost/tests/control.ludic | 2 +- selfhost/tests/long.ludic | 2 +- selfhost/tests/match_bits.ludic | 2 +- selfhost/tests/structs.ludic | 2 +- tools/atlas.ludic | 4 +- tools/check-docs.py | 2 +- tools/docgen/check-impl.py | 6 +- tools/docgen/inventory.json | 2 +- tools/docgen/palette.py | 2 +- tools/docgen/validate.py | 2 +- .../main/kotlin/io/ludic/ide/LudicTokens.kt | 4 +- tools/editors/shared/ludic.tmLanguage.json | 4 +- .../vscode/syntaxes/ludic.tmLanguage.json | 4 +- tools/ludic-tools/fmt.ludic | 84 ++--- tools/ludic-tools/lsp.ludic | 330 +++++++++--------- tools/ludic-tools/ludic_syntax.h | 2 +- tools/test-lsp.py | 4 +- tools/x/build.ludic | 8 +- tools/x/main.ludic | 4 +- tools/x/prelude.ludic | 46 +-- tools/x/selfhost.ludic | 24 +- tools/x/selfhost_test.ludic | 6 +- tools/x/test.ludic | 8 +- tools/x/tools.ludic | 14 +- 86 files changed, 793 insertions(+), 793 deletions(-) delete mode 100644 docs/language/structure/kw-fn.md create mode 100644 docs/language/structure/kw-function.md diff --git a/BOOTSTRAP.md b/BOOTSTRAP.md index cc3b772f..05ad7a75 100644 --- a/BOOTSTRAP.md +++ b/BOOTSTRAP.md @@ -109,10 +109,10 @@ All verified. A compiler needs each of these, and each one works today. | Deep recursion (recursive-descent parsing) | ✅ | 5,000 frames, no crash | | Heap allocation | ✅ | `mem_alloc`, `mem_free`, `mem_copy`, `mem_set`; 1 MiB alloc verified | | Byte-level memory | ✅ | `peek8`/`poke8`, `peek32`/`poke32`, `peekp`/`pokep`, `ptr_add` | -| `ptr` locals, params, returns | ✅ | `fn make(n: int) -> ptr` | +| `ptr` locals, params, returns | ✅ | `function make(n: int) -> ptr` | | `ptr` in a property field | ✅ | `property Nd { kind: int = 0, a: ptr = ptr_null() }` | | String literals as readable bytes | ✅ | `peek8("hello", 1)` → `101` | -| `str` accepted where `ptr` expected | ✅ | `f("A")` into `fn f(p: ptr)` | +| `str` accepted where `ptr` expected | ✅ | `f("A")` into `function f(p: ptr)` | | String comparison, **hand-written in Ludic** | ✅ | `streq` over `peek8` | | Integer → decimal, **hand-written in Ludic** | ✅ | `itoa(48291)` → `"48291"` | | File read: open/seek/tell/read/close | ✅ | full round-trip of a written file | @@ -361,7 +361,7 @@ whole project. | Gap | Why it does not block | |---|---| | **64-bit integers** ❌ (`100000*100000` → `1410065408`, wraps at i32) | Line numbers, offsets, node indices and string lengths all fit in `i32`. Only matters for source files > 2 GiB. | -| **A non-ECS entry point** | A `Start`-phase system plus `os_exit(n)` gives correct exit codes — verified. You do pay for an unused 1024-entity world; that is a constant, not a blocker. A `tool Name { fn main() -> int }` form would be nicer, not necessary. | +| **A non-ECS entry point** | A `Start`-phase system plus `os_exit(n)` gives correct exit codes — verified. You do pay for an unused 1024-entity world; that is a constant, not a blocker. A `tool Name { function main() -> int }` form would be nicer, not necessary. | | Closures, generics, unions, sum types | A compiler in the style of `ludicc.c` uses none of them. | | GC | A compiler should leak deliberately (§8). | | Unsigned integer types | `shr` is already logical and `band`/`bor` are bit-level — sufficient. | @@ -454,7 +454,7 @@ Each row verified by compiling a probe, not by reading docs. | ~~**R3**~~ | ~~**`and`/`or` alias `&&`/`\|\|`.**~~ **RESOLVED** — `and`/`or`/`not` are the only boolean operators; `&&`, `\|\|` and `!` are each rejected with a diagnostic naming the fix, and all three words are reserved. `!=` is unaffected. | landed via S3 | — | | **R4** | **`{ }` means seven different things** — statement block; property fields (`n: T = e`); model list (bare idents); spawn initialisers (`N = { … }`); ui props + children (`k=v` juxtaposed, no commas); match arms (`p, p => …`); machine states (`state N = v { … }`). | `block/comp/arche/spawn/parse_widget/match/machine` | The delimiter carries no information. You must already know the head keyword to know the inner grammar. | | **R5** | **Contextual keywords, not reserved.** `phase`, `query`, `reads`, `writes`, `needs`, `uses`, `where`, `in`, `on`, `layer`, `state`, `start` are matched with `isid()` — ordinary identifiers. `let query = 5 let phase = 6` compiles and prints `11`. | `sys()`, `scene_decl()` | A local named `enter` or `match` produces a baffling error far from the cause. | -| **R6** | **Contracts are parsed and thrown away.** `requires`/`ensures`/`invariant` parse an expression and **discard it** (`pi++; expr();`). `reads`/`writes`/`needs`/`uses`/`effects` are `skip_brackets()`. `pure` is consumed and ignored. Verified: `fn half(n: int) -> int requires n > 100000 ensures false` compiles, and `half(8)` returns `4`. Verified: a system declaring `reads [Pos]` that **writes** `p.x = 99` compiles. | `fn()`, `sys()` | **The worst item in the audit.** The language accepts a contract and does nothing. A model writing `requires n > 0` is rewarded with a clean compile and zero enforcement — it learns a lie, and so does a human reader trusting the annotation. | +| **R6** | **Contracts are parsed and thrown away.** `requires`/`ensures`/`invariant` parse an expression and **discard it** (`pi++; expr();`). `reads`/`writes`/`needs`/`uses`/`effects` are `skip_brackets()`. `pure` is consumed and ignored. Verified: `function half(n: int) -> int requires n > 100000 ensures false` compiles, and `half(8)` returns `4`. Verified: a system declaring `reads [Pos]` that **writes** `p.x = 99` compiles. | `fn()`, `sys()` | **The worst item in the audit.** The language accepts a contract and does nothing. A model writing `requires n > 0` is rewarded with a clean compile and zero enforcement — it learns a lie, and so does a human reader trusting the annotation. | | **R7** | **`str + str` typechecks, then emits invalid IR.** | verified (§4 B2) | The front-end accepts what the backend cannot lower. | | **R8** | **Two formatters, opposite philosophies, both called "format".** `ludicc --fmt` canonicalises hard (one statement per line, `and`→`&&`, full parenthesisation) but drops comments and inlines imports. `ludic-fmt` is token-based and preserves comments — but **normalises nothing**: handed the one-line `let a = 1 a = a + 1 if true and false { … }`, it returned it unchanged. | verified side-by-side | **Neither tool enforces a single spelling.** The canonicaliser is unusable on real source; the source formatter has no opinion. | | **R9** | **Two ways to spell a tag** — `property Player { }` (empty property) or `model`. | LANGUAGE.md | | @@ -863,14 +863,14 @@ current tree (`bin/x test` = 64/64). **Recursion** ✅ → `55` ```ludic program P { - fn fib(n: int) -> int { if n < 2 { return n }; return fib(n-1) + fib(n-2) } + function fib(n: int) -> int { if n < 2 { return n }; return fib(n-1) + fib(n-2) } handler B phase Start { print_int(fib(10)); quit() } } ``` **String comparison, hand-written** ✅ → `1` ```ludic -fn streq(a: ptr, b: ptr) -> bool { +function streq(a: ptr, b: ptr) -> bool { let i = 0 while true { let ca = peek8(a,i) @@ -885,7 +885,7 @@ fn streq(a: ptr, b: ptr) -> bool { **Integer → string, hand-written** ✅ → `48291` ```ludic -fn itoa(v: int, buf: ptr) -> int { +function itoa(v: int, buf: ptr) -> int { let n = 0 let x = v if x == 0 { poke8(buf,0,48); return 1 } @@ -955,7 +955,7 @@ print_int(query + phase) prints `4`: ```ludic # doc-check: skip — composite: declaration plus statements -fn half(n: int) -> int requires n > 100000 ensures false { return n / 2 } +function half(n: int) -> int requires n > 100000 ensures false { return n / 2 } print_int(half(8)) ``` And a system may declare read-only access, then write — also compiles: diff --git a/COMPILING.md b/COMPILING.md index 2a3d0f6d..f66849e3 100644 --- a/COMPILING.md +++ b/COMPILING.md @@ -103,8 +103,8 @@ A source file opens with `game Name { … }` or `module Name { … }`. ```ludic # doc-check: skip — illustrative: elided body program Combat { - @export fn damage(attack: int, armour: int, roll: int) -> int { … } - fn curve(level: int) -> int { … } # private: not a symbol + @export function damage(attack: int, armour: int, roll: int) -> int { … } + function curve(level: int) -> int { … } # private: not a symbol } ``` @@ -118,7 +118,7 @@ Those are ordinary C-ABI symbols, so anything that can call a shared library can call Ludic. To call them from another Ludic program, declare them and link: ```ludic -extern fn damage(attack: int, armour: int, roll: int) -> int = "damage" +extern function damage(attack: int, armour: int, roll: int) -> int = "damage" ``` ```bash diff --git a/LANGUAGE.md b/LANGUAGE.md index 232bbb22..08e5b2b3 100644 --- a/LANGUAGE.md +++ b/LANGUAGE.md @@ -502,7 +502,7 @@ object, so assigning or passing one shares it rather than copying. ```ludic property Tok { kind: int = 0, line: int = 0, next: Tok } -fn bump(t: Tok) -> void { t.kind = t.kind + 1 } +function bump(t: Tok) -> void { t.kind = t.kind + 1 } handler Share phase Update { let a = new Tok @@ -560,9 +560,9 @@ fields: `toks[i].kind = T_ID` is a single address computation. ## Functions & FFI ```ludic -fn heal(amount: int) -> int { return amount * 2 } +function heal(amount: int) -> int { return amount * 2 } -extern fn c_hypot(a: fixed, b: fixed) -> fixed = "hypot_fx" # bind a C symbol +extern function c_hypot(a: fixed, b: fixed) -> fixed = "hypot_fx" # bind a C symbol ``` `extern fn … = "symbol"` declares a foreign function and binds it to a symbol resolved at link time; pass `-L`/`-l` to ludicc to link its library. This is how diff --git a/LUANTI-ROADMAP.md b/LUANTI-ROADMAP.md index 12d3da6f..fb0a913d 100644 --- a/LUANTI-ROADMAP.md +++ b/LUANTI-ROADMAP.md @@ -429,7 +429,7 @@ nodes[i].param0 = C_STONE print_int(nodes.len) # slices: a (ptr, len) pair, no ownership — the workhorse for I/O -fn checksum(bytes: []u8) -> int { +function checksum(bytes: []u8) -> int { var sum = 0 for b in bytes { sum = sum + b } # `for x in slice` iteration return sum @@ -464,9 +464,9 @@ struct MapNode { struct v3i { x: int = 0, y: int = 0, z: int = 0 } -fn index(p: v3i) -> int { return (p.z * 16 + p.y) * 16 + p.x } +function index(p: v3i) -> int { return (p.z * 16 + p.y) * 16 + p.x } -fn get(blk: []MapNode, p: v3i) -> MapNode { return blk[index(p)] } +function get(blk: []MapNode, p: v3i) -> MapNode { return blk[index(p)] } # a component becomes "a struct that lives in the world" component Pos = v3i # aliasing form @@ -673,7 +673,7 @@ unknown node names, failed DB writes. It uses C++ exceptions sum-typed results with sugar, which falls straight out of G-06: ```ludic -fn read_block(path: str) -> MapBlock ! IoError { +function read_block(path: str) -> MapBlock ! IoError { let f = file_open(path, "rb") else return IoError.NotFound let n = try file_read(f, buf, 4096) # `try` propagates the error arm return parse(buf[0..n]) @@ -1085,7 +1085,7 @@ Ludic has a 320×240 software framebuffer presented through | Write a GPU driver | ✗ | not serious | **Recommended:** treat the GPU exactly as the project already treats the OS — -an ABI to call, not a C program to compile. `extern fn glDrawElements(…) = +an ABI to call, not a C program to compile. `extern function glDrawElements(…) = "glDrawElements"`, with a thin `runtime/native/gfx3d.ludic` over it. This is consistent with the existing rule ("C libraries are used via FFI") and needs **no compiler change beyond G-04's `f32`.** @@ -1097,11 +1097,11 @@ struct v3f { x: f32 = 0, y: f32 = 0, z: f32 = 0 } struct m4 { m: [16]f32 } struct aabb { min: v3f, max: v3f } -fn dot(a: v3f, b: v3f) -> f32 -fn cross(a: v3f, b: v3f) -> v3f -fn normalize(v: v3f) -> v3f -fn look_at(eye: v3f, at: v3f, up: v3f) -> m4 -fn perspective(fovy: f32, aspect: f32, znear: f32, zfar: f32) -> m4 +function dot(a: v3f, b: v3f) -> f32 +function cross(a: v3f, b: v3f) -> v3f +function normalize(v: v3f) -> v3f +function look_at(eye: v3f, at: v3f, up: v3f) -> m4 +function perspective(fovy: f32, aspect: f32, znear: f32, zfar: f32) -> m4 ``` Worth considering: **operator overloading for vector types only** — Ludic @@ -1211,11 +1211,11 @@ hand-rolled pointer arithmetic these features exist to delete. ```ludic # before (runtime/native/image.ludic today) -fn px(img: ptr, x: int, y: int, w: int) -> int { return peek32(img, (y*w+x)*4) } +function px(img: ptr, x: int, y: int, w: int) -> int { return peek32(img, (y*w+x)*4) } # after struct Rgba { r: u8, g: u8, b: u8, a: u8 } -fn px(img: []Rgba, x: int, y: int, w: int) -> Rgba { return img[y*w + x] } +function px(img: []Rgba, x: int, y: int, w: int) -> Rgba { return img[y*w + x] } ``` ### M1 — Language core, part 2: numbers, strings, errors *(≈6 weeks)* diff --git a/MOBILE-DESIGN.md b/MOBILE-DESIGN.md index d470fc1f..d651ff36 100644 --- a/MOBILE-DESIGN.md +++ b/MOBILE-DESIGN.md @@ -31,7 +31,7 @@ them: [:103](runtime/native/core.ludic:103), [:217](runtime/native/core.ludic:217)). `COMPILING.md` states the intent plainly: a new platform is "another `.ll` file with the same five entry points and no compiler change." -- **`extern fn` FFI is real and live** — `extern fn c_hypot(a: fixed, b: fixed) -> +- **`extern fn` FFI is real and live** — `extern function c_hypot(a: fixed, b: fixed) -> fixed = "hypot_fx"` ([LANGUAGE.md:565](LANGUAGE.md:565)), with a full pipeline: parse ([parse_game.ludic:236](selfhost/parse_game.ludic:236)) → call lowering to a direct `call @` ([emit_expr.ludic:168](selfhost/emit_expr.ludic:168)) → @@ -211,9 +211,9 @@ no new language feature, no new intrinsic: ```ludic # doc-check: skip — runtime/native/gfx3d.ludic, illustrative -extern fn gl_gen_textures(n: int, out: ptr) -> void = "glGenTextures" -extern fn gl_tex_image_2d(t: int, w: int, h: int, px: ptr) -> void = "gl_tex_image_2d" -extern fn gl_draw_elements(mode: int, count: int, ty: int, idx: ptr) -> void = "glDrawElements" +extern function gl_gen_textures(n: int, out: ptr) -> void = "glGenTextures" +extern function gl_tex_image_2d(t: int, w: int, h: int, px: ptr) -> void = "gl_tex_image_2d" +extern function gl_draw_elements(mode: int, count: int, ty: int, idx: ptr) -> void = "glDrawElements" ``` Two phases, additive: diff --git a/NETWORKING-DESIGN.md b/NETWORKING-DESIGN.md index 71a03e23..a18e2e0f 100644 --- a/NETWORKING-DESIGN.md +++ b/NETWORKING-DESIGN.md @@ -151,7 +151,7 @@ directly from these; the high-level layer (§6) is sugar over them. | Primitive | Signature (sketch) | Enables | |---|---|---| -| **Transport seam** | `extern fn net_send(peer: int, buf: ptr, len: int)` · `extern fn net_poll(buf: ptr, cap: int) -> int` | any model; host binds UDP (native) or WebRTC/WebSocket (wasm), or a loopback for tests | +| **Transport seam** | `extern function net_send(peer: int, buf: ptr, len: int)` · `extern function net_poll(buf: ptr, cap: int) -> int` | any model; host binds UDP (native) or WebRTC/WebSocket (wasm), or a loopback for tests | | **World snapshot ↔ buffer** | `world_save(buf: ptr) -> int` · `world_load(buf: ptr, len: int)` | rollback, replication, join/resync — generalizes `save()`/`load()` off the filesystem | | **Generated serializers** | `serialize_(e: entity, buf: ptr) -> int` · `apply_(e: entity, buf: ptr, len: int)` | per-model, touch only the `@Sync` fields; emitted from the schema | | **Ownership** | `owner(e: entity) -> int` · `set_owner(e: entity, id: int)` | authority checks, per-entity owner metadata (an `@L_owner` array, like `@L_kind`) | diff --git a/SYNTAX-REDESIGN.md b/SYNTAX-REDESIGN.md index a5593e07..c06a37d3 100644 --- a/SYNTAX-REDESIGN.md +++ b/SYNTAX-REDESIGN.md @@ -133,7 +133,7 @@ handler Move ### extern ```ludic # doc-check: skip — illustrative redesign snippet (proposed / partial syntax) -extern fn c_hypot(a: fixed, b: fixed) -> fixed = "hypot_fx" # unchanged +extern function c_hypot(a: fixed, b: fixed) -> fixed = "hypot_fx" # unchanged ``` The `= "symbol"` is a binding under Rule A — it stays. diff --git a/docs/language/annotations/annot-export.md b/docs/language/annotations/annot-export.md index ad66fb6c..3afbe7b7 100644 --- a/docs/language/annotations/annot-export.md +++ b/docs/language/annotations/annot-export.md @@ -4,18 +4,18 @@ name: @export category: annotations kind: annotation tokens: @export -sig: @export fn name(…) -> R +sig: @export function name(…) -> R tip: Expose a function as a native symbol so a host can call it. order: 3 --- -@export makes a fn visible outside the module as a plain native symbol, so a host program or another linked object can call it by name. Without it, functions are internal to the compiled unit; with it, the emitted symbol is externally linkable, which is how Ludic hands entry points to a runtime seam or a foreign caller. It is the outbound counterpart to extern fn, which pulls a foreign symbol in. Keep exported signatures to POD scalars and pointers, since they cross a native ABI boundary where Ludic's richer types do not apply. +@export makes a function visible outside the module as a plain native symbol, so a host program or another linked object can call it by name. Without it, functions are internal to the compiled unit; with it, the emitted symbol is externally linkable, which is how Ludic hands entry points to a runtime seam or a foreign caller. It is the outbound counterpart to extern function, which pulls a foreign symbol in. Keep exported signatures to POD scalars and pointers, since they cross a native ABI boundary where Ludic's richer types do not apply. ```ludic program ScoreModule { var running_total: int = 0 - @export fn add_points(amount: int) -> int { # callable from a native host + @export function add_points(amount: int) -> int { # callable from a native host running_total = running_total + amount return running_total } diff --git a/docs/language/structure/kw-extern.md b/docs/language/structure/kw-extern.md index 1e7bfb2c..8f939494 100644 --- a/docs/language/structure/kw-extern.md +++ b/docs/language/structure/kw-extern.md @@ -4,19 +4,19 @@ name: extern category: structure kind: keyword tokens: extern -sig: extern fn name(a: T) -> R = "symbol" +sig: extern function name(a: T) -> R = "symbol" tip: Bind a name to an external native symbol — the seam for platform and library calls. order: 12 --- -An extern fn declares a function whose body lives outside Ludic and binds it to a native symbol resolved at link time. It is the seam through which Ludic reaches anything with a native interface — a system library, a math routine, or even another `.ludic` file compiled as a `module`. You write the Ludic signature you want to call and give the real symbol name after `=`; the linker connects them (pass `-L`/`-l` to `ludicc` to point at the library). Types must match the foreign ABI, so map each parameter and the return to the right Ludic type (`int`, `fixed`, `ptr`, …). +An extern function declares a function whose body lives outside Ludic and binds it to a native symbol resolved at link time. It is the seam through which Ludic reaches anything with a native interface — a system library, a math routine, or even another `.ludic` file compiled as a `module`. You write the Ludic signature you want to call and give the real symbol name after `=`; the linker connects them (pass `-L`/`-l` to `ludicc` to point at the library). Types must match the foreign ABI, so map each parameter and the return to the right Ludic type (`int`, `fixed`, `ptr`, …). Parameters: - `a` — a typed argument passed straight through to the foreign symbol ```ludic program Physics { - extern fn c_hypot(a: fixed, b: fixed) -> fixed = "hypot_fx" + extern function c_hypot(a: fixed, b: fixed) -> fixed = "hypot_fx" property Velocity { delta_x: int = 0, delta_y: int = 0 } model Projectile { Velocity } diff --git a/docs/language/structure/kw-fn.md b/docs/language/structure/kw-fn.md deleted file mode 100644 index 47717353..00000000 --- a/docs/language/structure/kw-fn.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -id: kw-fn -name: fn -category: structure -kind: keyword -tokens: fn -sig: fn name(a: T, b: T) -> R { … } -tip: A function — reusable logic called positionally or with named arguments. -order: 8 ---- - -A fn declares a function: a reusable block of logic with typed parameters and a return type, written `-> R` (use `-> void` for one that returns nothing). Call it positionally, `heal(2, 10)`, or with named arguments, `heal(amount: 2, maximum: 10)`, which reads more clearly at the call site and is the house style. Functions live at program scope alongside handlers and may be called from any handler; they can `spawn`, run queries, and read program-scope state. Use them to factor out logic shared by several handlers so each handler stays short. - -Parameters: -- `a`, `b` — the typed inputs; pass them positionally or by name at the call site - -```ludic -program Healer { - property Health { current: int = 100, maximum: int = 100 } - model Player { Health } - - fn heal(amount: int, maximum: int) -> int { - let restored = amount * 2 - if restored > maximum { return maximum } - return restored - } - - handler Boot phase Start { - spawn Hero { Health { current: 10, maximum: 100 } } - } - - handler Recover phase Update { - for (health) in query [Health, {Player}] { - health.current = heal(amount: health.current, maximum: health.maximum) - } - } -} -``` diff --git a/docs/language/structure/kw-function.md b/docs/language/structure/kw-function.md new file mode 100644 index 00000000..4f5d707a --- /dev/null +++ b/docs/language/structure/kw-function.md @@ -0,0 +1,38 @@ +--- +id: kw-function +name: function +category: structure +kind: keyword +tokens: function +sig: function name(a: T, b: T) -> R { … } +tip: A function — reusable logic called positionally or with named arguments. +order: 8 +--- + +A function declares a function: a reusable block of logic with typed parameters and a return type, written `-> R` (use `-> void` for one that returns nothing). Call it positionally, `heal(2, 10)`, or with named arguments, `heal(amount: 2, maximum: 10)`, which reads more clearly at the call site and is the house style. Functions live at program scope alongside handlers and may be called from any handler; they can `spawn`, run queries, and read program-scope state. Use them to factor out logic shared by several handlers so each handler stays short. + +Parameters: +- `a`, `b` — the typed inputs; pass them positionally or by name at the call site + +```ludic +program Healer { + property Health { current: int = 100, maximum: int = 100 } + model Player { Health } + + function heal(amount: int, maximum: int) -> int { + let restored = amount * 2 + if restored > maximum { return maximum } + return restored + } + + handler Boot phase Start { + spawn Hero { Health { current: 10, maximum: 100 } } + } + + handler Recover phase Update { + for (health) in query [Health, {Player}] { + health.current = heal(amount: health.current, maximum: health.maximum) + } + } +} +``` diff --git a/docs/language/structure/kw-return.md b/docs/language/structure/kw-return.md index 38cf7f58..374f2373 100644 --- a/docs/language/structure/kw-return.md +++ b/docs/language/structure/kw-return.md @@ -16,7 +16,7 @@ program Clamp { property Health { current: int = 100, maximum: int = 100 } model Player { Health } - fn clamp_current(value: int, maximum: int) -> int { + function clamp_current(value: int, maximum: int) -> int { if value < 0 { return 0 } if value > maximum { return maximum } return value diff --git a/docs/language/types/type-ptr.md b/docs/language/types/type-ptr.md index 44c56c05..fb36276c 100644 --- a/docs/language/types/type-ptr.md +++ b/docs/language/types/type-ptr.md @@ -9,7 +9,7 @@ tip: A raw address into memory — a byte buffer from bytes(n), or an FFI handle order: 5 --- -`ptr` is a raw address into memory — the low-level type for runtime and foreign-function work, not something an everyday game reaches for. Allocate a raw byte buffer with `bytes(count)`, which returns a `ptr` you index as `buffer[index]` to read or write one byte; retype the binding as `words` / `fixeds` / `ptrs` to index in larger element sizes. A `ptr` is also how an `extern fn` passes an opaque C handle across the ABI. Test one for emptiness against the `null` literal. +`ptr` is a raw address into memory — the low-level type for runtime and foreign-function work, not something an everyday game reaches for. Allocate a raw byte buffer with `bytes(count)`, which returns a `ptr` you index as `buffer[index]` to read or write one byte; retype the binding as `words` / `fixeds` / `ptrs` to index in larger element sizes. A `ptr` is also how an `extern function` passes an opaque C handle across the ABI. Test one for emptiness against the `null` literal. ```ludic let scratch: ptr = bytes(256) diff --git a/docs/language/types/type-void.md b/docs/language/types/type-void.md index 58ad3620..ca3d63e7 100644 --- a/docs/language/types/type-void.md +++ b/docs/language/types/type-void.md @@ -9,10 +9,10 @@ tip: The absence of a value — the return type of a function that returns nothi order: 50 --- -`void` is the absence of a value. It appears as the return type of a function that runs for its effect and hands nothing back — `fn reset_score() -> void { … }`. Such a function is called as a statement, not used in an expression, and a bare `return` (with no value) leaves it early. Use `void` whenever a helper mutates program state, draws, or spawns rather than computing a result to return. +`void` is the absence of a value. It appears as the return type of a function that runs for its effect and hands nothing back — `function reset_score() -> void { … }`. Such a function is called as a statement, not used in an expression, and a bare `return` (with no value) leaves it early. Use `void` whenever a helper mutates program state, draws, or spawns rather than computing a result to return. ```ludic -fn reset_score() -> void { +function reset_score() -> void { score = 0 return } diff --git a/examples/chronorift/rules.ludic b/examples/chronorift/rules.ludic index 35b393ee..96d242de 100644 --- a/examples/chronorift/rules.ludic +++ b/examples/chronorift/rules.ludic @@ -1,6 +1,6 @@ # rules.ludic — pure game rules & map data (functions only, no systems). -fn tile_sprite(c: int) -> int { +function tile_sprite(c: int) -> int { match c { 'T', '#' => return SPR_TREE 'H' => return SPR_HOUSE @@ -11,18 +11,18 @@ fn tile_sprite(c: int) -> int { } return SPR_GRASS } -fn walkable(x: int, y: int) -> bool { +function walkable(x: int, y: int) -> bool { let c = Map.tile(x, y) return c == '.' or c == 'D' or c == 'r' or c == ',' or c == 'S' or c == 'B' or c == 'h' } -fn is_confirm(k: int) -> bool { return k == 32 or k == 10 or k == 13 } -fn enemy_name(et: int) -> str { +function is_confirm(k: int) -> bool { return k == 32 or k == 10 or k == 13 } +function enemy_name(et: int) -> str { if et == 0 { return "SLIME" } if et == 1 { return "SKELETON" } return "RIFT WARDEN" } -fn build_field(x: int) -> int { +function build_field(x: int) -> int { Map.size(40, 18) Map.row(0, "########################################") Map.row(1, "#......................................#") @@ -44,7 +44,7 @@ fn build_field(x: int) -> int { Map.row(17, "########################################") return 0 } -fn build_dungeon(x: int) -> int { +function build_dungeon(x: int) -> int { Map.size(40, 18) Map.row(0, "========================================") Map.row(1, "=,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,=") @@ -67,7 +67,7 @@ fn build_dungeon(x: int) -> int { return 0 } -fn grant_xp(amount: int) -> int { +function grant_xp(amount: int) -> int { for (t) in query [Stats, {Party}] { if t.hp > 0 { t.xp = t.xp + amount diff --git a/examples/lib/arena.ludic b/examples/lib/arena.ludic index e31effda..a6aeab3b 100644 --- a/examples/lib/arena.ludic +++ b/examples/lib/arena.ludic @@ -11,9 +11,9 @@ # ============================================================================ program Arena { - extern fn damage(attack: int, armour: int, roll: int) -> int = "damage" - extern fn hits_to_kill(hp: int, attack: int, armour: int) -> int = "hits_to_kill" - extern fn xp_for(level: int, kills: int) -> int = "xp_for" + extern function damage(attack: int, armour: int, roll: int) -> int = "damage" + extern function hits_to_kill(hp: int, attack: int, armour: int) -> int = "hits_to_kill" + extern function xp_for(level: int, kills: int) -> int = "xp_for" property Fighter { hp: int = 30, attack: int = 9, armour: int = 2 } diff --git a/examples/lib/combat.ludic b/examples/lib/combat.ludic index 2a66f393..2ccc1deb 100644 --- a/examples/lib/combat.ludic +++ b/examples/lib/combat.ludic @@ -16,7 +16,7 @@ program 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 function damage(attack: int, armour: int, roll: int) -> int { var raw = attack - armour if raw < 1 { raw = 1 } if roll == 20 { raw = raw * CRIT_MULT } @@ -25,7 +25,7 @@ program 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 function hits_to_kill(hp: int, attack: int, armour: int) -> int { var left = hp var n = 0 while left > 0 { @@ -35,12 +35,12 @@ program Combat { return n } - @export fn xp_for(level: int, kills: int) -> int { + @export function xp_for(level: int, kills: int) -> int { return curve(level) * kills } # private: not exported, so it is not a symbol in the library - fn curve(level: int) -> int { + function curve(level: int) -> int { return 10 + level * level * 3 } } diff --git a/examples/net_rt.ludic b/examples/net_rt.ludic index fd9d74c9..0e355728 100644 --- a/examples/net_rt.ludic +++ b/examples/net_rt.ludic @@ -14,7 +14,7 @@ # a real socket when a program binds `extern fn net_send/net_poll`). # The authority ships one entity's authoritative synced state to peers. -fn rt_replicate(e: int) -> void { +function rt_replicate(e: int) -> void { let w = words(512) w[0] = e # entity id in the first word let n = serialize(e, offset(w, 4)) # synced fields after it @@ -23,7 +23,7 @@ fn rt_replicate(e: int) -> void { # A peer drains every inbound snapshot and applies it to the named entity. One # datagram per poll (the transport is datagram-preserving), so loop until empty. -fn rt_receive() -> void { +function rt_receive() -> void { let w = words(512) var n = net_poll(w, 2048) while n > 0 { diff --git a/examples/snake.ludic b/examples/snake.ludic index 71907a68..31109f5e 100644 --- a/examples/snake.ludic +++ b/examples/snake.ludic @@ -46,7 +46,7 @@ program Snake { const SHADE_SCORE: int = 0xffe060 const SHADE_OVER: int = 0xff5555 - fn reset(unused: int) -> int { + function reset(unused: 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 } } diff --git a/runtime/native/core.ludic b/runtime/native/core.ludic index 4b38cfd1..a5a48e77 100644 --- a/runtime/native/core.ludic +++ b/runtime/native/core.ludic @@ -29,11 +29,11 @@ var rt_alive: int = 1 # platform still running? # 5x7 glyphs for ASCII 32..90, 7 rows per glyph, each row a 5-bit mask stored # biased by '0' so the whole font is one printable string literal. -fn rt_font() -> str { +function rt_font() -> str { return "00000004444404000000000000000000000IJ4:FC000000000000000000000000000000E>O>E0044O4400000448000O000000004012448@@>ACEIA>4<4444>>A168@ON11>11N26:BO22O@N11A>>@@NAA>O124888>AA>AA>>AA?11>04004000000000024842000O0O000842480>A164040000000>AAOAAANAANAAN>A@@@A>LBAAABLO@@L@@OO@@L@@@>A@GAA>AAAOAAA>44444>7222BBAAAAA>NAAN@@@>AAAEB=NAANDBA>A@>1A>O444444AAAAAA>AAAAA:4AAAEEKAAA:4:AAAA:4444O1248@O" } -fn rt_init() -> void { +function rt_init() -> void { rt_fb = words(320 * 240) rt_regs = words(64) fill(rt_regs, 0, 64 * 4) @@ -50,7 +50,7 @@ fn rt_init() -> void { } } -fn rt_shutdown() -> void { +function rt_shutdown() -> void { if is_windowed() { win_close() return @@ -59,17 +59,17 @@ fn rt_shutdown() -> void { } # ---- framebuffer ---------------------------------------------------------- -fn rt_screen_w() -> int { return rt_fbw } -fn rt_screen_h() -> int { return rt_fbh } +function rt_screen_w() -> int { return rt_fbw } +function rt_screen_h() -> int { return rt_fbh } -fn rt_clear(c: int) -> void { +function rt_clear(c: int) -> void { let n = rt_fbw * rt_fbh for i in 0 .. n { rt_fb[i] = c } } -fn rt_put_px(x: int, y: int, c: int) -> void { +function rt_put_px(x: int, y: int, c: int) -> void { if x < 0 { return } if y < 0 { return } if x >= rt_fbw { return } @@ -77,7 +77,7 @@ fn rt_put_px(x: int, y: int, c: int) -> void { rt_fb[y * rt_fbw + x] = c } -fn rt_fill_rect(x: int, y: int, w: int, h: int, c: int) -> void { +function rt_fill_rect(x: int, y: int, w: int, h: int, c: int) -> void { let x0 = max(0, x) let y0 = max(0, y) let x1 = min(rt_fbw, x + w) @@ -94,7 +94,7 @@ fn rt_fill_rect(x: int, y: int, w: int, h: int, c: int) -> void { } } -fn rt_frame_rect(x: int, y: int, w: int, h: int, c: int) -> void { +function rt_frame_rect(x: int, y: int, w: int, h: int, c: int) -> void { rt_fill_rect(x, y, w, 1, c) rt_fill_rect(x, y + h - 1, w, 1, c) rt_fill_rect(x, y, 1, h, c) @@ -102,7 +102,7 @@ fn rt_frame_rect(x: int, y: int, w: int, h: int, c: int) -> void { } # A straight line by Bresenham's algorithm — integer only, any direction. -fn rt_line(x0: int, y0: int, x1: int, y1: int, c: int) -> void { +function rt_line(x0: int, y0: int, x1: int, y1: int, c: int) -> void { var x = x0; var y = y0 let dx = abs(x1 - x0); let dy = 0 - abs(y1 - y0) var sx = 0 - 1; if x0 < x1 { sx = 1 } @@ -118,7 +118,7 @@ fn rt_line(x0: int, y0: int, x1: int, y1: int, c: int) -> void { } # A circle outline by the midpoint algorithm (eight-way symmetry). -fn rt_circle(cx: int, cy: int, r: int, c: int) -> void { +function rt_circle(cx: int, cy: int, r: int, c: int) -> void { if r < 0 { return } var x = r; var y = 0; var err = 1 - r while x >= y { @@ -133,7 +133,7 @@ fn rt_circle(cx: int, cy: int, r: int, c: int) -> void { } # A filled disc — one horizontal span per row, width from the circle equation. -fn rt_fill_circle(cx: int, cy: int, r: int, c: int) -> void { +function rt_fill_circle(cx: int, cy: int, r: int, c: int) -> void { if r < 0 { return } let r2 = r * r var dy = 0 - r @@ -146,14 +146,14 @@ fn rt_fill_circle(cx: int, cy: int, r: int, c: int) -> void { } # A triangle outline — three lines. -fn rt_triangle(x0: int, y0: int, x1: int, y1: int, x2: int, y2: int, c: int) -> void { +function rt_triangle(x0: int, y0: int, x1: int, y1: int, x2: int, y2: int, c: int) -> void { rt_line(x0, y0, x1, y1, c) rt_line(x1, y1, x2, y2, c) rt_line(x2, y2, x0, y0, c) } # A filled triangle — bounding-box scan with an edge-sign inside test. -fn rt_fill_triangle(x0: int, y0: int, x1: int, y1: int, x2: int, y2: int, c: int) -> void { +function rt_fill_triangle(x0: int, y0: int, x1: int, y1: int, x2: int, y2: int, c: int) -> void { let minx = min(x0, min(x1, x2)); let maxx = max(x0, max(x1, x2)) let miny = min(y0, min(y1, y2)); let maxy = max(y0, max(y1, y2)) var py = miny @@ -174,14 +174,14 @@ fn rt_fill_triangle(x0: int, y0: int, x1: int, y1: int, x2: int, y2: int, c: int # Windowed: hand the framebuffer to the platform layer, which blits it into # the view. Headless: nothing to do until shutdown writes the last frame out. -fn rt_present() -> void { +function rt_present() -> void { if is_windowed() { win_present(rt_fb, rt_fbw, rt_fbh) } } # ---- text ----------------------------------------------------------------- -fn rt_glyph(x: int, y: int, ch: int, colour: int, sc: int) -> void { +function rt_glyph(x: int, y: int, ch: int, colour: int, sc: int) -> void { var c = ch if c >= 97 { if c <= 122 { c = c - 32 } @@ -203,7 +203,7 @@ fn rt_glyph(x: int, y: int, ch: int, colour: int, sc: int) -> void { } } -fn rt_text(x: int, y: int, s: str, colour: int, sc: int) -> void { +function rt_text(x: int, y: int, s: str, colour: int, sc: int) -> void { var i = 0 var cx = x var ch = s[0] @@ -215,7 +215,7 @@ fn rt_text(x: int, y: int, s: str, colour: int, sc: int) -> void { } } -fn rt_text_int(x: int, y: int, n: int, colour: int, sc: int) -> void { +function rt_text_int(x: int, y: int, n: int, colour: int, sc: int) -> void { if n == 0 { rt_glyph(x, y, 48, colour, sc) return @@ -246,20 +246,20 @@ fn rt_text_int(x: int, y: int, n: int, colour: int, sc: int) -> void { } # ---- registers ------------------------------------------------------------ -fn rt_reg(i: int) -> int { +function rt_reg(i: int) -> int { if i < 0 { return 0 } if i >= 64 { return 0 } return rt_regs[i] } -fn rt_set_reg(i: int, v: int) -> void { +function rt_set_reg(i: int, v: int) -> void { if i < 0 { return } if i >= 64 { return } rt_regs[i] = v } # ---- rng (xorshift32) ----------------------------------------------------- -fn rt_seed(s: int) -> void { +function rt_seed(s: int) -> void { if s == 0 { rt_rng = 305419896 return @@ -269,7 +269,7 @@ fn rt_seed(s: int) -> void { # xorshift32 (Marsaglia). Runs on the raw 32-bit pattern, so the sign bit is # masked off only when a caller asks for a number. -fn rt_next_rand() -> int { +function rt_next_rand() -> int { var x = rt_rng x = (x ^ (x << 13)) x = (x ^ (x >> 17)) @@ -278,35 +278,35 @@ fn rt_next_rand() -> int { return (x & 2147483647) } -fn rt_rng_range(lo: int, hi: int) -> int { +function rt_rng_range(lo: int, hi: int) -> int { if hi <= lo { return lo } return lo + rt_next_rand() % (hi - lo + 1) } -fn rt_rng_chance(pct: int) -> bool { +function rt_rng_chance(pct: int) -> bool { return rt_next_rand() % 100 < pct } # a deterministic fixed-point value in [0, 1) — the raw 0..65535 is exactly the # Q16.16 fraction (fixed and int share the i32 representation). -fn rt_rng_value() -> fixed { +function rt_rng_value() -> fixed { return rt_rng_range(0, 65535) } # a deterministic integer in [0, max) — 0 when max <= 0 -fn rt_rng_int(max: int) -> int { +function rt_rng_int(max: int) -> int { if max <= 0 { return 0 } return rt_rng_range(0, max - 1) } # a deterministic +1 or -1 -fn rt_rng_sign() -> int { +function rt_rng_sign() -> int { if rt_rng_chance(50) { return 1 } return 0 - 1 } # ---- platform: input ------------------------------------------------------ -fn rt_poll() -> int { +function rt_poll() -> int { if is_windowed() { return win_poll() } @@ -321,7 +321,7 @@ fn rt_poll() -> int { return c } -fn rt_running() -> bool { +function rt_running() -> bool { if is_windowed() { return win_running() } @@ -329,7 +329,7 @@ fn rt_running() -> bool { } # ---- writing the frame out ------------------------------------------------ -fn rt_put_str(buf: ptr, at: int, s: str) -> int { +function rt_put_str(buf: ptr, at: int, s: str) -> int { var i = 0 var n = at var ch = s[0] @@ -342,7 +342,7 @@ fn rt_put_str(buf: ptr, at: int, s: str) -> int { return n } -fn rt_put_int(buf: ptr, at: int, v: int) -> int { +function rt_put_int(buf: ptr, at: int, v: int) -> int { if v == 0 { buf[at] = 48 return at + 1 @@ -367,7 +367,7 @@ fn rt_put_int(buf: ptr, at: int, v: int) -> int { return n } -fn rt_dump_ppm(path: str) -> void { +function rt_dump_ppm(path: str) -> void { let f = file_open(path, "wb") if (f == null) { return } @@ -406,13 +406,13 @@ var rt_map: ptr = null var rt_mapw: int = 0 var rt_maph: int = 0 -fn rt_map_size(w: int, h: int) -> void { +function rt_map_size(w: int, h: int) -> void { rt_mapw = clamp(w, 0, 96) rt_maph = clamp(h, 0, 64) fill(rt_map, 32, 96 * 64) } -fn rt_map_row(y: int, s: str) -> void { +function rt_map_row(y: int, s: str) -> void { if y < 0 { return } if y >= 64 { return } var x = 0 @@ -425,7 +425,7 @@ fn rt_map_row(y: int, s: str) -> void { } } -fn rt_tile(x: int, y: int) -> int { +function rt_tile(x: int, y: int) -> int { if x < 0 { return 35 } if y < 0 { return 35 } if x >= rt_mapw { return 35 } @@ -438,7 +438,7 @@ fn rt_tile(x: int, y: int) -> int { # so it survives whatever the caller does with the original. var rt_statusbuf: ptr = null -fn rt_status(s: str) -> void { +function rt_status(s: str) -> void { var i = 0 var ch = s[0] while ch != 0 { @@ -452,7 +452,7 @@ fn rt_status(s: str) -> void { rt_statusbuf[i] = 0 } -fn rt_status_text() -> ptr { +function rt_status_text() -> ptr { return rt_statusbuf } @@ -460,7 +460,7 @@ fn rt_status_text() -> ptr { # The compiler writes the ECS (entities, components, archetype kinds) because # only it knows their shape. Everything below belongs to the runtime, so the # runtime writes it — same order both ways. -fn rt_save_state(f: ptr) -> void { +function rt_save_state(f: ptr) -> void { let w: words = bytes(16) w[0] = rt_rng w[1] = rt_mapw @@ -473,7 +473,7 @@ fn rt_save_state(f: ptr) -> void { free(w) } -fn rt_load_state(f: ptr) -> void { +function rt_load_state(f: ptr) -> void { let w: words = bytes(16) file_read(f, w, 16) rt_rng = w[0] diff --git a/runtime/native/image.ludic b/runtime/native/image.ludic index d0f7052a..5d8a1ecf 100644 --- a/runtime/native/image.ludic +++ b/runtime/native/image.ludic @@ -21,7 +21,7 @@ var img_n: int = 0 var spr_px: words = null # SPR_MAX * 16 * 16 RGBA pixels, one block var spr_n: int = 0 -fn rt_image_init() -> void { +function rt_image_init() -> void { img_px = bytes(IMG_MAX * 8) img_w = words(IMG_MAX) img_h = words(IMG_MAX) @@ -30,11 +30,11 @@ fn rt_image_init() -> void { } # ---- decoding ------------------------------------------------------------- -fn png_be32(b: ptr, at: int) -> int { +function png_be32(b: ptr, at: int) -> int { return (b[at] << 24) | (b[at + 1] << 16) | (b[at + 2] << 8) | b[at + 3] } -fn png_tag(b: ptr, at: int, a: int, c: int, d: int, e: int) -> bool { +function png_tag(b: ptr, at: int, a: int, c: int, d: int, e: int) -> bool { if b[at] != a { return false } if b[at + 1] != c { return false } if b[at + 2] != d { return false } @@ -42,7 +42,7 @@ fn png_tag(b: ptr, at: int, a: int, c: int, d: int, e: int) -> bool { return true } -fn rt_read_file(path: str) -> ptr { +function rt_read_file(path: str) -> ptr { let f = file_open(path, "rb") if (f == null) { return null } file_seek(f, 0, 2) @@ -62,7 +62,7 @@ var png_w: int = 0 var png_h: int = 0 var png_px: words = null -fn rt_decode_png(path: str) -> bool { +function rt_decode_png(path: str) -> bool { png_w = 0 png_h = 0 png_px = null @@ -227,7 +227,7 @@ fn rt_decode_png(path: str) -> bool { } # ---- images --------------------------------------------------------------- -fn rt_image_load(path: str) -> int { +function rt_image_load(path: str) -> int { if img_n >= IMG_MAX { return 0 - 1 } if rt_decode_png(path) == false { return 0 - 1 } let id = img_n @@ -238,7 +238,7 @@ fn rt_image_load(path: str) -> int { return id } -fn rt_blend_px(x: int, y: int, argb: int) -> void { +function rt_blend_px(x: int, y: int, argb: int) -> void { let a = ((argb >> 24) & 255) if a == 0 { return } if x < 0 { return } @@ -260,7 +260,7 @@ fn rt_blend_px(x: int, y: int, argb: int) -> void { rt_fb[y * rt_fbw + x] = (((r << 16) | (g << 8)) | b) } -fn rt_draw_image(id: int, dx: int, dy: int) -> void { +function rt_draw_image(id: int, dx: int, dy: int) -> void { if id < 0 { return } if id >= img_n { return } let w = img_w[id] @@ -273,7 +273,7 @@ fn rt_draw_image(id: int, dx: int, dy: int) -> void { } } -fn rt_draw_image_scaled(id: int, dx: int, dy: int, dw: int, dh: int) -> void { +function rt_draw_image_scaled(id: int, dx: int, dy: int, dw: int, dh: int) -> void { if id < 0 { return } if id >= img_n { return } if dw <= 0 { return } @@ -290,7 +290,7 @@ fn rt_draw_image_scaled(id: int, dx: int, dy: int, dw: int, dh: int) -> void { # map one destination axis onto the source for a 9-slice: the two insets are # copied 1:1 and only the middle stretches -fn rt_9map(d: int, dsz: int, ssz: int, inset: int) -> int { +function rt_9map(d: int, dsz: int, ssz: int, inset: int) -> int { if inset * 2 >= dsz { return d * ssz / max(dsz, 1) } if inset * 2 >= ssz { return d * ssz / max(dsz, 1) } if d < inset { return d } @@ -298,7 +298,7 @@ fn rt_9map(d: int, dsz: int, ssz: int, inset: int) -> int { return inset + (d - inset) * (ssz - 2 * inset) / (dsz - 2 * inset) } -fn rt_draw_9slice(id: int, dx: int, dy: int, dw: int, dh: int, inset: int) -> void { +function rt_draw_9slice(id: int, dx: int, dy: int, dw: int, dh: int, inset: int) -> void { if id < 0 { return } if id >= img_n { return } if dw <= 0 { return } @@ -315,7 +315,7 @@ fn rt_draw_9slice(id: int, dx: int, dy: int, dw: int, dh: int, inset: int) -> vo } # ---- sprites (16x16 art) -------------------------------------------------- -fn rt_png_load(path: str) -> int { +function rt_png_load(path: str) -> int { if spr_n >= SPR_MAX { return 0 - 1 } if rt_decode_png(path) == false { return 0 - 1 } let id = spr_n @@ -338,7 +338,7 @@ fn rt_png_load(path: str) -> int { return id } -fn rt_draw_sprite(id: int, px: int, py: int) -> void { +function rt_draw_sprite(id: int, px: int, py: int) -> void { if id < 0 { return } if id >= spr_n { return } let base = id * SPR_SZ * SPR_SZ @@ -352,7 +352,7 @@ fn rt_draw_sprite(id: int, px: int, py: int) -> void { } } -fn rt_draw_sprite_scaled(id: int, px: int, py: int, sc: int) -> void { +function rt_draw_sprite_scaled(id: int, px: int, py: int, sc: int) -> void { if id < 0 { return } if id >= spr_n { return } if sc < 1 { return } @@ -368,14 +368,14 @@ fn rt_draw_sprite_scaled(id: int, px: int, py: int, sc: int) -> void { } # ---- .lspr sprite sheets (palette + ASCII rows) --------------------------- -fn rt_hexval(c: int) -> int { +function rt_hexval(c: int) -> int { if c >= 48 { if c <= 57 { return c - 48 } } if c >= 97 { if c <= 102 { return c - 87 } } if c >= 65 { if c <= 70 { return c - 55 } } return 0 - 1 } -fn rt_sprites_load(path: str) -> void { +function rt_sprites_load(path: str) -> void { let d = rt_read_file(path) if (d == null) { spr_n = 0; return } let size = rt_file_len diff --git a/runtime/native/inflate.ludic b/runtime/native/inflate.ludic index e22d15fb..094d2f1c 100644 --- a/runtime/native/inflate.ludic +++ b/runtime/native/inflate.ludic @@ -20,7 +20,7 @@ var z_bitbuf: int = 0 var z_bitcnt: int = 0 var z_err: int = 0 -fn z_start(src: ptr, len: int) -> void { +function z_start(src: ptr, len: int) -> void { z_src = src z_len = len z_pos = 0 @@ -29,7 +29,7 @@ fn z_start(src: ptr, len: int) -> void { z_err = 0 } -fn z_bits(need: int) -> int { +function z_bits(need: int) -> int { var val = z_bitbuf while z_bitcnt < need { if z_pos >= z_len { @@ -48,12 +48,12 @@ fn z_bits(need: int) -> int { # ---- Huffman tables ------------------------------------------------------- # A table is a single buffer: 16 length-counts followed by the symbols in # canonical order. One allocation, no structs. -fn z_table_new(nsym: int) -> ptr { +function z_table_new(nsym: int) -> ptr { return words((16 + nsym)) } # lengths[i] = code length of symbol i (0 = symbol unused) -fn z_table_build(table: words, lengths: words, n: int) -> void { +function z_table_build(table: words, lengths: words, n: int) -> void { for i in 0 .. 16 { table[i] = 0 } @@ -78,7 +78,7 @@ fn z_table_build(table: words, lengths: words, n: int) -> void { free(offs) } -fn z_decode(table: words) -> int { +function z_decode(table: words) -> int { var code = 0 var first = 0 var index = 0 @@ -97,7 +97,7 @@ fn z_decode(table: words) -> int { } # ---- length / distance code tables (RFC 1951 section 3.2.5) --------------- -fn z_len_base(sym: int) -> int { +function z_len_base(sym: int) -> int { if sym < 8 { return 3 + sym } if sym == 28 { return 258 } let extra = (sym - 4) / 4 @@ -105,27 +105,27 @@ fn z_len_base(sym: int) -> int { return 3 + ((group - 1) << 2) + 4 + (sym - 4 - extra * 4) * group } -fn z_len_extra(sym: int) -> int { +function z_len_extra(sym: int) -> int { if sym < 8 { return 0 } if sym == 28 { return 0 } return (sym - 4) / 4 } -fn z_dist_base(sym: int) -> int { +function z_dist_base(sym: int) -> int { if sym < 4 { return 1 + sym } let extra = (sym - 2) / 2 let group = (1 << extra) return 1 + (group << 1) + (sym - 2 - extra * 2) * group } -fn z_dist_extra(sym: int) -> int { +function z_dist_extra(sym: int) -> int { if sym < 4 { return 0 } return (sym - 2) / 2 } # ---- block decoders ------------------------------------------------------- # `out` is the destination window; returns the new write position, or -1. -fn z_stored(out: ptr, at: int, cap: int) -> int { +function z_stored(out: ptr, at: int, cap: int) -> int { z_bitbuf = 0 z_bitcnt = 0 # stored blocks are byte-aligned if z_pos + 4 > z_len { return -1 } @@ -142,7 +142,7 @@ fn z_stored(out: ptr, at: int, cap: int) -> int { return w } -fn z_codes(out: ptr, at: int, cap: int, lit: ptr, dist: ptr) -> int { +function z_codes(out: ptr, at: int, cap: int, lit: ptr, dist: ptr) -> int { var w = at var sym = z_decode(lit) while sym != 256 { @@ -172,7 +172,7 @@ fn z_codes(out: ptr, at: int, cap: int, lit: ptr, dist: ptr) -> int { return w } -fn z_fixed_tables(lit: ptr, dist: ptr) -> void { +function z_fixed_tables(lit: ptr, dist: ptr) -> void { let lengths: words = words(288) for i in 0 .. 144 { lengths[i] = 8 } for i in 144 .. 256 { lengths[i] = 9 } @@ -184,7 +184,7 @@ fn z_fixed_tables(lit: ptr, dist: ptr) -> void { free(lengths) } -fn z_dynamic_tables(lit: ptr, dist: ptr) -> int { +function z_dynamic_tables(lit: ptr, dist: ptr) -> int { let nlen = z_bits(5) + 257 let ndist = z_bits(5) + 1 let ncode = z_bits(4) + 4 @@ -240,7 +240,7 @@ fn z_dynamic_tables(lit: ptr, dist: ptr) -> int { } # Inflate a raw DEFLATE stream. Returns bytes written, or -1. -fn z_inflate(src: ptr, len: int, out: ptr, cap: int) -> int { +function z_inflate(src: ptr, len: int, out: ptr, cap: int) -> int { z_start(src, len) let lit = z_table_new(288) let dist = z_table_new(30) @@ -268,7 +268,7 @@ fn z_inflate(src: ptr, len: int, out: ptr, cap: int) -> int { } # zlib wrapper (RFC 1950): two header bytes, then DEFLATE, then Adler-32. -fn z_uncompress(src: ptr, len: int, out: ptr, cap: int) -> int { +function z_uncompress(src: ptr, len: int, out: ptr, cap: int) -> int { if len < 2 { return -1 } let cmf = src[0] if (cmf & 15) != 8 { return -1 } diff --git a/runtime/native/truetype.ludic b/runtime/native/truetype.ludic index 7ad0df33..bbacc74f 100644 --- a/runtime/native/truetype.ludic +++ b/runtime/native/truetype.ludic @@ -63,7 +63,7 @@ var gc_oy: words = null var gc_adv: words = null var gc_bmp: ptrs = null -fn rt_tt_init() -> void { +function rt_tt_init() -> void { tt_data = bytes(TT_MAX * 8) tt_size = words(TT_MAX) tt_upem = words(TT_MAX) @@ -105,24 +105,24 @@ fn rt_tt_init() -> void { } # ---- big-endian readers --------------------------------------------------- -fn tt_u16(d: ptr, at: int) -> int { +function tt_u16(d: ptr, at: int) -> int { return ((d[at] << 8) | d[at + 1]) } -fn tt_i16(d: ptr, at: int) -> int { +function tt_i16(d: ptr, at: int) -> int { let v = tt_u16(d, at) if v >= 32768 { return v - 65536 } return v } -fn tt_u32(d: ptr, at: int) -> int { +function tt_u32(d: ptr, at: int) -> int { return (d[at] << 24) | (d[at + 1] << 16) | (d[at + 2] << 8) | d[at + 3] } -fn tt_i8(d: ptr, at: int) -> int { +function tt_i8(d: ptr, at: int) -> int { let v = d[at] if v >= 128 { return v - 256 } return v } -fn tt_find_table(d: ptr, base: int, a: int, b: int, c: int, e: int) -> int { +function tt_find_table(d: ptr, base: int, a: int, b: int, c: int, e: int) -> int { let n = tt_u16(d, base + 4) for i in 0 .. n { let rec = base + 12 + i * 16 @@ -138,7 +138,7 @@ fn tt_find_table(d: ptr, base: int, a: int, b: int, c: int, e: int) -> int { } # pick the most capable Unicode subtable, as the C loader did -fn tt_pick_cmap(id: int, d: ptr, co: int) -> void { +function tt_pick_cmap(id: int, d: ptr, co: int) -> void { tt_cmap[id] = 0 - 1 tt_cfmt[id] = 0 if co < 0 { return } @@ -166,7 +166,7 @@ fn tt_pick_cmap(id: int, d: ptr, co: int) -> void { tt_cfmt[id] = tt_u16(d, best) } -fn rt_font_load(path: str) -> int { +function rt_font_load(path: str) -> int { if tt_n >= TT_MAX { return 0 - 1 } let d = rt_read_file(path) if (d == null) { return 0 - 1 } @@ -212,7 +212,7 @@ fn rt_font_load(path: str) -> int { } # ---- codepoint -> glyph id ------------------------------------------------ -fn tt_glyph_index(id: int, cp: int) -> int { +function tt_glyph_index(id: int, cp: int) -> int { let d = tt_data[id] let s = tt_cmap[id] if s < 0 { return 0 } @@ -261,7 +261,7 @@ fn tt_glyph_index(id: int, cp: int) -> int { return 0 } -fn tt_advance(id: int, gid: int) -> int { +function tt_advance(id: int, gid: int) -> int { let d = tt_data[id] let hmtx = tt_hmtx[id] let n = tt_nhm[id] @@ -270,7 +270,7 @@ fn tt_advance(id: int, gid: int) -> int { } # ---- outline extraction --------------------------------------------------- -fn ol_pt(x: fixed, y: fixed, on: int) -> void { +function ol_pt(x: fixed, y: fixed, on: int) -> void { if ol_n >= TT_PTS { return } ol_x[ol_n] = x ol_y[ol_n] = y @@ -278,13 +278,13 @@ fn ol_pt(x: fixed, y: fixed, on: int) -> void { ol_n = ol_n + 1 } -fn tt_glyph_start(id: int, gid: int) -> int { +function tt_glyph_start(id: int, gid: int) -> int { let d = tt_data[id] let loca = tt_loca[id] if tt_locfm[id] == 0 { return tt_u16(d, loca + gid * 2) * 2 } return tt_u32(d, loca + gid * 4) } -fn tt_glyph_end(id: int, gid: int) -> int { +function tt_glyph_end(id: int, gid: int) -> int { let d = tt_data[id] let loca = tt_loca[id] if tt_locfm[id] == 0 { return tt_u16(d, loca + (gid + 1) * 2) * 2 } @@ -292,7 +292,7 @@ fn tt_glyph_end(id: int, gid: int) -> int { } # Append glyph `gid`, transformed by [a b c e] + (dx,dy), to the outline. -fn tt_load_outline(id: int, gid: int, a: fixed, b: fixed, c: fixed, e: fixed, dx: fixed, dy: fixed, depth: int) -> void { +function tt_load_outline(id: int, gid: int, a: fixed, b: fixed, c: fixed, e: fixed, dx: fixed, dy: fixed, depth: int) -> void { if gid < 0 { return } if gid >= tt_nglyf[id] { return } if depth > 5 { return } diff --git a/runtime/native/truetype_raster.ludic b/runtime/native/truetype_raster.ludic index 411c3aa8..d85b053b 100644 --- a/runtime/native/truetype_raster.ludic +++ b/runtime/native/truetype_raster.ludic @@ -2,7 +2,7 @@ # and text drawing. Split out of truetype.ludic (the sfnt/cmap/outline half). # ---- rasterization -------------------------------------------------------- -fn ed_add(x0: fixed, y0: fixed, x1: fixed, y1: fixed) -> void { +function ed_add(x0: fixed, y0: fixed, x1: fixed, y1: fixed) -> void { if ed_n >= TT_EDGES { return } ed_x0[ed_n] = x0 ed_y0[ed_n] = y0 @@ -11,7 +11,7 @@ fn ed_add(x0: fixed, y0: fixed, x1: fixed, y1: fixed) -> void { ed_n = ed_n + 1 } -fn tt_isqrt(v: int) -> int { +function tt_isqrt(v: int) -> int { if v <= 0 { return 0 } var r = 0 var b = 32768 @@ -24,7 +24,7 @@ fn tt_isqrt(v: int) -> int { } # flatten one quadratic Bézier into line segments, subdivided by chord length -fn ed_quad(x0: fixed, y0: fixed, cx: fixed, cy: fixed, x1: fixed, y1: fixed) -> void { +function ed_quad(x0: fixed, y0: fixed, cx: fixed, cy: fixed, x1: fixed, y1: fixed) -> void { let dx = flr(x1) - flr(x0) let dy = flr(y1) - flr(y0) var n = tt_isqrt(dx * dx + dy * dy) / 3 @@ -51,7 +51,7 @@ var gr_ox: int = 0 var gr_oy: int = 0 var gr_adv: int = 0 -fn tt_raster(id: int, gid: int, px: int) -> ptr { +function tt_raster(id: int, gid: int, px: int) -> ptr { let upem = tt_upem[id] let scale = fx(px) / upem gr_adv = flr(fx(tt_advance(id, gid)) * scale + 0.5) @@ -231,11 +231,11 @@ fn tt_raster(id: int, gid: int, px: int) -> ptr { # ---- glyph cache ---------------------------------------------------------- # Rasterizing is far too slow to repeat per frame, so a coverage mask is kept # per (font, codepoint, size). Open addressing, 8 probes, evict on miss. -fn gc_hash(font: int, cp: int, px: int) -> int { +function gc_hash(font: int, cp: int, px: int) -> int { return ((((cp * 2654435761) ^ (font << 20)) ^ (px << 8)) & (TT_GC - 1)) } -fn tt_glyph_get(font: int, cp: int, px: int) -> int { +function tt_glyph_get(font: int, cp: int, px: int) -> int { let h = gc_hash(font, cp, px) for k in 0 .. 8 { let s = ((h + k) & (TT_GC - 1)) @@ -269,7 +269,7 @@ fn tt_glyph_get(font: int, cp: int, px: int) -> int { # ---- UTF-8 ---------------------------------------------------------------- var u8_next: int = 0 # byte index just past the codepoint last decoded -fn tt_utf8(s: str, at: int) -> int { +function tt_utf8(s: str, at: int) -> int { let c = s[at] var extra = 0 - 1 if c < 128 { extra = 0 } @@ -298,7 +298,7 @@ fn tt_utf8(s: str, at: int) -> int { } # ---- drawing -------------------------------------------------------------- -fn tt_blit(bmp: ptr, w: int, h: int, dx: int, dy: int, colour: int) -> void { +function tt_blit(bmp: ptr, w: int, h: int, dx: int, dy: int, colour: int) -> void { if (bmp == null) { return } let cr = ((colour >> 16) & 255) let cg = ((colour >> 8) & 255) @@ -327,7 +327,7 @@ fn tt_blit(bmp: ptr, w: int, h: int, dx: int, dy: int, colour: int) -> void { } } -fn rt_text_ttf(font: int, x: int, y: int, s: str, colour: int, px: int) -> void { +function rt_text_ttf(font: int, x: int, y: int, s: str, colour: int, px: int) -> void { if font < 0 { return } if font >= tt_n { return } if px <= 0 { return } @@ -351,7 +351,7 @@ fn rt_text_ttf(font: int, x: int, y: int, s: str, colour: int, px: int) -> void } } -fn rt_text_w(font: int, s: str, px: int) -> int { +function rt_text_w(font: int, s: str, px: int) -> int { if font < 0 { return 0 } if font >= tt_n { return 0 } if px <= 0 { return 0 } @@ -372,7 +372,7 @@ fn rt_text_w(font: int, s: str, px: int) -> int { return best } -fn rt_text_h(font: int, px: int) -> int { +function rt_text_h(font: int, px: int) -> int { if font < 0 { return 0 } if font >= tt_n { return 0 } let scale = fx(px) / tt_upem[font] diff --git a/runtime/native/ui.ludic b/runtime/native/ui.ludic index fa9f808f..7e26a09b 100644 --- a/runtime/native/ui.ludic +++ b/runtime/native/ui.ludic @@ -79,7 +79,7 @@ var ui_n: int = 0 var ui_active: int = -1 var ui_focus: int = -1 -fn rt_ui_init() -> void { +function rt_ui_init() -> void { ui_type = words(UI_MAX) ui_parent = words(UI_MAX) ui_w = words(UI_MAX) @@ -112,7 +112,7 @@ fn rt_ui_init() -> void { } # ---- build-time interface (called by compiler-emitted code) --------------- -fn rt_ui_reset(n: int) -> void { +function rt_ui_reset(n: int) -> void { ui_n = n for i in 0 .. n { ui_type[i] = 0 @@ -142,7 +142,7 @@ fn rt_ui_reset(n: int) -> void { } } -fn rt_ui_set(i: int, k: int, v: int) -> void { +function rt_ui_set(i: int, k: int, v: int) -> void { if i < 0 { return } if i >= UI_MAX { return } if k == K_TYPE { ui_type[i] = v } @@ -168,21 +168,21 @@ fn rt_ui_set(i: int, k: int, v: int) -> void { if k == K_SKIN { ui_skin[i] = v } } -fn rt_ui_static_text(i: int, s: str) -> void { +function rt_ui_static_text(i: int, s: str) -> void { if i < 0 { return } if i >= UI_MAX { return } ui_text[i] = s } # ---- text helpers: TrueType when a font is loaded, bitmap otherwise ------- -fn ui_str(i: int) -> ptr { +function ui_str(i: int) -> ptr { if ui_hasdyn[i] == 1 { return offset(ui_dyn, i * 96) } let t = ui_text[i] if (t == null) { return offset(ui_dyn, i * 96) } return t } -fn ui_draw_text(font: int, x: int, y: int, s: ptr, colour: int, size: int) -> void { +function ui_draw_text(font: int, x: int, y: int, s: ptr, colour: int, size: int) -> void { if font >= 0 { if font < tt_n { rt_text_ttf(font, x, y, s, colour, size) @@ -192,7 +192,7 @@ fn ui_draw_text(font: int, x: int, y: int, s: ptr, colour: int, size: int) -> vo rt_text(x, y, s, colour, max(size / 8, 1)) } -fn ui_tw(font: int, s: ptr, size: int) -> int { +function ui_tw(font: int, s: ptr, size: int) -> int { if font >= 0 { if font < tt_n { return rt_text_w(font, s, size) } } @@ -201,24 +201,24 @@ fn ui_tw(font: int, s: ptr, size: int) -> int { return n * 6 * max(size / 8, 1) } -fn ui_th(font: int, size: int) -> int { +function ui_th(font: int, size: int) -> int { if font >= 0 { if font < tt_n { return rt_text_h(font, size) } } return 7 * max(size / 8, 1) } -fn ui_is_container(t: int) -> bool { +function ui_is_container(t: int) -> bool { if t == WT_PANEL { return true } if t == WT_COL { return true } if t == WT_ROW { return true } return false } -fn ui_dir(t: int) -> int { +function ui_dir(t: int) -> int { if t == WT_ROW { return 0 } return 1 } -fn ui_under(i: int, root: int) -> bool { +function ui_under(i: int, root: int) -> bool { var k = i while k >= 0 { if k == root { return true } @@ -228,7 +228,7 @@ fn ui_under(i: int, root: int) -> bool { } # ---- measure: content sizes, children before parents ---------------------- -fn ui_measure() -> void { +function ui_measure() -> void { var i = ui_n - 1 while i >= 0 { if ui_visible[i] == 0 { @@ -303,7 +303,7 @@ fn ui_measure() -> void { } } -fn ui_eff_align(child: int, parent: int) -> int { +function ui_eff_align(child: int, parent: int) -> int { if ui_align[child] >= 0 { return ui_align[child] } if ui_align[parent] >= 0 { return ui_align[parent] } return 0 diff --git a/runtime/native/ui_draw.ludic b/runtime/native/ui_draw.ludic index 31ac0336..0ab6f837 100644 --- a/runtime/native/ui_draw.ludic +++ b/runtime/native/ui_draw.ludic @@ -2,7 +2,7 @@ # accessors. Split out of ui.ludic (the storage/build/measure half). # ---- arrange: rects, parents before children ------------------------------ -fn ui_arrange(i: int, x: int, y: int) -> void { +function ui_arrange(i: int, x: int, y: int) -> void { ui_rx[i] = x ui_ry[i] = y let t = ui_type[i] @@ -61,7 +61,7 @@ fn ui_arrange(i: int, x: int, y: int) -> void { } } -fn ui_layout() -> void { +function ui_layout() -> void { if ui_active < 0 { return } ui_measure() var px = (rt_fbw - ui_rw[ui_active]) / 2 @@ -74,7 +74,7 @@ fn ui_layout() -> void { } # ---- drawing -------------------------------------------------------------- -fn ui_lighten(c: int) -> int { +function ui_lighten(c: int) -> int { var r = ((c >> 16) & 255) var g = ((c >> 8) & 255) var b = (c & 255) @@ -84,7 +84,7 @@ fn ui_lighten(c: int) -> int { return (((r << 16) | (g << 8)) | b) } -fn ui_draw_node(i: int) -> void { +function ui_draw_node(i: int) -> void { if ui_visible[i] == 0 { return } let t = ui_type[i] let rx = ui_rx[i] @@ -148,7 +148,7 @@ fn ui_draw_node(i: int) -> void { # ---- focus and activation ------------------------------------------------- var ui_fl: words = null -fn ui_focusables() -> int { +function ui_focusables() -> int { if (ui_fl == null) { ui_fl = words(UI_MAX) } var n = 0 for i in 0 .. ui_n { @@ -164,14 +164,14 @@ fn ui_focusables() -> int { return n } -fn rt_ui_open(root: int) -> void { +function rt_ui_open(root: int) -> void { ui_active = root let n = ui_focusables() ui_focus = 0 - 1 if n > 0 { ui_focus = ui_fl[0] } } -fn rt_ui_tick(k: int) -> void { +function rt_ui_tick(k: int) -> void { if ui_active < 0 { return } for i in 0 .. ui_n { ui_fired[i] = 0 } ui_layout() @@ -201,20 +201,20 @@ fn rt_ui_tick(k: int) -> void { if k == 13 { ui_fired[ui_focus] = 1 } } -fn rt_ui_render() -> void { +function rt_ui_render() -> void { if ui_active < 0 { return } ui_layout() ui_draw_node(ui_active) } # ---- game-facing accessors ------------------------------------------------ -fn rt_ui_clicked(id: int) -> bool { +function rt_ui_clicked(id: int) -> bool { if id < 0 { return false } if id >= ui_n { return false } return ui_fired[id] == 1 } -fn rt_ui_set_text(id: int, s: str) -> void { +function rt_ui_set_text(id: int, s: str) -> void { if id < 0 { return } if id >= ui_n { return } let base = id * 96 @@ -232,7 +232,7 @@ fn rt_ui_set_text(id: int, s: str) -> void { ui_hasdyn[id] = 1 } -fn rt_ui_set_int(id: int, n: int) -> void { +function rt_ui_set_int(id: int, n: int) -> void { if id < 0 { return } if id >= ui_n { return } let base = id * 96 @@ -266,17 +266,17 @@ fn rt_ui_set_int(id: int, n: int) -> void { ui_hasdyn[id] = 1 } -fn rt_ui_focus(id: int) -> void { +function rt_ui_focus(id: int) -> void { if id < 0 { return } if id >= ui_n { return } ui_focus = id } -fn rt_ui_focused() -> int { +function rt_ui_focused() -> int { return ui_focus } -fn rt_ui_visible(id: int, v: int) -> void { +function rt_ui_visible(id: int, v: int) -> void { if id < 0 { return } if id >= ui_n { return } ui_visible[id] = v diff --git a/selfhost/ast.ludic b/selfhost/ast.ludic index 48bd6351..26a0b6c3 100644 --- a/selfhost/ast.ludic +++ b/selfhost/ast.ludic @@ -76,7 +76,7 @@ property Node { line: int = 0 } -fn node(kind: int) -> Node { +function node(kind: int) -> Node { let n = new Node n.kind = kind n.kids = new []Node diff --git a/selfhost/buf.ludic b/selfhost/buf.ludic index 54300be6..4ae05666 100644 --- a/selfhost/buf.ludic +++ b/selfhost/buf.ludic @@ -4,26 +4,26 @@ property Buf { data: ptr = null, len: int = 0, cap: int = 0 } -fn buf_new() -> Buf { +function buf_new() -> Buf { let b = new Buf b.cap = 256 b.data = bytes(b.cap) b.len = 0 return b } -fn buf_ensure(b: Buf, extra: int) -> void { +function buf_ensure(b: Buf, extra: int) -> void { if b.len + extra + 1 <= b.cap { return } while b.len + extra + 1 > b.cap { b.cap = b.cap * 2 } b.data = resize(b.data, b.cap) } -fn buf_putc(b: Buf, c: int) -> void { +function buf_putc(b: Buf, c: int) -> void { buf_ensure(b, 1) b.data[b.len] = c b.len = b.len + 1 } -fn buf_puts(b: Buf, s: ptr) -> void { +function buf_puts(b: Buf, s: ptr) -> void { var i = 0 while s[i] != 0 { buf_putc(b, s[i]); i = i + 1 } } -fn buf_puti(b: Buf, n: int) -> void { buf_puts(b, itoa(n)) } -fn buf_str(b: Buf) -> ptr { b.data[b.len] = 0; return b.data } +function buf_puti(b: Buf, n: int) -> void { buf_puts(b, itoa(n)) } +function buf_str(b: Buf) -> ptr { b.data[b.len] = 0; return b.data } diff --git a/selfhost/emit_addr.ludic b/selfhost/emit_addr.ludic index e1f8ebb1..a8c38543 100644 --- a/selfhost/emit_addr.ludic +++ b/selfhost/emit_addr.ludic @@ -5,7 +5,7 @@ var g_addr_ty: ptr # out-param: the type at the computed address # address of `base.field` -fn emit_member_addr(e: Node) -> ptr { +function emit_member_addr(e: Node) -> ptr { let base = emit_expr(e.a) let s = layout_node(base.ty) if (s == null) { perr(`member access on non-aggregate {base.ty}`) } @@ -19,7 +19,7 @@ fn emit_member_addr(e: Node) -> ptr { } # address of `base[index]` (slices only in this subset) -fn emit_index_addr(e: Node) -> ptr { +function emit_index_addr(e: Node) -> ptr { let base = emit_expr(e.a) if not is_slice_ty(base.ty) { # a raw pointer: address of element i let bi = emit_expr(e.b) # (evaluate index first — it may set g_addr_ty) diff --git a/selfhost/emit_collide.ludic b/selfhost/emit_collide.ludic index 4432f1ed..04651a2f 100644 --- a/selfhost/emit_collide.ludic +++ b/selfhost/emit_collide.ludic @@ -3,14 +3,14 @@ # origin at the top-left; circles are (x, y, r). Squared distances use i64 so a # large coordinate can't overflow. Each returns a bool. -fn is_collide_ns(meth: ptr) -> bool { +function is_collide_ns(meth: ptr) -> bool { if (meth == "rects") or (meth == "point_rect") { return true } if (meth == "circles") or (meth == "rect_circle") { return true } return false } # dx*dx + dy*dy widened to i64 (no overflow for 32-bit deltas) -fn coll_sq_sum(dx: ptr, dy: ptr) -> ptr { +function coll_sq_sum(dx: ptr, dy: ptr) -> ptr { let dx64 = emit_bind(`sext i32 {dx} to i64`) let dy64 = emit_bind(`sext i32 {dy} to i64`) let xx = emit_bind(`mul i64 {dx64}, {dx64}`) @@ -19,14 +19,14 @@ fn coll_sq_sum(dx: ptr, dy: ptr) -> ptr { } # max(lo, min(v, hi)) — clamp v into [lo, hi] -fn coll_clamp(v: ptr, lo: ptr, hi: ptr) -> ptr { +function coll_clamp(v: ptr, lo: ptr, hi: ptr) -> ptr { let c1 = emit_bind(`icmp slt i32 {v}, {hi}`) let t = emit_bind(`select i1 {c1}, i32 {v}, i32 {hi}`) let c2 = emit_bind(`icmp sgt i32 {lo}, {t}`) return emit_bind(`select i1 {c2}, i32 {lo}, i32 {t}`) } -fn emit_collide_ns(meth: ptr, e: Node) -> Val { +function emit_collide_ns(meth: ptr, e: Node) -> Val { if (meth == "rects") { # AABB overlap of two rects let ax = emit_expr(e.kids[0]); let ay = emit_expr(e.kids[1]); let aw = emit_expr(e.kids[2]); let ah = emit_expr(e.kids[3]) let bx = emit_expr(e.kids[4]); let by = emit_expr(e.kids[5]); let bw = emit_expr(e.kids[6]); let bh = emit_expr(e.kids[7]) diff --git a/selfhost/emit_color.ludic b/selfhost/emit_color.ludic index a5b24471..cb1f8c74 100644 --- a/selfhost/emit_color.ludic +++ b/selfhost/emit_color.ludic @@ -9,7 +9,7 @@ # Edit the palette there and regenerate; do not hand-edit this file. # ============================================================================ -fn color_lookup(name: ptr) -> int { +function color_lookup(name: ptr) -> int { if (name == "White") { return 0xFFFFFF } if (name == "Snow") { return 0xFFFAFA } if (name == "Ivory") { return 0xFFFFF0 } diff --git a/selfhost/emit_colorfn.ludic b/selfhost/emit_colorfn.ludic index e9bfae74..e76f1b8c 100644 --- a/selfhost/emit_colorfn.ludic +++ b/selfhost/emit_colorfn.ludic @@ -3,20 +3,20 @@ # and blend them with plain integer/fixed math. rgb/rgba pack channels; lerp/ # darken/lighten/with_alpha transform an existing color. -fn is_colorfn_ns(meth: ptr) -> bool { +function is_colorfn_ns(meth: ptr) -> bool { if (meth == "rgb") or (meth == "rgba") or (meth == "lerp") { return true } if (meth == "darken") or (meth == "lighten") or (meth == "with_alpha") { return true } return false } # (c >> shift) & 255 -> code of a channel value -fn color_ch(c: ptr, shift: ptr) -> ptr { +function color_ch(c: ptr, shift: ptr) -> ptr { let sh = emit_bind(`lshr i32 {c}, {shift}`) return emit_bind(`and i32 {sh}, 255`) } # (r << 16) | (g << 8) | b -> code of a packed color -fn color_pack(r: ptr, g: ptr, b: ptr) -> ptr { +function color_pack(r: ptr, g: ptr, b: ptr) -> ptr { let r16 = emit_bind(`shl i32 {r}, 16`) let g8 = emit_bind(`shl i32 {g}, 8`) let rg = emit_bind(`or i32 {r16}, {g8}`) @@ -24,14 +24,14 @@ fn color_pack(r: ptr, g: ptr, b: ptr) -> ptr { } # ch0 + ((ch1 - ch0) * t >> 16), t a fixed 0..1 -> code of a blended channel -fn color_lerp_ch(ch0: ptr, ch1: ptr, t: ptr) -> ptr { +function color_lerp_ch(ch0: ptr, ch1: ptr, t: ptr) -> ptr { let d = emit_bind(`sub i32 {ch1}, {ch0}`) let dt = emit_bind(`mul i32 {d}, {t}`) let dsh = emit_bind(`ashr i32 {dt}, 16`) return emit_bind(`add i32 {ch0}, {dsh}`) } -fn emit_colorfn_ns(meth: ptr, e: Node) -> Val { +function emit_colorfn_ns(meth: ptr, e: Node) -> Val { if (meth == "rgb") { # rgb(r, g, b) -> 0xRRGGBB let r = emit_expr(e.kids[0]); let g = emit_expr(e.kids[1]); let b = emit_expr(e.kids[2]) return val(color_pack(r.code, g.code, b.code), "int") diff --git a/selfhost/emit_core.ludic b/selfhost/emit_core.ludic index a6133ac3..6972db43 100644 --- a/selfhost/emit_core.ludic +++ b/selfhost/emit_core.ludic @@ -3,7 +3,7 @@ # structs and slices are references, so every non-scalar type lowers to `ptr`. property Val { code: ptr = null, ty: ptr = null } -fn val(code: ptr, ty: ptr) -> Val { let v = new Val; v.code = code; v.ty = ty; return v } +function 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 @@ -47,30 +47,30 @@ var g_scene_count: int = 0 # parse-time id counter var g_start_scene: int = 0 # id of the scene marked `start` (else the first) var g_cur_scene: Node = null # scene owning the handler being emitted, for `become` -fn find_scene(name: ptr) -> Node { +function find_scene(name: ptr) -> Node { var i = 0 while i < len(g_scenes) { if (g_scenes[i].s == name) { return g_scenes[i] }; i = i + 1 } return null } -fn emit(s: ptr) -> void { buf_puts(code, s) } -fn emith(s: ptr) -> void { buf_puts(head, s) } +function emit(s: ptr) -> void { buf_puts(code, s) } +function 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 { +function emit_alloca(llt: ptr) -> ptr { let r = `%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" fresh register -fn nreg() -> ptr { let r = `%t{itoa(ll_t)}`; ll_t = ll_t + 1; return r } -fn lbl(pfx: ptr) -> ptr { let r = (pfx + itoa(ll_lbl)); ll_lbl = ll_lbl + 1; return r } +function nreg() -> ptr { let r = `%t{itoa(ll_t)}`; ll_t = ll_t + 1; return r } +function lbl(pfx: ptr) -> ptr { let r = (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. -fn llty(t: ptr) -> ptr { +function llty(t: ptr) -> ptr { if (t == "int") or (t == "bool") or (t == "fixed") or (t == "entity") { return "i32" } # entity = an i32 handle (self()) if (t == "long") { return "i64" } # a 64-bit signed integer if (t == "byte") { return "i8" } # a single byte (p[i] on a raw ptr) @@ -79,37 +79,37 @@ fn llty(t: ptr) -> ptr { return "ptr" } -fn is_slice_ty(t: ptr) -> bool { return t[0] == 91 and t[1] == 93 } # "[]" -fn slice_elem(t: ptr) -> ptr { return t[2..len(t)] } +function is_slice_ty(t: ptr) -> bool { return t[0] == 91 and t[1] == 93 } # "[]" +function slice_elem(t: ptr) -> ptr { return t[2..len(t)] } -fn find_arch(name: ptr) -> Node { +function find_arch(name: ptr) -> Node { var i = 0 while i < len(prog) { let d = prog[i]; if d.kind == N_ARCH and (d.s == name) { return d }; i = i + 1 } return null } -fn find_comp(name: ptr) -> Node { +function find_comp(name: ptr) -> Node { var i = 0 while i < len(prog) { let d = prog[i]; if d.kind == N_COMP and (d.s == name) { return d }; i = i + 1 } return null } # every record is a `property` with a %Cmp_ layout of named fields — whether it # is stored per-entity by the ECS or heap-allocated by `new` is a matter of use. -fn layout_node(name: ptr) -> Node { return find_comp(name) } -fn layout_ty(name: ptr) -> ptr { return (("%Cmp_") + name) } +function layout_node(name: ptr) -> Node { return find_comp(name) } +function layout_ty(name: ptr) -> ptr { return (("%Cmp_") + name) } -fn field_index(s: Node, fname: ptr) -> int { +function field_index(s: Node, fname: ptr) -> int { var i = 0 while i < len(s.kids) { if (s.kids[i].s == fname) { return i }; i = i + 1 } return 0 - 1 } -fn field_type(s: Node, fname: ptr) -> ptr { +function field_type(s: Node, fname: ptr) -> ptr { var i = 0 while i < len(s.kids) { if (s.kids[i].s == fname) { return s.kids[i].ty }; i = i + 1 } return "int" } # find a global var/const by name -fn find_global(name: ptr) -> Node { +function find_global(name: ptr) -> Node { var i = 0 while i < len(prog) { let d = prog[i] @@ -122,7 +122,7 @@ fn find_global(name: ptr) -> Node { # `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 { +function enum_ordinal(ename: ptr, vname: ptr) -> int { var i = 0 while i < len(prog) { let d = prog[i] @@ -141,18 +141,18 @@ fn enum_ordinal(ename: ptr, vname: ptr) -> int { } return 0 - 1 } -fn find_fn(name: ptr) -> Node { +function find_fn(name: ptr) -> Node { var i = 0 while i < len(prog) { let d = prog[i]; if d.kind == N_FN and (d.s == name) { return d }; i = i + 1 } return null } -# `extern fn name(params) -> T = "sym"` binds a Ludic name to a link symbol. A +# `extern function name(params) -> T = "sym"` binds a Ludic name to a link symbol. A # call to `name` lowers to a direct `@` call (no @fn_ prefix — the string is # the exact linked symbol), and emit_extern_decls emits a matching `declare`. This # is the transport seam (net_send/net_poll), the windowing/socket FFI, and any # C/Rust/Zig library binding — the same seam NETWORKING-DESIGN §5 names. -fn find_extern(name: ptr) -> Node { +function find_extern(name: ptr) -> Node { var i = 0 while i < len(prog) { let d = prog[i]; if d.kind == N_EXTERN and (d.s == name) { return d }; i = i + 1 } return null @@ -163,11 +163,11 @@ fn find_extern(name: ptr) -> Node { # expression with its bare names read as fields of `x`. Populated at parse time. var g_computed: []Node # each: s = "Prop.field", ty = result type, a = expr -fn register_computed(prop: ptr, field: ptr, ty: ptr, e: Node) -> void { +function register_computed(prop: ptr, field: ptr, ty: ptr, e: Node) -> void { let cf = node(N_FIELD); cf.s = `{prop}.{field}`; cf.ty = ty; cf.a = e push(g_computed, cf) } -fn computed_expr(prop: ptr, field: ptr) -> Node { +function computed_expr(prop: ptr, field: ptr) -> Node { if (prop == null) { return null } let key = `{prop}.{field}` var i = 0 @@ -175,7 +175,7 @@ fn computed_expr(prop: ptr, field: ptr) -> Node { return null } # best-effort static type of an expression (for computed-field lookup; emits nothing) -fn static_type(e: Node) -> ptr { +function static_type(e: Node) -> ptr { if e.kind == E_ID { let li = loc_find(e.s); if li >= 0 { return loc_ty[li] } } return null } @@ -185,10 +185,10 @@ fn static_type(e: Node) -> ptr { # constructor). Spawn statically knows the model, so no runtime dispatch is needed. var g_onspawn: []Node # each: s = Model name, a = hook body block -fn register_onspawn(model: ptr, body: Node) -> void { +function register_onspawn(model: ptr, body: Node) -> void { let n = node(N_BLOCK); n.s = model; n.a = body; push(g_onspawn, n) } -fn onspawn_body(model: ptr) -> Node { +function onspawn_body(model: ptr) -> Node { var i = 0 while i < len(g_onspawn) { if (g_onspawn[i].s == model) { return g_onspawn[i].a }; i = i + 1 } return null @@ -203,18 +203,18 @@ var g_onattach: []Node # each: s = Property name, a = hook body block # LC1: `.ty` carries the optional `reason:` binding name (null if the hook took # no reason). The despawn hook function gains an `i32 %reason` parameter and each # teardown site passes a constant EndReason (see emit_despawn_hooks / emit_despawn). -fn register_ondespawn(model: ptr, body: Node, reason: ptr) -> void { +function register_ondespawn(model: ptr, body: Node, reason: ptr) -> void { let n = node(N_BLOCK); n.s = model; n.a = body; n.ty = reason; push(g_ondespawn, n) } -fn ondespawn_body(model: ptr) -> Node { +function ondespawn_body(model: ptr) -> Node { var i = 0 while i < len(g_ondespawn) { if (g_ondespawn[i].s == model) { return g_ondespawn[i].a }; i = i + 1 } return null } -fn register_onattach(prop: ptr, body: Node) -> void { +function register_onattach(prop: ptr, body: Node) -> void { let n = node(N_BLOCK); n.s = prop; n.a = body; push(g_onattach, n) } -fn onattach_body(prop: ptr) -> Node { +function onattach_body(prop: ptr) -> Node { var i = 0 while i < len(g_onattach) { if (g_onattach[i].s == prop) { return g_onattach[i].a }; i = i + 1 } return null @@ -224,10 +224,10 @@ fn onattach_body(prop: ptr) -> Node { # is removed from a live entity (`detach P on e`), with the property bound by name # so the body can read its outgoing value before it is cleared. var g_ondetach: []Node # each: s = Property name, a = hook body block -fn register_ondetach(prop: ptr, body: Node) -> void { +function register_ondetach(prop: ptr, body: Node) -> void { let n = node(N_BLOCK); n.s = prop; n.a = body; push(g_ondetach, n) } -fn ondetach_body(prop: ptr) -> Node { +function ondetach_body(prop: ptr) -> Node { var i = 0 while i < len(g_ondetach) { if (g_ondetach[i].s == prop) { return g_ondetach[i].a }; i = i + 1 } return null @@ -237,14 +237,14 @@ fn ondetach_body(prop: ptr) -> Node { # entity, with the property bound by name. var g_onenable: []Node var g_ondisable: []Node -fn register_onenable(prop: ptr, body: Node) -> void { let n = node(N_BLOCK); n.s = prop; n.a = body; push(g_onenable, n) } -fn register_ondisable(prop: ptr, body: Node) -> void { let n = node(N_BLOCK); n.s = prop; n.a = body; push(g_ondisable, n) } -fn onenable_body(prop: ptr) -> Node { +function register_onenable(prop: ptr, body: Node) -> void { let n = node(N_BLOCK); n.s = prop; n.a = body; push(g_onenable, n) } +function register_ondisable(prop: ptr, body: Node) -> void { let n = node(N_BLOCK); n.s = prop; n.a = body; push(g_ondisable, n) } +function onenable_body(prop: ptr) -> Node { var i = 0 while i < len(g_onenable) { if (g_onenable[i].s == prop) { return g_onenable[i].a }; i = i + 1 } return null } -fn ondisable_body(prop: ptr) -> Node { +function ondisable_body(prop: ptr) -> Node { var i = 0 while i < len(g_ondisable) { if (g_ondisable[i].s == prop) { return g_ondisable[i].a }; i = i + 1 } return null @@ -259,13 +259,13 @@ var g_events: []Node # each: an N_EVENT node (s = name, kids = payloa var g_onlisten: []Node # each: N_BLOCK, s = event name, a = listener body block var g_cancel_addr: ptr = null # EV3: address of the current cancellable dispatch's flag (null outside one) -fn register_event(n: Node) -> void { push(g_events, n) } -fn find_event(name: ptr) -> Node { +function register_event(n: Node) -> void { push(g_events, n) } +function find_event(name: ptr) -> Node { var i = 0 while i < len(g_events) { if (g_events[i].s == name) { return g_events[i] }; i = i + 1 } return null } -fn register_onlisten(evt: ptr, body: Node) -> void { +function register_onlisten(evt: ptr, body: Node) -> void { let n = node(N_BLOCK); n.s = evt; n.a = body; push(g_onlisten, n) } @@ -274,7 +274,7 @@ fn register_onlisten(evt: ptr, body: Node) -> void { # presence in g_events is what makes each lifecycle fire site also `emit` it, so # `find_event(name) != null` doubles as the "is this hook public?" test. Names are # the stable ABI contract: `model__spawn`, `model__despawn`, etc. -fn ensure_event(name: ptr, with_reason: bool) -> void { +function ensure_event(name: ptr, with_reason: bool) -> void { if (find_event(name) != null) { return } let n = node(N_EVENT); n.s = name let ent = node(N_FIELD); ent.s = "entity"; ent.ty = "int"; push(n.kids, ent) @@ -282,7 +282,7 @@ fn ensure_event(name: ptr, with_reason: bool) -> void { register_event(n) } # a promoted scene/program event has no per-entity payload -fn ensure_event_empty(name: ptr) -> void { +function ensure_event_empty(name: ptr) -> void { if (find_event(name) != null) { return } let n = node(N_EVENT); n.s = name; register_event(n) } @@ -292,32 +292,32 @@ fn ensure_event_empty(name: ptr) -> void { # handlers gate on it. Only managed layers pay for this, so a scene program that # never toggles a layer is byte-identical. var g_toggled_layers: []ptr -fn note_toggled_layer(name: ptr) -> void { +function note_toggled_layer(name: ptr) -> void { var i = 0 while i < len(g_toggled_layers) { if (g_toggled_layers[i] == name) { return }; i = i + 1 } push(g_toggled_layers, name) } -fn is_toggled_layer(name: ptr) -> bool { +function is_toggled_layer(name: ptr) -> bool { var i = 0 while i < len(g_toggled_layers) { if (g_toggled_layers[i] == name) { return true }; i = i + 1 } return false } # is `name` a model (archetype)? — chooses model-vs-handler for a bare enable/disable -fn is_model(name: ptr) -> bool { return find_arch_id(name) > 0 } +function is_model(name: ptr) -> bool { return find_arch_id(name) > 0 } # local variable environment -fn loc_reset() -> void { nloc = 0 } +function loc_reset() -> void { nloc = 0 } # push a local. Defaults to mutable (params, loop and query bindings are all # reassignable/rebindable); a `let` binding marks its slot immutable afterward # via loc_set_mut, so a later `name = …` can be rejected. -fn loc_push(name: ptr, r: ptr, ty: ptr) -> void { +function 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; loc_mut[nloc] = 1 } else { push(loc_name, name); push(loc_reg, r); push(loc_ty, ty); push(loc_mut, 1) } nloc = nloc + 1 } -fn loc_set_mut(m: int) -> void { if nloc > 0 { loc_mut[nloc - 1] = m } } -fn loc_find(name: ptr) -> int { +function loc_set_mut(m: int) -> void { if nloc > 0 { loc_mut[nloc - 1] = m } } +function loc_find(name: ptr) -> int { var i = nloc - 1 while i >= 0 { if (loc_name[i] == name) { return i }; i = i - 1 } return 0 - 1 diff --git a/selfhost/emit_decl.ludic b/selfhost/emit_decl.ludic index ee08b894..f07d9be6 100644 --- a/selfhost/emit_decl.ludic +++ b/selfhost/emit_decl.ludic @@ -2,7 +2,7 @@ # body is built into a scratch buffer so entry-block allocas can be spliced in # ahead of it (see emit_alloca). Returns go through a %retval slot + `ret` label. -fn emit_params_sig(d: Node) -> void { +function emit_params_sig(d: Node) -> void { var i = 0 while i < len(d.kids) { if i > 0 { emit(", ") } @@ -11,7 +11,7 @@ fn emit_params_sig(d: Node) -> void { } } -fn emit_fn(d: Node) -> void { +function emit_fn(d: Node) -> void { ll_t = 0; ll_lbl = 0; g_term = false; loc_reset(); nloop = 0 ret_ty = d.ty let fbody = buf_new() @@ -41,7 +41,7 @@ fn emit_fn(d: Node) -> void { emit("}\n\n") } -fn emit_main(d: Node) -> void { +function emit_main(d: Node) -> void { ll_t = 0; ll_lbl = 0; g_term = false; loc_reset(); nloop = 0 ret_ty = "int" let fbody = buf_new() @@ -64,7 +64,7 @@ fn emit_main(d: Node) -> void { emit("}\n") } -fn emit_program() -> void { +function emit_program() -> void { head = buf_new() code = buf_new() g_uses_str = false @@ -109,7 +109,7 @@ fn emit_program() -> void { # Flush the emitted IR. With a null path it goes to stdout (the pipe the shell # drivers read); with a path it is written to that file so ludicc can hand it to # clang itself. -fn ir_flush(path: ptr) -> bool { +function ir_flush(path: ptr) -> bool { let h = buf_str(head) let c = buf_str(code) if (path == null) { # raw IR to stdout (no trailing newline) diff --git a/selfhost/emit_ease.ludic b/selfhost/emit_ease.ludic index 24bb45c5..93a7528a 100644 --- a/selfhost/emit_ease.ludic +++ b/selfhost/emit_ease.ludic @@ -2,19 +2,19 @@ # t in 0.0..1.0, returning an eased fixed. All pure Q16.16, deterministic. The # "juice" layer that makes motion feel good (Robert Penner's easings). -fn is_ease_ns(meth: ptr) -> bool { +function is_ease_ns(meth: ptr) -> bool { if (meth == "in") or (meth == "out") or (meth == "in_out") { return true } if (meth == "back") or (meth == "bounce") or (meth == "elastic") { return true } return false } # n1 * u * u (u a fixed code) -> code of a fixed i32 -fn ease_bounce_seg(u: ptr) -> ptr { +function ease_bounce_seg(u: ptr) -> ptr { let uu = fx_mul_code(u, u) return fx_mul_code(uu, "495616") # 7.5625 * u*u } -fn emit_ease_ns(meth: ptr, e: Node) -> Val { +function emit_ease_ns(meth: ptr, e: Node) -> Val { let t = emit_expr(e.kids[0]) if (meth == "in") { # ease-in quad: t*t return val(fx_mul_code(t.code, t.code), "fixed") diff --git a/selfhost/emit_ecs.ludic b/selfhost/emit_ecs.ludic index 58bb742f..a1f8caac 100644 --- a/selfhost/emit_ecs.ludic +++ b/selfhost/emit_ecs.ludic @@ -5,12 +5,12 @@ const MAX_ENT: int = 1024 -fn has_systems() -> bool { +function has_systems() -> bool { var i = 0 while i < len(prog) { if prog[i].kind == N_SYS { return true }; i = i + 1 } return false } -fn has_models() -> bool { +function has_models() -> bool { var i = 0 while i < len(prog) { if prog[i].kind == N_ARCH { return true }; i = i + 1 } return false @@ -18,7 +18,7 @@ fn has_models() -> bool { # N5: does the program have an `entry` block? A game with both handlers and an # `entry` drives its own loop (calling tick_fixed/tick_render), instead of the # compiler's auto frame loop. A game with handlers and no entry uses the auto-loop. -fn has_entry() -> bool { +function has_entry() -> bool { var i = 0 while i < len(prog) { if prog[i].kind == N_MAIN { return true }; i = i + 1 } return false @@ -28,9 +28,9 @@ fn has_entry() -> bool { # `struct`). A program uses the ECS when it has a handler or a model; a tool that # only declares record types and functions does not, and gets no entity storage, # allocator, snapshot or runtime splice. -fn has_ecs() -> bool { return has_systems() or has_models() } +function has_ecs() -> bool { return has_systems() or has_models() } -fn emit_ecs_storage() -> void { +function emit_ecs_storage() -> void { emith("@L_running = internal global i32 1\n") emith("@L_key = internal global i32 0\n") emith("@L_frame = internal global i32 0\n") @@ -71,7 +71,7 @@ fn emit_ecs_storage() -> void { } # L_reset(e): clear every has-flag and the archetype kind for entity e -fn emit_ecs_allocator() -> void { +function emit_ecs_allocator() -> void { let me = itoa(MAX_ENT) emit("define void @L_reset(i32 %e) {\nentry:\n") var i = 0 diff --git a/selfhost/emit_expr.ludic b/selfhost/emit_expr.ludic index d37a8b64..b3a29156 100644 --- a/selfhost/emit_expr.ludic +++ b/selfhost/emit_expr.ludic @@ -1,13 +1,13 @@ # emit_expr.ludic — lower an expression to IR, returning its register and type. -fn emit_load_at(addr: ptr, ty: ptr) -> Val { +function emit_load_at(addr: ptr, ty: ptr) -> Val { let r = emit_bind(`load {llty(ty)}, ptr {addr}`) return val(r, ty) } # short-circuit `and`/`or`: seed a slot with (left!=0), branch to decide whether # to overwrite with (right!=0). -fn emit_logic(e: Node) -> Val { +function emit_logic(e: Node) -> Val { let slot = emit_alloca("i32") let la = emit_expr(e.a) let lc = emit_bind(`icmp ne i32 {la.code}, 0`) @@ -26,7 +26,7 @@ fn emit_logic(e: Node) -> Val { return val(emit_bind(`load i32, ptr {slot}`), "bool") } -fn cmp_code(op: ptr) -> ptr { +function cmp_code(op: ptr) -> ptr { if (op == ("<")) { return "slt" } if (op == ("<=")) { return "sle" } if (op == (">")) { return "sgt" } @@ -34,10 +34,10 @@ fn cmp_code(op: ptr) -> ptr { if (op == ("==")) { return "eq" } return "ne" } -fn is_cmp(op: ptr) -> bool { +function is_cmp(op: ptr) -> bool { return (op == ("<")) or (op == ("<=")) or (op == (">")) or (op == (">=")) or (op == ("==")) or (op == ("!=")) } -fn arith_code(op: ptr) -> ptr { +function arith_code(op: ptr) -> ptr { if (op == ("+")) { return "add" } if (op == ("-")) { return "sub" } if (op == ("*")) { return "mul" } @@ -51,7 +51,7 @@ fn arith_code(op: ptr) -> ptr { } # widen an int value to Q16.16 by shifting left 16 (a fixed value passes through) -fn to_fixed(v: Val) -> ptr { +function to_fixed(v: Val) -> ptr { if (v.ty == "fixed") { return v.code } return emit_bind(`shl i32 {v.code}, 16`) } @@ -59,7 +59,7 @@ fn to_fixed(v: Val) -> ptr { # coerce a value's code to the LLVM type of `target`, for the only cross-width # pair the language has: int (i32) <-> long (i64). int widens with sext, long # narrows with trunc; everything else (same width, or ptr) passes through. -fn coerce_code(v: Val, target: ptr) -> ptr { +function coerce_code(v: Val, target: ptr) -> ptr { let lt = llty(target) let vt = llty(v.ty) if (lt == vt) { return v.code } @@ -69,14 +69,14 @@ fn coerce_code(v: Val, target: ptr) -> ptr { } # widen an int value to i64 (a long passes through) — the long analogue of to_fixed -fn to_long(v: Val) -> ptr { +function to_long(v: Val) -> ptr { if (llty(v.ty) == "i64") { return v.code } return emit_bind(`sext i32 {v.code} to i64`) } # string operators: `a + b` concatenates, `a == b` / `a != b` compare by content. # Both call the @fn_str_* prelude (emitted once per program that uses them). -fn emit_str_op(op: ptr, a: Val, b: Val) -> Val { +function emit_str_op(op: ptr, a: Val, b: Val) -> Val { g_uses_str = true if (op == ("+")) { return val(emit_bind(`call ptr @fn_str_concat(ptr {a.code}, ptr {b.code})`), "str") @@ -89,7 +89,7 @@ fn emit_str_op(op: ptr, a: Val, b: Val) -> Val { return val(r, "bool") } -fn emit_bin(e: Node) -> Val { +function emit_bin(e: Node) -> Val { if (e.s == "and") or (e.s == "or") { return emit_logic(e) } let a = emit_expr(e.a) let b = emit_expr(e.b) @@ -147,12 +147,12 @@ fn emit_bin(e: Node) -> Val { # is an E_FINIT (label -> value); this rewrites e.kids into plain value exprs in # the order the callee declares its parameters, so the rest of emit_call is # oblivious to whether the caller used names. -fn args_are_named(e: Node) -> bool { +function args_are_named(e: Node) -> bool { var i = 0 while i < len(e.kids) { if e.kids[i].kind == E_FINIT { return true }; i = i + 1 } return false } -fn reorder_named(e: Node, labels: []ptr) -> void { +function reorder_named(e: Node, labels: []ptr) -> void { if not args_are_named(e) { return } var i = 0 while i < len(e.kids) { @@ -173,14 +173,14 @@ fn reorder_named(e: Node, labels: []ptr) -> void { e.kids = out } # The parameter labels of a resolved fn/extern, in declaration order. -fn param_labels(fn: Node) -> []ptr { +function param_labels(fn: Node) -> []ptr { let out = new []ptr var i = 0 while i < len(fn.kids) { if fn.kids[i].kind == N_PARAM { push(out, fn.kids[i].s) }; i = i + 1 } return out } -fn param_types(fn: Node) -> []ptr { +function param_types(fn: Node) -> []ptr { let out = new []ptr var i = 0 while i < len(fn.kids) { if fn.kids[i].kind == N_PARAM { push(out, fn.kids[i].ty) }; i = i + 1 } @@ -192,7 +192,7 @@ fn param_types(fn: Node) -> []ptr { # runtime builtin plus the parameter labels callers may use as named arguments; # after reordering we rewrite the callee to that bare name and fall back into the # ordinary builtin path (which resolves it to its rt_ function). -fn emit_ns_call(ns: ptr, meth: ptr, e: Node) -> Val { +function emit_ns_call(ns: ptr, meth: ptr, e: Node) -> Val { # Math.* is computed inline (deterministic fixed-point), not routed through a # bare rt_ name — so `floor`/`round`/`lerp` never leak into the bare namespace. if (ns == "Math") { @@ -325,7 +325,7 @@ fn emit_ns_call(ns: ptr, meth: ptr, e: Node) -> Val { return emit_call(e) } -fn emit_call(e: Node) -> Val { +function emit_call(e: Node) -> Val { # `Subject.action(...)` — a namespaced builtin (Screen/Random/Input). if e.a.kind == E_MEMBER { if e.a.a.kind == E_ID { return emit_ns_call(e.a.a.s, e.a.s, e) } @@ -525,7 +525,7 @@ fn emit_call(e: Node) -> Val { return val(rreg, fn2.ty) } -fn emit_expr(e: Node) -> Val { +function emit_expr(e: Node) -> Val { if (e == null) { return val("0", "int") } if e.kind == E_INT { return val(itoa(e.ival), "int") } if e.kind == E_FLOAT { return val(itoa(e.ival), "fixed") } diff --git a/selfhost/emit_game.ludic b/selfhost/emit_game.ludic index 949bcef7..b9255fbd 100644 --- a/selfhost/emit_game.ludic +++ b/selfhost/emit_game.ludic @@ -3,7 +3,7 @@ # order until the game stops. Runtime hooks (rt_init/poll/running/shutdown) are # called only when the runtime defines them. -fn emit_system_fn(sys: Node) -> void { +function emit_system_fn(sys: Node) -> void { g_cur_scene = sys.c # scene owning this handler (null if global) — for `become` ll_t = 0; ll_lbl = 0; g_term = false; loc_reset(); nloop = 0; nself = 0 ret_ty = "void" @@ -22,7 +22,7 @@ fn emit_system_fn(sys: Node) -> void { } # one enable-gated call to @sys_ (skipped while the handler is disabled). -fn emit_call_one(d: Node) -> void { +function emit_call_one(d: Node) -> void { let he = emit_bind(`load i32, ptr @HE_{d.s}`) var hc = emit_bind(`icmp ne i32 {he}, 0`) # a handler in a toggled layer also gates on its layer's @LE_ flag (byte-identical @@ -50,7 +50,7 @@ fn emit_call_one(d: Node) -> void { # declaration (draw) order. The active scene is snapshotted once per phase, so a # `become` mid-phase takes effect at the next phase boundary — exactly one scene # is active within any single phase. -fn emit_calls_for_phase(phase: ptr) -> void { +function emit_calls_for_phase(phase: ptr) -> void { var i = 0 while i < len(prog) { let d = prog[i] @@ -81,7 +81,7 @@ fn emit_calls_for_phase(phase: ptr) -> void { # on enter / on exit compile to void functions @scene_enter_ / # @scene_exit_, called at the transition point (and enter at boot for the # start scene). Emitted for every scene, empty body when the hook is absent. -fn emit_scene_fn(name: ptr, kind: ptr, body: Node) -> void { +function emit_scene_fn(name: ptr, kind: ptr, body: Node) -> void { ll_t = 0; ll_lbl = 0; g_term = false; loc_reset(); nloop = 0; nself = 0 ret_ty = "void" let fbody = buf_new() @@ -101,7 +101,7 @@ fn emit_scene_fn(name: ptr, kind: ptr, body: Node) -> void { emit("}\n\n") } -fn emit_scene_hooks() -> void { +function emit_scene_hooks() -> void { var i = 0 while i < len(g_scenes) { let sc = g_scenes[i] @@ -116,7 +116,7 @@ fn emit_scene_hooks() -> void { # functions that bind the model's properties and run the body — dispatched by # kind at `despawn`. LC1: the `i32 %reason` parameter carries an EndReason; when # the hook declared `reason: r`, `r` is bound as an int local reading it. -fn emit_despawn_hooks() -> void { +function emit_despawn_hooks() -> void { var i = 0 while i < len(g_ondespawn) { let hk = g_ondespawn[i] @@ -152,7 +152,7 @@ fn emit_despawn_hooks() -> void { # the same per-model dispatch as `despawn`, but without freeing (the process is # ending). Emitted only when the program has @OnDespawn hooks, so despawn-free # programs are byte-for-byte unchanged. -fn emit_despawn_all_fn() -> void { +function emit_despawn_all_fn() -> void { if len(g_ondespawn) == 0 { return } let me = itoa(MAX_ENT) emit("define void @L_despawn_all(i32 %reason) {\nentry:\n br label %loop\n") @@ -204,7 +204,7 @@ const EV_CAP: int = 16 # returns immediately (a cancellable event returns "not cancelled"). const EV_DEPTH_CAP: int = 32 -fn emit_event_fns() -> void { +function emit_event_fns() -> void { emith("@ev_depth = global i32 0\n") # EV6 re-entrancy counter var e = 0 while e < len(g_events) { @@ -400,7 +400,7 @@ fn emit_event_fns() -> void { # # First cut: integer component fields (the common case — hp, x, amount). Property # ids are assignment order in the source; field ids are declaration order. -fn emit_world_table() -> void { +function emit_world_table() -> void { let me = itoa(MAX_ENT) emith("declare i32 @strcmp(ptr, ptr)\n") @@ -687,7 +687,7 @@ fn emit_world_table() -> void { # runs are also exposed as callables, so a game that owns its `entry` loop can # drive the simulation itself (for prediction/rollback, replay, headless tests, or # AI). tick_fixed() runs the sim phases; tick_render() runs Render. -fn emit_tick_helpers() -> void { +function emit_tick_helpers() -> void { emit("define void @L_tick_fixed() {\nentry:\n") ll_t = 0; ll_lbl = 0 emit_calls_for_phase("Input") @@ -703,7 +703,7 @@ fn emit_tick_helpers() -> void { # system functions + lifecycle hooks + the drivable tick helpers — shared by the # auto-loop game (emit_game_main) and an entry-driven game that owns its own loop. -fn emit_game_defs() -> void { +function emit_game_defs() -> void { var i = 0 while i < len(prog) { if prog[i].kind == N_SYS { emit_system_fn(prog[i]) }; i = i + 1 } emit_despawn_hooks() @@ -711,7 +711,7 @@ fn emit_game_defs() -> void { emit_tick_helpers() } -fn emit_game_main() -> void { +function emit_game_main() -> void { emit_game_defs() emit("define i32 @main(i32 %argc, ptr %argv) {\nentry:\n") diff --git a/selfhost/emit_hash.ludic b/selfhost/emit_hash.ludic index 27c31e74..7171940e 100644 --- a/selfhost/emit_hash.ludic +++ b/selfhost/emit_hash.ludic @@ -11,7 +11,7 @@ # Hash.mix(x) fmix32 avalanche of a single int (turn a counter into a seed) # Hash.combine(...) fold several ints into one (e.g. world_seed, cx, cy) -fn is_hash_ns(meth: ptr) -> bool { +function is_hash_ns(meth: ptr) -> bool { if (meth == "of") or (meth == "fnv1a") or (meth == "crc32") { return true } if (meth == "mix") or (meth == "combine") { return true } if (meth == "of64") or (meth == "fnv1a_64") or (meth == "mix64") { return true } @@ -21,7 +21,7 @@ fn is_hash_ns(meth: ptr) -> bool { # fmix32 (MurmurHash3 finalizer) of a single i32 -> code of an i32. A strong # avalanche: flips ~half the output bits for any one input bit. Used on its own # (Hash.mix) and nowhere else — combine has its own mixing step. -fn hash_mix_code(x: ptr) -> ptr { +function hash_mix_code(x: ptr) -> ptr { let a = emit_bind(`lshr i32 {x}, 16`) let b = emit_bind(`xor i32 {x}, {a}`) let c = emit_bind(`mul i32 {b}, -2048144789`) # * 0x85ebca6b @@ -35,7 +35,7 @@ fn hash_mix_code(x: ptr) -> ptr { # fmix64 (MurmurHash3 64-bit finalizer) of a single i64 -> code of an i64. The # 64-bit twin of hash_mix_code: shift by 33 and multiply by the two 64-bit # constants. Backs Hash.mix64. -fn hash_mix64_code(x: ptr) -> ptr { +function hash_mix64_code(x: ptr) -> ptr { let a = emit_bind(`lshr i64 {x}, 33`) let b = emit_bind(`xor i64 {x}, {a}`) let c = emit_bind(`mul i64 {b}, -49064778989728563`) # * 0xff51afd7ed558ccd @@ -46,7 +46,7 @@ fn hash_mix64_code(x: ptr) -> ptr { return emit_bind(`xor i64 {f}, {g}`) } -fn emit_hash_ns(meth: ptr, e: Node) -> Val { +function emit_hash_ns(meth: ptr, e: Node) -> Val { if (meth == "of") or (meth == "fnv1a") { # FNV-1a 32-bit over the bytes g_uses_hashrt = true let s = emit_expr(e.kids[0]) @@ -94,7 +94,7 @@ fn emit_hash_ns(meth: ptr, e: Node) -> Val { # at a time with pure integer IR: FNV-1a with the standard 32-bit offset basis / # prime, and a bitwise CRC-32 with the reflected poly 0xEDB88320. No libc, no # allocation, bit-identical on every target. -fn emit_hash_prelude() -> void { +function emit_hash_prelude() -> void { emith("define i32 @fn_hash_fnv1a(ptr %s) {\n") emith("entry:\n") emith(" %hp = alloca i32\n") diff --git a/selfhost/emit_head.ludic b/selfhost/emit_head.ludic index d9e7c417..94c6c8d6 100644 --- a/selfhost/emit_head.ludic +++ b/selfhost/emit_head.ludic @@ -1,10 +1,10 @@ # 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 +function 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 { +function emit_str_const(s: ptr) -> ptr { let name = `@.str{itoa(ll_str)}` ll_str = ll_str + 1 let n = len(s) @@ -25,7 +25,7 @@ fn emit_str_const(s: ptr) -> ptr { } # the constant initializer for a global var: a literal, or 0/null -fn global_init(d: Node) -> ptr { +function global_init(d: Node) -> ptr { if (d.a == null) { if (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) } @@ -34,7 +34,7 @@ fn global_init(d: Node) -> ptr { return "0" } -fn emit_header() -> void { +function emit_header() -> void { emith("; Ludic (self-hosted) -> LLVM IR\n") emith("declare i32 @printf(ptr, ...)\n") emith("declare ptr @malloc(i64)\n") @@ -109,7 +109,7 @@ fn emit_header() -> void { # One `declare @()` per `extern fn`, so the linker resolves the # call to the bound symbol. Emitted after the header; a program with no `extern fn` # emits nothing here, so un-networked builds stay byte-identical. -fn emit_extern_decls() -> void { +function emit_extern_decls() -> void { var i = 0 while i < len(prog) { let d = prog[i] @@ -131,7 +131,7 @@ fn emit_extern_decls() -> void { # on strings. Hand-written IR over NUL-terminated byte buffers: str_eq walks both # until a mismatch or a shared terminator; str_concat measures both, mallocs # len+len+1, copies each half, and NUL-terminates. @malloc is always declared. -fn emit_str_prelude() -> void { +function emit_str_prelude() -> void { emith("define i32 @fn_str_eq(ptr %a, ptr %b) {\n") emith("entry:\n br label %loop\n") emith("loop:\n %i = phi i32 [ 0, %entry ], [ %i1, %cont ]\n") @@ -173,7 +173,7 @@ fn emit_str_prelude() -> void { # int -> decimal string, emitted (once) into any program that uses str(int) # (string interpolation of a number). Writes digits from the end of a 24-byte # buffer, prepends '-' for negatives, and returns a pointer into the buffer. -fn emit_int_str() -> void { +function emit_int_str() -> void { emith("define ptr @fn_int_str(i32 %n0) {\n") emith("entry:\n %buf = call ptr @malloc(i64 24)\n") emith(" %isneg = icmp slt i32 %n0, 0\n %neg = sub i32 0, %n0\n") @@ -196,7 +196,7 @@ fn emit_int_str() -> void { # the i64 twin of fn_int_str: a signed 64-bit integer -> decimal text. Emitted # once per program that stringifies a `long` (g_uses_longstr). A 64-bit value is # at most 20 digits plus sign and NUL, so the 24-byte scratch buffer still fits. -fn emit_long_str() -> void { +function emit_long_str() -> void { emith("define ptr @fn_long_str(i64 %n0) {\n") emith("entry:\n %buf = call ptr @malloc(i64 24)\n") emith(" %isneg = icmp slt i64 %n0, 0\n %neg = sub i64 0, %n0\n") @@ -218,7 +218,7 @@ fn emit_long_str() -> void { # s[a..b] -> a fresh NUL-terminated copy of the bytes [a, b), emitted (once) into # any program that slices a string. Mallocs (b-a)+1, copies, terminates. -fn emit_str_slice() -> void { +function emit_str_slice() -> void { emith("define ptr @fn_str_slice(ptr %s, i32 %start, i32 %end) {\n") emith("entry:\n %len = sub i32 %end, %start\n %sz = add i32 %len, 1\n") emith(" %sz64 = sext i32 %sz to i64\n %out = call ptr @malloc(i64 %sz64)\n br label %loop\n") diff --git a/selfhost/emit_intrin.ludic b/selfhost/emit_intrin.ludic index 219a6e1f..dc7695ef 100644 --- a/selfhost/emit_intrin.ludic +++ b/selfhost/emit_intrin.ludic @@ -6,7 +6,7 @@ var g_intrin_ok: bool = false # is `name` a low-level intrinsic? A pure name check, so it can gate dispatch # without evaluating arguments (which could clobber shared state). -fn is_intrinsic(name: ptr) -> bool { +function is_intrinsic(name: ptr) -> bool { if (name == "resize") { return true } if (name == "file_open") or (name == "file_read") or (name == "file_write") { return true } if (name == "file_seek") or (name == "file_tell") or (name == "file_close") { return true } @@ -16,11 +16,11 @@ fn is_intrinsic(name: ptr) -> bool { return false } # emit " = \n" and return r -fn emit_bind(rest: ptr) -> ptr { let r = nreg(); emit(" "); emit(r); emit(" = "); emit(rest); emit("\n"); return r } +function 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 } +function 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 { +function emit_intrinsic(name: ptr, e: Node) -> Val { g_intrin_ok = true # ptr_null / ptr_is_null are the `null` literal and `x == null` now. # mem_alloc is bytes(n) / words(n) now (see emit_call). diff --git a/selfhost/emit_intrin2.ludic b/selfhost/emit_intrin2.ludic index e3732f85..900866c2 100644 --- a/selfhost/emit_intrin2.ludic +++ b/selfhost/emit_intrin2.ludic @@ -4,7 +4,7 @@ var g_windowed: bool = false # headless by default (games read stdin / dump PPM) -fn is_intrinsic2(name: ptr) -> bool { +function is_intrinsic2(name: ptr) -> bool { if (name == "free") or (name == "fill") { return true } if (name == "offset") or (name == "read_char") { return true } if (name == "as_fixed") or (name == "as_int") { return true } @@ -14,7 +14,7 @@ fn is_intrinsic2(name: ptr) -> bool { return false } -fn emit_intrinsic2(name: ptr, e: Node) -> Val { +function emit_intrinsic2(name: ptr, e: Node) -> Val { if (name == "free") { let p = arg_code(e, 0); emit(" call void @free(ptr "); emit(p); emit(")\n"); return val("0", "void") } diff --git a/selfhost/emit_list.ludic b/selfhost/emit_list.ludic index 006f29a6..c21255f5 100644 --- a/selfhost/emit_list.ludic +++ b/selfhost/emit_list.ludic @@ -5,13 +5,13 @@ # for reference elements (structs/strings), matching how `==` behaves elsewhere. # address of element `idx` (an i32 code) in slice header `h`, element LLVM type `elt` -fn list_elem_addr(h: ptr, elt: ptr, idx: ptr) -> ptr { +function list_elem_addr(h: ptr, elt: ptr, idx: ptr) -> ptr { let dp = slice_field(h, 0) let data = emit_bind(`load ptr, ptr {dp}`) return emit_bind(`getelementptr inbounds {elt}, ptr {data}, i32 {idx}`) } -fn is_list_ns(meth: ptr) -> bool { +function is_list_ns(meth: ptr) -> bool { if (meth == "len") or (meth == "push") or (meth == "clear") { return true } if (meth == "first") or (meth == "last") or (meth == "pop") or (meth == "swap") { return true } if (meth == "contains") or (meth == "index_of") or (meth == "reverse") { return true } @@ -21,7 +21,7 @@ fn is_list_ns(meth: ptr) -> bool { # grow the slice's backing buffer if it is full, exactly as push does (double, # or 8 from empty). Leaves length untouched; only capacity/data may change. -fn list_grow_if_full(h: ptr, elt: ptr) -> void { +function list_grow_if_full(h: ptr, elt: ptr) -> void { let lp = slice_field(h, 1); let cp = slice_field(h, 2); let dp = slice_field(h, 0) let l = emit_bind(`load i32, ptr {lp}`) let c = emit_bind(`load i32, ptr {cp}`) @@ -43,7 +43,7 @@ fn list_grow_if_full(h: ptr, elt: ptr) -> void { emit(done); emit(":\n") } -fn emit_list_ns(meth: ptr, e: Node) -> Val { +function emit_list_ns(meth: ptr, e: Node) -> Val { if (meth == "len") { return emit_len(e) } # same header length as len(s) if (meth == "push") { return emit_push(e) } # same as push(s, v) let s = emit_expr(e.kids[0]) diff --git a/selfhost/emit_machine.ludic b/selfhost/emit_machine.ludic index caefed11..673f677a 100644 --- a/selfhost/emit_machine.ludic +++ b/selfhost/emit_machine.ludic @@ -4,7 +4,7 @@ # older code, a register index (reg()/set_reg()). # If `a` names a program-scope `var`, return it; else null (a register index). -fn machine_var(a: Node) -> Node { +function machine_var(a: Node) -> Node { if a.kind == E_ID { let g = find_global(a.s) if (g != null) { if g.kind == N_VAR { return g } } @@ -12,7 +12,7 @@ fn machine_var(a: Node) -> Node { return null } -fn emit_machine(st: Node) -> void { +function emit_machine(st: Node) -> void { let gv = machine_var(st.a) var s = "" if (gv != null) { s = emit_bind(`load i32, ptr @g_{st.a.s}`) } @@ -45,7 +45,7 @@ fn emit_machine(st: Node) -> void { # enclosing `machine`, otherwise a scene transition. A scene transition runs the # source scene's on-exit, stores the target scene id into @L_scene, and runs the # target's on-enter (two direct calls and a store — no dispatch table). -fn emit_become(st: Node) -> void { +function emit_become(st: Node) -> void { if nmach > 0 { # inside a machine: try a state first let m = mach_stk[nmach - 1] var target: Node = null diff --git a/selfhost/emit_math.ludic b/selfhost/emit_math.ludic index 5f5feb05..f9255790 100644 --- a/selfhost/emit_math.ludic +++ b/selfhost/emit_math.ludic @@ -5,11 +5,11 @@ # integer root, @fn_fx_sin a 256-entry interpolated sine table). Everything is # plain integer IR, so it is bit-identical on every platform. -fn is_math_builtin(name: ptr) -> bool { +function is_math_builtin(name: ptr) -> bool { return (name == "min") or (name == "max") or (name == "abs") or (name == "clamp") } -fn emit_math_builtin(name: ptr, e: Node) -> Val { +function emit_math_builtin(name: ptr, e: Node) -> Val { if (name == "abs") { let a = emit_expr(e.kids[0]) let c = emit_bind(`icmp slt i32 {a.code}, 0`) @@ -32,7 +32,7 @@ fn emit_math_builtin(name: ptr, e: Node) -> Val { } # a * b in Q16.16 (64-bit intermediate, arithmetic shift back) -> code of an i32 -fn fx_mul_code(a: ptr, b: ptr) -> ptr { +function fx_mul_code(a: ptr, b: ptr) -> ptr { let a64 = emit_bind(`sext i32 {a} to i64`) let b64 = emit_bind(`sext i32 {b} to i64`) let m = emit_bind(`mul i64 {a64}, {b64}`) @@ -41,7 +41,7 @@ fn fx_mul_code(a: ptr, b: ptr) -> ptr { } # a / b in Q16.16 (shift the numerator up before the divide) -> code of an i32 -fn fx_div_code(a: ptr, b: ptr) -> ptr { +function fx_div_code(a: ptr, b: ptr) -> ptr { let a64 = emit_bind(`sext i32 {a} to i64`) let ash = emit_bind(`shl i64 {a64}, 16`) let b64 = emit_bind(`sext i32 {b} to i64`) @@ -50,14 +50,14 @@ fn fx_div_code(a: ptr, b: ptr) -> ptr { } # lerp(a, b, t) = a + (b - a) * t, all Q16.16 -> code of a fixed i32 -fn fx_lerp_code(a: ptr, b: ptr, t: ptr) -> ptr { +function fx_lerp_code(a: ptr, b: ptr, t: ptr) -> ptr { let d = emit_bind(`sub i32 {b}, {a}`) let dt = fx_mul_code(d, t) return emit_bind(`add i32 {a}, {dt}`) } # inverse_lerp(a, b, v) = (v - a) / (b - a), all Q16.16 -> code of a fixed i32 -fn fx_inv_lerp_code(a: ptr, b: ptr, v: ptr) -> ptr { +function fx_inv_lerp_code(a: ptr, b: ptr, v: ptr) -> ptr { let num = emit_bind(`sub i32 {v}, {a}`) let den = emit_bind(`sub i32 {b}, {a}`) return fx_div_code(num, den) @@ -66,7 +66,7 @@ fn fx_inv_lerp_code(a: ptr, b: ptr, v: ptr) -> ptr { # Math.* — the namespaced surface. min/max/abs/clamp reuse the bare lowering; # the rest are new deterministic fixed-point helpers. Returns g_intrin-style via # a direct Val; callers guard with is_math_ns first. -fn is_math_ns(meth: ptr) -> bool { +function is_math_ns(meth: ptr) -> bool { if (meth == "min") or (meth == "max") or (meth == "abs") or (meth == "clamp") { return true } if (meth == "sign") or (meth == "floor") or (meth == "ceil") or (meth == "round") { return true } if (meth == "lerp") or (meth == "inverse_lerp") or (meth == "remap") { return true } @@ -79,7 +79,7 @@ fn is_math_ns(meth: ptr) -> bool { return false } -fn emit_math_ns(meth: ptr, e: Node) -> Val { +function emit_math_ns(meth: ptr, e: Node) -> Val { if (meth == "min") or (meth == "max") or (meth == "abs") or (meth == "clamp") { return emit_math_builtin(meth, e) } @@ -284,7 +284,7 @@ fn emit_math_ns(meth: ptr, e: Node) -> Val { # sine table with linear interpolation; @fn_fx_exp2/@fn_fx_log2 are range-reduced # Q16.16 polynomials (base-2 exp and log) that back exp/log/pow. All are pure # integer IR, so bit-identical on every platform. -fn emit_math_prelude() -> void { +function emit_math_prelude() -> void { emith("@L_sin_tab = private unnamed_addr constant [256 x i32] [i32 0, i32 1608, i32 3216, i32 4821, i32 6424, i32 8022, i32 9616, i32 11204, i32 12785, i32 14359, i32 15924, i32 17479, i32 19024, i32 20557, i32 22078, i32 23586, i32 25080, i32 26558, i32 28020, i32 29466, i32 30893, i32 32303, i32 33692, i32 35062, i32 36410, i32 37736, i32 39040, i32 40320, i32 41576, i32 42806, i32 44011, i32 45190, i32 46341, i32 47464, i32 48559, i32 49624, i32 50660, i32 51665, i32 52639, i32 53581, i32 54491, i32 55368, i32 56212, i32 57022, i32 57798, i32 58538, i32 59244, i32 59914, i32 60547, i32 61145, i32 61705, i32 62228, i32 62714, i32 63162, i32 63572, i32 63944, i32 64277, i32 64571, i32 64827, i32 65043, i32 65220, i32 65358, i32 65457, i32 65516, i32 65536, i32 65516, i32 65457, i32 65358, i32 65220, i32 65043, i32 64827, i32 64571, i32 64277, i32 63944, i32 63572, i32 63162, i32 62714, i32 62228, i32 61705, i32 61145, i32 60547, i32 59914, i32 59244, i32 58538, i32 57798, i32 57022, i32 56212, i32 55368, i32 54491, i32 53581, i32 52639, i32 51665, i32 50660, i32 49624, i32 48559, i32 47464, i32 46341, i32 45190, i32 44011, i32 42806, i32 41576, i32 40320, i32 39040, i32 37736, i32 36410, i32 35062, i32 33692, i32 32303, i32 30893, i32 29466, i32 28020, i32 26558, i32 25080, i32 23586, i32 22078, i32 20557, i32 19024, i32 17479, i32 15924, i32 14359, i32 12785, i32 11204, i32 9616, i32 8022, i32 6424, i32 4821, i32 3216, i32 1608, i32 0, i32 -1608, i32 -3216, i32 -4821, i32 -6424, i32 -8022, i32 -9616, i32 -11204, i32 -12785, i32 -14359, i32 -15924, i32 -17479, i32 -19024, i32 -20557, i32 -22078, i32 -23586, i32 -25080, i32 -26558, i32 -28020, i32 -29466, i32 -30893, i32 -32303, i32 -33692, i32 -35062, i32 -36410, i32 -37736, i32 -39040, i32 -40320, i32 -41576, i32 -42806, i32 -44011, i32 -45190, i32 -46341, i32 -47464, i32 -48559, i32 -49624, i32 -50660, i32 -51665, i32 -52639, i32 -53581, i32 -54491, i32 -55368, i32 -56212, i32 -57022, i32 -57798, i32 -58538, i32 -59244, i32 -59914, i32 -60547, i32 -61145, i32 -61705, i32 -62228, i32 -62714, i32 -63162, i32 -63572, i32 -63944, i32 -64277, i32 -64571, i32 -64827, i32 -65043, i32 -65220, i32 -65358, i32 -65457, i32 -65516, i32 -65536, i32 -65516, i32 -65457, i32 -65358, i32 -65220, i32 -65043, i32 -64827, i32 -64571, i32 -64277, i32 -63944, i32 -63572, i32 -63162, i32 -62714, i32 -62228, i32 -61705, i32 -61145, i32 -60547, i32 -59914, i32 -59244, i32 -58538, i32 -57798, i32 -57022, i32 -56212, i32 -55368, i32 -54491, i32 -53581, i32 -52639, i32 -51665, i32 -50660, i32 -49624, i32 -48559, i32 -47464, i32 -46341, i32 -45190, i32 -44011, i32 -42806, i32 -41576, i32 -40320, i32 -39040, i32 -37736, i32 -36410, i32 -35062, i32 -33692, i32 -32303, i32 -30893, i32 -29466, i32 -28020, i32 -26558, i32 -25080, i32 -23586, i32 -22078, i32 -20557, i32 -19024, i32 -17479, i32 -15924, i32 -14359, i32 -12785, i32 -11204, i32 -9616, i32 -8022, i32 -6424, i32 -4821, i32 -3216, i32 -1608]\n") emith("define i32 @fn_fx_sqrt(i32 %x) {\n") emith("entry:\n %neg = icmp slt i32 %x, 0\n br i1 %neg, label %ret0, label %go\n") diff --git a/selfhost/emit_mem.ludic b/selfhost/emit_mem.ludic index 74894f8d..2f179bd4 100644 --- a/selfhost/emit_mem.ludic +++ b/selfhost/emit_mem.ludic @@ -3,13 +3,13 @@ # read and write one byte. The low-level escape hatch, PICO-8's memcpy/memset/ # peek/poke by another name. -fn is_mem_ns(meth: ptr) -> bool { +function is_mem_ns(meth: ptr) -> bool { if (meth == "bytes") or (meth == "words") { return true } if (meth == "copy") or (meth == "fill") or (meth == "peek") or (meth == "poke") { return true } return false } -fn emit_mem_ns(meth: ptr, e: Node) -> Val { +function emit_mem_ns(meth: ptr, e: Node) -> Val { if (meth == "bytes") { # allocate n bytes -> a byte buffer let n = emit_expr(e.kids[0]) let w = emit_bind(`zext i32 {n.code} to i64`) diff --git a/selfhost/emit_net.ludic b/selfhost/emit_net.ludic index a8d6358e..ca769ce7 100644 --- a/selfhost/emit_net.ludic +++ b/selfhost/emit_net.ludic @@ -19,11 +19,11 @@ var g_uses_loopback: bool = false # component participates in the entity's model (the model member is @Sync, # member.ival==1). Participation is decided per model use-site. -fn net_field_ibytes(ty: ptr) -> int { if (llty(ty) == "i8") { return 1 }; return 4 } -fn net_field_bytes(ty: ptr) -> ptr { if (llty(ty) == "i8") { return "1" }; return "4" } +function net_field_ibytes(ty: ptr) -> int { if (llty(ty) == "i8") { return 1 }; return 4 } +function net_field_bytes(ty: ptr) -> ptr { if (llty(ty) == "i8") { return "1" }; return "4" } # total replicated bytes for model m (compile-time constant) -fn net_model_bytes(m: Node) -> int { +function net_model_bytes(m: Node) -> int { var total = 0 var ci = 0 while ci < len(m.kids) { @@ -39,16 +39,16 @@ fn net_model_bytes(m: Node) -> int { return total } -fn net_model_syncs(m: Node) -> bool { return net_model_bytes(m) > 0 } +function net_model_syncs(m: Node) -> bool { return net_model_bytes(m) > 0 } -fn net_has_sync() -> bool { +function net_has_sync() -> bool { var i = 0 while i < len(prog) { if prog[i].kind == N_ARCH { if net_model_syncs(prog[i]) { return true } }; i = i + 1 } return false } # ---- N3: ownership ----------------------------------------------------------- -fn net_has_owned() -> bool { +function net_has_owned() -> bool { var i = 0 while i < len(prog) { if prog[i].kind == N_ARCH and (prog[i].ival == 1) { return true }; i = i + 1 } return false @@ -56,7 +56,7 @@ fn net_has_owned() -> bool { # ---- N5: role-tagged handlers ------------------------------------------------ # A handler tagged @Server (ival==1) or @Predicted (ival==2) has a network role. -fn net_has_role() -> bool { +function net_has_role() -> bool { var i = 0 while i < len(prog) { if prog[i].kind == N_SYS and (prog[i].ival != 0) { return true }; i = i + 1 } return false @@ -64,10 +64,10 @@ fn net_has_role() -> bool { # Any networking feature in use → emit the shared role registers (@L_role / # @L_localid). A runtime sets them; offline they keep their single-player default. -fn net_any() -> bool { return net_has_sync() or net_has_owned() or net_has_role() } +function net_any() -> bool { return net_has_sync() or net_has_owned() or net_has_role() } # ---- diagnostics ------------------------------------------------------------- -fn net_warn(msg: ptr) -> void { +function net_warn(msg: ptr) -> void { let e = file_stderr() file_write(e, "ludicc(self): warning: ", 23) file_write(e, msg, len(msg)) @@ -77,7 +77,7 @@ fn net_warn(msg: ptr) -> void { # Validate @Sync usage: a participating member whose component replicates nothing # is a warning (participation that replicates nothing); a @Sync ptr field is a # hard error (footgun 3 — networked fields must be POD scalars). -fn net_check() -> void { +function net_check() -> void { var i = 0 while i < len(prog) { if prog[i].kind == N_ARCH { @@ -110,7 +110,7 @@ fn net_check() -> void { # ---- N2: per-model serializer / applier -------------------------------------- # serialize_(e, buf) -> bytes written. Copies each replicated field, tightly # packed in member-then-field order, so apply reads the identical layout. -fn emit_net_serialize(m: Node) -> void { +function emit_net_serialize(m: Node) -> void { ll_t = 0 let me = itoa(MAX_ENT) emit("define i32 @L_serialize_"); emit(m.s); emit("(i32 %e, ptr %buf) {\nentry:\n") @@ -143,7 +143,7 @@ fn emit_net_serialize(m: Node) -> void { # apply_(e, buf, len): the inverse — copy each replicated field back from the # buffer into component storage. `len` is accepted for symmetry (the runtime's # framing) but the layout is fixed, so it is not consulted. -fn emit_net_apply(m: Node) -> void { +function emit_net_apply(m: Node) -> void { ll_t = 0 let me = itoa(MAX_ENT) emit("define void @L_apply_"); emit(m.s); emit("(i32 %e, ptr %buf, i32 %len) {\nentry:\n") @@ -177,7 +177,7 @@ fn emit_net_apply(m: Node) -> void { # ludic_serialize(e, buf) -> bytes / ludic_apply(e, buf, len) / ludic_sync_size(e) # route on the entity's model kind to the per-model function above, so a # replication runtime replicates any entity without knowing its type. -fn emit_net_dispatch() -> void { +function emit_net_dispatch() -> void { let me = itoa(MAX_ENT) emit("define i32 @ludic_serialize(i32 %e, ptr %buf) {\nentry:\n") @@ -236,7 +236,7 @@ fn emit_net_dispatch() -> void { # @L_owner: one i32 owner id per entity, -1 = unowned. Only emitted when a model # is @Owned, and L_reset clears it to -1 on alloc/free (see emit_ecs). owner()/ # set_owner()/is_owner() read and write it; the authority assigns. -fn emit_net_owner() -> void { +function emit_net_owner() -> void { let me = itoa(MAX_ENT) emit("define i32 @L_owner(i32 %e) {\nentry:\n") emit(" %p = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_owner_arr, i32 0, i32 %e\n %v = load i32, ptr %p\n ret i32 %v\n}\n\n") @@ -256,7 +256,7 @@ fn emit_net_owner() -> void { # with NO foreign host at all (the Ludic-native default). Datagram-preserving: # one message per poll, matching how replication/RPC frame. Emitted only when a # program actually calls net_send/net_poll without an extern override. -fn emit_loopback() -> void { +function emit_loopback() -> void { emith("@L_netq = internal global [64 x [2048 x i8]] zeroinitializer\n") emith("@L_netlen = internal global [64 x i32] zeroinitializer\n") emith("@L_nethead = internal global i32 0\n") @@ -294,24 +294,24 @@ fn emit_loopback() -> void { # re-emits each into the ordinary @ev_ dispatch on the far side. Reuses the # EV0 payload (already flat) and the transport seam — no new concept. -fn net_has_remote() -> bool { +function net_has_remote() -> bool { var i = 0 while i < len(g_events) { if (g_events[i].ty != null) { return true }; i = i + 1 } return false } # stable wire id for an event = its index in g_events (same program both peers) -fn net_event_id(name: ptr) -> int { +function net_event_id(name: ptr) -> int { var i = 0 while i < len(g_events) { if (g_events[i].s == name) { return i }; i = i + 1 } return 0 - 1 } # the transport symbols: an `extern fn` override, else the built-in loopback. -fn net_send_sym() -> ptr { let x = find_extern("net_send"); if (x != null) { return x.a.s }; return "L_net_send" } -fn net_poll_sym() -> ptr { let x = find_extern("net_poll"); if (x != null) { return x.a.s }; return "L_net_poll" } +function net_send_sym() -> ptr { let x = find_extern("net_send"); if (x != null) { return x.a.s }; return "L_net_send" } +function net_poll_sym() -> ptr { let x = find_extern("net_poll"); if (x != null) { return x.a.s }; return "L_net_poll" } # net_pump(): poll every pending frame and re-emit it locally. The receive path # of a remote event — the runtime/game calls this each tick. -fn emit_net_pump() -> void { +function emit_net_pump() -> void { emith("@L_recvbuf = internal global [2048 x i8] zeroinitializer\n") if (find_extern("net_poll") == null) { g_uses_loopback = true } let psym = net_poll_sym() @@ -352,7 +352,7 @@ fn emit_net_pump() -> void { } # ---- driver ------------------------------------------------------------------ -fn emit_net() -> void { +function emit_net() -> void { if net_has_sync() { net_check() var i = 0 diff --git a/selfhost/emit_new.ludic b/selfhost/emit_new.ludic index daf0b158..16629fad 100644 --- a/selfhost/emit_new.ludic +++ b/selfhost/emit_new.ludic @@ -2,12 +2,12 @@ # push(slice, v) and len(slice). Slices are a { data, len, cap } header the # holder points at, so growth is visible to every holder. -fn emit_sizeof(llt: ptr) -> ptr { +function emit_sizeof(llt: ptr) -> ptr { let p = emit_bind(`getelementptr {llt}, ptr null, i32 1`) return emit_bind(`ptrtoint ptr {p} to i64`) } -fn emit_new_struct(name: ptr) -> Val { +function emit_new_struct(name: ptr) -> Val { let s = layout_node(name) # a struct or a property — same shape if (s == null) { perr("unknown record type in new") } let lty = layout_ty(name) @@ -29,7 +29,7 @@ fn emit_new_struct(name: ptr) -> Val { return val(obj, name) } -fn emit_new_slice(ty: ptr) -> Val { +function emit_new_slice(ty: ptr) -> Val { let sz = emit_sizeof("%LSlice") let h = emit_bind(`call ptr @malloc(i64 {sz})`) let d0 = nreg(); emit(" "); emit(d0); emit(" = getelementptr inbounds %LSlice, ptr "); emit(h); emit(", i32 0, i32 0\n") @@ -41,14 +41,14 @@ fn emit_new_slice(ty: ptr) -> Val { return val(h, ty) } -fn slice_field(h: ptr, i: int) -> ptr { +function 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") return r } -fn emit_len(e: Node) -> Val { +function emit_len(e: Node) -> Val { let s = emit_expr(e.kids[0]) if is_slice_ty(s.ty) { # a slice: read its header length let lp = slice_field(s.code, 1) @@ -58,7 +58,7 @@ fn emit_len(e: Node) -> Val { return val(emit_bind(`trunc i64 {r} to i32`), "int") } -fn emit_push(e: Node) -> Val { +function emit_push(e: Node) -> Val { let s = emit_expr(e.kids[0]) let el = slice_elem(s.ty) let elt = llty(el) diff --git a/selfhost/emit_query.ludic b/selfhost/emit_query.ludic index e1194d17..1579c309 100644 --- a/selfhost/emit_query.ludic +++ b/selfhost/emit_query.ludic @@ -2,13 +2,13 @@ # components/archetypes each carries, binds the requested components, and runs # the body once per match. Mirrors ll_query in compiler/back/ir_ecs.c. -fn find_arch_id(name: ptr) -> int { +function find_arch_id(name: ptr) -> int { var i = 0; var n = 1 while i < len(prog) { if prog[i].kind == N_ARCH { if (prog[i].s == name) { return n }; n = n + 1 }; i = i + 1 } return 0 } -fn emit_query(st: Node) -> void { +function emit_query(st: Node) -> void { let me = itoa(MAX_ENT) let ip = emit_alloca("i32") store_at("i32", "0", ip) diff --git a/selfhost/emit_save.ludic b/selfhost/emit_save.ludic index 9091ca41..613bcd2d 100644 --- a/selfhost/emit_save.ludic +++ b/selfhost/emit_save.ludic @@ -16,7 +16,7 @@ var g_iok: int = 0 var g_snap_mode: ptr = null # "file" | "save" | "load" | "size" var g_off: ptr = null # current byte-offset register, buffer modes -fn emit_io(fn2: ptr, p: ptr, bytes: ptr) -> void { +function emit_io(fn2: ptr, p: ptr, bytes: ptr) -> void { if (g_snap_mode == "file") { let r = `%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") @@ -43,7 +43,7 @@ fn emit_io(fn2: ptr, p: ptr, bytes: ptr) -> void { g_iok = g_iok + 1 } -fn emit_snapshot_blocks(fn2: ptr) -> void { +function emit_snapshot_blocks(fn2: ptr) -> void { g_iok = 0 g_off = "0" let me = itoa(MAX_ENT) @@ -73,7 +73,7 @@ fn emit_snapshot_blocks(fn2: ptr) -> void { } } -fn emit_snapshot() -> void { +function emit_snapshot() -> void { g_snap_mode = "file" # seed (module ptr inits are null) emith("@.sav_path = private unnamed_addr constant [10 x i8] c\"ludic.sav\\00\"\n") emith("@.sav_wb = private unnamed_addr constant [3 x i8] c\"wb\\00\"\n") diff --git a/selfhost/emit_spawn.ludic b/selfhost/emit_spawn.ludic index 236ff736..ddd4e57f 100644 --- a/selfhost/emit_spawn.ludic +++ b/selfhost/emit_spawn.ludic @@ -1,7 +1,7 @@ # emit_spawn.ludic — spawn / despawn / self(), and seeding a component's fields # from its declared defaults plus any per-spawn overrides. -fn emit_init_component(e: ptr, comp: ptr, rec: Node) -> void { +function emit_init_component(e: ptr, comp: ptr, rec: Node) -> void { let me = itoa(MAX_ENT) let c = find_comp(comp) if (c == null) { perr(`spawn: unknown property {comp}`) } @@ -54,7 +54,7 @@ fn emit_init_component(e: ptr, comp: ptr, rec: Node) -> void { # bind each of a model's properties to entity `e`'s component storage, so an # @OnSpawn hook body can address them by name (like a query binding for one entity). -fn emit_bind_props(model: Node, e: ptr) -> void { +function emit_bind_props(model: Node, e: ptr) -> void { let me = itoa(MAX_ENT) var c = 0 while c < len(model.kids) { @@ -68,7 +68,7 @@ fn emit_bind_props(model: Node, e: ptr) -> void { } } -fn emit_spawn(st: Node) -> ptr { +function emit_spawn(st: Node) -> ptr { let e = emit_bind("call i32 @L_alloc()") let ak = find_arch_id(st.s) if ak > 0 { @@ -103,7 +103,7 @@ fn emit_spawn(st: Node) -> ptr { return e # the new entity id (for ludic_spawn_) } -fn emit_despawn(st: Node) -> void { +function emit_despawn(st: Node) -> void { let v = emit_expr(st.a) # @OnDespawn: dispatch on the entity's kind and run the matching model's hook if len(g_ondespawn) > 0 { @@ -131,7 +131,7 @@ fn emit_despawn(st: Node) -> void { # (not a toggle): seeds the property's fields and fires @OnAttach, but only on a # real transition — if the entity already has the property it is a no-op, so the # hook fires once per genuine attach (flecs/Bevy "real add" semantics). -fn emit_attach(st: Node) -> void { +function emit_attach(st: Node) -> void { let ev = emit_expr(st.a) let me = itoa(MAX_ENT) let hp = nreg(); emit(" "); emit(hp); emit(" = getelementptr inbounds ["); emit(me); emit(" x i8], ptr @H_"); emit(st.s); emit(", i32 0, i32 "); emit(ev.code); emit("\n") @@ -149,7 +149,7 @@ fn emit_attach(st: Node) -> void { # property bound by name (its data still lives in @S_ storage, so the teardown # body reads the outgoing value), then clears the has-flag so queries skip it. # Only fires on a real transition; detaching an absent property is a no-op. -fn emit_detach(st: Node) -> void { +function emit_detach(st: Node) -> void { let ev = emit_expr(st.a) let me = itoa(MAX_ENT) let hp = nreg(); emit(" "); emit(hp); emit(" = getelementptr inbounds ["); emit(me); emit(" x i8], ptr @H_"); emit(st.s); emit(", i32 0, i32 "); emit(ev.code); emit("\n") @@ -178,7 +178,7 @@ fn emit_detach(st: Node) -> void { # enable/disable. `

on ` toggles a property's has-flag on an entity (its # data persists, so re-enabling restores it, and queries already skip a cleared # flag). A bare `` / `` flips a global enabled flag. -fn emit_toggle(st: Node) -> void { +function emit_toggle(st: Node) -> void { var val = "0"; if st.ival == 1 { val = "1" } if (st.ty != null) and (st.ty == "layer") { # enable/disable layer L emit(" store i32 "); emit(val); emit(", ptr @LE_"); emit(st.s); emit("\n") diff --git a/selfhost/emit_stmt.ludic b/selfhost/emit_stmt.ludic index 90b0300f..de92c5a7 100644 --- a/selfhost/emit_stmt.ludic +++ b/selfhost/emit_stmt.ludic @@ -1,7 +1,7 @@ # emit_stmt.ludic — lower statements. Terminators set g_term so the rest of a # block is skipped until a new basic block opens. -fn emit_block(b: Node) -> void { +function emit_block(b: Node) -> void { var i = 0 while i < len(b.kids) { if g_term { return } @@ -11,11 +11,11 @@ fn emit_block(b: Node) -> void { } # store `val` (llvm type `lt`) into address `addr` -fn store_at(lt: ptr, v: ptr, addr: ptr) -> void { +function store_at(lt: ptr, v: ptr, addr: ptr) -> void { emit(" store "); emit(lt); emit(" "); emit(v); emit(", ptr "); emit(addr); emit("\n") } -fn emit_assign(st: Node) -> void { +function emit_assign(st: Node) -> void { # resolve the target's address and type let t = st.a var addr = "0" @@ -53,7 +53,7 @@ fn emit_assign(st: Node) -> void { store_at(lt, v, addr) } -fn emit_if(st: Node) -> void { +function emit_if(st: Node) -> void { let c = emit_expr(st.a) let cc = emit_bind(`icmp ne i32 {c.code}, 0`) let has_else = (st.c != null) @@ -72,7 +72,7 @@ fn emit_if(st: Node) -> void { emit(en); emit(":\n"); g_term = false } -fn emit_while(st: Node) -> void { +function 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") @@ -87,7 +87,7 @@ fn emit_while(st: Node) -> void { emit(en); emit(":\n"); g_term = false } -fn emit_for(st: Node) -> void { +function emit_for(st: Node) -> void { let slot = emit_alloca("i32") let lo = emit_expr(st.a) store_at("i32", lo.code, slot) @@ -112,7 +112,7 @@ fn emit_for(st: Node) -> void { emit(en); emit(":\n"); g_term = false } -fn emit_return(st: Node) -> void { +function emit_return(st: Node) -> void { if (st.a != null) { let v = emit_expr(st.a) store_at(llty(ret_ty), coerce_code(v, ret_ty), "%retval") @@ -121,13 +121,13 @@ fn emit_return(st: Node) -> void { g_term = true } -fn arm_is_default(arm: Node) -> bool { +function arm_is_default(arm: Node) -> bool { var p = 0 while p < len(arm.kids) { if arm.kids[p].kind == E_ID and (arm.kids[p].s == "_") { return true }; p = p + 1 } return false } -fn emit_match(st: Node) -> void { +function emit_match(st: Node) -> void { let sv = emit_expr(st.a) let endl = lbl("mend") var deflt: Node = null @@ -163,7 +163,7 @@ fn emit_match(st: Node) -> void { # emit E(field: v, ...) — evaluate the payload args in the event's declared field # order (so call args line up with the `@ev_` signature), then a direct call. # A missing arg falls back to the field's default; an unset scalar/ptr to 0/null. -fn emit_emit(st: Node) -> Val { +function emit_emit(st: Node) -> Val { let ev = find_event(st.s) if (ev == null) { perr(`emit: unknown event {st.s}`) } # evaluate each payload field in declared order (default for a missing arg) @@ -221,7 +221,7 @@ fn emit_emit(st: Node) -> Val { return val("0", "int") } -fn emit_stmt(st: Node) -> void { +function emit_stmt(st: Node) -> void { if st.kind == S_LET { var ty = st.ty if (ty == null) { let v0 = emit_expr(st.a); ty = v0.ty @@ -261,9 +261,9 @@ fn emit_stmt(st: Node) -> void { perr("cannot emit statement") } -fn loop_push(cont: ptr, brk: ptr) -> void { +function 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) } nloop = nloop + 1 } -fn loop_pop() -> void { nloop = nloop - 1 } +function loop_pop() -> void { nloop = nloop - 1 } diff --git a/selfhost/emit_text.ludic b/selfhost/emit_text.ludic index 19ecf12b..e43129c4 100644 --- a/selfhost/emit_text.ludic +++ b/selfhost/emit_text.ludic @@ -3,7 +3,7 @@ # contains/index_of/to_int) allocate nothing; slice/from_int/equals/concat reuse # the string preludes that the `+`, `s[a..b]` and str(int) operators emit. -fn is_text_ns(meth: ptr) -> bool { +function is_text_ns(meth: ptr) -> bool { if (meth == "length") or (meth == "char_at") or (meth == "slice") { return true } if (meth == "equals") or (meth == "concat") or (meth == "to_int") or (meth == "from_int") { return true } if (meth == "starts_with") or (meth == "ends_with") { return true } @@ -14,7 +14,7 @@ fn is_text_ns(meth: ptr) -> bool { return false } -fn emit_text_ns(meth: ptr, e: Node) -> Val { +function emit_text_ns(meth: ptr, e: Node) -> Val { if (meth == "from_int") { # int -> str, same as str(n) let n = emit_expr(e.kids[0]) g_uses_intstr = true @@ -138,7 +138,7 @@ fn emit_text_ns(meth: ptr, e: Node) -> Val { # emit_text_prelude — string builders that allocate: upper/lower/trim/ # repeat/pad. Emitted once per program that uses them (g_uses_textrt). Plain # libc (strlen/malloc/memcpy), deterministic, C-string in and out. -fn emit_text_prelude() -> void { +function emit_text_prelude() -> void { emith("define ptr @fn_str_upper(ptr %s) {\n") emith("entry:\n") emith(" %n = call i64 @strlen(ptr %s)\n") @@ -343,7 +343,7 @@ fn emit_text_prelude() -> void { # replace, join (over a []str), split (returns a []str). Emitted once per # program that uses them (g_uses_textrt2). Slices are the {data,len,cap} # %LSlice with str (ptr) elements. -fn emit_text2_prelude() -> void { +function emit_text2_prelude() -> void { emith("define ptr @fn_str_replace(ptr %s, ptr %from, ptr %to) {\n") emith("entry:\n") emith(" %lf = call i64 @strlen(ptr %from)\n") diff --git a/selfhost/emit_time.ludic b/selfhost/emit_time.ludic index 21c13f3a..19fa1af3 100644 --- a/selfhost/emit_time.ludic +++ b/selfhost/emit_time.ludic @@ -3,12 +3,12 @@ # the wall clock and is explicitly non-deterministic. The frame clock ticks at a # fixed 60 per second, so delta is the constant 1/60 s = 1092 in Q16.16. -fn is_time_ns(meth: ptr) -> bool { +function is_time_ns(meth: ptr) -> bool { if (meth == "frame") or (meth == "delta") or (meth == "elapsed") or (meth == "now") { return true } return false } -fn emit_time_ns(meth: ptr, e: Node) -> Val { +function emit_time_ns(meth: ptr, e: Node) -> Val { if (meth == "frame") { # completed frames since start return val(emit_bind("load i32, ptr @L_frame"), "int") } diff --git a/selfhost/emit_ui.ludic b/selfhost/emit_ui.ludic index bc5bd888..553b5b23 100644 --- a/selfhost/emit_ui.ludic +++ b/selfhost/emit_ui.ludic @@ -7,25 +7,25 @@ var uiw: []Node # flattened widgets, pre-order var uiw_parent: []int var ui_roots: []int # first-widget index of each ui block -fn ui_wtype(w: Node) -> int { +function ui_wtype(w: Node) -> int { let s = w.s if (s == "panel") { return 0 }; if (s == "col") { return 1 }; if (s == "row") { return 2 } if (s == "label") { return 3 }; if (s == "button") { return 4 } if (s == "image") { return 5 }; if (s == "spacer") { return 6 } return 0 } -fn ui_prop(w: Node, key: ptr) -> Node { +function ui_prop(w: Node, key: ptr) -> Node { var i = 0 while i < len(w.b.kids) { if (w.b.kids[i].s == key) { return w.b.kids[i].a }; i = i + 1 } return null } -fn ui_flatten(w: Node, parent: int) -> void { +function ui_flatten(w: Node, parent: int) -> void { let idx = len(uiw) push(uiw, w); push(uiw_parent, parent) var i = 0 while i < len(w.kids) { ui_flatten(w.kids[i], idx); i = i + 1 } } -fn ui_flatten_all() -> void { +function ui_flatten_all() -> void { uiw = new []Node; uiw_parent = new []int; ui_roots = new []int var i = 0 while i < len(prog) { @@ -33,14 +33,14 @@ fn ui_flatten_all() -> void { i = i + 1 } } -fn has_ui() -> bool { +function has_ui() -> bool { var i = 0 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_: a ui block's root, or a widget's id= -fn ui_index_of(nm: ptr) -> int { +function ui_index_of(nm: ptr) -> int { let s = nm[3..len(nm)] # strip "UI_" let u = 0; var bi = 0 var i = 0 @@ -56,14 +56,14 @@ fn ui_index_of(nm: ptr) -> int { } return 0 } -fn is_ui_ident(nm: ptr) -> bool { +function is_ui_ident(nm: ptr) -> bool { return len(nm) > 3 and nm[0] == 85 and nm[1] == 73 and nm[2] == 95 # "UI_" } -fn ll_ui_set(idx: int, key: int, val: ptr) -> void { +function 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") } -fn ui_prop_key(k: ptr) -> int { +function ui_prop_key(k: ptr) -> int { if (k == "w") { return 2 }; if (k == "h") { return 3 }; if (k == "x") { return 4 }; if (k == "y") { return 5 } if (k == "pad") { return 7 }; if (k == "gap") { return 8 }; if (k == "bg") { return 9 }; if (k == "fg") { return 10 } if (k == "border") { return 11 }; if (k == "grow") { return 13 }; if (k == "font") { return 14 }; if (k == "size") { return 15 } @@ -71,7 +71,7 @@ fn ui_prop_key(k: ptr) -> int { return 0 - 1 } -fn emit_ui_build() -> void { +function emit_ui_build() -> void { ui_flatten_all() ll_t = 0; ll_lbl = 0; g_term = false; loc_reset(); nloop = 0 let fbody = buf_new() diff --git a/selfhost/io.ludic b/selfhost/io.ludic index aa9658f1..33097b79 100644 --- a/selfhost/io.ludic +++ b/selfhost/io.ludic @@ -1,7 +1,7 @@ # io.ludic — reading the input file and writing the output, plus tiny stdio. # The compiler reads one .ludic file whole and emits LLVM IR text to stdout. -fn read_file(path: str) -> ptr { +function read_file(path: str) -> ptr { let f = file_open(path, "rb") if (f == null) { return null } file_seek(f, 0, 2) @@ -15,7 +15,7 @@ fn read_file(path: str) -> ptr { } # length of a NUL-terminated buffer -fn cstr_len(s: ptr) -> int { +function cstr_len(s: ptr) -> int { var n = 0 while s[n] != 0 { n = n + 1 } return n diff --git a/selfhost/lex.ludic b/selfhost/lex.ludic index 64abb805..452cb33d 100644 --- a/selfhost/lex.ludic +++ b/selfhost/lex.ludic @@ -15,18 +15,18 @@ property Tok { kind: int = 0, text: ptr = null, ival: int = 0, line: int = 0 } var toks: []Tok -fn tok_push(kind: int, text: ptr, ival: int, line: int) -> void { +function 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 push(toks, t) } # does src match the 2-char operator op at position i? -fn two_at(src: ptr, i: int, a: int, b: int) -> bool { +function two_at(src: ptr, i: int, a: int, b: int) -> bool { return src[i] == a and src[i + 1] == b } -fn is_op1(c: int) -> bool { +function is_op1(c: int) -> bool { # + - * / % < > = ( ) { } [ ] , : . ! @ if c == 43 or c == 45 or c == 42 or c == 47 or c == 37 { return true } if c == 60 or c == 62 or c == 61 { return true } @@ -37,7 +37,7 @@ fn is_op1(c: int) -> bool { return false } -fn lex(src: ptr) -> void { +function lex(src: ptr) -> void { toks = new []Tok var i = 0 var line = 1 diff --git a/selfhost/ludicc.seed.ll b/selfhost/ludicc.seed.ll index 0d2a8892..12266175 100644 --- a/selfhost/ludicc.seed.ll +++ b/selfhost/ludicc.seed.ll @@ -301,7 +301,7 @@ declare void @win_close() @.str200 = private unnamed_addr constant [3 x i8] c"ui\00" @.str201 = private unnamed_addr constant [4 x i8] c"var\00" @.str202 = private unnamed_addr constant [6 x i8] c"const\00" -@.str203 = private unnamed_addr constant [3 x i8] c"fn\00" +@.str203 = private unnamed_addr constant [9 x i8] c"function\00" @.str204 = private unnamed_addr constant [7 x i8] c"extern\00" @.str205 = private unnamed_addr constant [6 x i8] c"entry\00" @.str206 = private unnamed_addr constant [21 x i8] c"expected declaration\00" diff --git a/selfhost/main.ludic b/selfhost/main.ludic index e61bd3e7..fd1283ef 100644 --- a/selfhost/main.ludic +++ b/selfhost/main.ludic @@ -15,7 +15,7 @@ # reseed.sh rely on, so the bootstrap is untouched. # basename: the part of a path after the last '/'. -fn base_name(path: ptr) -> ptr { +function base_name(path: ptr) -> ptr { var last = 0 - 1 var i = 0 while path[i] != 0 { if path[i] == 47 { last = i }; i = i + 1 } @@ -23,7 +23,7 @@ fn base_name(path: ptr) -> ptr { } # drop a trailing ".ludic" if present -fn strip_ludic(name: ptr) -> ptr { +function strip_ludic(name: ptr) -> ptr { let n = len(name) if n > 6 { if (name[n - 6..n] == ".ludic") { return name[0..n - 6] } @@ -32,21 +32,21 @@ fn strip_ludic(name: ptr) -> ptr { } # env var with a fallback when unset -fn getenv_or(name: ptr, dflt: ptr) -> ptr { +function getenv_or(name: ptr, dflt: ptr) -> ptr { let v = getenv(name) if (v == null) { return dflt } return v } # guarantee a directory string ends in '/' so path_join concatenates cleanly -fn ensure_slash(d: ptr) -> ptr { +function ensure_slash(d: ptr) -> ptr { let n = len(d) if n == 0 { return d } if d[n - 1] == 47 { return d } return (d + ("/")) } -fn die(msg: ptr) -> void { +function die(msg: ptr) -> void { file_write(file_stderr(), msg, len(msg)) exit(1) } diff --git a/selfhost/parse.ludic b/selfhost/parse.ludic index 14593057..34d62317 100644 --- a/selfhost/parse.ludic +++ b/selfhost/parse.ludic @@ -6,13 +6,13 @@ var pi: int = 0 var prog: []Node # the top-level declarations 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 (t.text == v) } -fn is_id(v: ptr) -> bool { let t = toks[pi]; return t.kind == TK_ID and (t.text == v) } -fn is_kw(v: ptr) -> bool { return is_id(v) } +function cur() -> Tok { return toks[pi] } +function pk(o: int) -> Tok { return toks[pi + o] } +function is_op(v: ptr) -> bool { let t = toks[pi]; return t.kind == TK_OP and (t.text == v) } +function is_id(v: ptr) -> bool { let t = toks[pi]; return t.kind == TK_ID and (t.text == v) } +function is_kw(v: ptr) -> bool { return is_id(v) } -fn perr(msg: ptr) -> void { +function perr(msg: ptr) -> void { let e = file_stderr() file_write(e, "ludicc(self): parse error: ", 27) file_write(e, msg, len(msg)) @@ -20,17 +20,17 @@ fn perr(msg: ptr) -> void { exit(1) } -fn eat_op(v: ptr) -> void { if not is_op(v) { perr(v) }; pi = pi + 1 } -fn eat_id() -> ptr { +function eat_op(v: ptr) -> void { if not is_op(v) { perr(v) }; pi = pi + 1 } +function eat_id() -> ptr { let t = toks[pi] if t.kind != TK_ID { perr("expected identifier") } pi = pi + 1 return t.text } -fn skipnl() -> void { while toks[pi].kind == TK_NL { pi = pi + 1 } } +function skipnl() -> void { while toks[pi].kind == TK_NL { pi = pi + 1 } } # a type: `[]T` slice, or a plain name (int/ptr/str/bool/struct) -fn ptype() -> ptr { +function ptype() -> ptr { if is_op("[") { pi = pi + 1 eat_op("]") @@ -46,13 +46,13 @@ fn ptype() -> ptr { } # ---- expressions ----------------------------------------------------------- -fn expr() -> Node { return p_or() } +function expr() -> Node { return p_or() } # Call arguments. Each argument is either positional (`expr`) or named # (`name: expr`) — a named argument is an identifier immediately followed by a # colon, which is unambiguous inside a call. Named args are stored as E_FINIT # (s=label, a=value) and reordered to the callee's parameter order at emit time. -fn args_call(call: Node) -> void { +function args_call(call: Node) -> void { eat_op("("); skipnl() while not is_op(")") { let t = toks[pi] @@ -70,22 +70,22 @@ fn args_call(call: Node) -> void { # ---- string interpolation -------------------------------------------------- # `text {expr} text` desugars to a `+` chain of string literals and `str(expr)` # holes, so it reuses the string-concat operator and needs no new runtime. -fn interp_lit(buf: ptr, len: int) -> Node { let n = node(E_STR); n.s = buf[0..0 + len]; return n } -fn interp_add(acc: Node, part: Node) -> Node { +function interp_lit(buf: ptr, len: int) -> Node { let n = node(E_STR); n.s = buf[0..0 + len]; return n } +function interp_add(acc: Node, part: Node) -> Node { if acc == null { return part } return mkbin("+", acc, part) } -fn interp_str(e: Node) -> Node { # wrap a hole in str(...) +function interp_str(e: Node) -> Node { # wrap a hole in str(...) let c = node(E_CALL); let id = node(E_ID); id.s = "str"; c.a = id; push(c.kids, e); return c } -fn parse_hole(inner: ptr) -> Node { # re-lex+parse an embedded expression +function parse_hole(inner: ptr) -> Node { # re-lex+parse an embedded expression let saved_toks = toks; let saved_pi = pi lex(inner); pi = 0; skipnl() let e = expr() toks = saved_toks; pi = saved_pi return e } -fn parse_interp(raw: ptr) -> Node { +function parse_interp(raw: ptr) -> Node { let n = len(raw) var acc: Node = null let lit = bytes(n + 1) @@ -125,7 +125,7 @@ fn parse_interp(raw: ptr) -> Node { # emit E(field: v, ...) — shared by the statement form and the expression form. # As an expression it yields a cancellable event's cancelled flag (0/1); a # non-cancellable event yields 0. -fn parse_emit() -> Node { +function parse_emit() -> Node { pi = pi + 1; let n = node(S_EMIT); n.s = eat_id() let r = node(E_REC) eat_op("("); skipnl() @@ -138,7 +138,7 @@ fn parse_emit() -> Node { return n } -fn p_primary() -> Node { +function p_primary() -> Node { let t = toks[pi] if t.kind == TK_INTERP { pi = pi + 1; return parse_interp(t.text) } if t.kind == TK_ID and (t.text == "emit") and (toks[pi + 1].kind == TK_ID) { return parse_emit() } @@ -157,7 +157,7 @@ fn p_primary() -> Node { return node(E_INT) } -fn p_postfix() -> Node { +function p_postfix() -> Node { var 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 } @@ -169,42 +169,42 @@ fn p_postfix() -> Node { return e } -fn p_unary() -> Node { +function p_unary() -> Node { if is_op("-") { pi = pi + 1; let n = node(E_UN); n.s = "-"; n.a = p_unary(); return n } if is_op("~") { pi = pi + 1; let n = node(E_UN); n.s = "~"; n.a = p_unary(); return n } # bitwise not 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 } +function mkbin(op: ptr, l: Node, r: Node) -> Node { let b = node(E_BIN); b.s = op; b.a = l; b.b = r; return b } # precedence (Go-style, so `flags & MASK == 0` needs no parens): shifts and `&` # bind like `*`; `|` and `^` bind like `+`; both tighter than comparison. -fn p_mul() -> Node { +function p_mul() -> Node { var l = p_unary() 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_unary()) } return l } -fn p_add() -> Node { +function p_add() -> Node { var l = p_mul() while is_op("+") or is_op("-") or 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 { +function p_cmp() -> Node { var 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()) } return l } -fn p_and() -> Node { +function p_and() -> Node { var l = p_cmp() while is_id("and") { pi = pi + 1; l = mkbin("and", l, p_cmp()) } return l } -fn p_or() -> Node { +function p_or() -> Node { var l = p_and() while is_id("or") { pi = pi + 1; l = mkbin("or", l, p_and()) } return l @@ -212,7 +212,7 @@ fn p_or() -> Node { # a record literal `{ field: value, ... }` — used by spawn component inits. # Rule A: a named part uses `:` (`=` is assignment/binding only). -fn record() -> Node { +function record() -> Node { eat_op("{") let r = node(E_REC) while true { skipnl(); if is_op("}") { break } @@ -222,7 +222,7 @@ fn record() -> Node { } # ---- statements ------------------------------------------------------------ -fn block() -> Node { +function block() -> Node { skipnl(); eat_op("{") let b = node(N_BLOCK) while true { skipnl(); if is_op("}") { break } @@ -238,7 +238,7 @@ fn block() -> Node { return b } -fn stmt() -> Node { +function stmt() -> Node { let t = toks[pi] if t.kind == TK_ID { if (t.text == "let") or (t.text == "var") { @@ -333,16 +333,16 @@ fn stmt() -> Node { } # ---- declarations ---------------------------------------------------------- -fn parse_var() -> Node { +function 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() } return n } -fn parse_const() -> Node { +function 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() return n } -fn parse_fn() -> Node { +function parse_fn() -> Node { 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) @@ -354,17 +354,17 @@ fn parse_fn() -> Node { n.a = block() return n } -fn parse_main() -> Node { pi = pi + 1; let n = node(N_MAIN); n.a = block(); return n } +function 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 { +function dir_of(path: ptr) -> ptr { var last = 0 - 1 var i = 0 while path[i] != 0 { if path[i] == 47 { last = i }; i = i + 1 } if last < 0 { return "" } return path[0..0 + (last + 1)] } -fn path_join(dir: ptr, rel: ptr) -> ptr { +function path_join(dir: ptr, rel: ptr) -> ptr { if rel[0] == 47 { return rel } # absolute return (dir + rel) } @@ -372,7 +372,7 @@ fn path_join(dir: ptr, rel: ptr) -> ptr { var loaded_paths: []ptr var cur_dir: ptr -fn already_loaded(full: ptr) -> bool { +function already_loaded(full: ptr) -> bool { var i = 0 while i < len(loaded_paths) { if (loaded_paths[i] == full) { return true }; i = i + 1 } return false @@ -381,7 +381,7 @@ fn already_loaded(full: ptr) -> bool { # 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 { +function parse_one_decl() -> void { var is_export = false var qspec: Node = null var onspawn_model: ptr = null @@ -482,14 +482,14 @@ fn parse_one_decl() -> void { 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("function") { 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("entry") { push(prog, parse_main()); return } perr("expected declaration") } # lex and parse an imported fragment into `prog`, saving/restoring lexer state -fn do_import(rel: ptr) -> void { +function do_import(rel: ptr) -> void { let full = path_join(cur_dir, rel) if already_loaded(full) { return } push(loaded_paths, full) @@ -506,7 +506,7 @@ fn do_import(rel: ptr) -> void { # a game (has systems/components) links the Ludic runtime; auto-splice it the # way the C compiler does. Tools (a `main` block, no ECS) get nothing. -fn maybe_splice_runtime() -> void { +function maybe_splice_runtime() -> void { if not has_ecs() { return } let saved = cur_dir cur_dir = "" @@ -514,7 +514,7 @@ fn maybe_splice_runtime() -> void { cur_dir = saved } -fn parse_program() -> void { +function parse_program() -> void { prog = new []Node g_computed = new []Node g_onspawn = new []Node diff --git a/selfhost/parse_game.ludic b/selfhost/parse_game.ludic index f654f40f..6fa90db2 100644 --- a/selfhost/parse_game.ludic +++ b/selfhost/parse_game.ludic @@ -2,7 +2,7 @@ # `for (vars) in query [terms] where cond`, spawn and despawn. Mirrors the # game-construct parsing in compiler/front/parse.c. -fn parse_component() -> Node { +function 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 } var is_computed = false @@ -20,7 +20,7 @@ fn parse_component() -> Node { # event Name { field: T = default, ... } — a public event's POD payload. Same # field grammar as a `property`, but stored in g_events, not prog: an event is a # signal shape, not per-entity storage. Zero fields is allowed (`event Ping {}`). -fn parse_event() -> Node { +function parse_event() -> Node { pi = pi + 1; let n = node(N_EVENT) if is_id("cancellable") { pi = pi + 1; n.ival = 1 } # a decision event: listeners may `cancel` it n.s = eat_id(); skipnl(); eat_op("{") @@ -32,7 +32,7 @@ fn parse_event() -> Node { eat_op("}"); return n } -fn parse_system() -> Node { +function parse_system() -> Node { pi = pi + 1; let n = node(N_SYS); n.s = eat_id(); n.ty = "Update" # postfix clauses on `handler Name …`: @anno(...) (parsed and reserved, e.g. # @deterministic / @Reads(...) / @Writes(...)) and `phase X`. The handler's @@ -53,7 +53,7 @@ fn parse_system() -> Node { # `[Term, ...]` with optional `where `, returning a node whose kids are # the terms (E_ID with ival=1 for {Tag} filters) and .a the where-expr or null. -fn parse_query_tail() -> Node { +function parse_query_tail() -> Node { eat_op("[") let q = node(N_BLOCK) while not is_op("]") { @@ -67,7 +67,7 @@ fn parse_query_tail() -> Node { } # `for (a, b) in query [Pos, Vel] where ... { body }` -fn parse_query_for() -> Node { +function 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 } } @@ -87,7 +87,7 @@ fn parse_query_for() -> Node { # where { body }` loop. It desugars to the same S_QUERY node, so # the whole query backend (iteration, filters, binding, break/continue) is reused. -fn mk_and(a: Node, b: Node) -> Node { +function mk_and(a: Node, b: Node) -> Node { if (a == null) { return b } let n = node(E_BIN); n.s = "and"; n.a = a; n.b = b; return n } @@ -96,7 +96,7 @@ fn mk_and(a: Node, b: Node) -> Node { # `Prop{constraint}` (base is the property binding) and by @Computed field # expansion (base is the accessed value). Non-destructive: builds a fresh tree, # so a stored computed expression can be expanded at many access sites. -fn qualify_fields(e: Node, base: Node) -> Node { +function qualify_fields(e: Node, base: Node) -> Node { if (e == null) { return e } if e.kind == E_ID { let m = node(E_MEMBER); m.a = base; m.s = e.s; return m @@ -112,7 +112,7 @@ fn qualify_fields(e: Node, base: Node) -> Node { # parse `(these: [...], on: Model)`, returning an S_QUERY with its vars/terms/where # filled in (the body `.a` is attached by the caller once the handler is parsed). -fn parse_queries_anno() -> Node { +function parse_queries_anno() -> Node { eat_op("(") let qn = node(S_QUERY) let terms = node(N_BLOCK) @@ -144,7 +144,7 @@ fn parse_queries_anno() -> Node { return qn } -fn parse_spawn() -> Node { +function parse_spawn() -> Node { pi = pi + 1; let n = node(S_SPAWN); n.s = eat_id(); skipnl(); eat_op("{") while true { skipnl(); if is_op("}") { break } @@ -162,7 +162,7 @@ fn parse_spawn() -> Node { # blocks (scene .a/.b); each layer's handlers are pushed straight into `prog` as # ordinary N_SYS nodes, tagged with the owning scene in `.c`, so the whole # system backend (functions, phases, enable/disable) is reused unchanged. -fn parse_scene() -> void { +function parse_scene() -> void { pi = pi + 1 # 'scene' let n = node(N_SCENE); n.s = eat_id() n.ival = g_scene_count @@ -210,7 +210,7 @@ fn parse_scene() -> void { # 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 { +function 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) @@ -223,7 +223,7 @@ fn parse_enum() -> Node { # N2): its @Sync-marked fields cross the wire for this model. Participation is # per model use-site — the same property syncs in one model, not another. The # per-member @Sync sets the member E_ID's ival=1 (read by emit_net). -fn parse_archetype() -> Node { +function 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) @@ -233,8 +233,8 @@ fn parse_archetype() -> Node { eat_op("}"); return n } -# extern fn name(params) -> T = "symbol" -fn parse_extern() -> Node { +# extern function name(params) -> T = "symbol" +function parse_extern() -> Node { pi = pi + 1 # 'extern' let fnkw = eat_id() # 'fn' let n = node(N_EXTERN); n.s = eat_id(); eat_op("(") @@ -250,7 +250,7 @@ fn parse_extern() -> Node { } # ui Name { widget-tree } — parsed into a widget node tree (emitted later) -fn parse_widget() -> Node { +function parse_widget() -> Node { 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 (toks[pi + 1].text == ":") { @@ -262,7 +262,7 @@ fn parse_widget() -> Node { eat_op("}") } return w } -fn parse_ui() -> Node { +function 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() n.a = parse_widget() diff --git a/selfhost/str.ludic b/selfhost/str.ludic index 0093cdc8..d7a6f07c 100644 --- a/selfhost/str.ludic +++ b/selfhost/str.ludic @@ -4,16 +4,16 @@ # a fresh NUL-terminated copy of src[start .. start+n] -fn char_is_digit(c: int) -> bool { return c >= 48 and c <= 57 } -fn char_is_alpha(c: int) -> bool { +function char_is_digit(c: int) -> bool { return c >= 48 and c <= 57 } +function char_is_alpha(c: int) -> bool { if c >= 65 and c <= 90 { return true } if c >= 97 and c <= 122 { return true } return c == 95 } -fn char_is_alnum(c: int) -> bool { return char_is_alpha(c) or char_is_digit(c) } +function 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 { +function itoa(v: int) -> ptr { if v == 0 { let z = bytes(2); z[0] = 48; z[1] = 0; return z } var neg = false var x = v diff --git a/selfhost/tests/control.ludic b/selfhost/tests/control.ludic index e3716ed6..5fa067c7 100644 --- a/selfhost/tests/control.ludic +++ b/selfhost/tests/control.ludic @@ -1,5 +1,5 @@ program T { - fn fib(n: int) -> int { if n < 2 { return n }; return fib(n-1) + fib(n-2) } + function fib(n: int) -> int { if n < 2 { return n }; return fib(n-1) + fib(n-2) } entry { print(fib(10)) # 55 var s = 0 diff --git a/selfhost/tests/long.ludic b/selfhost/tests/long.ludic index 90a2f586..e528c7c2 100644 --- a/selfhost/tests/long.ludic +++ b/selfhost/tests/long.ludic @@ -1,5 +1,5 @@ program T { - fn wide(a: long, b: int) -> long { return a * b } # int arg widens to i64 + function wide(a: long, b: int) -> long { return a * b } # int arg widens to i64 entry { let big: long = 1000000 # int literal widens to a long diff --git a/selfhost/tests/match_bits.ludic b/selfhost/tests/match_bits.ludic index 196e8da3..a7280a68 100644 --- a/selfhost/tests/match_bits.ludic +++ b/selfhost/tests/match_bits.ludic @@ -1,5 +1,5 @@ program T { - fn classify(c: int) -> int { + function classify(c: int) -> int { match c { 65, 66 => { return 1 }; 67 => { return 2 }; _ => { return 9 } } return 0 } diff --git a/selfhost/tests/structs.ludic b/selfhost/tests/structs.ludic index c1e2b3e0..260e75ee 100644 --- a/selfhost/tests/structs.ludic +++ b/selfhost/tests/structs.ludic @@ -1,6 +1,6 @@ program T { property P { x: int = 0, y: int = 7, next: P } - fn bump(p: P) -> void { p.x = p.x + 100 } + function bump(p: P) -> void { p.x = p.x + 100 } entry { let a = new P print(a.y) # 7 default diff --git a/tools/atlas.ludic b/tools/atlas.ludic index 906743fd..5965ed92 100644 --- a/tools/atlas.ludic +++ b/tools/atlas.ludic @@ -34,7 +34,7 @@ game Atlas { # build "

/tile_%04d.png" without a formatter: the runtime's string # helpers write into a scratch buffer we own - fn tile_path(i: int) -> str { + function tile_path(i: int) -> str { let buf = atlas_buf() let n = rt_put_str(buf, 0, DIR) n = rt_put_str(buf, n, "/tile_") @@ -49,7 +49,7 @@ game Atlas { var atlas_scratch: ptr = ptr_null() - fn atlas_buf() -> ptr { + function atlas_buf() -> ptr { if ptr_is_null(atlas_scratch) { atlas_scratch = mem_alloc(512) } return atlas_scratch } diff --git a/tools/check-docs.py b/tools/check-docs.py index 166e3d74..156154f6 100755 --- a/tools/check-docs.py +++ b/tools/check-docs.py @@ -19,7 +19,7 @@ A bare fragment is wrapped first: a declaration list goes inside import re, subprocess, sys, os, tempfile LC = './build/ludicc' -DECL = ('program','property','model','handler','enum','fn', +DECL = ('program','property','model','handler','enum','function', 'extern','const','var','ui','struct','import') def classify(body): diff --git a/tools/docgen/check-impl.py b/tools/docgen/check-impl.py index 8fb52a96..39a33b5b 100644 --- a/tools/docgen/check-impl.py +++ b/tools/docgen/check-impl.py @@ -33,12 +33,12 @@ def read(*parts): def fn_body(text, name): - """The source of `fn ` up to the next top-level `fn ` (or EOF).""" - m = re.search(r"^fn %s\b" % re.escape(name), text, re.M) + """The source of `function ` up to the next top-level `function ` (or EOF).""" + m = re.search(r"^function %s\b" % re.escape(name), text, re.M) if not m: return "" rest = text[m.end():] - nxt = re.search(r"^fn ", rest, re.M) + nxt = re.search(r"^function ", rest, re.M) return text[m.start(): m.end() + (nxt.start() if nxt else len(rest))] diff --git a/tools/docgen/inventory.json b/tools/docgen/inventory.json index 394b8663..3fe7c0a4 100644 --- a/tools/docgen/inventory.json +++ b/tools/docgen/inventory.json @@ -266,7 +266,7 @@ "kw-const", "kw-var", "kw-let", - "kw-fn", + "kw-function", "kw-return", "kw-import", "kw-extern", diff --git a/tools/docgen/palette.py b/tools/docgen/palette.py index be10622a..ca0eadd6 100644 --- a/tools/docgen/palette.py +++ b/tools/docgen/palette.py @@ -269,7 +269,7 @@ def emit_ludic(path): lines.append("# Edit the palette there and regenerate; do not hand-edit this file.") lines.append("# ============================================================================") lines.append("") - lines.append("fn color_lookup(name: ptr) -> int {") + lines.append("function color_lookup(name: ptr) -> int {") for (nm, hexv, grp) in PALETTE: lines.append(f' if (name == "{nm}") {{ return 0x{hexv:06X} }}') lines.append(" return -1") diff --git a/tools/docgen/validate.py b/tools/docgen/validate.py index 16502257..accb63fb 100644 --- a/tools/docgen/validate.py +++ b/tools/docgen/validate.py @@ -5,7 +5,7 @@ Full programs (start with program/module) compile directly; fragments are wrappe import os, re, subprocess, tempfile, sys REPO=os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))); LC=os.path.join(REPO,"bin","ludicc") FENCE=re.compile(r"```ludic\n(.*?)\n```", re.S) -DECL=("program","property","model","handler","enum","fn","extern","const","var","ui","event","module","import","scene","machine") +DECL=("program","property","model","handler","enum","function","extern","const","var","ui","event","module","import","scene","machine") def first_kw(body): for l in body.split("\n"): diff --git a/tools/editors/jetbrains/src/main/kotlin/io/ludic/ide/LudicTokens.kt b/tools/editors/jetbrains/src/main/kotlin/io/ludic/ide/LudicTokens.kt index 82974bf0..9d9dd775 100644 --- a/tools/editors/jetbrains/src/main/kotlin/io/ludic/ide/LudicTokens.kt +++ b/tools/editors/jetbrains/src/main/kotlin/io/ludic/ide/LudicTokens.kt @@ -20,7 +20,7 @@ object LudicTokens { val IDENTIFIER = LudicTokenType("IDENTIFIER") val TYPE_NAME = LudicTokenType("TYPE_NAME") // Capitalised: component, archetype, scene val KEYWORD = LudicTokenType("KEYWORD") - val DECL_KEYWORD = LudicTokenType("DECL_KEYWORD") // game, component, system, fn, ... + val DECL_KEYWORD = LudicTokenType("DECL_KEYWORD") // game, component, system, function, ... val CLAUSE = LudicTokenType("CLAUSE") // phase, query, reads, writes, ... val PRIMITIVE = LudicTokenType("PRIMITIVE") // int, fixed, bool, entity, str, ptr, void val PHASE = LudicTokenType("PHASE") // Start, Update, Render, ... @@ -42,7 +42,7 @@ object LudicTokens { object LudicVocabulary { val DECL = setOf( "program", "import", "property", "model", "enum", "ui", - "const", "var", "fn", "extern", "handler", "entry", "event", "scene" + "const", "var", "function", "extern", "handler", "entry", "event", "scene" ) val CLAUSE = setOf( "phase", "query", "on", "cancellable", "public", "layer", "start" diff --git a/tools/editors/shared/ludic.tmLanguage.json b/tools/editors/shared/ludic.tmLanguage.json index 4b8a0c8f..563c08f6 100644 --- a/tools/editors/shared/ludic.tmLanguage.json +++ b/tools/editors/shared/ludic.tmLanguage.json @@ -100,7 +100,7 @@ } }, { - "match": "\\b(extern\\s+)?(fn)\\s+([A-Za-z_][A-Za-z0-9_]*)", + "match": "\\b(extern\\s+)?(function)\\s+([A-Za-z_][A-Za-z0-9_]*)", "captures": { "1": { "name": "storage.modifier.extern.ludic" }, "2": { "name": "storage.type.function.ludic" }, @@ -176,7 +176,7 @@ { "name": "keyword.operator.logical.ludic", "match": "\\b(and|or|not)\\b" }, { "name": "keyword.other.clause.ludic", "match": "\\b(phase|query|on)\\b" }, { "name": "keyword.other.ludic", "match": "\\b(import|extern)\\b" }, - { "name": "storage.type.ludic", "match": "\\b(program|property|model|enum|ui|const|var|let|fn|handler|entry|state)\\b" }, + { "name": "storage.type.ludic", "match": "\\b(program|property|model|enum|ui|const|var|let|function|handler|entry|state)\\b" }, { "name": "support.type.primitive.ludic", "match": "\\b(int|long|fixed|bool|entity|str|ptr|byte|words|fixeds|ptrs|void)\\b" }, { "name": "constant.language.boolean.ludic", "match": "\\b(true|false|null)\\b" }, { "name": "constant.language.phase.ludic", "match": "\\b(Start|Input|FixedUpdate|Update|LateUpdate|Render)\\b" } diff --git a/tools/editors/vscode/syntaxes/ludic.tmLanguage.json b/tools/editors/vscode/syntaxes/ludic.tmLanguage.json index 4b8a0c8f..563c08f6 100644 --- a/tools/editors/vscode/syntaxes/ludic.tmLanguage.json +++ b/tools/editors/vscode/syntaxes/ludic.tmLanguage.json @@ -100,7 +100,7 @@ } }, { - "match": "\\b(extern\\s+)?(fn)\\s+([A-Za-z_][A-Za-z0-9_]*)", + "match": "\\b(extern\\s+)?(function)\\s+([A-Za-z_][A-Za-z0-9_]*)", "captures": { "1": { "name": "storage.modifier.extern.ludic" }, "2": { "name": "storage.type.function.ludic" }, @@ -176,7 +176,7 @@ { "name": "keyword.operator.logical.ludic", "match": "\\b(and|or|not)\\b" }, { "name": "keyword.other.clause.ludic", "match": "\\b(phase|query|on)\\b" }, { "name": "keyword.other.ludic", "match": "\\b(import|extern)\\b" }, - { "name": "storage.type.ludic", "match": "\\b(program|property|model|enum|ui|const|var|let|fn|handler|entry|state)\\b" }, + { "name": "storage.type.ludic", "match": "\\b(program|property|model|enum|ui|const|var|let|function|handler|entry|state)\\b" }, { "name": "support.type.primitive.ludic", "match": "\\b(int|long|fixed|bool|entity|str|ptr|byte|words|fixeds|ptrs|void)\\b" }, { "name": "constant.language.boolean.ludic", "match": "\\b(true|false|null)\\b" }, { "name": "constant.language.phase.ludic", "match": "\\b(Start|Input|FixedUpdate|Update|LateUpdate|Render)\\b" } diff --git a/tools/ludic-tools/fmt.ludic b/tools/ludic-tools/fmt.ludic index ab2c7fa4..9b3ac0ad 100644 --- a/tools/ludic-tools/fmt.ludic +++ b/tools/ludic-tools/fmt.ludic @@ -28,7 +28,7 @@ program LudicFmt { const LT_ERR: int = 14 # ---- tiny stdio + string helpers (self-contained) ---- - fn read_file(path: str) -> ptr { + function read_file(path: str) -> ptr { let f = file_open(path, "rb") if (f == null) { return null } file_seek(f, 0, 2) @@ -40,16 +40,16 @@ program LudicFmt { file_close(f) return buf } - fn cstr_len(s: ptr) -> int { var n = 0; while s[n] != 0 { n = n + 1 }; return n } - fn char_is_digit(c: int) -> bool { return c >= 48 and c <= 57 } - fn char_is_alpha(c: int) -> bool { + function cstr_len(s: ptr) -> int { var n = 0; while s[n] != 0 { n = n + 1 }; return n } + function char_is_digit(c: int) -> bool { return c >= 48 and c <= 57 } + function char_is_alpha(c: int) -> bool { if c >= 65 and c <= 90 { return true } if c >= 97 and c <= 122 { return true } return c == 95 } - fn char_is_alnum(c: int) -> bool { return char_is_alpha(c) or char_is_digit(c) } - fn char_is_hex(c: int) -> bool { return char_is_digit(c) or (c >= 97 and c <= 102) or (c >= 65 and c <= 70) } - fn itoa(v: int) -> ptr { + function char_is_alnum(c: int) -> bool { return char_is_alpha(c) or char_is_digit(c) } + function char_is_hex(c: int) -> bool { return char_is_digit(c) or (c >= 97 and c <= 102) or (c >= 65 and c <= 70) } + function itoa(v: int) -> ptr { if v == 0 { let z = bytes(2); z[0] = 48; z[1] = 0; return z } var neg = false; var x = v if x < 0 { neg = true; x = 0 - x } @@ -67,18 +67,18 @@ program LudicFmt { # ---- a growable byte buffer ---- property Buf { data: ptr = null, len: int = 0, cap: int = 0 } - fn buf_new() -> Buf { let b = new Buf; b.cap = 256; b.data = bytes(b.cap); b.len = 0; return b } - fn buf_ensure(b: Buf, extra: int) -> void { + function buf_new() -> Buf { let b = new Buf; b.cap = 256; b.data = bytes(b.cap); b.len = 0; return b } + function buf_ensure(b: Buf, extra: int) -> void { if b.len + extra + 1 <= b.cap { return } while b.len + extra + 1 > b.cap { b.cap = b.cap * 2 } b.data = resize(b.data, b.cap) } - fn buf_putc(b: Buf, c: int) -> void { buf_ensure(b, 1); b.data[b.len] = c; b.len = b.len + 1 } - fn buf_puts(b: Buf, s: ptr) -> void { var i = 0; while s[i] != 0 { buf_putc(b, s[i]); i = i + 1 } } - fn buf_indent(b: Buf, n: int) -> void { var i = 0; while i < n { buf_putc(b, 32); i = i + 1 } } - fn buf_str(b: Buf) -> ptr { b.data[b.len] = 0; return b.data } + function buf_putc(b: Buf, c: int) -> void { buf_ensure(b, 1); b.data[b.len] = c; b.len = b.len + 1 } + function buf_puts(b: Buf, s: ptr) -> void { var i = 0; while s[i] != 0 { buf_putc(b, s[i]); i = i + 1 } } + function buf_indent(b: Buf, n: int) -> void { var i = 0; while i < n { buf_putc(b, 32); i = i + 1 } } + function buf_str(b: Buf) -> ptr { b.data[b.len] = 0; return b.data } # append src[a..b) raw - fn buf_addrange(b: Buf, s: ptr, a: int, e: int) -> void { var k = a; while k < e { buf_putc(b, s[k]); k = k + 1 } } + function buf_addrange(b: Buf, s: ptr, a: int, e: int) -> void { var k = a; while k < e { buf_putc(b, s[k]); k = k + 1 } } # ---- the token stream (parallel slices) ---- var src: ptr = null @@ -88,35 +88,35 @@ program LudicFmt { var tk_line: []int var linestart: []int - fn ntok() -> int { return len(tk_kind) } - fn tok_len(i: int) -> int { return tk_end[i] - tk_start[i] } - fn tok_text(i: int) -> ptr { return src[tk_start[i]..tk_end[i]] } - fn push_tok(k: int, st: int, en: int, ln: int) -> void { + function ntok() -> int { return len(tk_kind) } + function tok_len(i: int) -> int { return tk_end[i] - tk_start[i] } + function tok_text(i: int) -> ptr { return src[tk_start[i]..tk_end[i]] } + function push_tok(k: int, st: int, en: int, ln: int) -> void { push(tk_kind, k); push(tk_start, st); push(tk_end, en); push(tk_line, ln) } # ---- vocabulary classifiers ---- - fn is_type_word(w: ptr) -> bool { + function is_type_word(w: ptr) -> bool { return (w == "int") or (w == "long") or (w == "fixed") or (w == "bool") or (w == "entity") or (w == "str") or (w == "ptr") or (w == "byte") or (w == "words") or (w == "fixeds") or (w == "ptrs") or (w == "void") } - fn is_phase_word(w: ptr) -> bool { + function is_phase_word(w: ptr) -> bool { return (w == "Start") or (w == "Input") or (w == "FixedUpdate") or (w == "Update") or (w == "LateUpdate") or (w == "Render") } - fn is_keyword_word(w: ptr) -> bool { + function is_keyword_word(w: ptr) -> bool { if (w == "program") or (w == "import") or (w == "property") or (w == "model") or (w == "enum") or (w == "ui") { return true } - if (w == "const") or (w == "var") or (w == "fn") or (w == "extern") or (w == "handler") or (w == "entry") or (w == "event") or (w == "scene") { return true } + if (w == "const") or (w == "var") or (w == "function") or (w == "extern") or (w == "handler") or (w == "entry") or (w == "event") or (w == "scene") { return true } if (w == "phase") or (w == "query") or (w == "on") or (w == "cancellable") or (w == "public") or (w == "layer") or (w == "start") { return true } if (w == "let") or (w == "return") or (w == "if") or (w == "else") or (w == "while") or (w == "for") or (w == "in") or (w == "spawn") or (w == "despawn") { return true } if (w == "enable") or (w == "disable") or (w == "match") or (w == "machine") or (w == "state") or (w == "become") or (w == "where") { return true } if (w == "and") or (w == "or") or (w == "not") or (w == "break") or (w == "continue") or (w == "new") or (w == "emit") or (w == "cancel") { return true } return false } - fn is_clause_word(w: ptr) -> bool { + function is_clause_word(w: ptr) -> bool { return (w == "phase") or (w == "query") or (w == "reads") or (w == "writes") or (w == "needs") or (w == "uses") or (w == "requires") or (w == "ensures") or (w == "invariant") or (w == "effects") } # ---- the lexer: keeps comments, newlines, byte spans; never exits on bad input ---- - fn is_op2(c0: int, c1: int) -> bool { + function is_op2(c0: int, c1: int) -> bool { if c0 == 45 and c1 == 62 { return true } # -> if c1 == 61 and (c0 == 43 or c0 == 45 or c0 == 42 or c0 == 47 or c0 == 61 or c0 == 33 or c0 == 60 or c0 == 62) { return true } # += -= *= /= == != <= >= if c0 == 38 and c1 == 38 { return true } # && @@ -125,11 +125,11 @@ program LudicFmt { if c0 == 61 and c1 == 62 { return true } # => return false } - fn is_op1(c: int) -> bool { + function is_op1(c: int) -> bool { return c == 43 or c == 45 or c == 42 or c == 47 or c == 37 or c == 60 or c == 62 or c == 61 or c == 40 or c == 41 or c == 123 or c == 125 or c == 91 or c == 93 or c == 44 or c == 58 or c == 46 or c == 33 or c == 64 or c == 59 } - fn lex(s: ptr) -> void { + function lex(s: ptr) -> void { src = s tk_kind = new []int; tk_start = new []int; tk_end = new []int; tk_line = new []int linestart = new []int @@ -197,19 +197,19 @@ program LudicFmt { } # ---- token-stream helpers ---- - fn next_sig(i: int) -> int { + function next_sig(i: int) -> int { var j = i + 1 while j < ntok() { let k = tk_kind[j]; if k != LT_NL and k != LT_COMMENT { return j }; j = j + 1 } return 0 - 1 } - fn prev_sig(i: int) -> int { + function prev_sig(i: int) -> int { var j = i - 1 while j >= 0 { let k = tk_kind[j]; if k != LT_NL and k != LT_COMMENT { return j }; j = j - 1 } return 0 - 1 } - fn name_like(k: int) -> bool { return k == LT_ID or k == LT_KW or k == LT_TYPE or k == LT_PHASE or k == LT_BOOL } + function name_like(k: int) -> bool { return k == LT_ID or k == LT_KW or k == LT_TYPE or k == LT_PHASE or k == LT_BOOL } # a '.' hugs an operand, a closing bracket, and another dot - fn dot_tight(t: int) -> bool { + function dot_tight(t: int) -> bool { if name_like(tk_kind[t]) { return true } if tk_kind[t] != LT_OP { return false } let n = tok_len(t); let c0 = src[tk_start[t]] @@ -218,7 +218,7 @@ program LudicFmt { return false } # is the token at index i a unary '-'/'!' rather than a binary operator? - fn is_unary(i: int) -> bool { + function is_unary(i: int) -> bool { if tk_kind[i] != LT_OP { return false } let n = tok_len(i) if not (n == 1 and (src[tk_start[i]] == 45 or src[tk_start[i]] == 33)) { return false } @@ -234,7 +234,7 @@ program LudicFmt { } # whitespace between the previous emitted token (prev) and the current one (cur) - fn space_before(prev: int, cur: int) -> int { + function space_before(prev: int, cur: int) -> int { if prev < 0 { return 0 } let pk = tk_kind[prev]; let ck = tk_kind[cur] let plen = tok_len(prev); let clen = tok_len(cur) @@ -248,7 +248,7 @@ program LudicFmt { if pk == LT_OP and is_unary(prev) { return 0 } if pk == LT_ANNO and c1 and c0 == 40 { return 0 } # @anno( if c1 and c0 == 40 and ck == LT_OP { - if pk == LT_ID or pk == LT_TYPE or pk == LT_PHASE { return 0 } # fn move( / clear( + if pk == LT_ID or pk == LT_TYPE or pk == LT_PHASE { return 0 } # function move( / clear( if pk == LT_OP { if p1 and (p0 == 41 or p0 == 93) { return 1 }; return 0 } return 1 } @@ -256,7 +256,7 @@ program LudicFmt { } # ---- the formatting pass ---- - fn format(indent_width: int) -> Buf { + function format(indent_width: int) -> Buf { let o = buf_new() let stack = words(600) let hang = words(600) @@ -394,7 +394,7 @@ program LudicFmt { var g_info: int = 0 var g_marker: int = 0 # detect a fence at line offset i; sets g_flen/g_info/g_marker; returns bool - fn md_fence_at(s: ptr, i: int) -> bool { + function md_fence_at(s: ptr, i: int) -> bool { var j = i; while s[j] == 32 { j = j + 1 } let m = s[j] if m != 96 and m != 126 { return false } # ` or ~ @@ -403,16 +403,16 @@ program LudicFmt { g_flen = n; g_info = j; g_marker = m return true } - fn md_info_is_ludic(s: ptr, at: int) -> bool { + function md_info_is_ludic(s: ptr, at: int) -> bool { var a = at; while s[a] == 32 or s[a] == 9 { a = a + 1 } # case-insensitive "ludic" if not (((s[a] == 108 or s[a] == 76)) and ((s[a + 1] == 117 or s[a + 1] == 85)) and ((s[a + 2] == 100 or s[a + 2] == 68)) and ((s[a + 3] == 105 or s[a + 3] == 73)) and ((s[a + 4] == 99 or s[a + 4] == 67))) { return false } let af = s[a + 5] return af == 0 or af == 10 or af == 32 or af == 9 or af == 13 } - fn line_end(s: ptr, i: int) -> int { var e = i; while s[e] != 0 and s[e] != 10 { e = e + 1 }; return e } + function line_end(s: ptr, i: int) -> int { var e = i; while s[e] != 0 and s[e] != 10 { e = e + 1 }; return e } - fn format_markdown(s: ptr, indent_width: int) -> Buf { + function format_markdown(s: ptr, indent_width: int) -> Buf { let o = buf_new() var i = 0 while s[i] != 0 { @@ -478,14 +478,14 @@ program LudicFmt { } # ---- ends-with helpers for extension detection ---- - fn ends_with(s: ptr, suf: ptr) -> bool { + function ends_with(s: ptr, suf: ptr) -> bool { let n = cstr_len(s); let m = cstr_len(suf) if m > n { return false } return (s[n - m..n] == suf) } # ---- stdin slurp (for `-`) ---- - fn slurp_stdin() -> ptr { + function slurp_stdin() -> ptr { let b = buf_new() var c = read_char() while c >= 0 { buf_putc(b, c); c = read_char() } @@ -493,14 +493,14 @@ program LudicFmt { } # format one source string according to its kind (markdown vs ludic) - fn format_source(text: ptr, is_md: bool, indent_width: int) -> ptr { + function format_source(text: ptr, is_md: bool, indent_width: int) -> ptr { if is_md { let m = format_markdown(text, indent_width); return buf_str(m) } lex(text) let f = format(indent_width) return buf_str(f) } - fn streq(a: ptr, b: ptr) -> bool { return (a == b) } + function streq(a: ptr, b: ptr) -> bool { return (a == b) } entry { var write = false diff --git a/tools/ludic-tools/lsp.ludic b/tools/ludic-tools/lsp.ludic index 54c1e0eb..3d783637 100644 --- a/tools/ludic-tools/lsp.ludic +++ b/tools/ludic-tools/lsp.ludic @@ -72,7 +72,7 @@ program LudicLsp { const SC_UNKNOWN: int = 24 # ---- tiny helpers ---- - fn read_file(path: ptr) -> ptr { + function read_file(path: ptr) -> ptr { let f = file_open(path, "rb") if (f == null) { return null } file_seek(f, 0, 2) @@ -84,16 +84,16 @@ program LudicLsp { file_close(f) return buf } - fn cstr_len(s: ptr) -> int { var n = 0; while s[n] != 0 { n = n + 1 }; return n } - fn char_is_digit(c: int) -> bool { return c >= 48 and c <= 57 } - fn char_is_alpha(c: int) -> bool { + function cstr_len(s: ptr) -> int { var n = 0; while s[n] != 0 { n = n + 1 }; return n } + function char_is_digit(c: int) -> bool { return c >= 48 and c <= 57 } + function char_is_alpha(c: int) -> bool { if c >= 65 and c <= 90 { return true } if c >= 97 and c <= 122 { return true } return c == 95 } - fn char_is_alnum(c: int) -> bool { return char_is_alpha(c) or char_is_digit(c) } - fn char_is_hex(c: int) -> bool { return char_is_digit(c) or (c >= 97 and c <= 102) or (c >= 65 and c <= 70) } - fn itoa(v: int) -> ptr { + function char_is_alnum(c: int) -> bool { return char_is_alpha(c) or char_is_digit(c) } + function char_is_hex(c: int) -> bool { return char_is_digit(c) or (c >= 97 and c <= 102) or (c >= 65 and c <= 70) } + function itoa(v: int) -> ptr { if v == 0 { let z = bytes(2); z[0] = 48; z[1] = 0; return z } var neg = false; var x = v if x < 0 { neg = true; x = 0 - x } @@ -108,7 +108,7 @@ program LudicLsp { out[total] = 0 return out } - fn atoi(s: ptr) -> int { + function atoi(s: ptr) -> int { var n = 0; var i = 0; var neg = false while s[i] == 32 or s[i] == 9 { i = i + 1 } if s[i] == 45 { neg = true; i = i + 1 } @@ -119,27 +119,27 @@ program LudicLsp { # ---- growable byte buffer ---- property Buf { data: ptr = null, len: int = 0, cap: int = 0 } - fn buf_new() -> Buf { let b = new Buf; b.cap = 256; b.data = bytes(b.cap); b.len = 0; return b } - fn buf_ensure(b: Buf, extra: int) -> void { + function buf_new() -> Buf { let b = new Buf; b.cap = 256; b.data = bytes(b.cap); b.len = 0; return b } + function buf_ensure(b: Buf, extra: int) -> void { if b.len + extra + 1 <= b.cap { return } while b.len + extra + 1 > b.cap { b.cap = b.cap * 2 } b.data = resize(b.data, b.cap) } - fn buf_putc(b: Buf, c: int) -> void { buf_ensure(b, 1); b.data[b.len] = c; b.len = b.len + 1 } - fn buf_puts(b: Buf, s: ptr) -> void { var i = 0; while s[i] != 0 { buf_putc(b, s[i]); i = i + 1 } } - fn buf_puti(b: Buf, n: int) -> void { buf_puts(b, itoa(n)) } - fn buf_addrange(b: Buf, s: ptr, a: int, e: int) -> void { var k = a; while k < e { buf_putc(b, s[k]); k = k + 1 } } - fn buf_str(b: Buf) -> ptr { b.data[b.len] = 0; return b.data } + function buf_putc(b: Buf, c: int) -> void { buf_ensure(b, 1); b.data[b.len] = c; b.len = b.len + 1 } + function buf_puts(b: Buf, s: ptr) -> void { var i = 0; while s[i] != 0 { buf_putc(b, s[i]); i = i + 1 } } + function buf_puti(b: Buf, n: int) -> void { buf_puts(b, itoa(n)) } + function buf_addrange(b: Buf, s: ptr, a: int, e: int) -> void { var k = a; while k < e { buf_putc(b, s[k]); k = k + 1 } } + function buf_str(b: Buf) -> ptr { b.data[b.len] = 0; return b.data } # ---- string helpers ---- - fn streq(a: ptr, b: ptr) -> bool { return (a == b) } - fn concat3(a: ptr, b: ptr, c: ptr) -> ptr { return ((a + b) + c) } - fn ends_with(s: ptr, suf: ptr) -> bool { + function streq(a: ptr, b: ptr) -> bool { return (a == b) } + function concat3(a: ptr, b: ptr, c: ptr) -> ptr { return ((a + b) + c) } + function ends_with(s: ptr, suf: ptr) -> bool { let n = cstr_len(s); let m = cstr_len(suf) if m > n { return false } return (s[n - m..n] == suf) } - fn starts_with(s: ptr, pre: ptr) -> bool { + function starts_with(s: ptr, pre: ptr) -> bool { let m = cstr_len(pre) if m > cstr_len(s) { return false } return (s[0..m] == pre) @@ -191,7 +191,7 @@ program LudicLsp { var g_shutdown: int = 0 # ---- the lexer, over a Doc ---- - fn is_op2(c0: int, c1: int) -> bool { + function is_op2(c0: int, c1: int) -> bool { if c0 == 45 and c1 == 62 { return true } if c1 == 61 and (c0 == 43 or c0 == 45 or c0 == 42 or c0 == 47 or c0 == 61 or c0 == 33 or c0 == 60 or c0 == 62) { return true } if c0 == 38 and c1 == 38 { return true } @@ -200,29 +200,29 @@ program LudicLsp { if c0 == 61 and c1 == 62 { return true } return false } - fn is_op1(c: int) -> bool { + function is_op1(c: int) -> bool { return c == 43 or c == 45 or c == 42 or c == 47 or c == 37 or c == 60 or c == 62 or c == 61 or c == 40 or c == 41 or c == 123 or c == 125 or c == 91 or c == 93 or c == 44 or c == 58 or c == 46 or c == 33 or c == 64 or c == 59 } - fn is_type_word(w: ptr) -> bool { + function is_type_word(w: ptr) -> bool { return (w == "int") or (w == "long") or (w == "fixed") or (w == "bool") or (w == "entity") or (w == "str") or (w == "ptr") or (w == "byte") or (w == "words") or (w == "fixeds") or (w == "ptrs") or (w == "void") } - fn is_phase_word(w: ptr) -> bool { + function is_phase_word(w: ptr) -> bool { return (w == "Start") or (w == "Input") or (w == "FixedUpdate") or (w == "Update") or (w == "LateUpdate") or (w == "Render") } - fn is_keyword_word(w: ptr) -> bool { + function is_keyword_word(w: ptr) -> bool { if (w == "program") or (w == "import") or (w == "property") or (w == "model") or (w == "enum") or (w == "ui") { return true } - if (w == "const") or (w == "var") or (w == "fn") or (w == "extern") or (w == "handler") or (w == "entry") or (w == "event") or (w == "scene") { return true } + if (w == "const") or (w == "var") or (w == "function") or (w == "extern") or (w == "handler") or (w == "entry") or (w == "event") or (w == "scene") { return true } if (w == "phase") or (w == "query") or (w == "on") or (w == "cancellable") or (w == "public") or (w == "layer") or (w == "start") { return true } if (w == "let") or (w == "return") or (w == "if") or (w == "else") or (w == "while") or (w == "for") or (w == "in") or (w == "spawn") or (w == "despawn") { return true } if (w == "enable") or (w == "disable") or (w == "match") or (w == "machine") or (w == "state") or (w == "become") or (w == "where") { return true } if (w == "and") or (w == "or") or (w == "not") or (w == "break") or (w == "continue") or (w == "new") or (w == "emit") or (w == "cancel") { return true } return false } - fn is_widget_word(w: ptr) -> bool { + function is_widget_word(w: ptr) -> bool { return (w == "panel") or (w == "col") or (w == "row") or (w == "label") or (w == "button") or (w == "image") or (w == "spacer") } - fn lex_doc(D: Doc) -> void { + function lex_doc(D: Doc) -> void { let s = D.src D.tk_kind = new []int; D.tk_start = new []int; D.tk_end = new []int; D.tk_line = new []int D.linestart = new []int @@ -265,15 +265,15 @@ program LudicLsp { push(D.tk_kind, LT_EOF); push(D.tk_start, i); push(D.tk_end, i); push(D.tk_line, line) } - fn ntok(D: Doc) -> int { return len(D.tk_kind) } - fn tlen(D: Doc, i: int) -> int { return D.tk_end[i] - D.tk_start[i] } - fn ttext(D: Doc, i: int) -> ptr { return D.src[D.tk_start[i]..D.tk_end[i]] } - fn tis(D: Doc, i: int, s: ptr) -> bool { if i < 0 or i >= ntok(D) { return false }; return (ttext(D, i) == s) } - fn next_sig(D: Doc, i: int) -> int { var j = i + 1; while j < ntok(D) { let k = D.tk_kind[j]; if k != LT_NL and k != LT_COMMENT { return j }; j = j + 1 }; return 0 - 1 } - fn prev_sig(D: Doc, i: int) -> int { var j = i - 1; while j >= 0 { let k = D.tk_kind[j]; if k != LT_NL and k != LT_COMMENT { return j }; j = j - 1 }; return 0 - 1 } + function ntok(D: Doc) -> int { return len(D.tk_kind) } + function tlen(D: Doc, i: int) -> int { return D.tk_end[i] - D.tk_start[i] } + function ttext(D: Doc, i: int) -> ptr { return D.src[D.tk_start[i]..D.tk_end[i]] } + function tis(D: Doc, i: int, s: ptr) -> bool { if i < 0 or i >= ntok(D) { return false }; return (ttext(D, i) == s) } + function next_sig(D: Doc, i: int) -> int { var j = i + 1; while j < ntok(D) { let k = D.tk_kind[j]; if k != LT_NL and k != LT_COMMENT { return j }; j = j + 1 }; return 0 - 1 } + function prev_sig(D: Doc, i: int) -> int { var j = i - 1; while j >= 0 { let k = D.tk_kind[j]; if k != LT_NL and k != LT_COMMENT { return j }; j = j - 1 }; return 0 - 1 } # ---- positions (UTF-16 for LSP) ---- - fn utf16_len(s: ptr, a: int, nbytes: int) -> int { + function utf16_len(s: ptr, a: int, nbytes: int) -> int { var u = 0; var i = 0 while i < nbytes { let c = s[a + i] @@ -284,13 +284,13 @@ program LudicLsp { } return u } - fn line_of(D: Doc, off: int) -> int { + function line_of(D: Doc, off: int) -> int { var lo = 0; var hi = len(D.linestart) - 1 while lo < hi { let mid = (lo + hi + 1) / 2; if D.linestart[mid] <= off { lo = mid } else { hi = mid - 1 } } return lo } - fn col_of(D: Doc, off: int) -> int { let l = line_of(D, off); return utf16_len(D.text, D.linestart[l], off - D.linestart[l]) } - fn offset_of(D: Doc, line: int, ch: int) -> int { + function col_of(D: Doc, off: int) -> int { let l = line_of(D, off); return utf16_len(D.text, D.linestart[l], off - D.linestart[l]) } + function offset_of(D: Doc, line: int, ch: int) -> int { if line < 0 { return 0 } if line >= len(D.linestart) { return cstr_len(D.text) } var off = D.linestart[line] @@ -306,7 +306,7 @@ program LudicLsp { } return off } - fn tok_at(D: Doc, off: int) -> int { + function tok_at(D: Doc, off: int) -> int { var touching = 0 - 1 var i = 0 let N = ntok(D) @@ -323,7 +323,7 @@ program LudicLsp { } # ---- brace matching ---- - fn match_braces(D: Doc) -> void { + function match_braces(D: Doc) -> void { let N = ntok(D) D.tmatch = new []int var i = 0 @@ -342,7 +342,7 @@ program LudicLsp { } # ---- doc comment: the comment block directly above a declaration ---- - fn collect_doc(D: Doc, decl_tok: int) -> ptr { + function collect_doc(D: Doc, decl_tok: int) -> ptr { var line = D.tk_line[decl_tok] var first = 0 - 1 var i = decl_tok - 1 @@ -379,17 +379,17 @@ program LudicLsp { var pD: Doc = null var pi: int = 0 - fn pk() -> int { if pi < ntok(pD) { return pD.tk_kind[pi] }; return LT_EOF } - fn pis(s: ptr) -> bool { return tis(pD, pi, s) } - fn pskipnl() -> void { while pi < ntok(pD) and (pk() == LT_NL or pk() == LT_COMMENT) { pi = pi + 1 } } - fn padv() -> void { if pi < ntok(pD) { pi = pi + 1 }; pskipnl() } - fn pword() -> ptr { if pi < ntok(pD) { return ttext(pD, pi) }; return "" } - fn pname() -> int { + function pk() -> int { if pi < ntok(pD) { return pD.tk_kind[pi] }; return LT_EOF } + function pis(s: ptr) -> bool { return tis(pD, pi, s) } + function pskipnl() -> void { while pi < ntok(pD) and (pk() == LT_NL or pk() == LT_COMMENT) { pi = pi + 1 } } + function padv() -> void { if pi < ntok(pD) { pi = pi + 1 }; pskipnl() } + function pword() -> ptr { if pi < ntok(pD) { return ttext(pD, pi) }; return "" } + function pname() -> int { let k = pk() if k == LT_ID or k == LT_TYPE or k == LT_PHASE or k == LT_KW or k == LT_BOOL { let t = pi; padv(); return t } return 0 - 1 } - fn sym_add(kind: int, nametok: int, parent: int) -> int { + function sym_add(kind: int, nametok: int, parent: int) -> int { let s = new Sym s.kind = kind; s.tok = nametok; s.parent = parent s.name = ""; s.ty = ""; s.detail = ""; s.doc = "" @@ -398,11 +398,11 @@ program LudicLsp { push(pD.syms, s) return len(pD.syms) - 1 } - fn cur_match() -> int { if pi < ntok(pD) { return pD.tmatch[pi] }; return 0 - 1 } - fn skip_group() -> void { let m = cur_match(); if m < 0 { padv(); return }; pi = m + 1; pskipnl() } + function cur_match() -> int { if pi < ntok(pD) { return pD.tmatch[pi] }; return 0 - 1 } + function skip_group() -> void { let m = cur_match(); if m < 0 { padv(); return }; pi = m + 1; pskipnl() } # `name: Type` pairs inside ( ) — record params, build the signature into sig - fn lp_params(owner: int, kind: int, sig: Buf) -> void { + function lp_params(owner: int, kind: int, sig: Buf) -> void { if not pis("(") { return } let close = cur_match() buf_putc(sig, 40) # '(' @@ -428,7 +428,7 @@ program LudicLsp { } # (a, b) variable list -> token indices into out; returns count - fn lp_varlist(out: []int) -> int { + function lp_varlist(out: []int) -> int { var n = 0 if not pis("(") { return 0 } let close = cur_match() @@ -446,7 +446,7 @@ program LudicLsp { } # [Pos, Vel, {Enemy}] — bind listed components to vars in order (skip {Tag}) - fn lp_query_terms(owner: int, vars: []int, nvars: int, scope_start: int, scope_end: int) -> void { + function lp_query_terms(owner: int, vars: []int, nvars: int, scope_start: int, scope_end: int) -> void { if not pis("[") { return } let close = cur_match() padv() @@ -470,7 +470,7 @@ program LudicLsp { if pis("where") { padv() } } - fn lp_block(owner: int, scope_end: int) -> void { + function lp_block(owner: int, scope_end: int) -> void { if not pis("{") { return } let close = cur_match() var endo = scope_end @@ -487,7 +487,7 @@ program LudicLsp { if close >= 0 { pi = close + 1 } else { padv() } pskipnl() } - fn lp_stmt(owner: int, scope_end: int) -> void { + function lp_stmt(owner: int, scope_end: int) -> void { if pis("let") { padv(); let nt = pname(); if nt < 0 { return } var ty = "" @@ -522,7 +522,7 @@ program LudicLsp { } # panel id=Root w=288 { … } — each id= mints a UI_ handle - fn lp_widget(owner: int, uisym: int) -> void { + function lp_widget(owner: int, uisym: int) -> void { let type_tok = pname() if type_tok < 0 { return } while pi < ntok(pD) and pk() == LT_ID { @@ -565,7 +565,7 @@ program LudicLsp { } # one top-level declaration - fn lp_decl(parent: int) -> void { + function lp_decl(parent: int) -> void { let w = pword() let decl_tok = pi @@ -638,11 +638,11 @@ program LudicLsp { return } if w == "extern" { - padv(); if pis("fn") { padv() } + padv(); if pis("function") { padv() } let nt = pname(); if nt < 0 { return } let sidx = sym_add(LS_EXTERN, nt, parent) pD.syms[sidx].doc = collect_doc(pD, decl_tok) - let sig = buf_new(); buf_puts(sig, "extern fn "); buf_puts(sig, pD.syms[sidx].name) + let sig = buf_new(); buf_puts(sig, "extern function "); buf_puts(sig, pD.syms[sidx].name) lp_params(sidx, LS_PARAM, sig) if pis("->") { padv(); let tt = pname(); if tt >= 0 { pD.syms[sidx].ty = ttext(pD, tt); buf_puts(sig, " -> "); buf_puts(sig, pD.syms[sidx].ty) } } pD.syms[sidx].detail = buf_str(sig) @@ -650,10 +650,10 @@ program LudicLsp { pskipnl() return } - if (w == "fn") or (w == "pure") or (w == "export") { + if (w == "function") or (w == "pure") or (w == "export") { let exported = (w == "export") if (w == "pure") or exported { padv(); if pis("pure") { padv() } } - if pis("fn") { padv() } + if pis("function") { padv() } let nt = pname(); if nt < 0 { return } let sidx = sym_add(LS_FN, nt, parent) if exported { pD.syms[sidx].exported = 1 } @@ -795,7 +795,7 @@ program LudicLsp { padv() } - fn parse_doc(D: Doc) -> void { + function parse_doc(D: Doc) -> void { D.syms = new []Sym D.imports = new []ptr D.is_unit = 0; D.is_module = 0; D.unit = "" @@ -833,22 +833,22 @@ program LudicLsp { # ============================================================================ # workspace: many documents, one project # ============================================================================ - fn is_local_kind(k: int) -> bool { return k == LS_LOCAL or k == LS_PARAM or k == LS_QUERYVAR or k == LS_STATE } - fn is_toplevel_kind(k: int) -> bool { + function is_local_kind(k: int) -> bool { return k == LS_LOCAL or k == LS_PARAM or k == LS_QUERYVAR or k == LS_STATE } + function is_toplevel_kind(k: int) -> bool { return k == LS_COMPONENT or k == LS_ARCHETYPE or k == LS_CONST or k == LS_VAR or k == LS_FN or k == LS_EXTERN or k == LS_SYSTEM or k == LS_UI or k == LS_WIDGET or k == LS_SCENE or k == LS_LAYER or k == LS_UNIT } - fn is_outline_kind(k: int) -> bool { + function is_outline_kind(k: int) -> bool { return k == LS_UNIT or k == LS_COMPONENT or k == LS_FIELD or k == LS_ARCHETYPE or k == LS_CONST or k == LS_VAR or k == LS_FN or k == LS_EXTERN or k == LS_SYSTEM or k == LS_UI or k == LS_WIDGET or k == LS_SCENE or k == LS_LAYER } # ---- paths & URIs ---- - fn hexval(c: int) -> int { + function hexval(c: int) -> int { if c >= 48 and c <= 57 { return c - 48 } let l = c | 32 if l >= 97 and l <= 102 { return l - 97 + 10 } return 0 - 1 } - fn uri_to_path(uri: ptr) -> ptr { + function uri_to_path(uri: ptr) -> ptr { var s = uri if starts_with(uri, "file://") { s = uri[7..cstr_len(uri)] } let n = cstr_len(s) @@ -861,12 +861,12 @@ program LudicLsp { out[j] = 0 return out } - fn uri_safe(c: int) -> bool { + function uri_safe(c: int) -> bool { if char_is_alnum(c) { return true } return c == 45 or c == 46 or c == 126 or c == 47 # - . ~ / } - fn hexch(n: int) -> int { if n < 10 { return 48 + n }; return 55 + n } - fn path_to_uri(path: ptr) -> ptr { + function hexch(n: int) -> int { if n < 10 { return 48 + n }; return 55 + n } + function path_to_uri(path: ptr) -> ptr { let out = buf_new() buf_puts(out, "file://") var i = 0 @@ -878,7 +878,7 @@ program LudicLsp { } return buf_str(out) } - fn dirname(p: ptr) -> ptr { + function dirname(p: ptr) -> ptr { var last = 0 - 1 var i = 0 while p[i] != 0 { if p[i] == 47 { last = i }; i = i + 1 } @@ -886,7 +886,7 @@ program LudicLsp { if last == 0 { return "/" } return p[0..last] } - fn normpath(p: ptr) -> ptr { + function normpath(p: ptr) -> ptr { let n = cstr_len(p) let abs = (n > 0 and p[0] == 47) let list = new []ptr @@ -914,16 +914,16 @@ program LudicLsp { while k < cnt { if k > 0 { buf_putc(out, 47) }; buf_puts(out, list[k]); k = k + 1 } return buf_str(out) } - fn join_path(dir: ptr, rel: ptr) -> ptr { + function join_path(dir: ptr, rel: ptr) -> ptr { if rel[0] == 47 { return normpath(rel) } return normpath(concat3(dir, "/", rel)) } # ---- markdown scrub: blank everything outside a ```ludic fence ---- - fn word_ludic_at(s: ptr, at: int) -> bool { + function word_ludic_at(s: ptr, at: int) -> bool { return ((s[at] == 108 or s[at] == 76)) and ((s[at + 1] == 117 or s[at + 1] == 85)) and ((s[at + 2] == 100 or s[at + 2] == 68)) and ((s[at + 3] == 105 or s[at + 3] == 73)) and ((s[at + 4] == 99 or s[at + 4] == 67)) } - fn scrub_markdown(src: ptr) -> ptr { + function scrub_markdown(src: ptr) -> ptr { let n = cstr_len(src) let out = bytes(n + 1) var c0 = 0 @@ -974,12 +974,12 @@ program LudicLsp { } # ---- documents ---- - fn set_text(D: Doc, raw: ptr) -> void { + function set_text(D: Doc, raw: ptr) -> void { D.raw = raw if D.is_md == 1 { D.text = scrub_markdown(raw) } else { D.text = raw } D.src = D.text } - fn reindex(D: Doc) -> void { + function reindex(D: Doc) -> void { lex_doc(D) match_braces(D) parse_doc(D) @@ -988,7 +988,7 @@ program LudicLsp { let N = ntok(D) while i < N { push(D.cls, SC_NONE); i = i + 1 } } - fn new_doc(path: ptr, text: ptr) -> Doc { + function new_doc(path: ptr, text: ptr) -> Doc { let D = new Doc D.path = normpath(path) D.uri = path_to_uri(D.path) @@ -999,13 +999,13 @@ program LudicLsp { reindex(D) return D } - fn by_path(path: ptr) -> Doc { + function by_path(path: ptr) -> Doc { let p = normpath(path) var i = 0 while i < len(g_docs) { if (g_docs[i].path == p) { return g_docs[i] }; i = i + 1 } return null } - fn by_uri(uri: ptr) -> Doc { + function by_uri(uri: ptr) -> Doc { let p = uri_to_path(uri) let D = by_path(p) if (D != null) { return D } @@ -1013,7 +1013,7 @@ program LudicLsp { while i < len(g_docs) { if (g_docs[i].uri == uri) { return g_docs[i] }; i = i + 1 } return null } - fn ensure_doc(path: ptr) -> Doc { + function ensure_doc(path: ptr) -> Doc { let D = by_path(path) if (D != null) { return D } let text = read_file(path) @@ -1023,7 +1023,7 @@ program LudicLsp { return nd } - fn scan_root(root: ptr) -> void { + function scan_root(root: ptr) -> void { if (root == null) { return } let listfile = "/tmp/ludic_lsp_scan.txt" run(concat3(concat3("find '", root, "' -name '*.ludic' -not -path '*/build/*' -not -path '*/node_modules/*' -not -path '*/.*' > "), listfile, " 2>/dev/null")) @@ -1041,7 +1041,7 @@ program LudicLsp { var g_owner: Doc = null var g_symidx: int = 0 - fn imports_of(D: Doc, out: []Doc) -> void { + function imports_of(D: Doc, out: []Doc) -> void { var i = 0 while i < len(D.imports) { let full = join_path(dirname(D.path), D.imports[i]) @@ -1055,7 +1055,7 @@ program LudicLsp { i = i + 1 } } - fn related(D: Doc) -> []Doc { + function related(D: Doc) -> []Doc { let out = new []Doc if (D == null) { return out } push(out, D) @@ -1088,7 +1088,7 @@ program LudicLsp { } return out } - fn unit_root(D: Doc) -> Doc { + function unit_root(D: Doc) -> Doc { if (D == null) or D.is_md == 1 { return null } if D.is_unit == 1 { return D } let rel = related(D) @@ -1096,7 +1096,7 @@ program LudicLsp { while i < len(rel) { if rel[i].is_unit == 1 and rel[i].is_md == 0 { return rel[i] }; i = i + 1 } return null } - fn find_local(D: Doc, name: ptr, off: int) -> int { + function find_local(D: Doc, name: ptr, off: int) -> int { var best = 0 - 1 var i = 0 while i < len(D.syms) { @@ -1109,7 +1109,7 @@ program LudicLsp { return best } # returns sym index in g_owner (set), or -1 - fn find_top(D: Doc, name: ptr) -> int { + function find_top(D: Doc, name: ptr) -> int { let rel = related(D) var i = 0 while i < len(rel) { @@ -1132,7 +1132,7 @@ program LudicLsp { return 0 - 1 } # component sym index (in g_owner) for a dotted receiver, or -1 - fn receiver_component(D: Doc, dot_tok: int) -> int { + function receiver_component(D: Doc, dot_tok: int) -> int { let recv = prev_sig(D, dot_tok) if recv < 0 or D.tk_kind[recv] != LT_ID { return 0 - 1 } let name = ttext(D, recv) @@ -1145,7 +1145,7 @@ program LudicLsp { return find_top(D, ty) } # resolve the token: sets g_owner/g_symidx; returns the token index (or -1) - fn resolve_tok(D: Doc, tok: int) -> int { + function resolve_tok(D: Doc, tok: int) -> int { g_owner = null; g_symidx = 0 - 1 if tok < 0 { return 0 - 1 } if D.tk_kind[tok] != LT_ID and D.tk_kind[tok] != LT_STR { return 0 - 1 } @@ -1173,10 +1173,10 @@ program LudicLsp { g_owner = null; g_symidx = 0 - 1 return tok } - fn resolve_at(D: Doc, off: int) -> int { return resolve_tok(D, tok_at(D, off)) } + function resolve_at(D: Doc, off: int) -> int { return resolve_tok(D, tok_at(D, off)) } - fn first_on_line(D: Doc, tok: int) -> bool { let p = prev_sig(D, tok); return p < 0 or D.tk_line[p] != D.tk_line[tok] } - fn in_ui_body(D: Doc, off: int) -> bool { + function first_on_line(D: Doc, tok: int) -> bool { let p = prev_sig(D, tok); return p < 0 or D.tk_line[p] != D.tk_line[tok] } + function in_ui_body(D: Doc, off: int) -> bool { var i = 0 while i < len(D.syms) { if D.syms[i].kind == LS_UI and off > D.syms[i].bstart and off < D.syms[i].bend { return true }; i = i + 1 } return false @@ -1185,16 +1185,16 @@ program LudicLsp { # ---- builtins / intrinsics (name -> signature) ---- var g_builtins: []ptr var g_bsigs: []ptr - fn badd(name: ptr, sig: ptr) -> void { push(g_builtins, name); push(g_bsigs, sig) } - fn is_builtin_name(name: ptr) -> bool { var i = 0; while i < len(g_builtins) { if (g_builtins[i] == name) { return true }; i = i + 1 }; return false } - fn builtin_sig(name: ptr) -> ptr { var i = 0; while i < len(g_builtins) { if (g_builtins[i] == name) { return g_bsigs[i] }; i = i + 1 }; return null } + function badd(name: ptr, sig: ptr) -> void { push(g_builtins, name); push(g_bsigs, sig) } + function is_builtin_name(name: ptr) -> bool { var i = 0; while i < len(g_builtins) { if (g_builtins[i] == name) { return true }; i = i + 1 }; return false } + function builtin_sig(name: ptr) -> ptr { var i = 0; while i < len(g_builtins) { if (g_builtins[i] == name) { return g_bsigs[i] }; i = i + 1 }; return null } # The namespaced standard-library surface: `Namespace.method`, mirroring the # compiler's emit_ns_call (selfhost/emit_expr.ludic). Screen/Random/Input/Map # methods resolve to the underlying bare builtin's signature; Math/Text/List # are pure fixed-point / string / slice ops with their own signatures. Returns # the signature label for signature help, or null if it is not a known method. - fn ns_method_sig(ns: ptr, meth: ptr) -> ptr { + function ns_method_sig(ns: ptr, meth: ptr) -> ptr { if (ns == "Screen") { if (meth == "clear") { return builtin_sig("clear") } if (meth == "fill_rectangle") { return builtin_sig("fill_rect") } @@ -1395,7 +1395,7 @@ program LudicLsp { } return null } - fn init_builtins() -> void { + function init_builtins() -> void { g_builtins = new []ptr; g_bsigs = new []ptr badd("min", "min(a: int, b: int) -> int") badd("max", "max(a: int, b: int) -> int") @@ -1478,7 +1478,7 @@ program LudicLsp { } # ---- classification ---- - fn class_of_symkind(k: int) -> int { + function class_of_symkind(k: int) -> int { if k == LS_COMPONENT { return SC_COMPONENT } if k == LS_ARCHETYPE { return SC_ARCHETYPE } if k == LS_FIELD { return SC_FIELD } @@ -1495,7 +1495,7 @@ program LudicLsp { if k == LS_UNIT { return SC_UI } return SC_UNKNOWN } - fn classify(D: Doc) -> void { + function classify(D: Doc) -> void { let N = ntok(D) if len(D.cls) < N { D.cls = new []int; var z = 0; while z < N { push(D.cls, SC_NONE); z = z + 1 } } var i = 0 @@ -1542,7 +1542,7 @@ program LudicLsp { # semantic token type index + modifiers (via g_semmods) var g_semmods: int = 0 - fn sem_type_of(cls: int) -> int { + function sem_type_of(cls: int) -> int { g_semmods = 0 if cls == SC_KEYWORD { return 0 } if cls == SC_TYPE { g_semmods = 4; return 1 } @@ -1580,23 +1580,23 @@ program LudicLsp { const JARR: int = 4 const JOBJ: int = 5 property JVal { t: int = 0, num: int = 0, b: int = 0, s: ptr = null, kids: []JVal, keys: []ptr } - fn jval(t: int) -> JVal { let v = new JVal; v.t = t; v.kids = new []JVal; v.keys = new []ptr; return v } + function jval(t: int) -> JVal { let v = new JVal; v.t = t; v.kids = new []JVal; v.keys = new []ptr; return v } var jsrc: ptr = null var jp: int = 0 - fn jskip() -> void { while jsrc[jp] == 32 or jsrc[jp] == 9 or jsrc[jp] == 10 or jsrc[jp] == 13 { jp = jp + 1 } } - fn jhex4(s: ptr, at: int) -> int { + function jskip() -> void { while jsrc[jp] == 32 or jsrc[jp] == 9 or jsrc[jp] == 10 or jsrc[jp] == 13 { jp = jp + 1 } } + function jhex4(s: ptr, at: int) -> int { var v = 0; var i = 0 while i < 4 { let h = hexval(s[at + i]); if h < 0 { return v }; v = v * 16 + h; i = i + 1 } return v } - fn utf8_emit(o: Buf, cp: int) -> void { + function utf8_emit(o: Buf, cp: int) -> void { if cp < 128 { buf_putc(o, cp) } else { if cp < 2048 { buf_putc(o, 192 | (cp >> 6)); buf_putc(o, 128 | (cp & 63)) } else { if cp < 65536 { buf_putc(o, 224 | (cp >> 12)); buf_putc(o, 128 | ((cp >> 6) & 63)); buf_putc(o, 128 | (cp & 63)) } else { buf_putc(o, 240 | (cp >> 18)); buf_putc(o, 128 | ((cp >> 12) & 63)); buf_putc(o, 128 | ((cp >> 6) & 63)); buf_putc(o, 128 | (cp & 63)) } } } } - fn jparse_string() -> ptr { + function jparse_string() -> ptr { jp = jp + 1 # skip opening quote let o = buf_new() while jsrc[jp] != 0 and jsrc[jp] != 34 { @@ -1621,7 +1621,7 @@ program LudicLsp { if jsrc[jp] == 34 { jp = jp + 1 } return buf_str(o) } - fn jparse() -> JVal { + function jparse() -> JVal { jskip() let c = jsrc[jp] if c == 123 { # '{' @@ -1671,18 +1671,18 @@ program LudicLsp { jp = jp + 1 return jval(JNULL) } - fn jget(v: JVal, key: ptr) -> JVal { + function jget(v: JVal, key: ptr) -> JVal { if (v == null) { return null } if v.t != JOBJ { return null } var i = 0 while i < len(v.keys) { if (v.keys[i] == key) { return v.kids[i] }; i = i + 1 } return null } - fn jat(v: JVal, i: int) -> JVal { if (v == null) { return null }; if i < 0 or i >= len(v.kids) { return null }; return v.kids[i] } - fn jlen(v: JVal) -> int { if (v == null) { return 0 }; return len(v.kids) } - fn jstr(v: JVal, def: ptr) -> ptr { if (v != null) and v.t == JSTR and (v.s != null) { return v.s }; return def } - fn jint(v: JVal, def: int) -> int { if (v != null) and v.t == JNUM { return v.num }; return def } - fn jpath(v: JVal, path: ptr) -> JVal { + function jat(v: JVal, i: int) -> JVal { if (v == null) { return null }; if i < 0 or i >= len(v.kids) { return null }; return v.kids[i] } + function jlen(v: JVal) -> int { if (v == null) { return 0 }; return len(v.kids) } + function jstr(v: JVal, def: ptr) -> ptr { if (v != null) and v.t == JSTR and (v.s != null) { return v.s }; return def } + function jint(v: JVal, def: int) -> int { if (v != null) and v.t == JNUM { return v.num }; return def } + function jpath(v: JVal, path: ptr) -> JVal { var cur = v let n = cstr_len(path) var start = 0; var i = 0 @@ -1701,8 +1701,8 @@ program LudicLsp { # ============================================================================ # the formatter (Doc-based; reused for LSP formatting) — mirrors fmt.ludic # ============================================================================ - fn f_name_like(k: int) -> bool { return k == LT_ID or k == LT_KW or k == LT_TYPE or k == LT_PHASE or k == LT_BOOL } - fn f_dot_tight(F: Doc, t: int) -> bool { + function f_name_like(k: int) -> bool { return k == LT_ID or k == LT_KW or k == LT_TYPE or k == LT_PHASE or k == LT_BOOL } + function f_dot_tight(F: Doc, t: int) -> bool { if f_name_like(F.tk_kind[t]) { return true } if F.tk_kind[t] != LT_OP { return false } let n = tlen(F, t); let c0 = F.src[F.tk_start[t]] @@ -1710,7 +1710,7 @@ program LudicLsp { if n == 2 and c0 == 46 and F.src[F.tk_start[t] + 1] == 46 { return true } return false } - fn f_is_unary(F: Doc, i: int) -> bool { + function f_is_unary(F: Doc, i: int) -> bool { if F.tk_kind[i] != LT_OP { return false } let n = tlen(F, i) if not (n == 1 and (F.src[F.tk_start[i]] == 45 or F.src[F.tk_start[i]] == 33)) { return false } @@ -1721,7 +1721,7 @@ program LudicLsp { if pk2 == LT_OP { let c = F.src[F.tk_start[p]]; return not (tlen(F, p) == 1 and (c == 41 or c == 93 or c == 125)) } return true } - fn f_space_before(F: Doc, prev: int, cur: int) -> int { + function f_space_before(F: Doc, prev: int, cur: int) -> int { if prev < 0 { return 0 } let pk2 = F.tk_kind[prev]; let ck = F.tk_kind[cur] let plen = tlen(F, prev); let clen = tlen(F, cur) @@ -1741,16 +1741,16 @@ program LudicLsp { } return 1 } - fn f_is_clause(w: ptr) -> bool { + function f_is_clause(w: ptr) -> bool { return (w == "phase") or (w == "query") or (w == "reads") or (w == "writes") or (w == "needs") or (w == "uses") or (w == "requires") or (w == "ensures") or (w == "invariant") or (w == "effects") } - fn make_text_doc(text: ptr) -> Doc { + function make_text_doc(text: ptr) -> Doc { let F = new Doc F.raw = text; F.text = text; F.src = text; F.is_md = 0 lex_doc(F) return F } - fn format_doc(F: Doc, iw: int) -> ptr { + function format_doc(F: Doc, iw: int) -> ptr { let o = buf_new() let stack = words(600) let hang = words(600) @@ -1855,7 +1855,7 @@ program LudicLsp { var mf_flen: int = 0 var mf_info: int = 0 var mf_marker: int = 0 - fn mf_fence_at(s: ptr, i: int) -> bool { + function mf_fence_at(s: ptr, i: int) -> bool { var j = i; while s[j] == 32 { j = j + 1 } let m = s[j] if m != 96 and m != 126 { return false } @@ -1864,8 +1864,8 @@ program LudicLsp { mf_flen = n; mf_info = j; mf_marker = m return true } - fn f_line_end(s: ptr, i: int) -> int { var e = i; while s[e] != 0 and s[e] != 10 { e = e + 1 }; return e } - fn format_md(s: ptr, iw: int) -> ptr { + function f_line_end(s: ptr, i: int) -> int { var e = i; while s[e] != 0 and s[e] != 10 { e = e + 1 }; return e } + function format_md(s: ptr, iw: int) -> ptr { let o = buf_new() var i = 0 while s[i] != 0 { @@ -1925,14 +1925,14 @@ program LudicLsp { } return buf_str(o) } - fn skip_ws2(s: ptr, at: int) -> int { var a = at; while s[a] == 32 or s[a] == 9 { a = a + 1 }; return a } + function skip_ws2(s: ptr, at: int) -> int { var a = at; while s[a] == 32 or s[a] == 9 { a = a + 1 }; return a } # ============================================================================ # framing + output # ============================================================================ - extern fn fflush(f: ptr) -> int = "fflush" + extern function fflush(f: ptr) -> int = "fflush" - fn jstr_out(o: Buf, z: ptr) -> void { + function jstr_out(o: Buf, z: ptr) -> void { buf_putc(o, 34) if (z == null) { buf_putc(o, 34); return } var i = 0 @@ -1950,7 +1950,7 @@ program LudicLsp { buf_putc(o, 34) } - fn send_raw(body: ptr) -> void { + function send_raw(body: ptr) -> void { let hdr = buf_new() buf_puts(hdr, "Content-Length: "); buf_puti(hdr, cstr_len(body)); buf_putc(hdr, 13); buf_putc(hdr, 10); buf_putc(hdr, 13); buf_putc(hdr, 10) let hs = buf_str(hdr) @@ -1959,7 +1959,7 @@ program LudicLsp { file_write(out, body, cstr_len(body)) let z = fflush(out) } - fn send_result(idv: JVal, result: ptr) -> void { + function send_result(idv: JVal, result: ptr) -> void { let o = buf_new() buf_puts(o, "{\"jsonrpc\":\"2.0\",\"id\":") if (idv == null) { buf_puts(o, "null") } @@ -1969,19 +1969,19 @@ program LudicLsp { buf_putc(o, 125) send_raw(buf_str(o)) } - fn send_notify(method: ptr, params: ptr) -> void { + function send_notify(method: ptr, params: ptr) -> void { let o = buf_new() buf_puts(o, "{\"jsonrpc\":\"2.0\",\"method\":"); jstr_out(o, method); buf_puts(o, ",\"params\":"); buf_puts(o, params); buf_putc(o, 125) send_raw(buf_str(o)) } - fn lower(c: int) -> int { if c >= 65 and c <= 90 { return c + 32 }; return c } - fn starts_with_ci(s: ptr, pre: ptr) -> bool { + function lower(c: int) -> int { if c >= 65 and c <= 90 { return c + 32 }; return c } + function starts_with_ci(s: ptr, pre: ptr) -> bool { var i = 0 while pre[i] != 0 { if s[i] == 0 or lower(s[i]) != lower(pre[i]) { return false }; i = i + 1 } return true } - fn read_message() -> ptr { + function read_message() -> ptr { var length = 0 - 1 let line = buf_new() while true { @@ -2004,26 +2004,26 @@ program LudicLsp { } # ---- position output ---- - fn put_pos(o: Buf, D: Doc, off: int) -> void { buf_puts(o, "{\"line\":"); buf_puti(o, line_of(D, off)); buf_puts(o, ",\"character\":"); buf_puti(o, col_of(D, off)); buf_putc(o, 125) } - fn put_range(o: Buf, D: Doc, a: int, b: int) -> void { buf_puts(o, "{\"start\":"); put_pos(o, D, a); buf_puts(o, ",\"end\":"); put_pos(o, D, b); buf_putc(o, 125) } - fn put_location(o: Buf, D: Doc, a: int, b: int) -> void { buf_puts(o, "{\"uri\":"); jstr_out(o, D.uri); buf_puts(o, ",\"range\":"); put_range(o, D, a, b); buf_putc(o, 125) } + function put_pos(o: Buf, D: Doc, off: int) -> void { buf_puts(o, "{\"line\":"); buf_puti(o, line_of(D, off)); buf_puts(o, ",\"character\":"); buf_puti(o, col_of(D, off)); buf_putc(o, 125) } + function put_range(o: Buf, D: Doc, a: int, b: int) -> void { buf_puts(o, "{\"start\":"); put_pos(o, D, a); buf_puts(o, ",\"end\":"); put_pos(o, D, b); buf_putc(o, 125) } + function put_location(o: Buf, D: Doc, a: int, b: int) -> void { buf_puts(o, "{\"uri\":"); jstr_out(o, D.uri); buf_puts(o, ",\"range\":"); put_range(o, D, a, b); buf_putc(o, 125) } - fn doc_of(msg: JVal) -> Doc { + function doc_of(msg: JVal) -> Doc { let uri = jstr(jpath(msg, "params.textDocument.uri"), null) if (uri == null) { return null } let D = by_uri(uri) if (D != null) { return D } return ensure_doc(uri_to_path(uri)) } - fn off_of(msg: JVal, D: Doc, base: ptr) -> int { + function off_of(msg: JVal, D: Doc, base: ptr) -> int { let line = jint(jpath(msg, concat3(base, ".line", "")), 0) let ch = jint(jpath(msg, concat3(base, ".character", "")), 0) return offset_of(D, line, ch) } - fn basename(p: ptr) -> ptr { var last = 0 - 1; var i = 0; while p[i] != 0 { if p[i] == 47 { last = i }; i = i + 1 }; return p[last + 1..i] } + function basename(p: ptr) -> ptr { var last = 0 - 1; var i = 0; while p[i] != 0 { if p[i] == 47 { last = i }; i = i + 1 }; return p[last + 1..i] } # ---- LSP symbol kinds & completion kinds ---- - fn lsp_symbol_kind(k: int) -> int { + function lsp_symbol_kind(k: int) -> int { if k == LS_UNIT { return 2 } if k == LS_COMPONENT { return 23 } if k == LS_FIELD { return 8 } @@ -2038,7 +2038,7 @@ program LudicLsp { if k == LS_LAYER { return 3 } return 13 } - fn comp_kind_of(k: int) -> int { + function comp_kind_of(k: int) -> int { if k == LS_COMPONENT { return 22 } if k == LS_ARCHETYPE { return 7 } if k == LS_CONST { return 21 } @@ -2054,7 +2054,7 @@ program LudicLsp { } # ---- diagnostics (structural) ---- - fn diag_structural(D: Doc, o: Buf) -> void { + function diag_structural(D: Doc, o: Buf) -> void { let ds_s = new []int; let ds_e = new []int; let ds_m = new []ptr let stack = words(1200); var top = 0 var i = 0; let N = ntok(D) @@ -2081,7 +2081,7 @@ program LudicLsp { j = j + 1 } } - fn publish_diag(D: Doc) -> void { + function publish_diag(D: Doc) -> void { let o = buf_new() buf_puts(o, "{\"uri\":"); jstr_out(o, D.uri); buf_puts(o, ",\"diagnostics\":[") if D.is_md == 0 { diag_structural(D, o) } @@ -2090,7 +2090,7 @@ program LudicLsp { } # ---- document symbols ---- - fn put_doc_symbols(o: Buf, D: Doc, parent: int) -> void { + function put_doc_symbols(o: Buf, D: Doc, parent: int) -> void { buf_putc(o, 91) var first = 1 var i = 0 @@ -2116,13 +2116,13 @@ program LudicLsp { } # ---- hover ---- - fn hover_md(o: Buf, code: ptr, docstr: ptr) -> void { + function hover_md(o: Buf, code: ptr, docstr: ptr) -> void { let m = buf_new() buf_puts(m, "```ludic\n"); buf_puts(m, code); buf_puts(m, "\n```") if (docstr != null) and (docstr[0] != 0) { buf_puts(m, "\n\n"); buf_puts(m, docstr) } buf_puts(o, "{\"contents\":{\"kind\":\"markdown\",\"value\":"); jstr_out(o, buf_str(m)); buf_puts(o, "}}") } - fn on_hover(msg: JVal, id: JVal) -> void { + function on_hover(msg: JVal, id: JVal) -> void { let D = doc_of(msg) if (D == null) { send_result(id, "null"); return } let off = off_of(msg, D, "params.position") @@ -2147,7 +2147,7 @@ program LudicLsp { } # ---- definition ---- - fn on_definition(msg: JVal, id: JVal) -> void { + function on_definition(msg: JVal, id: JVal) -> void { let D = doc_of(msg) if (D == null) { send_result(id, "null"); return } let off = off_of(msg, D, "params.position") @@ -2172,7 +2172,7 @@ program LudicLsp { # ---- occurrences (references / rename) ---- var g_occ_doc: []Doc var g_occ_tok: []int - fn find_occurrences(from: Doc, tdoc: Doc, tsym: int) -> void { + function find_occurrences(from: Doc, tdoc: Doc, tsym: int) -> void { g_occ_doc = new []Doc; g_occ_tok = new []int let want = tdoc.syms[tsym].name var docs = new []Doc @@ -2191,7 +2191,7 @@ program LudicLsp { i = i + 1 } } - fn on_references(msg: JVal, id: JVal) -> void { + function on_references(msg: JVal, id: JVal) -> void { let D = doc_of(msg) if (D == null) { send_result(id, "[]"); return } let off = off_of(msg, D, "params.position") @@ -2209,7 +2209,7 @@ program LudicLsp { } buf_putc(o, 93); send_result(id, buf_str(o)) } - fn on_rename(msg: JVal, id: JVal) -> void { + function on_rename(msg: JVal, id: JVal) -> void { let D = doc_of(msg) let newname = jstr(jpath(msg, "params.newName"), null) if (D == null) or (newname == null) or (newname[0] == 0) { send_result(id, "null"); return } @@ -2250,14 +2250,14 @@ program LudicLsp { # ---- completion ---- var g_first: int = 1 - fn comp_item(o: Buf, label: ptr, kind: int, detail: ptr) -> void { + function comp_item(o: Buf, label: ptr, kind: int, detail: ptr) -> void { if g_first == 0 { buf_putc(o, 44) } g_first = 0 buf_puts(o, "{\"label\":"); jstr_out(o, label); buf_puts(o, ",\"kind\":"); buf_puti(o, kind) if (detail != null) and (detail[0] != 0) { buf_puts(o, ",\"detail\":"); jstr_out(o, detail) } buf_putc(o, 125) } - fn comp_scope(o: Buf, D: Doc, off: int, types_only: int) -> void { + function comp_scope(o: Buf, D: Doc, off: int, types_only: int) -> void { if types_only == 0 { var i = 0 while i < len(D.syms) { @@ -2285,12 +2285,12 @@ program LudicLsp { r = r + 1 } } - fn comp_types(o: Buf) -> void { + function comp_types(o: Buf) -> void { comp_item(o, "int", 14, "built-in type"); comp_item(o, "long", 14, "built-in type"); comp_item(o, "fixed", 14, "built-in type"); comp_item(o, "bool", 14, "built-in type") comp_item(o, "entity", 14, "built-in type"); comp_item(o, "str", 14, "built-in type"); comp_item(o, "ptr", 14, "built-in type"); comp_item(o, "void", 14, "built-in type") } - fn comp_builtins(o: Buf) -> void { var i = 0; while i < len(g_builtins) { comp_item(o, g_builtins[i], 3, g_bsigs[i]); i = i + 1 } } - fn on_completion(msg: JVal, id: JVal) -> void { + function comp_builtins(o: Buf) -> void { var i = 0; while i < len(g_builtins) { comp_item(o, g_builtins[i], 3, g_bsigs[i]); i = i + 1 } } + function on_completion(msg: JVal, id: JVal) -> void { let D = doc_of(msg) if (D == null) { send_result(id, "{\"isIncomplete\":false,\"items\":[]}"); return } let off = off_of(msg, D, "params.position") @@ -2350,7 +2350,7 @@ program LudicLsp { } # ---- inlay hints, signature help, semantic tokens, folding ---- - fn on_inlay_hint(msg: JVal, id: JVal) -> void { + function on_inlay_hint(msg: JVal, id: JVal) -> void { let D = doc_of(msg) if (D == null) { send_result(id, "[]"); return } let o = buf_new(); buf_putc(o, 91) @@ -2367,7 +2367,7 @@ program LudicLsp { } buf_putc(o, 93); send_result(id, buf_str(o)) } - fn on_signature_help(msg: JVal, id: JVal) -> void { + function on_signature_help(msg: JVal, id: JVal) -> void { let D = doc_of(msg) if (D == null) { send_result(id, "null"); return } let off = off_of(msg, D, "params.position") @@ -2412,7 +2412,7 @@ program LudicLsp { buf_puts(o, "}],\"activeSignature\":0,\"activeParameter\":"); buf_puti(o, arg); buf_putc(o, 125) send_result(id, buf_str(o)) } - fn on_semantic_tokens(msg: JVal, id: JVal) -> void { + function on_semantic_tokens(msg: JVal, id: JVal) -> void { let D = doc_of(msg) if (D == null) { send_result(id, "null"); return } classify(D) @@ -2442,7 +2442,7 @@ program LudicLsp { } buf_puts(o, "]}"); send_result(id, buf_str(o)) } - fn on_folding(msg: JVal, id: JVal) -> void { + function on_folding(msg: JVal, id: JVal) -> void { let D = doc_of(msg) if (D == null) { send_result(id, "[]"); return } let o = buf_new(); buf_putc(o, 91); var first = 1 @@ -2463,7 +2463,7 @@ program LudicLsp { } buf_putc(o, 93); send_result(id, buf_str(o)) } - fn on_workspace_symbol(msg: JVal, id: JVal) -> void { + function on_workspace_symbol(msg: JVal, id: JVal) -> void { let q = jstr(jpath(msg, "params.query"), "") let o = buf_new(); buf_putc(o, 91); var first = 1 var d = 0 @@ -2487,7 +2487,7 @@ program LudicLsp { } buf_putc(o, 93); send_result(id, buf_str(o)) } - fn contains_ci(hay: ptr, needle: ptr) -> bool { + function contains_ci(hay: ptr, needle: ptr) -> bool { let hn = cstr_len(hay); let nn = cstr_len(needle) if nn == 0 { return true } var i = 0 @@ -2500,7 +2500,7 @@ program LudicLsp { } return false } - fn on_formatting(msg: JVal, id: JVal) -> void { + function on_formatting(msg: JVal, id: JVal) -> void { let D = doc_of(msg) if (D == null) { send_result(id, "null"); return } var tab = jint(jpath(msg, "params.options.tabSize"), 2) @@ -2518,7 +2518,7 @@ program LudicLsp { } # ---- document lifecycle ---- - fn on_did_open(msg: JVal) -> void { + function on_did_open(msg: JVal) -> void { let uri = jstr(jpath(msg, "params.textDocument.uri"), null) let text = jstr(jpath(msg, "params.textDocument.text"), "") if (uri == null) { return } @@ -2529,7 +2529,7 @@ program LudicLsp { D.isopen = 1 publish_diag(D) } - fn on_did_change(msg: JVal) -> void { + function on_did_change(msg: JVal) -> void { let D = doc_of(msg) if (D == null) { return } let changes = jpath(msg, "params.contentChanges") @@ -2540,7 +2540,7 @@ program LudicLsp { reindex(D) publish_diag(D) } - fn on_did_close(msg: JVal) -> void { + function on_did_close(msg: JVal) -> void { let D = doc_of(msg) if (D == null) { return } D.isopen = 0 @@ -2548,7 +2548,7 @@ program LudicLsp { if (disk != null) { set_text(D, disk); reindex(D) } } - fn caps() -> ptr { + function caps() -> ptr { let o = buf_new() buf_puts(o, "{\"capabilities\":{") buf_puts(o, "\"positionEncoding\":\"utf-16\"") @@ -2572,14 +2572,14 @@ program LudicLsp { buf_puts(o, "},\"serverInfo\":{\"name\":\"ludic-lsp\",\"version\":\"1.0.0\"}}") return buf_str(o) } - fn on_initialize(msg: JVal, id: JVal) -> void { + function on_initialize(msg: JVal, id: JVal) -> void { let root = jpath(msg, "params.rootUri") if (root != null) and root.t == JSTR { g_root = uri_to_path(root.s) } if (g_root != null) { scan_root(g_root) } send_result(id, caps()) } - fn handle(msg: JVal) -> void { + function handle(msg: JVal) -> void { let method = jstr(jget(msg, "method"), null) let id = jget(msg, "id") if (method == null) { return } diff --git a/tools/ludic-tools/ludic_syntax.h b/tools/ludic-tools/ludic_syntax.h index 400bd02c..f2463b56 100644 --- a/tools/ludic-tools/ludic_syntax.h +++ b/tools/ludic-tools/ludic_syntax.h @@ -53,7 +53,7 @@ typedef struct { * declaration keyword, anything stmt() dispatches on is a statement keyword. */ static const char* LUDIC_KW_DECL[] = { "program","import","property","model","enum","ui", - "const","var","fn","extern","handler","entry","event","scene", 0 + "const","var","function","extern","handler","entry","event","scene", 0 }; static const char* LUDIC_KW_CLAUSE[] = { "phase","query","on","cancellable","public","layer","start", 0 diff --git a/tools/test-lsp.py b/tools/test-lsp.py index c8479f41..580cac89 100755 --- a/tools/test-lsp.py +++ b/tools/test-lsp.py @@ -246,7 +246,7 @@ def main(): fh.write("program R {\n" " property Pos { x: int = 0, y: int = 0 }\n" " property Vel { x: int = 0 }\n" - " fn helper(x: int) -> int { return x + 1 }\n" + " function helper(x: int) -> int { return x + 1 }\n" " handler A phase Update {\n" " for (p) in query [Pos] {\n" " let x = 5\n" @@ -275,7 +275,7 @@ def main(): lines_touched(field), [1, 7]) local = rename_at(position_of(atext, "let x = 5", 4)) check("renaming a local touches only that local", lines_touched(local), [6, 7]) - param = rename_at(position_of(atext, "fn helper(x", 10)) + param = rename_at(position_of(atext, "function helper(x", 16)) check("renaming a parameter touches only that function", lines_touched(param), [3]) check("renaming a parameter makes exactly two edits", len(param), 2) diff --git a/tools/x/build.ludic b/tools/x/build.ludic index 46d10416..11113b4f 100644 --- a/tools/x/build.ludic +++ b/tools/x/build.ludic @@ -5,7 +5,7 @@ # compile a Ludic source to a native binary in bin/ via ludicc + clang (-O2). # Returns true on success. Used for x itself and the editor tools. -fn build_tool(name: ptr, src: ptr) -> bool { +function build_tool(name: ptr, src: ptr) -> bool { let ll = `build/{name}.ll` if not shq(`bin/ludicc {src} --emit-llvm -o {ll} 2>/dev/null`) { print(`build failed: {name} (compile)`); return false } # write to a temp then move, so a running bin/x can rebuild itself in place @@ -18,7 +18,7 @@ fn build_tool(name: ptr, src: ptr) -> bool { # Both ludicc and ludic are the SAME multi-call binary assembled from the IR seed # with clang alone; invoked as `ludic` it compiles-and-runs, as `ludicc` it just # compiles. -fn cmd_build_cli() -> int { +function cmd_build_cli() -> int { run("mkdir -p bin build") print("cc: selfhost/ludicc.seed.ll -> bin/ludicc, bin/ludic (from the IR seed, no C compiler)") if not shq(`{cc()} selfhost/ludicc.seed.ll -o bin/ludicc`) { err("x: seed did not assemble\n"); return 1 } @@ -28,7 +28,7 @@ fn cmd_build_cli() -> int { } # ---- build: the whole toolchain (ludicc, ludic, x, ludic-fmt, ludic-lsp) ----- -fn cmd_build() -> int { +function cmd_build() -> int { if cmd_build_cli() != 0 { return 1 } print("ludicc: tools/x/main.ludic -> bin/x (the task runner rebuilds itself)") if not build_tool("x", "tools/x/main.ludic") { return 1 } @@ -44,7 +44,7 @@ fn cmd_build() -> int { # ---- app: compile a Ludic program to a native app ---------------------------- # usage: x app [--headless|--windowed] [--save-temps] # windowed is the default for a game; headless renders the last frame to out.ppm. -fn cmd_app() -> int { +function cmd_app() -> int { ensure_ludicc() var src = null var mode = 1 # 1 = windowed, 2 = headless diff --git a/tools/x/main.ludic b/tools/x/main.ludic index 7cb72321..68f77efc 100644 --- a/tools/x/main.ludic +++ b/tools/x/main.ludic @@ -18,7 +18,7 @@ program X { import "selfhost_test.ludic" import "test.ludic" - fn usage() -> void { + function usage() -> void { print("x — the Ludic task runner (run from the repository root)") print("") print("build & run:") @@ -43,7 +43,7 @@ program X { } # a positional argument, or a default when absent - fn argn(i: int, dflt: ptr) -> ptr { + function argn(i: int, dflt: ptr) -> ptr { if (i < arg_count()) { return arg(i) } return dflt } diff --git a/tools/x/prelude.ludic b/tools/x/prelude.ludic index 5bdedd5f..34e3b0e6 100644 --- a/tools/x/prelude.ludic +++ b/tools/x/prelude.ludic @@ -13,7 +13,7 @@ # ---- file IO ---------------------------------------------------------------- # read a whole file into a fresh NUL-terminated buffer (null if it cannot open) -fn read_file(path: ptr) -> ptr { +function read_file(path: ptr) -> ptr { let f = file_open(path, "rb") if (f == null) { return null } file_seek(f, 0, 2) @@ -27,7 +27,7 @@ fn read_file(path: ptr) -> ptr { } # overwrite `path` with `s`; returns false if it could not be opened -fn write_file(path: ptr, s: ptr) -> bool { +function write_file(path: ptr, s: ptr) -> bool { let f = file_open(path, "wb") if (f == null) { return false } file_write(f, s, len(s)) @@ -35,22 +35,22 @@ fn write_file(path: ptr, s: ptr) -> bool { return true } -fn file_exists(path: ptr) -> bool { return shq(`test -e {path}`) } -fn is_exec(path: ptr) -> bool { return shq(`test -x {path}`) } +function file_exists(path: ptr) -> bool { return shq(`test -e {path}`) } +function is_exec(path: ptr) -> bool { return shq(`test -x {path}`) } # is `a` newer than `b` (like the shell's `-nt`)? -fn newer(a: ptr, b: ptr) -> bool { return shq(`test {a} -nt {b}`) } +function newer(a: ptr, b: ptr) -> bool { return shq(`test {a} -nt {b}`) } # ---- process control -------------------------------------------------------- # `run` returns the raw wait status; the program's exit code is the high byte. -fn exit_code(st: int) -> int { return (st >> 8) & 255 } +function exit_code(st: int) -> int { return (st >> 8) & 255 } # run a command, returning its exit code (0 = success) -fn sh(cmd: ptr) -> int { return exit_code(run(cmd)) } +function sh(cmd: ptr) -> int { return exit_code(run(cmd)) } # run a command, true when it succeeded -fn shq(cmd: ptr) -> bool { return exit_code(run(cmd)) == 0 } +function shq(cmd: ptr) -> bool { return exit_code(run(cmd)) == 0 } # run `cmd` and return its stdout (stderr discarded). Never null. -fn capture(cmd: ptr) -> ptr { +function capture(cmd: ptr) -> ptr { let tmp = "/tmp/x_capture.out" run(`{cmd} > {tmp} 2>/dev/null`) let s = read_file(tmp) @@ -60,16 +60,16 @@ fn capture(cmd: ptr) -> ptr { # run `cmd`, join its output lines with single spaces and trim — the Ludic twin # of the shell idiom `$(cmd | tr '\n' ' ' | sed 's/ *$//')`. -fn capture_line(cmd: ptr) -> ptr { +function capture_line(cmd: ptr) -> ptr { return capture(`{cmd} | tr '\n' ' ' | sed 's/ *$//'`) } # the last line of a command's output (for one-line error messages) -fn capture_tail(cmd: ptr) -> ptr { +function capture_tail(cmd: ptr) -> ptr { return capture(`{cmd} 2>&1 | tail -1`) } -fn getenv_or(name: ptr, dflt: ptr) -> ptr { +function getenv_or(name: ptr, dflt: ptr) -> ptr { let v = getenv(name) if (v == null) { return dflt } return v @@ -78,40 +78,40 @@ fn getenv_or(name: ptr, dflt: ptr) -> ptr { # ---- stdout helpers --------------------------------------------------------- # write `s` with no trailing newline (print() always adds one) -fn out(s: ptr) -> void { file_write(file_stdout(), s, len(s)) } -fn err(s: ptr) -> void { file_write(file_stderr(), s, len(s)) } +function out(s: ptr) -> void { file_write(file_stdout(), s, len(s)) } +function err(s: ptr) -> void { file_write(file_stderr(), s, len(s)) } # an ESC byte — the lexer has no \033, so build it by hand -fn esc() -> ptr { let b = bytes(2); b[0] = 27; b[1] = 0; return b } -fn c_green() -> ptr { return esc() + "[32m" } -fn c_red() -> ptr { return esc() + "[31m" } -fn c_reset() -> ptr { return esc() + "[0m" } +function esc() -> ptr { let b = bytes(2); b[0] = 27; b[1] = 0; return b } +function c_green() -> ptr { return esc() + "[32m" } +function c_red() -> ptr { return esc() + "[31m" } +function c_reset() -> ptr { return esc() + "[0m" } # ---- the PASS/FAIL test harness --------------------------------------------- var PASS: int = 0 var FAIL: int = 0 -fn ok(msg: ptr) -> void { +function ok(msg: ptr) -> void { PASS = PASS + 1 print(` {c_green()}PASS{c_reset()} {msg}`) } -fn bad(msg: ptr) -> void { +function bad(msg: ptr) -> void { FAIL = FAIL + 1 print(` {c_red()}FAIL{c_reset()} {msg}`) } -fn bad2(msg: ptr, detail: ptr) -> void { +function bad2(msg: ptr, detail: ptr) -> void { bad(msg) print(` {detail}`) } # assert two strings equal, reporting the mismatch -fn check(label: ptr, got: ptr, want: ptr) -> void { +function check(label: ptr, got: ptr, want: ptr) -> void { if (got == want) { ok(label) } else { bad2(label, `expected [{want}] got [{got}]`) } } # print the "== N passed, M failed ==" footer and return the process exit code -fn report() -> int { +function report() -> int { print("") print(`== {str(PASS)} passed, {str(FAIL)} failed ==`) if (FAIL == 0) { return 0 } diff --git a/tools/x/selfhost.ludic b/tools/x/selfhost.ludic index 4c02936d..982ddfea 100644 --- a/tools/x/selfhost.ludic +++ b/tools/x/selfhost.ludic @@ -6,11 +6,11 @@ # now lives here once, in selfhost_frags(). # the C toolchain driver (clang) that assembles and links the emitted IR -fn cc() -> ptr { return getenv_or("LUDIC_CC", "clang") } +function cc() -> ptr { return getenv_or("LUDIC_CC", "clang") } # the self-host compiler's source fragments, in link order. This is THE list; # the old scripts each carried their own copy. -fn selfhost_frags() -> []ptr { +function selfhost_frags() -> []ptr { let f = new []ptr push(f, "selfhost/str.ludic") push(f, "selfhost/buf.ludic") @@ -52,7 +52,7 @@ fn selfhost_frags() -> []ptr { # concatenate the fragments into `program SelfHost { ... }` at `outpath`. # This is the pure-Ludic equivalent of the scripts' `{ echo; for; cat; }` block. -fn write_selfhost_src(outpath: ptr) -> bool { +function write_selfhost_src(outpath: ptr) -> bool { let f = file_open(outpath, "wb") if (f == null) { return false } let hdr = "program SelfHost {\n" @@ -72,13 +72,13 @@ fn write_selfhost_src(outpath: ptr) -> bool { } # count the lines in a file (for the progress notes the scripts printed) -fn line_count(path: ptr) -> ptr { +function line_count(path: ptr) -> ptr { return capture_line(`wc -l < {path}`) } # ensure bin/ludicc exists and is current with the seed. Built from the checked-in # IR seed with clang alone — no C compiler is ever involved. -fn ensure_ludicc() -> void { +function ensure_ludicc() -> void { run("mkdir -p bin build") if (not is_exec("bin/ludicc")) or newer("selfhost/ludicc.seed.ll", "bin/ludicc") { print("cc: selfhost/ludicc.seed.ll -> bin/ludicc (from the IR seed, no C compiler)") @@ -90,7 +90,7 @@ fn ensure_ludicc() -> void { # ---- selfhost-build: assemble + compile the self-host compiler --------------- # usage: x selfhost-build [ludicc] [outbin] (defaults: bin/ludicc, build/selfhost) -fn cmd_selfhost_build(lc: ptr, outbin: ptr) -> int { +function cmd_selfhost_build(lc: ptr, outbin: ptr) -> int { run("mkdir -p build") let src = "build/selfhost.ludic" if not write_selfhost_src(src) { err("x: cannot write build/selfhost.ludic\n"); return 1 } @@ -104,7 +104,7 @@ fn cmd_selfhost_build(lc: ptr, outbin: ptr) -> int { # ---- sh-compile: compile one .ludic with a given selfhost binary and link ---- # usage: x sh-compile -fn cmd_sh_compile(shbin: ptr, in: ptr, outbin: ptr) -> int { +function cmd_sh_compile(shbin: ptr, in: ptr, outbin: ptr) -> int { if not shq(`{shbin} {in} > {outbin}.ll`) { return 1 } if not shq(`{cc()} {outbin}.ll -o {outbin}`) { return 1 } return 0 @@ -116,7 +116,7 @@ fn cmd_sh_compile(shbin: ptr, in: ptr, outbin: ptr) -> int { # the quiet core, reused by the test suites; returns true on success. On failure # the self-host/link diagnostics are left in /tmp/x_gb.err. -fn game_build_ok(shbin: ptr, game: ptr, outbin: ptr) -> bool { +function game_build_ok(shbin: ptr, game: ptr, outbin: ptr) -> bool { let ll = `{outbin}.ll` if not shq(`{shbin} {game} > {ll} 2>/tmp/x_gb.err`) { return false } if not shq(`{cc()} -O2 {ll} -o {outbin} 2>/tmp/x_gb.err`) { return false } @@ -125,7 +125,7 @@ fn game_build_ok(shbin: ptr, game: ptr, outbin: ptr) -> bool { } # usage: x game-build -fn cmd_game_build(shbin: ptr, game: ptr, outbin: ptr) -> int { +function cmd_game_build(shbin: ptr, game: ptr, outbin: ptr) -> int { if game_build_ok(shbin, game, outbin) { print(`built {outbin}`); return 0 } print("build failed:") out(capture("grep -i error /tmp/x_gb.err | head -5")) @@ -136,7 +136,7 @@ fn cmd_game_build(shbin: ptr, game: ptr, outbin: ptr) -> int { # stage0 bin/ludicc compiles the self-host source -> gen1 (built by a different # compiler, so its IR legitimately differs); gen1 -> gen2, gen2 -> gen3, and # gen2.ll == gen3.ll is the fixpoint that proves the compiler reproduces itself. -fn cmd_bootstrap() -> int { +function cmd_bootstrap() -> int { ensure_ludicc() run("mkdir -p build/boot") if cmd_selfhost_build("bin/ludicc", "build/boot/gen1") != 0 { print("FAIL: stage0 build"); return 1 } @@ -163,7 +163,7 @@ fn cmd_bootstrap() -> int { # The seed is the compiler's own IR, a proven fixed point: clang assembles it, # the seed-built compiler compiles its own source, and the result must equal the # seed byte for byte. -fn cmd_bootstrap_cfree() -> int { +function cmd_bootstrap_cfree() -> int { run("mkdir -p build/cfree") if not shq(`{cc()} selfhost/ludicc.seed.ll -o build/cfree/sh_seed 2>/dev/null`) { print("FAIL: assemble seed"); return 1 } print(" seed.ll --clang--> sh_seed (no C compiler used)") @@ -184,7 +184,7 @@ fn cmd_bootstrap_cfree() -> int { # fixed point of the NEW compiler, not a one-step image of the old. Falls back to # bin/ludicc only if the seed itself no longer assembles. Verify with # `x bootstrap-cfree` afterwards. -fn cmd_reseed() -> int { +function cmd_reseed() -> int { run("mkdir -p build/cfree") if not write_selfhost_src("build/cfree/selfhost.ludic") { print("FAIL: write source"); return 1 } if shq(`{cc()} selfhost/ludicc.seed.ll -o build/cfree/sh_old 2>/dev/null`) { diff --git a/tools/x/selfhost_test.ludic b/tools/x/selfhost_test.ludic index 8dc0c866..1c0dc49d 100644 --- a/tools/x/selfhost_test.ludic +++ b/tools/x/selfhost_test.ludic @@ -3,7 +3,7 @@ # fixpoint). Replaces selfhost/test.sh. # compile selfhost/tests/.ludic with bin/ludicc, assemble, run, compare -fn sh_case(name: ptr, exp: ptr) -> void { +function sh_case(name: ptr, exp: ptr) -> void { let ll = `/tmp/x_sh_{name}.ll` let er = `/tmp/x_sh_{name}.err` let bn = `/tmp/x_sh_{name}` @@ -16,14 +16,14 @@ fn sh_case(name: ptr, exp: ptr) -> void { } # compile examples/.ludic as a game and diff its render against the golden -fn game_case(name: ptr, keys: ptr) -> void { +function game_case(name: ptr, keys: ptr) -> void { if not game_build_ok("bin/ludicc", `examples/{name}.ludic`, `/tmp/x_g_{name}`) { bad(`{name}: build`); return } run(`printf '%s' '{keys}' | /tmp/x_g_{name} >/dev/null 2>&1`) if shq(`cmp -s selfhost/golden/{name}.ppm out.ppm`) { ok(`{name} matches the golden render`) } else { bad(`{name}: differs from golden`) } } -fn cmd_selfhost_test() -> int { +function cmd_selfhost_test() -> int { PASS = 0 FAIL = 0 diff --git a/tools/x/test.ludic b/tools/x/test.ludic index 259fb931..4bb713f9 100644 --- a/tools/x/test.ludic +++ b/tools/x/test.ludic @@ -9,7 +9,7 @@ # compile a feature example as a game, run it (optional keys on stdin) and match # its stdout against the expected space-joined output. -fn feat_case(name: ptr, keys: ptr, exp: ptr, label: ptr) -> void { +function feat_case(name: ptr, keys: ptr, exp: ptr, label: ptr) -> void { if not game_build_ok("bin/ludicc", `examples/{name}.ludic`, `/tmp/x_e_{name}`) { bad2(label, capture_line("grep -i error /tmp/x_gb.err | head -1")); return } @@ -20,7 +20,7 @@ fn feat_case(name: ptr, keys: ptr, exp: ptr, label: ptr) -> void { } # a "does it still compile" smoke test (parse -> lower -> link), no run -fn qsmoke(name: ptr) -> void { +function qsmoke(name: ptr) -> void { if game_build_ok("bin/ludicc", `examples/{name}.ludic`, `/tmp/x_q_{name}`) { ok(`{name} compiles (@Queries desugars to S_QUERY)`) } else { bad2(name, capture_line("grep -i error /tmp/x_gb.err | head -1")) } @@ -28,7 +28,7 @@ fn qsmoke(name: ptr) -> void { # a pure-Ludic example that drives and asserts itself: compile headless to IR, # let clang assemble it, run, and compare stdout. No C host and no C compiled. -fn net_case(name: ptr, exp: ptr) -> void { +function net_case(name: ptr, exp: ptr) -> void { let ll = `/tmp/x_n_{name}.ll` let log = `/tmp/x_n_{name}.out` if not shq(`bin/ludicc --headless examples/{name}.ludic --emit-llvm -o {ll} > {log} 2>&1`) { @@ -43,7 +43,7 @@ fn net_case(name: ptr, exp: ptr) -> void { if (got == exp) { ok(`{name} ({got})`) } else { bad2(name, `got [{got}] want [{exp}]`) } } -fn cmd_test() -> int { +function cmd_test() -> int { PASS = 0 FAIL = 0 diff --git a/tools/x/tools.ludic b/tools/x/tools.ludic index c0c7aab9..6211eb28 100644 --- a/tools/x/tools.ludic +++ b/tools/x/tools.ludic @@ -7,7 +7,7 @@ # driven through python3/node (they are not project shell scripts), invoked here. # refresh one editor-copy of a shared grammar file if it has drifted -fn sync_one(shared: ptr, dst: ptr) -> void { +function sync_one(shared: ptr, dst: ptr) -> void { if not shq(`cmp -s {shared} {dst}`) { run(`cp {shared} {dst}`) print(`sync: {shared} -> {dst}`) @@ -16,7 +16,7 @@ fn sync_one(shared: ptr, dst: ptr) -> void { # VS Code needs the grammar inside its own extension directory, so it gets a # copy that build refreshes rather than trusts (test-tools asserts they match). -fn sync_vscode_grammar() -> void { +function sync_vscode_grammar() -> void { if not shq("test -d tools/editors/vscode") { return } run("mkdir -p tools/editors/vscode/syntaxes") sync_one("tools/editors/shared/ludic.tmLanguage.json", "tools/editors/vscode/syntaxes/ludic.tmLanguage.json") @@ -25,7 +25,7 @@ fn sync_vscode_grammar() -> void { } # is `flag` present anywhere in argv? -fn has_flag(flag: ptr) -> bool { +function has_flag(flag: ptr) -> bool { var i = 2 while i < arg_count() { if (arg(i) == flag) { return true }; i = i + 1 } return false @@ -33,7 +33,7 @@ fn has_flag(flag: ptr) -> bool { # ---- tools: build the editor toolchain -------------------------------------- # usage: x tools [--install] [--test] -fn cmd_tools() -> int { +function cmd_tools() -> int { ensure_ludicc() print("ludicc: tools/ludic-tools/fmt.ludic -> bin/ludic-fmt (Ludic -> LLVM IR -> binary, no C)") if not build_tool("ludic-fmt", "tools/ludic-tools/fmt.ludic") { return 1 } @@ -53,7 +53,7 @@ fn cmd_tools() -> int { } # ---- test-tools: the editor-toolchain regression suite ----------------------- -fn cmd_test_tools() -> int { +function cmd_test_tools() -> int { PASS = 0 FAIL = 0 let fmt = "bin/ludic-fmt" @@ -172,13 +172,13 @@ fn cmd_test_tools() -> int { return report() } -fn test_json(path: ptr) -> void { +function test_json(path: ptr) -> void { let bn = capture_line(`basename {path}`) if shq(`python3 -c 'import json,sys;json.load(open(sys.argv[1]))' {path} 2>/dev/null`) { ok(`valid JSON: {bn}`) } else { bad(`invalid JSON: {path}`) } } -fn test_xml(path: ptr) -> void { +function test_xml(path: ptr) -> void { let bn = capture_line(`basename {path}`) if shq(`python3 -c 'import xml.dom.minidom,sys;xml.dom.minidom.parse(sys.argv[1])' {path} 2>/dev/null`) { ok(`valid XML: {bn}`)