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:
Orkun ÇAKILKAYA 2026-09-25 12:54:15 +03:00
parent e3219f17dd
commit 7b8c134c21
20 changed files with 47359 additions and 46193 deletions

View file

@ -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
View 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.

View 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
}

View file

@ -0,0 +1,5 @@
# items/index.ludic - below the layer: it uses nothing
module items uses
export function item_count() -> int {
return 3
}

View 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
}

View 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())
}
}

View 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())
}
}

View 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
}

View 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
}

View 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
}

View 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())
}
}

View 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()
}

View file

@ -0,0 +1,5 @@
# items/index.ludic - below the layer: it uses nothing
module items uses
export function item_count() -> int {
return 3
}

View 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
}

View file

@ -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
} }

View file

@ -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 is_id("uses") {
pi += 1
if toks[pi].kind == TK_ID { list = mod_parse_names() } 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

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -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 {

View file

@ -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")