Compiler DesignUnit 212 min read
Lexical Analysis: Tokens, Lexemes, Automata & Scanners
Unit 2 of Compiler Design covers the first phase of compilation—lexical analysis—where source code is broken into meaningful tokens (lexemes) using regular expressions, finite automata, and scanners. This note explains token classification, DFA/NFA construction, scanner design, and real-world applications in compilers
TAKEAWAYS:
- Lexical analysis converts source code into tokens (lexemes) using regular expressions and finite automata (DFA/NFA).
- A scanner (lexical analyzer) reads input, skips whitespace/comments, and groups characters into tokens (e.g.,
if,42,+) via deterministic finite automata (DFA). - Regular expressions define token patterns (e.g.,
\d+for integers), while lexeme is the actual sequence matched (e.g.,123). - NFA to DFA conversion ensures deterministic token recognition; subset construction is the standard method.
- Lexical errors (e.g., invalid characters) are caught here, while syntax errors are deferred to parsing.
- Tools like Lex/Flex automate scanner generation from regex rules.
1. Introduction to Lexical Analysis
Lexical analysis is the first phase of compilation, where the compiler:
- Reads source code (e.g.,
int x = 5 + y;). - Groups characters into tokens (lexemes) like
int,x,=,5,+,y,;. - Removes whitespace/comments (irrelevant to meaning).
- Outputs a stream of tokens for syntax analysis.
Why is it important?
Without lexical analysis, the parser would see raw text (e.g., intx=5+y;) and fail to recognize keywords, identifiers, or operators.
2. Tokens and Lexemes
| Term | Definition | Example |
|---|---|---|
| Token | A category of lexemes (e.g., IDENTIFIER, INTEGER, PLUS). |
+ is a PLUS token. |
| Lexeme | The actual sequence of characters in the source code. | x, 42, if are lexemes. |
| Terminal | Tokens recognized by the scanner (vs. non-terminals in syntax). | if, while, return. |
Key Idea:
- A token is like a part-of-speech tag (e.g., noun, verb), while a lexeme is the word itself (e.g., "run").
- Example: The lexeme
whilebelongs to the tokenWHILE.
3. Regular Expressions for Token Patterns
Regular expressions (regex) define how lexemes are matched. Common patterns:
- Identifiers:
[a-zA-Z_][a-zA-Z0-9_]*(e.g.,x,total_count). - Integers:
[0-9]+(e.g.,42,1000). - Operators:
[+\-*\/](e.g.,+,-). - Whitespace:
[ \t\n](skipped by the scanner).
Example Regex for a Simple Language:
IDENTIFIER = [a-zA-Z_][a-zA-Z0-9_]* // e.g., x, var1
INTEGER = [0-9]+ // e.g., 42
PLUS = \+ // e.g., +
LPAREN = \( // e.g., (
RPAREN = \) // e.g., )
Note: Regex must be ambiguous-free (no overlap between tokens). For example, if should not match iff.
4. Finite Automata: NFA and DFA
Lexical analyzers use finite automata to recognize tokens.
4.1 Non-Deterministic Finite Automata (NFA)
- States: Represent progress in matching a lexeme.
- Transitions: Can be non-deterministic (multiple transitions on the same input).
- Epsilon (ε) transitions: Allow "guessing" paths (e.g., in
a|b, the NFA may try bothaandb). - Accepting state: Marks successful matches.
Example: NFA for a*b* (even number of as followed by any bs)
4.2 Deterministic Finite Automata (DFA)
- No ε-transitions or ambiguity: Each state/input has exactly one transition.
- Efficient for scanners: DFAs are easier to implement in hardware/software.
- Constructed from NFA via subset construction (see Section 5).
Example: DFA for 01* (binary strings starting with 0)
5. NFA to DFA Conversion (Subset Construction)
Problem: NFAs are hard to implement directly. We convert them to DFAs.
Steps:
- Start with the NFA’s initial state (including ε-closures).
- For each input symbol, compute the next set of states reachable via ε-transitions.
- Repeat until all states are processed.
Example: Convert NFA for a|b to DFA
NFA:
DFA Construction:
| DFA State | Represents NFA States | Transitions |
|---|---|---|
{q0} |
Initial state | a → {q0, q1}, b → {q0, q2} |
{q0, q1} |
Accepting (matches a) |
a → {q0, q1}, b → {q0, q2} |
{q0, q2} |
Accepting (matches b) |
a → {q0, q1}, b → {q0, q2} |
Final DFA:
6. Lexical Analyzer (Scanner) Design
A scanner:
- Reads input characters one by one.
- Uses a DFA to classify tokens.
- Skips whitespace/comments (e.g.,
//,/* */). - Reports errors (e.g., invalid characters like
@).
Example: Scanner for int x = 5;
| Input | State | Action | Token Output |
|---|---|---|---|
i |
Start | Match i → transition to int state |
IDENTIFIER |
n |
int state |
Match n → transition to int state |
|
t |
int state |
Match t → accept INT |
INT |
|
Reset | Skip whitespace | |
x |
Start | Match x → transition to ID state |
IDENTIFIER |
|
Reset | Skip whitespace | |
= |
Start | Match = → accept ASSIGN |
ASSIGN |
|
Reset | Skip whitespace | |
5 |
Start | Match 5 → accept INTEGER |
INTEGER |
; |
Start | Match ; → accept SEMICOLON |
SEMICOLON |
7. Handling Ambiguity and Precedence
Problem: Overlapping regex (e.g., 01 could be 0 followed by 1 or the number 1).
Solutions:
- Longest-match rule: Prefer longer tokens (e.g.,
01→INTEGERover0+1). - Reserved words first: Check
ifbeforeIDENTIFIER. - Lexical precedence: Define an order (e.g.,
==before=).
Example: Ambiguity in 01
| Regex | Match | Solution |
|---|---|---|
0 |
0 |
Reject (too short). |
01 |
01 |
Accept as INTEGER. |
1 |
1 |
Reject (overlap). |
8. Lexical Errors
Common errors caught by the scanner:
- Invalid characters:
@,#(not in the language). - Unterminated strings/comments:
/*without*/. - Trigraphs:
??=(non-standard sequences).
Example Error Handling:
Input: `int x @ 5;`
Scanner output:
- `int` → `INT`
- `x` → `IDENTIFIER`
- `@` → **Lexical Error: Invalid character**
- `5` → `INTEGER`
- `;` → `SEMICOLON`
In the Real World
Lexical analysis is everywhere in software and systems:
eSewa (Nepal’s Digital Payment System)
- Regex Use: Validates transaction IDs (e.g.,
TXN12345) using\d{3}[A-Z]{2}\d{5}. - Scanner Role: Ensures only alphanumeric IDs are processed, rejecting malformed inputs like
TXN@123.
- Regex Use: Validates transaction IDs (e.g.,
Khalti (Mobile Payment App)
- Token Classification: Separates amounts (
\d+\.\d{2}), phone numbers (\d{10}), and commands (pay,transfer). - Error Handling: Flags invalid inputs like
pay 100@(rejects@symbol).
- Token Classification: Separates amounts (
Daraz (E-Commerce Platform)
- Order Processing: Uses regex to parse product codes (e.g.,
DZ-PHONE-001) and quantities (\d+). - Queue Management: Lexical analysis ensures order IDs (e.g.,
ORD-2024-001) are correctly tokenized before routing to the order fulfillment DFA.
- Order Processing: Uses regex to parse product codes (e.g.,
Ncell (Telecom Billing)
- SMS Parsing: Extracts keywords (
balance,recharge) and numbers (\d{10}) from user messages. - Example: Input
balance 9812345678→ Tokens:BALANCE,PHONE_NUMBER.
- SMS Parsing: Extracts keywords (
Google’s Go Language Compiler
- Lexical Scanner: Uses Lex/Flex to tokenize code like:
→ Tokens:package main import "fmt" func main() { fmt.Println("Hello") }PACKAGE,main,IMPORT,"fmt",FUNC,main,(,),{,Println,"Hello",}.
- Lexical Scanner: Uses Lex/Flex to tokenize code like:
Worked Example: Scanner for a Simple Arithmetic Expression
Input: 3 + 5 * ( 2 - 1 )
Regex Rules:
| Token | Regex |
|---|---|
INTEGER |
[0-9]+ |
PLUS |
\+ |
MINUS |
- |
MULTIPLY |
\* |
LPAREN |
\( |
RPAREN |
\) |
Scanner Trace:
Input: 3 + 5 * ( 2 - 1 )
Step 1: '3' → INTEGER (3)
Step 2: ' ' → skip
Step 3: '+' → PLUS
Step 4: ' ' → skip
Step 5: '5' → INTEGER (5)
Step 6: ' ' → skip
Step 7: '*' → MULTIPLY
Step 8: ' ' → skip
Step 9: '(' → LPAREN
Step 10: ' ' → skip
Step 11: '2' → INTEGER (2)
Step 12: ' ' → skip
Step 13: '-' → MINUS
Step 14: ' ' → skip
Step 15: '1' → INTEGER (1)
Step 16: ')' → RPAREN
Output Tokens:
[INTEGER(3), PLUS, INTEGER(5), MULTIPLY, LPAREN, INTEGER(2), MINUS, INTEGER(1), RPAREN]
9. Tools for Lexical Analysis
| Tool | Language | Use Case |
|---|---|---|
| Lex | C | Generates scanners from regex rules. |
| Flex | C/C++ | Modern alternative to Lex. |
| JFlex | Java | Java-compatible scanner generator. |
| ANTLR | Multi-lang | Combines lexer + parser. |
Example Lex Specification (for INTEGER and PLUS):
%%
[0-9]+ { yylval = atoi(yytext); return INTEGER; }
"+" { return PLUS; }
[ \t\n] ; // Skip whitespace
. { printf("Lexical error: %s\n", yytext); }
%%
Compilation:
lex scanner.l → scanner.c → Compile with gcc scanner.c -lfl -o scanner.
Exam Tip
What to Expect in TU/PU Exams:
Define and Differentiate:
- "Explain the difference between a token and a lexeme with examples."
- "What is the role of a DFA in lexical analysis?"
NFA to DFA Conversion:
- Given an NFA, construct the DFA using subset construction.
- Common pitfall: Forgetting ε-closures in intermediate steps.
Regex and Ambiguity:
- "Write regex for identifiers and floating-point numbers."
- "Resolve ambiguity between
01asINTEGERvs.0+1."
Scanner Design:
- "Design a scanner for a language with keywords
if,else, and identifiers starting with a letter." - Trace: Show how
if(x>0)is tokenized.
- "Design a scanner for a language with keywords
Error Handling:
- "How does a scanner detect invalid characters? Give an example."
Marks Distribution:
- Short answers (2-4 marks): Definitions, regex, token examples.
- Long answers (6-8 marks): NFA/DFA conversion, scanner traces.
- Programming (4-6 marks): Lex/Flex code snippets or pseudocode.
Pro Tip:
- Draw DFAs for regex like
a*b*or01*—examiners love visuals! - Memorize the subset construction steps for NFA to DFA.
- Practice tokenizing real code snippets (e.g., C/Java/Python).
Based on the PU BE Computer (PU) syllabus for Compiler Design (CMP360), unit 2.
Discussion
Loading…