feat(stdlib): namespaced standard library (issue #2)

Implement the bulk of the namespaced-stdlib proposal (workshopsoft/ludic#2):
156 namespace methods across Math, Text, List, Ease, Collide, World, Net,
Sys, Save, Mem, extended Screen, Color functions, extended Random, and Time.
All deterministic fixed-point; self-hosting (C-free bootstrap fixpoint holds).

Compiler (selfhost/):
- Math.*: sqrt/sin/cos/tan/atan2/asin/acos (fixed-point runtime prelude —
  bit-by-bit isqrt, 256-entry interpolated sine table, Ross atan2), plus
  hypot/dist/dist2/deg_to_rad/rad_to_deg/posmod/wrap/ping_pong/snapped/
  move_toward/smoothstep/lerp/remap/sign/floor/ceil/round.
- Text.* (complete): upper/lower/trim/repeat/pad, split/join/replace,
  and the libc-backed queries.
- List.* (complete): insert/remove_at/remove/sort plus the earlier ops.
- Ease.* (in/out/in_out/back/bounce) and Collide.* (rects/point_rect/
  circles/rect_circle).
- Phase 3: World/Net/Sys/Save namespaced over the bare builtins (byte-
  identical IR) and Mem.* (bytes/words/copy/fill/peek/poke).
- Screen.* extended (line/circle/fill_circle/triangle/fill_triangle via new
  runtime primitives; sprite/sprite_scaled aliases), Color.* functions,
  Random.* (value/int/sign), Time.* (frame/delta/elapsed/now — new
  game-loop frame counter).
- Fix a lexer bug: fixed-point literals with >4 fractional digits overflowed.

Docs & tooling:
- 129 new per-symbol doc pages; gen.py made data-driven (namespaces
  discovered from the docs, no hardcoded list); new check-impl.py enforces
  that every implemented namespace method / keyword / type / phase has a
  doc page, wired into `x test-tools`. Document the previously-undocumented
  keywords (break/continue/where/entry/new/public + and/or/not tokens).
- LSP: namespaced signature help (ns_method_sig) covering every namespace.

Tests: 12 new self-host/regression tests + a golden render for the drawing
primitives. All suites green (selfhost 21, regression 45, tools 29).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-08-30 00:26:19 +03:00
parent ff15c4e01d
commit a38195128f
235 changed files with 24676 additions and 7762 deletions

View file

@ -1,41 +0,0 @@
#!/bin/bash
# bootstrap-cfree.sh — rebuild the self-hosted compiler with NO C compiler.
#
# The C `ludicc` is retired to a one-time seed: selfhost/ludicc.seed.ll is the
# self-host compiler's own LLVM IR, a proven fixed point. From it plus clang
# (an IR assembler, the same floor Rust and Swift stand on) the compiler
# rebuilds itself and reproduces its own IR — the C source is never invoked.
#
# seed.ll --clang--> sh_seed
# sh_seed compiles selfhost.ludic -> out.ll
# assert out.ll == seed.ll (the seed is a true fixed point)
set -u
cd "$(dirname "$0")/.."
CC="${LUDIC_CC:-clang}"
B=build/cfree
mkdir -p "$B"
# assemble the compiler straight from the checked-in seed — no C involved
$CC selfhost/ludicc.seed.ll -o "$B/sh_seed" 2>/dev/null || { echo "FAIL: assemble seed"; exit 1; }
echo " seed.ll --clang--> sh_seed (no C compiler used)"
# regenerate the concatenated source the same way build.sh does (pure shell)
FRAGS="selfhost/str.ludic selfhost/buf.ludic selfhost/io.ludic selfhost/ast.ludic
selfhost/lex.ludic selfhost/parse.ludic selfhost/parse_game.ludic selfhost/emit_core.ludic selfhost/emit_head.ludic
selfhost/emit_addr.ludic selfhost/emit_intrin.ludic selfhost/emit_intrin2.ludic selfhost/emit_math.ludic selfhost/emit_new.ludic
selfhost/emit_expr.ludic selfhost/emit_stmt.ludic selfhost/emit_ecs.ludic selfhost/emit_query.ludic selfhost/emit_spawn.ludic selfhost/emit_game.ludic selfhost/emit_machine.ludic selfhost/emit_save.ludic
selfhost/emit_net.ludic selfhost/emit_ui.ludic selfhost/emit_decl.ludic selfhost/main.ludic"
{ echo "program SelfHost {"; for f in $FRAGS; do cat "$f"; echo; done; echo "}"; } > "$B/selfhost.ludic"
# the seed-built compiler compiles its own source
"$B/sh_seed" "$B/selfhost.ludic" > "$B/out.ll" 2>/dev/null || { echo "FAIL: seed compiler self-compile"; exit 1; }
echo " sh_seed compiles selfhost.ludic -> out.ll ($(wc -l < "$B/out.ll" | tr -d ' ') lines)"
if cmp -s "$B/out.ll" selfhost/ludicc.seed.ll; then
echo " ✅ out.ll == seed.ll — the compiler rebuilds itself with no C compiler"
exit 0
else
echo " ❌ out.ll != seed.ll (seed is stale — regenerate with ./selfhost/reseed.sh)"
diff "$B/out.ll" selfhost/ludicc.seed.ll | head
exit 1
fi

View file

@ -1,40 +0,0 @@
#!/bin/bash
# bootstrap.sh — the self-hosting proof.
#
# stage0: the C compiler (build/ludicc) compiles the self-host source -> gen1
# stage1: gen1 compiles the self-host source -> gen2
# stage2: gen2 compiles the self-host source -> gen3
# assert: gen2.ll == gen3.ll (the compiler reproduces itself exactly)
#
# gen1 is built by a *different* compiler (the C one), so its IR legitimately
# differs; gen2 == gen3 is the fixpoint that proves independence.
set -u
cd "$(dirname "$0")/.."
CC="${LUDIC_CC:-clang}"
B=build/boot
mkdir -p "$B"
say() { printf " %s\n" "$1"; }
# stage 0: C compiler builds the self-host compiler (also writes build/selfhost.ludic)
./selfhost/build.sh build/ludicc "$B/gen1" >/dev/null 2>&1 || { echo "FAIL: stage0 build"; exit 1; }
say "stage0: C ludicc -> gen1 (self-host compiler)"
# stage 1: gen1 compiles the self-host source to IR, assemble -> gen2
"$B/gen1" build/selfhost.ludic > "$B/gen2.ll" 2>/dev/null || { echo "FAIL: gen1 self-compile"; exit 1; }
$CC "$B/gen2.ll" -o "$B/gen2" 2>/dev/null || { echo "FAIL: gen2 assemble"; exit 1; }
say "stage1: gen1 -> gen2.ll ($(wc -l < "$B/gen2.ll" | tr -d ' ') lines) -> gen2"
# stage 2: gen2 compiles the self-host source to IR -> gen3
"$B/gen2" build/selfhost.ludic > "$B/gen3.ll" 2>/dev/null || { echo "FAIL: gen2 self-compile"; exit 1; }
say "stage2: gen2 -> gen3.ll ($(wc -l < "$B/gen3.ll" | tr -d ' ') lines)"
# the fixpoint
if cmp -s "$B/gen2.ll" "$B/gen3.ll"; then
rm -f build/selfhost.ludic
echo " ✅ FIXPOINT: gen2.ll == gen3.ll — the self-hosted compiler reproduces itself"
exit 0
else
echo " ❌ gen2.ll != gen3.ll"
diff "$B/gen2.ll" "$B/gen3.ll" | head
exit 1
fi

View file

@ -1,40 +0,0 @@
#!/bin/bash
# build.sh — assemble the self-host compiler from its fragments and compile it
# with the given ludicc (default: the C build/ludicc).
# ./selfhost/build.sh [ludicc] [outbin]
set -u
cd "$(dirname "$0")/.."
LC="${1:-build/ludicc}"
OUT="${2:-build/selfhost}"
mkdir -p build
FRAGS="selfhost/str.ludic
selfhost/buf.ludic
selfhost/io.ludic
selfhost/ast.ludic
selfhost/lex.ludic
selfhost/parse.ludic
selfhost/parse_game.ludic
selfhost/emit_core.ludic
selfhost/emit_head.ludic
selfhost/emit_addr.ludic
selfhost/emit_intrin.ludic
selfhost/emit_intrin2.ludic
selfhost/emit_math.ludic
selfhost/emit_new.ludic
selfhost/emit_expr.ludic
selfhost/emit_stmt.ludic
selfhost/emit_ecs.ludic
selfhost/emit_query.ludic
selfhost/emit_spawn.ludic
selfhost/emit_game.ludic
selfhost/emit_machine.ludic
selfhost/emit_save.ludic
selfhost/emit_net.ludic
selfhost/emit_ui.ludic
selfhost/emit_decl.ludic
selfhost/main.ludic"
SRC=build/selfhost.ludic
{ echo "program SelfHost {"; for f in $FRAGS; do cat "$f"; echo; done; echo "}"; } > "$SRC"
# the compiler emits IR to stdout; clang assembles and links it
"$LC" "$SRC" > "$OUT.ll" 2>/dev/null && ${LUDIC_CC:-clang} "$OUT.ll" -o "$OUT" 2>/dev/null
rc=$?; rm -f "$OUT.ll"; [ $rc -eq 0 ]

View file

@ -1,8 +0,0 @@
#!/bin/bash
# compile.sh — compile a .ludic file with the self-hosted compiler and link it.
# ./selfhost/compile.sh <selfhost-binary> <input.ludic> <output-binary>
set -eu
cd "$(dirname "$0")/.."
SH="$1"; IN="$2"; OUT="$3"
"$SH" "$IN" > "$OUT.ll"
clang "$OUT.ll" -o "$OUT"

View file

@ -0,0 +1,86 @@
# emit_collide.ludic — the Collide.* namespace: 2D overlap tests on plain
# integer coordinates (pixels or tiles). Rectangles are (x, y, w, h) with the
# 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 {
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 {
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}`)
let yy = emit_bind(`mul i64 {dy64}, {dy64}`)
return emit_bind(`add i64 {xx}, {yy}`)
}
# max(lo, min(v, hi)) — clamp v into [lo, hi]
fn 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 {
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])
let axw = emit_bind(`add i32 {ax.code}, {aw.code}`)
let bxw = emit_bind(`add i32 {bx.code}, {bw.code}`)
let ayh = emit_bind(`add i32 {ay.code}, {ah.code}`)
let byh = emit_bind(`add i32 {by.code}, {bh.code}`)
let c1 = emit_bind(`icmp slt i32 {ax.code}, {bxw}`)
let c2 = emit_bind(`icmp slt i32 {bx.code}, {axw}`)
let c3 = emit_bind(`icmp slt i32 {ay.code}, {byh}`)
let c4 = emit_bind(`icmp slt i32 {by.code}, {ayh}`)
let x = emit_bind(`and i1 {c1}, {c2}`)
let y = emit_bind(`and i1 {c3}, {c4}`)
let r = emit_bind(`and i1 {x}, {y}`)
return val(emit_bind(`zext i1 {r} to i32`), "bool")
}
if (meth == "point_rect") { # is a point inside a rect
let px = emit_expr(e.kids[0]); let py = emit_expr(e.kids[1])
let rx = emit_expr(e.kids[2]); let ry = emit_expr(e.kids[3]); let rw = emit_expr(e.kids[4]); let rh = emit_expr(e.kids[5])
let rxw = emit_bind(`add i32 {rx.code}, {rw.code}`)
let ryh = emit_bind(`add i32 {ry.code}, {rh.code}`)
let c1 = emit_bind(`icmp sge i32 {px.code}, {rx.code}`)
let c2 = emit_bind(`icmp slt i32 {px.code}, {rxw}`)
let c3 = emit_bind(`icmp sge i32 {py.code}, {ry.code}`)
let c4 = emit_bind(`icmp slt i32 {py.code}, {ryh}`)
let x = emit_bind(`and i1 {c1}, {c2}`)
let y = emit_bind(`and i1 {c3}, {c4}`)
let r = emit_bind(`and i1 {x}, {y}`)
return val(emit_bind(`zext i1 {r} to i32`), "bool")
}
if (meth == "circles") { # do two circles overlap
let ax = emit_expr(e.kids[0]); let ay = emit_expr(e.kids[1]); let ar = emit_expr(e.kids[2])
let bx = emit_expr(e.kids[3]); let by = emit_expr(e.kids[4]); let br = emit_expr(e.kids[5])
let dx = emit_bind(`sub i32 {ax.code}, {bx.code}`)
let dy = emit_bind(`sub i32 {ay.code}, {by.code}`)
let d2 = coll_sq_sum(dx, dy)
let rs = emit_bind(`add i32 {ar.code}, {br.code}`)
let rs64 = emit_bind(`sext i32 {rs} to i64`)
let r2 = emit_bind(`mul i64 {rs64}, {rs64}`)
let le = emit_bind(`icmp sle i64 {d2}, {r2}`)
return val(emit_bind(`zext i1 {le} to i32`), "bool")
}
# rect_circle: nearest point on the rect to the circle centre, within radius
let rx = emit_expr(e.kids[0]); let ry = emit_expr(e.kids[1]); let rw = emit_expr(e.kids[2]); let rh = emit_expr(e.kids[3])
let cx = emit_expr(e.kids[4]); let cy = emit_expr(e.kids[5]); let cr = emit_expr(e.kids[6])
let rxw = emit_bind(`add i32 {rx.code}, {rw.code}`)
let ryh = emit_bind(`add i32 {ry.code}, {rh.code}`)
let clx = coll_clamp(cx.code, rx.code, rxw)
let cly = coll_clamp(cy.code, ry.code, ryh)
let dx = emit_bind(`sub i32 {cx.code}, {clx}`)
let dy = emit_bind(`sub i32 {cy.code}, {cly}`)
let d2 = coll_sq_sum(dx, dy)
let cr64 = emit_bind(`sext i32 {cr.code} to i64`)
let r2 = emit_bind(`mul i64 {cr64}, {cr64}`)
let le = emit_bind(`icmp sle i64 {d2}, {r2}`)
return val(emit_bind(`zext i1 {le} to i32`), "bool")
}

View file

@ -0,0 +1,74 @@
# emit_colorfn.ludic — the Color.* function surface (Color.Name constants are
# resolved at compile time elsewhere). Colors are 0x00RRGGBB ints; these build
# 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 {
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 {
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 {
let r16 = emit_bind(`shl i32 {r}, 16`)
let g8 = emit_bind(`shl i32 {g}, 8`)
let rg = emit_bind(`or i32 {r16}, {g8}`)
return emit_bind(`or i32 {rg}, {b}`)
}
# 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 {
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 {
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")
}
if (meth == "rgba") { # rgba(r, g, b, a) -> 0xAARRGGBB
let r = emit_expr(e.kids[0]); let g = emit_expr(e.kids[1]); let b = emit_expr(e.kids[2]); let a = emit_expr(e.kids[3])
let base = color_pack(r.code, g.code, b.code)
let a24 = emit_bind(`shl i32 {a.code}, 24`)
return val(emit_bind(`or i32 {base}, {a24}`), "int")
}
if (meth == "with_alpha") { # replace the alpha byte
let c = emit_expr(e.kids[0]); let a = emit_expr(e.kids[1])
let rgb = emit_bind(`and i32 {c.code}, 16777215`) # c & 0x00FFFFFF
let a24 = emit_bind(`shl i32 {a.code}, 24`)
return val(emit_bind(`or i32 {rgb}, {a24}`), "int")
}
if (meth == "lerp") { # blend two colors by t (fixed 0..1)
let c0 = emit_expr(e.kids[0]); let c1 = emit_expr(e.kids[1]); let t = emit_expr(e.kids[2])
let r = color_lerp_ch(color_ch(c0.code, "16"), color_ch(c1.code, "16"), t.code)
let g = color_lerp_ch(color_ch(c0.code, "8"), color_ch(c1.code, "8"), t.code)
let b = color_lerp_ch(color_ch(c0.code, "0"), color_ch(c1.code, "0"), t.code)
return val(color_pack(r, g, b), "int")
}
if (meth == "darken") { # scale channels by (1 - amount)
let c = emit_expr(e.kids[0]); let amt = emit_expr(e.kids[1])
let factor = emit_bind(`sub i32 65536, {amt.code}`)
let r0 = color_ch(c.code, "16"); let g0 = color_ch(c.code, "8"); let b0 = color_ch(c.code, "0")
let rm = emit_bind(`mul i32 {r0}, {factor}`); let r = emit_bind(`ashr i32 {rm}, 16`)
let gm = emit_bind(`mul i32 {g0}, {factor}`); let g = emit_bind(`ashr i32 {gm}, 16`)
let bm = emit_bind(`mul i32 {b0}, {factor}`); let b = emit_bind(`ashr i32 {bm}, 16`)
return val(color_pack(r, g, b), "int")
}
# lighten: ch + (255 - ch) * amount
let c = emit_expr(e.kids[0]); let amt = emit_expr(e.kids[1])
let r0 = color_ch(c.code, "16"); let g0 = color_ch(c.code, "8"); let b0 = color_ch(c.code, "0")
let rr = emit_bind(`sub i32 255, {r0}`); let rm = emit_bind(`mul i32 {rr}, {amt.code}`); let rt = emit_bind(`ashr i32 {rm}, 16`); let r = emit_bind(`add i32 {r0}, {rt}`)
let gr = emit_bind(`sub i32 255, {g0}`); let gm = emit_bind(`mul i32 {gr}, {amt.code}`); let gt = emit_bind(`ashr i32 {gm}, 16`); let g = emit_bind(`add i32 {g0}, {gt}`)
let br = emit_bind(`sub i32 255, {b0}`); let bm = emit_bind(`mul i32 {br}, {amt.code}`); let bt = emit_bind(`ashr i32 {bm}, 16`); let b = emit_bind(`add i32 {b0}, {bt}`)
return val(color_pack(r, g, b), "int")
}

View file

@ -21,6 +21,9 @@ var nloc: int = 0
var g_uses_str: bool = false # a `str + str` / `str == str` was emitted -> emit the prelude
var g_uses_intstr: bool = false # `str(int)` was emitted -> emit the int->string prelude
var g_uses_strslice: bool = false # `s[a..b]` was emitted -> emit the substring prelude
var g_uses_mathrt: bool = false # Math.sqrt/sin/cos/tan was emitted -> emit the math runtime prelude
var g_uses_textrt: bool = false # Text.upper/lower/trim/repeat/pad was emitted -> emit the text builders
var g_uses_textrt2: bool = false # Text.split/join/replace was emitted -> emit the string/slice builders
# loop targets for break/continue (innermost last)
var brk_lbl: []ptr

View file

@ -99,6 +99,9 @@ fn emit_program() -> void {
if g_uses_str { emit_str_prelude() } # @fn_str_eq / @fn_str_concat, after all uses are seen
if g_uses_intstr { emit_int_str() } # @fn_int_str, for str(int) in interpolation
if g_uses_strslice { emit_str_slice() } # @fn_str_slice, for s[a..b]
if g_uses_mathrt { emit_math_prelude() } # @fn_fx_sqrt / @fn_fx_sin + the sine table
if g_uses_textrt { emit_text_prelude() } # @fn_str_upper/lower/trim/repeat/pad builders
if g_uses_textrt2 { emit_text2_prelude() } # @fn_str_replace/join/split builders
}
# Flush the emitted IR. With a null path it goes to stdout (the pipe the shell

52
selfhost/emit_ease.ludic Normal file
View file

@ -0,0 +1,52 @@
# emit_ease.ludic — the Ease.* namespace: tween curves over a normalized amount
# 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 {
if (meth == "in") or (meth == "out") or (meth == "in_out") { return true }
if (meth == "back") or (meth == "bounce") { return true }
return false
}
# n1 * u * u (u a fixed code) -> code of a fixed i32
fn 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 {
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")
}
if (meth == "out") { # ease-out quad: t*(2 - t)
let inv = emit_bind(`sub i32 131072, {t.code}`)
return val(fx_mul_code(t.code, inv), "fixed")
}
if (meth == "in_out") { # smooth ease-in-out: 3t^2 - 2t^3
let t2 = fx_mul_code(t.code, t.code)
let t3 = fx_mul_code(t2, t.code)
let three = emit_bind(`mul i32 {t2}, 3`)
let two = emit_bind(`mul i32 {t3}, 2`)
return val(emit_bind(`sub i32 {three}, {two}`), "fixed")
}
if (meth == "back") { # ease-in-back (overshoots below 0)
let t2 = fx_mul_code(t.code, t.code)
let t3 = fx_mul_code(t2, t.code)
let a = fx_mul_code(t3, "177051") # 2.70158 * t^3
let b = fx_mul_code(t2, "111515") # 1.70158 * t^2
return val(emit_bind(`sub i32 {a}, {b}`), "fixed")
}
# ease-out bounce: four parabolic segments, selected by t (all computed, then
# picked branch-free). Shifts/offsets are the standard 2.75-denominator set.
let sA = ease_bounce_seg(t.code)
let uB = emit_bind(`sub i32 {t.code}, 35747`); let sB0 = ease_bounce_seg(uB); let sB = emit_bind(`add i32 {sB0}, 49152`)
let uC = emit_bind(`sub i32 {t.code}, 53620`); let sC0 = ease_bounce_seg(uC); let sC = emit_bind(`add i32 {sC0}, 61440`)
let uD = emit_bind(`sub i32 {t.code}, 62557`); let sD0 = ease_bounce_seg(uD); let sD = emit_bind(`add i32 {sD0}, 64512`)
let cCD = emit_bind(`icmp slt i32 {t.code}, 59578`)
let rCD = emit_bind(`select i1 {cCD}, i32 {sC}, i32 {sD}`)
let cB = emit_bind(`icmp slt i32 {t.code}, 47663`)
let rB = emit_bind(`select i1 {cB}, i32 {sB}, i32 {rCD}`)
let cA = emit_bind(`icmp slt i32 {t.code}, 23831`)
return val(emit_bind(`select i1 {cA}, i32 {sA}, i32 {rB}`), "fixed")
}

View file

@ -33,6 +33,7 @@ fn has_ecs() -> bool { return has_systems() or has_models() }
fn 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")
if len(g_scenes) > 0 { emith("@L_scene = internal global i32 0\n") } # active base scene id
emith("@L_entc = internal global i32 0\n")
let me = itoa(MAX_ENT)

View file

@ -160,6 +160,40 @@ fn param_labels(fn: Node) -> []ptr {
# 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 {
# 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") {
if is_math_ns(meth) { return emit_math_ns(meth, e) }
perr(`unknown builtin Math.{meth}`)
}
if (ns == "Text") {
if is_text_ns(meth) { return emit_text_ns(meth, e) }
perr(`unknown builtin Text.{meth}`)
}
if (ns == "List") {
if is_list_ns(meth) { return emit_list_ns(meth, e) }
perr(`unknown builtin List.{meth}`)
}
if (ns == "Ease") {
if is_ease_ns(meth) { return emit_ease_ns(meth, e) }
perr(`unknown builtin Ease.{meth}`)
}
if (ns == "Collide") {
if is_collide_ns(meth) { return emit_collide_ns(meth, e) }
perr(`unknown builtin Collide.{meth}`)
}
if (ns == "Mem") {
if is_mem_ns(meth) { return emit_mem_ns(meth, e) }
perr(`unknown builtin Mem.{meth}`)
}
if (ns == "Color") {
if is_colorfn_ns(meth) { return emit_colorfn_ns(meth, e) }
perr(`unknown builtin Color.{meth}`)
}
if (ns == "Time") {
if is_time_ns(meth) { return emit_time_ns(meth, e) }
perr(`unknown builtin Time.{meth}`)
}
var bare: ptr = null
let labels = new []ptr
if (ns == "Screen") {
@ -173,6 +207,13 @@ fn emit_ns_call(ns: ptr, meth: ptr, e: Node) -> Val {
if (meth == "width") { bare = "screen_w" }
if (meth == "height") { bare = "screen_h" }
if (meth == "status") { bare = "status"; push(labels, "text") }
if (meth == "line") { bare = "line"; push(labels, "x1"); push(labels, "y1"); push(labels, "x2"); push(labels, "y2"); push(labels, "color") }
if (meth == "circle") { bare = "circle"; push(labels, "x"); push(labels, "y"); push(labels, "radius"); push(labels, "color") }
if (meth == "fill_circle") { bare = "fill_circle"; push(labels, "x"); push(labels, "y"); push(labels, "radius"); push(labels, "color") }
if (meth == "triangle") { bare = "triangle"; push(labels, "x1"); push(labels, "y1"); push(labels, "x2"); push(labels, "y2"); push(labels, "x3"); push(labels, "y3"); push(labels, "color") }
if (meth == "fill_triangle") { bare = "fill_triangle"; push(labels, "x1"); push(labels, "y1"); push(labels, "x2"); push(labels, "y2"); push(labels, "x3"); push(labels, "y3"); push(labels, "color") }
if (meth == "sprite") { bare = "draw_sprite"; push(labels, "id"); push(labels, "x"); push(labels, "y") }
if (meth == "sprite_scaled") { bare = "draw_sprite_scaled"; push(labels, "id"); push(labels, "x"); push(labels, "y"); push(labels, "scale") }
}
if (ns == "Map") {
if (meth == "size") { bare = "map_size"; push(labels, "width"); push(labels, "height") }
@ -183,10 +224,64 @@ fn emit_ns_call(ns: ptr, meth: ptr, e: Node) -> Val {
if (meth == "range") { bare = "rng_range"; push(labels, "low"); push(labels, "high") }
if (meth == "chance") { bare = "rng_chance"; push(labels, "percent") }
if (meth == "seed") { bare = "seed"; push(labels, "value") }
if (meth == "value") { bare = "rng_value" }
if (meth == "int") { bare = "rng_int"; push(labels, "max") }
if (meth == "sign") { bare = "rng_sign" }
}
if (ns == "Input") {
if (meth == "key") { bare = "key" }
}
# Phase 3: the bare reflection / networking / process builtins, namespaced.
# Each is a pure alias — the callee is rewritten to the bare name below.
if (ns == "World") {
if (meth == "get") { bare = "world_get" }
if (meth == "set") { bare = "world_set" }
if (meth == "has") { bare = "world_has" }
if (meth == "count") { bare = "world_count" }
if (meth == "size") { bare = "world_size" }
if (meth == "spawn") { bare = "world_spawn" }
if (meth == "save") { bare = "world_save" }
if (meth == "load") { bare = "world_load" }
if (meth == "prop_id") { bare = "world_prop_id" }
if (meth == "field_id") { bare = "world_field_id" }
if (meth == "model_id") { bare = "world_model_id" }
if (meth == "kind") { bare = "world_kind" }
if (meth == "register_prop") { bare = "world_register_prop" }
if (meth == "attach") { bare = "world_attach_dyn" }
if (meth == "detach") { bare = "world_detach_dyn" }
if (meth == "query_next") { bare = "world_query_next" }
}
if (ns == "Net") {
if (meth == "send") { bare = "net_send" }
if (meth == "poll") { bare = "net_poll" }
if (meth == "serialize") { bare = "serialize" }
if (meth == "apply") { bare = "apply" }
if (meth == "owner") { bare = "owner" }
if (meth == "set_owner") { bare = "set_owner" }
if (meth == "is_server") { bare = "is_server" }
if (meth == "is_owner") { bare = "is_owner" }
if (meth == "local_id") { bare = "local_id" }
}
if (ns == "Sys") {
if (meth == "arg") { bare = "arg" }
if (meth == "arg_count") { bare = "arg_count" }
if (meth == "exit") { bare = "exit" }
if (meth == "run") { bare = "run" }
if (meth == "env") { bare = "getenv" }
if (meth == "read_char") { bare = "read_char" }
if (meth == "file_open") { bare = "file_open" }
if (meth == "file_read") { bare = "file_read" }
if (meth == "file_write") { bare = "file_write" }
if (meth == "file_seek") { bare = "file_seek" }
if (meth == "file_tell") { bare = "file_tell" }
if (meth == "file_close") { bare = "file_close" }
if (meth == "stdout") { bare = "file_stdout" }
if (meth == "stderr") { bare = "file_stderr" }
}
if (ns == "Save") {
if (meth == "write") { bare = "save" }
if (meth == "read") { bare = "load" }
}
if (bare == null) { perr(`unknown builtin {ns}.{meth}`) }
reorder_named(e, labels)
let id = node(E_ID); id.s = bare; e.a = id

View file

@ -751,6 +751,9 @@ fn emit_game_main() -> void {
emit_calls_for_phase("Update")
emit_calls_for_phase("LateUpdate")
emit_calls_for_phase("Render")
let fcur = emit_bind("load i32, ptr @L_frame") # Time.frame(): count completed frames
let fnext = emit_bind(`add i32 {fcur}, 1`)
emit(" store i32 "); emit(fnext); emit(", ptr @L_frame\n")
emit(" br label %loop\n")
emit("done:\n")
if len(g_ondespawn) > 0 { emit(" call void @L_despawn_all(i32 2)\n") } # LC1: every survivor's @OnDespawn fires with reason Quit

View file

@ -52,6 +52,9 @@ fn emit_header() -> void {
emith("declare ptr @memcpy(ptr, ptr, i64)\n")
emith("declare ptr @memset(ptr, i32, i64)\n")
emith("declare i64 @strlen(ptr)\n")
emith("declare i32 @strncmp(ptr, ptr, i64)\n")
emith("declare ptr @strstr(ptr, ptr)\n")
emith("declare i32 @atoi(ptr)\n")
emith("declare i32 @getchar()\n")
emith("declare i32 @putchar(i32)\n")
emith("declare i64 @time(ptr)\n")

294
selfhost/emit_list.ludic Normal file
View file

@ -0,0 +1,294 @@
# emit_list.ludic — the List.* namespace over []T slices. A slice is the
# { data, len, cap } %LSlice header (see emit_new.ludic); every op here reads or
# mutates that header in place, so all holders of the slice observe the change.
# Element comparison (contains/index_of) is by value for scalars and by identity
# 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 {
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 {
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 }
if (meth == "insert") or (meth == "remove_at") or (meth == "remove") or (meth == "sort") { return true }
return false
}
# 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 {
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}`)
let full = emit_bind(`icmp sge i32 {l}, {c}`)
let grow = lbl("ig"); let done = lbl("igd")
emit(" br i1 "); emit(full); emit(", label %"); emit(grow); emit(", label %"); emit(done); emit("\n")
emit(grow); emit(":\n")
let dbl = emit_bind(`mul i32 {c}, 2`)
let isz = emit_bind(`icmp eq i32 {c}, 0`)
let nc = emit_bind(`select i1 {isz}, i32 8, i32 {dbl}`)
let esz = emit_sizeof(elt)
let ncw = emit_bind(`zext i32 {nc} to i64`)
let byts = emit_bind(`mul i64 {ncw}, {esz}`)
let old = emit_bind(`load ptr, ptr {dp}`)
let nd = emit_bind(`call ptr @realloc(ptr {old}, i64 {byts})`)
emit(" store ptr "); emit(nd); emit(", ptr "); emit(dp); emit("\n")
emit(" store i32 "); emit(nc); emit(", ptr "); emit(cp); emit("\n")
emit(" br label %"); emit(done); emit("\n")
emit(done); emit(":\n")
}
fn 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])
if not is_slice_ty(s.ty) { perr(`List.{meth} needs a slice`) }
let el = slice_elem(s.ty)
let elt = llty(el)
let h = s.code
let lp = slice_field(h, 1)
if (meth == "clear") { # drop to length 0 (keeps capacity)
emit(" store i32 0, ptr "); emit(lp); emit("\n")
return val("0", "void")
}
if (meth == "first") { # element 0 (assumes non-empty)
let a = list_elem_addr(h, elt, "0")
return val(emit_bind(`load {elt}, ptr {a}`), el)
}
if (meth == "last") { # element len-1 (assumes non-empty)
let l = emit_bind(`load i32, ptr {lp}`)
let l1 = emit_bind(`sub i32 {l}, 1`)
let a = list_elem_addr(h, elt, l1)
return val(emit_bind(`load {elt}, ptr {a}`), el)
}
if (meth == "pop") { # remove & return the last element
let l = emit_bind(`load i32, ptr {lp}`)
let l1 = emit_bind(`sub i32 {l}, 1`)
let a = list_elem_addr(h, elt, l1)
let v = emit_bind(`load {elt}, ptr {a}`)
emit(" store i32 "); emit(l1); emit(", ptr "); emit(lp); emit("\n")
return val(v, el)
}
if (meth == "swap") { # exchange elements i and j
let i = emit_expr(e.kids[1]); let j = emit_expr(e.kids[2])
let ai = list_elem_addr(h, elt, i.code)
let aj = list_elem_addr(h, elt, j.code)
let vi = emit_bind(`load {elt}, ptr {ai}`)
let vj = emit_bind(`load {elt}, ptr {aj}`)
emit(" store "); emit(elt); emit(" "); emit(vj); emit(", ptr "); emit(ai); emit("\n")
emit(" store "); emit(elt); emit(" "); emit(vi); emit(", ptr "); emit(aj); emit("\n")
return val("0", "void")
}
if (meth == "contains") or (meth == "index_of") { # linear scan; index_of -> -1 if absent
let needle = emit_expr(e.kids[1])
let l = emit_bind(`load i32, ptr {lp}`)
let res = emit_alloca("i32")
store_at("i32", "-1", res)
let ix = emit_alloca("i32")
store_at("i32", "0", ix)
let cl = lbl("lc_cond"); let bl = lbl("lc_body"); let hit = lbl("lc_hit")
let nx = lbl("lc_next"); let en = lbl("lc_end")
emit(" br label %"); emit(cl); emit("\n")
emit(cl); emit(":\n")
let iv = emit_bind(`load i32, ptr {ix}`)
let more = emit_bind(`icmp slt i32 {iv}, {l}`)
emit(" br i1 "); emit(more); emit(", label %"); emit(bl); emit(", label %"); emit(en); emit("\n")
emit(bl); emit(":\n")
let a = list_elem_addr(h, elt, iv)
let ev = emit_bind(`load {elt}, ptr {a}`)
let eq = emit_bind(`icmp eq {elt} {ev}, {needle.code}`)
emit(" br i1 "); emit(eq); emit(", label %"); emit(hit); emit(", label %"); emit(nx); emit("\n")
emit(hit); emit(":\n")
store_at("i32", iv, res)
emit(" br label %"); emit(en); emit("\n")
emit(nx); emit(":\n")
let iv1 = emit_bind(`add i32 {iv}, 1`)
store_at("i32", iv1, ix)
emit(" br label %"); emit(cl); emit("\n")
emit(en); emit(":\n")
let found = emit_bind(`load i32, ptr {res}`)
if (meth == "index_of") { return val(found, "int") }
let hasit = emit_bind(`icmp sge i32 {found}, 0`) # contains -> found >= 0
return val(emit_bind(`zext i1 {hasit} to i32`), "bool")
}
if (meth == "insert") { # insert v at index i, shifting the rest up
let idx = emit_expr(e.kids[1]); let vv = emit_expr(e.kids[2])
list_grow_if_full(h, elt)
let l = emit_bind(`load i32, ptr {lp}`)
let jp = emit_alloca("i32")
store_at("i32", l, jp)
let cl = lbl("li_c"); let bl = lbl("li_b"); let en = lbl("li_e")
emit(" br label %"); emit(cl); emit("\n"); emit(cl); emit(":\n")
let j = emit_bind(`load i32, ptr {jp}`)
let go = emit_bind(`icmp sgt i32 {j}, {idx.code}`)
emit(" br i1 "); emit(go); emit(", label %"); emit(bl); emit(", label %"); emit(en); emit("\n")
emit(bl); emit(":\n")
let jm1 = emit_bind(`sub i32 {j}, 1`)
let src = list_elem_addr(h, elt, jm1)
let sv = emit_bind(`load {elt}, ptr {src}`)
let dst = list_elem_addr(h, elt, j)
emit(" store "); emit(elt); emit(" "); emit(sv); emit(", ptr "); emit(dst); emit("\n")
store_at("i32", jm1, jp)
emit(" br label %"); emit(cl); emit("\n")
emit(en); emit(":\n")
let at = list_elem_addr(h, elt, idx.code)
emit(" store "); emit(elt); emit(" "); emit(vv.code); emit(", ptr "); emit(at); emit("\n")
let l1 = emit_bind(`add i32 {l}, 1`)
emit(" store i32 "); emit(l1); emit(", ptr "); emit(lp); emit("\n")
return val("0", "void")
}
if (meth == "remove_at") { # remove index i, shifting the rest down
let idx = emit_expr(e.kids[1])
let l = emit_bind(`load i32, ptr {lp}`)
let lm1 = emit_bind(`sub i32 {l}, 1`)
let jp = emit_alloca("i32")
store_at("i32", idx.code, jp)
let cl = lbl("lr_c"); let bl = lbl("lr_b"); let en = lbl("lr_e")
emit(" br label %"); emit(cl); emit("\n"); emit(cl); emit(":\n")
let j = emit_bind(`load i32, ptr {jp}`)
let go = emit_bind(`icmp slt i32 {j}, {lm1}`)
emit(" br i1 "); emit(go); emit(", label %"); emit(bl); emit(", label %"); emit(en); emit("\n")
emit(bl); emit(":\n")
let jp1 = emit_bind(`add i32 {j}, 1`)
let src = list_elem_addr(h, elt, jp1)
let sv = emit_bind(`load {elt}, ptr {src}`)
let dst = list_elem_addr(h, elt, j)
emit(" store "); emit(elt); emit(" "); emit(sv); emit(", ptr "); emit(dst); emit("\n")
store_at("i32", jp1, jp)
emit(" br label %"); emit(cl); emit("\n")
emit(en); emit(":\n")
emit(" store i32 "); emit(lm1); emit(", ptr "); emit(lp); emit("\n")
return val("0", "void")
}
if (meth == "remove") { # remove the first element equal to v
let needle = emit_expr(e.kids[1])
let l = emit_bind(`load i32, ptr {lp}`)
# scan for the index of v (-1 if absent)
let fp = emit_alloca("i32")
store_at("i32", "-1", fp)
let ip = emit_alloca("i32")
store_at("i32", "0", ip)
let sc = lbl("lv_c"); let sb = lbl("lv_b"); let sh = lbl("lv_h"); let sn = lbl("lv_n"); let se = lbl("lv_e")
emit(" br label %"); emit(sc); emit("\n"); emit(sc); emit(":\n")
let iv = emit_bind(`load i32, ptr {ip}`)
let more = emit_bind(`icmp slt i32 {iv}, {l}`)
let seen = emit_bind(`load i32, ptr {fp}`)
let notyet = emit_bind(`icmp slt i32 {seen}, 0`)
let cont = emit_bind(`and i1 {more}, {notyet}`)
emit(" br i1 "); emit(cont); emit(", label %"); emit(sb); emit(", label %"); emit(se); emit("\n")
emit(sb); emit(":\n")
let a = list_elem_addr(h, elt, iv)
let ev = emit_bind(`load {elt}, ptr {a}`)
let eq = emit_bind(`icmp eq {elt} {ev}, {needle.code}`)
emit(" br i1 "); emit(eq); emit(", label %"); emit(sh); emit(", label %"); emit(sn); emit("\n")
emit(sh); emit(":\n"); store_at("i32", iv, fp); emit(" br label %"); emit(sc); emit("\n")
emit(sn); emit(":\n")
let iv1 = emit_bind(`add i32 {iv}, 1`)
store_at("i32", iv1, ip)
emit(" br label %"); emit(sc); emit("\n")
emit(se); emit(":\n")
# if found, shift the tail down and shrink
let found = emit_bind(`load i32, ptr {fp}`)
let has = emit_bind(`icmp sge i32 {found}, 0`)
let doit = lbl("lv_do"); let end = lbl("lv_end")
emit(" br i1 "); emit(has); emit(", label %"); emit(doit); emit(", label %"); emit(end); emit("\n")
emit(doit); emit(":\n")
let lm1 = emit_bind(`sub i32 {l}, 1`)
let jp = emit_alloca("i32")
store_at("i32", found, jp)
let dc = lbl("lv_dc"); let db = lbl("lv_db"); let de = lbl("lv_de")
emit(" br label %"); emit(dc); emit("\n"); emit(dc); emit(":\n")
let j = emit_bind(`load i32, ptr {jp}`)
let go = emit_bind(`icmp slt i32 {j}, {lm1}`)
emit(" br i1 "); emit(go); emit(", label %"); emit(db); emit(", label %"); emit(de); emit("\n")
emit(db); emit(":\n")
let jp1 = emit_bind(`add i32 {j}, 1`)
let src = list_elem_addr(h, elt, jp1)
let sv = emit_bind(`load {elt}, ptr {src}`)
let dst = list_elem_addr(h, elt, j)
emit(" store "); emit(elt); emit(" "); emit(sv); emit(", ptr "); emit(dst); emit("\n")
store_at("i32", jp1, jp)
emit(" br label %"); emit(dc); emit("\n")
emit(de); emit(":\n")
emit(" store i32 "); emit(lm1); emit(", ptr "); emit(lp); emit("\n")
emit(" br label %"); emit(end); emit("\n")
emit(end); emit(":\n")
return val("0", "void")
}
if (meth == "sort") { # ascending insertion sort, in place
let l = emit_bind(`load i32, ptr {lp}`)
let ip = emit_alloca("i32")
store_at("i32", "1", ip)
let oc = lbl("ls_oc"); let ob = lbl("ls_ob"); let oe = lbl("ls_oe")
emit(" br label %"); emit(oc); emit("\n"); emit(oc); emit(":\n")
let i = emit_bind(`load i32, ptr {ip}`)
let omore = emit_bind(`icmp slt i32 {i}, {l}`)
emit(" br i1 "); emit(omore); emit(", label %"); emit(ob); emit(", label %"); emit(oe); emit("\n")
emit(ob); emit(":\n")
let ai = list_elem_addr(h, elt, i)
let key = emit_bind(`load {elt}, ptr {ai}`)
let jp = emit_alloca("i32")
let im1 = emit_bind(`sub i32 {i}, 1`)
store_at("i32", im1, jp)
let ic = lbl("ls_ic"); let ib = lbl("ls_ib"); let ie = lbl("ls_ie")
emit(" br label %"); emit(ic); emit("\n"); emit(ic); emit(":\n")
let j = emit_bind(`load i32, ptr {jp}`)
let jok = emit_bind(`icmp sge i32 {j}, 0`)
let aj = list_elem_addr(h, elt, j)
let ev = emit_bind(`load {elt}, ptr {aj}`)
let gt = emit_bind(`icmp sgt {elt} {ev}, {key}`)
let shift = emit_bind(`and i1 {jok}, {gt}`)
emit(" br i1 "); emit(shift); emit(", label %"); emit(ib); emit(", label %"); emit(ie); emit("\n")
emit(ib); emit(":\n")
let jp1 = emit_bind(`add i32 {j}, 1`)
let dst = list_elem_addr(h, elt, jp1)
emit(" store "); emit(elt); emit(" "); emit(ev); emit(", ptr "); emit(dst); emit("\n")
let jm1 = emit_bind(`sub i32 {j}, 1`)
store_at("i32", jm1, jp)
emit(" br label %"); emit(ic); emit("\n")
emit(ie); emit(":\n")
let j2 = emit_bind(`load i32, ptr {jp}`)
let slot = emit_bind(`add i32 {j2}, 1`)
let sa = list_elem_addr(h, elt, slot)
emit(" store "); emit(elt); emit(" "); emit(key); emit(", ptr "); emit(sa); emit("\n")
let i1 = emit_bind(`add i32 {i}, 1`)
store_at("i32", i1, ip)
emit(" br label %"); emit(oc); emit("\n")
emit(oe); emit(":\n")
return val("0", "void")
}
# reverse: swap ends inward until the cursors meet
let l = emit_bind(`load i32, ptr {lp}`)
let loi = emit_alloca("i32")
store_at("i32", "0", loi)
let hii = emit_alloca("i32")
let lm1 = emit_bind(`sub i32 {l}, 1`)
store_at("i32", lm1, hii)
let cl = lbl("lr_cond"); let bl = lbl("lr_body"); let en = lbl("lr_end")
emit(" br label %"); emit(cl); emit("\n")
emit(cl); emit(":\n")
let lo = emit_bind(`load i32, ptr {loi}`)
let hi = emit_bind(`load i32, ptr {hii}`)
let go = emit_bind(`icmp slt i32 {lo}, {hi}`)
emit(" br i1 "); emit(go); emit(", label %"); emit(bl); emit(", label %"); emit(en); emit("\n")
emit(bl); emit(":\n")
let alo = list_elem_addr(h, elt, lo)
let ahi = list_elem_addr(h, elt, hi)
let vlo = emit_bind(`load {elt}, ptr {alo}`)
let vhi = emit_bind(`load {elt}, ptr {ahi}`)
emit(" store "); emit(elt); emit(" "); emit(vhi); emit(", ptr "); emit(alo); emit("\n")
emit(" store "); emit(elt); emit(" "); emit(vlo); emit(", ptr "); emit(ahi); emit("\n")
let lo1 = emit_bind(`add i32 {lo}, 1`)
store_at("i32", lo1, loi)
let hi1 = emit_bind(`sub i32 {hi}, 1`)
store_at("i32", hi1, hii)
emit(" br label %"); emit(cl); emit("\n")
emit(en); emit(":\n")
return val("0", "void")
}

View file

@ -1,6 +1,9 @@
# emit_math.ludic — the math builtins that lower to inline IR rather than a
# runtime call: min, max, abs, clamp. Everything else named like a builtin
# (clear, fill_rect, reg, ...) is resolved to its rt_ function by emit_call.
# emit_math.ludic — the Math.* namespace, all deterministic Q16.16 fixed-point.
# min/max/abs/clamp lower to inline IR (and stay bare too); sign/floor/ceil/
# round/lerp/inverse_lerp/remap and the geometry/interp helpers are inline; and
# sqrt/sin/cos/tan call the runtime prelude below (@fn_fx_sqrt is a bit-by-bit
# 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 {
return (name == "min") or (name == "max") or (name == "abs") or (name == "clamp")
@ -11,19 +14,331 @@ fn emit_math_builtin(name: ptr, e: Node) -> Val {
let a = emit_expr(e.kids[0])
let c = emit_bind(`icmp slt i32 {a.code}, 0`)
let n = emit_bind(`sub i32 0, {a.code}`)
return val(emit_bind(`select i1 {c}, i32 {n}, i32 {a.code}`), "int")
return val(emit_bind(`select i1 {c}, i32 {n}, i32 {a.code}`), a.ty)
}
if (name == "min") or (name == "max") {
let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1])
var op = "slt"
if (name == "max") { op = "sgt" }
let c = emit_bind(`icmp {op} i32 {a.code}, {b.code}`)
return val(emit_bind(`select i1 {c}, i32 {a.code}, i32 {b.code}`), "int")
return val(emit_bind(`select i1 {c}, i32 {a.code}, i32 {b.code}`), a.ty)
}
# clamp(v, lo, hi) = max(lo, min(v, hi))
let v = emit_expr(e.kids[0]); let lo = emit_expr(e.kids[1]); let hi = emit_expr(e.kids[2])
let c1 = emit_bind(`icmp slt i32 {v.code}, {hi.code}`)
let t = emit_bind(`select i1 {c1}, i32 {v.code}, i32 {hi.code}`)
let c2 = emit_bind(`icmp sgt i32 {lo.code}, {t}`)
return val(emit_bind(`select i1 {c2}, i32 {lo.code}, i32 {t}`), "int")
return val(emit_bind(`select i1 {c2}, i32 {lo.code}, i32 {t}`), v.ty)
}
# a * b in Q16.16 (64-bit intermediate, arithmetic shift back) -> code of an i32
fn 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}`)
let sh = emit_bind(`ashr i64 {m}, 16`)
return emit_bind(`trunc i64 {sh} to i32`)
}
# 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 {
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`)
let dv = emit_bind(`sdiv i64 {ash}, {b64}`)
return emit_bind(`trunc i64 {dv} to i32`)
}
# 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 {
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 {
let num = emit_bind(`sub i32 {v}, {a}`)
let den = emit_bind(`sub i32 {b}, {a}`)
return fx_div_code(num, den)
}
# 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 {
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 }
if (meth == "sqrt") or (meth == "sin") or (meth == "cos") or (meth == "tan") or (meth == "hypot") { return true }
if (meth == "atan2") or (meth == "asin") or (meth == "acos") { return true }
if (meth == "deg_to_rad") or (meth == "rad_to_deg") or (meth == "posmod") or (meth == "wrap") { return true }
if (meth == "ping_pong") or (meth == "snapped") or (meth == "move_toward") or (meth == "smoothstep") { return true }
if (meth == "dist") or (meth == "dist2") { return true }
return false
}
fn 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)
}
if (meth == "sign") { # sign(x) -> -1 / 0 / 1 (int)
let a = emit_expr(e.kids[0])
let pos = emit_bind(`icmp sgt i32 {a.code}, 0`)
let neg = emit_bind(`icmp slt i32 {a.code}, 0`)
let lo = emit_bind(`select i1 {neg}, i32 -1, i32 0`)
return val(emit_bind(`select i1 {pos}, i32 1, i32 {lo}`), "int")
}
if (meth == "floor") { # floor(fixed) -> int
let a = emit_expr(e.kids[0])
return val(emit_bind(`ashr i32 {a.code}, 16`), "int")
}
if (meth == "ceil") { # ceil(fixed) -> int
let a = emit_expr(e.kids[0])
let t = emit_bind(`add i32 {a.code}, 65535`)
return val(emit_bind(`ashr i32 {t}, 16`), "int")
}
if (meth == "round") { # round(fixed) -> nearest int (half up)
let a = emit_expr(e.kids[0])
let t = emit_bind(`add i32 {a.code}, 32768`)
return val(emit_bind(`ashr i32 {t}, 16`), "int")
}
if (meth == "lerp") { # lerp(a, b, t: fixed) -> fixed
let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1]); let t = emit_expr(e.kids[2])
return val(fx_lerp_code(a.code, b.code, t.code), "fixed")
}
if (meth == "inverse_lerp") { # inverse_lerp(a, b, v: fixed) -> fixed
let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1]); let v = emit_expr(e.kids[2])
return val(fx_inv_lerp_code(a.code, b.code, v.code), "fixed")
}
if (meth == "sqrt") { # sqrt(fixed) -> fixed (deterministic isqrt)
g_uses_mathrt = true
let a = emit_expr(e.kids[0])
return val(emit_bind(`call i32 @fn_fx_sqrt(i32 {a.code})`), "fixed")
}
if (meth == "sin") { # sin(radians: fixed) -> fixed
g_uses_mathrt = true
let a = emit_expr(e.kids[0])
return val(emit_bind(`call i32 @fn_fx_sin(i32 {a.code})`), "fixed")
}
if (meth == "cos") { # cos(x) = sin(x + pi/2), pi/2 = 102944 fixed
g_uses_mathrt = true
let a = emit_expr(e.kids[0])
let sh = emit_bind(`add i32 {a.code}, 102944`)
return val(emit_bind(`call i32 @fn_fx_sin(i32 {sh})`), "fixed")
}
if (meth == "tan") { # tan(x) = sin(x) / cos(x)
g_uses_mathrt = true
let a = emit_expr(e.kids[0])
let s = emit_bind(`call i32 @fn_fx_sin(i32 {a.code})`)
let sh = emit_bind(`add i32 {a.code}, 102944`)
let c = emit_bind(`call i32 @fn_fx_sin(i32 {sh})`)
return val(fx_div_code(s, c), "fixed")
}
if (meth == "atan2") { # atan2(y, x) -> angle in radians
g_uses_mathrt = true
let y = emit_expr(e.kids[0]); let x = emit_expr(e.kids[1])
return val(emit_bind(`call i32 @fn_fx_atan2(i32 {y.code}, i32 {x.code})`), "fixed")
}
if (meth == "asin") { # asin(x) = atan2(x, sqrt(1 - x^2))
g_uses_mathrt = true
let x = emit_expr(e.kids[0])
let xx = fx_mul_code(x.code, x.code)
let om = emit_bind(`sub i32 65536, {xx}`)
let root = emit_bind(`call i32 @fn_fx_sqrt(i32 {om})`)
return val(emit_bind(`call i32 @fn_fx_atan2(i32 {x.code}, i32 {root})`), "fixed")
}
if (meth == "acos") { # acos(x) = atan2(sqrt(1 - x^2), x)
g_uses_mathrt = true
let x = emit_expr(e.kids[0])
let xx = fx_mul_code(x.code, x.code)
let om = emit_bind(`sub i32 65536, {xx}`)
let root = emit_bind(`call i32 @fn_fx_sqrt(i32 {om})`)
return val(emit_bind(`call i32 @fn_fx_atan2(i32 {root}, i32 {x.code})`), "fixed")
}
if (meth == "hypot") { # hypot(x, y) = sqrt(x*x + y*y)
g_uses_mathrt = true
let x = emit_expr(e.kids[0]); let y = emit_expr(e.kids[1])
let xx = fx_mul_code(x.code, x.code); let yy = fx_mul_code(y.code, y.code)
let s = emit_bind(`add i32 {xx}, {yy}`)
return val(emit_bind(`call i32 @fn_fx_sqrt(i32 {s})`), "fixed")
}
if (meth == "dist2") { # dist2(x0,y0,x1,y1) = dx*dx + dy*dy
let x0 = emit_expr(e.kids[0]); let y0 = emit_expr(e.kids[1])
let x1 = emit_expr(e.kids[2]); let y1 = emit_expr(e.kids[3])
let dx = emit_bind(`sub i32 {x1.code}, {x0.code}`)
let dy = emit_bind(`sub i32 {y1.code}, {y0.code}`)
let xx = fx_mul_code(dx, dx); let yy = fx_mul_code(dy, dy)
return val(emit_bind(`add i32 {xx}, {yy}`), "fixed")
}
if (meth == "dist") { # dist(x0,y0,x1,y1) = sqrt(dist2)
g_uses_mathrt = true
let x0 = emit_expr(e.kids[0]); let y0 = emit_expr(e.kids[1])
let x1 = emit_expr(e.kids[2]); let y1 = emit_expr(e.kids[3])
let dx = emit_bind(`sub i32 {x1.code}, {x0.code}`)
let dy = emit_bind(`sub i32 {y1.code}, {y0.code}`)
let xx = fx_mul_code(dx, dx); let yy = fx_mul_code(dy, dy)
let s = emit_bind(`add i32 {xx}, {yy}`)
return val(emit_bind(`call i32 @fn_fx_sqrt(i32 {s})`), "fixed")
}
if (meth == "deg_to_rad") { # d * (pi/180), pi/180 = 1144 fixed
let d = emit_expr(e.kids[0])
return val(fx_mul_code(d.code, "1144"), "fixed")
}
if (meth == "rad_to_deg") { # r * (180/pi), 180/pi = 3754936 fixed
let r = emit_expr(e.kids[0])
return val(fx_mul_code(r.code, "3754936"), "fixed")
}
if (meth == "posmod") { # ((a % m) + m) % m, always in [0, m)
let a = emit_expr(e.kids[0]); let m = emit_expr(e.kids[1])
let r = emit_bind(`srem i32 {a.code}, {m.code}`)
let rm = emit_bind(`add i32 {r}, {m.code}`)
return val(emit_bind(`srem i32 {rm}, {m.code}`), "int")
}
if (meth == "wrap") { # wrap(v, lo, hi) into [lo, hi)
let v = emit_expr(e.kids[0]); let lo = emit_expr(e.kids[1]); let hi = emit_expr(e.kids[2])
let range = emit_bind(`sub i32 {hi.code}, {lo.code}`)
let off = emit_bind(`sub i32 {v.code}, {lo.code}`)
let r = emit_bind(`srem i32 {off}, {range}`)
let rm = emit_bind(`add i32 {r}, {range}`)
let pm = emit_bind(`srem i32 {rm}, {range}`)
return val(emit_bind(`add i32 {lo.code}, {pm}`), "int")
}
if (meth == "ping_pong") { # bounce 0..len..0, integer
let t = emit_expr(e.kids[0]); let l = emit_expr(e.kids[1])
let two = emit_bind(`mul i32 {l.code}, 2`)
let r = emit_bind(`srem i32 {t.code}, {two}`)
let rm = emit_bind(`add i32 {r}, {two}`)
let pm = emit_bind(`srem i32 {rm}, {two}`)
let sub = emit_bind(`sub i32 {pm}, {l.code}`)
let neg = emit_bind(`sub i32 0, {sub}`)
let c = emit_bind(`icmp slt i32 {sub}, 0`)
let ab = emit_bind(`select i1 {c}, i32 {neg}, i32 {sub}`)
return val(emit_bind(`sub i32 {l.code}, {ab}`), "int")
}
if (meth == "snapped") { # nearest multiple of step (fixed)
let v = emit_expr(e.kids[0]); let step = emit_expr(e.kids[1])
let q = fx_div_code(v.code, step.code)
let qr = emit_bind(`add i32 {q}, 32768`)
let n = emit_bind(`ashr i32 {qr}, 16`)
return val(emit_bind(`mul i32 {n}, {step.code}`), "fixed")
}
if (meth == "move_toward") { # step from -> to by at most delta (fixed)
let f = emit_expr(e.kids[0]); let to = emit_expr(e.kids[1]); let d = emit_expr(e.kids[2])
let diff = emit_bind(`sub i32 {to.code}, {f.code}`)
let dneg = emit_bind(`sub i32 0, {diff}`)
let dc = emit_bind(`icmp slt i32 {diff}, 0`)
let adiff = emit_bind(`select i1 {dc}, i32 {dneg}, i32 {diff}`)
let pos = emit_bind(`icmp sgt i32 {diff}, 0`)
let neg = emit_bind(`icmp slt i32 {diff}, 0`)
let slo = emit_bind(`select i1 {neg}, i32 -1, i32 0`)
let sgn = emit_bind(`select i1 {pos}, i32 1, i32 {slo}`)
let stepv = emit_bind(`mul i32 {sgn}, {d.code}`)
let moved = emit_bind(`add i32 {f.code}, {stepv}`)
let reach = emit_bind(`icmp sle i32 {adiff}, {d.code}`)
return val(emit_bind(`select i1 {reach}, i32 {to.code}, i32 {moved}`), "fixed")
}
if (meth == "smoothstep") { # smooth 0..1 ramp between e0 and e1
let e0 = emit_expr(e.kids[0]); let e1 = emit_expr(e.kids[1]); let x = emit_expr(e.kids[2])
let tt = fx_inv_lerp_code(e0.code, e1.code, x.code)
let c1 = emit_bind(`icmp slt i32 {tt}, 0`)
let t0 = emit_bind(`select i1 {c1}, i32 0, i32 {tt}`)
let c2 = emit_bind(`icmp sgt i32 {t0}, 65536`)
let t = emit_bind(`select i1 {c2}, i32 65536, i32 {t0}`)
let twot = emit_bind(`mul i32 {t}, 2`)
let poly = emit_bind(`sub i32 196608, {twot}`)
let tsq = fx_mul_code(t, t)
return val(fx_mul_code(tsq, poly), "fixed")
}
# remap(v, in0, in1, out0, out1) = lerp(out0, out1, inverse_lerp(in0, in1, v))
let v = emit_expr(e.kids[0])
let i0 = emit_expr(e.kids[1]); let i1 = emit_expr(e.kids[2])
let o0 = emit_expr(e.kids[3]); let o1 = emit_expr(e.kids[4])
let t = fx_inv_lerp_code(i0.code, i1.code, v.code)
return val(fx_lerp_code(o0.code, o1.code, t), "fixed")
}
# emit_math_prelude — the deterministic fixed-point math runtime, emitted once
# per program that uses Math.sqrt/sin/cos/tan. @fn_fx_sqrt is a 64-bit integer
# square root (bit-by-bit); @fn_fx_sin reads a 256-entry Q16.16 sine table with
# linear interpolation. Both are pure integer IR, so bit-identical everywhere.
fn 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")
emith("ret0:\n ret i32 0\n")
emith("go:\n %x64 = sext i32 %x to i64\n %n0 = shl i64 %x64, 16\n")
emith(" %np = alloca i64\n %rp = alloca i64\n %bp = alloca i64\n")
emith(" store i64 %n0, ptr %np\n store i64 0, ptr %rp\n store i64 4611686018427387904, ptr %bp\n br label %adj\n")
emith("adj:\n %b1 = load i64, ptr %bp\n %n1 = load i64, ptr %np\n %tb = icmp ugt i64 %b1, %n1\n br i1 %tb, label %adjb, label %loop\n")
emith("adjb:\n %b2 = lshr i64 %b1, 2\n store i64 %b2, ptr %bp\n br label %adj\n")
emith("loop:\n %b3 = load i64, ptr %bp\n %bz = icmp eq i64 %b3, 0\n br i1 %bz, label %done, label %body\n")
emith("body:\n %r1 = load i64, ptr %rp\n %n2 = load i64, ptr %np\n %rb = add i64 %r1, %b3\n %ge = icmp uge i64 %n2, %rb\n br i1 %ge, label %sub, label %shift\n")
emith("sub:\n %n3 = sub i64 %n2, %rb\n store i64 %n3, ptr %np\n %rsh = lshr i64 %r1, 1\n %rnew = add i64 %rsh, %b3\n store i64 %rnew, ptr %rp\n br label %next\n")
emith("shift:\n %rsh2 = lshr i64 %r1, 1\n store i64 %rsh2, ptr %rp\n br label %next\n")
emith("next:\n %b4 = lshr i64 %b3, 2\n store i64 %b4, ptr %bp\n br label %loop\n")
emith("done:\n %rf = load i64, ptr %rp\n %r32 = trunc i64 %rf to i32\n ret i32 %r32\n}\n")
emith("define i32 @fn_fx_sin(i32 %x) {\n")
emith(" %xe = sext i32 %x to i64\n %m = mul i64 %xe, 2670177\n %idxf = ashr i64 %m, 16\n")
emith(" %i0 = ashr i64 %idxf, 16\n %i0m = and i64 %i0, 255\n %frac = and i64 %idxf, 65535\n")
emith(" %i1 = add i64 %i0m, 1\n %i1m = and i64 %i1, 255\n")
emith(" %p0 = getelementptr [256 x i32], ptr @L_sin_tab, i64 0, i64 %i0m\n %v0 = load i32, ptr %p0\n")
emith(" %p1 = getelementptr [256 x i32], ptr @L_sin_tab, i64 0, i64 %i1m\n %v1 = load i32, ptr %p1\n")
emith(" %d = sub i32 %v1, %v0\n %de = sext i32 %d to i64\n %dm = mul i64 %de, %frac\n %dsh = ashr i64 %dm, 16\n %dsh32 = trunc i64 %dsh to i32\n %res = add i32 %v0, %dsh32\n ret i32 %res\n}\n")
emith("define i32 @fn_fx_atan2(i32 %y, i32 %x) {\n")
emith("entry:\n")
emith(" %xz = icmp eq i32 %x, 0\n")
emith(" %yz = icmp eq i32 %y, 0\n")
emith(" %both0 = and i1 %xz, %yz\n")
emith(" br i1 %both0, label %z, label %go\n")
emith("z:\n")
emith(" ret i32 0\n")
emith("go:\n")
emith(" %yneg = icmp slt i32 %y, 0\n")
emith(" %yng = sub i32 0, %y\n")
emith(" %ay0 = select i1 %yneg, i32 %yng, i32 %y\n")
emith(" %ay = add i32 %ay0, 1\n")
emith(" %xpos = icmp sge i32 %x, 0\n")
emith(" br i1 %xpos, label %xp, label %xn\n")
emith("xp:\n")
emith(" %n1 = sub i32 %x, %ay\n")
emith(" %d1 = add i32 %x, %ay\n")
emith(" br label %dv\n")
emith("xn:\n")
emith(" %n2 = add i32 %x, %ay\n")
emith(" %d2 = sub i32 %ay, %x\n")
emith(" br label %dv\n")
emith("dv:\n")
emith(" %num = phi i32 [ %n1, %xp ], [ %n2, %xn ]\n")
emith(" %den = phi i32 [ %d1, %xp ], [ %d2, %xn ]\n")
emith(" %base = phi i32 [ 51472, %xp ], [ 154416, %xn ]\n")
emith(" %n64 = sext i32 %num to i64\n")
emith(" %nsh = shl i64 %n64, 16\n")
emith(" %d64 = sext i32 %den to i64\n")
emith(" %rdv = sdiv i64 %nsh, %d64\n")
emith(" %r = trunc i64 %rdv to i32\n")
emith(" %r64a = sext i32 %r to i64\n")
emith(" %r64b = sext i32 %r to i64\n")
emith(" %rr = mul i64 %r64a, %r64b\n")
emith(" %rrs = ashr i64 %rr, 16\n")
emith(" %r2 = trunc i64 %rrs to i32\n")
emith(" %r2e = sext i32 %r2 to i64\n")
emith(" %re = sext i32 %r to i64\n")
emith(" %r3m = mul i64 %r2e, %re\n")
emith(" %r3s = ashr i64 %r3m, 16\n")
emith(" %r3 = trunc i64 %r3s to i32\n")
emith(" %r3e = sext i32 %r3 to i64\n")
emith(" %c1 = mul i64 %r3e, 12865\n")
emith(" %c1s = ashr i64 %c1, 16\n")
emith(" %t1 = trunc i64 %c1s to i32\n")
emith(" %re2 = sext i32 %r to i64\n")
emith(" %c2 = mul i64 %re2, 64337\n")
emith(" %c2s = ashr i64 %c2, 16\n")
emith(" %t2 = trunc i64 %c2s to i32\n")
emith(" %poly = sub i32 %t1, %t2\n")
emith(" %angle = add i32 %poly, %base\n")
emith(" %angneg = sub i32 0, %angle\n")
emith(" %res = select i1 %yneg, i32 %angneg, i32 %angle\n")
emith(" ret i32 %res\n")
emith("}\n")
}

48
selfhost/emit_mem.ludic Normal file
View file

@ -0,0 +1,48 @@
# emit_mem.ludic — the Mem.* namespace: raw buffers and byte pokes. bytes/words
# allocate (as the bare builtins do); copy/fill wrap memcpy/memset; peek/poke
# 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 {
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 {
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`)
return val(emit_bind(`call ptr @malloc(i64 {w})`), "ptr")
}
if (meth == "words") { # allocate n 32-bit words
let n = emit_expr(e.kids[0])
let by = emit_bind(`mul i32 {n.code}, 4`)
let w = emit_bind(`zext i32 {by} to i64`)
return val(emit_bind(`call ptr @malloc(i64 {w})`), "words")
}
if (meth == "copy") { # copy n bytes src -> dst
let dst = emit_expr(e.kids[0]); let src = emit_expr(e.kids[1]); let n = emit_expr(e.kids[2])
let w = emit_bind(`zext i32 {n.code} to i64`)
emit(" call ptr @memcpy(ptr "); emit(dst.code); emit(", ptr "); emit(src.code); emit(", i64 "); emit(w); emit(")\n")
return val("0", "void")
}
if (meth == "fill") { # set n bytes of buf to value v
let buf = emit_expr(e.kids[0]); let v = emit_expr(e.kids[1]); let n = emit_expr(e.kids[2])
let w = emit_bind(`zext i32 {n.code} to i64`)
emit(" call ptr @memset(ptr "); emit(buf.code); emit(", i32 "); emit(v.code); emit(", i64 "); emit(w); emit(")\n")
return val("0", "void")
}
if (meth == "peek") { # read one byte at buf[i], 0..255
let buf = emit_expr(e.kids[0]); let i = emit_expr(e.kids[1])
let p = emit_bind(`getelementptr inbounds i8, ptr {buf.code}, i32 {i.code}`)
let c = emit_bind(`load i8, ptr {p}`)
return val(emit_bind(`zext i8 {c} to i32`), "int")
}
# poke: write the low byte of v at buf[i]
let buf = emit_expr(e.kids[0]); let i = emit_expr(e.kids[1]); let v = emit_expr(e.kids[2])
let p = emit_bind(`getelementptr inbounds i8, ptr {buf.code}, i32 {i.code}`)
let b = emit_bind(`trunc i32 {v.code} to i8`)
emit(" store i8 "); emit(b); emit(", ptr "); emit(p); emit("\n")
return val("0", "void")
}

558
selfhost/emit_text.ludic Normal file
View file

@ -0,0 +1,558 @@
# emit_text.ludic — the Text.* namespace over `str` (null-terminated byte
# strings). The libc-backed queries (length/char_at/starts_with/ends_with/
# 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 {
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 }
if (meth == "contains") or (meth == "index_of") { return true }
if (meth == "upper") or (meth == "lower") or (meth == "trim") or (meth == "repeat") { return true }
if (meth == "pad_left") or (meth == "pad_right") { return true }
if (meth == "split") or (meth == "join") or (meth == "replace") { return true }
return false
}
fn 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
return val(emit_bind(`call ptr @fn_int_str(i32 {n.code})`), "str")
}
if (meth == "slice") { # s[a..b], same substring helper
let s0 = emit_expr(e.kids[0]); let a = emit_expr(e.kids[1]); let b = emit_expr(e.kids[2])
g_uses_strslice = true
return val(emit_bind(`call ptr @fn_str_slice(ptr {s0.code}, i32 {a.code}, i32 {b.code})`), "str")
}
if (meth == "equals") { # byte-wise equality, same as ==
let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1])
g_uses_str = true
return val(emit_bind(`call i32 @fn_str_eq(ptr {a.code}, ptr {b.code})`), "bool")
}
if (meth == "concat") { # a + b, same as the + operator
let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1])
g_uses_str = true
return val(emit_bind(`call ptr @fn_str_concat(ptr {a.code}, ptr {b.code})`), "str")
}
let s = emit_expr(e.kids[0])
if (meth == "length") { # byte length
let r = emit_bind(`call i64 @strlen(ptr {s.code})`)
return val(emit_bind(`trunc i64 {r} to i32`), "int")
}
if (meth == "char_at") { # the byte at index i, 0..255
let i = emit_expr(e.kids[1])
let a = emit_bind(`getelementptr inbounds i8, ptr {s.code}, i32 {i.code}`)
let c = emit_bind(`load i8, ptr {a}`)
return val(emit_bind(`zext i8 {c} to i32`), "int")
}
if (meth == "to_int") { # parse a leading integer, 0 if none
return val(emit_bind(`call i32 @atoi(ptr {s.code})`), "int")
}
if (meth == "upper") { # ASCII a-z -> A-Z, fresh string
g_uses_textrt = true
return val(emit_bind(`call ptr @fn_str_upper(ptr {s.code})`), "str")
}
if (meth == "lower") { # ASCII A-Z -> a-z, fresh string
g_uses_textrt = true
return val(emit_bind(`call ptr @fn_str_lower(ptr {s.code})`), "str")
}
if (meth == "trim") { # drop leading/trailing whitespace
g_uses_textrt = true
return val(emit_bind(`call ptr @fn_str_trim(ptr {s.code})`), "str")
}
if (meth == "repeat") { # s repeated n times
g_uses_textrt = true
let n = emit_expr(e.kids[1])
return val(emit_bind(`call ptr @fn_str_repeat(ptr {s.code}, i32 {n.code})`), "str")
}
if (meth == "pad_left") { # pad with spaces to width, on the left
g_uses_textrt = true
let w = emit_expr(e.kids[1])
return val(emit_bind(`call ptr @fn_str_pad(ptr {s.code}, i32 {w.code}, i1 1)`), "str")
}
if (meth == "pad_right") { # pad with spaces to width, on the right
g_uses_textrt = true
let w = emit_expr(e.kids[1])
return val(emit_bind(`call ptr @fn_str_pad(ptr {s.code}, i32 {w.code}, i1 0)`), "str")
}
if (meth == "replace") { # replace every `from` with `to`
g_uses_textrt2 = true
let from = emit_expr(e.kids[1]); let to = emit_expr(e.kids[2])
return val(emit_bind(`call ptr @fn_str_replace(ptr {s.code}, ptr {from.code}, ptr {to.code})`), "str")
}
if (meth == "split") { # split on a separator -> []str
g_uses_textrt2 = true
let sep = emit_expr(e.kids[1])
return val(emit_bind(`call ptr @fn_str_split(ptr {s.code}, ptr {sep.code})`), "[]str")
}
if (meth == "join") { # join a []str with a separator (s is the slice)
g_uses_textrt2 = true
let sep = emit_expr(e.kids[1])
return val(emit_bind(`call ptr @fn_str_join(ptr {s.code}, ptr {sep.code})`), "str")
}
if (meth == "contains") or (meth == "index_of") { # substring search
let sub = emit_expr(e.kids[1])
let p = emit_bind(`call ptr @strstr(ptr {s.code}, ptr {sub.code})`)
if (meth == "contains") {
let nn = emit_bind(`icmp ne ptr {p}, null`)
return val(emit_bind(`zext i1 {nn} to i32`), "bool")
}
let isnull = emit_bind(`icmp eq ptr {p}, null`) # index_of -> byte offset or -1
let pi = emit_bind(`ptrtoint ptr {p} to i64`)
let si = emit_bind(`ptrtoint ptr {s.code} to i64`)
let d = emit_bind(`sub i64 {pi}, {si}`)
let d32 = emit_bind(`trunc i64 {d} to i32`)
return val(emit_bind(`select i1 {isnull}, i32 -1, i32 {d32}`), "int")
}
# starts_with / ends_with: compare against the affix over its own length
let affix = emit_expr(e.kids[1])
let la = emit_bind(`call i64 @strlen(ptr {affix.code})`)
if (meth == "starts_with") { # strncmp of the head is null-safe
let cmp = emit_bind(`call i32 @strncmp(ptr {s.code}, ptr {affix.code}, i64 {la})`)
let eqz = emit_bind(`icmp eq i32 {cmp}, 0`)
return val(emit_bind(`zext i1 {eqz} to i32`), "bool")
}
# ends_with: compare the tail, but only when the affix fits (else a negative
# offset would read before the string) — branch so the strncmp never underflows
let ls = emit_bind(`call i64 @strlen(ptr {s.code})`)
let off = emit_bind(`sub i64 {ls}, {la}`)
let res = emit_alloca("i32")
store_at("i32", "0", res)
let neg = emit_bind(`icmp slt i64 {off}, 0`)
let cmpl = lbl("ew_cmp"); let en = lbl("ew_end")
emit(" br i1 "); emit(neg); emit(", label %"); emit(en); emit(", label %"); emit(cmpl); emit("\n")
emit(cmpl); emit(":\n")
let tail = emit_bind(`getelementptr inbounds i8, ptr {s.code}, i64 {off}`)
let cmp = emit_bind(`call i32 @strncmp(ptr {tail}, ptr {affix.code}, i64 {la})`)
let eqz = emit_bind(`icmp eq i32 {cmp}, 0`)
let z = emit_bind(`zext i1 {eqz} to i32`)
store_at("i32", z, res)
emit(" br label %"); emit(en); emit("\n")
emit(en); emit(":\n")
return val(emit_bind(`load i32, ptr {res}`), "bool")
}
# 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 {
emith("define ptr @fn_str_upper(ptr %s) {\n")
emith("entry:\n")
emith(" %n = call i64 @strlen(ptr %s)\n")
emith(" %sz = add i64 %n, 1\n")
emith(" %out = call ptr @malloc(i64 %sz)\n")
emith(" %ip = alloca i64\n")
emith(" store i64 0, ptr %ip\n")
emith(" br label %cond\n")
emith("cond:\n")
emith(" %i = load i64, ptr %ip\n")
emith(" %d = icmp ult i64 %i, %n\n")
emith(" br i1 %d, label %body, label %fin\n")
emith("body:\n")
emith(" %sp = getelementptr i8, ptr %s, i64 %i\n")
emith(" %c = load i8, ptr %sp\n")
emith(" %ge = icmp uge i8 %c, 97\n")
emith(" %le = icmp ule i8 %c, 122\n")
emith(" %in = and i1 %ge, %le\n")
emith(" %cc = add i8 %c, -32\n")
emith(" %oc = select i1 %in, i8 %cc, i8 %c\n")
emith(" %op = getelementptr i8, ptr %out, i64 %i\n")
emith(" store i8 %oc, ptr %op\n")
emith(" %i1 = add i64 %i, 1\n")
emith(" store i64 %i1, ptr %ip\n")
emith(" br label %cond\n")
emith("fin:\n")
emith(" %tp = getelementptr i8, ptr %out, i64 %n\n")
emith(" store i8 0, ptr %tp\n")
emith(" ret ptr %out\n")
emith("}\n")
emith("define ptr @fn_str_lower(ptr %s) {\n")
emith("entry:\n")
emith(" %n = call i64 @strlen(ptr %s)\n")
emith(" %sz = add i64 %n, 1\n")
emith(" %out = call ptr @malloc(i64 %sz)\n")
emith(" %ip = alloca i64\n")
emith(" store i64 0, ptr %ip\n")
emith(" br label %cond\n")
emith("cond:\n")
emith(" %i = load i64, ptr %ip\n")
emith(" %d = icmp ult i64 %i, %n\n")
emith(" br i1 %d, label %body, label %fin\n")
emith("body:\n")
emith(" %sp = getelementptr i8, ptr %s, i64 %i\n")
emith(" %c = load i8, ptr %sp\n")
emith(" %ge = icmp uge i8 %c, 65\n")
emith(" %le = icmp ule i8 %c, 90\n")
emith(" %in = and i1 %ge, %le\n")
emith(" %cc = add i8 %c, 32\n")
emith(" %oc = select i1 %in, i8 %cc, i8 %c\n")
emith(" %op = getelementptr i8, ptr %out, i64 %i\n")
emith(" store i8 %oc, ptr %op\n")
emith(" %i1 = add i64 %i, 1\n")
emith(" store i64 %i1, ptr %ip\n")
emith(" br label %cond\n")
emith("fin:\n")
emith(" %tp = getelementptr i8, ptr %out, i64 %n\n")
emith(" store i8 0, ptr %tp\n")
emith(" ret ptr %out\n")
emith("}\n")
emith("define ptr @fn_str_trim(ptr %s) {\n")
emith("entry:\n")
emith(" %n = call i64 @strlen(ptr %s)\n")
emith(" %sp = alloca i64\n")
emith(" %ep = alloca i64\n")
emith(" store i64 0, ptr %sp\n")
emith(" store i64 %n, ptr %ep\n")
emith(" br label %lcond\n")
emith("lcond:\n")
emith(" %a0 = load i64, ptr %sp\n")
emith(" %e0 = load i64, ptr %ep\n")
emith(" %lt0 = icmp ult i64 %a0, %e0\n")
emith(" br i1 %lt0, label %lchk, label %rcond\n")
emith("lchk:\n")
emith(" %p0 = getelementptr i8, ptr %s, i64 %a0\n")
emith(" %c0 = load i8, ptr %p0\n")
emith(" %ws0 = call i1 @fn_is_ws(i8 %c0)\n")
emith(" br i1 %ws0, label %linc, label %rcond\n")
emith("linc:\n")
emith(" %a1 = add i64 %a0, 1\n")
emith(" store i64 %a1, ptr %sp\n")
emith(" br label %lcond\n")
emith("rcond:\n")
emith(" %a2 = load i64, ptr %sp\n")
emith(" %e2 = load i64, ptr %ep\n")
emith(" %lt2 = icmp ult i64 %a2, %e2\n")
emith(" br i1 %lt2, label %rchk, label %build\n")
emith("rchk:\n")
emith(" %em1 = sub i64 %e2, 1\n")
emith(" %p1 = getelementptr i8, ptr %s, i64 %em1\n")
emith(" %c1 = load i8, ptr %p1\n")
emith(" %ws1 = call i1 @fn_is_ws(i8 %c1)\n")
emith(" br i1 %ws1, label %rdec, label %build\n")
emith("rdec:\n")
emith(" store i64 %em1, ptr %ep\n")
emith(" br label %rcond\n")
emith("build:\n")
emith(" %st = load i64, ptr %sp\n")
emith(" %en = load i64, ptr %ep\n")
emith(" %len = sub i64 %en, %st\n")
emith(" %sz = add i64 %len, 1\n")
emith(" %out = call ptr @malloc(i64 %sz)\n")
emith(" %src = getelementptr i8, ptr %s, i64 %st\n")
emith(" call ptr @memcpy(ptr %out, ptr %src, i64 %len)\n")
emith(" %tp = getelementptr i8, ptr %out, i64 %len\n")
emith(" store i8 0, ptr %tp\n")
emith(" ret ptr %out\n")
emith("}\n")
emith("define i1 @fn_is_ws(i8 %c) {\n")
emith(" %a = icmp eq i8 %c, 32\n")
emith(" %b = icmp eq i8 %c, 9\n")
emith(" %d = icmp eq i8 %c, 10\n")
emith(" %e = icmp eq i8 %c, 13\n")
emith(" %ab = or i1 %a, %b\n")
emith(" %de = or i1 %d, %e\n")
emith(" %r = or i1 %ab, %de\n")
emith(" ret i1 %r\n")
emith("}\n")
emith("define ptr @fn_str_repeat(ptr %s, i32 %n32) {\n")
emith("entry:\n")
emith(" %nneg = icmp slt i32 %n32, 0\n")
emith(" %nn = select i1 %nneg, i32 0, i32 %n32\n")
emith(" %n = zext i32 %nn to i64\n")
emith(" %L = call i64 @strlen(ptr %s)\n")
emith(" %tot = mul i64 %L, %n\n")
emith(" %sz = add i64 %tot, 1\n")
emith(" %out = call ptr @malloc(i64 %sz)\n")
emith(" %ip = alloca i64\n")
emith(" store i64 0, ptr %ip\n")
emith(" br label %cond\n")
emith("cond:\n")
emith(" %i = load i64, ptr %ip\n")
emith(" %d = icmp ult i64 %i, %n\n")
emith(" br i1 %d, label %body, label %fin\n")
emith("body:\n")
emith(" %off = mul i64 %i, %L\n")
emith(" %dst = getelementptr i8, ptr %out, i64 %off\n")
emith(" call ptr @memcpy(ptr %dst, ptr %s, i64 %L)\n")
emith(" %i1 = add i64 %i, 1\n")
emith(" store i64 %i1, ptr %ip\n")
emith(" br label %cond\n")
emith("fin:\n")
emith(" %tp = getelementptr i8, ptr %out, i64 %tot\n")
emith(" store i8 0, ptr %tp\n")
emith(" ret ptr %out\n")
emith("}\n")
emith("define ptr @fn_str_pad(ptr %s, i32 %w32, i1 %left) {\n")
emith("entry:\n")
emith(" %L = call i64 @strlen(ptr %s)\n")
emith(" %w0 = sext i32 %w32 to i64\n")
emith(" %need = icmp ugt i64 %w0, %L\n")
emith(" br i1 %need, label %do, label %copy\n")
emith("copy:\n")
emith(" %sz0 = add i64 %L, 1\n")
emith(" %o0 = call ptr @malloc(i64 %sz0)\n")
emith(" %e0 = add i64 %L, 0\n")
emith(" call ptr @memcpy(ptr %o0, ptr %s, i64 %L)\n")
emith(" %t0 = getelementptr i8, ptr %o0, i64 %L\n")
emith(" store i8 0, ptr %t0\n")
emith(" ret ptr %o0\n")
emith("do:\n")
emith(" %pad = sub i64 %w0, %L\n")
emith(" %sz = add i64 %w0, 1\n")
emith(" %out = call ptr @malloc(i64 %sz)\n")
emith(" br i1 %left, label %padleft, label %padright\n")
emith("padleft:\n")
emith(" call void @fn_fill_sp(ptr %out, i64 0, i64 %pad)\n")
emith(" %dstL = getelementptr i8, ptr %out, i64 %pad\n")
emith(" call ptr @memcpy(ptr %dstL, ptr %s, i64 %L)\n")
emith(" br label %term\n")
emith("padright:\n")
emith(" call ptr @memcpy(ptr %out, ptr %s, i64 %L)\n")
emith(" call void @fn_fill_sp(ptr %out, i64 %L, i64 %pad)\n")
emith(" br label %term\n")
emith("term:\n")
emith(" %tp = getelementptr i8, ptr %out, i64 %w0\n")
emith(" store i8 0, ptr %tp\n")
emith(" ret ptr %out\n")
emith("}\n")
emith("define void @fn_fill_sp(ptr %buf, i64 %start, i64 %count) {\n")
emith("entry:\n")
emith(" %ip = alloca i64\n")
emith(" store i64 0, ptr %ip\n")
emith(" br label %cond\n")
emith("cond:\n")
emith(" %i = load i64, ptr %ip\n")
emith(" %d = icmp ult i64 %i, %count\n")
emith(" br i1 %d, label %body, label %done\n")
emith("body:\n")
emith(" %off = add i64 %start, %i\n")
emith(" %p = getelementptr i8, ptr %buf, i64 %off\n")
emith(" store i8 32, ptr %p\n")
emith(" %i1 = add i64 %i, 1\n")
emith(" store i64 %i1, ptr %ip\n")
emith(" br label %cond\n")
emith("done:\n")
emith(" ret void\n")
emith("}\n")
}
# emit_text2_prelude — the allocating Text ops that build strings/slices:
# 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 {
emith("define ptr @fn_str_replace(ptr %s, ptr %from, ptr %to) {\n")
emith("entry:\n")
emith(" %lf = call i64 @strlen(ptr %from)\n")
emith(" %lz = icmp eq i64 %lf, 0\n")
emith(" br i1 %lz, label %copyall, label %scan\n")
emith("copyall:\n")
emith(" %ls0 = call i64 @strlen(ptr %s)\n")
emith(" %sz0 = add i64 %ls0, 1\n")
emith(" %o0 = call ptr @malloc(i64 %sz0)\n")
emith(" call ptr @memcpy(ptr %o0, ptr %s, i64 %sz0)\n")
emith(" ret ptr %o0\n")
emith("scan:\n")
emith(" %lt = call i64 @strlen(ptr %to)\n")
emith(" %ls = call i64 @strlen(ptr %s)\n")
emith(" %cntp = alloca i64\n")
emith(" store i64 0, ptr %cntp\n")
emith(" %curp = alloca ptr\n")
emith(" store ptr %s, ptr %curp\n")
emith(" br label %ccond\n")
emith("ccond:\n")
emith(" %cur = load ptr, ptr %curp\n")
emith(" %hit = call ptr @strstr(ptr %cur, ptr %from)\n")
emith(" %isnull = icmp eq ptr %hit, null\n")
emith(" br i1 %isnull, label %csize, label %cinc\n")
emith("cinc:\n")
emith(" %c0 = load i64, ptr %cntp\n")
emith(" %c1 = add i64 %c0, 1\n")
emith(" store i64 %c1, ptr %cntp\n")
emith(" %adv = getelementptr i8, ptr %hit, i64 %lf\n")
emith(" store ptr %adv, ptr %curp\n")
emith(" br label %ccond\n")
emith("csize:\n")
emith(" %cnt = load i64, ptr %cntp\n")
emith(" %delta = sub i64 %lt, %lf\n")
emith(" %grow = mul i64 %cnt, %delta\n")
emith(" %newlen = add i64 %ls, %grow\n")
emith(" %osz = add i64 %newlen, 1\n")
emith(" %out = call ptr @malloc(i64 %osz)\n")
emith(" %srcp = alloca ptr\n")
emith(" store ptr %s, ptr %srcp\n")
emith(" %dstp = alloca ptr\n")
emith(" store ptr %out, ptr %dstp\n")
emith(" br label %bcond\n")
emith("bcond:\n")
emith(" %src = load ptr, ptr %srcp\n")
emith(" %h2 = call ptr @strstr(ptr %src, ptr %from)\n")
emith(" %n2 = icmp eq ptr %h2, null\n")
emith(" br i1 %n2, label %tail, label %seg\n")
emith("seg:\n")
emith(" %si = ptrtoint ptr %src to i64\n")
emith(" %hi = ptrtoint ptr %h2 to i64\n")
emith(" %seglen = sub i64 %hi, %si\n")
emith(" %dst0 = load ptr, ptr %dstp\n")
emith(" call ptr @memcpy(ptr %dst0, ptr %src, i64 %seglen)\n")
emith(" %dst1 = getelementptr i8, ptr %dst0, i64 %seglen\n")
emith(" call ptr @memcpy(ptr %dst1, ptr %to, i64 %lt)\n")
emith(" %dst2 = getelementptr i8, ptr %dst1, i64 %lt\n")
emith(" store ptr %dst2, ptr %dstp\n")
emith(" %src2 = getelementptr i8, ptr %h2, i64 %lf\n")
emith(" store ptr %src2, ptr %srcp\n")
emith(" br label %bcond\n")
emith("tail:\n")
emith(" %src3 = load ptr, ptr %srcp\n")
emith(" %rem = call i64 @strlen(ptr %src3)\n")
emith(" %dst3 = load ptr, ptr %dstp\n")
emith(" %remp1 = add i64 %rem, 1\n")
emith(" call ptr @memcpy(ptr %dst3, ptr %src3, i64 %remp1)\n")
emith(" ret ptr %out\n")
emith("}\n")
emith("define ptr @fn_str_join(ptr %h, ptr %sep) {\n")
emith("entry:\n")
emith(" %lp = getelementptr inbounds %LSlice, ptr %h, i32 0, i32 1\n")
emith(" %len32 = load i32, ptr %lp\n")
emith(" %len = sext i32 %len32 to i64\n")
emith(" %dp = getelementptr inbounds %LSlice, ptr %h, i32 0, i32 0\n")
emith(" %data = load ptr, ptr %dp\n")
emith(" %lsep = call i64 @strlen(ptr %sep)\n")
emith(" %totp = alloca i64\n")
emith(" store i64 0, ptr %totp\n")
emith(" %ip = alloca i64\n")
emith(" store i64 0, ptr %ip\n")
emith(" br label %scond\n")
emith("scond:\n")
emith(" %i = load i64, ptr %ip\n")
emith(" %d = icmp ult i64 %i, %len\n")
emith(" br i1 %d, label %sbody, label %alloc\n")
emith("sbody:\n")
emith(" %ep = getelementptr ptr, ptr %data, i64 %i\n")
emith(" %es = load ptr, ptr %ep\n")
emith(" %el = call i64 @strlen(ptr %es)\n")
emith(" %t0 = load i64, ptr %totp\n")
emith(" %t1 = add i64 %t0, %el\n")
emith(" store i64 %t1, ptr %totp\n")
emith(" %i1 = add i64 %i, 1\n")
emith(" store i64 %i1, ptr %ip\n")
emith(" br label %scond\n")
emith("alloc:\n")
emith(" %nz = icmp ugt i64 %len, 0\n")
emith(" %lm1 = sub i64 %len, 1\n")
emith(" %seps = mul i64 %lm1, %lsep\n")
emith(" %sepsz = select i1 %nz, i64 %seps, i64 0\n")
emith(" %tot0 = load i64, ptr %totp\n")
emith(" %tot = add i64 %tot0, %sepsz\n")
emith(" %osz = add i64 %tot, 1\n")
emith(" %out = call ptr @malloc(i64 %osz)\n")
emith(" %dstp = alloca ptr\n")
emith(" store ptr %out, ptr %dstp\n")
emith(" %jp = alloca i64\n")
emith(" store i64 0, ptr %jp\n")
emith(" br label %jcond\n")
emith("jcond:\n")
emith(" %j = load i64, ptr %jp\n")
emith(" %d2 = icmp ult i64 %j, %len\n")
emith(" br i1 %d2, label %jbody, label %jfin\n")
emith("jbody:\n")
emith(" %jnz = icmp ugt i64 %j, 0\n")
emith(" br i1 %jnz, label %putsep, label %putel\n")
emith("putsep:\n")
emith(" %ds0 = load ptr, ptr %dstp\n")
emith(" call ptr @memcpy(ptr %ds0, ptr %sep, i64 %lsep)\n")
emith(" %ds1 = getelementptr i8, ptr %ds0, i64 %lsep\n")
emith(" store ptr %ds1, ptr %dstp\n")
emith(" br label %putel\n")
emith("putel:\n")
emith(" %ep2 = getelementptr ptr, ptr %data, i64 %j\n")
emith(" %es2 = load ptr, ptr %ep2\n")
emith(" %el2 = call i64 @strlen(ptr %es2)\n")
emith(" %ds2 = load ptr, ptr %dstp\n")
emith(" call ptr @memcpy(ptr %ds2, ptr %es2, i64 %el2)\n")
emith(" %ds3 = getelementptr i8, ptr %ds2, i64 %el2\n")
emith(" store ptr %ds3, ptr %dstp\n")
emith(" %j1 = add i64 %j, 1\n")
emith(" store i64 %j1, ptr %jp\n")
emith(" br label %jcond\n")
emith("jfin:\n")
emith(" %dsf = load ptr, ptr %dstp\n")
emith(" store i8 0, ptr %dsf\n")
emith(" ret ptr %out\n")
emith("}\n")
emith("define ptr @fn_str_split(ptr %s, ptr %sep) {\n")
emith("entry:\n")
emith(" %lsep = call i64 @strlen(ptr %sep)\n")
emith(" %cntp = alloca i64\n")
emith(" store i64 1, ptr %cntp\n")
emith(" %curp = alloca ptr\n")
emith(" store ptr %s, ptr %curp\n")
emith(" br label %ccond\n")
emith("ccond:\n")
emith(" %cur = load ptr, ptr %curp\n")
emith(" %hit = call ptr @strstr(ptr %cur, ptr %sep)\n")
emith(" %isnull = icmp eq ptr %hit, null\n")
emith(" br i1 %isnull, label %mk, label %cinc\n")
emith("cinc:\n")
emith(" %c0 = load i64, ptr %cntp\n")
emith(" %c1 = add i64 %c0, 1\n")
emith(" store i64 %c1, ptr %cntp\n")
emith(" %adv = getelementptr i8, ptr %hit, i64 %lsep\n")
emith(" store ptr %adv, ptr %curp\n")
emith(" br label %ccond\n")
emith("mk:\n")
emith(" %cnt = load i64, ptr %cntp\n")
emith(" %hdr = call ptr @malloc(i64 16)\n")
emith(" %arrsz = mul i64 %cnt, 8\n")
emith(" %arr = call ptr @malloc(i64 %arrsz)\n")
emith(" %d0 = getelementptr inbounds %LSlice, ptr %hdr, i32 0, i32 0\n")
emith(" store ptr %arr, ptr %d0\n")
emith(" %cnt32 = trunc i64 %cnt to i32\n")
emith(" %d1 = getelementptr inbounds %LSlice, ptr %hdr, i32 0, i32 1\n")
emith(" store i32 %cnt32, ptr %d1\n")
emith(" %d2 = getelementptr inbounds %LSlice, ptr %hdr, i32 0, i32 2\n")
emith(" store i32 %cnt32, ptr %d2\n")
emith(" %srcp = alloca ptr\n")
emith(" store ptr %s, ptr %srcp\n")
emith(" %ip = alloca i64\n")
emith(" store i64 0, ptr %ip\n")
emith(" br label %fcond\n")
emith("fcond:\n")
emith(" %i = load i64, ptr %ip\n")
emith(" %lt = icmp ult i64 %i, %cnt\n")
emith(" br i1 %lt, label %fbody, label %done\n")
emith("fbody:\n")
emith(" %src = load ptr, ptr %srcp\n")
emith(" %h2 = call ptr @strstr(ptr %src, ptr %sep)\n")
emith(" %n2 = icmp eq ptr %h2, null\n")
emith(" br i1 %n2, label %last, label %mid\n")
emith("mid:\n")
emith(" %si = ptrtoint ptr %src to i64\n")
emith(" %hi = ptrtoint ptr %h2 to i64\n")
emith(" %seglen = sub i64 %hi, %si\n")
emith(" br label %store\n")
emith("last:\n")
emith(" %rem = call i64 @strlen(ptr %src)\n")
emith(" br label %store\n")
emith("store:\n")
emith(" %seg = phi i64 [ %seglen, %mid ], [ %rem, %last ]\n")
emith(" %ssz = add i64 %seg, 1\n")
emith(" %sub = call ptr @malloc(i64 %ssz)\n")
emith(" %src2 = load ptr, ptr %srcp\n")
emith(" call ptr @memcpy(ptr %sub, ptr %src2, i64 %seg)\n")
emith(" %tp = getelementptr i8, ptr %sub, i64 %seg\n")
emith(" store i8 0, ptr %tp\n")
emith(" %slot = getelementptr ptr, ptr %arr, i64 %i\n")
emith(" store ptr %sub, ptr %slot\n")
emith(" %adv2 = getelementptr i8, ptr %h2, i64 %lsep\n")
emith(" store ptr %adv2, ptr %srcp\n")
emith(" %i1 = add i64 %i, 1\n")
emith(" store i64 %i1, ptr %ip\n")
emith(" br label %fcond\n")
emith("done:\n")
emith(" ret ptr %hdr\n")
emith("}\n")
}

26
selfhost/emit_time.ludic Normal file
View file

@ -0,0 +1,26 @@
# emit_time.ludic — the Time.* namespace. frame/elapsed/delta are deterministic
# (driven by @L_frame, the per-frame counter the game loop increments); now() is
# 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 {
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 {
if (meth == "frame") { # completed frames since start
return val(emit_bind("load i32, ptr @L_frame"), "int")
}
if (meth == "delta") { # seconds per frame, 1/60 (fixed)
return val("1092", "fixed")
}
if (meth == "elapsed") { # seconds since start = frame / 60 (fixed)
let f = emit_bind("load i32, ptr @L_frame")
return val(emit_bind(`mul i32 {f}, 1092`), "fixed")
}
# now: wall-clock seconds since the epoch — NON-DETERMINISTIC, for seeding /
# telemetry only, never the lockstep simulation.
let t = emit_bind("call i64 @time(ptr null)")
return val(emit_bind(`trunc i64 {t} to i32`), "int")
}

View file

@ -1,16 +0,0 @@
#!/bin/bash
# game-build.sh — compile a Ludic GAME with the self-hosted compiler. The
# compiler resolves the game's own `import`s and auto-splices the Ludic runtime
# (runtime/native/core.ludic + its imports), exactly as the C compiler does.
# Headless only for now (renders to out.ppm). Run from the repo root.
# ./selfhost/game-build.sh <selfhost-bin> <game.ludic> <out-binary>
set -u
cd "$(dirname "$0")/.."
SH="$1"; GAME="$2"; OUT="$3"
CC="${LUDIC_CC:-clang}"
ERR="$(mktemp)"
"$SH" "$GAME" > "$OUT.ll" 2>"$ERR" || { echo "self-host failed:"; head -5 "$ERR"; rm -f "$ERR"; exit 1; }
# -O2 removes the dead windowing branches (is_windowed()==0) so win_* need no defs
$CC -O2 "$OUT.ll" -o "$OUT" 2>"$ERR" || { echo "link failed:"; grep -i error "$ERR" | head -5; rm -f "$ERR"; exit 1; }
rm -f "$ERR" "$OUT.ll" # keep only the binary
echo "built $OUT"

File diff suppressed because one or more lines are too long

View file

@ -115,9 +115,15 @@ fn lex(src: ptr) -> void {
# a fractional part makes it a Q16.16 fixed literal
if i < n and src[i] == 46 and char_is_digit(src[i + 1]) {
i = i + 1
# Accumulate only the first 4 fractional digits: `fnum << 16` must stay
# in i32 (5+ digits overflow), and Q16.16 resolves ~4-5 decimals anyway.
# Extra digits are still consumed so they don't become a stray token.
var fnum = 0; var fden = 1
while i < n and char_is_digit(src[i]) { fnum = fnum * 10 + (src[i] - 48); fden = fden * 10; i = i + 1 }
let bits = (v << 16) + (fnum << 16) / fden
while i < n and char_is_digit(src[i]) {
if fden < 10000 { fnum = fnum * 10 + (src[i] - 48); fden = fden * 10 }
i = i + 1
}
let bits = (v << 16) + ((fnum << 16) + (fden >> 1)) / fden
tok_push(TK_FLOAT, null, bits, line)
continue
}

File diff suppressed because it is too large Load diff

View file

@ -1,27 +0,0 @@
#!/bin/bash
# reseed.sh — regenerate the checked-in IR seed after a deliberate change to the
# self-host compiler. Uses the CURRENT seed (C-free) to produce the new one, so
# the C compiler is not reintroduced; falls back to the C ludicc if no seed
# builds. Always verify with ./selfhost/bootstrap-cfree.sh afterwards.
set -eu
cd "$(dirname "$0")/.."
CC="${LUDIC_CC:-clang}"
B=build/cfree; mkdir -p "$B"
FRAGS="selfhost/str.ludic selfhost/buf.ludic selfhost/io.ludic selfhost/ast.ludic
selfhost/lex.ludic selfhost/parse.ludic selfhost/parse_game.ludic selfhost/emit_core.ludic selfhost/emit_head.ludic
selfhost/emit_addr.ludic selfhost/emit_intrin.ludic selfhost/emit_intrin2.ludic selfhost/emit_math.ludic selfhost/emit_new.ludic
selfhost/emit_expr.ludic selfhost/emit_stmt.ludic selfhost/emit_ecs.ludic selfhost/emit_query.ludic selfhost/emit_spawn.ludic selfhost/emit_game.ludic selfhost/emit_machine.ludic selfhost/emit_save.ludic
selfhost/emit_net.ludic selfhost/emit_ui.ludic selfhost/emit_decl.ludic selfhost/main.ludic"
{ echo "program SelfHost {"; for f in $FRAGS; do cat "$f"; echo; done; echo "}"; } > "$B/selfhost.ludic"
if $CC selfhost/ludicc.seed.ll -o "$B/sh_old" 2>/dev/null; then
# compile once with the old seed, then AGAIN with the freshly built one so the
# seed is a fixed point of the NEW compiler, not a one-step image of the old.
"$B/sh_old" "$B/selfhost.ludic" > "$B/step1.ll"
$CC "$B/step1.ll" -o "$B/sh_new"
"$B/sh_new" "$B/selfhost.ludic" > selfhost/ludicc.seed.ll
else
echo "seed does not build; reseeding from the C ludicc"
./selfhost/build.sh build/ludicc "$B/sh_c"
"$B/sh_c" "$B/selfhost.ludic" > selfhost/ludicc.seed.ll
fi
echo "reseeded: $(wc -l < selfhost/ludicc.seed.ll | tr -d ' ') lines"

View file

@ -1,52 +0,0 @@
#!/bin/bash
# test.sh — the self-hosting suite: the compiler is correct (compiles diverse
# programs to working binaries) and self-reproducing (the bootstrap fixpoint).
set -u
cd "$(dirname "$0")/.."
CC="${LUDIC_CC:-clang}"
PASS=0; FAIL=0
ok() { printf " \033[32mPASS\033[0m %s\n" "$1"; PASS=$((PASS+1)); }
bad() { printf " \033[31mFAIL\033[0m %s\n" "$1"; FAIL=$((FAIL+1)); }
echo "== build the self-host compiler (via the C ludicc) =="
[ -x build/ludicc ] || ./build.sh >/dev/null 2>&1
./build.sh examples/snake.ludic --headless >/dev/null 2>&1 && [ -x build/ludicc ] && ok "compiler builds from the IR seed (no C)" || { bad "self-host build"; exit 1; }
echo "== the self-host compiler is correct =="
run_case() { # name expected
local n="$1" exp="$2"
./build/ludicc "selfhost/tests/$n.ludic" > "/tmp/sh_$n.ll" 2>"/tmp/sh_$n.err" || { bad "$n: self-host errored: $(head -1 /tmp/sh_$n.err)"; return; }
$CC "/tmp/sh_$n.ll" -o "/tmp/sh_$n" 2>/dev/null || { bad "$n: IR did not assemble"; return; }
local got; got=$("/tmp/sh_$n" 2>/dev/null | tr '\n' ' ' | sed 's/ *$//')
[ "$got" = "$exp" ] && ok "$n ($got)" || bad "$n: got [$got] want [$exp]"
}
run_case structs "7 9 109 2 42"
run_case slices "0 20 361 777"
run_case control "55 4 15 1"
run_case match_bits "1 2 9 16 4 9999"
run_case fixed "2 3 0 6 1"
echo "== the self-host compiler compiles real games (vs golden output) =="
# The golden PPMs in selfhost/golden/ were produced by the original C compiler.
# The self-hosted compiler must reproduce them exactly.
game_case() { # name keys
local n="$1" keys="$2"
./selfhost/game-build.sh build/ludicc "examples/$n.ludic" "/tmp/ludic_${n}_sh" >/tmp/gb.out 2>&1 || { bad "$n: build ($(tail -1 /tmp/gb.out))"; return; }
printf '%s' "$keys" | "/tmp/ludic_${n}_sh" >/dev/null 2>&1; cp out.ppm "/tmp/${n}_sh.ppm"
cmp -s "selfhost/golden/$n.ppm" "/tmp/${n}_sh.ppm" && ok "$n matches the golden render" || bad "$n: differs from golden"
}
game_case snake "ddssaawwddss"
game_case menu "ssss"
game_case chronorift "ddddwwwwaassK"
echo "== the bootstrap fixpoint (seeded from the C compiler) =="
./selfhost/bootstrap.sh >/tmp/boot.out 2>&1
if grep -q "FIXPOINT" /tmp/boot.out; then ok "gen2.ll == gen3.ll (compiler reproduces itself)"; else bad "fixpoint not reached"; cat /tmp/boot.out; fi
echo "== the C-free bootstrap (from the checked-in IR seed, no C compiler) =="
./selfhost/bootstrap-cfree.sh >/tmp/cfree.out 2>&1
if grep -q "no C compiler" /tmp/cfree.out; then ok "seed.ll rebuilds the compiler and is its own fixed point"; else bad "C-free bootstrap failed (seed stale? run ./selfhost/reseed.sh)"; cat /tmp/cfree.out; fi
echo
printf "== %d passed, %d failed ==\n" "$PASS" "$FAIL"
[ "$FAIL" -eq 0 ]

View file

@ -0,0 +1,10 @@
program T {
entry {
print(Color.rgb(255, 128, 64)) # 16744512
print(Color.rgba(255, 128, 64, 64)) # 1090486336
print(Color.with_alpha(0xFF8040, 64)) # 1090486336
print(Color.lerp(0x000000, 0xFFFFFF, 0.5)) # 8355711
print(Color.darken(0xFFFFFF, 0.5)) # 8355711
print(Color.lighten(0x000000, 0.5)) # 8355711
}
}

View file

@ -0,0 +1,22 @@
program T {
entry {
print(Math.floor(Ease.in(0.5) * 1000.0)) # 250
print(Math.floor(Ease.out(0.5) * 1000.0)) # 750
print(Math.floor(Ease.in_out(0.5) * 1000.0)) # 500
print(Math.floor(Ease.in(1.0) * 1000.0)) # 1000
print(Math.floor(Ease.out(1.0) * 1000.0)) # 1000
print(Math.floor(Ease.bounce(1.0) * 1000.0)) # 999
print(Math.floor(Ease.bounce(0.5) * 1000.0)) # 765
print(Math.floor(Ease.back(0.5) * 1000.0)) # -88
print(Math.floor(Ease.back(1.0) * 1000.0)) # 1000
print(Collide.rects(0, 0, 10, 10, 5, 5, 10, 10)) # 1
print(Collide.rects(0, 0, 10, 10, 20, 20, 5, 5)) # 0
print(Collide.point_rect(5, 5, 0, 0, 10, 10)) # 1
print(Collide.point_rect(15, 5, 0, 0, 10, 10)) # 0
print(Collide.point_rect(10, 5, 0, 0, 10, 10)) # 0
print(Collide.circles(0, 0, 5, 6, 0, 5)) # 1
print(Collide.circles(0, 0, 5, 20, 0, 5)) # 0
print(Collide.rect_circle(0, 0, 10, 10, 15, 5, 6)) # 1
print(Collide.rect_circle(0, 0, 10, 10, 20, 5, 6)) # 0
}
}

View file

@ -11,6 +11,6 @@ program T {
print(((4 | 1) & 6)) # 4
let p = bytes(16)
p[1] = 9999
print(p[1]) # 9999
print(p[1]) # 15 (a byte buffer truncates: 9999 & 0xFF)
}
}

27
selfhost/tests/math.ludic Normal file
View file

@ -0,0 +1,27 @@
program T {
entry {
# min / max / abs / clamp, now reachable under the Math namespace
print(Math.min(3, 7)) # 3
print(Math.max(3, 7)) # 7
print(Math.abs(0 - 5)) # 5
print(Math.clamp(12, 0, 10)) # 10
# sign -> -1 / 0 / 1
print(Math.sign(0 - 4)) # -1
print(Math.sign(0)) # 0
print(Math.sign(9)) # 1
# floor / ceil / round on Q16.16 fixed
print(Math.floor(2.7)) # 2
print(Math.ceil(2.1)) # 3
print(Math.round(2.5)) # 3
print(Math.round(2.4)) # 2
# lerp(0, 10, 0.5) = 5.0
print(Math.floor(Math.lerp(0.0, 10.0, 0.5))) # 5
# inverse_lerp(0, 10, 2.5) = 0.25 -> *100 = 25
print(Math.floor(Math.inverse_lerp(0.0, 10.0, 2.5) * 100.0)) # 25
# remap(5, [0..10] -> [0..100]) = 50.0
print(Math.floor(Math.remap(5.0, 0.0, 10.0, 0.0, 100.0))) # 50
}
}

View file

@ -0,0 +1,20 @@
program T {
entry {
print(Math.floor(Math.sqrt(2.0) * 1000.0)) # 1414
print(Math.floor(Math.sqrt(9.0))) # 3
print(Math.floor(Math.sin(1.5707963) * 1000.0)) # ~999 (sin pi/2)
print(Math.floor(Math.cos(0.0) * 1000.0)) # ~999 (cos 0)
print(Math.floor(Math.sin(0.5235987) * 1000.0)) # ~499 (sin 30deg)
print(Math.floor(Math.hypot(3.0, 4.0))) # 5
print(Math.floor(Math.dist(0.0, 0.0, 3.0, 4.0))) # 5
print(Math.floor(Math.dist2(0.0, 0.0, 3.0, 4.0))) # 25
print(Math.floor(Math.deg_to_rad(180.0) * 1000.0)) # 3142
print(Math.floor(Math.rad_to_deg(3.1415926))) # 180
print(Math.posmod(0 - 1, 5)) # 4
print(Math.wrap(12, 0, 10)) # 2
print(Math.ping_pong(13, 10)) # 7
print(Math.floor(Math.snapped(2.3, 0.5) * 10.0)) # 25
print(Math.floor(Math.move_toward(0.0, 10.0, 3.0) * 10.0)) # 30
print(Math.floor(Math.smoothstep(0.0, 10.0, 5.0) * 1000.0)) # 500
}
}

View file

@ -0,0 +1,11 @@
program T {
entry {
print(Math.round(Math.rad_to_deg(Math.atan2(1.0, 1.0)))) # 45
print(Math.round(Math.rad_to_deg(Math.atan2(1.0, 0.0)))) # 90
print(Math.round(Math.rad_to_deg(Math.atan2(0.0, 0.0 - 1.0)))) # 180
print(Math.round(Math.rad_to_deg(Math.atan2(0.0 - 1.0, 0.0 - 1.0)))) # -135
print(Math.round(Math.rad_to_deg(Math.asin(0.5)))) # 30
print(Math.round(Math.rad_to_deg(Math.acos(0.5)))) # 60
print(Math.round(Math.rad_to_deg(Math.asin(1.0)))) # 90
}
}

View file

@ -0,0 +1,19 @@
program T {
entry {
let b = Mem.bytes(8)
Mem.poke(b, 0, 65)
Mem.poke(b, 1, 66)
print(Mem.peek(b, 0)) # 65
print(Mem.peek(b, 1)) # 66
Mem.fill(b, 0, 8)
print(Mem.peek(b, 0)) # 0
let c = Mem.bytes(8)
Mem.poke(b, 3, 99)
Mem.copy(c, b, 8)
print(Mem.peek(c, 3)) # 99
let w = Mem.words(4)
w[0] = 12345
print(w[0]) # 12345
print(Sys.arg_count()) # 1
}
}

View file

@ -0,0 +1,44 @@
program T {
entry {
# ---- List.* over a []int slice ----
let xs = new []int
push(xs, 10)
push(xs, 20)
push(xs, 30)
print(List.len(xs)) # 3
print(List.first(xs)) # 10
print(List.last(xs)) # 30
print(List.contains(xs, 20)) # 1
print(List.contains(xs, 99)) # 0
print(List.index_of(xs, 30)) # 2
print(List.index_of(xs, 99)) # -1
List.swap(xs, 0, 2) # [30, 20, 10]
print(List.first(xs)) # 30
print(List.last(xs)) # 10
List.reverse(xs) # [10, 20, 30]
print(List.first(xs)) # 10
print(List.last(xs)) # 30
print(List.pop(xs)) # 30 -> [10, 20]
print(List.len(xs)) # 2
List.clear(xs)
print(List.len(xs)) # 0
# ---- Text.* over a str ----
let s = "hello world"
print(Text.length(s)) # 11
print(Text.char_at(s, 0)) # 104 ('h')
print(Text.starts_with(s, "hello")) # 1
print(Text.starts_with(s, "world")) # 0
print(Text.ends_with(s, "world")) # 1
print(Text.ends_with(s, "hello")) # 0
print(Text.contains(s, "lo w")) # 1
print(Text.contains(s, "xyz")) # 0
print(Text.index_of(s, "world")) # 6
print(Text.index_of(s, "xyz")) # -1
print(Text.to_int("42abc")) # 42
print(Text.length(Text.slice(s, 0, 5))) # 5
print(Text.equals(Text.slice(s, 0, 5), "hello")) # 1
print(Text.length(Text.concat("ab", "cd"))) # 4
print(Text.length(Text.from_int(1234))) # 4
}
}

View file

@ -0,0 +1,41 @@
program T {
entry {
# ---- Text builders (upper/lower/trim/repeat/pad) ----
print(Text.length(Text.upper("abc"))) # 3
print(Text.equals(Text.upper("aBc"), "ABC")) # 1
print(Text.equals(Text.lower("aBc"), "abc")) # 1
print(Text.equals(Text.trim(" hi "), "hi")) # 1
print(Text.length(Text.trim(" hi "))) # 2
print(Text.equals(Text.repeat("ab", 3), "ababab")) # 1
print(Text.length(Text.repeat("ab", 3))) # 6
print(Text.equals(Text.pad_left("7", 3), " 7")) # 1
print(Text.equals(Text.pad_right("7", 3), "7 ")) # 1
print(Text.length(Text.pad_left("7", 3))) # 3
print(Text.equals(Text.pad_left("toolong", 3), "toolong")) # 1
# ---- List insert/remove_at/remove/sort ----
let xs = new []int
push(xs, 3); push(xs, 1); push(xs, 2)
List.sort(xs)
print(List.first(xs)) # 1
print(List.last(xs)) # 3
print(List.index_of(xs, 2)) # 1
List.insert(xs, 1, 99)
print(List.len(xs)) # 4
print(List.index_of(xs, 99)) # 1
print(List.index_of(xs, 2)) # 2
List.remove_at(xs, 0)
print(List.first(xs)) # 99
print(List.len(xs)) # 3
List.remove(xs, 2)
print(List.len(xs)) # 2
print(List.contains(xs, 2)) # 0
print(List.last(xs)) # 3
let ys = new []int
push(ys, 5); push(ys, 2); push(ys, 8); push(ys, 1); push(ys, 9); push(ys, 3)
List.sort(ys)
print(List.first(ys)) # 1
print(List.last(ys)) # 9
print(List.index_of(ys, 5)) # 3
}
}

View file

@ -0,0 +1,17 @@
program T {
entry {
print(Text.equals(Text.replace("a.b.c", ".", "-"), "a-b-c")) # 1
print(Text.equals(Text.replace("hello", "l", "L"), "heLLo")) # 1
print(Text.equals(Text.replace("hello", "x", "y"), "hello")) # 1
let parts = Text.split("a,b,c", ",")
print(List.len(parts)) # 3
print(Text.equals(parts[0], "a")) # 1
print(Text.equals(parts[2], "c")) # 1
let one = Text.split("noseps", ",")
print(List.len(one)) # 1
print(Text.equals(one[0], "noseps")) # 1
print(Text.equals(Text.join(parts, "-"), "a-b-c")) # 1
print(Text.equals(Text.join(parts, ""), "abc")) # 1
print(Text.equals(Text.join(Text.split("x/y/z", "/"), "/"), "x/y/z")) # 1
}
}