Luce / engineering
Learn Luce LuciaOS

Source loading and modules

Before func can be a keyword, it is four bytes from a file. The source stage validates UTF-8, gives the file an identity, and records enough position information to point back to a bad character later.

In the running example: this stage reads journey.luc and its imports. Its output is validated text plus a path and coordinate system. Name resolution begins after parsing.

Preparing one source file

  1. Bound the inputReject oversized or binary-looking input before later stages allocate structures proportional to it.
  2. Validate encodingAccept valid UTF-8, ignore a leading UTF-8 BOM, and reject NUL or unsupported encodings.
  3. Normalize line endingsSupported CRLF becomes LF; a stray carriage return produces a source diagnostic.
  4. Register the fileOwn its bytes, assign a source identity, and build line indexes used by every later diagnostic and trace.

The result combines text, source identity, and a coordinate system. Later stages keep compact byte spans; the source registry converts a span into path, line, and column when it builds a diagnostic or trace.

Loading the module graph

Breadth-first module loadingStandard modules and project modules share one registry
root: main.luc

import std.math
import model

std.math

Embedded source, reached only through the reserved std. namespace.

model.luc

Loaded by the host-provided loader under the project root identity.

A module is loaded once. Imports declare dependencies, while execution begins from the selected entry function. That model allows module graph cycles. Semantic analysis later diagnoses cycles that imply an infinite value layout or recursive constant.

The host supplies files; the compiler supplies meaning

The compiler asks a compile.Loader for source by logical module request. The command-line app answers from a filesystem and manifest, a test can answer from memory, and an editor can answer from its current buffer.

Why this matters: filesystem policy stays in the host application, so the language compiler can serve command-line, test, and editor callers. Request and source identities also let a loading error point to the import that caused it.

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.

byte 148source registry
token spanlexer
node spanparser / HIR
originMIR / trace

Decisions kept here

QuestionAnswerReason
Who opens files?The host loaderThe language module stays host-independent.
Who normalizes CRLF?SourceEvery later stage receives one representation.
When does module code run?From a called functionImporting registers declarations; execution begins when the entry path calls one.
How is one module recognized?Opaque root identity plus resolved pathDifferent import spellings converge on the same loaded module.