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
|
|
@ -1,6 +1,8 @@
|
||||||
# fn_type_mismatch.ludic - a function of one type where another is wanted is refused, by name
|
# fn_type_mismatch.ludic - a function of one type where another is wanted is refused, by name
|
||||||
program FnTypeMismatch {
|
program FnTypeMismatch {
|
||||||
function half(x: float) -> float { return x / 2.0 }
|
function half(x: float) -> float { return x / 2.0 }
|
||||||
var op: fn(int) -> int = null
|
state Ops {
|
||||||
entry { op = fn half }
|
op: fn(int) -> int = null
|
||||||
|
}
|
||||||
|
entry (ops: mut Ops) { ops.op = fn half }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ import "private_bank"
|
||||||
import "private_kit"
|
import "private_kit"
|
||||||
program FriendScope {
|
program FriendScope {
|
||||||
friend module lab of kit
|
friend module lab of kit
|
||||||
entry {
|
entry (bank_st: mut BankState) {
|
||||||
add(5)
|
add(bank_st, 5)
|
||||||
print(total())
|
print(total(bank_st))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
# L3: a variable answers a member that takes nothing; price takes an int, so it binds a function
|
# L3: a state's field answers a member that takes nothing; price takes an int, so it binds a function
|
||||||
program PortVarArgs {
|
program PortVarArgs {
|
||||||
port Purse { price: fn(int) -> int }
|
port Purse { price: fn(int) -> int }
|
||||||
var g_rate: int = 3
|
state Rates { rate: int = 3 }
|
||||||
bind Purse { price: g_rate }
|
bind Purse { price: Rates.rate }
|
||||||
entry {
|
entry {
|
||||||
print(Purse.price(2))
|
print(Purse.price(2))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,15 @@
|
||||||
# bank/ledger.ludic - inherits `module bank` from the barrel that imports it
|
# bank/ledger.ludic - inherits `module bank` from the barrel that imports it
|
||||||
var balance: int = 0
|
export state BankState {
|
||||||
export event Deposited { amount: int }
|
balance: int = 0
|
||||||
function add(n: int) -> void {
|
|
||||||
balance += n
|
|
||||||
}
|
}
|
||||||
export function deposit(n: int) -> void {
|
export event Deposited { amount: int }
|
||||||
add(n)
|
function add(bank_st: mut BankState, n: int) -> void {
|
||||||
|
bank_st.balance += n
|
||||||
|
}
|
||||||
|
export function deposit(bank_st: mut BankState, n: int) -> void {
|
||||||
|
add(bank_st, n)
|
||||||
emit Deposited(amount: n)
|
emit Deposited(amount: n)
|
||||||
}
|
}
|
||||||
export function total() -> int {
|
export function total(bank_st: BankState) -> int {
|
||||||
return balance
|
return bank_st.balance
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
# L3: `add` is private to module bank; calling it from outside the module is refused
|
# L3: `add` is private to module bank; calling it from outside the module is refused
|
||||||
import "private_bank"
|
import "private_bank"
|
||||||
program Private {
|
program Private {
|
||||||
entry {
|
entry (bank_st: mut BankState) {
|
||||||
add(5)
|
add(bank_st, 5)
|
||||||
print(total())
|
print(total(bank_st))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,5 +4,5 @@ module mech uses ludic_base
|
||||||
import "ludic.base"
|
import "ludic.base"
|
||||||
import "ludic.inventory"
|
import "ludic.inventory"
|
||||||
export function stock() -> int {
|
export function stock() -> int {
|
||||||
return inv_count(0)
|
return inv_limit(0)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
# a view field that is not a bare global has to say its type
|
# a view field that is not a bare field of its state has to say its type
|
||||||
import "ludic.ui"
|
import "ludic.ui"
|
||||||
program ViewUntyped {
|
program ViewUntyped {
|
||||||
var a: int = 1
|
state Nums { a: int = 1 }
|
||||||
view Sums {
|
view Sums (n: Nums) {
|
||||||
total = a + 2
|
total = n.a + 2
|
||||||
}
|
}
|
||||||
entry { print(view_sums().name) }
|
entry { print(1) }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
# a view is for templates, which ludic.ui shows
|
# a view is for templates, which ludic.ui shows
|
||||||
program ViewWithoutUi {
|
program ViewWithoutUi {
|
||||||
var a: int = 1
|
state Nums { a: int = 1 }
|
||||||
view Sums {
|
view Sums (n: Nums) {
|
||||||
a = a
|
a: int = n.a
|
||||||
}
|
}
|
||||||
entry { print(a) }
|
entry { print(1) }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -358,6 +358,7 @@ function emit_ns_call(ns: pointer, meth: pointer, e: Node) -> Val {
|
||||||
let al = ns_alias_find(ns, meth)
|
let al = ns_alias_find(ns, meth)
|
||||||
if al >= 0 { return emit_alias_call(al, e) }
|
if al >= 0 { return emit_alias_call(al, e) }
|
||||||
var bare: pointer = null
|
var bare: pointer = null
|
||||||
|
var nsfn: Node = null # a namespace function behind Ns.meth
|
||||||
let labels = new []pointer
|
let labels = new []pointer
|
||||||
# Sprite.* / Assets.* — the namespaced spritesheet / atlas API (#81), a runtime
|
# 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)
|
# 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}`) }
|
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
|
# a namespace function takes named arguments like any other function: its
|
||||||
# labels are its parameter names, so `Weapon.def(name: "pistol", ...)` works.
|
# labels are its parameter names, so `Weapon.def(name: "pistol", ...)` works.
|
||||||
let nsfn = find_fn(bare)
|
nsfn = find_fn(bare)
|
||||||
if (nsfn != null) {
|
if (nsfn != null) {
|
||||||
let names = param_labels(nsfn)
|
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 }
|
while pi2 < len(names) { push(labels, names[pi2]); pi2 += 1 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (bare == null) { perr(`unknown builtin {ns}.{meth}`) }
|
if (bare == null) { perr(`unknown builtin {ns}.{meth}`) }
|
||||||
reorder_named(e, labels)
|
reorder_named(e, labels)
|
||||||
|
state_inject(e, nsfn)
|
||||||
let id = node(E_ID); id.s = bare; e.a = id
|
let id = node(E_ID); id.s = bare; e.a = id
|
||||||
return emit_call(e)
|
return emit_call(e)
|
||||||
}
|
}
|
||||||
|
|
@ -955,6 +957,7 @@ function emit_call(e: Node) -> Val {
|
||||||
cname = rtname
|
cname = rtname
|
||||||
}
|
}
|
||||||
vis_check(fn2, name)
|
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
|
call_fill_defaults(e, fn2) # L11 - for code the checker does not walk
|
||||||
reorder_named(e, param_labels(fn2))
|
reorder_named(e, param_labels(fn2))
|
||||||
# evaluate args first (their IR is emitted before the call instruction), coercing
|
# evaluate args first (their IR is emitted before the call instruction), coercing
|
||||||
|
|
|
||||||
|
|
@ -154,6 +154,36 @@ function deps_chain_root(t: Node) -> Node {
|
||||||
if n == null or n.kind != E_ID { return null }
|
if n == null or n.kind != E_ID { return null }
|
||||||
return n
|
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
|
# `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 {
|
function deps_alias_let(local: pointer, e: Node) -> void {
|
||||||
if not deps_on() or g_err_file == null { return }
|
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)
|
let r = deps_chain_root(e)
|
||||||
if r == null { return }
|
if r == null { return }
|
||||||
if loc_find(r.s) >= 0 {
|
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)
|
let k = deps_alias_find(r.s)
|
||||||
if k < 0 { return }
|
if k < 0 { return }
|
||||||
push(g_dp_al_local, local)
|
push(g_dp_al_local, local)
|
||||||
|
|
|
||||||
|
|
@ -113,6 +113,13 @@ function emit_inj_thunk(d: Node) -> pointer {
|
||||||
emith(`define {rt} {name}({params}) {{\nentry:\n{body}{call}}}\n\n`)
|
emith(`define {rt} {name}({params}) {{\nentry:\n{body}{call}}}\n\n`)
|
||||||
return name
|
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
|
# a call through a value of a function type
|
||||||
function emit_indirect_call(fv: Val, e: Node) -> Val {
|
function emit_indirect_call(fv: Val, e: Node) -> Val {
|
||||||
let ptys = fn_ty_params(fv.ty)
|
let ptys = fn_ty_params(fv.ty)
|
||||||
|
|
@ -174,7 +181,8 @@ function check_worker_ref(e: Node) -> void {
|
||||||
let d = find_fn(e.s)
|
let d = find_fn(e.s)
|
||||||
if d == null { return }
|
if d == null { return }
|
||||||
# the thread calls it as void(i32, ptr): any pointer-sized context will do (words, a record)
|
# 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"
|
let s = state_lead(d) # its states come from the thunk
|
||||||
if ok { ok = llty(d.kids[0].ty) == "i32" and llty(d.kids[1].ty) == "ptr" }
|
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`) }
|
if not ok { perr(`fn {e.s}: a worker function takes (i: int, ctx: pointer) and returns nothing`) }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ function emit_assign(st: Node) -> void {
|
||||||
} else {
|
} else {
|
||||||
if deps_on() {
|
if deps_on() {
|
||||||
let root = deps_target_root(t)
|
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 }
|
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 }
|
else { if t.kind == E_INDEX { addr = emit_index_addr(t); ty = g_addr_ty }
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ function emit_call_one(d: Node) -> void {
|
||||||
function emit_one_engine_system(comp: pointer, fn: pointer) -> 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 is_system_disabled(fn) { return } # lever 5 (#57): `disable system <fn>` drops the tick
|
||||||
if (find_comp(comp) != null) and (find_fn(fn) != null) {
|
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 {
|
function emit_engine_systems_for_phase(phase: pointer) -> void {
|
||||||
|
|
|
||||||
|
|
@ -11,27 +11,49 @@ function machine_var(a: Node) -> Node {
|
||||||
}
|
}
|
||||||
return null
|
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
|
# 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
|
# 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.
|
# (`var mode: HeroState` + `state Rolling` -> HeroState.Rolling); else its ordinal.
|
||||||
function machine_state_value(m: Node, state: Node) -> pointer {
|
function machine_state_value(m: Node, state: Node) -> pointer {
|
||||||
if (state.b != null) { return emit_expr(state.b).code }
|
if (state.b != null) { return emit_expr(state.b).code }
|
||||||
let gv = machine_var(m.a)
|
g_mach_ty = null
|
||||||
if (gv != null) {
|
let addr = machine_addr(m.a)
|
||||||
if (gv.ty != null) {
|
let ty = g_mach_ty
|
||||||
let ord = enum_ordinal(gv.ty, state.s)
|
if addr != null and ty != null {
|
||||||
if ord >= 0 { return itoa(ord) }
|
let ord = enum_ordinal(ty, state.s)
|
||||||
if (find_enum(gv.ty) != null) { perr(`machine state {state.s} is not a variant of enum {gv.ty}`) }
|
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)
|
return itoa(state.ival)
|
||||||
}
|
}
|
||||||
|
|
||||||
function emit_machine(st: Node) -> void {
|
function emit_machine(st: Node) -> void {
|
||||||
let gv = machine_var(st.a)
|
let addr = machine_addr(st.a)
|
||||||
var s: pointer = ""
|
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 {
|
else {
|
||||||
let regv = emit_expr(st.a)
|
let regv = emit_expr(st.a)
|
||||||
s = emit_bind(`call i32 @fn_rt_reg(i32 {regv.code})`)
|
s = emit_bind(`call i32 @fn_rt_reg(i32 {regv.code})`)
|
||||||
|
|
@ -68,10 +90,10 @@ function emit_become(st: Node) -> void {
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(m.kids) { if (m.kids[i].s == st.s) { target = m.kids[i] }; i += 1 }
|
while i < len(m.kids) { if (m.kids[i].s == st.s) { target = m.kids[i] }; i += 1 }
|
||||||
if (target != null) {
|
if (target != null) {
|
||||||
let gv = machine_var(m.a)
|
|
||||||
let sv = machine_state_value(m, target)
|
let sv = machine_state_value(m, target)
|
||||||
if (gv != null) {
|
let addr = machine_addr(m.a)
|
||||||
emit(" store i32 "); emit(sv); emit(", ptr @g_"); emit(m.a.s); emit("\n")
|
if (addr != null) {
|
||||||
|
emit(` store i32 {sv}, ptr {addr}\n`)
|
||||||
} else {
|
} else {
|
||||||
let regv = emit_expr(m.a)
|
let regv = emit_expr(m.a)
|
||||||
emit(" call void @fn_rt_set_reg(i32 "); emit(regv.code); emit(", i32 "); emit(sv); emit(")\n")
|
emit(" call void @fn_rt_set_reg(i32 "); emit(regv.code); emit(", i32 "); emit(sv); emit(")\n")
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
function ck_call_fn(e: Node, name: pointer, f: Node) -> pointer {
|
||||||
|
state_inject_generated(e, f)
|
||||||
mg_call(e, f)
|
mg_call(e, f)
|
||||||
call_fill_defaults(e, f)
|
call_fill_defaults(e, f)
|
||||||
ck_state_args(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: 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
|
# `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
|
# 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
|
# the way down: the same writes starting at one are refused. A reference read out of either into a
|
||||||
# read out of a read-only state into a local (`let l = h.list`) and changed there.
|
# 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_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 {
|
function ck_set_ro(ro: bool) -> void {
|
||||||
let k = ck_top - 1
|
let k = ck_top - 1
|
||||||
while len(ck_ro) < len(ck_names) { push(ck_ro, 0) }
|
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 }
|
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 {
|
function ck_is_ro(li: int) -> bool {
|
||||||
if li < 0 or li >= len(ck_ro) { return false }
|
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 }
|
if r == null { return }
|
||||||
let li = ck_local(r.s)
|
let li = ck_local(r.s)
|
||||||
if li >= 0 {
|
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) {
|
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`)
|
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 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`) }
|
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)
|
ck_set_ro(s.uns == 0)
|
||||||
}
|
} else { ck_let_from(s.a, s.ty) }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ck_bind(s.s, t)
|
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 {
|
if s.a != null and s.a.kind == E_ID {
|
||||||
let li = ck_local(s.a.s)
|
let li = ck_local(s.a.s)
|
||||||
if li >= 0 and li < ck_top - 1 and ck_is_ro(li) { ck_set_ro(true) }
|
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 {
|
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)
|
let lt = ck_target(s.a)
|
||||||
if (s.s == "=") { ck_expect = lt }
|
if (s.s == "=") { ck_expect = lt }
|
||||||
let rt = ck_expr(s.b)
|
let rt = ck_expr(s.b)
|
||||||
|
|
@ -124,6 +126,33 @@ function ck_stmt(s: Node) -> void {
|
||||||
ck_unsafe -= 1
|
ck_unsafe -= 1
|
||||||
return
|
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 {
|
function ck_fn_body(d: Node) -> void {
|
||||||
mg_enter(d, 0)
|
mg_enter(d, 0)
|
||||||
|
|
@ -182,6 +211,35 @@ function ck_runtime_shadows() -> void {
|
||||||
r += 1
|
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
|
# the pass also makes the generics real (check_gen.ludic), so it always runs
|
||||||
function check_program() -> void {
|
function check_program() -> void {
|
||||||
gen_collect()
|
gen_collect()
|
||||||
|
|
@ -196,6 +254,11 @@ function check_program() -> void {
|
||||||
ck_block(d.a)
|
ck_block(d.a)
|
||||||
mg_leave()
|
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
|
if d.kind == N_SYS and g_migrate { # a handler's body: only the migration reads it here
|
||||||
mg_enter(d, 2)
|
mg_enter(d, 2)
|
||||||
ck_block(d.a)
|
ck_block(d.a)
|
||||||
|
|
@ -215,6 +278,20 @@ function check_program() -> void {
|
||||||
ck_listener(g_onlisten[j])
|
ck_listener(g_onlisten[j])
|
||||||
j += 1
|
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
|
# a test block is checked like `entry`: that is also what makes its generic calls real
|
||||||
j = 0
|
j = 0
|
||||||
while j < len(g_tests) {
|
while j < len(g_tests) {
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,7 @@ property Node {
|
||||||
pos: int = -1 # 0.S: byte offset of its first token in its file (-1: generated)
|
pos: int = -1 # 0.S: byte offset of its first token in its file (-1: generated)
|
||||||
pos2: int = -1 # 0.S: a second place a rewrite needs (a declaration's end, a '(')
|
pos2: int = -1 # 0.S: a second place a rewrite needs (a declaration's end, a '(')
|
||||||
mg: int = -1 # 0.S2: its index in the migration's tables (migrate.ludic)
|
mg: int = -1 # 0.S2: its index in the migration's tables (migrate.ludic)
|
||||||
|
vw: int = -1 # 0.S: the view a function was generated for or written in
|
||||||
}
|
}
|
||||||
|
|
||||||
# every node remembers where it was parsed (file + the line of the token the
|
# every node remembers where it was parsed (file + the line of the token the
|
||||||
|
|
@ -104,6 +105,7 @@ function node(kind: int) -> Node {
|
||||||
if g_parsing and pi < len(toks) {
|
if g_parsing and pi < len(toks) {
|
||||||
n.line = toks[pi].line
|
n.line = toks[pi].line
|
||||||
n.pos = toks[pi].pos
|
n.pos = toks[pi].pos
|
||||||
|
if g_gen_nodes { n.pos = -1 } # made by the compiler, not read from a file
|
||||||
}
|
}
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,7 @@ function parse_ui_component() -> void {
|
||||||
let head = node(N_VAR)
|
let head = node(N_VAR)
|
||||||
head.s = px + "reg"
|
head.s = px + "reg"
|
||||||
head.ty = "int"
|
head.ty = "int"
|
||||||
|
head.pos = -1 # the compiler's own, not a program's var
|
||||||
push(g_cm_head, head)
|
push(g_cm_head, head)
|
||||||
push(prog, head)
|
push(prog, head)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ var g_ms_param: []pointer = new []pointer
|
||||||
var g_ms_export: []int = new []int
|
var g_ms_export: []int = new []int
|
||||||
# units: the functions and entry points whose states are worked out
|
# units: the functions and entry points whose states are worked out
|
||||||
var g_mu_node: []Node = new []Node
|
var g_mu_node: []Node = new []Node
|
||||||
var g_mu_kind: []int = new []int # 0 function, 1 entry block, 2 handler, 3 test, 4 listener
|
var g_mu_kind: []int = new []int # 0 function, 1 entry block, 2 handler, 3 test, 4 listener or hook, 5 a scene's on enter/exit
|
||||||
var g_mu_need: []pointer = new []pointer # ",0,3," state indices it needs
|
var g_mu_need: []pointer = new []pointer # ",0,3," state indices it needs
|
||||||
var g_mu_mut: []pointer = new []pointer # the ones it changes
|
var g_mu_mut: []pointer = new []pointer # the ones it changes
|
||||||
var g_mg_cur: int = -1
|
var g_mg_cur: int = -1
|
||||||
|
|
@ -33,6 +33,7 @@ var g_mc_unit: []int = new []int
|
||||||
var g_mc_callee: []int = new []int
|
var g_mc_callee: []int = new []int
|
||||||
var g_mr_node: []Node = new []Node # every reference to a moving var
|
var g_mr_node: []Node = new []Node # every reference to a moving var
|
||||||
var g_mr_var: []int = new []int
|
var g_mr_var: []int = new []int
|
||||||
|
var g_mr_ui: []int = new []int # 1: in a ui block, rewritten as State.var
|
||||||
var g_mg_report: []pointer = new []pointer
|
var g_mg_report: []pointer = new []pointer
|
||||||
|
|
||||||
function mg_camel(s: pointer) -> pointer {
|
function mg_camel(s: pointer) -> pointer {
|
||||||
|
|
@ -118,12 +119,40 @@ function mg_state_of_mod(m: pointer, base: pointer) -> int {
|
||||||
if (g_ms_mod[i] == m) { return i }
|
if (g_ms_mod[i] == m) { return i }
|
||||||
i += 1
|
i += 1
|
||||||
}
|
}
|
||||||
|
let name = `{mg_camel(base)}State`
|
||||||
|
# the module's state from a run before: its new vars are more of its fields
|
||||||
|
i = 0
|
||||||
|
while i < len(g_ms_name) {
|
||||||
|
if (g_ms_name[i] == name) {
|
||||||
|
g_ms_mod[i] = m
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
push(g_ms_mod, m)
|
push(g_ms_mod, m)
|
||||||
push(g_ms_name, `{mg_camel(base)}State`)
|
push(g_ms_name, name)
|
||||||
push(g_ms_param, `{base}_st`)
|
push(g_ms_param, `{base}_st`)
|
||||||
push(g_ms_export, 0)
|
push(g_ms_export, 0)
|
||||||
return len(g_ms_mod) - 1
|
return len(g_ms_mod) - 1
|
||||||
}
|
}
|
||||||
|
# where a state is already declared, for its new fields: the declaration, else null
|
||||||
|
function mg_state_decl(name: pointer) -> Node {
|
||||||
|
var i = 0
|
||||||
|
while i < len(prog) {
|
||||||
|
if prog[i].kind == N_COMP and prog[i].uns == 2 and (prog[i].s == name) { return prog[i] }
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
# a name as its source spells it: a module's private one is `dice$shop` inside the compiler
|
||||||
|
function mg_src_name(s: pointer) -> pointer {
|
||||||
|
var i = 0
|
||||||
|
while i < len(s) {
|
||||||
|
if s[i] == '$' { return s[0 .. i] }
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
function mg_is_target(v: Node) -> bool {
|
function mg_is_target(v: Node) -> bool {
|
||||||
if v.kind != N_VAR or v.uns != 0 or v.file == null or v.pos < 0 { return false }
|
if v.kind != N_VAR or v.uns != 0 or v.file == null or v.pos < 0 { return false }
|
||||||
if reg_find(v.s) >= 0 or is_port_var(v) { return false }
|
if reg_find(v.s) >= 0 or is_port_var(v) { return false }
|
||||||
|
|
@ -202,9 +231,11 @@ function mg_ref(e: Node, g: Node) -> void {
|
||||||
if not g_migrate or g == null or g.kind != N_VAR or g.mg < 0 { return }
|
if not g_migrate or g == null or g.kind != N_VAR or g.mg < 0 { return }
|
||||||
push(g_mr_node, e)
|
push(g_mr_node, e)
|
||||||
push(g_mr_var, g.mg)
|
push(g_mr_var, g.mg)
|
||||||
|
if g_mg_cur == -3 { push(g_mr_ui, 1) } else { push(g_mr_ui, 0) }
|
||||||
|
if g_mg_cur == -3 { return }
|
||||||
if g_mg_cur >= 0 { mg_need(g_mg_cur, g_mg_vstate[g.mg], false) }
|
if g_mg_cur >= 0 { mg_need(g_mg_cur, g_mg_vstate[g.mg], false) }
|
||||||
else { mg_say(e, `{g.s} is read where no function is (a global's initializer); move it by hand`) }
|
else { mg_say(e, `{g.s} is read where no function is (a global's initializer); move it by hand`) }
|
||||||
if e.pos < 0 { mg_say(e, `a reference to {g.s} in generated code (a component, a view, a registry's lookup)`) }
|
if e.pos < 0 and not mg_bound_var(g.s) { mg_say(e, `a reference to {g.s} in generated code (a component, a view, a registry's lookup)`) }
|
||||||
}
|
}
|
||||||
function mg_write(t: Node, g: Node) -> void {
|
function mg_write(t: Node, g: Node) -> void {
|
||||||
if not g_migrate or g == null or g.kind != N_VAR or g.mg < 0 { return }
|
if not g_migrate or g == null or g.kind != N_VAR or g.mg < 0 { return }
|
||||||
|
|
@ -213,6 +244,7 @@ function mg_write(t: Node, g: Node) -> void {
|
||||||
var g_mc_passed: []int = new []int # 1: the call already hands the callee a state (a run before)
|
var g_mc_passed: []int = new []int # 1: the call already hands the callee a state (a run before)
|
||||||
function mg_call(e: Node, f: Node) -> void {
|
function mg_call(e: Node, f: Node) -> void {
|
||||||
if not g_migrate or f == null or g_mg_cur < 0 { return }
|
if not g_migrate or f == null or g_mg_cur < 0 { return }
|
||||||
|
if e.pos < 0 { return } # a generated call gets its states when compiled
|
||||||
push(g_mc_call, e)
|
push(g_mc_call, e)
|
||||||
push(g_mc_unit, g_mg_cur)
|
push(g_mc_unit, g_mg_cur)
|
||||||
push(g_mc_callee, mg_unit(f, 0))
|
push(g_mc_callee, mg_unit(f, 0))
|
||||||
|
|
@ -346,9 +378,28 @@ function mg_units() -> void {
|
||||||
u += 1
|
u += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var g_mv_need: []pointer = new []pointer # per view: the states its functions need
|
||||||
|
var g_mv_mut: []pointer = new []pointer
|
||||||
function mg_unit_edit(u: int, n: Node, text: pointer) -> void {
|
function mg_unit_edit(u: int, n: Node, text: pointer) -> void {
|
||||||
let kind = g_mu_kind[u]
|
let kind = g_mu_kind[u]
|
||||||
|
if kind == 0 and n.vw >= 0 {
|
||||||
|
# a view's getter, query or event: the view declares the states, for all of them
|
||||||
|
while len(g_mv_need) < len(g_vw_name) {
|
||||||
|
push(g_mv_need, ",")
|
||||||
|
push(g_mv_mut, ",")
|
||||||
|
}
|
||||||
|
let need = mg_list(g_mu_need[u])
|
||||||
|
var k = 0
|
||||||
|
while k < len(need) {
|
||||||
|
let key = `,{itoa(need[k])},`
|
||||||
|
if not has_sub(g_mv_need[n.vw], key) { g_mv_need[n.vw] = g_mv_need[n.vw] + itoa(need[k]) + "," }
|
||||||
|
if has_sub(g_mu_mut[u], key) and not has_sub(g_mv_mut[n.vw], key) { g_mv_mut[n.vw] = g_mv_mut[n.vw] + itoa(need[k]) + "," }
|
||||||
|
k += 1
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
if kind == 0 {
|
if kind == 0 {
|
||||||
|
if n.pos2 < 0 and len(n.s) > 5 and (n.s[0 .. 5] == "bind_") { return } # a port bind's getter: its bind moves
|
||||||
if n.pos2 < 0 or n.file == null {
|
if n.pos2 < 0 or n.file == null {
|
||||||
mg_say(n, `{n.s} needs {text} and is generated code: its caller cannot be rewritten`)
|
mg_say(n, `{n.s} needs {text} and is generated code: its caller cannot be rewritten`)
|
||||||
return
|
return
|
||||||
|
|
@ -360,14 +411,29 @@ function mg_unit_edit(u: int, n: Node, text: pointer) -> void {
|
||||||
}
|
}
|
||||||
# an entry point: a list of the states the runtime supplies
|
# an entry point: a list of the states the runtime supplies
|
||||||
var body = n.a
|
var body = n.a
|
||||||
if kind == 4 { body = n.a }
|
if kind == 5 { body = n } # a scene's `on enter` block is its own body
|
||||||
if body != null and len(body.kids) > 0 and body.kids[0].tps != null and (body.kids[0].tps == "state") {
|
if body != null and len(body.kids) > 0 and body.kids[0].tps != null and (body.kids[0].tps == "state") {
|
||||||
mg_say(n, `this entry point declares states already; add ({text}) to them by hand`)
|
# it declares some already: whatever it needs beyond them is for a person to add
|
||||||
|
let need = mg_list(g_mu_need[u])
|
||||||
|
var more = ""
|
||||||
|
var k = 0
|
||||||
|
while k < len(need) {
|
||||||
|
var have = false
|
||||||
|
var j = 0
|
||||||
|
while j < len(body.kids) and body.kids[j].tps != null and (body.kids[j].tps == "state") {
|
||||||
|
if (body.kids[j].ty == g_ms_name[need[k]]) { have = true }
|
||||||
|
j += 1
|
||||||
|
}
|
||||||
|
if not have { more = more + " " + g_ms_name[need[k]] }
|
||||||
|
k += 1
|
||||||
|
}
|
||||||
|
if len(more) > 0 { mg_say(n, `this entry point declares states already; add{more} to them by hand`) }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if kind == 1 { mg_emit(`INS {n.file} {itoa(n.pos)} {mg_esc(`({text}) `)}`, `{n.file}@{itoa(n.pos)}`) }
|
if kind == 1 { mg_emit(`INS {n.file} {itoa(n.pos)} {mg_esc(`({text}) `)}`, `{n.file}@{itoa(n.pos)}`) }
|
||||||
if kind == 2 { mg_emit(`INS {n.file} {itoa(n.pos2)} {mg_esc(`({text})`)}`, `{n.file}@{itoa(n.pos2)}`) }
|
if kind == 2 { mg_emit(`INS {n.file} {itoa(n.pos2)} {mg_esc(`({text})`)}`, `{n.file}@{itoa(n.pos2)}`) }
|
||||||
if kind == 3 { mg_emit(`INS {n.file} {itoa(n.pos2)} {mg_esc(` ({text})`)}`, `{n.file}@{itoa(n.pos2)}`) }
|
if kind == 3 { mg_emit(`INS {n.file} {itoa(n.pos2)} {mg_esc(` ({text})`)}`, `{n.file}@{itoa(n.pos2)}`) }
|
||||||
|
if kind == 5 { mg_emit(`INS {n.file} {itoa(n.pos2)} {mg_esc(` ({text})`)}`, `{n.file}@{itoa(n.pos2)}`) }
|
||||||
if kind == 4 {
|
if kind == 4 {
|
||||||
let b = n.a
|
let b = n.a
|
||||||
if b == null or b.pos2 < 0 {
|
if b == null or b.pos2 < 0 {
|
||||||
|
|
@ -377,6 +443,26 @@ function mg_unit_edit(u: int, n: Node, text: pointer) -> void {
|
||||||
mg_emit(`INS {b.file} {itoa(b.pos2)} {mg_esc(`({text})`)}`, `{b.file}@{itoa(b.pos2)}`)
|
mg_emit(`INS {b.file} {itoa(b.pos2)} {mg_esc(`({text})`)}`, `{b.file}@{itoa(b.pos2)}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function mg_views() -> void {
|
||||||
|
var v = 0
|
||||||
|
while v < len(g_mv_need) {
|
||||||
|
let need = mg_list(g_mv_need[v])
|
||||||
|
if len(need) > 0 {
|
||||||
|
let head = g_vw_node[v]
|
||||||
|
if g_vw_nps[v] > 0 { mg_say(head, `view {g_vw_name[v]} declares states already; add what it needs by hand`) } else {
|
||||||
|
var text = ""
|
||||||
|
var k = 0
|
||||||
|
while k < len(need) {
|
||||||
|
if len(text) > 0 { text = text + ", " }
|
||||||
|
if has_sub(g_mv_mut[v], `,{itoa(need[k])},`) { text = text + `{g_ms_param[need[k]]}: mut {g_ms_name[need[k]]}` } else { text = text + `{g_ms_param[need[k]]}: {g_ms_name[need[k]]}` }
|
||||||
|
k += 1
|
||||||
|
}
|
||||||
|
mg_emit(`INS {head.file} {itoa(g_vw_hdr[v])} {mg_esc(` ({text})`)}`, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
function mg_calls() -> void {
|
function mg_calls() -> void {
|
||||||
var c = 0
|
var c = 0
|
||||||
while c < len(g_mc_call) {
|
while c < len(g_mc_call) {
|
||||||
|
|
@ -395,8 +481,8 @@ function mg_calls() -> void {
|
||||||
}
|
}
|
||||||
k += 1
|
k += 1
|
||||||
}
|
}
|
||||||
if len(text) > 0 {
|
if len(text) > 0 and e.pos >= 0 { # a generated call gets its states when compiled
|
||||||
if e.pos < 0 or e.file == null { mg_say(e, `a call to {callee.s} in generated code needs ({text})`) } else {
|
if e.file == null { mg_say(e, `a call to {callee.s} with no place needs ({text})`) } else {
|
||||||
var t = text
|
var t = text
|
||||||
if len(e.kids) > 0 { t = t + ", " }
|
if len(e.kids) > 0 { t = t + ", " }
|
||||||
mg_emit(`INS {e.file} {itoa(e.pos + 1)} {mg_esc(t)}`, `{e.file}@{itoa(e.pos)}`)
|
mg_emit(`INS {e.file} {itoa(e.pos + 1)} {mg_esc(t)}`, `{e.file}@{itoa(e.pos)}`)
|
||||||
|
|
@ -413,7 +499,10 @@ function mg_refs() -> void {
|
||||||
let v = g_mg_vars[g_mr_var[r]]
|
let v = g_mg_vars[g_mr_var[r]]
|
||||||
if e.pos >= 0 and e.file != null {
|
if e.pos >= 0 and e.file != null {
|
||||||
let s = g_mg_vstate[g_mr_var[r]]
|
let s = g_mg_vstate[g_mr_var[r]]
|
||||||
mg_emit(`REF {e.file} {itoa(e.pos)} {itoa(len(v.s))} {mg_esc(`{g_ms_param[s]}.{v.s}`)}`, `{e.file}@{itoa(e.pos)}`)
|
let nm = mg_src_name(v.s)
|
||||||
|
var text = `{g_ms_param[s]}.{nm}`
|
||||||
|
if g_mr_ui[r] == 1 { text = `{g_ms_name[s]}.{nm}` }
|
||||||
|
mg_emit(`REF {e.file} {itoa(e.pos)} {itoa(len(nm))} {mg_esc(text)}`, `{e.file}@{itoa(e.pos)}`)
|
||||||
}
|
}
|
||||||
r += 1
|
r += 1
|
||||||
}
|
}
|
||||||
|
|
@ -436,6 +525,32 @@ function mg_exports() -> void {
|
||||||
u += 1
|
u += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
# a port member bound to a var (`money: g_money`) becomes the state's field (`money: S.g_money`)
|
||||||
|
var g_mg_pbv: []Node = new []Node
|
||||||
|
function mg_bind_var(fi: Node) -> void {
|
||||||
|
if g_migrate and fi != null and fi.a != null and fi.a.kind == E_ID { push(g_mg_pbv, fi.a) }
|
||||||
|
}
|
||||||
|
function mg_bound_var(name: pointer) -> bool {
|
||||||
|
var i = 0
|
||||||
|
while i < len(g_mg_pbv) {
|
||||||
|
if (g_mg_pbv[i].s == name) { return true }
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
function mg_binds() -> void {
|
||||||
|
var i = 0
|
||||||
|
while i < len(g_mg_pbv) {
|
||||||
|
let e = g_mg_pbv[i]
|
||||||
|
let g = find_global(e.s)
|
||||||
|
if g != null and g.mg >= 0 and e.pos >= 0 {
|
||||||
|
let s = g_mg_vstate[g.mg]
|
||||||
|
let nm = mg_src_name(e.s)
|
||||||
|
mg_emit(`REF {e.file} {itoa(e.pos)} {itoa(len(nm))} {mg_esc(`{g_ms_name[s]}.{nm}`)}`, "")
|
||||||
|
}
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
function mg_finish() -> void {
|
function mg_finish() -> void {
|
||||||
mg_fixpoint()
|
mg_fixpoint()
|
||||||
mg_exports()
|
mg_exports()
|
||||||
|
|
@ -455,14 +570,19 @@ function mg_finish() -> void {
|
||||||
if g_mg_vstate[j] == s { first = j }
|
if g_mg_vstate[j] == s { first = j }
|
||||||
j += 1
|
j += 1
|
||||||
}
|
}
|
||||||
if first >= 0 {
|
let decl = mg_state_decl(g_ms_name[s])
|
||||||
|
if first >= 0 and decl != null {
|
||||||
|
mg_emit(`STATE {g_ms_name[s]} {decl.file} {itoa(decl.pos)} {itoa(g_ms_export[s])} {g_ms_param[s]} more`, "")
|
||||||
|
} else if first >= 0 {
|
||||||
let v = g_mg_vars[first]
|
let v = g_mg_vars[first]
|
||||||
mg_emit(`STATE {g_ms_name[s]} {v.file} {itoa(v.pos)} {itoa(g_ms_export[s])} {g_ms_param[s]}`, "")
|
mg_emit(`STATE {g_ms_name[s]} {v.file} {itoa(v.pos)} {itoa(g_ms_export[s])} {g_ms_param[s]}`, "")
|
||||||
}
|
}
|
||||||
s += 1
|
s += 1
|
||||||
}
|
}
|
||||||
mg_refs()
|
mg_refs()
|
||||||
|
mg_binds()
|
||||||
mg_units()
|
mg_units()
|
||||||
|
mg_views()
|
||||||
mg_calls()
|
mg_calls()
|
||||||
let f = file_open(g_mg_plan, "wb")
|
let f = file_open(g_mg_plan, "wb")
|
||||||
if f == null { die("ludicc: cannot write the migration plan\n") }
|
if f == null { die("ludicc: cannot write the migration plan\n") }
|
||||||
|
|
|
||||||
|
|
@ -1023,7 +1023,7 @@ function parse_one_decl() -> void {
|
||||||
}
|
}
|
||||||
if is_id("registry") and (toks[pi + 1].kind == TK_ID) and (toks[pi + 2].text == "of") { parse_registry(); return } # L8
|
if is_id("registry") and (toks[pi + 1].kind == TK_ID) and (toks[pi + 2].text == "of") { parse_registry(); return } # L8
|
||||||
if is_id("def") and (toks[pi + 1].kind == TK_ID) and (toks[pi + 2].kind == TK_ID) { parse_def(); return } # L8
|
if is_id("def") and (toks[pi + 1].kind == TK_ID) and (toks[pi + 2].kind == TK_ID) { parse_def(); return } # L8
|
||||||
if is_id("view") and (toks[pi + 1].kind == TK_ID) and (toks[pi + 2].text == "{") { parse_view(); return } # L11
|
if is_id("view") and (toks[pi + 1].kind == TK_ID) and ((toks[pi + 2].text == "{") or (toks[pi + 2].text == "(")) { parse_view(); return } # L11
|
||||||
if is_id("component") and (toks[pi + 1].kind == TK_ID) and (toks[pi + 2].text == "{") { parse_ui_component(); return } # L11
|
if is_id("component") and (toks[pi + 1].kind == TK_ID) and (toks[pi + 2].text == "{") { parse_ui_component(); return } # L11
|
||||||
if is_id("state") and (toks[pi + 1].kind == TK_ID) and (toks[pi + 2].text == "{") { parse_state(); return } # 0.S
|
if is_id("state") and (toks[pi + 1].kind == TK_ID) and (toks[pi + 2].text == "{") { parse_state(); return } # 0.S
|
||||||
if is_id("var") {
|
if is_id("var") {
|
||||||
|
|
@ -1565,9 +1565,12 @@ function parse_program() -> void {
|
||||||
if is_op("}") { break }
|
if is_op("}") { break }
|
||||||
parse_one_decl()
|
parse_one_decl()
|
||||||
}
|
}
|
||||||
|
g_gen_nodes = true # 0.S: what the finishing passes make is generated
|
||||||
modules_finish() # L3: the declared uses have no cycle
|
modules_finish() # L3: the declared uses have no cycle
|
||||||
registries_finish() # L8: each registry gets its defs
|
registries_finish() # L8: each registry gets its defs
|
||||||
ports_finish() # L3: each port gets its one bind
|
ports_finish() # L3: each port gets its one bind
|
||||||
views_finish() # L11: each view gets its model and call
|
views_finish() # L11: each view gets its model and call
|
||||||
components_finish() # L11: each component gets its class
|
components_finish() # L11: each component gets its class
|
||||||
|
ui_blocks_states() # 0.S: a ui block reads a state as State.field
|
||||||
|
g_gen_nodes = false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -209,10 +209,16 @@ function parse_scene() -> void {
|
||||||
while true {
|
while true {
|
||||||
skipnl(); if is_op("}") { break }
|
skipnl(); if is_op("}") { break }
|
||||||
if is_id("on") { # on enter { .. } / on exit { .. }
|
if is_id("on") { # on enter { .. } / on exit { .. }
|
||||||
pi += 1; let which = eat_id(); skipnl()
|
pi += 1; let which = eat_id()
|
||||||
if which == "enter" { n.a = block() }
|
let at = toks[pi - 1].end # 0.S: where its states would go
|
||||||
else if which == "exit" { n.b = block() }
|
let ps = parse_entry_params() # on enter (h: mut Hiker) { .. }
|
||||||
|
skipnl()
|
||||||
|
var body: Node = null
|
||||||
|
if which == "enter" { n.a = block(); body = n.a }
|
||||||
|
else if which == "exit" { n.b = block(); body = n.b }
|
||||||
else { perr("expected 'enter' or 'exit' after 'on'") }
|
else { perr("expected 'enter' or 'exit' after 'on'") }
|
||||||
|
body.pos2 = at
|
||||||
|
entry_bind(body, ps)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if is_id("layer") { # layer Name [public] { handler .. }
|
if is_id("layer") { # layer Name [public] { handler .. }
|
||||||
|
|
@ -268,7 +274,7 @@ function scene_add_loader(sc: Node, next: pointer) -> void {
|
||||||
# generated Update handler that `become`s T at zero — the splash / banner scene.
|
# generated Update handler that `become`s T at zero — the splash / banner scene.
|
||||||
function scene_add_timer(sc: Node, frames: Node, next: pointer) -> void {
|
function scene_add_timer(sc: Node, frames: Node, next: pointer) -> void {
|
||||||
let counter = `scene_{sc.s}_left`
|
let counter = `scene_{sc.s}_left`
|
||||||
let v = node(N_VAR); v.s = counter; v.ty = "int"; push(prog, v)
|
let v = node(N_VAR); v.s = counter; v.ty = "int"; v.pos = -1; push(prog, v) # the compiler's own
|
||||||
if (sc.a == null) { sc.a = node(N_BLOCK) }
|
if (sc.a == null) { sc.a = node(N_BLOCK) }
|
||||||
let set = node(S_ASSIGN); set.s = "="; let lhs = node(E_ID); lhs.s = counter; set.a = lhs; set.b = frames
|
let set = node(S_ASSIGN); set.s = "="; let lhs = node(E_ID); lhs.s = counter; set.a = lhs; set.b = frames
|
||||||
let ins = new []Node; push(ins, set)
|
let ins = new []Node; push(ins, set)
|
||||||
|
|
|
||||||
|
|
@ -136,6 +136,7 @@ function port_defaults_only(k: int) -> void {
|
||||||
# `money: gm_money` for a `money: fn() -> int` member: the function `bind_<Port>_<member>` that
|
# `money: gm_money` for a `money: fn() -> int` member: the function `bind_<Port>_<member>` that
|
||||||
# returns the variable, written in the bind's own file (so it sees what the bind sees)
|
# returns the variable, written in the bind's own file (so it sees what the bind sees)
|
||||||
function port_bind_var(b: Node, c: Node, fi: Node) -> void {
|
function port_bind_var(b: Node, c: Node, fi: Node) -> void {
|
||||||
|
mg_bind_var(fi) # 0.S2: ludic migrate state moves it to the state's field
|
||||||
# 0.S: `money: Wallet.cash` - a field of a state, read through the state the runtime supplies
|
# 0.S: `money: Wallet.cash` - a field of a state, read through the state the runtime supplies
|
||||||
var sty: pointer = null
|
var sty: pointer = null
|
||||||
var name: pointer = null
|
var name: pointer = null
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,35 @@
|
||||||
# toolchain's own programs, which are not part of this); a module-level `let` is deeply immutable.
|
# toolchain's own programs, which are not part of this); a module-level `let` is deeply immutable.
|
||||||
var g_state_names: []pointer = new []pointer
|
var g_state_names: []pointer = new []pointer
|
||||||
var g_allow_globals: bool = false
|
var g_allow_globals: bool = false
|
||||||
|
var g_gen_nodes: bool = false # the finishing passes are writing code: its nodes are generated
|
||||||
|
|
||||||
|
# a call the compiler wrote itself (a view's model, a component's glue): its callee's states are
|
||||||
|
# supplied - the runtime's side of an entry point
|
||||||
|
function state_inject_generated(e: Node, f: Node) -> void {
|
||||||
|
if e != null and e.pos < 0 { state_inject(e, f) }
|
||||||
|
}
|
||||||
|
# `(h: mut Hiker)` copied onto the front of a function's parameters (a view's functions)
|
||||||
|
function state_params_onto(f: Node, ps: []Node) -> void {
|
||||||
|
if len(ps) == 0 { return }
|
||||||
|
let kids = new []Node
|
||||||
|
var i = 0
|
||||||
|
while i < len(ps) {
|
||||||
|
let p = node(N_PARAM)
|
||||||
|
p.s = ps[i].s
|
||||||
|
p.ty = ps[i].ty
|
||||||
|
p.uns = ps[i].uns
|
||||||
|
p.file = ps[i].file
|
||||||
|
p.line = ps[i].line
|
||||||
|
push(kids, p)
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
i = 0
|
||||||
|
while i < len(f.kids) {
|
||||||
|
push(kids, f.kids[i])
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
f.kids = kids
|
||||||
|
}
|
||||||
|
|
||||||
function state_global(name: pointer) -> pointer { return `state${name}` }
|
function state_global(name: pointer) -> pointer { return `state${name}` }
|
||||||
function is_state_ty(t: pointer) -> bool {
|
function is_state_ty(t: pointer) -> bool {
|
||||||
|
|
@ -104,6 +133,26 @@ function state_inject(e: Node, f: Node) -> void {
|
||||||
}
|
}
|
||||||
e.kids = args
|
e.kids = args
|
||||||
}
|
}
|
||||||
|
# a retained `ui` block is an entry point: `font: MenuState.title_font` reads the instance
|
||||||
|
function ui_state_refs(n: Node) -> void {
|
||||||
|
if n == null { return }
|
||||||
|
if n.kind == E_MEMBER and n.a != null and n.a.kind == E_ID and is_state_ty(n.a.s) { n.a.s = state_global(n.a.s) }
|
||||||
|
ui_state_refs(n.a)
|
||||||
|
ui_state_refs(n.b)
|
||||||
|
ui_state_refs(n.c)
|
||||||
|
var i = 0
|
||||||
|
while i < len(n.kids) {
|
||||||
|
ui_state_refs(n.kids[i])
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function ui_blocks_states() -> void {
|
||||||
|
var i = 0
|
||||||
|
while i < len(prog) {
|
||||||
|
if prog[i].kind == N_UI and prog[i].ival == 1 { ui_state_refs(prog[i].a) }
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
function entry_bind(body: Node, ps: []Node) -> void {
|
function entry_bind(body: Node, ps: []Node) -> void {
|
||||||
if len(ps) == 0 or body == null { return }
|
if len(ps) == 0 or body == null { return }
|
||||||
let lets = new []Node
|
let lets = new []Node
|
||||||
|
|
|
||||||
|
|
@ -19,13 +19,22 @@ var g_vw_fnames: [][]pointer = new [][]pointer
|
||||||
var g_vw_calls: [][]Node = new [][]Node # the queries and the events
|
var g_vw_calls: [][]Node = new [][]Node # the queries and the events
|
||||||
var g_vw_cnames: [][]pointer = new [][]pointer
|
var g_vw_cnames: [][]pointer = new [][]pointer
|
||||||
var g_vw_helpers: []pointer = new []pointer # conversion helpers already written, program-wide
|
var g_vw_helpers: []pointer = new []pointer # conversion helpers already written, program-wide
|
||||||
|
var g_vw_hdr: []int = new []int # 0.S: where state parameters go (after the name)
|
||||||
|
var g_vw_nps: []int = new []int # how many the view declares
|
||||||
|
var g_vw_ps: [][]Node = new [][]Node # and which
|
||||||
|
|
||||||
function vw_prefix(name: pointer) -> pointer { return `view_{reg_lower(name)}_` }
|
function vw_prefix(name: pointer) -> pointer { return `view_{reg_lower(name)}_` }
|
||||||
|
|
||||||
# view NAME { members }
|
# view NAME [(states)] { members } - 0.S: the states the view's getters, queries and events read,
|
||||||
|
# supplied by the runtime when a template asks
|
||||||
function parse_view() -> void {
|
function parse_view() -> void {
|
||||||
pi += 1
|
pi += 1
|
||||||
let name = eat_id()
|
let name = eat_id()
|
||||||
|
push(g_vw_hdr, toks[pi - 1].end)
|
||||||
|
let vps = parse_entry_params()
|
||||||
|
push(g_vw_nps, len(vps))
|
||||||
|
push(g_vw_ps, vps)
|
||||||
|
let vi = len(g_vw_name)
|
||||||
let px = vw_prefix(name)
|
let px = vw_prefix(name)
|
||||||
g_uses_value = true
|
g_uses_value = true
|
||||||
let fields = new []Node
|
let fields = new []Node
|
||||||
|
|
@ -41,6 +50,8 @@ function parse_view() -> void {
|
||||||
let ev = is_id("on")
|
let ev = is_id("on")
|
||||||
if ev { toks[pi].text = "function" }
|
if ev { toks[pi].text = "function" }
|
||||||
let f = parse_fn()
|
let f = parse_fn()
|
||||||
|
state_params_onto(f, vps)
|
||||||
|
f.vw = vi
|
||||||
push(cnames, f.s)
|
push(cnames, f.s)
|
||||||
if ev { f.s = px + "on_" + f.s } else { f.s = px + f.s }
|
if ev { f.s = px + "on_" + f.s } else { f.s = px + f.s }
|
||||||
push(calls, f)
|
push(calls, f)
|
||||||
|
|
@ -48,7 +59,10 @@ function parse_view() -> void {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
push(fnames, eat_id())
|
push(fnames, eat_id())
|
||||||
push(fields, vw_field(px, fnames[len(fnames) - 1]))
|
let getter = vw_field(px, fnames[len(fnames) - 1])
|
||||||
|
state_params_onto(getter, vps)
|
||||||
|
getter.vw = vi
|
||||||
|
push(fields, getter)
|
||||||
}
|
}
|
||||||
eat_op("}")
|
eat_op("}")
|
||||||
let head = vw_head(name, px)
|
let head = vw_head(name, px)
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,18 @@ function vw_infer(v: int, g: Node, fname: pointer) -> pointer {
|
||||||
let d = find_global(e.s)
|
let d = find_global(e.s)
|
||||||
if d != null and d.ty != null { return d.ty }
|
if d != null and d.ty != null { return d.ty }
|
||||||
}
|
}
|
||||||
|
# 0.S: a field of one of the view's states, `picked = s.picked`
|
||||||
|
if e.kind == E_MEMBER and e.a != null and e.a.kind == E_ID {
|
||||||
|
let ps = g_vw_ps[v]
|
||||||
|
var i = 0
|
||||||
|
while i < len(ps) {
|
||||||
|
if (ps[i].s == e.a.s) {
|
||||||
|
let r = find_comp(ps[i].ty)
|
||||||
|
if r != null and field_index(r, e.s) >= 0 { return field_type(r, e.s) }
|
||||||
|
}
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
if e.kind == E_INT { return "int" }
|
if e.kind == E_INT { return "int" }
|
||||||
if e.kind == E_FLOAT { return "float" }
|
if e.kind == E_FLOAT { return "float" }
|
||||||
if e.kind == E_BOOL { return "bool" }
|
if e.kind == E_BOOL { return "bool" }
|
||||||
|
|
@ -62,10 +74,10 @@ function vw_call_src(v: int, px: pointer, head: Node) -> pointer {
|
||||||
while i < len(calls) {
|
while i < len(calls) {
|
||||||
let f = calls[i]
|
let f = calls[i]
|
||||||
var args = ""
|
var args = ""
|
||||||
var p = 0
|
var p = g_vw_nps[v] # 0.S: the view's states are supplied, not passed
|
||||||
while p < len(f.kids) {
|
while p < len(f.kids) {
|
||||||
if p > 0 { args = args + ", " }
|
if p > g_vw_nps[v] { args = args + ", " }
|
||||||
args = args + vw_arg(v, f.kids[p], p)
|
args = args + vw_arg(v, f.kids[p], p - g_vw_nps[v])
|
||||||
p += 1
|
p += 1
|
||||||
}
|
}
|
||||||
let call = `{f.s}({args})`
|
let call = `{f.s}({args})`
|
||||||
|
|
|
||||||
86015
selfhost/ludicc.seed.ll
86015
selfhost/ludicc.seed.ll
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -79,6 +79,26 @@ function mgx_rtrim(s: pointer) -> pointer {
|
||||||
while n > 0 and (s[n - 1] == 10 or s[n - 1] == 13 or s[n - 1] == 32) { n -= 1 }
|
while n > 0 and (s[n - 1] == 10 or s[n - 1] == 13 or s[n - 1] == 32) { n -= 1 }
|
||||||
return sslice(s, 0, n)
|
return sslice(s, 0, n)
|
||||||
}
|
}
|
||||||
|
# the brace that closes the first block after `at` (strings skipped), or -1
|
||||||
|
function mgx_close(t: pointer, at: int) -> int {
|
||||||
|
var i = at
|
||||||
|
while t[i] != 0 and t[i] != '{' { i += 1 }
|
||||||
|
var depth = 0
|
||||||
|
var instr = false
|
||||||
|
while t[i] != 0 {
|
||||||
|
let c = t[i]
|
||||||
|
if instr {
|
||||||
|
if c == 92 { i += 1 } else if c == 34 { instr = false }
|
||||||
|
} else if c == 34 { instr = true }
|
||||||
|
else if c == '{' { depth += 1 }
|
||||||
|
else if c == '}' {
|
||||||
|
depth -= 1
|
||||||
|
if depth == 0 { return i }
|
||||||
|
}
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
function cmd_migrate() -> int {
|
function cmd_migrate() -> int {
|
||||||
if arg_count() < 3 or not (arg(2) == "state") {
|
if arg_count() < 3 or not (arg(2) == "state") {
|
||||||
err("usage: ludic migrate state [file|dir...] [--runtime] [--unsafe] [--dry-run]\n")
|
err("usage: ludic migrate state [file|dir...] [--runtime] [--unsafe] [--dry-run]\n")
|
||||||
|
|
@ -167,6 +187,7 @@ function mgx_apply(lines: []pointer, dry: bool) -> int {
|
||||||
let sfile = new []pointer
|
let sfile = new []pointer
|
||||||
let spos = new []int
|
let spos = new []int
|
||||||
let sexp = new []int
|
let sexp = new []int
|
||||||
|
let smore = new []int # 1: the state is already declared, and gains fields
|
||||||
let reports = new []pointer
|
let reports = new []pointer
|
||||||
for i in 0 .. len(lines) {
|
for i in 0 .. len(lines) {
|
||||||
let w = dp_words(lines[i])
|
let w = dp_words(lines[i])
|
||||||
|
|
@ -188,6 +209,7 @@ function mgx_apply(lines: []pointer, dry: bool) -> int {
|
||||||
push(sfile, w[2])
|
push(sfile, w[2])
|
||||||
push(spos, s_to_int(w[3]))
|
push(spos, s_to_int(w[3]))
|
||||||
push(sexp, s_to_int(w[4]))
|
push(sexp, s_to_int(w[4]))
|
||||||
|
if len(w) >= 7 { push(smore, 1) } else { push(smore, 0) }
|
||||||
} else if (w[0] == "REF") and len(w) == 5 {
|
} else if (w[0] == "REF") and len(w) == 5 {
|
||||||
mgx_edit(w[1], s_to_int(w[2]), s_to_int(w[3]), mgx_unesc(w[4]))
|
mgx_edit(w[1], s_to_int(w[2]), s_to_int(w[3]), mgx_unesc(w[4]))
|
||||||
} else if (w[0] == "INS") and len(w) == 4 {
|
} else if (w[0] == "INS") and len(w) == 4 {
|
||||||
|
|
@ -224,6 +246,19 @@ function mgx_apply(lines: []pointer, dry: bool) -> int {
|
||||||
var block = ""
|
var block = ""
|
||||||
var at = -1
|
var at = -1
|
||||||
var indent = ""
|
var indent = ""
|
||||||
|
if smore[s] == 1 {
|
||||||
|
# the fields go in before the declaration's closing brace
|
||||||
|
let t = mgx_text_of(sfile[s])
|
||||||
|
indent = mgx_indent(t, mgx_line_start(t, spos[s]))
|
||||||
|
var ff = ""
|
||||||
|
for ov in 0 .. len(order) {
|
||||||
|
let v = order[ov]
|
||||||
|
if vstate[v] == sname[s] { ff = ff + `{indent} {mgx_field(mgx_text_of(vfile[v]), vpos[v], vpos2[v])}\n` }
|
||||||
|
}
|
||||||
|
let close = mgx_close(t, spos[s])
|
||||||
|
if close >= 0 { mgx_edit(sfile[s], mgx_line_start(t, close), 0, ff) }
|
||||||
|
continue
|
||||||
|
}
|
||||||
for ov in 0 .. len(order) {
|
for ov in 0 .. len(order) {
|
||||||
let v = order[ov]
|
let v = order[ov]
|
||||||
if vstate[v] == sname[s] {
|
if vstate[v] == sname[s] {
|
||||||
|
|
|
||||||
|
|
@ -181,7 +181,7 @@ function cmd_test_pkg() -> int {
|
||||||
pt_commit_tag(cp, "v1.0.0")
|
pt_commit_tag(cp, "v1.0.0")
|
||||||
let proj4 = `{work}/proj4`
|
let proj4 = `{work}/proj4`
|
||||||
pt_write(`{proj4}/package.ludic`, `package "game"` + nl() + `version "0.0.0"` + nl() + `require "example.test/coachpack" "1.0.0"` + nl())
|
pt_write(`{proj4}/package.ludic`, `package "game"` + nl() + `version "0.0.0"` + nl() + `require "example.test/coachpack" "1.0.0"` + nl())
|
||||||
pt_write(`{proj4}/game.ludic`, "program Game {\n import \"example.test/coachpack/coach.ludic\"\n model Player { Score }\n @OnSpawn(Player) handler Init { }\n var frames: int = 0\n handler Boot phase Start { let e = world_spawn(world_model_id(\"Player\")) }\n handler Run phase Update {\n frames = frames + 1\n if frames >= 5 {\n let P = world_prop_id(\"Score\")\n let e = world_query_next(P, 0)\n print(world_get(e, P, 0))\n print(Coach.bonus())\n quit()\n }\n }\n}\n")
|
pt_write(`{proj4}/game.ludic`, "program Game {\n import \"example.test/coachpack/coach.ludic\"\n model Player { Score }\n @OnSpawn(Player) handler Init { }\n state Clock { frames: int = 0 }\n handler Boot phase Start { let e = world_spawn(world_model_id(\"Player\")) }\n handler Run(c: mut Clock) phase Update {\n c.frames = c.frames + 1\n if c.frames >= 5 {\n let P = world_prop_id(\"Score\")\n let e = world_query_next(P, 0)\n print(world_get(e, P, 0))\n print(Coach.bonus())\n quit()\n }\n }\n}\n")
|
||||||
if shq(`( cd {proj4} && {envp} {root}/bin/ludic get > {work}/get4.out 2>&1 )`) {
|
if shq(`( cd {proj4} && {envp} {root}/bin/ludic get > {work}/get4.out 2>&1 )`) {
|
||||||
shell(`LUDIC_HOME={root} LUDIC_MODULES={proj4}/ludic_modules {root}/bin/ludicc --headless {proj4}/game.ludic -o {proj4}/game 2>{work}/gc4.err`)
|
shell(`LUDIC_HOME={root} LUDIC_MODULES={proj4}/ludic_modules {root}/bin/ludicc --headless {proj4}/game.ludic -o {proj4}/game 2>{work}/gc4.err`)
|
||||||
if file_exists(`{proj4}/game`) {
|
if file_exists(`{proj4}/game`) {
|
||||||
|
|
@ -213,7 +213,7 @@ function cmd_test_pkg() -> int {
|
||||||
|
|
||||||
let proj3 = `{work}/proj3`
|
let proj3 = `{work}/proj3`
|
||||||
pt_write(`{proj3}/package.ludic`, `package "game"` + nl() + `version "0.0.0"` + nl() + `require "example.test/manapack" "1.0.0"` + nl())
|
pt_write(`{proj3}/package.ludic`, `package "game"` + nl() + `version "0.0.0"` + nl() + `require "example.test/manapack" "1.0.0"` + nl())
|
||||||
pt_write(`{proj3}/game.ludic`, "program Game {\n property Dummy { x: int = 0 }\n model Ent { Dummy }\n @OnSpawn(Ent) handler Init { }\n extern function mana_bonus() -> int = \"fn_mana_bonus\"\n var frames: int = 0\n handler Boot phase Start {\n let e = world_spawn(world_model_id(\"Ent\"))\n let M = world_prop_id(\"Mana\")\n world_attach_dyn(e, M)\n world_set(e, M, 0, 0)\n }\n handler Run phase Update {\n frames = frames + 1\n if frames >= 4 {\n let M = world_prop_id(\"Mana\")\n let e = world_query_next(M, 0)\n print(world_get(e, M, 0))\n print(mana_bonus())\n quit()\n }\n }\n}\n")
|
pt_write(`{proj3}/game.ludic`, "program Game {\n property Dummy { x: int = 0 }\n model Ent { Dummy }\n @OnSpawn(Ent) handler Init { }\n extern function mana_bonus() -> int = \"fn_mana_bonus\"\n state Clock { frames: int = 0 }\n handler Boot phase Start {\n let e = world_spawn(world_model_id(\"Ent\"))\n let M = world_prop_id(\"Mana\")\n world_attach_dyn(e, M)\n world_set(e, M, 0, 0)\n }\n handler Run(c: mut Clock) phase Update {\n c.frames = c.frames + 1\n if c.frames >= 4 {\n let M = world_prop_id(\"Mana\")\n let e = world_query_next(M, 0)\n print(world_get(e, M, 0))\n print(mana_bonus())\n quit()\n }\n }\n}\n")
|
||||||
if shq(`( cd {proj3} && {envp} {root}/bin/ludic get > {work}/get3.out 2>&1 )`) and file_exists(`{proj3}/ludic_modules/example.test/manapack/lib/{tgt}/libmanapack.dylib`) {
|
if shq(`( cd {proj3} && {envp} {root}/bin/ludic get > {work}/get3.out 2>&1 )`) and file_exists(`{proj3}/ludic_modules/example.test/manapack/lib/{tgt}/libmanapack.dylib`) {
|
||||||
ok("ludic get fetches + links a prebuilt (kind prebuilt) package for the target")
|
ok("ludic get fetches + links a prebuilt (kind prebuilt) package for the target")
|
||||||
} else { bad2("prebuilt ludic get failed", capture_line(`tail -1 {work}/get3.out`)) }
|
} else { bad2("prebuilt ludic get failed", capture_line(`tail -1 {work}/get3.out`)) }
|
||||||
|
|
|
||||||
|
|
@ -517,7 +517,7 @@ function migrate_case() -> void {
|
||||||
var src = "program Demo {\n var count: int = 0\n var names: []string = new []string # the names\n"
|
var src = "program Demo {\n var count: int = 0\n var names: []string = new []string # the names\n"
|
||||||
src = src + " function add(n: string) -> void {\n push(names, n)\n count += 1\n }\n"
|
src = src + " function add(n: string) -> void {\n push(names, n)\n count += 1\n }\n"
|
||||||
src = src + " function total() -> int {\n return count\n }\n"
|
src = src + " function total() -> int {\n return count\n }\n"
|
||||||
src = src + " function report() -> string {\n return `{{total()}} names, first {{names[0]}}`\n }\n"
|
src = src + " function report() -> string {\n return `{total()} names, first {names[0]}`\n }\n"
|
||||||
src = src + " entry {\n add(\"a\")\n add(\"b\")\n print(report())\n }\n}\n"
|
src = src + " entry {\n add(\"a\")\n add(\"b\")\n print(report())\n }\n}\n"
|
||||||
write_file(`{work}/demo.ludic`, src)
|
write_file(`{work}/demo.ludic`, src)
|
||||||
if not shq(`bin/ludic migrate state {work}/demo.ludic > {work}/out.txt 2>&1`) { bad2(lbl, capture_line(`tail -1 {work}/out.txt`)); return }
|
if not shq(`bin/ludic migrate state {work}/demo.ludic > {work}/out.txt 2>&1`) { bad2(lbl, capture_line(`tail -1 {work}/out.txt`)); return }
|
||||||
|
|
@ -545,9 +545,9 @@ function test_dir_case() -> void {
|
||||||
let work = `{tmp_dir()}/testdir`
|
let work = `{tmp_dir()}/testdir`
|
||||||
let root = capture_line("pwd")
|
let root = capture_line("pwd")
|
||||||
shell(`rm -rf {work} && mkdir -p {work}/pk/tests {work}/pk/src/deep`)
|
shell(`rm -rf {work} && mkdir -p {work}/pk/tests {work}/pk/src/deep`)
|
||||||
var a = "program State {\n var counter: int = 0\n var seen: []int = new []int\n"
|
var a = "program State {\n state Tally {\n counter: int = 0\n seen: []int = new []int\n }\n"
|
||||||
a = a + " test \"first\" {\n counter += 1\n push(seen, 1)\n expect_eq(counter, 1)\n expect_eq(len(seen), 1)\n }\n"
|
a = a + " test \"first\" (t: mut Tally) {\n t.counter += 1\n push(t.seen, 1)\n expect_eq(t.counter, 1)\n expect_eq(len(t.seen), 1)\n }\n"
|
||||||
a = a + " test \"second\" {\n counter += 1\n push(seen, 2)\n expect_eq(counter, 1)\n expect_eq(len(seen), 1)\n }\n}\n"
|
a = a + " test \"second\" (t: mut Tally) {\n t.counter += 1\n push(t.seen, 2)\n expect_eq(t.counter, 1)\n expect_eq(len(t.seen), 1)\n }\n}\n"
|
||||||
write_file(`{work}/pk/tests/state.ludic`, a)
|
write_file(`{work}/pk/tests/state.ludic`, a)
|
||||||
write_file(`{work}/pk/src/deep/sums_test.ludic`, "program Sums {\n test \"adds\" {\n expect_eq(2 + 2, 4)\n }\n test \"wrong\" {\n expect_eq(2 + 2, 5)\n }\n}\n")
|
write_file(`{work}/pk/src/deep/sums_test.ludic`, "program Sums {\n test \"adds\" {\n expect_eq(2 + 2, 4)\n }\n test \"wrong\" {\n expect_eq(2 + 2, 5)\n }\n}\n")
|
||||||
write_file(`{work}/pk/src/helper.ludic`, "program NotATest {\n entry { exit(3) }\n}\n")
|
write_file(`{work}/pk/src/helper.ludic`, "program NotATest {\n entry { exit(3) }\n}\n")
|
||||||
|
|
@ -889,7 +889,7 @@ function cmd_dev_test() -> int {
|
||||||
reject_case("rejected/port_var_args", "only a member that takes nothing can be bound to a variable", "a member with parameters binds a function, not a variable")
|
reject_case("rejected/port_var_args", "only a member that takes nothing can be bound to a variable", "a member with parameters binds a function, not a variable")
|
||||||
reject_case("rejected/port_new", "Clock is a port: it is filled once with 'bind Clock", "a port is bound, not made with new")
|
reject_case("rejected/port_new", "Clock is a port: it is filled once with 'bind Clock", "a port is bound, not made with new")
|
||||||
reject_case("rejected/port_private", "Clock is private to module clock", "whoever binds a port has to see it")
|
reject_case("rejected/port_private", "Clock is private to module clock", "whoever binds a port has to see it")
|
||||||
reject_case("rejected/uses_package", "mech uses ludic_inventory.inv_count", "a package's module counts under `uses` like any other")
|
reject_case("rejected/uses_package", "mech uses ludic_inventory.inv_limit", "a package's module counts under `uses` like any other")
|
||||||
reject_case("rejected/uses_render3d", "mech uses ludic_render3d.PI", "a package with no module line is still named under `uses`")
|
reject_case("rejected/uses_render3d", "mech uses ludic_render3d.PI", "a package with no module line is still named under `uses`")
|
||||||
feat_case("modules/private_twins", "", "105 41 7", "private_twins.ludic (L3: two modules' private seed, dice and LIMIT, and the program's seed, do not collide)")
|
feat_case("modules/private_twins", "", "105 41 7", "private_twins.ludic (L3: two modules' private seed, dice and LIMIT, and the program's seed, do not collide)")
|
||||||
feat_case("modules/private_kinds", "", "7 hare", "private_kinds.ludic (L3: two modules' private record Catch and event Landed do not collide)")
|
feat_case("modules/private_kinds", "", "7 hare", "private_kinds.ludic (L3: two modules' private record Catch and event Landed do not collide)")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue