feat(cli): 0.R4 - ludic deps sees through fn values (widest_reach) and lists the widest functions (--widest N, --reach N)
A step list or a registry of fn values takes no state and still reaches every state its steps take. The compiler now writes `reach <n> <function>` - every state a function can come to by a call, a `fn f` it writes or a global holding fn values it reads, to a fixed point - and ludic deps reports widest_reach beside widest_function, with how many of those states the function does not take (Maroon Lake: app_boot, 72, all 72 through fn values). --widest N lists the N functions that take the most states with what each reaches; --reach N orders them by reach. A baseline without widest_reach does not hold it until rewritten. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
2eefae0618
commit
eb1e780733
8 changed files with 45622 additions and 42625 deletions
|
|
@ -381,7 +381,12 @@ program Pack {
|
||||||
|
|
||||||
`ludic deps` reports the widest function - the most states any function or entry point of the
|
`ludic deps` reports the widest function - the most states any function or entry point of the
|
||||||
program's own takes - and `--check` holds it as a ratchet like its other numbers
|
program's own takes - and `--check` holds it as a ratchet like its other numbers
|
||||||
(`widest_function 12` in the baseline file).
|
(`widest_function 12` in the baseline file). A function value's states are supplied where it is called, so a
|
||||||
|
step list - `let STEPS = [fn a, fn b]` walked by a function that takes nothing - hides what it
|
||||||
|
touches; `widest_reach` is the most states any function can come to, through its calls, the `fn f`
|
||||||
|
it writes and the globals holding fn values it reads (a registry of systems), and the report says how
|
||||||
|
many of them it does not take itself. `ludic deps --widest N` lists the N functions that take the
|
||||||
|
most states, each with what it reaches; `--reach N` lists them by reach.
|
||||||
|
|
||||||
### Types are checked before anything is emitted
|
### Types are checked before anything is emitted
|
||||||
|
|
||||||
|
|
|
||||||
10
changes/deps-reach.md
Normal file
10
changes/deps-reach.md
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
bump: minor
|
||||||
|
type: feat
|
||||||
|
**`ludic deps` sees through fn values, and lists the widest functions.** A step list or a registry of
|
||||||
|
fn values takes no state and still reaches every state its steps take; `widest_reach` is the most
|
||||||
|
states any function can come to - by a call, a `fn f` it writes, or a global holding fn values it
|
||||||
|
reads - reported beside `widest_function` with how many of them it does not take itself
|
||||||
|
(`the widest reach: app_boot (src/app/boot.ludic:30), 72 states (72 through calls and fn values it
|
||||||
|
does not take)`). `--widest N` lists the N functions that take the most states with what each
|
||||||
|
reaches; `--reach N` orders them by reach. A baseline written before this has no `widest_reach` and
|
||||||
|
does not hold it until it is rewritten.
|
||||||
21
examples/state/reach.ludic
Normal file
21
examples/state/reach.ludic
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
# ludic deps --reach: a step list takes no state and still reaches every state its steps take
|
||||||
|
program Reach {
|
||||||
|
state Alpha { n: int = 0 }
|
||||||
|
state Beta { n: int = 0 }
|
||||||
|
state Gamma { n: int = 0 }
|
||||||
|
function step_a(a: mut Alpha) -> void { a.n += 1 }
|
||||||
|
function step_b(b: mut Beta) -> void { b.n += 2 }
|
||||||
|
function step_c(c: mut Gamma) -> void { c.n += 3 }
|
||||||
|
let STEPS: []fn() -> void = [fn step_a, fn step_b]
|
||||||
|
function run_steps() -> void {
|
||||||
|
for i in 0 .. len(STEPS) { STEPS[i]() }
|
||||||
|
}
|
||||||
|
function one(c: mut Gamma) -> void {
|
||||||
|
step_c(c)
|
||||||
|
run_steps()
|
||||||
|
}
|
||||||
|
entry (c: mut Gamma) {
|
||||||
|
one(c)
|
||||||
|
print(`{c.n}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,6 +7,8 @@
|
||||||
# edge <from> <to> <count> <first name>
|
# edge <from> <to> <count> <first name>
|
||||||
# write <owner> <global> <from> <file>:<line>
|
# write <owner> <global> <from> <file>:<line>
|
||||||
# alias <owner> <global> <from> <file>:<line> <local> (a write through a local, below)
|
# alias <owner> <global> <from> <file>:<line> <local> (a write through a local, below)
|
||||||
|
# width <n> <name> <file>:<line> <pkg> (the states it takes)
|
||||||
|
# reach <n> <name> <file>:<line> <pkg> (the states it can come to, fn values too)
|
||||||
var g_dp_on: int = -1
|
var g_dp_on: int = -1
|
||||||
var g_dp_key: []pointer = new []pointer # "from to"
|
var g_dp_key: []pointer = new []pointer # "from to"
|
||||||
var g_dp_cnt: []int = new []int
|
var g_dp_cnt: []int = new []int
|
||||||
|
|
@ -117,6 +119,7 @@ function deps_flush() -> void {
|
||||||
i += 1
|
i += 1
|
||||||
}
|
}
|
||||||
deps_widths(f)
|
deps_widths(f)
|
||||||
|
deps_reach(f)
|
||||||
file_close(f)
|
file_close(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -261,3 +264,137 @@ function deps_alias_write(t: Node) -> void {
|
||||||
if k < 0 { return }
|
if k < 0 { return }
|
||||||
push(g_dp_aliases, `alias {g_dp_al_owner[k]} {g_dp_al_global[k]} {module_of(g_err_file)} {g_err_file}:{itoa(g_err_line)} {r.s}`)
|
push(g_dp_aliases, `alias {g_dp_al_owner[k]} {g_dp_al_global[k]} {module_of(g_err_file)} {g_err_file}:{itoa(g_err_line)} {r.s}`)
|
||||||
}
|
}
|
||||||
|
# 0.R4: the states a function REACHES - its own, and every state of every function it can come to:
|
||||||
|
# by a call, by `fn f` written in it, or by reading a global whose value holds `fn f` (a step list,
|
||||||
|
# a registry of fn values). A function value's states are supplied where it is called, so a step list
|
||||||
|
# or a reducer that walks one takes none itself and still reaches all of them.
|
||||||
|
# `reach <n> <name> <file>:<line> <pkg>`, for each of the program's functions that reaches any.
|
||||||
|
var g_dr_name: []pointer = new []pointer # the declarations a name can reach: functions, globals
|
||||||
|
var g_dr_node: []Node = new []Node
|
||||||
|
var g_dr_refs: [][]int = new [][]int # what each one names
|
||||||
|
var g_dr_bits: [][]int = new [][]int # the states it reaches, 60 to a word
|
||||||
|
var g_dr_find_k: []pointer = new []pointer
|
||||||
|
var g_dr_find_v: []Node = new []Node
|
||||||
|
function dr_state_ix(t: pointer) -> int {
|
||||||
|
var i = 0
|
||||||
|
while i < len(g_state_names) {
|
||||||
|
if (g_state_names[i] == t) { return i }
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
function dr_set(bits: []int, s: int) -> void { bits[s / 60] = bits[s / 60] | (1 << (s % 60)) }
|
||||||
|
function dr_words() -> int { return len(g_state_names) / 60 + 1 }
|
||||||
|
function dr_index(name: pointer) -> int {
|
||||||
|
let n = ck_tab_get(g_dr_find_k, g_dr_find_v, name)
|
||||||
|
if n == null { return -1 }
|
||||||
|
return n.ival
|
||||||
|
}
|
||||||
|
function dr_walk(n: Node, refs: []int) -> void {
|
||||||
|
if n == null { return }
|
||||||
|
if (n.kind == E_ID or n.kind == E_FNREF) and n.s != null {
|
||||||
|
let k = dr_index(n.s)
|
||||||
|
if k >= 0 { push(refs, k) }
|
||||||
|
}
|
||||||
|
dr_walk(n.a, refs)
|
||||||
|
dr_walk(n.b, refs)
|
||||||
|
dr_walk(n.c, refs)
|
||||||
|
if n.kids != null {
|
||||||
|
var i = 0
|
||||||
|
while i < len(n.kids) {
|
||||||
|
dr_walk(n.kids[i], refs)
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function dr_own(d: Node, bits: []int) -> void {
|
||||||
|
if d.kind == N_FN {
|
||||||
|
var k = 0
|
||||||
|
while k < len(d.kids) {
|
||||||
|
if d.kids[k].kind == N_PARAM {
|
||||||
|
let s = dr_state_ix(d.kids[k].ty)
|
||||||
|
if s >= 0 { dr_set(bits, s) }
|
||||||
|
}
|
||||||
|
k += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function dr_count(bits: []int) -> int {
|
||||||
|
var n = 0
|
||||||
|
var i = 0
|
||||||
|
while i < len(bits) {
|
||||||
|
var w = bits[i]
|
||||||
|
while w != 0 {
|
||||||
|
n += w & 1
|
||||||
|
w = w >> 1
|
||||||
|
}
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
function deps_reach(f: pointer) -> void {
|
||||||
|
ck_tab_init(g_dr_find_k, g_dr_find_v)
|
||||||
|
var i = 0
|
||||||
|
while i < len(prog) {
|
||||||
|
let d = prog[i]
|
||||||
|
if d.s != null and (d.kind == N_FN or d.kind == N_VAR or d.kind == N_CONST) and ck_tab_get(g_dr_find_k, g_dr_find_v, d.s) == null {
|
||||||
|
let h = new Node
|
||||||
|
h.ival = len(g_dr_node)
|
||||||
|
ck_tab_put(g_dr_find_k, g_dr_find_v, d.s, h)
|
||||||
|
push(g_dr_name, d.s)
|
||||||
|
push(g_dr_node, d)
|
||||||
|
}
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
let nw = dr_words()
|
||||||
|
var j = 0
|
||||||
|
while j < len(g_dr_node) {
|
||||||
|
let refs = new []int
|
||||||
|
dr_walk(g_dr_node[j], refs)
|
||||||
|
push(g_dr_refs, refs)
|
||||||
|
let bits = new []int
|
||||||
|
var w = 0
|
||||||
|
while w < nw {
|
||||||
|
push(bits, 0)
|
||||||
|
w += 1
|
||||||
|
}
|
||||||
|
dr_own(g_dr_node[j], bits)
|
||||||
|
push(g_dr_bits, bits)
|
||||||
|
j += 1
|
||||||
|
}
|
||||||
|
var changed = true
|
||||||
|
while changed { # to a fixed point: what a callee reaches, the caller does
|
||||||
|
changed = false
|
||||||
|
j = 0
|
||||||
|
while j < len(g_dr_node) {
|
||||||
|
let mine = g_dr_bits[j]
|
||||||
|
let refs = g_dr_refs[j]
|
||||||
|
var r = 0
|
||||||
|
while r < len(refs) {
|
||||||
|
let theirs = g_dr_bits[refs[r]]
|
||||||
|
var w2 = 0
|
||||||
|
while w2 < nw {
|
||||||
|
let u = mine[w2] | theirs[w2]
|
||||||
|
if u != mine[w2] {
|
||||||
|
mine[w2] = u
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
w2 += 1
|
||||||
|
}
|
||||||
|
r += 1
|
||||||
|
}
|
||||||
|
j += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
j = 0
|
||||||
|
while j < len(g_dr_node) {
|
||||||
|
let d = g_dr_node[j]
|
||||||
|
if d.kind == N_FN and d.file != null and not is_runtime_file(d.file) {
|
||||||
|
let n = dr_count(g_dr_bits[j])
|
||||||
|
var pkg = 0
|
||||||
|
if not (pkg_of_file(d.file) == "") { pkg = 1 }
|
||||||
|
if n > 0 { deps_line(f, `reach {itoa(n)} {d.s} {d.file}:{itoa(d.line)} {itoa(pkg)}`) }
|
||||||
|
}
|
||||||
|
j += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
43983
selfhost/ludicc.seed.ll
43983
selfhost/ludicc.seed.ll
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -13,13 +13,17 @@
|
||||||
# ludic deps --uses MOD what the other modules use of MOD
|
# ludic deps --uses MOD what the other modules use of MOD
|
||||||
# ludic deps --check FILE fail if any number is above FILE's
|
# ludic deps --check FILE fail if any number is above FILE's
|
||||||
# ludic deps --baseline FILE write the numbers to FILE
|
# ludic deps --baseline FILE write the numbers to FILE
|
||||||
|
# ludic deps --widest N the N functions that take the most states, and what each reaches
|
||||||
|
# ludic deps --reach N the same, the N that reach the most
|
||||||
#
|
#
|
||||||
# dependencies: module pairs (a, b) where a uses something of b, not counting b when b uses no
|
# dependencies: module pairs (a, b) where a uses something of b, not counting b when b uses no
|
||||||
# other module (a leaf); largest_cycle: the largest set of modules that all reach each other, not
|
# other module (a leaf); largest_cycle: the largest set of modules that all reach each other, not
|
||||||
# counting the edges inside a declared layer (which may go round by design - reported beside it);
|
# counting the edges inside a declared layer (which may go round by design - reported beside it);
|
||||||
# cross_writes: assignments to a global of another module; globals_written_from_outside: those
|
# cross_writes: assignments to a global of another module; globals_written_from_outside: those
|
||||||
# globals, each counted once; widest_function: the most states any function or entry point of the
|
# globals, each counted once; widest_function: the most states any function or entry point of the
|
||||||
# program's own takes (0.R2 - a reducer takes one, and the dispatchers should come down to a few).
|
# program's own takes (0.R2 - a reducer takes one, and the dispatchers should come down to a few);
|
||||||
|
# widest_reach: the most states any of them can come to - what it takes, and through calls, `fn f`
|
||||||
|
# and globals holding fn values (a step list, a registry of systems) what those take (0.R4).
|
||||||
var dp_mods: []pointer = null
|
var dp_mods: []pointer = null
|
||||||
var dp_pkg: []int = null
|
var dp_pkg: []int = null
|
||||||
var dp_uses: []pointer = null
|
var dp_uses: []pointer = null
|
||||||
|
|
@ -35,6 +39,12 @@ var dp_wname: []pointer = null
|
||||||
var dp_wfrom: []pointer = null
|
var dp_wfrom: []pointer = null
|
||||||
var dp_wide_n: int = 0 # 0.R2: the widest function's state parameters, and which
|
var dp_wide_n: int = 0 # 0.R2: the widest function's state parameters, and which
|
||||||
var dp_wide_at: pointer = ""
|
var dp_wide_at: pointer = ""
|
||||||
|
var dp_reach_n: int = 0 # 0.R4: the most states a function reaches, fn values included
|
||||||
|
var dp_reach_at: pointer = ""
|
||||||
|
var dp_reach_own: int = 0 # ... of which it takes itself
|
||||||
|
var dp_fn_at: []pointer = null # each of the program's own functions: where, what it takes, what it reaches
|
||||||
|
var dp_fn_w: []int = null
|
||||||
|
var dp_fn_r: []int = null
|
||||||
|
|
||||||
function dp_words(line: pointer) -> []pointer {
|
function dp_words(line: pointer) -> []pointer {
|
||||||
let out = new []pointer
|
let out = new []pointer
|
||||||
|
|
@ -68,6 +78,8 @@ function dp_load(path: pointer) -> bool {
|
||||||
dp_mods = new []pointer; dp_pkg = new []int; dp_uses = new []pointer; dp_layer = new []pointer
|
dp_mods = new []pointer; dp_pkg = new []int; dp_uses = new []pointer; dp_layer = new []pointer
|
||||||
dp_ef = new []int; dp_et = new []int; dp_ec = new []int; dp_en = new []pointer
|
dp_ef = new []int; dp_et = new []int; dp_ec = new []int; dp_en = new []pointer
|
||||||
dp_wide_n = 0; dp_wide_at = ""
|
dp_wide_n = 0; dp_wide_at = ""
|
||||||
|
dp_reach_n = 0; dp_reach_at = ""; dp_reach_own = 0
|
||||||
|
dp_fn_at = new []pointer; dp_fn_w = new []int; dp_fn_r = new []int
|
||||||
dp_writes = new []pointer; dp_aliases = new []pointer; dp_wowner = new []pointer; dp_wname = new []pointer; dp_wfrom = new []pointer
|
dp_writes = new []pointer; dp_aliases = new []pointer; dp_wowner = new []pointer; dp_wname = new []pointer; dp_wfrom = new []pointer
|
||||||
let lines = split_lines(text)
|
let lines = split_lines(text)
|
||||||
for i in 0 .. len(lines) {
|
for i in 0 .. len(lines) {
|
||||||
|
|
@ -93,6 +105,10 @@ function dp_load(path: pointer) -> bool {
|
||||||
dp_wide_n = s_to_int(w[1])
|
dp_wide_n = s_to_int(w[1])
|
||||||
dp_wide_at = `{dp_fn_name(w[2])} ({w[3]})`
|
dp_wide_at = `{dp_fn_name(w[2])} ({w[3]})`
|
||||||
}
|
}
|
||||||
|
if len(w) >= 5 and (w[0] == "width" or w[0] == "reach") and w[4] == "0" {
|
||||||
|
let k = dp_fn(`{dp_fn_name(w[2])} ({w[3]})`)
|
||||||
|
if w[0] == "width" { dp_fn_w[k] = s_to_int(w[1]) } else { dp_fn_r[k] = s_to_int(w[1]) }
|
||||||
|
}
|
||||||
if len(w) >= 5 and w[0] == "write" {
|
if len(w) >= 5 and w[0] == "write" {
|
||||||
push(dp_wowner, w[1])
|
push(dp_wowner, w[1])
|
||||||
push(dp_wname, w[2])
|
push(dp_wname, w[2])
|
||||||
|
|
@ -102,6 +118,67 @@ function dp_load(path: pointer) -> bool {
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
# a function of the program's own, by `name (file:line)`
|
||||||
|
function dp_fn(at: pointer) -> int {
|
||||||
|
for i in 0 .. len(dp_fn_at) { if dp_fn_at[i] == at { return i } }
|
||||||
|
push(dp_fn_at, at)
|
||||||
|
push(dp_fn_w, 0)
|
||||||
|
push(dp_fn_r, 0)
|
||||||
|
return len(dp_fn_at) - 1
|
||||||
|
}
|
||||||
|
# after the load: the widest reach (a function reaches at least what it takes)
|
||||||
|
function dp_reach_best() -> void {
|
||||||
|
for i in 0 .. len(dp_fn_at) {
|
||||||
|
if dp_fn_r[i] < dp_fn_w[i] { dp_fn_r[i] = dp_fn_w[i] }
|
||||||
|
if dp_fn_r[i] > dp_reach_n {
|
||||||
|
dp_reach_n = dp_fn_r[i]
|
||||||
|
dp_reach_at = dp_fn_at[i]
|
||||||
|
dp_reach_own = dp_fn_w[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# --widest N / --reach N: the top N functions by what they take (or by what they reach), both shown
|
||||||
|
function dp_top(n: int, by_reach: bool) -> void {
|
||||||
|
let order = new []int
|
||||||
|
for i in 0 .. len(dp_fn_at) { push(order, i) }
|
||||||
|
for a in 1 .. len(order) {
|
||||||
|
let x = order[a]
|
||||||
|
var b = a - 1
|
||||||
|
while b >= 0 and dp_top_before(x, order[b], by_reach) {
|
||||||
|
order[b + 1] = order[b]
|
||||||
|
b -= 1
|
||||||
|
}
|
||||||
|
order[b + 1] = x
|
||||||
|
}
|
||||||
|
print("takes reaches function")
|
||||||
|
var shown = 0
|
||||||
|
for i in 0 .. len(order) {
|
||||||
|
let k = order[i]
|
||||||
|
if shown < n and (dp_fn_w[k] > 0 or dp_fn_r[k] > 0) {
|
||||||
|
print(`{dp_pad(string(dp_fn_w[k]), 5)} {dp_pad(string(dp_fn_r[k]), 7)} {dp_fn_at[k]}`)
|
||||||
|
shown += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function dp_top_before(x: int, y: int, by_reach: bool) -> bool {
|
||||||
|
var kx = dp_fn_w[x]
|
||||||
|
var ky = dp_fn_w[y]
|
||||||
|
var tx = dp_fn_r[x]
|
||||||
|
var ty = dp_fn_r[y]
|
||||||
|
if by_reach {
|
||||||
|
kx = dp_fn_r[x]
|
||||||
|
ky = dp_fn_r[y]
|
||||||
|
tx = dp_fn_w[x]
|
||||||
|
ty = dp_fn_w[y]
|
||||||
|
}
|
||||||
|
if kx != ky { return kx > ky }
|
||||||
|
return tx > ty
|
||||||
|
}
|
||||||
|
function dp_pad(s: pointer, n: int) -> pointer {
|
||||||
|
var out = s
|
||||||
|
while slen(out) < n { out = " " + out }
|
||||||
|
return out
|
||||||
|
}
|
||||||
# a function as its source names it: a reducer is `reducer Bag on PickUp`, not its symbol
|
# a function as its source names it: a reducer is `reducer Bag on PickUp`, not its symbol
|
||||||
function dp_fn_name(n: pointer) -> pointer {
|
function dp_fn_name(n: pointer) -> pointer {
|
||||||
if not s_starts(n, "ludic_reduce__") { return n }
|
if not s_starts(n, "ludic_reduce__") { return n }
|
||||||
|
|
@ -213,7 +290,7 @@ var dp_vals: []int = null
|
||||||
var dp_cycle: []int = null
|
var dp_cycle: []int = null
|
||||||
var dp_cycle_all: []int = null # the same with the layers' own edges counted
|
var dp_cycle_all: []int = null # the same with the layers' own edges counted
|
||||||
function dp_numbers() -> void {
|
function dp_numbers() -> void {
|
||||||
dp_names = ["modules", "dependencies", "largest_cycle", "cross_writes", "globals_written_from_outside", "widest_function"]
|
dp_names = ["modules", "dependencies", "largest_cycle", "cross_writes", "globals_written_from_outside", "widest_function", "widest_reach"]
|
||||||
var mods = 0
|
var mods = 0
|
||||||
for k in 0 .. len(dp_mods) { if dp_own(k) { mods += 1 } }
|
for k in 0 .. len(dp_mods) { if dp_own(k) { mods += 1 } }
|
||||||
var deps = 0
|
var deps = 0
|
||||||
|
|
@ -234,7 +311,8 @@ function dp_numbers() -> void {
|
||||||
if not dup { push(seen, key) }
|
if not dup { push(seen, key) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dp_vals = [mods, deps, len(dp_cycle), writes, len(seen), dp_wide_n]
|
dp_reach_best()
|
||||||
|
dp_vals = [mods, deps, len(dp_cycle), writes, len(seen), dp_wide_n, dp_reach_n]
|
||||||
}
|
}
|
||||||
|
|
||||||
# ---- the command --------------------------------------------------------------
|
# ---- the command --------------------------------------------------------------
|
||||||
|
|
@ -243,6 +321,7 @@ function dp_print_numbers() -> void {
|
||||||
print(`in the largest cycle: {dp_sorted_names(dp_cycle)}`)
|
print(`in the largest cycle: {dp_sorted_names(dp_cycle)}`)
|
||||||
if dp_wide_n == 1 { print(`the widest function: {dp_wide_at}, 1 state`) }
|
if dp_wide_n == 1 { print(`the widest function: {dp_wide_at}, 1 state`) }
|
||||||
if dp_wide_n > 1 { print(`the widest function: {dp_wide_at}, {string(dp_wide_n)} states`) }
|
if dp_wide_n > 1 { print(`the widest function: {dp_wide_at}, {string(dp_wide_n)} states`) }
|
||||||
|
if dp_reach_n > dp_reach_own { print(`the widest reach: {dp_reach_at}, {string(dp_reach_n)} states ({string(dp_reach_n - dp_reach_own)} through calls and fn values it does not take)`) }
|
||||||
let layers = dp_layers()
|
let layers = dp_layers()
|
||||||
if layers != "" {
|
if layers != "" {
|
||||||
print(`layers: {layers}`)
|
print(`layers: {layers}`)
|
||||||
|
|
@ -385,7 +464,7 @@ function cmd_deps() -> int {
|
||||||
if a == "--dot" { mode = "dot" }
|
if a == "--dot" { mode = "dot" }
|
||||||
else if a == "--graph" { mode = "graph" }
|
else if a == "--graph" { mode = "graph" }
|
||||||
else if a == "--writes" { mode = "writes" }
|
else if a == "--writes" { mode = "writes" }
|
||||||
else if a == "--uses" or a == "--check" or a == "--baseline" {
|
else if a == "--uses" or a == "--check" or a == "--baseline" or a == "--widest" or a == "--reach" {
|
||||||
if ai + 1 >= arg_count() { err(`ludic deps: {a} needs an argument\n`); return 2 }
|
if ai + 1 >= arg_count() { err(`ludic deps: {a} needs an argument\n`); return 2 }
|
||||||
mode = sslice(a, 2, slen(a))
|
mode = sslice(a, 2, slen(a))
|
||||||
ai += 1
|
ai += 1
|
||||||
|
|
@ -393,7 +472,7 @@ function cmd_deps() -> int {
|
||||||
}
|
}
|
||||||
else if a[0] == '-' {
|
else if a[0] == '-' {
|
||||||
err(`ludic deps: unknown option {a}\n`)
|
err(`ludic deps: unknown option {a}\n`)
|
||||||
err(" usage: ludic deps [file] [--graph | --dot | --writes | --uses MOD | --check FILE | --baseline FILE]\n")
|
err(" usage: ludic deps [file] [--graph | --dot | --writes | --uses MOD | --check FILE | --baseline FILE | --widest N | --reach N]\n")
|
||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
else { src = a }
|
else { src = a }
|
||||||
|
|
@ -426,6 +505,8 @@ function cmd_deps() -> int {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
if mode == "uses" { dp_uses_of(arg2); return 0 }
|
if mode == "uses" { dp_uses_of(arg2); return 0 }
|
||||||
|
if mode == "widest" { dp_top(s_to_int(arg2), false); return 0 }
|
||||||
|
if mode == "reach" { dp_top(s_to_int(arg2), true); return 0 }
|
||||||
if mode == "check" { return dp_check(arg2) }
|
if mode == "check" { return dp_check(arg2) }
|
||||||
if mode == "baseline" { return dp_baseline(arg2) }
|
if mode == "baseline" { return dp_baseline(arg2) }
|
||||||
dp_print_numbers()
|
dp_print_numbers()
|
||||||
|
|
|
||||||
|
|
@ -476,7 +476,7 @@ function deps_case() -> void {
|
||||||
let lbl = "ludic deps: the numbers, --check against a baseline, --dot, --writes"
|
let lbl = "ludic deps: the numbers, --check against a baseline, --dot, --writes"
|
||||||
let p = "examples/modules/tangle.ludic"
|
let p = "examples/modules/tangle.ludic"
|
||||||
let got = capture(`bin/ludic deps {p} 2>&1`)
|
let got = capture(`bin/ludic deps {p} 2>&1`)
|
||||||
let want = "modules: 3\ndependencies: 2\nlargest_cycle: 2\ncross_writes: 1\nglobals_written_from_outside: 1\nwidest_function: 1\nin the largest cycle: a b\nthe widest function: a_run (examples/modules/tangle/a/index.ludic:3), 1 state\n"
|
let want = "modules: 3\ndependencies: 2\nlargest_cycle: 2\ncross_writes: 1\nglobals_written_from_outside: 1\nwidest_function: 1\nwidest_reach: 1\nin the largest cycle: a b\nthe widest function: a_run (examples/modules/tangle/a/index.ludic:3), 1 state\n"
|
||||||
if not (s_trim(got) == s_trim(want)) { bad2(lbl, `said [{s_trim(got)}]`); return }
|
if not (s_trim(got) == s_trim(want)) { bad2(lbl, `said [{s_trim(got)}]`); return }
|
||||||
let base = `{tmp_dir()}/deps-base.txt`
|
let base = `{tmp_dir()}/deps-base.txt`
|
||||||
if not shq(`bin/ludic deps {p} --baseline {base} > /dev/null`) { bad2(lbl, "--baseline failed"); return }
|
if not shq(`bin/ludic deps {p} --baseline {base} > /dev/null`) { bad2(lbl, "--baseline failed"); return }
|
||||||
|
|
@ -583,6 +583,17 @@ function actions_deps_case() -> void {
|
||||||
let got = capture(`bin/ludic deps examples/actions/modules.ludic 2>&1`)
|
let got = capture(`bin/ludic deps examples/actions/modules.ludic 2>&1`)
|
||||||
if s_contains(got, "largest_cycle: 1") and s_contains(got, "dependencies: 0") and s_contains(got, "the widest function: reducer Bag on PickUp (") { ok(lbl) } else { bad2(lbl, s_trim(got)) }
|
if s_contains(got, "largest_cycle: 1") and s_contains(got, "dependencies: 0") and s_contains(got, "the widest function: reducer Bag on PickUp (") { ok(lbl) } else { bad2(lbl, s_trim(got)) }
|
||||||
}
|
}
|
||||||
|
# 0.R4: a step list takes no state and reaches every state its steps take; --widest and --reach
|
||||||
|
# list the functions by what they take and by what they reach
|
||||||
|
function deps_reach_case() -> void {
|
||||||
|
let lbl = "ludic deps: the reach through fn values, --widest N and --reach N"
|
||||||
|
let got = capture(`bin/ludic deps examples/state/reach.ludic 2>&1`)
|
||||||
|
if not s_contains(got, "widest_reach: 3") or not s_contains(got, "the widest reach: one (examples/state/reach.ludic:13), 3 states (2 through calls and fn values it does not take)") { bad2(lbl, s_trim(got)); return }
|
||||||
|
let top = capture(`bin/ludic deps examples/state/reach.ludic --reach 2 2>&1`)
|
||||||
|
if not s_contains(top, " 1 3 one (examples/state/reach.ludic:13)\n 0 2 run_steps (examples/state/reach.ludic:10)") { bad2(lbl, s_trim(top)); return }
|
||||||
|
let wide = capture(`bin/ludic deps examples/state/reach.ludic --widest 1 2>&1`)
|
||||||
|
if s_contains(wide, "takes reaches function") and s_contains(wide, "one (examples/state/reach.ludic:13)") and not s_contains(wide, "step_a") { ok(lbl) } else { bad2(lbl, s_trim(wide)) }
|
||||||
|
}
|
||||||
# a program built headless with the toolchain and run: its first line
|
# a program built headless with the toolchain and run: its first line
|
||||||
function headless_case(path: pointer, exp: pointer, label: pointer) -> void {
|
function headless_case(path: pointer, exp: pointer, label: pointer) -> void {
|
||||||
let out = `{tmp_dir()}/h_{flat(path)}`
|
let out = `{tmp_dir()}/h_{flat(path)}`
|
||||||
|
|
@ -1001,6 +1012,8 @@ function cmd_dev_test() -> int {
|
||||||
feat_case("actions/pack", "", "0 1 9 13 picked 7 picked 9 too heavy", "pack.ludic (0.R: actions, reducers - one per state, in the order of the states' names - dispatch, drain_actions, an action a reducer dispatches goes behind)")
|
feat_case("actions/pack", "", "0 1 9 13 picked 7 picked 9 too heavy", "pack.ludic (0.R: actions, reducers - one per state, in the order of the states' names - dispatch, drain_actions, an action a reducer dispatches goes behind)")
|
||||||
feat_case("actions/modules", "", "2", "modules.ludic (0.R: an action in one module, its reducer in another)")
|
feat_case("actions/modules", "", "2", "modules.ludic (0.R: an action in one module, its reducer in another)")
|
||||||
actions_deps_case()
|
actions_deps_case()
|
||||||
|
deps_reach_case()
|
||||||
|
feat_case("state/reach", "", "3", "reach.ludic (0.R4: a step list of fn values supplies each step its states)")
|
||||||
feat_case("actions/phases", "dd a q", "1 0 2 0 2 1 1 1 1 2 1 2", "phases.ludic (0.R: the frame loop drains the queue after every phase - Input's actions are reduced before Update)")
|
feat_case("actions/phases", "dd a q", "1 0 2 0 2 1 1 1 1 2 1 2", "phases.ludic (0.R: the frame loop drains the queue after every phase - Input's actions are reduced before Update)")
|
||||||
feat_case("actions/runaway", "", "actions: Ping is still being dispatched after 64 rounds of reducers - a reducer dispatches what dispatches it", "runaway.ludic (0.R: a reducer that dispatches what dispatches it is stopped by name)")
|
feat_case("actions/runaway", "", "actions: Ping is still being dispatched after 64 rounds of reducers - a reducer dispatches what dispatches it", "runaway.ludic (0.R: a reducer that dispatches what dispatches it is stopped by name)")
|
||||||
reject_case("rejected/registry_count_key", "def Tools count: its constant would be TL_COUNT", "a registry key named count is refused")
|
reject_case("rejected/registry_count_key", "def Tools count: its constant would be TL_COUNT", "a registry key named count is refused")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue