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:
parent
ff15c4e01d
commit
a38195128f
235 changed files with 24676 additions and 7762 deletions
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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 ]
|
||||
|
|
@ -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"
|
||||
86
selfhost/emit_collide.ludic
Normal file
86
selfhost/emit_collide.ludic
Normal 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")
|
||||
}
|
||||
74
selfhost/emit_colorfn.ludic
Normal file
74
selfhost/emit_colorfn.ludic
Normal 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")
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
52
selfhost/emit_ease.ludic
Normal 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")
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
294
selfhost/emit_list.ludic
Normal 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")
|
||||
}
|
||||
|
|
@ -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
48
selfhost/emit_mem.ludic
Normal 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
558
selfhost/emit_text.ludic
Normal 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
26
selfhost/emit_time.ludic
Normal 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")
|
||||
}
|
||||
|
|
@ -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"
|
||||
4
selfhost/golden/draw_prims.ppm
Normal file
4
selfhost/golden/draw_prims.ppm
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
24763
selfhost/ludicc.seed.ll
24763
selfhost/ludicc.seed.ll
File diff suppressed because it is too large
Load diff
|
|
@ -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"
|
||||
|
|
@ -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 ]
|
||||
10
selfhost/tests/color.ludic
Normal file
10
selfhost/tests/color.ludic
Normal 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
|
||||
}
|
||||
}
|
||||
22
selfhost/tests/easecollide.ludic
Normal file
22
selfhost/tests/easecollide.ludic
Normal 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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
27
selfhost/tests/math.ludic
Normal 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
|
||||
}
|
||||
}
|
||||
20
selfhost/tests/math2.ludic
Normal file
20
selfhost/tests/math2.ludic
Normal 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
|
||||
}
|
||||
}
|
||||
11
selfhost/tests/math3.ludic
Normal file
11
selfhost/tests/math3.ludic
Normal 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
|
||||
}
|
||||
}
|
||||
19
selfhost/tests/memsys.ludic
Normal file
19
selfhost/tests/memsys.ludic
Normal 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
|
||||
}
|
||||
}
|
||||
44
selfhost/tests/textlist.ludic
Normal file
44
selfhost/tests/textlist.ludic
Normal 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
|
||||
}
|
||||
}
|
||||
41
selfhost/tests/textlist2.ludic
Normal file
41
selfhost/tests/textlist2.ludic
Normal 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
|
||||
}
|
||||
}
|
||||
17
selfhost/tests/textsplit.ludic
Normal file
17
selfhost/tests/textsplit.ludic
Normal 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
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue