# modules.ludic — L3 the module graph. `module fishing uses base, data` says which other modules # fishing may reach at all: a reference from fishing into a module it does not name is refused # (backend/emit_vis.ludic), exported or not, unless fishing is a friend of that module. A module # that says no `uses` keeps the old rule (anything exported), so the rule comes in one module at a # time. A package's module counts like any other (ludic_base, ludic_ui, ludic_render3d must be # named); only the engine's own runtime, which is in no module, needs no naming. The declared # graph may not have a cycle. # # `friend module lab` sees every module's private names; `friend module lab of fishing, data` # sees only those modules'. var g_mu_mod: []pointer = new []pointer # a module that said `uses` var g_mu_list: []pointer = new []pointer # ",a,b," - what it may use (union of its lines) var g_mu_file: []pointer = new []pointer # where it said so first var g_mu_line: []int = new []int var g_fr_scope: []pointer = new []pointer # per g_mod_friends entry: "" for all, else ",a,b," function mod_list_has(list: pointer, name: pointer) -> bool { return has_sub(list, `,{name},`) } function mod_find_uses(m: pointer) -> int { var i = 0 while i < len(g_mu_mod) { if (g_mu_mod[i] == m) { return i } i += 1 } return -1 } # `A, B, C` after `uses` / `of`, as ",A,B,C," function mod_parse_names() -> pointer { var out = "," out = out + eat_id() + "," while is_op(",") { pi += 1 out = out + eat_id() + "," } return out } # 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 { pi += 1 let name = eat_id() module_set(g_parse_file, name) 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 # `uses` and nothing after it (or a layer and no uses): nothing outside the module's layer var list = "," if is_id("uses") { pi += 1 if toks[pi].kind == TK_ID { list = mod_parse_names() } } let k = mod_find_uses(name) if k >= 0 { g_mu_list[k] = g_mu_list[k] + list return } push(g_mu_mod, name) push(g_mu_list, list) push(g_mu_file, g_parse_file) push(g_mu_line, ln) } # `friend module NAME [of A, B]` function mod_parse_friend() -> void { pi += 2 let fm = eat_id() module_set(g_parse_file, fm) var scope = "" if is_id("of") { pi += 1 scope = mod_parse_names() } push(g_mod_friends, fm) push(g_fr_scope, scope) } # may `from` see `to`'s private names? function module_friend_of(from: pointer, to: pointer) -> bool { var i = 0 while i < len(g_mod_friends) { if (g_mod_friends[i] == from) { if (g_fr_scope[i] == "") or mod_list_has(g_fr_scope[i], to) { return true } } i += 1 } return false } # the package each of a package's files came from, as a module name: `ludic.render3d/r3d.ludic` is # ludic_render3d. A package with no `module` line of its own is public to the export rule, but a # module that says `uses` still has to name it - a mechanic reaching into the renderer is caught. var g_pk_file: []pointer = new []pointer var g_pk_name: []pointer = new []pointer function pkg_name_of(rel: pointer) -> pointer { var n = 0 while n < len(rel) and rel[n] != '/' { n += 1 } let first = rel[0 .. n] var out = "" var i = 0 while i < len(first) { let c = first[i] if c == '.' or c == '-' { out = out + "_" } else { out = out + first[i .. i + 1] } i += 1 } return out } function pkg_file_set(f: pointer, name: pointer) -> void { push(g_pk_file, f) push(g_pk_name, name) fm_put(g_fm_pkg, f, name) } function pkg_of_file(f: pointer) -> pointer { return fm_get(g_fm_pkg, f) } # the module a declaration is in, for the uses rule: its own, else its package's function module_for_uses(f: pointer) -> pointer { let m = module_of(f) if not (m == "") { return m } return pkg_of_file(f) } # may module `from` reach into module `to` at all? (L3 uses; the export rule is separate) function module_may_use(from: pointer, to: pointer) -> bool { if (from == "") or (to == "") or (from == to) { return true } let k = mod_find_uses(from) 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) } # the names in ",a,b," as a slice function mod_names(list: pointer) -> []pointer { let out = new []pointer var a = 1 var j = 1 while j < len(list) { if list[j] == ',' { if j > a { push(out, list[a .. j]) } a = j + 1 } j += 1 } return out } # the declared uses graph has no cycle: a depth-first walk that carries its path, "a -> b -> " 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 { if has_sub(`-> {path}`, `-> {m} -> `) { let k0 = mod_find_uses(first) g_err_file = g_mu_file[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, or the ones that belong together declare one layer (module m in layer L)`) } if has_sub(g_mu_done, `,{m},`) { return } let edges = mod_node_edges(m) if (edges == ",") { return } let next = mod_names(edges) var n = 0 while n < len(next) { mod_walk(next[n], `{path}{m} -> `, first) n += 1 } g_mu_done = g_mu_done + m + "," } function modules_finish() -> void { let saved = g_parsing g_parsing = false var i = 0 while i < len(g_mu_mod) { mod_walk(mod_node(g_mu_mod[i]), "", g_mu_mod[i]) i += 1 } g_parsing = saved }