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:
parent
4919c9c2fc
commit
e7f8ee6b91
31 changed files with 100137 additions and 72476 deletions
|
|
@ -13,6 +13,7 @@ function ck_params(f: Node, labels: []pointer, tys: []pointer) -> void {
|
|||
}
|
||||
}
|
||||
function ck_call_fn(e: Node, name: pointer, f: Node) -> pointer {
|
||||
state_inject_generated(e, f)
|
||||
mg_call(e, f)
|
||||
call_fill_defaults(e, f)
|
||||
ck_state_args(e, f)
|
||||
|
|
|
|||
|
|
@ -2,14 +2,41 @@
|
|||
# `h: Hiker` may only read it - an assignment whose target starts at it (`h.hips = 1`,
|
||||
# `h.list[i] = x`), a push onto something in it, or passing it where a `mut Hiker` is wanted is
|
||||
# refused. A module-level `let` (and a const, a registry, a state's own instance) is immutable all
|
||||
# the way down: the same writes starting at one are refused. What this does not follow: a reference
|
||||
# read out of a read-only state into a local (`let l = h.list`) and changed there.
|
||||
# the way down: the same writes starting at one are refused. A reference read out of either into a
|
||||
# local (`let l = h.list`, `let r = TABLE[0]`) carries the same rule: a write through it is refused.
|
||||
var ck_ro: []int = new []int # parallel to ck_names: 1 for a read-only state
|
||||
var ck_alias: []Node = new []Node # parallel to ck_names: the global a local's reference came from
|
||||
|
||||
function ck_set_ro(ro: bool) -> void {
|
||||
let k = ck_top - 1
|
||||
while len(ck_ro) < len(ck_names) { push(ck_ro, 0) }
|
||||
while len(ck_alias) < len(ck_names) { push(ck_alias, null) }
|
||||
if ro { ck_ro[k] = 1 } else { ck_ro[k] = 0 }
|
||||
ck_alias[k] = null
|
||||
}
|
||||
# a value, not a reference: nothing can be written through it
|
||||
function ck_scalar(t: pointer) -> bool {
|
||||
if t == null or (t == "?") or (t == "int") or (t == "float") or (t == "fixed") or (t == "long") { return true }
|
||||
if (t == "bool") or (t == "string") or (t == "byte") or (t == "pointer") { return true }
|
||||
if is_fn_type(t) { return true }
|
||||
return find_enum(t) != null
|
||||
}
|
||||
# `let l = <e>` just bound at the top: a reference out of a read-only state is read-only, and one out
|
||||
# of a global is that global's (a write through it writes the global)
|
||||
function ck_let_from(e: Node, ty: pointer) -> void {
|
||||
if e == null or ck_scalar(ty) { return }
|
||||
let r = ck_chain_root(e)
|
||||
if r == null { return }
|
||||
let k = ck_top - 1
|
||||
let li = ck_local(r.s)
|
||||
if li >= 0 and li < k {
|
||||
if ck_is_ro(li) { ck_ro[k] = 1 }
|
||||
if li < len(ck_alias) { ck_alias[k] = ck_alias[li] }
|
||||
return
|
||||
}
|
||||
if li >= 0 { return }
|
||||
let g = ck_global(r.s)
|
||||
if g != null and g.kind == N_VAR { ck_alias[k] = g }
|
||||
}
|
||||
function ck_is_ro(li: int) -> bool {
|
||||
if li < 0 or li >= len(ck_ro) { return false }
|
||||
|
|
@ -35,6 +62,11 @@ function ck_write_check(t: Node, what: pointer) -> void {
|
|||
if r == null { return }
|
||||
let li = ck_local(r.s)
|
||||
if li >= 0 {
|
||||
if li < len(ck_alias) and ck_alias[li] != null {
|
||||
let ag = ck_alias[li]
|
||||
mg_write(t, ag)
|
||||
if ck_module_let(ag) { ck_err("state", t, `{what}: {r.s} holds part of {mg_src_name(ag.s)}, which is module-level and immutable all the way down`) }
|
||||
}
|
||||
if ck_is_ro(li) {
|
||||
ck_err("state", t, `{what}: {r.s} is read-only here ({r.s}: {ck_tys[li]}); take it as {r.s}: mut {ck_tys[li]} to change it`)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,18 +31,20 @@ function ck_let(s: Node) -> void {
|
|||
if s.tps != null and (s.tps == "state") { # an entry point's state (state.ludic)
|
||||
if not is_state_ty(s.ty) { ck_err("state", s, `an entry point takes states, and {s.s}: {s.ty} is not one`) }
|
||||
ck_set_ro(s.uns == 0)
|
||||
}
|
||||
} else { ck_let_from(s.a, s.ty) }
|
||||
return
|
||||
}
|
||||
ck_bind(s.s, t)
|
||||
# a local bound to a read-only state is read-only too
|
||||
# a local bound to a read-only state, or to a reference out of one, is read-only too
|
||||
if s.a != null and s.a.kind == E_ID {
|
||||
let li = ck_local(s.a.s)
|
||||
if li >= 0 and li < ck_top - 1 and ck_is_ro(li) { ck_set_ro(true) }
|
||||
}
|
||||
ck_let_from(s.a, t)
|
||||
}
|
||||
function ck_assign(s: Node) -> void {
|
||||
ck_write_check(s.a, "this assignment")
|
||||
# a local rebound is not a write through it
|
||||
if not (s.a.kind == E_ID and ck_local(s.a.s) >= 0) { ck_write_check(s.a, "this assignment") }
|
||||
let lt = ck_target(s.a)
|
||||
if (s.s == "=") { ck_expect = lt }
|
||||
let rt = ck_expr(s.b)
|
||||
|
|
@ -124,6 +126,33 @@ function ck_stmt(s: Node) -> void {
|
|||
ck_unsafe -= 1
|
||||
return
|
||||
}
|
||||
# a machine's store: its states' `become` writes it
|
||||
if k == S_MACHINE {
|
||||
if s.a != null and not (s.a.kind == E_INT) { ck_write_check(s.a, "a machine's become") }
|
||||
ck_walk(s)
|
||||
return
|
||||
}
|
||||
# 0.S: a query's body, a spawn's fields, a machine's states - read for what they touch
|
||||
if k == S_QUERY {
|
||||
let m = ck_mark()
|
||||
var t = 0
|
||||
var i = 0
|
||||
while i < len(s.kids) {
|
||||
var ty: pointer = "?"
|
||||
if s.c != null {
|
||||
while t < len(s.c.kids) and s.c.kids[t].ival == 1 { t += 1 }
|
||||
if t < len(s.c.kids) { ty = s.c.kids[t].s }
|
||||
t += 1
|
||||
}
|
||||
ck_bind(s.kids[i].s, ty)
|
||||
i += 1
|
||||
}
|
||||
ck_any(s.b)
|
||||
ck_block(s.a)
|
||||
ck_pop(m)
|
||||
return
|
||||
}
|
||||
ck_walk(s)
|
||||
}
|
||||
function ck_fn_body(d: Node) -> void {
|
||||
mg_enter(d, 0)
|
||||
|
|
@ -182,6 +211,35 @@ function ck_runtime_shadows() -> void {
|
|||
r += 1
|
||||
}
|
||||
}
|
||||
function ck_hooks(hs: []Node) -> void {
|
||||
var i = 0
|
||||
while i < len(hs) {
|
||||
mg_enter(hs[i], 4)
|
||||
ck_block(hs[i].a)
|
||||
mg_leave()
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
function ck_scene_block(b: Node) -> void {
|
||||
if b == null { return }
|
||||
mg_enter(b, 5)
|
||||
ck_block(b)
|
||||
mg_leave()
|
||||
}
|
||||
# a retained ui widget (N_UI, whose number is also S_IF's): its props and its children
|
||||
function ck_widget(w: Node) -> void {
|
||||
if w == null { return }
|
||||
var i = 0
|
||||
while w.b != null and i < len(w.b.kids) {
|
||||
ck_expr(w.b.kids[i].a)
|
||||
i += 1
|
||||
}
|
||||
i = 0
|
||||
while i < len(w.kids) {
|
||||
ck_widget(w.kids[i])
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
# the pass also makes the generics real (check_gen.ludic), so it always runs
|
||||
function check_program() -> void {
|
||||
gen_collect()
|
||||
|
|
@ -196,6 +254,11 @@ function check_program() -> void {
|
|||
ck_block(d.a)
|
||||
mg_leave()
|
||||
}
|
||||
if d.kind == N_UI and d.ival == 1 and g_migrate { # 0.S2: a ui block's props, for what they read
|
||||
g_mg_cur = -3
|
||||
ck_widget(d.a)
|
||||
g_mg_cur = -1
|
||||
}
|
||||
if d.kind == N_SYS and g_migrate { # a handler's body: only the migration reads it here
|
||||
mg_enter(d, 2)
|
||||
ck_block(d.a)
|
||||
|
|
@ -215,6 +278,20 @@ function check_program() -> void {
|
|||
ck_listener(g_onlisten[j])
|
||||
j += 1
|
||||
}
|
||||
if g_migrate { # 0.S2: the hooks' bodies and the scenes', for what they touch
|
||||
ck_hooks(g_onspawn)
|
||||
ck_hooks(g_ondespawn)
|
||||
ck_hooks(g_onattach)
|
||||
ck_hooks(g_ondetach)
|
||||
ck_hooks(g_onenable)
|
||||
ck_hooks(g_ondisable)
|
||||
j = 0
|
||||
while j < len(g_scenes) {
|
||||
ck_scene_block(g_scenes[j].a)
|
||||
ck_scene_block(g_scenes[j].b)
|
||||
j += 1
|
||||
}
|
||||
}
|
||||
# a test block is checked like `entry`: that is also what makes its generic calls real
|
||||
j = 0
|
||||
while j < len(g_tests) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue