feat(lang): functions are values (L2), and render3d takes its scene as callbacks

fn(int, float) -> bool is a type, fn name is any top-level function's value, and a call through
a local, a global, a record field, a slice element, a parameter or a result of a function type
is an indirect call; two function types mix only when equal, a call checks its argument count,
and a value may be null (examples/functions/values.ludic). Job.parallel_for keeps its worker
check.

render3d's scene is registered rather than required by name: r3d_on_draw, r3d_on_casters and
r3d_on_stream_fill (hooks.ludic). The two rendering examples register theirs - and had defined
scene_draw_casters with no parameter while the renderer passed one, which nothing checked.
render3d declares numbers float itself; smooth.ludic is converted to floats and returns when
r3d_init fails instead of running on into a segfault. Noise.* check their argument count (a call
one short crashed the compiler). selfhost-build says why it failed. The migration tool reads a
declared float as evidence. Seed regenerated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-24 00:27:26 +03:00
parent dc75a5a5ab
commit 0c73287e35
23 changed files with 71430 additions and 68575 deletions

View file

@ -35,23 +35,28 @@ function emit_noise_ns(meth: pointer, e: Node) -> Val {
}
g_uses_noisert = true
if (meth == "value2") {
noise_arity(e, "value2", 3)
let x = emit_expr(e.kids[0]); let y = emit_expr(e.kids[1]); let s = emit_expr(e.kids[2])
return val(emit_bind(`call i32 @lp_noise_value2(i32 {x.code}, i32 {y.code}, i32 {s.code})`), "fixed")
}
if (meth == "perlin2") {
noise_arity(e, "perlin2", 3)
let x = emit_expr(e.kids[0]); let y = emit_expr(e.kids[1]); let s = emit_expr(e.kids[2])
return val(emit_bind(`call i32 @lp_noise_perlin2(i32 {x.code}, i32 {y.code}, i32 {s.code})`), "fixed")
}
if (meth == "simplex2") {
noise_arity(e, "simplex2", 3)
let x = emit_expr(e.kids[0]); let y = emit_expr(e.kids[1]); let s = emit_expr(e.kids[2])
return val(emit_bind(`call i32 @lp_noise_simplex2(i32 {x.code}, i32 {y.code}, i32 {s.code})`), "fixed")
}
if (meth == "fbm2") {
noise_arity(e, "fbm2", 4)
g_uses_mathrt = true # simplex path is standalone; fbm needs fx_div only (local)
let x = emit_expr(e.kids[0]); let y = emit_expr(e.kids[1]); let s = emit_expr(e.kids[2]); let o = emit_expr(e.kids[3])
return val(emit_bind(`call i32 @lp_noise_fbm2(i32 {x.code}, i32 {y.code}, i32 {s.code}, i32 {o.code})`), "fixed")
}
if (meth == "cellular2") {
noise_arity(e, "cellular2", 3)
g_uses_mathrt = true # F1 distance needs @lp_fx_sqrt
let x = emit_expr(e.kids[0]); let y = emit_expr(e.kids[1]); let s = emit_expr(e.kids[2])
return val(emit_bind(`call i32 @lp_noise_cellular2(i32 {x.code}, i32 {y.code}, i32 {s.code})`), "fixed")
@ -215,3 +220,13 @@ function emit_noise_cellular() -> void {
emith(" %d2 = alloca i32\n %id = alloca i32\n call void @lp_noise_cell_scan(i32 %x, i32 %y, i32 %seed, ptr %d2, ptr %id)\n")
emith(" %r = load i32, ptr %id\n ret i32 %r\n}\n")
}
# Noise.* take their arguments by position, and fbm2's fourth is the octave count: a call one short
# used to read past its arguments and crash the compiler - or, where the call happened to fit,
# put the octaves in the seed and give a flat 0.5 everywhere
function noise_arity(e: Node, meth: pointer, n: int) -> void {
if len(e.kids) != n { perr(`Noise.{meth} takes {itoa(n)} arguments (x, y, seed{noise_arity_more(n)}), and this call gives {itoa(len(e.kids))}`) }
}
function noise_arity_more(n: int) -> pointer {
if n == 4 { return ", octaves" }
return ""
}