Lexical Analysis
The first phase of a compiler — the lexer reads raw source characters and groups them into a stream of tokens that every later phase understands.
What is Lexical Analysis?
Lexical analysis (also called scanning or tokenisation) is Phase 1 of the compiler pipeline. Its job is straightforward:
Read the source code character by character and group those characters into tokens — the smallest meaningful units of the language.
Think of it like reading an English sentence. You don't process the sentence one letter at a time — you naturally chunk letters into words, then punctuation. The lexer does the same for source code.
Position in the Compiler Pipeline
The lexer sits between the raw source text and everything else. Without it, the parser would have to deal with whitespace, comments, and individual characters — a nightmare. The lexer cleans all of that up.
Tokens, Lexemes, and Patterns
Three terms are essential for understanding lexical analysis:
| Term | Definition | Example |
|---|---|---|
| Token | A category — the type of the matched text | KEYWORD, IDENTIFIER, NUMBER |
| Lexeme | The actual text from the source code that matched | while, total, 42 |
| Pattern | The rule (often a regular expression) that describes what text matches this token | [a-zA-Z_][a-zA-Z0-9_]* matches identifiers |
Common Token Types
| Token Type | Pattern (informal) | Lexeme Examples |
|---|---|---|
KEYWORD | Fixed reserved words | int, if, while, return |
IDENTIFIER | Letter/underscore then letters/digits | total, _count, myVar2 |
INTEGER_LITERAL | Sequence of digits | 0, 42, 1000 |
FLOAT_LITERAL | Digits, dot, digits | 3.14, 0.5, 2.0 |
OPERATOR | Arithmetic/comparison symbols | +, -, *, ==, <= |
PUNCTUATION | Delimiters and separators | (, ), {, }, ;, , |
STRING_LITERAL | Text between double quotes | "hello", "world" |
Whitespace (spaces, tabs, newlines) and comments are not tokens — the lexer discards them silently.
Worked Example — Tokenising a Statement
Let's trace the lexer on a real C-style statement:
The lexer scans left to right. Each time it finds the longest matching pattern, it emits a token and advances:
Token stream output:
The parser never sees whitespace — it receives a clean sequence of typed tokens.
Another Example — a Function Declaration
Token stream:
How the Lexer Works — Step by Step
The lexer implements a longest-match rule (also called maximal munch):
At each position, consume the longest sequence of characters that matches any pattern.
This is why <= is ONE token (LESS_EQUAL), not two (LESS, EQUAL). The lexer always prefers the longer match.
Lexer Algorithm
Under the hood, most lexers are implemented as a Deterministic Finite Automaton (DFA). Each token type corresponds to a regular expression; those regular expressions are combined into a single DFA that runs in O(n) time on a source file of length n.
Keyword vs Identifier — How the Lexer Decides
Keywords like int, while, return look exactly like identifiers by their character pattern. Lexers handle this two ways:
Method 1 — Keyword table:
- Recognise
whileas an IDENTIFIER first - Then look it up in a keyword table
- If found → reclassify as KEYWORD
Method 2 — Higher priority pattern:
- Keyword patterns are given higher priority than the IDENTIFIER pattern
- The DFA resolves the ambiguity by picking the keyword path first
Lexical Errors
A lexical error occurs when the lexer encounters a sequence of characters that does not match any valid token pattern.
Common Lexical Errors
| Error | Example | Problem |
|---|---|---|
| Invalid character | @sum (@ is not a valid start) | Character not in the language's alphabet |
| Unterminated string | "hello (missing closing quote) | String literal never closed |
| Ill-formed number | 3.14.15 | Two decimal points |
| Invalid escape | "\q" (\q is not a valid escape) | Unknown escape sequence |
What Happens on a Lexical Error?
Most lexers use error recovery to continue scanning and report multiple errors at once:
- Panic mode — skip characters until a valid token can be formed, then continue
- Insert/delete — hypothetically insert or delete one character to make progress
- Emit an ERROR token — the parser will then reject the token stream at the syntax phase
Lexical errors are the easiest category to recover from — the lexer simply skips the bad character and resynchronises at the next recognisable token.
Summary
| Concept | Key Takeaway |
|---|---|
| Token | Category/type label (IDENTIFIER, KEYWORD, …) |
| Lexeme | Actual matched text from source |
| Pattern | Regular expression that defines the token |
| Maximal munch | Always match the longest possible string |
| Implementation | DFA running in O(n) on input of length n |
| Output | A flat token stream — no whitespace or comments |
The lexer's output — a stream of (type, lexeme) pairs — is everything the parser needs. Lexical analysis effectively converts a character-level view of the program into a word-level view, making every subsequent phase far simpler to implement.