Home

dev / tinycast

publicthedavidweng/tinycast· sync paused
Overview Code History Branches Pull requestsIssuesInsights
main
HomeOverview Code PRsIssues

Add typed calculator expressions (#70)

2 months ago

1a18b60
Authored
Andrew Pashynnyk7/28/2026, 9:56:11 PM
* 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

8 files changed
  • Tinycast.xcodeproj/project.pbxproj+4−0
  • Tinycast/Core/Calculator/CalcCurrency.swift+10−1
  • Tinycast/Core/Calculator/CalcEngine.swift+99−1
  • Tinycast/Core/Calculator/CalcParser.swift+65−14
  • Tinycast/Core/Calculator/CalcQuantity.swift+617−0
  • Tools/calc-test.swift+204−4
  • docs/calculator.md+58−5
  • docs/ui.md+1
−1