COMPILER DESIGN / 1. LEXICAL ANALYSIS
Lexical Analysis — The Scanner
Turning raw source text into a stream of meaningful tokens using finite automata
EXPLANATION
Lexical analysis (scanning) is the first phase of compilation. It reads the raw source code character by character and groups characters into TOKENS — the smallest meaningful units of the language.
What is a token?
A token is a pair (type, value):
- Keywords: INT, IF, WHILE, RETURN — reserved words of the language
- Identifiers: variable names, function names — matched by regex [a-zA-Z_][a-zA-Z0-9_]*
- Literals: numbers (42, 3.14), strings ("hello"), booleans (true, false)
- Operators: +, -, *, /, ==, !=, <=, >=, &&, ||
- Punctuation: (, ), {, }, [, ], ;, ,, .
- Whitespace and comments: usually DISCARDED (not passed to parser)
The scanner ignores whitespace and comments — they carry no semantic meaning (usually). This is why you can format code however you like.
How it works — Finite Automata:
Each token type is described by a regular expression. The scanner builds a DFA from all these regexes combined. As it reads characters, it follows DFA transitions. When it reaches an accepting state and the next character doesn't extend the current token, it emits the token and resets.
Maximal munch rule: always consume the longest possible token. "==" is one EQUALS_EQUALS token, not two EQUALS tokens. "integer" is one IDENTIFIER, not keyword INT + identifier "eger".
Lexer vs Scanner: used interchangeably. The tool that builds scanners from regex specs is called lex (Unix) or flex (faster lex). Python's tokenize module does exactly this.
Symbol Table: identifiers found during lexing are entered into the symbol table — a data structure that maps names to their properties (type, scope, memory location). Built during lexing, enriched during semantic analysis.
Lexical errors: if no token pattern matches the current input → lexical error. "int x = @5;" → '@' is not part of any valid token → error: invalid character '@'.
Token attributes: some tokens carry values needed later. NUM token carries the numeric value. ID token carries the string name. The parser needs these to build the AST correctly.DIAGRAM
SOURCE: result = 42 + x * 3.14; CHARACTER STREAM: r e s u l t = 4 2 + x * 3 . 1 4 ; LEXER (DFA-based): ┌───────────────────────────────────────────────┐ │ Read 'r','e','s','u','l','t' → IDENTIFIER │ │ Skip ' ' → whitespace discarded │ │ Read '=' → ASSIGN │ │ Skip ' ' │ │ Read '4','2' → NUMBER(42) │ │ Skip ' ' │ │ Read '+' → PLUS │ │ Skip ' ' │ │ Read 'x' → IDENTIFIER(x) │ │ Skip ' ' │ │ Read '*' → MULTIPLY │ │ Skip ' ' │ │ Read '3','.','1','4' → FLOAT(3.14) │ │ Read ';' → SEMICOLON │ └───────────────────────────────────────────────┘ TOKEN STREAM: [ID:result][=][NUM:42][+][ID:x][*][FLOAT:3.14][;] DFA for IDENTIFIERS: [a-zA-Z_][a-zA-Z0-9_]* →q0 ──[a-z,A-Z,_]──→ q1* ──[a-z,A-Z,0-9,_]──→ q1* (q1 is accept state — any alpha/underscore continues)
CODE