What to build
Add block scoping infrastructure to the Casa compiler and implement it end-to-end for while/do/done blocks.
Add a scope_stack: List[List[str]] field to both the TypeChecker and BytecodeCompiler. When entering a while block (WhileStart), push a new empty scope frame. When a variable is first assigned inside the block (and doesn't exist in any outer scope), record it in the current scope frame. When exiting the block (WhileEnd), pop the scope frame — variables introduced in that frame are no longer visible.
The typechecker enforces visibility: accessing a variable after its scope has been popped is a compile error. The bytecode compiler maintains a parallel scope stack for correct slot resolution when variable names are reused across sibling scopes (shadowing via = x when no outer x exists).
Key design decisions:
= x inside a block mutates outer x if it exists in any enclosing scope; declares a new block-local variable only if x is not already in scope
Function::variables stays as a flat List[Variable] (all variables ever declared, for slot allocation) — scope visibility is tracked separately via the scope stack
- Local variable slots grow monotonically — no slot reuse at block exit
for loop variables are automatically covered because for desugars to while at parse time — the loop variable (= x) is assigned inside the while body and will be block-scoped
global declarations bypass block scoping entirely
- Scope starts at
WhileStart (condition area is inside the scope), so for-desugared hidden variables like __for_opt_N assigned in the condition area are accessible in the body
Acceptance criteria
Blocked by
None — can start immediately.
What to build
Add block scoping infrastructure to the Casa compiler and implement it end-to-end for
while/do/doneblocks.Add a
scope_stack: List[List[str]]field to both the TypeChecker and BytecodeCompiler. When entering awhileblock (WhileStart), push a new empty scope frame. When a variable is first assigned inside the block (and doesn't exist in any outer scope), record it in the current scope frame. When exiting the block (WhileEnd), pop the scope frame — variables introduced in that frame are no longer visible.The typechecker enforces visibility: accessing a variable after its scope has been popped is a compile error. The bytecode compiler maintains a parallel scope stack for correct slot resolution when variable names are reused across sibling scopes (shadowing via
= xwhen no outerxexists).Key design decisions:
= xinside a block mutates outerxif it exists in any enclosing scope; declares a new block-local variable only ifxis not already in scopeFunction::variablesstays as a flatList[Variable](all variables ever declared, for slot allocation) — scope visibility is tracked separately via the scope stackforloop variables are automatically covered becausefordesugars towhileat parse time — the loop variable (= x) is assigned inside the while body and will be block-scopedglobaldeclarations bypass block scoping entirelyWhileStart(condition area is inside the scope), sofor-desugared hidden variables like__for_opt_Nassigned in the condition area are accessible in the bodyAcceptance criteria
WhileStart/WhileEndwhilebody is not accessible afterdone(compile error)= xinsidewhilemutates outerxif it exists in enclosing scopeforloop iteration variable is not accessible afterdonefor-desugared hidden variables (__for_iter_N,__for_opt_N) work correctly (assigned before/inside while respectively)globaldeclarations insidewhileare unaffectedBlocked by
None — can start immediately.