Implement the following plan:
Fix Parser Infinite Loop & Support Course Library Transpilation
Context
When transpiling programs that #INCLUDE "course.module", the parser enters an infinite loop causing OOM. The root cause is that occam allows multiple declarations (specifications) scoped before a process in IF/CASE/ALT/WHILE branch bodies, but the parser only calls parseStatement() once per body. After parsing the first declaration, it tries to interpret subsequent declarations as new branch conditions, looping forever.
A secondary issue is that channel direction annotations at call sites (out!, in?) aren't handled in expression parsing, causing the first parse error at line 188 of the preprocessed output.
Changes
1. AST: Change Body from Statement to []Statement (ast/ast.go)
Change in four structs:
- IfChoice.Body (line ~207)
- CaseChoice.Body (line ~223)
- AltCase.Body (line ~397)
- WhileLoop.Body (line ~192)
2. Parser: Add parseBodyStatements() helper (parser/parser.go)
Add a new function modeled on parseBlockStatements() (line 1649) that:
- Is called after the caller has consumed the INDENT token
- Loops parsing statements at the current indent level until DEDENT
- Includes a "no progress" safety guard to prevent infinite loops
- Returns []ast.Statement
Update four call sites to use it:
- parseIfStatement() (line ~2225): replace parseStatement() with parseBodyStatements()
- parseCaseStatement() (line ~2305): same
- parseWhileLoop() (line ~2148): same
- parseAltCase() (line ~1635): same
3. Parser: Handle channel direction at call sites (parser/parser.go)
In parseProcCall() (line 1893): after each parseExpression(LOWEST) for an argument, check if peekToken is SEND (!) or RECEIVE (?) and consume it. The direction annotation is informational — in Go codegen we just pass the channel.
4. Codegen: Update to iterate over body slices (codegen/codegen.go)
Generators — change g.generateStatement(x.Body) to loop:
- generateWhileLoop() (line 1503)
- generateIfStatement() (lines 1551, 1574)
- generateCaseStatement() (line 1605)
- generateAltBlock() (line 1252)
Scanner functions — change containsX(x.Body) to loop over slice:
- containsPar() — AltCase (198), WhileLoop (215), IfChoice (220), CaseChoice (226)
- containsPrint() — same pattern at ~258, 275, 280, 286
- containsTimer() — same pattern at ~309, 338, 343, 349
- containsStop() — same pattern at ~381, 398, 403, 409
- containsMostExpr() — same pattern at ~468, 476, 490, 511
- collectChanProtocols() — same pattern at ~901, 906, 912, 918
- collectRecordVars() — same pattern at ~957, 962, 968, 974
Note: VariantCase.Body has the same single-statement pattern but is left for a follow-up.
5. Tests
- Update existing parser tests that access
.Body (change nil checks to len() checks, access Body[0] instead of Body)
- Add parser test for multi-statement IF body
- Add parser test for channel direction at call site
- Add e2e tests for both features
6. Documentation
Update TODO.md to note these features are supported.
Implementation Order
- AST changes (will cause compile errors)
- Parser changes (fixes parser compile errors)
- Codegen changes (fixes codegen compile errors)
- Test updates + new tests
go test ./... to verify
- Try transpiling course library demo
Verification
go test ./... # all existing tests pass
go build -o occam2go .
./occam2go gen-module -o kroc/modules/course/libsrc/course.module kroc/modules/course/libsrc/SConscript
./occam2go -I kroc/modules/course/libsrc kroc/demos/complexity/concurrent_hello_1.occ
Note: The full course library transpilation may surface additional unsupported constructs (e.g., RETYPES in float_io.occ). Those would be addressed as follow-up work. The immediate goal is fixing the infinite loop and the two feature gaps that block parsing.
If you need specific details from before exiting plan mode (like exact code snippets, error messages, or content you generated), read the full transcript at: /home/david/.claude/projects/-home-david-projects-code-associates-occam2go/c48afd9c-f64c-4ecf-931b-d6fbb0089261.jsonl