v5 Parsing: modules, imports, and member access
Lesson, slides, and applied problem sets.
View SlidesLesson
v5 Parsing: modules, imports, exports, and member access
v5 introduces compilation-unit structure. Before this stage, every token stream is a plain statement sequence. In v5, the parser must decide between:
- explicit multi-module files (
moduleblocks), or - implicit
module mainfor files without a leadingmodule.
This decision happens at parse start and is never flipped later.
New grammar
program -> moduleDecl* | stmts (implicit main)
moduleDecl -> "module" IDENT "{" moduleItem* "}"
moduleItem -> importStmt | exportDecl | stmt
importStmt -> "import" IDENT ("as" IDENT)? ";"
exportDecl -> "export" (fnDecl | varDecl)
postfix -> primary (call | index | member)*
member -> "." IDENT
stmt -> varDecl
| ifStmt
| whileStmt
| forStmt
| breakStmt
| continueStmt
| returnStmt
| block
| fnDecl
| assignOrExpr
ifStmt -> "if" "(" expr ")" block ("else" block)?
forStmt -> "for" "(" forInit? ";" expr? ";" forPost? ")" block
forInit -> varDeclNoSemi | assignOrExprNoSemi
forPost -> assignOrExprNoSemi
Parse-mode contract (important)
1) Parse entrypoint rule:
- If first token is
TokenModule, parse repeatedmoduleDecluntil EOF. - Otherwise, parse all statements and wrap in one module named
main.
2) Module mode implications:
- Every top-level item is processed through
moduleItem. - Imports and exports are only recognized in this mode.
parseExportis only reached from module items, not from plain statement mode.
3) Implicit-main mode implication:
exportandimportare no longer meaningful syntax in practice because onlyparseStmtis active. Unrecognized top-level tokens should surface as normal parse errors.
This boundary is where many “why did parser accept/reject this file” bugs hide.
Module and import semantics in the parser
moduleDeclrequires:modulekeyword- module name identifier
- opening
{ - zero or more module items
- closing
}
importsyntax:import name;and alias default =nameimport name as alias;for explicit rename- required trailing semicolon for each import declaration
- Empty module bodies are legal when only braces close immediately.
Subtle edge: once in module mode, the parser stays in module mode until EOF, so mixing module ... and implicit-main statements in one file is rejected at token-level by grammar mismatch.
Export declaration contract
export applies only to declarations, and only where module items are expected.
- valid forms:
export fn ...export let|var|const ...
- invalid forms should fail as:
expected declaration after export
No other exported node kind exists in v5.
Postfix chain as parser strategy
a.b()[0].c(1) is parsed by:
1) parsing a primary (a) 2) repeatedly consuming one of: (), [], . 3) each step producing a new expression node (ExprCall, ExprIndex, ExprMember)
The chain is therefore left-to-right by construction.
Chain details:
- member access uses
ExprMemberwithLeft+Value= identifier string. a.(...)is illegal because after.an identifier is mandatory.- this stage treats member access as syntactic shape only; name resolution is a later stage.
Assignment target rule in v5
parseAssignOrExpr accepts assignment only when the left side is an L-value:
ExprIdentorExprIndexare validExprMemberis not a valid target in v5 parser
So:
x = 1-> validx[0] = 1-> validx.y = 1-> rejected asinvalid assignment target
This is intentional: member writes become a later semantic/runtime concern.
Practical diagnostics to preserve
Keep these parse errors stable where possible:
- expected
module name - expected
'{' after module name/expected '}' after module body - expected
';' after import - expected declaration after
export - expected member name after
. - expected
';'after assignment or expression
Also keep early EOF detection strict: parse should not silently accept trailing garbage once EOF conditions are mismatched.
Deep practice checkpoints (almost solved)
1) Explicit module dispatch
Input tokens start with module and include two module blocks.
Expected:
- parse mode remains module mode for entire input
- both blocks become distinct entries in
Program.Modules
2) Import aliasing
Parse sequence:
import core as rt;
Expected:
- one import entry
Name == "core",Alias == "rt"
3) Export gate in module mode
Parse:
export fn f() {}
Expected:
StmtExportedtrue on that function- function node otherwise structurally valid
4) Export restriction
Parse:
export if (x) {}
Expected:
- parse error: expected declaration after export
5) Member/access chains
Parse expression:
m.add(1)[0].name;
Expected:
- postfix chain built left-to-right
- ending node is
ExprMember - child path includes call then index then member
6) Invalid member read as target
Parse:
obj.field = 1;
Expected:
- parse error
invalid assignment target
7) Valid index assignment target
Parse:
arr[0] = 1;
Expected:
- assignment target accepted
- target node kind
ExprIndex
Module Items
Program Parser v5