Reading — step 1 of 5
Read
Strict Number Grammar
This is the exact number grammar from RFC 8259 — small enough to memorize, strict enough that most languages' built-in float parsers get it wrong:
number = [ "-" ] int [ frac ] [ exp ]
int = "0" | digit1-9 *digit
frac = "." 1*digit
exp = ("e" | "E") [ "+" | "-" ] 1*digit
In English: an optional minus (never a plus); an integer part that is either the single digit 0 or a 1–9 digit followed by any digits; an optional fraction that must contain at least one digit after the dot; an optional exponent that must contain at least one digit, with an optional sign.
Why ban leading zeros? In C and old JavaScript, 010 meant octal 8. JSON removes the ambiguity by making 01 flatly illegal — the only literal starting with 0 is 0 itself (optionally followed by .digits or an exponent). Note -0 is valid, and the grammar puts no ceiling on size: 1e999999 is grammatically perfect JSON even though it overflows every double (RFC 8259 only promises good interop for numbers within IEEE-754 double range).
The canonical rejects — make each one a test in your head:
| Input | Why it fails |
|---|---|
+5 | leading plus not in the grammar |
01, 00 | leading zero |
.5 | integer part required |
5. | frac requires 1+ digits after . |
5e | exp requires 1+ digits |
--5 | one minus, in one place |
0x1F | no hex |
NaN, Infinity | not in the grammar at all |
The idiom: translate the ABNF into a regex
The grammar is regular (no recursion), so a regex is the honest transcription:
Each group is one production: (0|[1-9][0-9]*) is int, (\.[0-9]+)? is [frac], ([eE][+-]?[0-9]+)? is [exp].
Trap 1 — re.match instead of re.fullmatch. match anchors only the start, so 1.5x and 5..2 sail through on their valid prefixes. Anchor both ends.
Trap 2 — using float() as the validator. Python's float() happily accepts +5, 01, Infinity, nan, 1_000 (underscores!), and surrounding whitespace. It answers "can Python make a float from this?", which is a much looser question than "is this an RFC 8259 number?". The exercise exists precisely because those two questions differ.
This strictness is the point of JSON: a lenient parser accepts documents that every other conforming implementation rejects, and the bug surfaces later, in someone else's service.
Your exercise
Validate one candidate per line: print OK or an error. The grader-caught mistake is the error text: every invalid input — 01, +5, .5, 5., 5e, --5, 00, 1., 0x1F, NaN, Infinity — must print exactly the same uniform line ERR invalid number. Do not emit per-case reasons like "leading zero"; whatever diagnosis your code makes internally, the reason string it returns must be invalid number, letter for letter. And confirm the accepts against the hidden tests: -0, 1.0, 1e+10, 1e-10, and -0.5 are all OK.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…