Reading Hexadecimal
Two probes in a row wrote numbers in decimal that everyone writes in hex — SHA-256's round constants and a Unicode width table — because the lexer read 0xFF as the integer zero followed by an identifier named xFF. That is a papercut, not a bug: the workaround is trivial and the program still runs. But a papercut you hit twice is data, and the fix is one branch in the lexer. This closes a day of six releases with the smallest change of all, and a note on when a papercut earns its fix.
The part closes on the smallest change in it, and the point of ending here is that smallness was the reason it took six releases to arrive. This is an episode about papercuts: when to ignore one, and when it has earned a fix.
The same stone, twice
A papercut is not a bug. The program compiles, runs, and gives the right answer; something
is just needlessly awkward on the way. The language had one in its number lexer: 0xFF
did not read as the hexadecimal integer 255. It read as the integer 0 immediately
followed by an identifier named xFF, so a line using it failed later with “unbound
variable: xFF” — a confusing error a long way from its cause. The workaround is trivial:
convert the number to decimal by hand and write 255. So for a long time nobody fixed it,
because nobody had a reason to type a hex literal.
Then two probes in this very part did, back to back. SHA-256 has sixty-four round constants that the specification lists in hex, and they went into the code in decimal. The East Asian Width table is a list of Unicode block boundaries that every chart writes in hex, and they went in as decimal ranges. Both worked. Both were harder to check against their sources than they should have been, because a reviewer comparing the code to the spec had to convert every number in their head. One probe writing decimal for hex is a shrug; two in a row is a measurement. A papercut you hit once is noise; a papercut you hit twice, transcribing from a reference both times, is telling you where the language rubs against a real kind of program.
One branch, and the payoff
The fix is one branch in the lexer: on a 0 followed by x or X and a hex digit,
consume the hex digits and convert. 0xFF and 0Xff now lex as ordinary integers — no
new type, the same per-backend width as any other integer literal — and a bare 0x with
no hex digit still reads as 0 then the identifier x, so nothing that used to work
breaks. There is no octal, no binary, no digit separator; none of those has been asked for
twice yet. The visible payoff went straight back into the two probes that argued for it:
the SHA-256 example’s sixty-four round constants and eight initial hash values are now
hex, matching the published specification line for line, and the checksum tool’s
polynomial and masks are hex too — and both still agree with their reference vectors. That
is the whole case for the change: not that the language could not express these programs
before, but that now they read like the documents they were copied from.
The ledger
Six releases in a day, from four small programs, and the shape is worth stating plainly. The integer width and the bitwise operators were a question and its answer — the probe that needed the operators found the width problem underneath first. The byte reader and the byte writer were two halves of the same door. The Mandelbrot renderer found the documentation lying in the language’s favor, and this last one fixed the friction the crypto and Unicode probes both felt. Underneath all of it is one sentence: a language with four backends is really a promise to keep four implementations telling the same story, and the cheapest way to find where the story diverges is to write a small, real program that reaches the exact spot — a hash, a checksum, a picture, a table — and read what each backend says back. The probes were cheap. The truths they compared were not. Where the method goes next is, as ever, not yet decided — which is the point.