feat(lang): layers - module flow in layer app uses base, items
The modules of one layer use each other freely and may go round; anything outside the layer is held to the module's uses, and a layered module with no uses reaches nothing outside it. The cycle check walks the graph with each layer as one node, so a cycle leaving a layer is refused. ludic deps shows the layers and counts the largest cycle without their own edges. Reseed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
e3219f17dd
commit
7b8c134c21
20 changed files with 47359 additions and 46193 deletions
|
|
@ -14,12 +14,14 @@
|
|||
# ludic deps --baseline FILE write the numbers to FILE
|
||||
#
|
||||
# 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;
|
||||
# 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);
|
||||
# cross_writes: assignments to a global of another module; globals_written_from_outside: those
|
||||
# globals, each counted once.
|
||||
var dp_mods: []pointer = null
|
||||
var dp_pkg: []int = null
|
||||
var dp_uses: []pointer = null
|
||||
var dp_layer: []pointer = null
|
||||
var dp_ef: []int = null
|
||||
var dp_et: []int = null
|
||||
var dp_ec: []int = null
|
||||
|
|
@ -52,22 +54,24 @@ function dp_mod(name: pointer) -> int {
|
|||
push(dp_mods, name)
|
||||
push(dp_pkg, 1)
|
||||
push(dp_uses, "-")
|
||||
push(dp_layer, "-")
|
||||
return len(dp_mods) - 1
|
||||
}
|
||||
function dp_load(path: pointer) -> bool {
|
||||
let text = read_file(path)
|
||||
if text == null { return false }
|
||||
dp_mods = new []pointer; dp_pkg = new []int; dp_uses = 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_writes = new []pointer; dp_wowner = new []pointer; dp_wname = new []pointer; dp_wfrom = new []pointer
|
||||
let lines = split_lines(text)
|
||||
for i in 0 .. len(lines) {
|
||||
let w = dp_words(lines[i])
|
||||
if len(w) == 4 and w[0] == "module" {
|
||||
if len(w) >= 4 and w[0] == "module" {
|
||||
let k = dp_mod(w[1])
|
||||
dp_pkg[k] = 0
|
||||
if w[2] == "1" { dp_pkg[k] = 1 }
|
||||
dp_uses[k] = w[3]
|
||||
if len(w) >= 5 { dp_layer[k] = w[4] }
|
||||
}
|
||||
}
|
||||
for i in 0 .. len(lines) {
|
||||
|
|
@ -90,6 +94,11 @@ function dp_load(path: pointer) -> bool {
|
|||
function dp_own(k: int) -> bool { return dp_pkg[k] == 0 }
|
||||
# an edge the numbers count: between two of the program's own modules
|
||||
function dp_counted(e: int) -> bool { return dp_own(dp_ef[e]) and dp_own(dp_et[e]) }
|
||||
# an edge inside a declared layer: allowed to go round, so not part of the cycle the numbers count
|
||||
function dp_in_layer(e: int) -> bool {
|
||||
let a = dp_layer[dp_ef[e]]
|
||||
return a != "-" and a == dp_layer[dp_et[e]]
|
||||
}
|
||||
function dp_leaf(k: int) -> bool {
|
||||
for e in 0 .. len(dp_ef) { if dp_ef[e] == k and dp_counted(e) { return false } }
|
||||
return true
|
||||
|
|
@ -103,6 +112,7 @@ var tj_stack: []int = null
|
|||
var tj_sp: int = 0
|
||||
var tj_n: int = 0
|
||||
var tj_best: []int = null
|
||||
var dp_skip_layers: bool = true # the largest cycle leaves out the edges inside a declared layer
|
||||
function tj_visit(v: int) -> void {
|
||||
tj_index[v] = tj_n
|
||||
tj_low[v] = tj_n
|
||||
|
|
@ -111,7 +121,7 @@ function tj_visit(v: int) -> void {
|
|||
tj_sp += 1
|
||||
tj_on[v] = 1
|
||||
for e in 0 .. len(dp_ef) {
|
||||
if dp_ef[e] == v and dp_counted(e) {
|
||||
if dp_ef[e] == v and dp_counted(e) and not (dp_skip_layers and dp_in_layer(e)) {
|
||||
let w = dp_et[e]
|
||||
if tj_index[w] < 0 {
|
||||
tj_visit(w)
|
||||
|
|
@ -181,12 +191,16 @@ function s_less(a: pointer, b: pointer) -> bool {
|
|||
var dp_names: []pointer = null
|
||||
var dp_vals: []int = null
|
||||
var dp_cycle: []int = null
|
||||
var dp_cycle_all: []int = null # the same with the layers' own edges counted
|
||||
function dp_numbers() -> void {
|
||||
dp_names = ["modules", "dependencies", "largest_cycle", "cross_writes", "globals_written_from_outside"]
|
||||
var mods = 0
|
||||
for k in 0 .. len(dp_mods) { if dp_own(k) { mods += 1 } }
|
||||
var deps = 0
|
||||
for e in 0 .. len(dp_ef) { if dp_counted(e) and not dp_leaf(dp_et[e]) { deps += 1 } }
|
||||
dp_skip_layers = false
|
||||
dp_cycle_all = dp_largest_cycle()
|
||||
dp_skip_layers = true
|
||||
dp_cycle = dp_largest_cycle()
|
||||
var writes = 0
|
||||
let seen = new []pointer
|
||||
|
|
@ -207,6 +221,11 @@ function dp_numbers() -> void {
|
|||
function dp_print_numbers() -> void {
|
||||
for i in 0 .. len(dp_names) { print(`{dp_names[i]}: {string(dp_vals[i])}`) }
|
||||
print(`in the largest cycle: {dp_sorted_names(dp_cycle)}`)
|
||||
let layers = dp_layers()
|
||||
if layers != "" {
|
||||
print(`layers: {layers}`)
|
||||
print(`largest cycle counting the layers' own edges: {string(len(dp_cycle_all))}`)
|
||||
}
|
||||
}
|
||||
# FILE: `name value` lines, `#` comments; every number but modules may only go down
|
||||
function dp_check(path: pointer) -> int {
|
||||
|
|
@ -246,7 +265,28 @@ function dp_baseline(path: pointer) -> int {
|
|||
print(`deps: wrote {path}`)
|
||||
return 0
|
||||
}
|
||||
# "app (11: core flow ...), ..." for the declared layers, "" when there are none
|
||||
function dp_layers() -> pointer {
|
||||
var out = ""
|
||||
let seen = new []pointer
|
||||
for k in 0 .. len(dp_mods) {
|
||||
let l = dp_layer[k]
|
||||
if l != "-" {
|
||||
var dup = false
|
||||
for s in 0 .. len(seen) { if seen[s] == l { dup = true } }
|
||||
if not dup {
|
||||
push(seen, l)
|
||||
let members = new []int
|
||||
for m in 0 .. len(dp_mods) { if dp_layer[m] == l { push(members, m) } }
|
||||
if out != "" { out = out + "; " }
|
||||
out = out + `{l} ({string(len(members))}: {dp_sorted_names(members)})`
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
function dp_declared(from: int, to: int) -> bool {
|
||||
if dp_layer[from] != "-" and dp_layer[from] == dp_layer[to] { return true }
|
||||
let u = dp_uses[from]
|
||||
if u == "-" { return false }
|
||||
return s_contains(u, `,{dp_mods[to]},`)
|
||||
|
|
@ -268,6 +308,7 @@ function dp_graph() -> void {
|
|||
if dp_own(k) {
|
||||
var uses = "(no uses line)"
|
||||
if dp_uses[k] != "-" { uses = `uses {dp_list(dp_uses[k])}` }
|
||||
if dp_layer[k] != "-" { uses = `layer {dp_layer[k]}, {uses}` }
|
||||
var to = ""
|
||||
for e in 0 .. len(dp_ef) {
|
||||
if dp_ef[e] == k {
|
||||
|
|
|
|||
|
|
@ -485,6 +485,8 @@ function deps_case() -> void {
|
|||
if shq(`bin/ludic deps {p} --check {base} > /dev/null`) { bad2(lbl, "--check passed with more dependencies than the baseline"); return }
|
||||
if not shq(`bin/ludic deps {p} --dot | grep -q '"a" -> "b"'`) { bad2(lbl, "--dot has no a -> b edge"); return }
|
||||
if not shq(`bin/ludic deps {p} --writes | grep -q 'b b_count a examples/modules/tangle/a/index.ludic:4'`) { bad2(lbl, "--writes misses a's write to b_count"); return }
|
||||
let lay = capture(`bin/ludic deps examples/modules/layers.ludic 2>&1`)
|
||||
if not s_contains(lay, "largest_cycle: 1") or not s_contains(lay, "layers: app (2: hud menu)") or not s_contains(lay, "counting the layers' own edges: 2") { bad2(lbl, `layers: [{s_trim(lay)}]`); return }
|
||||
ok(lbl)
|
||||
}
|
||||
|
||||
|
|
@ -852,6 +854,9 @@ function cmd_dev_test() -> int {
|
|||
reject_case("rejected/exported_events", "event 'Landed' is defined twice", "two modules that export one event clash")
|
||||
feat_case("modules/tangle", "", "4", "tangle.ludic (the program ludic deps is tested on)")
|
||||
reject_case("rejected/exported_twins", "function 'seed' is defined twice", "two modules that export one name still clash")
|
||||
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")
|
||||
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")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue