Skip to content

Design decisions

Re-evaluating on every keystroke makes interpretation speed the dominant cost. Compiling once and executing repeatedly is the standard answer, and it also makes bounding execution straightforward.

  1. Keystrokethe document is evaluated again
  2. Walk the treea virtual call and a branch at every node
  3. Walk it againnext keystroke, same work

The structure is convenient to build and expensive to run. Every evaluation pays the cost of navigating the shape as well as the cost of doing the arithmetic.

The trade

Precedence climbing rather than a generated parser

Section titled “Precedence climbing rather than a generated parser”

Adding an operator should be registering a parselet, not regenerating a grammar. Precedence climbing keeps the extension point small and the parser readable, and it handles associativity without special cases.

Natural phrasing could have been handled in the parser with lookahead. Making it a separate token-rewriting stage keeps the parser simple and makes phrase fusion something a package can contribute declaratively.

It is also what makes prose safety achievable. Words are recognised in context rather than reserved globally.

Units are case-sensitive and are never remapped

Section titled “Units are case-sensitive and are never remapped”

m is metres and M is a millions suffix. Accepting both cases, or guessing between plausible aliases, produces confidently wrong answers. Refusing to guess is the safer default for a tool doing arithmetic on someone’s real numbers.

“No aliases” means no remapping: mt is not silently read as t, and floz is not silently read as a US fluid ounce. It does not mean a unit gets only one spelling. The conversion table carries several spellings for most units, and all of them are accepted, so lb, lbs and pounds all work. Those are the unit’s own names, not guesses about what you meant.

The same principle removed an inherited behaviour where m was read as minutes whenever the other side of a conversion was a time unit. It made today + 5 m add five minutes to someone who wrote metres, which is precisely the confidently wrong answer this rule exists to prevent.

  1. 15m5.00 m
  2. 25M5,000,000

Lower case m is metres. Upper case M is the millions suffix. These are the values the engine returns, and they differ by six orders of magnitude and a dimension.

Refusing to guess

Both could have been exceptions or nulls. Making them value types means they propagate through arithmetic and arrive with their cause intact, rather than being coerced to zero and producing a plausible but wrong result.

flowchart TD
  price["AAPL price<br/><i>pending</i>"] --> mul["× 100"]
  mul --> pendingOut["<i>pending</i><br/>shown as waiting"]

  bad["A rate that failed to fetch<br/><i>error: no such symbol</i>"] --> add["+ 50"]
  add --> errOut["<i>error: no such symbol</i><br/>the cause survives the arithmetic"]

  coerce["If either were coerced to 0"] --> wrong["0 and 50<br/>plausible, authoritative, wrong"]
The same document, with a live price that has not arrived yet.

No public holidays in working-day arithmetic

Section titled “No public holidays in working-day arithmetic”

Correct holiday handling needs a region-specific, continuously updated calendar. Offering an approximation would be worse than not offering it, because the answer would look authoritative while being wrong for most users.