Source loading and modules
Before func can be a keyword, it is four bytes from a file. The source stage proves those bytes are safe UTF-8, gives the file an identity, and remembers enough position information to point back to a bad character later.
In the running example: this stage reads journey.luc and any imports. It does not know what clamp_double means. Its output is trusted text plus a path and coordinate system.
Preparing one source file
- Bound the inputReject oversized or binary-looking input before later stages allocate structures proportional to it.
- Validate encodingAccept valid UTF-8, ignore a leading UTF-8 BOM, and reject NUL or unsupported encodings.
- Normalize line endingsSupported CRLF becomes LF; a stray carriage return is diagnosed rather than guessed around.
- Register the fileOwn its bytes, assign a source identity, and build line indexes used by every later diagnostic and trace.
The result is not merely a string. It is text with an identity and a coordinate system. Later stages keep byte spans; the source registry turns those spans into path, line, and column only when a human-facing report needs them.
Loading the module graph
import std.mathimport model
Embedded source, reached only through the reserved std. namespace.
Loaded by the host-provided loader under the project root identity.
A module is loaded once. Cycles between modules are allowed because Luce has no module initialization phase; a semantic cycle that implies an impossible value layout or recursive constant is rejected later by the stage that understands that meaning.
The host supplies files; the compiler supplies meaning
The compiler asks a compile.Loader for source by logical module request. It never opens a path itself. The command-line app can implement that request with a filesystem and manifest; a test can answer from memory; an editor can compile a buffer that has never been saved.
Why this matters: the same language compiler runs in every host context without smuggling filesystem policy into src/luce/. Loading failures still point at the import that requested them because request and source identities live in this stage.
Positions survive every transformation
Tokens carry source spans. AST nodes combine them. HIR records the span of the checked operation. MIR origins freeze source, line, and column onto emitted instructions. Debug artifacts retain those origins for traps; release artifacts may strip line detail while keeping function names.
Decisions kept here
| Question | Answer | Reason |
|---|---|---|
| Who opens files? | The host loader | The language module stays host-independent. |
| Who normalizes CRLF? | Source | Every later stage receives one representation. |
| Can imports initialize in order? | No initialization phase exists | Module graph cycles are therefore structurally safe. |
| Can two names load one module twice? | No | Opaque root identity and exact resolution prevent accidental duplicate worlds. |