ludic/selfhost/backend/emit_vis.ludic
Orkuncakilkaya e738741521 feat(cli): ludic deps - the module graph as the compiler resolved it
With LUDIC_DEPS=<file> the compiler records every reference the
visibility pass resolves (from module, to module) and every assignment
to another module's global. ludic deps prints the modules,
dependencies, largest cycle, cross writes and globals written from
outside, with --graph, --dot, --writes, --uses MOD, --check FILE and
--baseline FILE. Reseed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 11:16:34 +03:00

59 lines
2.6 KiB
Text

# emit_vis.ludic — L3: module scope. A file belongs to the module its own `module NAME` line or
# its importer's names (frontend/parse.ludic, module_of); a file in no module is public. A
# reference from one module to another module's function, global, record or event needs that
# declaration `export`ed, and - when the referring module says `uses` - that module (or, for a
# package with no module line, the package) named there (frontend/modules.ludic), unless the
# referring module is a friend of it (a test harness). The engine's runtime needs no naming.
# LUDIC_VIS_REPORT=1 prints every violation as `vis: <file>: <module>.<name>` (a private name) or
# `uses: <file>: ...` (a module not in the uses list) and goes on, so a tool can add the exports
# and the uses a codebase needs before the rule is switched on for it.
var g_vis_report: int = -1
var g_vis_off: bool = false # a port's binding: checked where it is written (ports.ludic)
function vis_reporting() -> bool {
if g_vis_report < 0 {
g_vis_report = 0
if getenv("LUDIC_VIS_REPORT") != null { g_vis_report = 1 }
}
return g_vis_report == 1
}
# `what` is the name as written at the reference
# a generic function's instance is named for its type arguments (grow$int); a message names grow
function vis_plain(what: pointer) -> pointer {
var i = 0
while i < len(what) {
if what[i] == '$' { return what[0 .. i] }
i += 1
}
return what
}
function vis_say(m: pointer) -> void {
file_write(file_stderr(), m, len(m))
}
function vis_check(d: Node, what0: pointer) -> void {
if d == null { return }
port_check_bound(d)
deps_edge(d, what0) # LUDIC_DEPS: the graph ludic deps reads (emit_deps.ludic)
if g_vis_off { return }
if d.file == null { return }
let what = vis_plain(what0)
var here = g_err_file
if here == null { return }
let from = module_of(here)
let uto = module_for_uses(d.file)
if not (uto == "") and not (from == uto) and not module_friend_of(from, uto) and not module_may_use(from, uto) {
if vis_reporting() {
vis_say(`uses: {d.file}:{itoa(d.line)}: {uto}.{what} used from {here} (module {from})\n`)
} else {
perr(`{from} uses {uto}.{what} ({d.file}:{itoa(d.line)}): add 'uses {uto}' to {from}'s module line, or take it through a port`)
}
}
let to = module_of(d.file)
if (to == "") or (from == to) { return }
if module_friend_of(from, to) { return }
if d.vis == 1 { return }
if vis_reporting() {
vis_say(`vis: {d.file}:{itoa(d.line)}: {to}.{what} used from {here}\n`)
return
}
perr(`{what} is private to module {to}; mark it 'export' where it is declared ({d.file})`)
}