decimal: std surface, conversions, Encode/Decode Exact and conformance programs #336

Closed
opened 2026-10-12 13:15:57 +00:00 by orion · 6 comments
Collaborator

Last step of the epic #34, after #332, #334 and #335 (all merged).

Goal

What is left of Decimal after the runtime, the std surface of std/number and the literals landed: the encoding side and the status. EncodedValue gets the case Exact(value: Decimal) that ENCODING.md section 7 declares, JSON keeps the digits of a number it reads into a Decimal, and the docs stop saying "planned". Specification: DECIMAL.md sections 11 and 14 (slice 4), ENCODING.md section 7 and section 16 item 3, RELEASE.md 1.0 list, FIRST-PARTY.md gap 12.

Already on main (checked 2026-10-28, not part of this issue): rounded, divided, scale, normalized, unscaledText, Decimal.of, Decimal.from(Int64/UInt64), Decimal.tryFrom(String), Int64/UInt64.tryFrom(Decimal), Float64.from(Decimal), Rounding, Show, Hash, no Decimal entry left Planned in compiler/src/backend/c/natives.trb, the conformance programs tests/conformance/decimal-arithmetic and decimal-literals (rows of DECIMAL.md section 12 for text, equality, + - * / %, both rounding tables, conversions).

Design (decided by the loop, see the comment below)

  • EncodedValue.Exact(value: Decimal) is added after Float (order as in ENCODING.md section 7). Values.decimal completes it, ValueDecoder.decimal reads it (and keeps reading String, Int, Float as today), show()/plain() print the Decimal unquoted, Encode for EncodedValue calls target.decimal. ValueDecoder.float/number accept an Exact through Float64.from, int only a whole number in range.
  • JSON writes a Decimal as the number token with the show() text (already so). JSON reading: a number token with fraction or exponent stays an EncodedValue.Float when printing the parsed Float64 gives the token back, and becomes EncodedValue.Exact otherwise (19.90, 0.1234567890123456789), so ordinary floats and the existing tests keep their shape and a Decimal field never goes through a lossy Float64. Integers keep Int/Unsigned.
  • YAML: the decoder already reads a scalar into a Decimal from its text (std/yaml/src/decoder.trb); only tests and, for the generic tree, the same rule as JSON (resolve.trb).
  • TOML has no decimal type: a Decimal is written as a string and read back from a string, exact. A TOML float token read into a Decimal goes through its shortest Float64 text; this is documented, not changed.
  • std/sandbox crossing gets a tag for Exact (text form). Every other match over EncodedValue in std/ is made exhaustive.
  • Out of scope: Postgres/MySQL/BSON drivers (SqlValue.Numeric, FIRST-PARTY gap 12 is closed by this issue only for the language side), Power and roots, locale formatting.

Acceptance criteria

  • EncodedValue.Exact exists; Values, ValueDecoder, Show/plain, Encode for EncodedValue, sandbox crossing, std/uri and std/expression handle it; torb ir --statistics . no longer counts the three Decimal findings for encoding.
  • JSON, YAML and TOML round trip a derived Decimal field exactly: Price(amount: 19.90) writes and reads back 19.90 (scale kept), a value of 40 digits and a negative scale (1e3) survive; JSON also reads a hand-written {"amount": 19.90} and {"amount": 0.1234567890123456789} exactly and refuses a string. Tests in std/json, std/yaml, std/toml and std/encoding (*.test.trb).
  • A float token that is exact as Float64 (1.5) is still EncodedValue.Float in JSON and YAML (existing tests unchanged).
  • One conformance program tests/conformance/decimal-encoding.trb (with .expected, .exit, .allocations like its neighbours) with identical output in VM and native.
  • Docs: ENCODING.md section 7 / 16 item 3 and the ir --statistics note updated; DECIMAL.md status line "built" with the slice table done; language/values-and-types/decimal.md status: planned to stable without the banner; ROADMAP "planned natives" and RELEASE.md/FIRST-PARTY.md Decimal rows marked done; torb docs skill docs skills regenerated and skills/ committed.

Seed impact

No new native, no syntax change, no renamed std name. The compiler finds EncodedValue cases by name (variantNamed in ir/lower/quoted.trb, the cases Int, Float, Bool, String, Nothing), so an added case is additive. The implementer still checks with torb ir --statistics . that the compiler's own program, which links std/encoding, builds with the published seed: if it does not, apply the two-commit rule of CLAUDE.md "Seed and breaking changes" (teach the form, refresh the seed by Torben, then migrate); the loop does not refresh the seed.

Size

One PR, two commits: feat(encoding): EncodedValue.Exact and exact Decimal in JSON, YAML, TOML (with tests and the conformance program), docs(number): Decimal is stable (statuses, regenerated skills/).

Last step of the epic #34, after #332, #334 and #335 (all merged). ## Goal What is left of `Decimal` after the runtime, the std surface of `std/number` and the literals landed: the encoding side and the status. `EncodedValue` gets the case `Exact(value: Decimal)` that ENCODING.md section 7 declares, JSON keeps the digits of a number it reads into a `Decimal`, and the docs stop saying "planned". Specification: [DECIMAL.md](https://git.torb.dev/torbscript/language/src/branch/main/docs/design/DECIMAL.md) sections 11 and 14 (slice 4), [ENCODING.md](https://git.torb.dev/torbscript/language/src/branch/main/docs/design/ENCODING.md) section 7 and section 16 item 3, RELEASE.md 1.0 list, FIRST-PARTY.md gap 12. Already on `main` (checked 2026-10-28, not part of this issue): `rounded`, `divided`, `scale`, `normalized`, `unscaledText`, `Decimal.of`, `Decimal.from(Int64/UInt64)`, `Decimal.tryFrom(String)`, `Int64/UInt64.tryFrom(Decimal)`, `Float64.from(Decimal)`, `Rounding`, `Show`, `Hash`, no `Decimal` entry left `Planned` in `compiler/src/backend/c/natives.trb`, the conformance programs `tests/conformance/decimal-arithmetic` and `decimal-literals` (rows of DECIMAL.md section 12 for text, equality, `+ - * / %`, both rounding tables, conversions). ## Design (decided by the loop, see the comment below) - `EncodedValue.Exact(value: Decimal)` is added after `Float` (order as in ENCODING.md section 7). `Values.decimal` completes it, `ValueDecoder.decimal` reads it (and keeps reading `String`, `Int`, `Float` as today), `show()`/`plain()` print the `Decimal` unquoted, `Encode` for `EncodedValue` calls `target.decimal`. `ValueDecoder.float`/`number` accept an `Exact` through `Float64.from`, `int` only a whole number in range. - JSON writes a `Decimal` as the number token with the `show()` text (already so). JSON reading: a number token with fraction or exponent stays an `EncodedValue.Float` when printing the parsed `Float64` gives the token back, and becomes `EncodedValue.Exact` otherwise (`19.90`, `0.1234567890123456789`), so ordinary floats and the existing tests keep their shape and a `Decimal` field never goes through a lossy `Float64`. Integers keep `Int`/`Unsigned`. - YAML: the decoder already reads a scalar into a `Decimal` from its text (`std/yaml/src/decoder.trb`); only tests and, for the generic tree, the same rule as JSON (`resolve.trb`). - TOML has no decimal type: a `Decimal` is written as a string and read back from a string, exact. A TOML float token read into a `Decimal` goes through its shortest `Float64` text; this is documented, not changed. - `std/sandbox` crossing gets a tag for `Exact` (text form). Every other `match` over `EncodedValue` in `std/` is made exhaustive. - Out of scope: Postgres/MySQL/BSON drivers (`SqlValue.Numeric`, FIRST-PARTY gap 12 is closed by this issue only for the language side), `Power` and roots, locale formatting. ## Acceptance criteria - [ ] `EncodedValue.Exact` exists; `Values`, `ValueDecoder`, `Show`/`plain`, `Encode` for `EncodedValue`, sandbox crossing, `std/uri` and `std/expression` handle it; `torb ir --statistics .` no longer counts the three `Decimal` findings for encoding. - [ ] JSON, YAML and TOML round trip a derived `Decimal` field exactly: `Price(amount: 19.90)` writes and reads back `19.90` (scale kept), a value of 40 digits and a negative scale (`1e3`) survive; JSON also reads a hand-written `{"amount": 19.90}` and `{"amount": 0.1234567890123456789}` exactly and refuses a string. Tests in `std/json`, `std/yaml`, `std/toml` and `std/encoding` (`*.test.trb`). - [ ] A float token that is exact as `Float64` (`1.5`) is still `EncodedValue.Float` in JSON and YAML (existing tests unchanged). - [ ] One conformance program `tests/conformance/decimal-encoding.trb` (with `.expected`, `.exit`, `.allocations` like its neighbours) with identical output in VM and native. - [ ] Docs: ENCODING.md section 7 / 16 item 3 and the `ir --statistics` note updated; `DECIMAL.md` status line "built" with the slice table done; `language/values-and-types/decimal.md` `status: planned` to `stable` without the banner; ROADMAP "planned natives" and RELEASE.md/FIRST-PARTY.md Decimal rows marked done; `torb docs skill docs skills` regenerated and `skills/` committed. ## Seed impact No new native, no syntax change, no renamed std name. The compiler finds `EncodedValue` cases by name (`variantNamed` in `ir/lower/quoted.trb`, the cases `Int`, `Float`, `Bool`, `String`, `Nothing`), so an added case is additive. The implementer still checks with `torb ir --statistics .` that the compiler's own program, which links `std/encoding`, builds with the published seed: if it does not, apply the two-commit rule of `CLAUDE.md` "Seed and breaking changes" (teach the form, refresh the seed by Torben, then migrate); the loop does not refresh the seed. ## Size One PR, two commits: `feat(encoding): EncodedValue.Exact and exact Decimal in JSON, YAML, TOML` (with tests and the conformance program), `docs(number): Decimal is stable` (statuses, regenerated `skills/`).
Author
Collaborator

[loop:torbscript] refine: slice 4 of #34, next: waiting (dependency #335)

[loop:torbscript] refine: slice 4 of #34, next: waiting (dependency #335)
Author
Collaborator

[loop:torbscript] decided by the loop: refined to the part that is really open (the std surface, natives and the arithmetic conformance programs are already on main); body rewritten with testable criteria, design for EncodedValue.Exact and JSON reading, seed impact (none expected, re-check with ir --statistics .) and size (one PR, two commits). Reasoning in the comment on #34. Next: ready.

[loop:torbscript] decided by the loop: refined to the part that is really open (the std surface, natives and the arithmetic conformance programs are already on `main`); body rewritten with testable criteria, design for `EncodedValue.Exact` and JSON reading, seed impact (none expected, re-check with `ir --statistics .`) and size (one PR, two commits). Reasoning in the comment on #34. Next: ready.
Author
Collaborator

[loop:torbscript] claim implement by session:loop-torbscript-20261028 until 2026-10-28T19:56:27Z

[loop:torbscript] claim implement by session:loop-torbscript-20261028 until 2026-10-28T19:56:27Z
Author
Collaborator

[loop:torbscript] implement: PR #544 open (feat(encoding) + docs, two Conventional Commits, Closes #336); fast CI checks green (commit messages, change scope, pdisc), the heavy jobs (gates, bootstrap, tiers, cross builds) still queued at handoff; worker hit its 30 min budget while waiting on CI, worktree cleaned. Worker usage: 1 run, 30 min (token count not reported). next: waiting (CI on PR and merge gate)

[loop:torbscript] implement: PR https://git.torb.dev/torbscript/language/pulls/544 open (feat(encoding) + docs, two Conventional Commits, Closes #336); fast CI checks green (commit messages, change scope, pdisc), the heavy jobs (gates, bootstrap, tiers, cross builds) still queued at handoff; worker hit its 30 min budget while waiting on CI, worktree cleaned. Worker usage: 1 run, 30 min (token count not reported). next: waiting (CI on PR and merge gate)
Author
Collaborator

[loop:torbscript] claim review by session:loop-torbscript-20261028 until 2026-10-28T19:38:19Z

[loop:torbscript] claim review by session:loop-torbscript-20261028 until 2026-10-28T19:38:19Z
Author
Collaborator

[loop:torbscript] review: tier A (linux-x64) was red on d0bc3307 because of a real defect in the PR, not main or a flake: Json().encode price, Yaml().encode price and EncodedValue.Exact(..).encode builder do not parse as command calls (receiver is a call expression), which broke check ., the std/example tests, the repository typing test and doc --check std; plus torb format --check on four new decimal tests and two stale generated indexes (docs/language/values-and-types/index.md, skills references index). Fixed: parenthesised encode(...) calls in std/encoding/tests/decimal.test.trb and tests/conformance/decimal-encoding.trb, formatter run on the six touched test files, torb docs index and torb docs skill regenerated. Commits 11226d86 (test(encoding)) and a8228be9 (docs), normal push, no rewrite. Locally only format --check and check on the two fixed files (clean), no suite. next: waiting (CI on PR #544 and merge gate)

[loop:torbscript] review: tier A (linux-x64) was red on d0bc3307 because of a real defect in the PR, not main or a flake: `Json().encode price`, `Yaml().encode price` and `EncodedValue.Exact(..).encode builder` do not parse as command calls (receiver is a call expression), which broke `check .`, the std/example tests, the repository typing test and `doc --check std`; plus `torb format --check` on four new decimal tests and two stale generated indexes (docs/language/values-and-types/index.md, skills references index). Fixed: parenthesised `encode(...)` calls in std/encoding/tests/decimal.test.trb and tests/conformance/decimal-encoding.trb, formatter run on the six touched test files, `torb docs index` and `torb docs skill` regenerated. Commits 11226d86 (test(encoding)) and a8228be9 (docs), normal push, no rewrite. Locally only format --check and check on the two fixed files (clean), no suite. next: waiting (CI on PR #544 and merge gate)
argus closed this issue 2026-10-28 17:44:47 +00:00
Sign in to join this conversation.
No labels
loop
ready
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
torbscript/language#336
No description provided.