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

@ -722,7 +722,7 @@ function emit_ns_call(ns: pointer, meth: pointer, e: Node) -> Val {
if (meth == "error") { bare = "job_error"; push(labels, "handle") }
if (meth == "pending") { bare = "job_pending" }
if (meth == "free") { bare = "job_free"; push(labels, "handle") }
if (meth == "parallel_for") { bare = "job_parallel_for"; push(labels, "count"); push(labels, "work"); push(labels, "ctx") }
if (meth == "parallel_for") { bare = "job_parallel_for"; push(labels, "count"); push(labels, "work"); push(labels, "ctx"); if len(e.kids) > 1 { check_worker_ref(e.kids[1]) } }
if (meth == "is_worker") { bare = "job_is_worker" }
}
if (ns == "Promise") {
@ -1035,6 +1035,9 @@ function emit_variant_new(en: Node, ord: int, args: []Node) -> Val {
}
function emit_call(e: Node) -> Val {
# a call through a value of a function type (L2): a local, a global, a field, an element
let fv = callee_value(e)
if fv != null { return emit_indirect_call(fv, e) }
# `Subject.action(...)` — a namespaced builtin (Screen/Random/Input).
if e.a.kind == E_MEMBER {
if e.a.a.kind == E_ID { return emit_ns_call(e.a.a.s, e.a.s, e) }
@ -1462,14 +1465,7 @@ function emit_expr(e: Node) -> Val {
if e.kind == E_LIST { return emit_list(e) } # [a, b, c] -> a fresh slice
# fn name -> the function's address, for a worker entry point. The OS-thread runtime calls it
# as void(i32, ptr), so that is the only signature a reference may have.
if e.kind == E_FNREF {
let d = find_fn(e.s)
if d == null { perr(`fn {e.s}: no function called {e.s}`) }
var ok = len(d.kids) == 2 and llty(d.ty) == "void"
if ok { ok = llty(d.kids[0].ty) == "i32" and llty(d.kids[1].ty) == "ptr" }
if not ok { perr(`fn {e.s}: a worker function takes (i: int, ctx: pointer) and returns nothing`) }
return val(`@fn_{e.s}`, "pointer")
}
if e.kind == E_FNREF { return emit_fnref(e) }
if e.kind == E_ID {
let li = loc_find(e.s)
if li >= 0 { return emit_load_at(loc_reg[li], loc_ty[li]) }