2 months ago
1a18b60* Add typed calculator expressions
Evaluate arithmetic across compatible units and consent-gated currencies
while preserving the leftmost unit, standard precedence, parentheses,
percentages, and complete-expression conversion suffixes. Keep
unsupported derived dimensions explicit instead of guessing.
Recognize attached k/K as a thousands suffix without taking spaced k
away from Kelvin, and keep the last complete result visible when the
query ends with a binary operator. Preserve the result badge so partial
quantity expressions continue to name their unit.
Expand the Foundation-only calculator harness across mixed units, scalar
operations, percentages, currencies, invalid dimensions, conversions,
and incomplete queries.
Closes #64
* Resolve review findings from #70
Keep half-typed units silent. A dimensioned side against a bare number
now returns nil instead of an error card: "10kg + 5" is one keystroke
short of "10kg + 5kg", and "1hr 30" of "1hr 30min", so the old behavior
flashed "Cannot add Weight and a unitless value" mid-typing and made
"5 feet 3 inches" oscillate value/error/value/error/value. Errors stay
for input that can only be a mistake (1kg + 1m, currency vs unit).
Fix the card's expression echo. Sign-first money echoed code-first
("USD 10 + EUR 5"); it now reads "10 USD + 5 EUR". Signs, parentheses
and postfix % / ! hug their operand via a glue rule in expressionText,
replacing the two post-hoc string scans, so "10 kg × 3 %" becomes
"10 kg × 3%" and "- 5 kg" becomes "-5 kg". A partial after a conversion
echoes the typed text ("10km to mi ×") instead of the conversion's own
shortened echo, and tokenQuery keeps radix prefixes so "0xff -" still
reports Hexadecimal → Decimal.
Drop `of` as a partial operator so a stray English word after a number
("10 of") stays a search.
Share one factorial between CalcParser and QuantityParser, hoist the
tokenizer's two `k` lookaheads behind the k/K guard so they stop running
for every numeric literal, and collapse the duplicated number-token
extraction and the returning for-where loop.
* Answer in the last unit typed, and let a bare number take its unit
Two semantics changes, matching Raycast and the behavior asked for in #64.
`+` / `-` now convert the left side into the right operand's unit rather
than the reverse, so `5feet + 1m` is `2.524 m` and `10kg + 500g` is
`10,500 g` — the unit you finished writing is the one you were thinking
in. Chains stay left-associative, so `1kg + 500g + 2lb` ends in pounds,
and a `to` / `in` suffix still overrides everything.
Composite notation is exempt. `5 feet 3 inches` and `1hr 30min` are one
quantity, not a sum, so they keep answering in the leading unit.
`peekBinary` already separated the two cases — it reports
`consumesToken: false` for the invisible `+` between adjacent quantities
— so `addOrSubtract` keys the unit choice off that flag rather than
inventing a second signal.
A bare number now takes the unit it is written against: `5kg+5` is
`10 kg`, `$10 + 5` is `15.00 USD`. Under adjacency it still returns nil,
because there a bare trailing number is a unit mid-typing: `1hr 30` is
one keystroke short of `1hr 30min`, and `31 hr` is a worse answer than
none. That keeps the typing progressions card-flicker-free.
Also stop handing an expression to the bare auto-conversion path once it
contains an operator, so `2 * 5kg` is `10 kg` instead of `22.05 lb` and
matches `5kg * 2`. Only a bare quantity (`50cm`, `1m`) auto-converts now.
426 assertions pass.
---------
Co-authored-by: abue-ammar <iabueammar@gmail.com>Parent489d94e