Expand description
Multi-file loader: resolves import "./...", import "../...", and
import "/abs/..." statements relative to the importer, recursively
parses, and produces a single Program with all stages merged.
Names that are local to an imported file are mangled with a
per-file-path prefix, so the same module imported via multiple
aliases (or from multiple parents in a diamond shape) collapses to
one set of mangled names — same SigId, same nominal identity.
Stdlib imports (import "std.foo" as bar) pass through unchanged.
§Mangling
Each loaded file gets a prefix derived from its filesystem path.
The entry file’s prefix is empty (so lex run main.lex process
works unchanged). Imported files use <stem>_<hash> where hash
is the first 8 hex chars of SHA-256 of the file’s mangling key.
The hash disambiguates same-stem files in different directories
without forcing a project manifest.
The mangling key is the canonical absolute path by default, and the
path relative to a caller-supplied root when loading through
load_program_with_root. Absolute paths are only stable as long
as the tree stays put, which makes them unusable for anything that
loads the same logical package from a fresh directory each time: a
server unpacking an uploaded package into a per-request temp dir got
a different prefix — and therefore a brand-new set of function names
— for every file reached through a local import on every single
request, so byte-identical republishes diffed as all-new functions
and grew the branch’s function set without bound (#826). Pass the
package root and the key becomes src/error.lex, identical across
requests. Files outside the root keep the absolute-path key (a
dependency in the shared package cache lives at a stable absolute
path of its own, and “relative to this package” says nothing useful
about it).
Within a file at prefix P:
fn foodeclared in this file becomes<P>.foo(justfooat root).type Tdeclared in this file becomes<P>.T.- References to a locally-declared name get mangled, unless the name is shadowed by a binder (let, fn param, lambda param, or pattern binder) in scope.
m.foowheremis a path-import alias is rewritten to the imported file’s prefix-qualified name. Two parents importing the same file see the same prefix → calls and types unify.m.foowheremis a stdlib alias is unchanged.
Variant constructors are not mangled — they live in a global namespace, and a collision between two imported types’ constructors surfaces later as a type-check error. Same for record field names.
§Diamond imports
main.lex imports ./left and ./right, both of which import
./shared. shared.lex is parsed once per resolution, but its
mangled items are merged into the output exactly once (subsequent
loads from the same canonical path return an empty Program). This
is what makes s.build_report(...) and v.read_score(...) agree
on Report’s nominal identity.
§Limitations (tracked separately)
The mangling key is a filesystem path (see above). Moving a file
changes its SigId; renaming changes the file-stem half of the
prefix. A root-relative key narrows this to moves within the
package, but does not remove it. The eventual fix —
content-addressed identity decoupled from filesystem layout — lives
with store-native imports
(import "stage:..."); see the corresponding follow-up tracker.
Enums§
Functions§
- load_
program - Load a multi-file Lex program, expanding local imports relative to
the entry path. Stdlib imports (
std.*) pass through unchanged. - load_
program_ from_ str - Load a Lex program from a string source. Local-path imports are rejected up-front since there’s no base path to resolve from.
- load_
program_ with_ root - Load a multi-file Lex program like
load_program, but derive mangling prefixes from each file’s path relative torootinstead of its absolute path.