Skip to content

Shares one fraction expander for durations - #160

Merged
johnnyt merged 1 commit into
mainfrom
pts-kc88-one-fraction-expander
Oct 1, 2026
Merged

johnnyt merged 1 commit into
mainfrom
pts-kc88-one-fraction-expander

Conversation

@johnnyt

@johnnyt johnnyt commented Oct 1, 2026

Copy link
Copy Markdown
Member

What

The grammar (a duration literal) and the parse behind ::duration and parseDuration each turned a fractional duration component into whole units with their own arithmetic. They now call one function, expandFraction in src/duration-units.ts, beside the unit table they already shared.

  • The shared expander keeps the grammar's arithmetic: the written digits are read as an integer, the shared factors of the unit's weight and the power of ten are cancelled, and the remainder is spent down the ladder of days and smaller units. It answers each amount with its unit row.
  • The grammar's expandFraction in src/parser.ts is now a thin adapter that names each amount's unit by its suffix. expandComponents and its refusal of a unit named twice after expansion are untouched.
  • The cast's readDuration in src/cast.ts calls the shared expander and keeps accumulating a repeated unit. The check that refuses a component past the largest safe integer stays where it was, after the components are added.
  • The two arithmetics answered alike: a fraction with no trailing zero is exact only when its places are at most the twos (or fives) its unit's weight carries, so the grammar's place bound and the cast's long multiplication refused the same texts.

Provenance

  • Trailing zeros are now counted back from the end of the digits. The grammar's end-anchored pattern took time quadratic in a long zero run (about 2.4 s to compile a literal with an 80,000-zero fraction at the base of this branch). Moving that pattern into the shared expander would have brought the same cost to parseDuration, which read such a text in about 2 ms. Both now read it in linear time. This is an engineering choice inside the bead's footprint. No answer changes, so there is no changelog fragment.
  • New test file test/fraction-expansion.test.ts. It checks that a literal and a parsed text expand a fraction alike and refuse an inexact one alike, and that a long zero run reads in linear time. The existing parser, cast and parse-duration test files are unchanged.
  • The comment above the safe-integer test in test/duration-to-milliseconds.test.ts now states the reason a plain number is kept, with its date: the bound is enforced where a text is read.

Checks

  • Full gate (mise exec -- pnpm run gate) green on the rebased head.
  • Sabotage, each run with scripts/sabotage.mjs and caught on an assertion in the new test file: keeping the trailing zeros; answering the integer part for an inexact fraction; adding every cast amount to the written unit; naming every grammar amount by the written unit; stripping the zeros with an end-anchored pattern. Turning off the factor cancellation in the shared expander also turns the existing parser and cast tests red.

Review tier: gate (an internal refactor; the in-turn review round ran before this request).

The grammar and the duration cast each turned a fractional component
into whole units with their own arithmetic. Both now call one
expandFraction in the unit-table module: whole-number arithmetic over
the written digits, the remainder spent down the same ladder. The
grammar keeps its duplicate-unit refusal and the cast keeps
accumulation and its safe-range check where they were.

Trailing zeros are counted back from the end instead of matched by an
end-anchored pattern, which took time quadratic in a long zero run.

A new test pins that a literal and a parsed text expand a fraction
alike, refuse an inexact one alike, and read a long zero run in
linear time. The comment above the safe-integer conversion test now
states its reason.

Refs: pts-kc88
@johnnyt
johnnyt merged commit 02d4a21 into main Oct 1, 2026
1 check passed
@johnnyt
johnnyt deleted the pts-kc88-one-fraction-expander branch October 1, 2026 12:41
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.

1 participant