Exercise 3: Tokenizing "1..2" — Possible Solution ==================================================================== RESULT ------------------------------ '1..2' tokenizes as: [NUMBER('1'), DOT('.'), DOT('.'), NUMBER('2')] Four tokens: a number, two separate DOT tokens in a row, then another number - not a single malformed number token, and not an error. WHY THIS HAPPENS ------------------------------ The number-scanning logic in the chapter's own lexer only consumes a decimal point if a DIGIT immediately follows it (the check is `source[i] == '.' and source[i+1].isdigit()`). After consuming the first '1', the lexer looks ahead and sees '.' followed by another '.', not a digit - so it correctly stops the number at '1' rather than greedily consuming "1." and then having nowhere sensible to put the second dot. The two dots then tokenize independently as two DOT tokens, and the trailing '2' becomes its own NUMBER token. IS THIS SENSIBLE, OR A GAP? ------------------------------ This is genuinely sensible, not a bug - and it's a well-known real pattern in language design, not something invented for this exercise: many real languages (Ruby's Range literals, Rust's exclusive ranges) use exactly this ".." syntax for a "from X to Y" range, and their own lexers rely on precisely this same lookahead discipline to distinguish "1..2" (a range) from "1.2" (a single float) correctly. A future chapter's own parser could recognize two consecutive DOT tokens as a range operator, or the lexer itself could be extended to merge them into a single RANGE token directly - either approach builds cleanly on top of the current, correct tokenization, rather than needing to un-do a wrong one. WHY THIS WORKS AS AN ANSWER ------------------------------ This reveals that the chapter's own "require a digit after the dot" number-scanning rule wasn't just about handling floats correctly - it was, as a direct side effect, already laying the groundwork for a range-operator-friendly grammar, without that being an explicit design goal at the time. Getting the narrower rule right (a float's own decimal point vs. any other appearance of '.') is what makes the broader, currently-unbuilt feature (ranges) possible to add later without revisiting this chapter's own lexer at all.