wip(0.S1, 0.S2): state records, mut and read-only state parameters, entry injection, module var refused; ludic migrate state
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
63a1fa1378
commit
1e8b5b0523
35 changed files with 1227 additions and 30 deletions
|
|
@ -31,6 +31,7 @@ program Ludic {
|
|||
import "scripts.ludic"
|
||||
import "deps.ludic"
|
||||
import "testpar.ludic"
|
||||
import "migrate.ludic"
|
||||
|
||||
function usage() -> void {
|
||||
print("ludic — the toolchain for the Ludic language")
|
||||
|
|
@ -47,6 +48,7 @@ program Ludic {
|
|||
print(" compile and run the project's tests (-v: every test's line)")
|
||||
print(" deps [file] [--graph|--dot|--writes|--uses MOD|--check F|--baseline F]")
|
||||
print(" the module graph as the compiler sees it, and how tangled it is")
|
||||
print(" migrate state [file] [--runtime] module-level vars into states, passed as parameters (0.S)")
|
||||
print(" clean remove build/")
|
||||
print("")
|
||||
print("packages:")
|
||||
|
|
@ -99,6 +101,7 @@ program Ludic {
|
|||
if (cmd == "run") { return cmd_run() }
|
||||
if (cmd == "test") { return cmd_test() }
|
||||
if (cmd == "deps") { return cmd_deps() }
|
||||
if (cmd == "migrate") { return cmd_migrate() }
|
||||
if (cmd == "clean") { return cmd_clean() }
|
||||
if (cmd == "fmt") { return cmd_fmt() }
|
||||
if (cmd == "lsp") { return cmd_lsp() }
|
||||
|
|
|
|||
202
tools/ludic-cli/migrate.ludic
Normal file
202
tools/ludic-cli/migrate.ludic
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
# ---- ludic migrate state -------------------------------------------------------
|
||||
# 0.S2: module-level vars become states. The compiler works out the edits (selfhost/frontend/
|
||||
# migrate.ludic, `ludicc --migrate-state <plan>`); this applies them to the source, file by file,
|
||||
# and prints what it could not decide.
|
||||
#
|
||||
# ludic migrate state [file] [--runtime] [--unsafe] [--dry-run]
|
||||
#
|
||||
# Run it once per program that uses what changes (a package's test programs, the game's entry,
|
||||
# its lab): a later run finds the states an earlier one made and passes them on.
|
||||
var mgx_file: []pointer = null # an edit: its file, where, how much it replaces, with what
|
||||
var mgx_pos: []int = null
|
||||
var mgx_len: []int = null
|
||||
var mgx_text: []pointer = null
|
||||
var mgx_texts: []pointer = null # the files' texts, read once
|
||||
var mgx_names: []pointer = null
|
||||
|
||||
function mgx_unesc(s: pointer) -> pointer {
|
||||
var out = ""
|
||||
var i = 0
|
||||
let n = slen(s)
|
||||
while i < n {
|
||||
if s[i] == 92 and i + 1 < n {
|
||||
let c = s[i + 1]
|
||||
if c == 's' { out = out + " " } else if c == 'n' { out = out + "\n" } else { out = out + sslice(s, i + 1, i + 2) }
|
||||
i += 2
|
||||
} else {
|
||||
out = out + sslice(s, i, i + 1)
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
function mgx_text_of(path: pointer) -> pointer {
|
||||
for i in 0 .. len(mgx_names) { if mgx_names[i] == path { return mgx_texts[i] } }
|
||||
let t = read_file(path)
|
||||
push(mgx_names, path)
|
||||
push(mgx_texts, t)
|
||||
return t
|
||||
}
|
||||
function mgx_edit(file: pointer, pos: int, n: int, text: pointer) -> void {
|
||||
push(mgx_file, file)
|
||||
push(mgx_pos, pos)
|
||||
push(mgx_len, n)
|
||||
push(mgx_text, text)
|
||||
}
|
||||
function mgx_line_start(t: pointer, pos: int) -> int {
|
||||
var i = pos
|
||||
while i > 0 and t[i - 1] != 10 { i -= 1 }
|
||||
return i
|
||||
}
|
||||
function mgx_line_end(t: pointer, pos: int) -> int {
|
||||
var i = pos
|
||||
while t[i] != 0 and t[i] != 10 { i += 1 }
|
||||
if t[i] == 10 { i += 1 }
|
||||
return i
|
||||
}
|
||||
function mgx_indent(t: pointer, start: int) -> pointer {
|
||||
var i = start
|
||||
while t[i] == 32 or t[i] == 9 { i += 1 }
|
||||
return sslice(t, start, i)
|
||||
}
|
||||
function mgx_rtrim(s: pointer) -> pointer {
|
||||
var n = slen(s)
|
||||
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)
|
||||
}
|
||||
function cmd_migrate() -> int {
|
||||
if arg_count() < 3 or not (arg(2) == "state") {
|
||||
err("usage: ludic migrate state [file] [--runtime] [--unsafe] [--dry-run]\n")
|
||||
return 2
|
||||
}
|
||||
var src = ""
|
||||
var runtime = ""
|
||||
var dry = false
|
||||
var ai = 3
|
||||
while ai < arg_count() {
|
||||
let a = arg(ai)
|
||||
if a == "--runtime" { runtime = " --migrate-runtime" }
|
||||
else if a == "--unsafe" { g_unsafe_build = true }
|
||||
else if a == "--dry-run" { dry = true }
|
||||
else if a[0] == '-' { err(`ludic migrate: unknown option {a}\n`); return 2 }
|
||||
else { src = a }
|
||||
ai += 1
|
||||
}
|
||||
let entry = find_entry(src)
|
||||
if entry == "" { return no_entry() }
|
||||
ensure_ludicc()
|
||||
let plan = tmp_path("migrate.plan")
|
||||
shell(`rm -f {plan}`)
|
||||
shq(`{ludicc()} --headless{unsafe_flag()}{runtime} --migrate-state {plan} {entry} > {tmp_path("migrate.err")} 2>&1`)
|
||||
let text = read_file(plan)
|
||||
if text == null {
|
||||
err(`ludic migrate: the compiler wrote no plan for {entry}:\n`)
|
||||
err(capture(`grep -i error {tmp_path("migrate.err")} | head -5`))
|
||||
return 1
|
||||
}
|
||||
return mgx_apply(split_lines(text), dry)
|
||||
}
|
||||
function mgx_apply(lines: []pointer, dry: bool) -> int {
|
||||
mgx_file = new []pointer; mgx_pos = new []int; mgx_len = new []int; mgx_text = new []pointer
|
||||
mgx_texts = new []pointer; mgx_names = new []pointer
|
||||
let vfile = new []pointer
|
||||
let vpos = new []int
|
||||
let vpos2 = new []int
|
||||
let vstate = new []pointer
|
||||
let sname = new []pointer
|
||||
let sfile = new []pointer
|
||||
let spos = new []int
|
||||
let sexp = new []int
|
||||
let reports = new []pointer
|
||||
for i in 0 .. len(lines) {
|
||||
let w = dp_words(lines[i])
|
||||
if len(w) == 0 { continue }
|
||||
if w[0] == "VAR" and len(w) == 5 {
|
||||
push(vfile, w[1])
|
||||
push(vpos, s_to_int(w[2]))
|
||||
push(vpos2, s_to_int(w[3]))
|
||||
push(vstate, w[4])
|
||||
} else if w[0] == "STATE" and len(w) >= 6 {
|
||||
push(sname, w[1])
|
||||
push(sfile, w[2])
|
||||
push(spos, s_to_int(w[3]))
|
||||
push(sexp, s_to_int(w[4]))
|
||||
} 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]))
|
||||
} else if (w[0] == "INS") and len(w) == 4 {
|
||||
mgx_edit(w[1], s_to_int(w[2]), 0, mgx_unesc(w[3]))
|
||||
} else if w[0] == "?" { push(reports, lines[i]) }
|
||||
}
|
||||
# the vars: each line goes, and the first of each state's becomes the state
|
||||
let del_file = new []pointer
|
||||
let del_a = new []int
|
||||
let del_b = new []int
|
||||
for v in 0 .. len(vfile) {
|
||||
let t = mgx_text_of(vfile[v])
|
||||
let a = mgx_line_start(t, vpos[v])
|
||||
let b = mgx_line_end(t, vpos2[v])
|
||||
push(del_file, vfile[v])
|
||||
push(del_a, a)
|
||||
push(del_b, b)
|
||||
}
|
||||
for s in 0 .. len(sname) {
|
||||
var block = ""
|
||||
var at = -1
|
||||
var indent = ""
|
||||
for v in 0 .. len(vfile) {
|
||||
if vstate[v] == sname[s] {
|
||||
let t = mgx_text_of(vfile[v])
|
||||
if vfile[v] == sfile[s] and vpos[v] == spos[s] {
|
||||
at = v
|
||||
indent = mgx_indent(t, del_a[v])
|
||||
}
|
||||
block = block + `{indent} {mgx_rtrim(sslice(t, vpos[v], del_b[v]))}\n`
|
||||
}
|
||||
}
|
||||
var head = ""
|
||||
if sexp[s] == 1 { head = "export " }
|
||||
if at >= 0 { mgx_edit(vfile[at], del_a[at], 0, `{indent}{head}state {sname[s]} {{\n{block}{indent}}}\n`) }
|
||||
}
|
||||
for d in 0 .. len(del_file) { mgx_edit(del_file[d], del_a[d], del_b[d] - del_a[d], "") }
|
||||
# an edit inside a line that goes is dropped with it
|
||||
var files = 0
|
||||
var edits = 0
|
||||
for f in 0 .. len(mgx_names) {
|
||||
let path = mgx_names[f]
|
||||
var t = mgx_texts[f]
|
||||
let idx = new []int
|
||||
for e in 0 .. len(mgx_file) {
|
||||
if mgx_file[e] == path {
|
||||
var inside = false
|
||||
for d in 0 .. len(del_file) {
|
||||
if del_file[d] == path and mgx_len[e] < del_b[d] - del_a[d] and mgx_pos[e] > del_a[d] and mgx_pos[e] < del_b[d] { inside = true }
|
||||
}
|
||||
if not inside { push(idx, e) }
|
||||
}
|
||||
}
|
||||
# last first, so every earlier place stays where it was; at one place, the deletion last
|
||||
var a = 1
|
||||
while a < len(idx) {
|
||||
let x = idx[a]
|
||||
var b = a - 1
|
||||
while b >= 0 and (mgx_pos[idx[b]] < mgx_pos[x] or (mgx_pos[idx[b]] == mgx_pos[x] and mgx_len[idx[b]] < mgx_len[x])) {
|
||||
idx[b + 1] = idx[b]
|
||||
b -= 1
|
||||
}
|
||||
idx[b + 1] = x
|
||||
a += 1
|
||||
}
|
||||
for k in 0 .. len(idx) {
|
||||
let e = idx[k]
|
||||
t = sslice(t, 0, mgx_pos[e]) + mgx_text[e] + sslice(t, mgx_pos[e] + mgx_len[e], slen(t))
|
||||
edits += 1
|
||||
}
|
||||
if len(idx) > 0 {
|
||||
files += 1
|
||||
if not dry { write_file(path, t) }
|
||||
}
|
||||
}
|
||||
print(`migrate: {string(len(vfile))} vars into {string(len(sname))} states; {string(edits)} edits in {string(files)} files`)
|
||||
for r in 0 .. len(reports) { print(` by hand: {sslice(reports[r], 2, slen(reports[r]))}`) }
|
||||
return 0
|
||||
}
|
||||
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
# the built-in commands a hook can wrap (every user command that does work)
|
||||
function is_hookable(cmd: pointer) -> bool {
|
||||
return (cmd == "build") or (cmd == "run") or (cmd == "test") or (cmd == "deps") or (cmd == "bundle") or (cmd == "pack") or (cmd == "clean") or (cmd == "fmt") or (cmd == "get") or (cmd == "add") or (cmd == "update") or (cmd == "verify") or (cmd == "vendor") or (cmd == "assets") or (cmd == "build-lib")
|
||||
return (cmd == "build") or (cmd == "run") or (cmd == "test") or (cmd == "deps") or (cmd == "migrate") or (cmd == "bundle") or (cmd == "pack") or (cmd == "clean") or (cmd == "fmt") or (cmd == "get") or (cmd == "add") or (cmd == "update") or (cmd == "verify") or (cmd == "vendor") or (cmd == "assets") or (cmd == "build-lib")
|
||||
}
|
||||
|
||||
# the directory this `ludic` binary lives in, with no trailing slash
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@ function selfhost_frags() -> []pointer {
|
|||
push(f, "selfhost/frontend/ports.ludic")
|
||||
push(f, "selfhost/frontend/privates.ludic")
|
||||
push(f, "selfhost/frontend/privates_types.ludic")
|
||||
push(f, "selfhost/frontend/state.ludic")
|
||||
push(f, "selfhost/frontend/migrate.ludic")
|
||||
push(f, "selfhost/frontend/registry.ludic")
|
||||
push(f, "selfhost/frontend/registry_finish.ludic")
|
||||
push(f, "selfhost/frontend/registry_open.ludic")
|
||||
|
|
@ -75,6 +77,7 @@ function selfhost_frags() -> []pointer {
|
|||
push(f, "selfhost/backend/emit_call.ludic")
|
||||
push(f, "selfhost/backend/emit_fnval.ludic")
|
||||
push(f, "selfhost/backend/emit_vis.ludic")
|
||||
push(f, "selfhost/check/check_state.ludic")
|
||||
push(f, "selfhost/check/check_index.ludic")
|
||||
push(f, "selfhost/check/check_types.ludic")
|
||||
push(f, "selfhost/check/check_env.ludic")
|
||||
|
|
@ -135,7 +138,7 @@ function cmd_selfhost_build(lc: pointer, outbin: pointer) -> int {
|
|||
if not write_selfhost_src(src) { err("ludic-dev: cannot write build/selfhost.ludic\n"); return 1 }
|
||||
let ll = `{outbin}.ll`
|
||||
# say why when it fails: it used to exit 1 with nothing on the screen
|
||||
if not shq(`{lc} --unsafe {src} > {ll} 2>{tmp_dir()}/shb.err`) {
|
||||
if not shq(`{lc} --unsafe --globals {src} > {ll} 2>{tmp_dir()}/shb.err`) {
|
||||
shell(`rm -f {ll}`)
|
||||
err(capture(`grep -i error {tmp_dir()}/shb.err | head -5`))
|
||||
return 1
|
||||
|
|
@ -162,7 +165,7 @@ function cmd_sh_compile(shbin: pointer, in: pointer, outbin: pointer) -> int {
|
|||
# the quiet core, reused by the test suites; returns true on success. On failure
|
||||
# the self-host/link diagnostics are left in tmp_path("gb.err").
|
||||
# an example that is a raw-memory program on purpose (a decoder's ABI, a Vulkan demo) is built
|
||||
# with --unsafe (L7); the suite sets this around those cases only
|
||||
# with --unsafe --globals (L7); the suite sets this around those cases only
|
||||
var g_case_unsafe: bool = false
|
||||
function case_unsafe_flag() -> pointer {
|
||||
if g_case_unsafe { return " --unsafe" }
|
||||
|
|
@ -194,11 +197,11 @@ function cmd_bootstrap() -> int {
|
|||
if cmd_selfhost_build("bin/ludicc", "build/boot/gen1") != 0 { print("FAIL: stage0 build"); return 1 }
|
||||
print(" stage0: bin/ludicc -> gen1 (self-host compiler)")
|
||||
|
||||
if not shq("build/boot/gen1 --unsafe build/selfhost.ludic > build/boot/gen2.ll 2>/dev/null") { print("FAIL: gen1 self-compile"); return 1 }
|
||||
if not shq("build/boot/gen1 --unsafe --globals build/selfhost.ludic > build/boot/gen2.ll 2>/dev/null") { print("FAIL: gen1 self-compile"); return 1 }
|
||||
if not shq(`{cc()} build/boot/gen2.ll -o build/boot/gen2 2>/dev/null`) { print("FAIL: gen2 assemble"); return 1 }
|
||||
print(` stage1: gen1 -> gen2.ll ({line_count("build/boot/gen2.ll")} lines) -> gen2`)
|
||||
|
||||
if not shq("build/boot/gen2 --unsafe build/selfhost.ludic > build/boot/gen3.ll 2>/dev/null") { print("FAIL: gen2 self-compile"); return 1 }
|
||||
if not shq("build/boot/gen2 --unsafe --globals build/selfhost.ludic > build/boot/gen3.ll 2>/dev/null") { print("FAIL: gen2 self-compile"); return 1 }
|
||||
print(` stage2: gen2 -> gen3.ll ({line_count("build/boot/gen3.ll")} lines)`)
|
||||
|
||||
if shq("cmp -s build/boot/gen2.ll build/boot/gen3.ll") {
|
||||
|
|
@ -220,7 +223,7 @@ function cmd_bootstrap_cfree() -> int {
|
|||
if not shq(`{cc()} selfhost/ludicc.seed.ll -o build/cfree/sh_seed 2>/dev/null`) { print("FAIL: assemble seed"); return 1 }
|
||||
print(" seed.ll --clang--> sh_seed (no C compiler used)")
|
||||
if not write_selfhost_src("build/cfree/selfhost.ludic") { print("FAIL: write source"); return 1 }
|
||||
if not shq("build/cfree/sh_seed --unsafe build/cfree/selfhost.ludic > build/cfree/out.ll 2>/dev/null") { print("FAIL: seed compiler self-compile"); return 1 }
|
||||
if not shq("build/cfree/sh_seed --unsafe --globals build/cfree/selfhost.ludic > build/cfree/out.ll 2>/dev/null") { print("FAIL: seed compiler self-compile"); return 1 }
|
||||
print(` sh_seed compiles selfhost.ludic -> out.ll ({line_count("build/cfree/out.ll")} lines)`)
|
||||
if shq("cmp -s build/cfree/out.ll selfhost/ludicc.seed.ll") {
|
||||
print(" out.ll == seed.ll — the compiler rebuilds itself with no C compiler")
|
||||
|
|
@ -240,17 +243,17 @@ function cmd_reseed() -> int {
|
|||
shell("mkdir -p build/cfree")
|
||||
if not write_selfhost_src("build/cfree/selfhost.ludic") { print("FAIL: write source"); return 1 }
|
||||
if shq(`{cc()} selfhost/ludicc.seed.ll -o build/cfree/sh_old 2>/dev/null`) {
|
||||
if not shq("build/cfree/sh_old --unsafe build/cfree/selfhost.ludic > build/cfree/step1.ll") { print("FAIL: step1"); return 1 }
|
||||
if not shq("build/cfree/sh_old --unsafe --globals build/cfree/selfhost.ludic > build/cfree/step1.ll") { print("FAIL: step1"); return 1 }
|
||||
if not shq(`{cc()} build/cfree/step1.ll -o build/cfree/sh_new`) { print("FAIL: assemble sh_new"); return 1 }
|
||||
if not shq("build/cfree/sh_new --unsafe build/cfree/selfhost.ludic > selfhost/ludicc.seed.ll") { print("FAIL: reseed"); return 1 }
|
||||
if not shq("build/cfree/sh_new --unsafe --globals build/cfree/selfhost.ludic > selfhost/ludicc.seed.ll") { print("FAIL: reseed"); return 1 }
|
||||
# and the Windows seed, from the same compiler: a Windows checkout bootstraps from it
|
||||
if not shq("build/cfree/sh_new --unsafe --target x86_64-pc-windows-msvc build/cfree/selfhost.ludic > selfhost/ludicc.win.seed.ll") { print("FAIL: reseed (windows)"); return 1 }
|
||||
if not shq("build/cfree/sh_new --unsafe --globals --target x86_64-pc-windows-msvc build/cfree/selfhost.ludic > selfhost/ludicc.win.seed.ll") { print("FAIL: reseed (windows)"); return 1 }
|
||||
} else {
|
||||
print("seed does not build; reseeding from bin/ludicc")
|
||||
ensure_ludicc()
|
||||
if cmd_selfhost_build("bin/ludicc", "build/cfree/sh_c") != 0 { print("FAIL: build from bin/ludicc"); return 1 }
|
||||
if not shq("build/cfree/sh_c --unsafe build/cfree/selfhost.ludic > selfhost/ludicc.seed.ll") { print("FAIL: reseed"); return 1 }
|
||||
if not shq("build/cfree/sh_c --unsafe --target x86_64-pc-windows-msvc build/cfree/selfhost.ludic > selfhost/ludicc.win.seed.ll") { print("FAIL: reseed (windows)"); return 1 }
|
||||
if not shq("build/cfree/sh_c --unsafe --globals build/cfree/selfhost.ludic > selfhost/ludicc.seed.ll") { print("FAIL: reseed"); return 1 }
|
||||
if not shq("build/cfree/sh_c --unsafe --globals --target x86_64-pc-windows-msvc build/cfree/selfhost.ludic > selfhost/ludicc.win.seed.ll") { print("FAIL: reseed (windows)"); return 1 }
|
||||
}
|
||||
print(`reseeded: {line_count("selfhost/ludicc.seed.ll")} lines`)
|
||||
return 0
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ function sh_case(name: pointer, exp: pointer) -> void {
|
|||
let ll = `{tmp_dir()}/sh_{name}.ll`
|
||||
let er = `{tmp_dir()}/sh_{name}.err`
|
||||
let bn = `{tmp_dir()}/sh_{name}`
|
||||
if not shq(`bin/ludicc --unsafe selfhost/tests/{name}.ludic > {ll} 2>{er}`) {
|
||||
if not shq(`bin/ludicc --unsafe --globals selfhost/tests/{name}.ludic > {ll} 2>{er}`) {
|
||||
bad2(`{name}: self-host errored`, capture_line(`head -1 {er}`)); return
|
||||
}
|
||||
if not shq(`{cc()} {ll} -o {bn} 2>/dev/null`) { bad(`{name}: IR did not assemble`); return }
|
||||
|
|
|
|||
|
|
@ -508,6 +508,29 @@ function check_build_case() -> void {
|
|||
ok(lbl)
|
||||
}
|
||||
|
||||
# ludic migrate state: a program with module-level vars comes out with a state, threaded, and runs
|
||||
# the same
|
||||
function migrate_case() -> void {
|
||||
let lbl = "ludic migrate state: vars into a state, parameters threaded, the same output"
|
||||
let work = `{tmp_dir()}/migrate`
|
||||
shell(`rm -rf {work} && mkdir -p {work}`)
|
||||
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 total() -> int {\n return count\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"
|
||||
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 }
|
||||
let got = read_file(`{work}/demo.ludic`)
|
||||
if not s_contains(got, "state DemoState {") or not s_contains(got, "function add(demo_st: mut DemoState, n: string)") or not s_contains(got, "function total(demo_st: DemoState)") or not s_contains(got, "entry (demo_st: mut DemoState)") {
|
||||
bad2(lbl, `rewrote it as [{got}]`)
|
||||
return
|
||||
}
|
||||
if not shq(`bin/ludicc {work}/demo.ludic -o {work}/demo > {work}/cc.txt 2>&1`) { bad2(lbl, capture_line(`grep -i error {work}/cc.txt | head -1`)); return }
|
||||
let ran = capture_line(`{work}/demo`)
|
||||
if ran == "2 names, first a" { ok(lbl) } else { bad2(lbl, `ran [{ran}]`) }
|
||||
}
|
||||
|
||||
# every package's own tests, the way a package author runs them: `ludic test packages`
|
||||
function packages_test_case() -> void {
|
||||
let lbl = "ludic test packages: every package's tests pass"
|
||||
|
|
@ -877,6 +900,15 @@ function cmd_dev_test() -> int {
|
|||
feat_case("modules/layers", "", "5", "layers.ludic (L3: `module menu in layer app uses items` - one layer's modules use each other and may go round)")
|
||||
reject_case("rejected/layer_reach", "hud uses items.item_count", "a layered module is still held to its uses outside the layer")
|
||||
reject_case("rejected/layer_cycle", "go round in a circle: items -> layer app -> items", "a cycle through a layer and out of it is refused")
|
||||
feat_case("state/counter", "", "11 3 heard 5", "counter.ludic (0.S: a state, mut and read-only parameters, entry, listener and fn-value injection)")
|
||||
feat_case("state/port_field", "", "40", "port_field.ludic (0.S: a port member bound to a state's field)")
|
||||
spec_case("state/tested", "== 2 passed, 0 failed ==")
|
||||
reject_case("rejected/state_readonly", "c is read-only here (c: Tally)", "a read-only state cannot be assigned through")
|
||||
reject_case("rejected/state_mut_arg", "bump changes Tally (c: mut Tally), and c is read-only here", "a read-only state is not passed as mut")
|
||||
reject_case("rejected/module_var", "a module-level var is refused", "a module-level var is refused")
|
||||
reject_case("rejected/module_let_write", "LIMITS is module-level and immutable all the way down", "a module-level let is immutable all the way down")
|
||||
reject_case("rejected/mut_not_state", "mut is for a state parameter", "mut is for a state parameter")
|
||||
migrate_case()
|
||||
vis_report_case("rejected/uses_missing", "uses: examples/rejected/uses_valley/items/index.ludic:3: items.inv_add used from", "LUDIC_VIS_REPORT=1 lists a uses violation and builds")
|
||||
feat_case("lang/checked", "", "7.5 3 12 hi 2 ok", "checked.ludic (L4: literals take their slot's kind, string(p), []string as []pointer, null, named args)")
|
||||
reject_case("rejected/wrong_arity", "this call to area leaves out h, which has no default", "a call with the wrong number of arguments is refused")
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
function build_tool(name: pointer, src: pointer) -> bool {
|
||||
let ll = `build/{name}.ll`
|
||||
let out = `bin/{exe_name(name)}`
|
||||
if not shq(`bin/{exe_name("ludicc")} --unsafe {src} --emit-llvm -o {ll} 2>/dev/null`) { print(`build failed: {name} (compile)`); return false }
|
||||
if not shq(`bin/{exe_name("ludicc")} --unsafe --globals {src} --emit-llvm -o {ll} 2>/dev/null`) { print(`build failed: {name} (compile)`); return false }
|
||||
# write to a temp then move, so a running bin/ludic can rebuild itself in place
|
||||
# (Windows lets a running executable be renamed, never replaced, so the old one
|
||||
# steps aside first)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue