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 <noreply@anthropic.com>
This commit is contained in:
parent
2f19c8d8e2
commit
4c48077d68
86 changed files with 793 additions and 793 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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])
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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<n>" 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 `@<sym>` 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_<M>_spawn`, `model_<M>_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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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") }
|
||||
|
|
|
|||
|
|
@ -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_<d.s> (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_<Name> /
|
||||
# @scene_exit_<Name>, 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")
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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 <ret> @<sym>(<argtys>)` 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")
|
||||
|
|
|
|||
|
|
@ -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 " <r> = <rest>\n" and return r
|
||||
fn emit_bind(rest: ptr) -> ptr { let r = nreg(); emit(" "); emit(r); emit(" = "); emit(rest); emit("\n"); return r }
|
||||
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).
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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])
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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`)
|
||||
|
|
|
|||
|
|
@ -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_<M>(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_<M>(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_<E> 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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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_<M>)
|
||||
}
|
||||
|
||||
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. `<P> on <e>` 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 `<Model>` / `<Handler>` 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")
|
||||
|
|
|
|||
|
|
@ -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_<E>` 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 }
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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_<name>: 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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 <expr>`, 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 <constraints> { 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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue