Semantic Analysis: Names, Scopes, and Rules
- Trace lexical scope and declared-before-use lookup.
- Register a function before its own body without hoisting later declarations.
- Classify name/arity errors separately from dynamic runtime types.
1 / 6
Analyze let x=1; { let x=2; x=3; } x=4;:
| Visit | Scope stack, outer first | Lookup/update target |
|---|---|---|
| outer declaration | {x:var} |
Introduce outer x |
| enter block | {x:var}, {} |
New current scope |
| inner declaration | {x:var}, {x:var} |
Shadowing is legal |
| inner assignment | same | Nearest x is inner |
| exit block | {x:var} |
Inner binding disappears |
| final assignment | same | Nearest x is outer |
Duplicates are checked only in the current scope; lookup searches outward.
An initializer is analyzed before its new name is inserted. Therefore
let x=x; fails when no outer x exists, while an inner let x=x; can refer to
an already declared outer x.
An implementation registers every function before visiting any statements. It accepts later(); fn later(){ return 1; }. Explain why this is wrong for this course even though another language could permit it.
Construct one legal shadowing example, one same-scope duplicate, and one recursive function. Write the scope snapshot at the first disputed identifier. Add a wrong-arity call without changing its otherwise valid function body.
Carry a semantically accepted Program into compilation; do not assume acceptance means every runtime arithmetic operation will succeed.