0% found this document useful (0 votes)
103 views12 pages

Error Handling in Compilers Explained

The document discusses error handling and recovery in compilers, emphasizing the importance of detecting errors, providing clear messages, and allowing the compiler to continue processing. It outlines different types of errors (lexical, syntax, semantic, runtime) and recovery methods (panic mode, phrase level, error productions, global correction). Additionally, it explains parser generators, which automate parser creation from grammar, highlighting their advantages and when to use specific tools like ANTLR or Bison.

Uploaded by

aleenarubab13
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
103 views12 pages

Error Handling in Compilers Explained

The document discusses error handling and recovery in compilers, emphasizing the importance of detecting errors, providing clear messages, and allowing the compiler to continue processing. It outlines different types of errors (lexical, syntax, semantic, runtime) and recovery methods (panic mode, phrase level, error productions, global correction). Additionally, it explains parser generators, which automate parser creation from grammar, highlighting their advantages and when to use specific tools like ANTLR or Bison.

Uploaded by

aleenarubab13
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd

🌟 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.

Common questions

Powered by AI

The error recovery methods differ in their approach and complexity as follows: Panic Mode Recovery is simple and easy to implement; it skips input until a safe symbol is found but might skip too much . Phrase Level Recovery attempts to fix small mistakes automatically, but its guesses might be wrong . Error Productions use special grammar rules for errors, providing clear messages but making the grammar more complex . Global Correction aims for the smallest change to fix the code logically, providing accuracy but being very slow and hard to implement .

Using a parser generator has several advantages: it saves time because you do not need to write complex parsing code manually, is less error-prone due to automatic handling of grammar, facilitates faster development, and allows for easy updates if grammar changes. However, disadvantages include needing to learn the generator's syntax and conventions, difficulty in debugging certain conflicts, potentially large or less flexible parsers, and restrictions associated with certain grammar types (LL vs. LR) that may require rewriting the grammar .

A developer might choose ANTLR over Bison/Yacc for modern features, multi-language support, built-in lexer, and easier tree walking, making it suitable for domain-specific languages or tools in Java/Python/etc. Conversely, Bison/Yacc might be chosen if developing a classic C/C++ compiler or requiring table-driven LR parsing with tight C integration since it handles complex grammars well .

Debugging parsers generated by tools like ANTLR or Bison can be challenging due to conflicts in the grammar, such as shift/reduce or reduce/reduce conflicts. These conflicts arise when the grammar rules can be interpreted in multiple ways, which can lead to ambiguities in parsing. Handling these conflicts requires a deep understanding of the parsing algorithms and might involve rewriting grammar or adjusting tool-specific settings to resolve ambiguities, a task that can be quite complex and time-consuming .

Semantic actions in parser generators are code snippets that are executed when a grammar rule is matched. They facilitate the generation of an abstract syntax tree (AST) by allowing the integration of code that constructs nodes in the AST. These actions often involve using attributes of the grammar symbols to create and link nodes, thereby transforming the raw parse structure into a meaningful representation of the program's syntactic and semantic elements .

Panic mode error recovery works by skipping erroneous input until a safe symbol, such as a semicolon or closing brace, is found, allowing the parser to resume checking the subsequent input. The main strengths of panic mode are its simplicity and ease of implementation, as well as its ability to avoid endless loops. However, its main weakness is that it might skip too much input, including potentially correct statements, which could lead to further errors being masked or incorrect assumptions being made about the program's structure .

Lexical analysis and parsing are two critical stages in a compiler. Lexical analysis involves tokenizing the input program, breaking it down into understandable pieces called tokens. Parsing follows and constructs a parse tree or abstract syntax tree (AST) from these tokens, ensuring syntactical correctness according to the grammar rules of the language. While lexical analysis handles the basic structure and sequencing of inputs, parsing focuses on understanding the logical structure and relationships defined by syntax rules .

After providing grammar and token rules to a parser generator, you typically receive parser source code or parser tables that can process input according to the defined grammar. Additionally, you get APIs/functions for feeding tokens from a lexer to obtain parse results, which include a success/failure status and a parse tree or AST. The generator often provides hooks for adding semantic actions that enable further processing like AST building or error handling .

Global Correction in error recovery involves making the smallest possible change to correct the entire program, aiming for optimal logical correction. However, it is very slow and difficult to implement, resulting in limited practical usage in real compilers due to performance concerns. This method is typically not favored in practice due to its computational intensity and the complexity of implementation .

The main purpose of error handling in a compiler is to find, report, and recover from errors in a program so that the compiler can continue checking the rest of the program instead of stopping completely. The primary goals of error handling are to detect errors correctly, provide clear and helpful error messages to the programmer, recover and continue checking the rest of the program, and avoid stopping after the first error .

You might also like