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 or load_package. 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).
load_package adds a namespace ahead of the relative path
(lex-schema/src/error.lex), because a relative key is only unique
within one package: two packages published into one branch can both
have a src/error.lex, and without the namespace both get the same
error_<hash>.format.
§Whole-package loading
load_program and load_program_with_root each flatten one
entry’s entire local-import closure into that entry’s program, which
is what lex run/lex check want for a single file. A caller holding
every file of a package — a publish server, say — gets each shared
dependency back once per importer instead: 2,239 declarations for 693
distinct names on a real 21-file package whose error.lex 17 files
import (#828). load_package is the whole-package entry point: one
shared pass, every file exactly once, and every file mangled (no
unmangled entry), since bare names from different files would collide
in one program.
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, and under load_package that applies to every
declaration, not only imported ones — a function moved between two
files of a package is a new function there. 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.
Structs§
- Loaded
Package - A package loaded as one unit by
load_package.
Enums§
Functions§
- load_
package - Load a whole package as one program: every file gets its path-derived mangling prefix (no file is the unmangled “entry”), and each file’s declarations appear exactly once however many other files import it.
- 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.