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
10
LANGUAGE.md
10
LANGUAGE.md
|
|
@ -159,6 +159,12 @@ fishing/land.ludic:4: error: fishing uses items.inv_add (items/index.ludic:3): a
|
||||||
- The declared graph may not go round: `module a uses b` beside `module b uses a` is refused
|
- The declared graph may not go round: `module a uses b` beside `module b uses a` is refused
|
||||||
(`the modules' uses go round in a circle: a -> b -> a`) - one of them takes the other through a
|
(`the modules' uses go round in a circle: a -> b -> a`) - one of them takes the other through a
|
||||||
port instead.
|
port instead.
|
||||||
|
- **Layers.** `module flow in layer app uses base, items` puts `flow` in layer `app`. The modules of
|
||||||
|
one layer use each other freely, without naming each other, and may go round - a game's app
|
||||||
|
modules (the flow, the menus, the HUD) reach each other by design. Everything outside the layer is
|
||||||
|
still held to the module's `uses`, and a layered module with no `uses` may reach nothing outside
|
||||||
|
its layer. A cycle is allowed only inside a layer: `items uses hud` with `hud` in layer `app`
|
||||||
|
using `items` back is `items -> layer app -> items`, refused. A module is in one layer.
|
||||||
- `LUDIC_VIS_REPORT=1` lists these too, as `uses: <file>:<line>: <module>.<name> used from <file>
|
- `LUDIC_VIS_REPORT=1` lists these too, as `uses: <file>:<line>: <module>.<name> used from <file>
|
||||||
(module <m>)`, and builds.
|
(module <m>)`, and builds.
|
||||||
|
|
||||||
|
|
@ -1844,7 +1850,9 @@ the other, not counting a use of a module that itself uses none), `largest_cycle
|
||||||
of modules that all reach each other, named on the last line), `cross_writes` and
|
of modules that all reach each other, named on the last line), `cross_writes` and
|
||||||
`globals_written_from_outside`. `--graph` lists each module with its declared `uses` and the edges
|
`globals_written_from_outside`. `--graph` lists each module with its declared `uses` and the edges
|
||||||
seen (`!` marks one its `uses` line does not name), `--dot` is the same for Graphviz with the cycle
|
seen (`!` marks one its `uses` line does not name), `--dot` is the same for Graphviz with the cycle
|
||||||
filled, `--writes` lists the writes and `--uses MOD` who uses MOD. `--check FILE` fails when any
|
filled, `--writes` lists the writes and `--uses MOD` who uses MOD. The largest cycle leaves out the
|
||||||
|
edges inside a declared layer, which may go round by design; the layers and the cycle counting their
|
||||||
|
own edges are printed after it. `--check FILE` fails when any
|
||||||
number is above FILE's `name value` lines; `--baseline FILE` writes them.
|
number is above FILE's `name value` lines; `--baseline FILE` writes them.
|
||||||
|
|
||||||
A test program is a file of `test "name" { ... }` blocks with `expect(cond)`, `expect_eq(a, b)` and
|
A test program is a file of `test "name" { ... }` blocks with `expect(cond)`, `expect_eq(a, b)` and
|
||||||
|
|
|
||||||
7
changes/module-layers.md
Normal file
7
changes/module-layers.md
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
bump: minor
|
||||||
|
type: feature
|
||||||
|
**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 still held to the module's `uses` (a
|
||||||
|
layered module with no `uses` reaches nothing outside its layer), and a cycle is refused unless it
|
||||||
|
stays inside one layer. `ludic deps` shows the layers and counts the largest cycle without the edges
|
||||||
|
inside them, printing the count with them beside it.
|
||||||
5
examples/modules/layered/hud/index.ludic
Normal file
5
examples/modules/layered/hud/index.ludic
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
# hud/index.ludic - in layer app: it reaches menu back - a cycle, allowed inside the layer
|
||||||
|
module hud in layer app
|
||||||
|
export function hud_lines() -> int {
|
||||||
|
return menu_title() + 1
|
||||||
|
}
|
||||||
5
examples/modules/layered/items/index.ludic
Normal file
5
examples/modules/layered/items/index.ludic
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
# items/index.ludic - below the layer: it uses nothing
|
||||||
|
module items uses
|
||||||
|
export function item_count() -> int {
|
||||||
|
return 3
|
||||||
|
}
|
||||||
8
examples/modules/layered/menu/index.ludic
Normal file
8
examples/modules/layered/menu/index.ludic
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
# menu/index.ludic - in layer app: it reaches hud (same layer) freely, and items through its uses
|
||||||
|
module menu in layer app uses items
|
||||||
|
export function menu_rows() -> int {
|
||||||
|
return item_count() + hud_lines()
|
||||||
|
}
|
||||||
|
export function menu_title() -> int {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
13
examples/modules/layers.ludic
Normal file
13
examples/modules/layers.ludic
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
# layers.ludic — L3: `module menu in layer app uses items`. The modules of one layer use each other
|
||||||
|
# freely and may go round (menu and hud use each other); anything outside the layer is held to the
|
||||||
|
# module's `uses` - a layered module with no uses line may reach nothing outside its layer.
|
||||||
|
#
|
||||||
|
# Running it prints: 5
|
||||||
|
import "layered/items"
|
||||||
|
import "layered/menu"
|
||||||
|
import "layered/hud"
|
||||||
|
program Layers {
|
||||||
|
entry {
|
||||||
|
print(menu_rows())
|
||||||
|
}
|
||||||
|
}
|
||||||
9
examples/rejected/layer_cycle.ludic
Normal file
9
examples/rejected/layer_cycle.ludic
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
# L3: a cycle is allowed inside a layer and nowhere else: items -> layer app -> items is refused
|
||||||
|
import "layer_cycle/items"
|
||||||
|
import "layer_cycle/menu"
|
||||||
|
import "layer_cycle/hud"
|
||||||
|
program LayerCycle {
|
||||||
|
entry {
|
||||||
|
print(menu_rows())
|
||||||
|
}
|
||||||
|
}
|
||||||
5
examples/rejected/layer_cycle/hud/index.ludic
Normal file
5
examples/rejected/layer_cycle/hud/index.ludic
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
# hud/index.ludic - in layer app: it reaches menu back - a cycle, allowed inside the layer
|
||||||
|
module hud in layer app
|
||||||
|
export function hud_lines() -> int {
|
||||||
|
return menu_title() + 1
|
||||||
|
}
|
||||||
5
examples/rejected/layer_cycle/items/index.ludic
Normal file
5
examples/rejected/layer_cycle/items/index.ludic
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
# items/index.ludic - uses hud, which is in layer app
|
||||||
|
module items uses hud
|
||||||
|
export function item_count() -> int {
|
||||||
|
return 3
|
||||||
|
}
|
||||||
8
examples/rejected/layer_cycle/menu/index.ludic
Normal file
8
examples/rejected/layer_cycle/menu/index.ludic
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
# menu/index.ludic - in layer app: it reaches hud (same layer) freely, and items through its uses
|
||||||
|
module menu in layer app uses items
|
||||||
|
export function menu_rows() -> int {
|
||||||
|
return item_count() + hud_lines()
|
||||||
|
}
|
||||||
|
export function menu_title() -> int {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
9
examples/rejected/layer_reach.ludic
Normal file
9
examples/rejected/layer_reach.ludic
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
# L3: a layer frees the modules in it from naming each other, not from naming anything else
|
||||||
|
import "layer_reach/items"
|
||||||
|
import "layer_reach/menu"
|
||||||
|
import "layer_reach/hud"
|
||||||
|
program LayerReach {
|
||||||
|
entry {
|
||||||
|
print(menu_rows())
|
||||||
|
}
|
||||||
|
}
|
||||||
5
examples/rejected/layer_reach/hud/index.ludic
Normal file
5
examples/rejected/layer_reach/hud/index.ludic
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
# hud/index.ludic - in layer app with no uses line: items is outside the layer, so it is refused
|
||||||
|
module hud in layer app
|
||||||
|
export function hud_lines() -> int {
|
||||||
|
return menu_title() + item_count()
|
||||||
|
}
|
||||||
5
examples/rejected/layer_reach/items/index.ludic
Normal file
5
examples/rejected/layer_reach/items/index.ludic
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
# items/index.ludic - below the layer: it uses nothing
|
||||||
|
module items uses
|
||||||
|
export function item_count() -> int {
|
||||||
|
return 3
|
||||||
|
}
|
||||||
8
examples/rejected/layer_reach/menu/index.ludic
Normal file
8
examples/rejected/layer_reach/menu/index.ludic
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
# menu/index.ludic - in layer app: it reaches hud (same layer) freely, and items through its uses
|
||||||
|
module menu in layer app uses items
|
||||||
|
export function menu_rows() -> int {
|
||||||
|
return item_count() + hud_lines()
|
||||||
|
}
|
||||||
|
export function menu_title() -> int {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
# of what it names, and every assignment to a global of another module is a write; the file lists
|
# of what it names, and every assignment to a global of another module is a write; the file lists
|
||||||
# the modules (their declared `uses`, and whether they are a package's), the edges with a count, and
|
# the modules (their declared `uses`, and whether they are a package's), the edges with a count, and
|
||||||
# the writes, one a line:
|
# the writes, one a line:
|
||||||
# module <name> <package 0|1> <uses: -|,a,b,>
|
# module <name> <package 0|1> <uses: -|,a,b,> <layer: -|name>
|
||||||
# edge <from> <to> <count> <first name>
|
# edge <from> <to> <count> <first name>
|
||||||
# write <owner> <global> <from> <file>:<line>
|
# write <owner> <global> <from> <file>:<line>
|
||||||
var g_dp_on: int = -1
|
var g_dp_on: int = -1
|
||||||
|
|
@ -78,7 +78,9 @@ function deps_flush() -> void {
|
||||||
var uses: pointer = "-"
|
var uses: pointer = "-"
|
||||||
let k = mod_find_uses(m)
|
let k = mod_find_uses(m)
|
||||||
if k >= 0 { uses = g_mu_list[k] }
|
if k >= 0 { uses = g_mu_list[k] }
|
||||||
deps_line(f, `module {m} {itoa(pkg)} {uses}`)
|
var layer = mod_layer(m)
|
||||||
|
if (layer == "") { layer = "-" }
|
||||||
|
deps_line(f, `module {m} {itoa(pkg)} {uses} {layer}`)
|
||||||
}
|
}
|
||||||
i += 1
|
i += 1
|
||||||
}
|
}
|
||||||
|
|
@ -94,7 +96,7 @@ function deps_flush() -> void {
|
||||||
}
|
}
|
||||||
if not dup and not (m == "") {
|
if not dup and not (m == "") {
|
||||||
push(seen, m)
|
push(seen, m)
|
||||||
deps_line(f, `module {m} 1 -`)
|
deps_line(f, `module {m} 1 - -`)
|
||||||
}
|
}
|
||||||
i += 1
|
i += 1
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,17 +35,44 @@ function mod_parse_names() -> pointer {
|
||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
# `module NAME [uses A, B]`
|
# a module's layer, or "": `module flow in layer app` - the modules of one layer may use each other
|
||||||
|
# (and go round) freely; everything outside the layer is held to the module's `uses`
|
||||||
|
var g_ly_mod: []pointer = new []pointer
|
||||||
|
var g_ly_name: []pointer = new []pointer
|
||||||
|
function mod_layer(m: pointer) -> pointer {
|
||||||
|
var i = 0
|
||||||
|
while i < len(g_ly_mod) {
|
||||||
|
if (g_ly_mod[i] == m) { return g_ly_name[i] }
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
function mod_set_layer(m: pointer, layer: pointer) -> void {
|
||||||
|
let was = mod_layer(m)
|
||||||
|
if (was == layer) { return }
|
||||||
|
if not (was == "") { perr(`module {m} is in layer {was}, and this line puts it in layer {layer}`) }
|
||||||
|
push(g_ly_mod, m)
|
||||||
|
push(g_ly_name, layer)
|
||||||
|
}
|
||||||
|
# `module NAME [in layer L] [uses A, B]`
|
||||||
function mod_parse_line() -> void {
|
function mod_parse_line() -> void {
|
||||||
pi += 1
|
pi += 1
|
||||||
let name = eat_id()
|
let name = eat_id()
|
||||||
module_set(g_parse_file, name)
|
module_set(g_parse_file, name)
|
||||||
if not is_id("uses") { return }
|
var layered = false
|
||||||
|
if is_id("in") and toks[pi + 1].text == "layer" {
|
||||||
|
pi += 2
|
||||||
|
mod_set_layer(name, eat_id())
|
||||||
|
layered = true
|
||||||
|
}
|
||||||
|
if not is_id("uses") and not layered { return }
|
||||||
let ln = toks[pi].line
|
let ln = toks[pi].line
|
||||||
pi += 1
|
# `uses` and nothing after it (or a layer and no uses): nothing outside the module's layer
|
||||||
# `uses` and nothing after it: a module that reaches no other module at all
|
|
||||||
var list = ","
|
var list = ","
|
||||||
if toks[pi].kind == TK_ID { list = mod_parse_names() }
|
if is_id("uses") {
|
||||||
|
pi += 1
|
||||||
|
if toks[pi].kind == TK_ID { list = mod_parse_names() }
|
||||||
|
}
|
||||||
let k = mod_find_uses(name)
|
let k = mod_find_uses(name)
|
||||||
if k >= 0 {
|
if k >= 0 {
|
||||||
g_mu_list[k] = g_mu_list[k] + list
|
g_mu_list[k] = g_mu_list[k] + list
|
||||||
|
|
@ -121,6 +148,7 @@ function module_may_use(from: pointer, to: pointer) -> bool {
|
||||||
if (from == "") or (to == "") or (from == to) { return true }
|
if (from == "") or (to == "") or (from == to) { return true }
|
||||||
let k = mod_find_uses(from)
|
let k = mod_find_uses(from)
|
||||||
if k < 0 { return true }
|
if k < 0 { return true }
|
||||||
|
if not (mod_layer(from) == "") and (mod_layer(from) == mod_layer(to)) { return true }
|
||||||
return mod_list_has(g_mu_list[k], to)
|
return mod_list_has(g_mu_list[k], to)
|
||||||
}
|
}
|
||||||
# the names in ",a,b," as a slice
|
# the names in ",a,b," as a slice
|
||||||
|
|
@ -138,18 +166,42 @@ function mod_names(list: pointer) -> []pointer {
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
# the declared uses graph has no cycle: a depth-first walk that carries its path, "a -> b -> "
|
# the declared uses graph has no cycle: a depth-first walk that carries its path, "a -> b -> "
|
||||||
var g_mu_done: pointer = "," # modules whose every path is known to end
|
var g_mu_done: pointer = "," # nodes whose every path is known to end
|
||||||
|
# the graph the cycle check walks: a layer is one node ("layer app"), since its modules may cycle
|
||||||
|
function mod_node(m: pointer) -> pointer {
|
||||||
|
let l = mod_layer(m)
|
||||||
|
if (l == "") { return m }
|
||||||
|
return `layer {l}`
|
||||||
|
}
|
||||||
|
# a node's edges, as ",a,layer b,": every uses entry of the modules in it, leaving its own layer
|
||||||
|
function mod_node_edges(nd: pointer) -> pointer {
|
||||||
|
var out = ","
|
||||||
|
var k = 0
|
||||||
|
while k < len(g_mu_mod) {
|
||||||
|
if (mod_node(g_mu_mod[k]) == nd) {
|
||||||
|
let next = mod_names(g_mu_list[k])
|
||||||
|
var n = 0
|
||||||
|
while n < len(next) {
|
||||||
|
let t = mod_node(next[n])
|
||||||
|
if not (t == nd) and not has_sub(out, `,{t},`) { out = out + t + "," }
|
||||||
|
n += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
k += 1
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
function mod_walk(m: pointer, path: pointer, first: pointer) -> void {
|
function mod_walk(m: pointer, path: pointer, first: pointer) -> void {
|
||||||
if has_sub(`-> {path}`, `-> {m} -> `) {
|
if has_sub(`-> {path}`, `-> {m} -> `) {
|
||||||
let k0 = mod_find_uses(first)
|
let k0 = mod_find_uses(first)
|
||||||
g_err_file = g_mu_file[k0]
|
g_err_file = g_mu_file[k0]
|
||||||
g_err_line = g_mu_line[k0]
|
g_err_line = g_mu_line[k0]
|
||||||
perr(`the modules' uses go round in a circle: {path}{m}; one of them has to take the other through a port`)
|
perr(`the modules' uses go round in a circle: {path}{m}; one of them has to take the other through a port, or the ones that belong together declare one layer (module m in layer L)`)
|
||||||
}
|
}
|
||||||
if has_sub(g_mu_done, `,{m},`) { return }
|
if has_sub(g_mu_done, `,{m},`) { return }
|
||||||
let k = mod_find_uses(m)
|
let edges = mod_node_edges(m)
|
||||||
if k < 0 { return }
|
if (edges == ",") { return }
|
||||||
let next = mod_names(g_mu_list[k])
|
let next = mod_names(edges)
|
||||||
var n = 0
|
var n = 0
|
||||||
while n < len(next) {
|
while n < len(next) {
|
||||||
mod_walk(next[n], `{path}{m} -> `, first)
|
mod_walk(next[n], `{path}{m} -> `, first)
|
||||||
|
|
@ -162,7 +214,7 @@ function modules_finish() -> void {
|
||||||
g_parsing = false
|
g_parsing = false
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(g_mu_mod) {
|
while i < len(g_mu_mod) {
|
||||||
mod_walk(g_mu_mod[i], "", g_mu_mod[i])
|
mod_walk(mod_node(g_mu_mod[i]), "", g_mu_mod[i])
|
||||||
i += 1
|
i += 1
|
||||||
}
|
}
|
||||||
g_parsing = saved
|
g_parsing = saved
|
||||||
|
|
|
||||||
46657
selfhost/ludicc.seed.ll
46657
selfhost/ludicc.seed.ll
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -14,12 +14,14 @@
|
||||||
# ludic deps --baseline FILE write the numbers to FILE
|
# 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
|
# 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
|
# cross_writes: assignments to a global of another module; globals_written_from_outside: those
|
||||||
# globals, each counted once.
|
# globals, each counted once.
|
||||||
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
|
||||||
|
var dp_layer: []pointer = null
|
||||||
var dp_ef: []int = null
|
var dp_ef: []int = null
|
||||||
var dp_et: []int = null
|
var dp_et: []int = null
|
||||||
var dp_ec: []int = null
|
var dp_ec: []int = null
|
||||||
|
|
@ -52,22 +54,24 @@ function dp_mod(name: pointer) -> int {
|
||||||
push(dp_mods, name)
|
push(dp_mods, name)
|
||||||
push(dp_pkg, 1)
|
push(dp_pkg, 1)
|
||||||
push(dp_uses, "-")
|
push(dp_uses, "-")
|
||||||
|
push(dp_layer, "-")
|
||||||
return len(dp_mods) - 1
|
return len(dp_mods) - 1
|
||||||
}
|
}
|
||||||
function dp_load(path: pointer) -> bool {
|
function dp_load(path: pointer) -> bool {
|
||||||
let text = read_file(path)
|
let text = read_file(path)
|
||||||
if text == null { return false }
|
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_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
|
dp_writes = 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) {
|
||||||
let w = dp_words(lines[i])
|
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])
|
let k = dp_mod(w[1])
|
||||||
dp_pkg[k] = 0
|
dp_pkg[k] = 0
|
||||||
if w[2] == "1" { dp_pkg[k] = 1 }
|
if w[2] == "1" { dp_pkg[k] = 1 }
|
||||||
dp_uses[k] = w[3]
|
dp_uses[k] = w[3]
|
||||||
|
if len(w) >= 5 { dp_layer[k] = w[4] }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for i in 0 .. len(lines) {
|
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 }
|
function dp_own(k: int) -> bool { return dp_pkg[k] == 0 }
|
||||||
# an edge the numbers count: between two of the program's own modules
|
# 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]) }
|
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 {
|
function dp_leaf(k: int) -> bool {
|
||||||
for e in 0 .. len(dp_ef) { if dp_ef[e] == k and dp_counted(e) { return false } }
|
for e in 0 .. len(dp_ef) { if dp_ef[e] == k and dp_counted(e) { return false } }
|
||||||
return true
|
return true
|
||||||
|
|
@ -103,6 +112,7 @@ var tj_stack: []int = null
|
||||||
var tj_sp: int = 0
|
var tj_sp: int = 0
|
||||||
var tj_n: int = 0
|
var tj_n: int = 0
|
||||||
var tj_best: []int = null
|
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 {
|
function tj_visit(v: int) -> void {
|
||||||
tj_index[v] = tj_n
|
tj_index[v] = tj_n
|
||||||
tj_low[v] = tj_n
|
tj_low[v] = tj_n
|
||||||
|
|
@ -111,7 +121,7 @@ function tj_visit(v: int) -> void {
|
||||||
tj_sp += 1
|
tj_sp += 1
|
||||||
tj_on[v] = 1
|
tj_on[v] = 1
|
||||||
for e in 0 .. len(dp_ef) {
|
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]
|
let w = dp_et[e]
|
||||||
if tj_index[w] < 0 {
|
if tj_index[w] < 0 {
|
||||||
tj_visit(w)
|
tj_visit(w)
|
||||||
|
|
@ -181,12 +191,16 @@ function s_less(a: pointer, b: pointer) -> bool {
|
||||||
var dp_names: []pointer = null
|
var dp_names: []pointer = null
|
||||||
var dp_vals: []int = null
|
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
|
||||||
function dp_numbers() -> void {
|
function dp_numbers() -> void {
|
||||||
dp_names = ["modules", "dependencies", "largest_cycle", "cross_writes", "globals_written_from_outside"]
|
dp_names = ["modules", "dependencies", "largest_cycle", "cross_writes", "globals_written_from_outside"]
|
||||||
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
|
||||||
for e in 0 .. len(dp_ef) { if dp_counted(e) and not dp_leaf(dp_et[e]) { deps += 1 } }
|
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()
|
dp_cycle = dp_largest_cycle()
|
||||||
var writes = 0
|
var writes = 0
|
||||||
let seen = new []pointer
|
let seen = new []pointer
|
||||||
|
|
@ -207,6 +221,11 @@ function dp_numbers() -> void {
|
||||||
function dp_print_numbers() -> void {
|
function dp_print_numbers() -> void {
|
||||||
for i in 0 .. len(dp_names) { print(`{dp_names[i]}: {string(dp_vals[i])}`) }
|
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)}`)
|
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
|
# FILE: `name value` lines, `#` comments; every number but modules may only go down
|
||||||
function dp_check(path: pointer) -> int {
|
function dp_check(path: pointer) -> int {
|
||||||
|
|
@ -246,7 +265,28 @@ function dp_baseline(path: pointer) -> int {
|
||||||
print(`deps: wrote {path}`)
|
print(`deps: wrote {path}`)
|
||||||
return 0
|
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 {
|
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]
|
let u = dp_uses[from]
|
||||||
if u == "-" { return false }
|
if u == "-" { return false }
|
||||||
return s_contains(u, `,{dp_mods[to]},`)
|
return s_contains(u, `,{dp_mods[to]},`)
|
||||||
|
|
@ -268,6 +308,7 @@ function dp_graph() -> void {
|
||||||
if dp_own(k) {
|
if dp_own(k) {
|
||||||
var uses = "(no uses line)"
|
var uses = "(no uses line)"
|
||||||
if dp_uses[k] != "-" { uses = `uses {dp_list(dp_uses[k])}` }
|
if dp_uses[k] != "-" { uses = `uses {dp_list(dp_uses[k])}` }
|
||||||
|
if dp_layer[k] != "-" { uses = `layer {dp_layer[k]}, {uses}` }
|
||||||
var to = ""
|
var to = ""
|
||||||
for e in 0 .. len(dp_ef) {
|
for e in 0 .. len(dp_ef) {
|
||||||
if dp_ef[e] == k {
|
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 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} --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 }
|
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)
|
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")
|
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)")
|
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")
|
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")
|
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)")
|
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")
|
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