Semantic Analysis and Symbol Tables With easy examples.
Semantic analysis is the compiler phase that checks whether a syntactically valid program actually makes sense. The parser accepts "colorless green ideas sleep furiously" as a grammatical English sentence; the semantic analyzer is the part that objects. In a compiler, it catches errors like using a variable that was never declared or adding a string to a number.
Grammar alone cannot express meaning. Rules like "every variable must be declared" and "both sides of + must be numbers" are not context-free, so they need a dedicated pass. Semantic analysis turns "it parses" into "it is a valid program", and its central data structure — the symbol table — appears in every serious language tool.
Key Concepts
- Symbol table: maps each declared name to its type, scope, memory address, and declaration line.
- Scope: the region where a name is visible; nested blocks nest scopes, and inner declarations shadow outer ones.
- Type checking: verifying operations get compatible operands, e.g. both sides of arithmetic are numeric.
- Type coercion: automatic safe conversion, like promoting an integer to float in a mixed expression.
- Declaration before use: a name must be declared before it is referenced — enforced here.
How It Works
- The analyzer walks the abstract syntax tree, keeping a stack of symbol tables — one per open scope.
- On a declaration like "int x = 5;", it inserts x with type int, erroring if x is already declared in that scope.
- On a use of a name, it searches outward through enclosing scopes; finding nothing, it reports "undeclared identifier".
- On an operation, it checks operand types against the language's rules — coercing where allowed, erroring where not.
Worked Example
Analyze: int x = 5; float y = x + 2.5;
- "int x = 5;": insert { name: x, type: int, value: 5 }. Table size: 1.
- "float y = x + 2.5;": look up x — found, type int. Operands of + are int and float.
- Type rule: mixed int/float allowed with coercion — 5 becomes 5.0; 5.0 + 2.5 = 7.5.
- Result float matches y's declared type: insert { name: y, type: float, value: 7.5 }. Table size: 2. No errors.
Had the second line used an undeclared z, the lookup would fail in every scope and report "undeclared identifier z" — an error the parser cannot catch, since "z + 2.5" is perfectly grammatical.
Common Misconceptions
- The parser catches type errors. It cannot — "3 + true" parses fine; type errors are semantic.
- The symbol table is one flat list. Tables nest by scope so a local can shadow a global safely.
- Only compiled languages need this. Interpreted languages like Python run the same checks at run time.
Real-World Applications
- Compilers: every production compiler runs semantic analysis before code generation.
- IDEs: red squiggles, go-to-definition, and rename-refactoring query symbol-table-like structures.
- Static analyzers: security and bug-finding tools extend semantic checks with deeper rules.
- Linters: reuse scope and type info to flag suspicious code.
Semantic analysis gives a program meaning: it tracks every name in scoped symbol tables, enforces declaration and type rules, and rejects grammatical nonsense. It turns a well-formed token tree into a program the compiler can trust.