wip(0.S3): ui blocks, scene on enter/exit, hooks, machines over a state's field, namespace and engine-system injection; a reference out of a read-only state or a module let is read-only; deps counts writes into another module's state; the rejected examples hold states

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-25 14:49:03 +03:00
parent 4919c9c2fc
commit e7f8ee6b91
31 changed files with 100137 additions and 72476 deletions

View file

@ -358,6 +358,7 @@ function emit_ns_call(ns: pointer, meth: pointer, e: Node) -> Val {
let al = ns_alias_find(ns, meth)
if al >= 0 { return emit_alias_call(al, e) }
var bare: pointer = null
var nsfn: Node = null # a namespace function behind Ns.meth
let labels = new []pointer
# Sprite.* / Assets.* — the namespaced spritesheet / atlas API (#81), a runtime
# in atlas.ludic over the variable-size image loader. A cell (or multi-cell span)
@ -519,15 +520,16 @@ function emit_ns_call(ns: pointer, meth: pointer, e: Node) -> Val {
if is_ns_block(ns) and (not ns_export_has(bare)) { perr(`{ns}.{meth} is internal to namespace {ns}`) }
# a namespace function takes named arguments like any other function: its
# labels are its parameter names, so `Weapon.def(name: "pistol", ...)` works.
let nsfn = find_fn(bare)
nsfn = find_fn(bare)
if (nsfn != null) {
let names = param_labels(nsfn)
var pi2 = 0
var pi2 = state_lead(nsfn) # 0.S: its states are the runtime's to give
while pi2 < len(names) { push(labels, names[pi2]); pi2 += 1 }
}
}
if (bare == null) { perr(`unknown builtin {ns}.{meth}`) }
reorder_named(e, labels)
state_inject(e, nsfn)
let id = node(E_ID); id.s = bare; e.a = id
return emit_call(e)
}
@ -955,6 +957,7 @@ function emit_call(e: Node) -> Val {
cname = rtname
}
vis_check(fn2, name)
state_inject_generated(e, fn2) # 0.S: a call the compiler wrote gets its states
call_fill_defaults(e, fn2) # L11 - for code the checker does not walk
reorder_named(e, param_labels(fn2))
# evaluate args first (their IR is emitted before the call instruction), coercing

View file

@ -154,6 +154,36 @@ function deps_chain_root(t: Node) -> Node {
if n == null or n.kind != E_ID { return null }
return n
}
# 0.S: a chain rooted in a state parameter (`b_st.b_count`): the state's module and the field, when
# the state is another module's - else null
var g_dp_st_field: pointer = null
function deps_state_owner(t: Node) -> pointer {
let r = deps_chain_root(t)
if r == null { return null }
let li = loc_find(r.s)
if li < 0 or not is_state_ty(loc_ty[li]) { return null }
let g = find_global(state_global(loc_ty[li]))
if g == null or g.file == null { return null }
let owner = module_for_uses(g.file)
let from = module_of(g_err_file)
if (from == "") or (owner == "") or (from == owner) { return null }
var n = t
g_dp_st_field = null
while n != null and n != r {
if n.kind == E_MEMBER and n.a == r { g_dp_st_field = n.s }
n = n.a
}
if g_dp_st_field == null { g_dp_st_field = loc_ty[li] }
return owner
}
# an assignment into another module's state, through the parameter that holds it
function deps_state_write(t: Node) -> bool {
if not deps_on() or g_err_file == null { return false }
let owner = deps_state_owner(t)
if owner == null { return false }
push(g_dp_writes, `write {owner} {g_dp_st_field} {module_of(g_err_file)} {g_err_file}:{itoa(g_err_line)}`)
return true
}
# `let local = <expr>`: an alias when the expression is another module's global, or an alias
function deps_alias_let(local: pointer, e: Node) -> void {
if not deps_on() or g_err_file == null { return }
@ -162,6 +192,13 @@ function deps_alias_let(local: pointer, e: Node) -> void {
let r = deps_chain_root(e)
if r == null { return }
if loc_find(r.s) >= 0 {
let so = deps_state_owner(e) # a field of another module's state
if so != null {
push(g_dp_al_local, local)
push(g_dp_al_owner, so)
push(g_dp_al_global, g_dp_st_field)
return
}
let k = deps_alias_find(r.s)
if k < 0 { return }
push(g_dp_al_local, local)

View file

@ -113,6 +113,13 @@ function emit_inj_thunk(d: Node) -> pointer {
emith(`define {rt} {name}({params}) {{\nentry:\n{body}{call}}}\n\n`)
return name
}
# the symbol a call the compiler writes by name reaches: the function, or its thunk when it takes
# states (an engine system, a runtime hook) - the runtime's side of an entry point
function fn_sym(name: pointer) -> pointer {
let d = find_fn(name)
if d != null and state_lead(d) > 0 { return emit_inj_thunk(d) }
return `@fn_{name}`
}
# a call through a value of a function type
function emit_indirect_call(fv: Val, e: Node) -> Val {
let ptys = fn_ty_params(fv.ty)
@ -174,7 +181,8 @@ function check_worker_ref(e: Node) -> void {
let d = find_fn(e.s)
if d == null { return }
# the thread calls it as void(i32, ptr): any pointer-sized context will do (words, a record)
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" }
let s = state_lead(d) # its states come from the thunk
var ok = len(d.kids) == s + 2 and llty(d.ty) == "void"
if ok { ok = llty(d.kids[s].ty) == "i32" and llty(d.kids[s + 1].ty) == "ptr" }
if not ok { perr(`fn {e.s}: a worker function takes (i: int, ctx: pointer) and returns nothing`) }
}

View file

@ -49,7 +49,7 @@ function emit_assign(st: Node) -> void {
} else {
if deps_on() {
let root = deps_target_root(t)
if root != null { deps_write(root, root.s) } else { deps_alias_write(t) }
if root != null { deps_write(root, root.s) } else if not deps_state_write(t) { deps_alias_write(t) }
}
if t.kind == E_MEMBER { addr = emit_member_addr(t); ty = g_addr_ty }
else { if t.kind == E_INDEX { addr = emit_index_addr(t); ty = g_addr_ty }

View file

@ -59,7 +59,7 @@ function emit_call_one(d: Node) -> void {
function emit_one_engine_system(comp: pointer, fn: pointer) -> void {
if is_system_disabled(fn) { return } # lever 5 (#57): `disable system <fn>` drops the tick
if (find_comp(comp) != null) and (find_fn(fn) != null) {
emit(" call void @fn_"); emit(fn); emit("()\n")
emit(` call void {fn_sym(fn)}()\n`)
}
}
function emit_engine_systems_for_phase(phase: pointer) -> void {

View file

@ -11,27 +11,49 @@ function machine_var(a: Node) -> Node {
}
return null
}
# 0.S: a machine over a state's field (`machine hero.mode`) or a local: its address, else null
var g_mach_ty: pointer = null
function machine_addr(a: Node) -> pointer {
if a.kind == E_MEMBER {
let p = emit_member_addr(a)
g_mach_ty = g_addr_ty
return p
}
if a.kind == E_ID {
let li = loc_find(a.s)
if li >= 0 {
g_mach_ty = loc_ty[li]
return loc_reg[li]
}
let gv = machine_var(a)
if gv != null {
g_mach_ty = gv.ty
return `@g_{a.s}`
}
}
return null
}
# the value a state dispatches on: its explicit `= expr`; else, when the machine's
# store is a `var` declared with an enum type, the variant named like the state
# (`var mode: HeroState` + `state Rolling` -> HeroState.Rolling); else its ordinal.
function machine_state_value(m: Node, state: Node) -> pointer {
if (state.b != null) { return emit_expr(state.b).code }
let gv = machine_var(m.a)
if (gv != null) {
if (gv.ty != null) {
let ord = enum_ordinal(gv.ty, state.s)
if ord >= 0 { return itoa(ord) }
if (find_enum(gv.ty) != null) { perr(`machine state {state.s} is not a variant of enum {gv.ty}`) }
}
g_mach_ty = null
let addr = machine_addr(m.a)
let ty = g_mach_ty
if addr != null and ty != null {
let ord = enum_ordinal(ty, state.s)
if ord >= 0 { return itoa(ord) }
if (find_enum(ty) != null) { perr(`machine state {state.s} is not a variant of enum {ty}`) }
}
return itoa(state.ival)
}
function emit_machine(st: Node) -> void {
let gv = machine_var(st.a)
let addr = machine_addr(st.a)
var s: pointer = ""
if (gv != null) { s = emit_bind(`load i32, ptr @g_{st.a.s}`) }
if (addr != null) { s = emit_bind(`load i32, ptr {addr}`) }
else {
let regv = emit_expr(st.a)
s = emit_bind(`call i32 @fn_rt_reg(i32 {regv.code})`)
@ -68,10 +90,10 @@ function emit_become(st: Node) -> void {
var i = 0
while i < len(m.kids) { if (m.kids[i].s == st.s) { target = m.kids[i] }; i += 1 }
if (target != null) {
let gv = machine_var(m.a)
let sv = machine_state_value(m, target)
if (gv != null) {
emit(" store i32 "); emit(sv); emit(", ptr @g_"); emit(m.a.s); emit("\n")
let addr = machine_addr(m.a)
if (addr != null) {
emit(` store i32 {sv}, ptr {addr}\n`)
} else {
let regv = emit_expr(m.a)
emit(" call void @fn_rt_set_reg(i32 "); emit(regv.code); emit(", i32 "); emit(sv); emit(")\n")