🌟 Error Handling and Recovery in Compiler
What is Error Handling?
When we write a program, we can make many mistakes (called errors).
The compiler’s job is to find, report, and try to fix or recover from those errors so that it can continue
checking the rest of the program instead of stopping completely.
So, Error Handling means:
“Finding and managing the errors in a program so the compiler can continue its work.”
Main Goals of Error Handling
1. Detect the errors correctly.
2. Show clear and helpful error messages to the programmer.
3. Recover and continue checking the rest of the program.
4. Avoid stopping after the first error.
Types of Errors
Type Meaning Example
Lexical Error Wrong characters or symbols int @x = 10; (invalid
symbol @)
Syntax Error Wrong structure or missing if (x > 0 { (missing ))
symbols
Semantic Error Wrong meaning (logic/type) int a = "Hello"; (type
mismatch)
Runtime Error Happens when running the b = 10 / 0; (divide by zero)
program
What is Error Recovery?
After finding an error, the compiler tries to recover so it can continue checking the next part of the
[Link] process is called Error Recovery.
Without recovery, the compiler would stop after one mistake — which is not helpful.
⚙ Methods of Error Recovery
There are 4 common techniques for error recovery:
1. Panic Mode Recovery
Idea:
When an error occurs, the compiler skips some part of the input until it finds a “safe point,” like a
semicolon ; or closing brace } — then it starts checking again.
Example:
int x = 10
y = 5;
Missing ; after 10.
The compiler skips until ;, then continues from y = 5;.
✅ Advantages:
Simple and easy to implement.
Prevents getting stuck in an endless loop.
❌ Disadvantages:
Some correct statements may also get skipped.
🟣 2. Phrase Level Recovery
Idea:
The compiler tries to fix small mistakes automatically — for example, inserting a missing symbol or
deleting an extra one.
Example:
if (x > 0 { // missing ')'
The compiler may insert ) and continue as if the line was correct.
✅ Advantages:
Fixes small mistakes quickly.
Keeps the program checking smoothly.
❌ Disadvantages:
compiler’s “guess” might be wrong.
🟣 3. Error Productions
Idea:
The compiler’s grammar includes special rules for common errors.
This helps in giving clear and specific error messages.
Example: If the compiler expects:
if (expression) statement
But user writes:
if (x > 0)
(no statement after condition)
The compiler can show:
Error: Missing statement after 'if' condition.
✅ Advantages:
Gives helpful error messages.
❌ Disadvantages:
Makes the grammar longer and more complex.
🟣 4. Global Correction
Idea:
The compiler tries to find the smallest possible change to make the whole program correct.
Example:
a = (b + c;
The compiler might add a missing ) and continue as:
a = (b + c);
✅ Advantages:
Can completely fix the code logically.
❌ Disadvantages:
Very slow and hard to implement.
Not used much in real compilers.
🧠 Example of Error Handling
Program:
int main() {
int x = 10
if (x > 5) {
printf("Hello");
}
}
Error: Missing ; after 10
Steps:
1. Compiler detects error.
2. Shows message:
Error: Missing ';' before 'if' (line 2)
3. Uses panic mode to skip until safe point (if statement).
4. Continues checking the rest of the code successfully.
✅ The compiler doesn’t stop after the first mistake.
🔹 Error Messages Should Include:
The line number where the error occurred.
The type of error.
A helpful hint on how to fix it.
Example:
Syntax Error at line 4:
Found '}' but expected ';' before it.
Tip: Did you forget a semicolon?
---
🧾 Summary Table
Method Meaning Example Pros Cons
Panic Mode Skip until safe Skip till ; Simple May skip too
symbol much
Phrase Level Fix small error Insert missing ) Quick May guess
wrong
Error Add special Missing Helpful Complex
Productions rules statement grammar
Global Best possible Add missing ) Accurate Slow and hard
Correction fix
💬 In Simple Words
Error handling and recovery help the compiler:
> “Find errors, show them clearly, and still continue compiling the rest of the code.”
What is a parser generator? — Detailed (but easy) explanation
A parser generator is a tool that automatically builds a parser from a grammar you write. Instead of hand-
writing a parser by coding every rule and state machine, you give the tool a formal description of the
language (a grammar) and it generates the parser code (in C, Java, Python, etc.) for you.
Think of it like: you write the rules of a language in a simple file, press a button, and the tool creates a
program that can read strings in that language and tell you whether they’re valid — often also producing a
parse tree or AST you can use later.
Why use a parser generator?
Saves time — you don’tneed to write complex parsing code yourself.
Less error-prone — generators implement complex parsing algorithms for you.
Reduces errors___Automatically handles the grammer correctly.
Faster development___helps build compilers correctly.
Easy to update___if grammar changes, just re-run the tool.
Main pieces: what you give, what you get
Means the grammar you give as input determines the parser you get as output
You give:
1. A grammar (usually a context-free grammar in BNF/EBNF form).
2. Optionally: token definitions (regexes), semantic actions (snippets of code to run when a rule
matches), and error-handling hints.
You get:
Parser source code (or parser tables).
Functions/APIs to feed tokens (from a lexer) and get parse results (success/failure, parse
tree/AST).
Usually hooks to add semantic actions (build AST, type info).
Two main parser families used by generators
1. Top-down / LL family (predictive parsers)
Generates recursive-descent-style parsers.
Grammar restrictions: must be LL(k) or LL(*). Left recursion must be removed.
Example tools: ANTLR, JavaCC.
Good when you want easy-to-read generated code and embedded actions.
2. Bottom-up / LR family (shift-reduce parsers)
Generates LR/ LALR / SLR parsers using parsing tables.
Can handle a wider class of grammars (including left recursion).
Example tools: Yacc, Bison, Menhir.
Often used for languages with complex grammars (C-like languages).
How a parser generator typically works (high level)
1. Grammar input: you write productions, e.g.
Expr -> Expr '+' Term
| Term
Term -> Term '*' Factor
| Factor
Factor -> '(' Expr ')' | NUMBER | ID
2. Compute parsing tables or recursive-descent code: the tool analyzes the grammar
(FIRST/FOLLOW sets, conflict resolution) and produces either:
A table-driven parser (LR): shift/reduce/goto/action tables; or
Recursive-descent functions (LL/ANTLR).
3. Combine with lexer tokens: tokens come from a lexer (you can hand-write one or use a lexer
generator like Flex or ANTLR’s built-in lexer).
4. Semantic actions / AST building: you hook code into grammar rules to build AST nodes, track
symbol table entries, etc.
5. Run the generated parser on input source: it returns success/failure and typically an AST
Example: simple arithmetic grammar and what a generator gives
Grammar (EBNF):
expr : expr '+' term
| term
term : term '*' factor
| factor
factor : '(' expr ')'
| NUMBER
What you do with a parser generator:
Put the grammar into the tool (plus token rules for NUMBER, +, *, (, )).
Add semantic actions to create nodes, e.g. on expr -> expr '+' term create AddNode(expr, term).
The tool generates parser code that:
o Reads tokens,
o Uses shift/reduce or recursive function calls,
o Builds the tree AddNode(a, MulNode(b, c)) for a + b * c.
Concrete tool examples (what they look like)
Yacc / Bison (LR family — classic)
You write a .y file with grammar rules and C code snippets as actions.
Bison builds C source files implementing a shift-reduce parser and uses a separate lexical
analyzer (Flex) for tokens.
Good for language compilers in C/C++.
Tiny sample (Bison-like):
%token NUMBER
%%
expr:
expr '+' term { $$ = new Add($1, $3); }
| term { $$ = $1; }
;
term:
term '*' factor { $$ = new Mul($1, $3); }
| factor { $$ = $1; }
;
factor:
'(' expr ')' { $$ = $2; }
| NUMBER { $$ = new Num($1); }
;
%%
ANTLR (LL / LL(*) family — modern)
Single .g4 file can define both lexer and parser.
Generates parsers in Java/Python/C#/JS, etc.
Produces visitor/listener patterns for walking the parse tree (helpful to build ASTs).
Tiny sample (ANTLR-like):
grammar Expr;
expr : expr '+' term # Add
| term # ToTerm
;
term : term '*' factor # Mul
| factor # ToFactor
;
factor : '(' expr ')' # Parens
| NUMBER # Number
;
NUMBER : [0-9]+ ;
WS : [ \t\r\n]+ -> skip ;
How you build an AST / semantic actions
Most generators let you attach code to a production. When a production is recognized, that code runs —
usually used to make AST nodes.
Example (pseudo):
expr -> expr '+' term { $$ = new AddNode($1, $3); }
Here $1 and $3 refer to the semantic values of the first and third symbols; $$ is the value returned for
expr.
ANTLR uses a different pattern: it produces a parse tree and then you use a listener or visitor class in
which you create AST nodes while walking the tree.
Error handling with parser generators
Generators usually provide hooks for error reporting and recovery.
o Bison has the error token and yyerror() callback.
o ANTLR gives automatic error messages and strategies (recover, bail, custom error
listeners).
You can teach the grammar common error productions or write custom recovery code in actions.
Pros and cons of using parser generators
Pros
Faster development of language tools.
Handles complex parsing details (lookahead, conflicts).
Well-tested parsing algorithms.
Produces clear grammar-based documentation.
Cons
You must learn the generator’s grammar syntax and conventions.
Debugging shift/reduce or reduce/reduce conflicts can be tricky.
Generated parsers can be large or less flexible for tiny DSLs.
Grammar restrictions (LL vs LR) may require grammar rewriting.
When to pick what
Use ANTLR if you want: modern features, multi-language generation, built-in lexer, good
tooling, and easier tree walking (great for domain-specific languages or tools in Java/Python/etc.).
Use Bison/Yacc + Flex if you’re building a classic C/C++ compiler or want table-driven LR
parsing and tight C integration.
Use hand-written recursive-descent parser if grammar is small/simple and you want full control
or best performance for tiny DSLs.
Practical tips
Start with a clear grammar and keep it modular (separate expression rules, statements,
declarations).
Test with many inputs — generators produce helpful conflict messages; use them to refine
grammar.
Use a lexer generator (Flex/ANTLR lexer) to separate tokenization from parsing.
Build an AST quickly — working on semantic analysis and code generation is much easier with
an AST than with raw parse trees.
Use parser’s debug modes to view parse tables or trace parse actions when something goes
wrong.
Quick summary
A parser generator automatically creates a parser from a grammar.
It saves time and reduces bugs vs hand-written parsers.
Major families: LL (ANTLR/JavaCC) and LR (Yacc/Bison).
You give grammar + token rules + semantic actions, the tool gives parser code/tables and hooks for AST
building and error handling.