v5 Modules: imports, exports, and a linker mindset
Work through examples, try the reasoning yourself, then put it into practice.
View SlidesBefore you start
- Complete the v4 integration gate and understand BC2 function IDs.
What you will practice
- Explain imports/exports as dependency and visibility contracts.
- Trace ordered module initialization without assuming source concatenation.
- Separate v5 lexical syntax from later static type annotations.
In this lesson
Lesson
3 min readv5 Modules: imports, exports, and a linker mindset
v5 introduces module, import, export, as, and member-access dot tokens. Its
lexer otherwise carries forward v4. Type annotations and arrow tokens belong
to v6: export fn add(a,b){...} is the v5 function syntax.
Worked reconstruction
module util {
export fn add(a,b){ return a+b; }
let hidden=99;
}
module main {
import util as u;
let result=u.add(2,3);
}
The parser creates two Modules, not one flattened statement list. util has one export and an internal binding. main records imported module name util with local alias u. The analyzer checks module order and export visibility. Each module compiles to its own initializer and locally numbered function table. The linker combines those artifacts; the VM runs util's initializer before main's so main can obtain the export map.
The module name identifies a compiled dependency; the alias is a binding local to the importing module. Renaming alias u to tools need not rename util or its function IDs. hidden exists inside util but cannot be accessed as u.hidden. These visibility rules are a language convention, not a security sandbox.
Ordered dependencies are a deliberate simplification
Every import must name an earlier module in the source order. main must exist and be last. This supplies a simple dependency order without a general graph solver or cyclic initialization protocol. An unused forward import is still invalid; absence of runtime use does not erase the dependency contract.
The compiler emits one export map per module at the end of initialization. Exported primitive values are values placed into that map, not automatically live binding cells. A closure export still captures its defining environment. This implementation differs from languages whose imports are live bindings.
A new compilation boundary
Two independently compiled modules can both have local function ID 0 and jump address 5. Combining code requires relocation in two coordinate spaces; merely concatenating source or replacing names does not solve it. The later linker module derives both offsets explicitly.
Debugging lab
A learner changes import util as u to import util as tools and also renames util’s compiled module to tools. Explain why an alias change should not alter dependency identity.
Compare your diagnosis after attempting the lab
util remains the referenced module name; tools is only the local alias bound to util’s exports in this importer. Changing dependency identity can make lookup fail or connect a different module.
Transfer task
Draw the dependency and alias tables for modules base, middle importing base as b, and main importing middle as m. Mark an internal symbol that must remain inaccessible. Then tokenize an exported untyped v5 function.
Carry forward
Carry separate module identity, alias, and export lists into v5 parsing. Preserve earlier single-program tests through implicit main while adding explicit multi-module artifacts.
Build program-lexer-v5. Keep a before/after trace of one failing test. Use the checkpoint to diagnose specific skills; passing it does not replace implementing and testing the artifact.
Practice sequence
Use the order below for a guided path, or choose an item to revisit. A pass records a successful attempt; try a fresh example later to check what you can reconstruct.