Skip to content

feat(calc): evaluate mixed unit expressions - #6

Merged
cursor[bot] merged 8 commits into
mainfrom
cursor/calc-expressions-2d41
Aug 23, 2026
Merged

feat(calc): evaluate mixed unit expressions#6
cursor[bot] merged 8 commits into
mainfrom
cursor/calc-expressions-2d41

Conversation

@Aymericr

@Aymericr Aymericr commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Description

Plan 032 go, recorded as D73.

@pascal-app/lingo/calc evaluates closed arithmetic over quantities and numbers — inspired by mathjs expression trees, without scopes, functions, or dimensional algebra.

  • calc('7m*2') → 14 million (SCALE_ASSUMED); format as "14 million" / "14,000,000" / "14e6" / "14m"
  • calc('9min x 4') → 36 min, or 0.6 h
  • calc('half of 56kg+1700g') → 28.85 kg

lingo() stays range-first. 5-10 kg is still a range. 2+3 kg was a silent CJK-adjacent-range false positive and now fails with TRAILING_INPUT; calc('2+3 kg') is 5 kg.

Completions still require a leading = so mixed fields do not steal dashes. quantityField({ calc }) uses looksLikeCalc (never -; glued 5/10 kg stays a fraction) and then evaluates, so 12 * 0.75 kg and 10 kg / 2 work unprefixed.

Always-ship parser fixes: CJK adjacent-range gated on adjacentRange; additive affine compounds warn AFFINE_DELTA_ASSUMED and still delta-convert.

Quality pass after adversarial review:

  • Compact / latex keep tight unit symbols (25°C, 10%, $5) instead of dropping them.
  • calc('1e999') reports a single NONFINITE instead of masking it as NO_VALUE.
  • SCALAR_EXPECTED copy is honest for n / q, not only q * q.
  • Cross-currency arithmetic is RATE_REQUIRED (same guard as ranges), not $15 at full confidence or a masked NONFINITE. The eval try/catch that relabeled conversion throws is gone.
  • Size budgets stay green (./calc marginal 4.10 / 4.1). bun run check: 1068 passed, 1 skipped.

Holdover (parked in plans/backlog.md): calc('10 L / 2 L', { kind: 'mass', unit: 'kg' }) still re-attaches the implied unit. A ratio flag to skip that busts the 4.1 kB budget.

Checklist

Gates

  • bun run check green (typecheck, tests, build, size budgets, corpus gate, zero-deps gate)
  • bun run lint green (Biome via Ultracite; rule mismatches go in biome.jsonc with a reason, never inline)
  • Corpus diff is ADDITIVE — 2+3 kg is not an English corpus row; D73 records the interpretation change

API surface (public exports touched)

  • Semver impact stated: new ./calc entry (additive). 2+3 kg previously returned a range at full confidence (bug fix / interpretation change, not in corpus). Affine warning is additive. Next 0.x release can take this as a minor with a Changed note.
  • wiki/api-design.md checklist run
  • TSDoc @example on every new public symbol
  • README / llms.txt / site docs updated together

Knowledge layer

  • Plan 032 operand table records RATE_REQUIRED for rate-based mixed units
  • CHANGELOG updated under [Unreleased]
  • mathjs credited in wiki/inspiration.md (shipped as ./calc)
Open in Web Open in Cursor 

Users type light math into quantity fields and models emit expressions
they cannot reliably compute. @pascal-app/lingo/calc is a closed
calculator over already-parsed values — no variables, functions, or
dimensional algebra — so 7m*2 is 14 million, 9min x 4 is 36 min, and
half of 56kg+1700g is 28.85 kg.

lingo() stays range-first: 5-10 kg is still a range, 2 ft + 3 in is
still a compound. 2+3 kg was a silent CJK-adjacent-range false
positive and now fails with TRAILING_INPUT; calc('2+3 kg') is 5 kg.
Completions and quantityField inject calc with trigger '=' so mixed
fields do not steal dashes.

Glued m at an operator boundary is million unless kind is length or
duration (SCALE_ASSUMED); spaced 7 m is meters; 1m80 stays 1.80 m.
Results format as words, grouped, scientific, or compact. expression
is two-way infix; latex is display-only. Additive affine compounds
warn AFFINE_DELTA_ASSUMED and still delta-convert.

Budgets recalibrated in D73: the calculator is ~4 kB marginal / ~35 kB
standalone. Full and core grow because the CJK gate, affine warning,
and calc-scale matching must run inside parseQty before unit matching.

Co-authored-by: Aymeric Rabot <Aymericr@users.noreply.github.com>
@vercel

vercel Bot commented Aug 22, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
lingo Ready Ready Preview Aug 22, 2026 7:51pm

@github-actions

github-actions Bot commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

React Doctor found no new issues. 🎉

Reviewed by React Doctor for commit 910fafd.

1700 g was typeset as 1.7 × 10^3 because latex reused the compact
scientific threshold meant for million-scale numbers. Docs import for
the Calculations snippet and regenerated schema artifacts (five new
issue codes) land in the same change so check stays green.

Co-authored-by: Aymeric Rabot <Aymericr@users.noreply.github.com>
Adversarial pass: quantityField missed division, compact/latex dropped
tight symbols, snapshot restore turned 1e999 into NO_VALUE, and
SCALAR_EXPECTED claimed 10 / 2 kg was two quantities.

Co-authored-by: Aymeric Rabot <Aymericr@users.noreply.github.com>
Keep unit/latex and operator-slash behavior; drop the extra gzip that
tipped ./calc, ./ai, and quantityField-only over their D73 lines.

Co-authored-by: Aymeric Rabot <Aymericr@users.noreply.github.com>
Reuse the number parser's issue instead of emitting a second copy.

Co-authored-by: Aymeric Rabot <Aymericr@users.noreply.github.com>
…nless

Cross-currency +/−/÷ reported RATE_REQUIRED instead of adding factors of 1
or relabeling the conversion throw as NONFINITE. Same-kind q/q stays a
number even when kind/unit are implied, and quantityField does not stuff
that ratio into the field unit.

Co-authored-by: Aymeric Rabot <Aymericr@users.noreply.github.com>
cursoragent and others added 2 commits August 22, 2026 19:48
Inline the cross-currency guard and drop the implied-unit ratio skip that
tipped ./calc over 4.1 kB. Park the canceled q/q re-unit holdover in the
backlog.

Co-authored-by: Aymeric Rabot <Aymericr@users.noreply.github.com>
p.reg.kind() takes Kind, not Kind | null; require left.kind so the
cross-currency guard typechecks.

Co-authored-by: Aymeric Rabot <Aymericr@users.noreply.github.com>
@cursor
cursor Bot marked this pull request as ready for review August 23, 2026 01:57
@cursor
cursor Bot merged commit 910fafd into main Aug 23, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants