scan
April 26, 2026 ยท View on GitHub
Language-independent lexical scanner.
Overview
The scan package tokenizes source code into a flat slice of Token values.
It is driven entirely by a lang.Spec, making it reusable across languages.
It sits at the start of the pipeline, feeding tokens to goparser.
Key types and functions
Scanner-- holds a*lang.Spec, precomputed lookup tables, and aSourcesregistry. Created viaNewScanner(spec), which builds the lookup tables from the spec's maps.Source-- describes a registered source: name, base offset, length.Sources-- ordered list ofSourceentries mapping global byte offsets to file/line/col. Methods:Add(name, src) int(returns base offset),Resolve(pos) (name, line, col),FormatPos(pos) string.Token-- a single lexical unit: token type (lang.Token), source position, text, and block delimiter lengths.Scan(src string, semiEOF bool) ([]Token, error)-- tokenizes the entire source. WhensemiEOFis true, appends a semicolon at end-of-input if the last token warrants one.Next(src string) (Token, error)-- returns the next single token (used internally byScan).
Internal design
The scanner is a state machine that classifies characters via lang.Spec.CharProp
(a 128-entry ASCII lookup table). NewScanner precomputes several
fixed-size arrays from the spec's maps, eliminating map lookups and regexp
from the hot path:
charTok[128]-- token for single-byteTokenskeys (operators, separators).blockTok[128]-- block token by opening byte ((,{,[).endByte[128]-- end delimiter for single-byte openers (fast path forgetStrandgetBlock).charBlockProp[128]--BlockPropfor single-byte keys.multiStrStart[128]-- flags first byte of multi-byte string/comment starts (e.g.//,/*).
The scanner handles:
- Identifiers and numbers -- classified by character properties.
- Operators -- longest-match greedy scan; if the longest candidate is
not a known token, shorter prefixes are tried. Single-byte operators
resolve via
charTok(no map lookup). - String literals -- delimiters and escape sequences from
Spec.EndandSpec.BlockProp. - Nested blocks --
(),[],{}are matched and balanced at scan time, simplifying the parser. AnErrBlockis returned if input ends mid-block, allowing the REPL to prompt for continuation. - Automatic semicolons -- inserted after newlines when the preceding
token's
SkipSemiproperty is set (mirrors Go's semicolon rules).
Dependencies
lang/-- token types,Spec, character property constants.
Open questions / TODOs
- The scanner is currently ASCII-only (
ASCIILen = 128). Unicode identifier support would require extendingCharPropor switching to a different classification strategy.