v5 Modules: imports, exports, and a linker mindset

Lesson, slides, and applied problem sets.

View Slides

Lesson

v5 Modules: imports, exports, and a linker mindset

Why modules are the scale point

Before modules, names live in one mostly global pool. With modules, names are partitioned by explicit ownership and visibility. That enables:

  • composition without collisions
  • clean APIs (export)
  • deterministic linking

1) Public surface vs private body

module NAME { ... } defines a unit with two parts:

  • public surface: exported declarations
  • private implementation: non-exported declarations

Private names intentionally cannot be accessed outside module scope.

This is not just style; it changes the language contract and analyzer obligations.


2) Import forms and alias model

Supported forms:

  • import math;
  • import math as alias;

Alias rules:

  • explicit alias replaces default identifier
  • alias lives in importing module scope as immutable binding in this pack
  • duplicates in same scope are errors

An import does not clone symbols; it creates a module handle and visibility edge.


3) Qualified access contract

Only module access form is valid:

alias.name

This requires:

  1. alias resolution
  2. module existence
  3. export membership of name

If name is not exported, runtime never sees it through this module contract.

math.add is parsed as member-like access, then resolved to map/index semantics by compiler.


4) Entry module rule

main is the entry module and must be final.

This keeps linking simple in this stage:

  • dependency checks only need one pass
  • initialization order is deterministic
  • final execution entry is stable

5) Why modules and linker are coupled

Without stable module boundaries, linker cannot:

  • know import graph
  • patch function closures deterministically
  • compose MAIN sections in correct order

This is why module ordering, exports, and imports are enforced together.


6) Common pitfalls

  • relying on hidden globals across module boundaries
  • using non-exported symbol via alias
  • importing a module declared later (forward import)
  • duplicate module names
  • assuming import aliases carry runtime-only behavior

Each has a clear static diagnostic in this pack.


7) Practice checkpoints (almost solved)

1) Entry rule + module main {} in middle and another module later should fail.

2) Unknown module import + module main { import missing; } should fail with unknown module diagnostic.

3) Export requirement + module util { let hidden = 1; export let public = 2; } then main accessing util.hidden should fail; util.public should pass.

4) Alias reuse + import math as m; import util as m; should fail as redeclare collision.

5) default alias + import util; binds alias util.

6) Map lowerings + util.add lowers to module map lookup in compiled form, preserving no new VM opcode requirement.

Module compilation is where language structure becomes API contract.


Module Items

  • Program Lexer v5

    Lex the v5 language with module keywords and member access.

    medium Sign in to access medium and hard problems
Join Discord