Skip to main content
v0.14.0

Module Loading & Parsing

The pipeline's front door: a root source file becomes a per-file-parsed, dependency-ordered ModuleGraph before any resolution or type checking begins. Owning crate: metel-frontend. Defined in module_loader.rs (graph loading), module_paths.rs (path-root resolution), parser/ + grammar.pest (per-file parsing), and ast/ (the untyped AST the parser produces).

Model​

Loading first establishes the program boundary: modules are discovered from a root, their dependencies are ordered, and cycles are rejected before a later stage can see a partial program (module graph). Source providers make that same model work for files, embedded standard-library modules, and in-memory test programs (source provisioning).

Parsing then produces one ordinary AST vocabulary for the rest of the pipeline. Control flow is expression-shaped whether used as a block tail or statement (control-flow expressions); the grammar keeps keyword-prefix identifiers distinct from keywords (disambiguation); and interpolation lowers immediately to ordinary expressions (interpolation). No downstream stage needs a parser-only semantic special case.

Verifiable architecture claims

Owner metel-frontend unless a requirement names another.

Requirement №1​

Loading a root file yields a ModuleGraph in dependency order: a module appears only after every module it imports or re-exports, because Loader::load_module recurses into them first. A circular import is rejected with the full cycle traced through the DFS stack.

Implemented by (1)
  • load_module metel-frontend/src/pipeline/parsing/module_loader/mod.rs
Tested by (3)
Requirement №2​

Module source is read through a SourceProvider (embedded stdlib plus disk by default), and InMemorySourceProvider / MultiFileSourceProvider can substitute a virtual root without touching disk, so an LSP overlay uses the same loading path as production.

Implemented by (1)
Tested by (4)
Requirement №3​

A file parses through one PEG grammar (grammar.pest) via the sole entry point parser::parse(source, filename). Every AST node's Span carries byte offsets and resolved line/col and derives Eq/Hash, so it can key the reference-resolution side table.

Implemented by (1)
  • parse metel-frontend/src/pipeline/parsing/parser/mod.rs
Tested by (2)
Requirement №4​

Control flow has one expression-shaped representation: if, match and loop may be a block tail or an expression statement, with statement position discarding the value. An if without else is Unit-typed, and else if is a nested if in the else block.

Implemented by (1)
Tested by (3)
Requirement №5​

Keyword disambiguation is a grammar invariant: keyword matches a whole word only and ident is !keyword plus identifier characters, so letter or iffy is an identifier and a whole keyword never is. None is not special-cased by the grammar.

Implemented by (2)
  • ident metel-frontend/src/grammar.pest
  • keyword metel-frontend/src/grammar.pest
Tested by (2)
Requirement №6​

String interpolation is lowered while parsing: each hole becomes a to_string method call and the segments are joined with BinOp::Add in a balanced tree, so downstream passes see no interpolation node and no deeply left-nested chain.

Implemented by (1)
Tested by (2)

Known limitations​

No active limitations are currently recorded for this section. This is a current inventory, not a claim of complete coverage.

Resolved (2)
resolvedLIMIT-DECLARATIONS-001 — record declarations are not implemented. The record declaration kind (RFC-0120) does not parse; only struct does. Open record ↗resolvedLIMIT-PARSING-001 — The dynamic array type's RFC-0171 spelling ([T]) does not parse yet. RFC-0171 moves the dynamic array type from postfix T[] to prefix [T]; the grammar has not migrated, so only T[] parses. Open record ↗