COMPILER DESIGN / 3. SEMANTIC ANALYSIS
Semantic Analysis — Type Checking & Scope
Ensuring the program makes sense — type systems, symbol tables, and scope resolution
EXPLANATION
Semantic analysis checks meaning beyond syntax. The parser ensures the program is grammatically correct. The semantic analyzer ensures it makes logical sense.
"x + y" is syntactically valid. But if x is a string and y is an integer, it may be semantically invalid (in statically-typed languages). If z is used but never declared — semantic error.
Symbol Table:
The central data structure of semantic analysis. Maps identifiers to their attributes:
- Name → type, scope level, memory location, whether initialized
- Built during parsing/semantic analysis, used throughout compilation
- Implemented as a hash table or linked list of scopes
Scope:
- Block scope: each { } introduces a new scope
- Nested scopes form a scope chain: inner scopes can see outer scope names
- Name resolution: look up identifier in current scope, then enclosing scopes outward
- Shadowing: inner declaration hides outer one with same name
Scope implementation — scope stack:
- Enter scope: push new hash table onto stack
- Declare variable: insert into top table
- Look up variable: search from top of stack downward
- Exit scope: pop top table (all local variables "disappear")
Type Checking:
- Static typing (C, Java, Rust): types checked at COMPILE TIME. Type errors = compile errors. Better performance, earlier bug detection.
- Dynamic typing (Python, JS): types checked at RUNTIME. Type errors = runtime exceptions. More flexible.
- Strong typing: implicit type conversion not allowed (Python: "3" + 3 is an error)
- Weak typing: implicit coercion allowed (JS: "3" + 3 = "33")
Type inference (Hindley-Milner):
Modern languages (Rust, Haskell, TypeScript, Python with mypy) can infer types without explicit annotations. Unification algorithm propagates type constraints. "let x = 5" → x inferred as int.
Attribute Grammar:
Formal framework for semantic analysis. Each grammar symbol has ATTRIBUTES (type, value, etc). Semantic RULES compute attribute values as the parse tree is traversed.
Synthesized attributes: computed from children (bottom-up). Type of expression = type of subexpressions.
Inherited attributes: passed from parent/siblings (top-down). Scope environment passed down.
Common semantic errors:
- Undeclared variable: use before declaration
- Type mismatch: incompatible types in operation
- Wrong number of arguments to function
- Return type mismatch
- Duplicate declaration in same scope
- Use of uninitialized variableDIAGRAM
SCOPE ANALYSIS example:
int x = 10; // scope 0: x→int
{
int y = x + 1; // scope 1: y→int, x found in scope 0
{
float x = 3.14; // scope 2: x→float (SHADOWS outer x)
y = x + 1; // x → float (from scope 2), y → int
} // scope 2 popped: x→float gone
// x here = 10 again (scope 0)
} // scope 1 popped: y gone
SCOPE STACK:
After "float x = 3.14":
Top → [scope 2: x→float]
[scope 1: y→int ]
[scope 0: x→int ] ← global
TYPE CHECKING rules:
E1 : int, E2 : int → E1 + E2 : int ✓
E1 : int, E2 : float → E1 + E2 : float (implicit widening)
E1 : int, E2 : string → E1 + E2 : ERROR ✗ (in Java/C)
E1 : string, E2 : string → E1 + E2 : string ✓
SYMBOL TABLE entry for "int factorial(int n)":
Name: factorial
Kind: function
Return type: int
Parameters: [(n, int)]
Scope level: 0 (global)
Defined at: line 1CODE