Lexical analysis
How the scanner turns characters into tokens: tokens, lexemes and patterns, regular expressions for token classes, the longest-match and rule-priority rules, keywords versus identifiers, input buffering, the symbol table, lexical errors and what a lexer cannot check; with token-counting practice.
Why it exists
The parser should deal with words of the language (an identifier, a number, the keyword while), not with individual characters. The lexical analyser (scanner, lexer) reads the character stream and delivers one token at a time on demand. Separating it from the parser keeps both simple: white space and comments are removed in one place, the patterns of tokens are regular and can be recognised by a fast finite automaton, and the parser's grammar can be written over tokens. It also isolates machine and character-set details.
Prerequisites
You need the first phase of the compiler from phases of a compiler, and regular expressions, because the pattern of every token class is a regular expression (see regular expressions).
Precise definitions
Token. A pair such as or . The name is the abstract symbol the parser uses; the attribute is extra information for later phases. Operators and keywords usually need no attribute.
Lexeme. The actual character sequence in the source that matches a token's pattern, for example rate or 60.
Pattern. The rule describing the set of lexemes of a token, normally a regular expression.
| Token | Sample lexemes | Pattern (informal) |
|---|---|---|
id | rate, x1, _tmp | letter or underscore, then letters, digits, underscores |
num | 60, 3.14, 2.5e-3 | digits, optional fraction, optional exponent |
if | if | the characters if |
relop | <, <=, == | one of the comparison operators |
literal | "hello" | quote, anything but quote, quote |
Regular definitions name the parts: for C-like identifiers
An unsigned number: .
The two rules that decide ambiguity. When several patterns could match at the current position, the scanner uses
- Longest match (maximal munch): take the longest prefix of the remaining input that matches any pattern. So
<=is one token, not<followed by=, anda+++bis read asa,++,+,b. - Rule priority: if two patterns match the same longest lexeme, the one listed first wins. That is how the lexeme
ifbecomes the keyword and not an identifier, as long as the keyword rule is listed before the identifier rule. A common implementation instead matches an identifier and then looks the lexeme up in a table of reserved words.
The longest match is a greedy local rule: it never backs off to make the rest parse. In C, x = y+++z; always gives y ++ + z, even where another split could be valid.
What the scanner removes or keeps. White space (blanks, tabs, newlines), comments and, in C, the result of preprocessing are discarded. A string literal (including spaces or // inside the quotes) is one token. Newlines matter in some languages (Python, Fortran) and are then tokens.
Recognising tokens. The patterns are combined into one automaton: each regular expression becomes an NFA, the NFAs are joined, the result is converted to a DFA and minimised (regex to automata, DFA). The scanner runs the DFA; every accepting state is tagged with a token name. The scanner remembers the last accepting state passed and, when the DFA gets stuck, backs up to it, which is exactly the longest-match rule. Tools such as Lex and Flex generate this scanner from a list of pattern action rules.
Input buffering. Reading one character at a time from disk is slow, so the scanner uses a buffer pair: two halves of characters each, with a lexemeBegin pointer and a forward pointer. Characters are read a block at a time into one half while the other is scanned. A sentinel (an end-of-file character at the end of each half) lets the scanner test for the end of a buffer only when it reads a sentinel, not at every character. The lookahead after a lexeme (to decide whether < is the start of <=) is the reason a pointer can run ahead of the lexeme.
Symbol table. When the scanner finds an identifier lexeme it looks it up in the symbol table (entering it if new) and returns the token with a pointer to the entry, so that later phases attach type, scope and address to it.
Lexical errors. The scanner reports only things it can see locally: an illegal character, an unterminated string or comment, a malformed number. It cannot detect a missing semicolon or an undeclared name. Recovery is usually panic mode (delete characters until a token can be formed) or inserting, deleting, replacing or transposing a single character.
What a lexer cannot do. Its patterns are regular, so it cannot check anything that needs counting or nesting, such as balanced parentheses or matching begin and end: that is the parser's job (a context-free grammar, see phases). It also does not know which names are declared.
How it works
Two small machines, run on lexemes. The first accepts identifiers. State ID is accepting; any letter, digit or underscore keeps it there; a digit in the start state leads to the dead state, because an identifier cannot begin with a digit.
DFA for identifiers, reading the lexeme rate2: start
Simulated, not typedMoore machine: the output is written inside the state (state/output); edges are labelled with the input.
Run so far
Step 1
The scanner starts in S. An identifier starts with a letter or underscore and continues with letters, digits and underscores. A state with an output is accepting: the output names the token found if the lexeme ended here.
The same DFA reading 2rate: a digit cannot start an identifier: start
Simulated, not typedMoore machine: the output is written inside the state (state/output); edges are labelled with the input.
Run so far
Step 1
The same DFA, now fed 2rate. In a real scanner the number pattern would claim the 2 first; here only the identifier pattern is shown.
The second shows the longest-match rule for relational operators. After < the scanner is in an accepting state (LT) but an edge on = exists, so it continues; it ends in LE with no outgoing edge, so the token is <=.
DFA for relational operators reading <= (longest match): start
Simulated, not typedMoore machine: the output is written inside the state (state/output); edges are labelled with the input.
Run so far
Step 1
Start in S. The relational operators share a first character, so the scanner reads on as long as an edge exists (the longest match) and accepts in the last accepting state it passed.
The following C program is a complete scanner for a small part of C (identifiers, numbers, strings, operators, punctuation). Its next_token function returns the length of the longest token at a position, which is the longest-match rule written directly. Keywords come out as id here because the program does not look names up in a reserved-word table; a real scanner would label if and else as keywords.
#include <ctype.h>
#include <stdio.h>
#include <string.h>
static const char *OPS3[] = {"<<=", ">>=", "...", NULL};
static const char *OPS2[] = {"++", "--", "<<", ">>", "<=", ">=", "==", "!=", "&&", "||", "+=", "-=", "*=", "/=", "->", NULL};
static int starts_with_any(const char *s, const char **list, size_t n) {
for (int i = 0; list[i] != NULL; i++)
if (strlen(list[i]) == n && strncmp(s, list[i], n) == 0) return 1;
return 0;
}
/* length of the longest token that starts at s (maximal munch); *kind names the token class */
static size_t next_token(const char *s, const char **kind) {
size_t n = 0;
if (isalpha((unsigned char)s[0]) || s[0] == '_') {
while (isalnum((unsigned char)s[n]) || s[n] == '_') n++;
*kind = "id";
} else if (isdigit((unsigned char)s[0])) {
if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) {
n = 2;
while (isxdigit((unsigned char)s[n])) n++;
} else {
while (isdigit((unsigned char)s[n])) n++;
if (s[n] == '.') {
n++;
while (isdigit((unsigned char)s[n])) n++;
}
if ((s[n] == 'e' || s[n] == 'E') &&
(isdigit((unsigned char)s[n + 1]) || ((s[n + 1] == '-' || s[n + 1] == '+') && isdigit((unsigned char)s[n + 2])))) {
n += 2;
while (isdigit((unsigned char)s[n])) n++;
}
}
while (s[n] != '\0' && strchr("fFlLuU", s[n]) != NULL) n++;
*kind = "num";
} else if (s[0] == '"') {
n = 1;
while (s[n] != '\0' && s[n] != '"') n += (s[n] == '\\' && s[n + 1] != '\0') ? 2 : 1;
if (s[n] == '"') n++;
*kind = "str";
} else if (starts_with_any(s, OPS3, 3)) {
n = 3;
*kind = "op";
} else if (starts_with_any(s, OPS2, 2)) {
n = 2;
*kind = "op";
} else {
n = 1;
*kind = strchr("(){}[];,", s[0]) != NULL ? "punct" : "op";
}
return n;
}
static void scan(const char *src) {
int count = 0;
printf("%s\n ", src);
for (const char *p = src; *p != '\0';) {
if (isspace((unsigned char)*p)) {
p++;
continue;
}
const char *kind;
size_t n = next_token(p, &kind);
printf(" <%s %.*s>", kind, (int)n, p);
p += n;
count++;
}
printf("\n %d tokens\n", count);
}
int main(void) {
scan("a = b+++c;");
scan("a+++++b;");
scan("x = 3.14e-2f + 0x1F;");
scan("printf(\"i = %d, &i = %x\", i, &i);");
scan("if (a <= b) c = a; else c = b;");
return 0;
}a = b+++c;
<id a> <op => <id b> <op ++> <op +> <id c> <punct ;>
7 tokens
a+++++b;
<id a> <op ++> <op ++> <op +> <id b> <punct ;>
6 tokens
x = 3.14e-2f + 0x1F;
<id x> <op => <num 3.14e-2f> <op +> <num 0x1F> <punct ;>
6 tokens
printf("i = %d, &i = %x", i, &i);
<id printf> <punct (> <str "i = %d, &i = %x"> <punct ,> <id i> <punct ,> <op &> <id i> <punct )> <punct ;>
10 tokens
if (a <= b) c = a; else c = b;
<id if> <punct (> <id a> <op <=> <id b> <punct )> <id c> <op => <id a> <punct ;> <id else> <id c> <op => <id b> <punct ;>
15 tokensWorked examples
EX 1Count the tokens
EasyHow many tokens are in printf("i = %d, &i = %x", i, &i);? The string literal is a single token however many characters and spaces it contains. The tokens are: printf, (, the string, ,, i, ,, &, i, ), ;. That is 10. A frequent wrong answer splits the string at the spaces, the commas or the & inside it.
EX 2Longest match on a+++b style input
EasyTokenise a = b+++c;. At +++ the longest operator that matches is ++ (two characters; there is no +++), so the first token is ++, then the remaining +. The tokens are a, =, b, ++, +, c, ;: 7. For a+++++b; the same rule gives a, ++, ++, +, b, ;: 6. The scanner does not care that a++ ++ + b makes no sense for C: (a++)++ fits the grammar but a++ is not something that can be incremented, so the compiler rejects it later, in semantic analysis, not in the scanner.
EX 3Keyword or identifier?
MediumWhich tokens come out of if iff ifx = 3? The lexeme if matches both the keyword rule and the identifier rule with the same length, so the rule listed first (the keyword) wins. The lexeme iff is longer: the identifier rule matches three characters and the keyword rule only two, so by longest match the token is an identifier. The same for ifx. So the tokens are: keyword if, identifier iff, identifier ifx, =, number 3. The scanner never reads if and then f.
EX 4A pattern for numbers
MediumWrite a regular expression for unsigned numbers such as 42, 3.14, 2.5e-3, 7e2 (digits, optional fraction, optional exponent). With : . Test the lexeme 3. : the fraction needs at least one digit after the point, so the scanner takes 3 and leaves the point for the next token (in C 3. is a valid float, so a real C pattern is d^{+}\,.\,d^{*} as well; the pattern depends on the language). Test 1e: the exponent part would need digits after e, so with this pattern the number is 1 and e starts a new identifier token (real C instead reads 1e as one malformed preprocessing number and rejects it later; the example follows the pattern given here). That back-up to the last accepting position is exactly what the stuck DFA does.
EX 5What the lexer cannot decide
GATE levelFor each of the following, can the lexical analyser alone detect the error? An unterminated string literal ("abc): yes, no token can be formed. A missing right parenthesis in (a + b: no, each token is fine and the problem is in the structure (a parser job; checking balance needs a pushdown automaton, not a finite one). A call to an undeclared function: no, it needs the symbol table. An integer literal too large for its type: usually not in the pattern but in a later conversion check (implementation dependent). Questions on this theme test the boundary between regular patterns (lexer) and context-free structure (parser).
Cheat box
Cheat box
- Token = name (and attribute); lexeme = the matching text; pattern = the rule (a regular expression).
- Longest match first; if equal length, the earlier rule (keywords before identifiers).
- Strings and comments: a string literal is one token; comments and white space produce none.
a+++bisa ++ + b;<=is one token;3.14e-2fis one token.- Lexer = finite automaton; cannot check balanced brackets or declarations.
- Identifiers go to the symbol table; the token carries a pointer to the entry.
- Input buffering: buffer pairs and sentinels; the
forwardpointer may run ahead of the lexeme.
Traps and GATE patterns
Connections
Across subjects
Within this subject
Journeys that pass through here
The scanner is the first phase in the phases of a compiler. Its patterns are regular expressions, turned into a DFA by the conversions. The tokens it produces are the terminals of the grammar used by the parser (LL(1) parsing and LR parsing).
PYQs
6 previous year questions from your book. I re-solved each one myself (3 by running or simulating, 3 by reasoning only); the book's key is shown next to my answer. All PYQs
A lexical analyzer uses the following token
definitions
letter → [A – Z a - z]
digit → [0 - 9]
id → letter (letter | digit)*
number → digit +
ws → (blank | tab | newline) +
For the string given below,
x1 23mm 78 y 7z zz5 14A 8H AaYcD
the number of tokens (excluding ws) that will be
produced by the lexical analyzer is __________.
(answer in integer)A lexical analyzer uses the following patterns to
recognize three tokens T1, T2, and T3 over the
alphabet {a,b,c}.
T1 : a?(b|c)*a
T2 : b?(a|c)*b
T3 : c?(b|a)*c
Note that 'x?' means 0 or 1 occurrence of the symbol
x. Note also that the analyzer outputs the token that
matches the longest possible prefix. If the string
bbaacabc is processed by the analyzer, which one of
the following is the sequence of tokens it outputs?- (a)T1T2T3
- (b)T1T1T3
- (c)T2T1T3
- (d)T3T3
In a compiler, keywords of a language are recognized during:-
- (a)Parsing of the program
- (b)the code generation
- (c)the lexical analysis of the program
- (d)dataflow analysis
The lexical analysis for a modern computer language such as Java needs the power of which one of the following machine models in a necessary and sufficient sense?
- (a)Finite state automata
- (b)Deterministic pushdown automata
- (c)Non-Deterministic pushdown automata
- (d)Turing machine
Consider line number 3 of the following C- program.
int main ( ) { /*Line 1 */
int I, N;
/*Line 2 */
fro (I = 0, I < N, I++); /*Line 3 */
}
Identify the compiler's response about this line while
creating the object-module- (a)Both lexical and syntactic errors
- (b)Only a lexical error
- (c)Only syntactic errors
- (d)No compilation error
Consider the following C statements:
char *str1 = "Hello; /* Statement S1 */
char *str2 = "Hello;"; /* Statement S2 */
int *str3 = "Hello"; /* Statement S3 */
Which of the following options is/are correct?- (a)S1 and S2 have syntactic errors
- (b)S2 has a lexical error and S3 has a syntactic error
- (c)S1 has a lexical error and S3 has a semantic error
- (d)S1 has a syntactic error and S3 has a semantic error
Self-check
Question 1
What are the tokens of x = a<=b;, and how many are there?
Show answerHide
x, =, a, <=, b, ;: six tokens. <= is one token by the longest-match rule.
Question 2
Why is the lexeme while1 an identifier and not the keyword while followed by 1?
Show answerHide
Both the identifier pattern and the keyword pattern could match a prefix, but the identifier pattern matches the longer lexeme (while1), and longest match is applied before rule priority.
Question 3
Can a scanner detect unbalanced parentheses? Why or why not?
Show answerHide
No. Balanced parentheses are not a regular language, so no finite automaton (the model of a scanner) can check them; a parser, which has a stack, does it.
Question 4
How many tokens does p->next = p->next->next; have?
Show answerHide
Ten: p, ->, next, =, p, ->, next, ->, next, ;.
Revision card
One-screen revision
- Lexeme matches a pattern; the token is a (name, attribute) pair.
- Longest match, then rule priority (keywords before identifiers).
- Strings are single tokens; multi-character operators are single tokens; white space and comments give none.
- Scanner = DFA from the regular expressions; no nesting or counting.
- Identifiers entered in the symbol table; errors: illegal character, unterminated string.
References
- ·Aho, Lam, Sethi, Ullman, Compilers: Principles, Techniques, and Tools (2nd edition), Chapter 3 Lexical Analysis (the role of the lexical analyzer, input buffering, specification and recognition of tokens, the lexical-analyzer generator Lex)
- ·Cooper and Torczon, Engineering a Compiler, Chapter 2 Scanners
Verification
The token counts quoted here (10, 7, 6, 15 and others) were obtained with two independent scanners and agree: pycparser's real C lexer and a longest-match regular-expression scanner (scripts/enum-checks/cd_front.py). The C scanner shown on this page is compiled and run by npm run verify:c. The two DFA animations are runs of the machines drawn (scripts/gen-cd-traces.py). Exact rules for what counts as one token differ a little between languages; the rules stated here are those of C. SECOND PASS (2026-11-02, done by the assistant, not a human check): every Self-check question was solved before the printed answer was read and all agreed; the definitions, worked examples, cheat box and traps were read claim by claim and the numbers re-derived by hand or by a small script (token counts, FIRST and FOLLOW sets, LL(1) tables and moves, LR item sets, state counts and parses, operator-precedence values, attribute evaluation and printing schemes, activation-record counts and access links, parameter-passing results, three-address code, jumping and backpatched code, leaders and blocks, value numbering and DAGs, gen/kill and data flow sets, Sethi-Ullman labels and live ranges); long C programs were not re-read line by line (they are compiled and compared by npm run verify:c, and their printed outputs were checked against the prose); tables and animations are machine output and were read, not re-run; One example (the token 1e) was made precise: with the pattern given on the page 1e is a number followed by an identifier, while real C reads it as one malformed preprocessing number. Status raised to self-checked; verified needs a textbook or human check.
last verified: never
My notes (saved in this browser)