ludic.base's Queue<T> carries a QueueTag (its name and pending count): queue_new(name), q_push(q, v), q_drain(q), q_clear(q) take no BaseState, and core_undrained(tags) names the given queues still holding facts. A reducer on a package's state can now call that package's verbs (wallet_earn(wallet_st, n)). ludic migrate state --prune (ludicc --migrate-prune) takes out each state parameter a function no longer uses, nor anything it calls, and the argument that fills it - including an argument for a parameter the callee has dropped, which is taken out before the call is checked, so a generic's T is told by the argument that says it. A reducer keeps its state. --dry-run now counts the edits it would make. Every package was moved with it: 608 base_st parameters and their arguments, 1889 edits. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1118 lines
39 KiB
Text
1118 lines
39 KiB
Text
# migrate.ludic — 0.S2: `ludicc --migrate-state <plan>` works out how a program's module-level
|
|
# vars become states and writes the edits as a plan (`ludic migrate state` applies it):
|
|
# 1. each module's vars are one state, `state <Module>State { ... }`, where the first of them was;
|
|
# 2. every reference to one - read or written - is `<module>_st.<var>`;
|
|
# 3. each function's states - those it touches and those of everything it calls, to a fixed
|
|
# point - become its leading parameters (`mut` where it or a callee writes);
|
|
# 4. each call passes them on, and each entry point (entry, a handler, a listener, a test)
|
|
# declares them for the runtime to supply.
|
|
# It runs inside the checker (check_*.ludic call the mg_* hooks), which already knows which name is a
|
|
# local and which a global, and which function a call reaches. Plan lines, one edit each:
|
|
# VAR <file> <pos> <pos2> <state> <at> a declaration to move into <state>, declared at <at>
|
|
# LET <file> <pos> a declaration nothing writes: its `var` becomes `let`
|
|
# STATE <state> <file> <pos> <export 0|1> <param> <key> [more] where <state> is declared (the
|
|
# first var this program sees, or its declaration when it has one already: `more`)
|
|
# REF <file> <pos> <len> <text> a reference's name replaced
|
|
# INS <file> <pos> <text> text inserted (a parameter, an argument)
|
|
# CLOSE <file> <pos> <text> text inserted before the first `)` after <pos>
|
|
# ? <file>:<line> <what> what the tool cannot decide, for a person
|
|
var g_migrate: bool = false
|
|
var g_mg_runtime: bool = false # --migrate-runtime: the runtime's own vars too
|
|
var g_mg_prune: bool = false # --migrate-prune: a declared state nothing uses is taken out
|
|
var g_ma_node: []Node = new []Node # each state argument a call passes (an E_ID), its call, its state
|
|
var g_ma_call: []int = new []int
|
|
var g_ma_state: []int = new []int
|
|
var g_ma_excess: []bool = new []bool # an argument for a parameter the callee no longer has: it goes
|
|
var g_mg_plan: pointer = null
|
|
var g_mg_vars: []Node = new []Node # the vars that move
|
|
var g_mg_vstate: []int = new []int # the state each moves into
|
|
var g_mg_written: []int = new []int # 1: something writes it
|
|
var g_mg_let: []int = new []int # 1: a value nothing writes - it stays, as a module-level let;
|
|
# 2: moved already (a package's, migrated before this program)
|
|
var g_mr_unit: []int = new []int # per reference: the unit it is read in (-1: none)
|
|
var g_ms_name: []pointer = new []pointer
|
|
var g_ms_mod: []pointer = new []pointer
|
|
var g_ms_param: []pointer = new []pointer
|
|
var g_ms_export: []int = new []int
|
|
# units: the functions and entry points whose states are worked out
|
|
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 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_mut: []pointer = new []pointer # the ones it changes
|
|
var g_mg_cur: int = -1
|
|
var g_mc_call: []Node = new []Node # every call to a function, and where it is
|
|
var g_mc_unit: []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_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
|
|
|
|
function mg_camel(s: pointer) -> pointer {
|
|
var out = ""
|
|
var up = true
|
|
var i = 0
|
|
while i < len(s) {
|
|
let c = s[i]
|
|
if c == '_' or c == '.' or c == '-' { up = true } else {
|
|
var ch = s[i .. i + 1]
|
|
if up and c >= 'a' and c <= 'z' { ch = reg_upper(ch) }
|
|
out = out + ch
|
|
up = false
|
|
}
|
|
i += 1
|
|
}
|
|
return out
|
|
}
|
|
function mg_param_for(name: pointer) -> pointer {
|
|
var base = name
|
|
let n = len(name)
|
|
if n > 5 and (name[n - 5 .. n] == "State") { base = name[0 .. n - 5] }
|
|
var out = ""
|
|
var i = 0
|
|
while i < len(base) {
|
|
let c = base[i]
|
|
if c >= 'A' and c <= 'Z' {
|
|
if i > 0 { out = out + "_" }
|
|
out = out + reg_lower(base[i .. i + 1])
|
|
} else { out = out + base[i .. i + 1] }
|
|
i += 1
|
|
}
|
|
return out + "_st"
|
|
}
|
|
function mg_short(m: pointer) -> pointer {
|
|
if len(m) > 6 and (m[0 .. 6] == "ludic_") { return m[6 .. len(m)] }
|
|
return m
|
|
}
|
|
var g_mg_entry: pointer = "" # the program's own file: its vars are the program's state
|
|
# the state a var's file moves into: its module's, or - a file in no module - the program's for the
|
|
# program's own file, else one named for the file's directory (a test's shared fakes)
|
|
function mg_state_of_file(f: pointer) -> int {
|
|
if is_runtime_file(f) { # the runtime: a state per file (rt_input: RtInputState)
|
|
var b = f
|
|
var i = 0
|
|
while i < len(f) {
|
|
if f[i] == '/' { b = f[i + 1 .. len(f)] }
|
|
i += 1
|
|
}
|
|
if len(b) > 6 and (b[len(b) - 6 .. len(b)] == ".ludic") { b = b[0 .. len(b) - 6] }
|
|
return mg_state_of_mod(`@{f}`, `rt_{b}`)
|
|
}
|
|
if (f == g_mg_entry) { # the program's own file: its own state, whatever module
|
|
let p = mg_param_for(g_game_name)
|
|
return mg_state_of_mod("", p[0 .. len(p) - 3])
|
|
}
|
|
var m = module_for_uses(f)
|
|
# a friend module (`friend module lab`) is a view of the others each program declares for itself,
|
|
# not one module: its files' vars go by their directory, as a file in no module does
|
|
var fi = 0
|
|
while fi < len(g_mod_friends) {
|
|
if (g_mod_friends[fi] == m) { m = "" }
|
|
fi += 1
|
|
}
|
|
if not (m == "") and not (module_of(f) == "") and pkg_of_file(f) == "" and mg_pkg_named(mg_short(m)) {
|
|
# the program's own module named like a package's (a game's `module fishing` beside
|
|
# ludic.fishing): a state of its own, never merged into the package's
|
|
return mg_state_of_mod(m, mg_short(m) + "_app")
|
|
}
|
|
if not (m == "") { return mg_state_of_mod(m, mg_short(m)) }
|
|
if (f == g_mg_entry) { # the program's own: SceneDemo is SceneDemoState, scene_demo_st
|
|
let p = mg_param_for(g_game_name)
|
|
return mg_state_of_mod("", p[0 .. len(p) - 3])
|
|
}
|
|
var d = dir_of(f)
|
|
while len(d) > 1 and d[len(d) - 1] == '/' { d = d[0 .. len(d) - 1] }
|
|
return mg_state_of_mod(`@{d}`, mg_dir_name(d))
|
|
}
|
|
# a name for a directory's state that two directories called `fake` do not share: the path under
|
|
# packages/ (ludic.wildlife/tests/fake: wildlife_tests_fake), else its last two parts
|
|
function mg_dir_name(d: pointer) -> pointer {
|
|
var at = -1
|
|
var i = 0
|
|
while i + 9 <= len(d) {
|
|
if (d[i .. i + 9] == "packages/") { at = i + 9 }
|
|
i += 1
|
|
}
|
|
var rest: pointer = ""
|
|
if at >= 0 { rest = d[at .. len(d)] } else {
|
|
var k = len(d)
|
|
var slashes = 0
|
|
while k > 0 and slashes < 2 {
|
|
k -= 1
|
|
if d[k] == '/' { slashes += 1 }
|
|
}
|
|
if d[k] == '/' { k += 1 }
|
|
rest = d[k .. len(d)]
|
|
}
|
|
if len(rest) > 6 and (rest[0 .. 6] == "ludic.") { rest = rest[6 .. len(rest)] }
|
|
var out = ""
|
|
i = 0
|
|
while i < len(rest) {
|
|
let c = rest[i]
|
|
if c == '/' or c == '.' or c == '-' { out = out + "_" } else { out = out + rest[i .. i + 1] }
|
|
i += 1
|
|
}
|
|
return out
|
|
}
|
|
function mg_state_of_mod(m: pointer, base: pointer) -> int {
|
|
var i = 0
|
|
while i < len(g_ms_mod) {
|
|
if (g_ms_mod[i] == m) { return i }
|
|
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_name, name)
|
|
push(g_ms_param, `{base}_st`)
|
|
push(g_ms_export, 0)
|
|
return len(g_ms_mod) - 1
|
|
}
|
|
# where a state is already declared, for its new fields: the declaration, else null
|
|
function mg_state_key(s: int) -> pointer {
|
|
let m = g_ms_mod[s]
|
|
if m == "" { return `program:{g_mg_entry}` }
|
|
if m[0] == '@' or m[0] == '=' { return m }
|
|
# a module: by the file that first says it (its barrel), else a package's module by its name
|
|
var i = 0
|
|
while i < len(g_mod_file) {
|
|
if (g_mod_name[i] == m) { return `module:{m}@{dir_of(g_mod_file[i])}` }
|
|
i += 1
|
|
}
|
|
return `package:{m}`
|
|
}
|
|
# is there a package called ludic.<short> - installed with the toolchain, or fetched into ludic_modules
|
|
function mg_pkg_named(short: pointer) -> bool {
|
|
if read_file(`{ludic_home()}packages/ludic.{short}/package.ludic`) != null { return true }
|
|
let mods = ensure_slash(getenv_or("LUDIC_MODULES", "ludic_modules"))
|
|
return read_file(`{mods}ludic.{short}/package.ludic`) != 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 {
|
|
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 not g_mg_runtime and has_sub(v.file, "runtime/") { return false }
|
|
return true
|
|
}
|
|
# which vars move, and the state each moves into
|
|
# a name the program uses that is a field of a state now - a package migrated before it moved its
|
|
# var there (`cam_pos` is Render3dState's): a reference to that state's field, which the program is
|
|
# rewritten to reach through the state. Only a name exactly one state has.
|
|
var g_mg_fv_name: []pointer = new []pointer
|
|
var g_mg_fv_node: []Node = new []Node
|
|
function mg_field_var(name: pointer) -> Node {
|
|
if not g_migrate { return null }
|
|
var i = 0
|
|
while i < len(g_mg_fv_name) {
|
|
if (g_mg_fv_name[i] == name) { return g_mg_fv_node[i] }
|
|
i += 1
|
|
}
|
|
var found: Node = null
|
|
var many = false
|
|
i = 0
|
|
while i < len(prog) {
|
|
let d = prog[i]
|
|
if d.kind == N_COMP and d.uns == 2 and field_index(d, name) >= 0 {
|
|
if found != null { many = true }
|
|
found = d
|
|
}
|
|
i += 1
|
|
}
|
|
var v: Node = null
|
|
if found != null and not many {
|
|
v = node(N_VAR)
|
|
v.s = name
|
|
v.ty = field_type(found, name)
|
|
v.file = found.file
|
|
v.line = found.line
|
|
v.pos = -1
|
|
push(g_mg_vars, v)
|
|
push(g_mg_vstate, mg_state_named(found.s))
|
|
push(g_mg_written, 0)
|
|
push(g_mg_let, 2)
|
|
v.mg = len(g_mg_vars) - 1
|
|
}
|
|
push(g_mg_fv_name, name)
|
|
push(g_mg_fv_node, v)
|
|
return v
|
|
}
|
|
function mg_collect() -> void {
|
|
var end = g_prog_user_end
|
|
if g_mg_runtime { end = len(prog) } # the runtime's declarations come after the program's
|
|
var i = 0
|
|
while i < end and i < len(prog) {
|
|
let d = prog[i]
|
|
if d.kind == N_VAR and mg_is_target(d) {
|
|
let k = mg_state_of_file(d.file)
|
|
push(g_mg_vars, d)
|
|
push(g_mg_vstate, k)
|
|
push(g_mg_written, 0)
|
|
push(g_mg_let, 0)
|
|
d.mg = len(g_mg_vars) - 1
|
|
if d.vis == 1 { g_ms_export[k] = 1 }
|
|
}
|
|
i += 1
|
|
}
|
|
}
|
|
function mg_unit(n: Node, kind: int) -> int {
|
|
if n.mg >= 0 and n.kind != N_VAR { return n.mg }
|
|
push(g_mu_node, n)
|
|
push(g_mu_kind, kind)
|
|
push(g_mu_need, ",")
|
|
push(g_mu_mut, ",")
|
|
n.mg = len(g_mu_node) - 1
|
|
# an already-migrated function: its declared states are needs - unless pruning, where only what
|
|
# it uses (and what what it calls uses) is
|
|
if kind == 0 and not g_mg_prune {
|
|
var i = 0
|
|
while i < len(n.kids) and n.kids[i].kind == N_PARAM and is_state_ty(n.kids[i].ty) {
|
|
let s = mg_state_named(n.kids[i].ty)
|
|
if s >= 0 {
|
|
mg_need(n.mg, s, n.kids[i].uns == 1)
|
|
}
|
|
i += 1
|
|
}
|
|
}
|
|
return n.mg
|
|
}
|
|
function mg_state_named(name: pointer) -> int {
|
|
var i = 0
|
|
while i < len(g_ms_name) {
|
|
if (g_ms_name[i] == name) { return i }
|
|
i += 1
|
|
}
|
|
# a state from a run before (or by hand): known by its name, its param `<snake>_st` as the tool
|
|
# names them (BaseState: base_st)
|
|
push(g_ms_mod, `={name}`)
|
|
push(g_ms_name, name)
|
|
push(g_ms_param, mg_param_for(name))
|
|
push(g_ms_export, 0)
|
|
return len(g_ms_mod) - 1
|
|
}
|
|
function mg_need(u: int, s: int, m: bool) -> bool {
|
|
var changed = false
|
|
let key = `,{itoa(s)},`
|
|
if not has_sub(g_mu_need[u], key) {
|
|
g_mu_need[u] = g_mu_need[u] + itoa(s) + ","
|
|
changed = true
|
|
}
|
|
if m and not has_sub(g_mu_mut[u], key) {
|
|
g_mu_mut[u] = g_mu_mut[u] + itoa(s) + ","
|
|
changed = true
|
|
}
|
|
return changed
|
|
}
|
|
# ---- the checker's hooks --------------------------------------------------------
|
|
function mg_enter(n: Node, kind: int) -> void {
|
|
if not g_migrate { return }
|
|
g_mg_cur = mg_unit(n, kind)
|
|
}
|
|
function mg_leave() -> void { g_mg_cur = -1 }
|
|
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 }
|
|
push(g_mr_node, e)
|
|
push(g_mr_var, g.mg)
|
|
push(g_mr_unit, g_mg_cur)
|
|
if g_mg_cur == -3 { push(g_mr_ui, 1) } else { push(g_mr_ui, 0) }
|
|
}
|
|
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 }
|
|
g_mg_written[g.mg] = 1
|
|
if is_runtime_file(g.file) and t != null and not is_runtime_file(t.file) {
|
|
mg_say(t, `{mg_src_name(g.s)} is the runtime's; change it through the runtime's function for it`)
|
|
return
|
|
}
|
|
if g_mg_cur >= 0 { mg_need(g_mg_cur, g_mg_vstate[g.mg], true) }
|
|
}
|
|
# a var nothing writes, holding a value (not a reference something could change through), and
|
|
# that no generated code or port reads: it is a module-level let
|
|
function mg_decide_lets() -> void {
|
|
var i = 0
|
|
while i < len(g_mg_vars) {
|
|
let v = g_mg_vars[i]
|
|
if g_mg_let[i] == 0 and g_mg_written[i] == 0 and mg_value_ty(v.ty) and not mg_bound_var(v.s) and not mg_knob(v) { g_mg_let[i] = 1 }
|
|
i += 1
|
|
}
|
|
var r = 0
|
|
while r < len(g_mr_node) {
|
|
if g_mr_node[r].pos < 0 and g_mg_let[g_mr_var[r]] == 1 { g_mg_let[g_mr_var[r]] = 0 }
|
|
r += 1
|
|
}
|
|
}
|
|
# a package's var a program may set (render3d's r3d_dem_path): nothing in these programs writes it,
|
|
# but a game using the package may - it stays changing data. A private one of a package's module is
|
|
# the package's own business.
|
|
function mg_knob(v: Node) -> bool {
|
|
let f = mg_norm(v.file)
|
|
let pkg = not (pkg_of_file(v.file) == "") or s_has_prefix(f, "packages/") or has_sub(f, "/packages/") or has_sub(f, "ludic_modules/")
|
|
if not pkg { return false }
|
|
if module_of(v.file) == "" { return true }
|
|
return v.vis == 1
|
|
}
|
|
function s_has_prefix(s: pointer, p: pointer) -> bool { return len(s) >= len(p) and (s[0 .. len(p)] == p) }
|
|
# the runtime's function that only answers its var `name` (`function gl_width() -> int { return gl_w }`)
|
|
function mg_runtime_reader(name: pointer) -> pointer {
|
|
var i = 0
|
|
while i < len(prog) {
|
|
let f = prog[i]
|
|
if f.kind == N_FN and is_runtime_file(f.file) and f.a != null and len(f.a.kids) == 1 and f.a.kids[0].kind == S_RETURN {
|
|
var np = 0
|
|
var j = 0
|
|
while j < len(f.kids) {
|
|
if f.kids[j].kind == N_PARAM and not is_state_ty(f.kids[j].ty) { np += 1 }
|
|
j += 1
|
|
}
|
|
let x = f.a.kids[0].a
|
|
if np == 0 and x != null and x.kind == E_MEMBER and x.a != null and x.a.kind == E_ID and (x.s == name) { return f.s }
|
|
if np == 0 and x != null and x.kind == E_ID and (mg_src_name(x.s) == name) { return f.s }
|
|
}
|
|
i += 1
|
|
}
|
|
return null
|
|
}
|
|
function mg_value_ty(t: pointer) -> bool {
|
|
if t == null { return false }
|
|
if (t == "int") or (t == "float") or (t == "fixed") or (t == "long") or (t == "bool") or (t == "string") or (t == "byte") { return true }
|
|
return find_enum(t) != null
|
|
}
|
|
# what each unit reads, now that the lets are known
|
|
function mg_read_needs() -> void {
|
|
var r = 0
|
|
while r < len(g_mr_node) {
|
|
let v = g_mr_var[r]
|
|
let e = g_mr_node[r]
|
|
let u = g_mr_unit[r]
|
|
let g = g_mg_vars[v]
|
|
if is_runtime_file(g.file) and not is_runtime_file(e.file) {
|
|
# the runtime's state is the runtime's: from outside it is read through its functions - the
|
|
# one that answers it, when there is one (gl_w: gl_width())
|
|
let rd = mg_runtime_reader(mg_src_name(g.s))
|
|
if rd != null and e.pos >= 0 and e.file != null {
|
|
mg_emit(`REF {e.file} {itoa(e.pos)} {itoa(len(mg_src_name(g.s)))} {mg_esc(`{rd}()`)}`, "")
|
|
} else { mg_say(e, `{mg_src_name(g.s)} is the runtime's; read it through the runtime's function for it`) }
|
|
g_mr_var[r] = -1 - v
|
|
} else if not (g_mg_let[v] == 1) and u != -3 {
|
|
if u >= 0 { mg_need(u, g_mg_vstate[v], false) }
|
|
else { mg_say(e, `{g.s} is read where no function is (a global's initializer); move it by hand`) }
|
|
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)`) }
|
|
}
|
|
r += 1
|
|
}
|
|
}
|
|
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 {
|
|
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
|
|
if is_runtime_file(f.file) and not is_runtime_file(e.file) { return } # the runtime's are supplied
|
|
if is_action_builtin(f.s) { return } # 0.R: the action queue is supplied too
|
|
push(g_mc_call, e)
|
|
push(g_mc_unit, g_mg_cur)
|
|
push(g_mc_callee, mg_unit(f, 0))
|
|
mg_state_args(e, f, len(g_mc_call) - 1)
|
|
var passed = 0
|
|
if len(e.kids) > 0 and e.kids[0].kind == E_ID {
|
|
let li = ck_local(e.kids[0].s)
|
|
if li >= 0 and is_state_ty(ck_tys[li]) { passed = 1 }
|
|
}
|
|
push(g_mc_passed, passed)
|
|
}
|
|
# the states a call hands on as plain names: a passing is not a use (the callee's use, through the
|
|
# fixed point, is), and with --migrate-prune one the callee does not take (any more) goes
|
|
function mg_state_args(e: Node, f: Node, c: int) -> void {
|
|
var np = 0
|
|
var k = 0
|
|
while k < len(f.kids) {
|
|
if f.kids[k].kind == N_PARAM { np += 1 }
|
|
k += 1
|
|
}
|
|
var excess = len(e.kids) - np # arguments for parameters the callee no longer has
|
|
let keep = new []Node
|
|
var i = 0
|
|
while i < len(e.kids) {
|
|
let a = e.kids[i]
|
|
var gone = false
|
|
if a.kind == E_ID {
|
|
let li = ck_local(a.s)
|
|
if li >= 0 and is_state_ty(ck_tys[li]) {
|
|
var passed = mg_declared_ty(f, ck_tys[li])
|
|
if not passed and excess > 0 {
|
|
gone = true
|
|
excess -= 1
|
|
}
|
|
if passed or gone {
|
|
a.mg = -2 # marked: ck_id does not count it as a use
|
|
push(g_ma_node, a)
|
|
push(g_ma_call, c)
|
|
push(g_ma_state, mg_state_named(ck_tys[li]))
|
|
push(g_ma_excess, gone)
|
|
}
|
|
}
|
|
}
|
|
if not (gone and g_mg_prune) { push(keep, a) }
|
|
i += 1
|
|
}
|
|
# with --migrate-prune the call is checked as it will read: without the arguments that go, so the
|
|
# rest meet their parameters (and a generic's T is told by the argument that says it)
|
|
if g_mg_prune and len(keep) < len(e.kids) { e.kids = keep }
|
|
}
|
|
function mg_declared_ty(f: Node, ty: pointer) -> bool {
|
|
var i = 0
|
|
while i < len(f.kids) {
|
|
if f.kids[i].kind == N_PARAM and (f.kids[i].ty == ty) { return true }
|
|
i += 1
|
|
}
|
|
return false
|
|
}
|
|
# with --migrate-prune, a state local used in place (read, written, handed to anything but a
|
|
# function taking it) is a need of the unit it is used in
|
|
function mg_local_use(e: Node, ty: pointer) -> void {
|
|
if not g_migrate or not g_mg_prune or g_mg_cur < 0 or e.mg == -2 or e.pos < 0 { return }
|
|
if not is_state_ty(ty) { return }
|
|
mg_need(g_mg_cur, mg_state_named(ty), false)
|
|
}
|
|
function mg_local_write(ty: pointer) -> void {
|
|
if not g_migrate or not g_mg_prune or g_mg_cur < 0 or ty == null { return }
|
|
if not is_state_ty(ty) { return }
|
|
mg_need(g_mg_cur, mg_state_named(ty), true)
|
|
}
|
|
function mg_say(n: Node, what: pointer) -> void {
|
|
var at = "?"
|
|
if n != null and n.file != null { at = `{n.file}:{itoa(n.line)}` }
|
|
push(g_mg_report, `? {at} {what}`)
|
|
}
|
|
# ---- the fixed point, and the plan ---------------------------------------------------
|
|
function mg_fixpoint() -> void {
|
|
var changed = true
|
|
while changed {
|
|
changed = false
|
|
var c = 0
|
|
while c < len(g_mc_call) {
|
|
let from = g_mc_unit[c]
|
|
let to = g_mc_callee[c]
|
|
let need = mg_list(g_mu_need[to])
|
|
var k = 0
|
|
while k < len(need) {
|
|
let s = need[k]
|
|
if mg_need(from, s, has_sub(g_mu_mut[to], `,{itoa(s)},`)) { changed = true }
|
|
k += 1
|
|
}
|
|
c += 1
|
|
}
|
|
}
|
|
}
|
|
function mg_list(set: pointer) -> []int {
|
|
let out = new []int
|
|
var v = 0
|
|
var any = false
|
|
var i = 1
|
|
while i < len(set) {
|
|
let ch = set[i]
|
|
if ch == ',' {
|
|
if any { push(out, v) }
|
|
v = 0
|
|
any = false
|
|
} else {
|
|
v = v * 10 + (ch - 48)
|
|
any = true
|
|
}
|
|
i += 1
|
|
}
|
|
# in the order of the states' names, so every signature and every call agree
|
|
var a = 1
|
|
while a < len(out) {
|
|
let x = out[a]
|
|
var b = a - 1
|
|
while b >= 0 and reg_str_less(g_ms_name[x], g_ms_name[out[b]]) {
|
|
out[b + 1] = out[b]
|
|
b -= 1
|
|
}
|
|
out[b + 1] = x
|
|
a += 1
|
|
}
|
|
return out
|
|
}
|
|
# a function's states that it declares already (a migration run before this one)
|
|
function mg_declared(n: Node, s: int) -> bool {
|
|
if n.kind != N_FN { return false }
|
|
var i = 0
|
|
while i < len(n.kids) and n.kids[i].kind == N_PARAM and is_state_ty(n.kids[i].ty) {
|
|
if (n.kids[i].ty == g_ms_name[s]) { return true }
|
|
i += 1
|
|
}
|
|
return false
|
|
}
|
|
function mg_param_text(u: int, s: int) -> pointer {
|
|
if has_sub(g_mu_mut[u], `,{itoa(s)},`) { return `{g_ms_param[s]}: mut {g_ms_name[s]}` }
|
|
return `{g_ms_param[s]}: {g_ms_name[s]}`
|
|
}
|
|
var g_mg_lines: []pointer = new []pointer
|
|
var g_mg_keys: pointer = ","
|
|
# the same edit twice (a generic's instances share its source) is dropped by ludic migrate
|
|
function mg_emit(line: pointer, key: pointer) -> void { push(g_mg_lines, mg_norm_line(line)) }
|
|
# one spelling per file in every plan, whichever way a program reached it (`../../packages/x`, an
|
|
# absolute path under the working directory): its paths and its state keys, normalized
|
|
function mg_norm_line(line: pointer) -> pointer {
|
|
let w = new []pointer
|
|
var a = 0
|
|
var i = 0
|
|
while i <= len(line) {
|
|
if i == len(line) or line[i] == ' ' {
|
|
push(w, line[a .. i])
|
|
a = i + 1
|
|
}
|
|
i += 1
|
|
}
|
|
if len(w) < 2 { return line }
|
|
let k = w[0]
|
|
if (k == "VAR") or (k == "REF") or (k == "INS") or (k == "LET") or (k == "CLOSE") or (k == "DELITEM") { w[1] = mg_norm(w[1]) }
|
|
if (k == "?") { w[1] = mg_norm(w[1]) }
|
|
if (k == "VAR") and len(w) > 5 { w[5] = mg_norm_key(w[5]) }
|
|
if (k == "STATE") and len(w) > 6 {
|
|
w[2] = mg_norm(w[2])
|
|
w[6] = mg_norm_key(w[6])
|
|
}
|
|
var out = w[0]
|
|
i = 1
|
|
while i < len(w) {
|
|
out = out + " " + w[i]
|
|
i += 1
|
|
}
|
|
return out
|
|
}
|
|
# a state's key: `@dir`, `module:m@dir`, `program:file`, `package:m` - a directory that is a package's
|
|
# own (`packages/ludic.render3d`) is that package
|
|
function mg_norm_key(k: pointer) -> pointer {
|
|
if k[0] == '@' {
|
|
var d = mg_norm(k[1 .. len(k)])
|
|
while len(d) > 1 and d[len(d) - 1] == '/' { d = d[0 .. len(d) - 1] }
|
|
var slash = -1
|
|
var i = 0
|
|
while i < len(d) {
|
|
if d[i] == '/' { slash = i }
|
|
i += 1
|
|
}
|
|
var parent = ""
|
|
if slash >= 0 { parent = d[0 .. slash] }
|
|
let pn = len(parent)
|
|
if (parent == "packages") or (pn > 9 and (parent[pn - 9 .. pn] == "/packages")) {
|
|
var name = ""
|
|
var j = slash + 1
|
|
while j < len(d) {
|
|
if d[j] == '.' { name = name + "_" } else { name = name + d[j .. j + 1] }
|
|
j += 1
|
|
}
|
|
return `package:{name}`
|
|
}
|
|
return `@{d}`
|
|
}
|
|
var at = -1
|
|
var i = 0
|
|
while i < len(k) and at < 0 {
|
|
if k[i] == '@' or k[i] == ':' { at = i }
|
|
i += 1
|
|
}
|
|
if len(k) > 8 and (k[0 .. 8] == "package:") { return k }
|
|
if at >= 0 and k[at] == '@' { return k[0 .. at + 1] + mg_norm(k[at + 1 .. len(k)]) }
|
|
if at >= 0 and k[0 .. at] == "program" { return "program:" + mg_norm(k[at + 1 .. len(k)]) }
|
|
if at >= 0 {
|
|
# module:m@dir
|
|
var j = at + 1
|
|
while j < len(k) and k[j] != '@' { j += 1 }
|
|
if j < len(k) { return k[0 .. j + 1] + mg_norm(k[j + 1 .. len(k)]) }
|
|
}
|
|
return k
|
|
}
|
|
# `a/b/../c` is `a/c`, `./x` is `x`, and a path under the working directory is relative to it
|
|
function mg_norm(p0: pointer) -> pointer {
|
|
var p = p0
|
|
let cwd = getenv("PWD")
|
|
if cwd != null {
|
|
let c = `{cwd}/`
|
|
if len(p) > len(c) and (p[0 .. len(c)] == c) { p = p[len(c) .. len(p)] }
|
|
}
|
|
let abs = len(p) > 0 and p[0] == '/'
|
|
let segs = new []pointer
|
|
var n = 0
|
|
var a = 0
|
|
var i = 0
|
|
while i <= len(p) {
|
|
if i == len(p) or p[i] == '/' {
|
|
let seg = p[a .. i]
|
|
if (seg == "..") and n > 0 and not (segs[n - 1] == "..") { n -= 1 }
|
|
else if not (seg == ".") and not (seg == "") {
|
|
if n < len(segs) { segs[n] = seg } else { push(segs, seg) }
|
|
n += 1
|
|
}
|
|
a = i + 1
|
|
}
|
|
i += 1
|
|
}
|
|
var out = ""
|
|
if abs { out = "/" }
|
|
i = 0
|
|
while i < n {
|
|
if i > 0 { out = out + "/" }
|
|
out = out + segs[i]
|
|
i += 1
|
|
}
|
|
if len(p) > 0 and p[len(p) - 1] == '/' { out = out + "/" }
|
|
return out
|
|
}
|
|
# text for a plan line: spaces and newlines escaped
|
|
function mg_esc(s: pointer) -> pointer {
|
|
var out = ""
|
|
var i = 0
|
|
while i < len(s) {
|
|
let c = s[i]
|
|
if c == ' ' { out = out + "\\s" } else if c == '\n' { out = out + "\\n" } else if c == '\\' { out = out + "\\\\" } else { out = out + s[i .. i + 1] }
|
|
i += 1
|
|
}
|
|
return out
|
|
}
|
|
function mg_first_positional_is_state(e: Node) -> bool {
|
|
if len(e.kids) == 0 or e.kids[0].kind != E_ID { return false }
|
|
var i = 0
|
|
while i < len(g_ms_param) {
|
|
if (g_ms_param[i] == e.kids[0].s) { return true }
|
|
i += 1
|
|
}
|
|
return false
|
|
}
|
|
function mg_nparams(n: Node) -> int {
|
|
var k = 0
|
|
var i = 0
|
|
while i < len(n.kids) {
|
|
if n.kids[i].kind == N_PARAM { k += 1 }
|
|
i += 1
|
|
}
|
|
return k
|
|
}
|
|
# a state declared read-only (a run before) that the unit, or something it calls, now changes
|
|
function mg_mut_upgrades(u: int, n: Node) -> void {
|
|
var ps = n.kids
|
|
if g_mu_kind[u] != 0 {
|
|
ps = new []Node
|
|
var body = n.a
|
|
if g_mu_kind[u] == 5 { body = n }
|
|
var j = 0
|
|
while body != null and j < len(body.kids) and body.kids[j].tps != null and (body.kids[j].tps == "state") {
|
|
push(ps, body.kids[j])
|
|
j += 1
|
|
}
|
|
}
|
|
var i = 0
|
|
while i < len(ps) {
|
|
let p = ps[i]
|
|
if (p.kind == N_PARAM or p.kind == S_LET) and is_state_ty(p.ty) and p.uns == 0 and p.pos2 >= 0 and p.file != null {
|
|
let s = mg_state_named(p.ty)
|
|
if has_sub(g_mu_mut[u], `,{itoa(s)},`) { mg_emit(`INS {p.file} {itoa(p.pos2)} {mg_esc("mut ")}`, "") }
|
|
}
|
|
i += 1
|
|
}
|
|
}
|
|
function mg_units() -> void {
|
|
var u = 0
|
|
while u < len(g_mu_node) {
|
|
let n = g_mu_node[u]
|
|
if g_mu_kind[u] == 0 and n.cm >= 0 {
|
|
mg_comp_acc(u, n.cm) # a component's member: the component declares them
|
|
u += 1
|
|
continue
|
|
}
|
|
mg_mut_upgrades(u, n)
|
|
if g_mg_prune { mg_prune_unit(u, n) }
|
|
let need = mg_list(g_mu_need[u])
|
|
if len(need) > 0 {
|
|
var text = ""
|
|
var k = 0
|
|
while k < len(need) {
|
|
if not mg_declared(n, need[k]) {
|
|
if len(text) > 0 { text = text + ", " }
|
|
text = text + mg_param_text(u, need[k])
|
|
}
|
|
k += 1
|
|
}
|
|
if len(text) > 0 { mg_unit_edit(u, n, text) }
|
|
}
|
|
u += 1
|
|
}
|
|
}
|
|
# --migrate-prune: each state a unit declares and does not need (nor anything it calls) goes
|
|
function mg_prune_unit(u: int, n: Node) -> void {
|
|
if n.file == null or (is_runtime_file(n.file) and not g_mg_runtime) { return }
|
|
let kind = g_mu_kind[u]
|
|
if kind == 0 {
|
|
if n.vw >= 0 { return }
|
|
if s_has_prefix(n.s, "ludic_reduce__") { return } # a reducer takes its state whether it reads it or not
|
|
var i = 0
|
|
while i < len(n.kids) {
|
|
let p = n.kids[i]
|
|
if p.kind == N_PARAM and is_state_ty(p.ty) and p.pos >= 0 and p.file != null {
|
|
if not has_sub(g_mu_need[u], `,{itoa(mg_state_named(p.ty))},`) { mg_emit(`DELITEM {p.file} {itoa(p.pos)}`, "") }
|
|
}
|
|
i += 1
|
|
}
|
|
return
|
|
}
|
|
var body = n.a
|
|
if kind == 5 { body = n }
|
|
if body == null { return }
|
|
var j = 0
|
|
while j < len(body.kids) and body.kids[j].tps != null and (body.kids[j].tps == "state") {
|
|
let l = body.kids[j]
|
|
if l.pos >= 0 and l.file != null and not has_sub(g_mu_need[u], `,{itoa(mg_state_named(l.ty))},`) {
|
|
mg_emit(`DELITEM {l.file} {itoa(l.pos)}`, "")
|
|
}
|
|
j += 1
|
|
}
|
|
}
|
|
# --migrate-prune: a state a call hands on that its callee does not take, or no longer needs
|
|
function mg_prune_args() -> void {
|
|
var a = 0
|
|
while a < len(g_ma_node) {
|
|
let x = g_ma_node[a]
|
|
let c = g_ma_call[a]
|
|
let to = g_mc_callee[c]
|
|
let callee = g_mu_node[to]
|
|
let unused = callee.cm < 0 and not has_sub(g_mu_need[to], `,{itoa(g_ma_state[a])},`)
|
|
if x.pos >= 0 and x.file != null and (g_ma_excess[a] or unused) {
|
|
mg_emit(`DELITEM {x.file} {itoa(x.pos)}`, "")
|
|
}
|
|
a += 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 {
|
|
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 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 {
|
|
mg_say(n, `{n.s} needs {text} and is generated code: its caller cannot be rewritten`)
|
|
return
|
|
}
|
|
var t = text
|
|
if mg_nparams(n) > 0 { t = t + ", " }
|
|
mg_emit(`INS {n.file} {itoa(n.pos2 + 1)} {mg_esc(t)}`, `{n.file}@{itoa(n.pos2)}`)
|
|
return
|
|
}
|
|
# an entry point: a list of the states the runtime supplies
|
|
var 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") {
|
|
# it declares some already: whatever it needs beyond them is for a person to add
|
|
# it declares some already: the rest go after them
|
|
let need = mg_list(g_mu_need[u])
|
|
var more = ""
|
|
var last: Node = null
|
|
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 }
|
|
last = body.kids[j]
|
|
j += 1
|
|
}
|
|
if not have { more = more + ", " + mg_param_text(u, need[k]) }
|
|
k += 1
|
|
}
|
|
if len(more) > 0 {
|
|
if last == null or last.pos2 < 0 or last.file == null { mg_say(n, `this entry point declares states already; add{more} to them by hand`) }
|
|
else { mg_emit(`CLOSE {last.file} {itoa(last.pos2)} {mg_esc(more)}`, "") }
|
|
}
|
|
return
|
|
}
|
|
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 == 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 {
|
|
let b = n.a
|
|
if b == null or b.pos2 < 0 {
|
|
mg_say(n, `a listener that needs ({text}) whose place is not known`)
|
|
return
|
|
}
|
|
mg_emit(`INS {b.file} {itoa(b.pos2)} {mg_esc(`({text})`)}`, `{b.file}@{itoa(b.pos2)}`)
|
|
}
|
|
}
|
|
var g_mcm_need: []pointer = new []pointer # per component: the states its members need
|
|
var g_mcm_mut: []pointer = new []pointer
|
|
function mg_comp_acc(u: int, c: int) -> void {
|
|
while len(g_mcm_need) < len(g_cm_name) {
|
|
push(g_mcm_need, ",")
|
|
push(g_mcm_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_mcm_need[c], key) { g_mcm_need[c] = g_mcm_need[c] + itoa(need[k]) + "," }
|
|
if has_sub(g_mu_mut[u], key) and not has_sub(g_mcm_mut[c], key) { g_mcm_mut[c] = g_mcm_mut[c] + itoa(need[k]) + "," }
|
|
k += 1
|
|
}
|
|
}
|
|
# each component's header: the states its members need that it does not declare yet, and `mut` on
|
|
# one it declares read-only that a member now changes
|
|
function mg_comps() -> void {
|
|
while len(g_mcm_need) < len(g_cm_name) {
|
|
push(g_mcm_need, ",")
|
|
push(g_mcm_mut, ",")
|
|
}
|
|
var c = 0
|
|
while c < len(g_mcm_need) {
|
|
if g_mg_prune { # a header state no member needs
|
|
var q = 0
|
|
while q < len(g_cm_ps[c]) {
|
|
let p = g_cm_ps[c][q]
|
|
if p.pos >= 0 and not has_sub(g_mcm_need[c], `,{itoa(mg_state_named(p.ty))},`) { mg_emit(`DELITEM {g_cm_file[c]} {itoa(p.pos)}`, "") }
|
|
q += 1
|
|
}
|
|
}
|
|
let need = mg_list(g_mcm_need[c])
|
|
let ps = g_cm_ps[c]
|
|
var text = ""
|
|
var k = 0
|
|
while k < len(need) {
|
|
let s = need[k]
|
|
let m = has_sub(g_mcm_mut[c], `,{itoa(s)},`)
|
|
var have: Node = null
|
|
var j = 0
|
|
while j < len(ps) {
|
|
if (ps[j].ty == g_ms_name[s]) { have = ps[j] }
|
|
j += 1
|
|
}
|
|
if have == null {
|
|
if len(text) > 0 { text = text + ", " }
|
|
if m { text = text + `{g_ms_param[s]}: mut {g_ms_name[s]}` } else { text = text + `{g_ms_param[s]}: {g_ms_name[s]}` }
|
|
} else if m and have.uns == 0 and have.pos2 >= 0 {
|
|
mg_emit(`INS {g_cm_file[c]} {itoa(have.pos2)} {mg_esc("mut ")}`, "")
|
|
}
|
|
k += 1
|
|
}
|
|
if len(text) > 0 {
|
|
if len(ps) == 0 { mg_emit(`INS {g_cm_file[c]} {itoa(g_cm_hdr[c])} {mg_esc(` ({text})`)}`, "") } else {
|
|
# after the last one it declares: its type's end is not kept, so before the `)` that follows
|
|
let last = ps[len(ps) - 1]
|
|
mg_emit(`CLOSE {g_cm_file[c]} {itoa(last.pos2)} {mg_esc(`, {text}`)}`, "")
|
|
}
|
|
}
|
|
c += 1
|
|
}
|
|
}
|
|
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 {
|
|
var c = 0
|
|
while c < len(g_mc_call) {
|
|
let e = g_mc_call[c]
|
|
let to = g_mc_callee[c]
|
|
let callee = g_mu_node[to]
|
|
let need = mg_list(g_mu_need[to])
|
|
if len(need) > 0 and callee.cm < 0 { # a component's member is passed its states by the compiler
|
|
var text = ""
|
|
let passed = g_mc_passed[c] == 1
|
|
var k = 0
|
|
while k < len(need) {
|
|
if not (mg_declared(callee, need[k]) and passed) {
|
|
if len(text) > 0 { text = text + ", " }
|
|
text = text + g_ms_param[need[k]]
|
|
}
|
|
k += 1
|
|
}
|
|
if len(text) > 0 and e.pos >= 0 { # a generated call gets its states when compiled
|
|
if e.file == null { mg_say(e, `a call to {callee.s} with no place needs ({text})`) } else {
|
|
var t = text
|
|
if len(e.kids) > 0 { t = t + ", " }
|
|
mg_emit(`INS {e.file} {itoa(e.pos + 1)} {mg_esc(t)}`, `{e.file}@{itoa(e.pos)}`)
|
|
}
|
|
}
|
|
}
|
|
c += 1
|
|
}
|
|
}
|
|
function mg_refs() -> void {
|
|
var r = 0
|
|
while r < len(g_mr_node) {
|
|
let e = g_mr_node[r]
|
|
if g_mr_var[r] < 0 { # the runtime's, read from outside it: by hand
|
|
r += 1
|
|
continue
|
|
}
|
|
let v = g_mg_vars[g_mr_var[r]]
|
|
if e.pos >= 0 and e.file != null and not (g_mg_let[g_mr_var[r]] == 1) {
|
|
let s = g_mg_vstate[g_mr_var[r]]
|
|
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
|
|
}
|
|
}
|
|
# a state needed outside its module is exported: whoever needs it declares it
|
|
function mg_exports() -> void {
|
|
var u = 0
|
|
while u < len(g_mu_node) {
|
|
let n = g_mu_node[u]
|
|
if n.file != null {
|
|
var here = module_for_uses(n.file)
|
|
if (here == "") and not (n.file == g_mg_entry) { here = `@{dir_of(n.file)}` }
|
|
let need = mg_list(g_mu_need[u])
|
|
var k = 0
|
|
while k < len(need) {
|
|
if not (g_ms_mod[need[k]] == here) { g_ms_export[need[k]] = 1 }
|
|
k += 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 {
|
|
mg_decide_lets()
|
|
mg_read_needs()
|
|
mg_fixpoint()
|
|
mg_exports()
|
|
# each state is known by what it belongs to - a module where its barrel is, a directory, a program -
|
|
# the same in every program's plan (two programs may each have a module fishing, and see different
|
|
# files of one)
|
|
let anchor = new []pointer
|
|
var s = 0
|
|
while s < len(g_ms_name) {
|
|
push(anchor, mg_state_key(s))
|
|
s += 1
|
|
}
|
|
var i = 0
|
|
while i < len(g_mg_vars) {
|
|
let v = g_mg_vars[i]
|
|
let vs = g_mg_vstate[i]
|
|
if g_mg_let[i] == 1 { mg_emit(`LET {v.file} {itoa(v.pos)}`, "") }
|
|
else if g_mg_let[i] == 0 { mg_emit(`VAR {v.file} {itoa(v.pos)} {itoa(v.pos2)} {g_ms_name[vs]} {anchor[vs]}`, "") }
|
|
i += 1
|
|
}
|
|
s = 0
|
|
while s < len(g_ms_name) {
|
|
# the first var of each new state is where the state is declared
|
|
var first = -1
|
|
var j = 0
|
|
while j < len(g_mg_vars) and first < 0 {
|
|
if g_mg_vstate[j] == s and g_mg_let[j] == 0 { first = j }
|
|
j += 1
|
|
}
|
|
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]} {anchor[s]} more`, "")
|
|
} else if first >= 0 {
|
|
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]} {anchor[s]}`, "")
|
|
}
|
|
s += 1
|
|
}
|
|
mg_refs()
|
|
mg_binds()
|
|
mg_units()
|
|
if g_mg_prune { mg_prune_args() }
|
|
mg_comps()
|
|
mg_views()
|
|
mg_calls()
|
|
let f = file_open(g_mg_plan, "wb")
|
|
if f == null { die("ludicc: cannot write the migration plan\n") }
|
|
i = 0
|
|
while i < len(g_mg_lines) {
|
|
file_write(f, g_mg_lines[i], len(g_mg_lines[i]))
|
|
file_write(f, "\n", 1)
|
|
i += 1
|
|
}
|
|
i = 0
|
|
while i < len(g_mg_report) {
|
|
file_write(f, g_mg_report[i], len(g_mg_report[i]))
|
|
file_write(f, "\n", 1)
|
|
i += 1
|
|
}
|
|
file_close(f)
|
|
exit(0)
|
|
}
|