formats: std/toml #77

Closed
opened 2026-10-05 21:07:17 +00:00 by orion · 5 comments
Collaborator

Context

TOML on Encode/Decode with its tree.

Source

Acceptance criteria

  • The package on Encode and Decode in the versions of the table
  • The format's public test suite passes
  • A page under docs/standard-library/

Dependencies

None known.

Moved from the planning lists of the docs; the design record stays the specification.

## Context TOML on `Encode`/`Decode` with its tree. ## Source - [ROADMAP.md, format versions](https://git.torb.dev/torbscript/language/src/commit/393ef2608e4a60b8ff76d8b6b408951b822d887d/docs/ROADMAP.md#L185) - [ENCODING.md section 9](https://git.torb.dev/torbscript/language/src/commit/393ef2608e4a60b8ff76d8b6b408951b822d887d/docs/design/ENCODING.md#L709) ## Acceptance criteria - [ ] The package on `Encode` and `Decode` in the versions of the table - [ ] The format's public test suite passes - [ ] A page under `docs/standard-library/` ## Dependencies None known. _Moved from the planning lists of the docs; the design record stays the specification._
Author
Collaborator

[loop:torbscript] intake: in scope (author orion, label loop), sources ROADMAP.md format versions and ENCODING.md section 9, not a duplicate, no collision with a design record. TorbScript package on Encode/Decode; the public test suite runs in forge CI; no seed impact expected. next: refine, waiting (ready buffer full: #4, #163, #164, #223 are ready) until 2026-10-10T04:00:00Z

[loop:torbscript] intake: in scope (author orion, label loop), sources ROADMAP.md format versions and ENCODING.md section 9, not a duplicate, no collision with a design record. TorbScript package on `Encode`/`Decode`; the public test suite runs in forge CI; no seed impact expected. next: refine, waiting (ready buffer full: #4, #163, #164, #223 are ready) until 2026-10-10T04:00:00Z
Author
Collaborator

[loop:torbscript] claim refine by session:loop-torbscript-20261009 until 2026-10-10T00:30Z

[loop:torbscript] claim refine by session:loop-torbscript-20261009 until 2026-10-10T00:30Z
Author
Collaborator

[loop:torbscript] refine: ready.

Goal. A new package std/toml: TOML as a format of std/encoding, written in TorbScript like std/yaml/std/json. Toml reads and writes any Encode/Decode type, and TomlValue is the tree of a document for a file nobody has a type for. Versions per the ROADMAP table: reads 1.0 and every later 1.x (the 0.x drafts are not supported), writes the current version unless the caller asks for an earlier one.

Implements. ROADMAP part 3 ("Formats", the row TOML | 1.0 and every later 1.x) and ENCODING.md section 9 (family "text tree", baseline, a naming option). No new design record: the decisions below go into the package page and a "built" note in the ROADMAP; ENCODING.md stays unchanged.

Seed impact: none. New std package only: no syntax, no native, no driver flag, no std name the compiler looks up by string, and the compiler does not import std/toml. One PR, no seed refresh.

Size. One PR, two Conventional Commits (the first is large because the reader is one unit, but the tests are mostly one generated file):

  1. feat(std/toml): read and write TOML 1.x as a tree and through Encode and Decode
  2. docs(std/toml): add the package page and refresh the skills

Files and directories touched.

  • std/toml/project.trb (name = "std/toml"; the workspace member glob std/* picks it up)
  • std/toml/src/: lib.trb (public exports + package doc), toml.trb (Toml, TomlVersion), error.trb (TomlError with line and column), value.trb (tree: TomlValue, TomlTable, date/time types), scanner.trb/parser.trb (text -> tree, TOML ABNF), writer.trb (tree -> text), encoder.trb (Encoder building the tree), decoder.trb (tree -> Decoder, reusing ValueDecoder of std/encoding where it fits)
  • std/toml/tests/: suite-cases.trb (generated, vendored), suite.test.trb, toml.test.trb
  • docs/standard-library/toml.md (new), docs/standard-library/index.md (one entry), docs/ROADMAP.md (part 3: "json and toml exist")
  • skills/torbscript-standard-library/** regenerated with build/release/torb docs skill docs skills
    Model after std/yaml (same file split, same page layout with trb check fragments, order in the front matter after yaml/markdown).

API (match yaml/json conventions).

  • Toml { naming: Naming = Unchanged, strict: Bool = false, version: TomlVersion = current } with encode<Value: Encode>(value): Result<String, TomlError> (a top-level value must be a table; None in a field is omitted; None inside an array is an error, because TOML has no null), decode<Value: Decode>(text): Result<Value, TomlError>, parse(text): Result<TomlValue, TomlError>, write(tree): String.
  • TomlVersion { case Toml10, Toml11 } selects what the writer emits (1.0 output uses no 1.1-only syntax); the reader accepts the superset of every released 1.x grammar.
  • TomlValue cases: String, Integer(Int64), Float(Float64), Boolean, OffsetDateTime, LocalDateTime, LocalDate, LocalTime, Array, Table (keys in document order). The tree does not keep comments (not required by the ROADMAP or this issue).
  • Date/time: values are TomlDate, TomlTime, TomlLocalDateTime, TomlOffsetDateTime value types owned by std/toml (nanosecond precision, offset in minutes, show() gives the RFC 3339 text). They encode/decode as wrappers named by type (ENCODING.md wrapper rule), so a typed field can hold one; TomlOffsetDateTime.toTimestamp() converts to Timestamp. A String field reads the text of a date/time value.

Decided by the loop (record).

  • [loop:torbscript] decided by the loop: date/time values are std/toml's own small value types, not a dependency on #111 (calendar types). Why: #111 is open and unrefined and TOML needs local date/time forms that Timestamp cannot hold; the package is self-contained and a bridge to #111's types is a follow-up once it lands.
  • [loop:torbscript] decided by the loop: encode returns Result<String, TomlError>, not String. Why: TOML cannot express a top-level scalar or a null in an array, so writing can fail; a panic would break the PANICS.md rule.
  • [loop:torbscript] decided by the loop: no Format<TomlError> impl and no framer in this issue. Why: a TOML file is one document, nothing in the acceptance criteria needs streaming; add it if a user needs it.
  • [loop:torbscript] decided by the loop: the reader accepts the superset grammar of all released 1.x versions and version only controls writing. Why: the ROADMAP rule (read the old, write the current unless asked) and one grammar is smaller than two.
  • If the pinned toml-test release has no 1.1 file list, the package reads and writes 1.0 only and the page says so; do not invent 1.1 behaviour from memory.

Acceptance criteria (testable).

  1. Toml().decode<T> / encode round-trip records, nested records, List, Map<String, V>, Option fields (omitted when None, default when absent), variants, and Naming.SnakeCase; strict: true refuses unknown keys. Errors carry line and column.
  2. The reader passes the public suite: every valid case of toml-test (https://github.com/toml-lang/toml-test, MIT) for the supported versions yields the suite's tagged JSON (compared numerically for floats, nan/inf handled), every invalid case is refused. The cases are vendored as a generated std/toml/tests/suite-cases.trb with the suite's MIT notice and the pinned release tag in the header, nothing hand-edited (same as std/yaml/tests/suite-cases.trb).
  3. Every valid case written back by the writer and parsed again gives an equal tree.
  4. Dotted keys, super-tables, arrays of tables, inline tables, duplicate-key and redefinition errors, all four string kinds, integer bases and separators, and inf/nan have tests of their own in toml.test.trb beyond the suite where the suite is silent (typed decode, naming, strict, version option, date/time wrappers, toTimestamp).
  5. docs/standard-library/toml.md exists, its trb check blocks pass, the front matter satisfies torb docs check, the skills are regenerated (torb docs skill --check clean).

Test plan (forge CI, tier A, .forgejo/workflows/gates.yml). torb format --check ., torb check ., manifest --check (new project.trb), torb test --native std/toml/tests and torb test std/toml/tests (VM) via the std/example package loop in tools/gates.sh, the docs checks (docs check, docs skill --check, signature blocks of the std pages). Nothing runs locally in the loop; the implementer may run sh tools/gates.sh a in the worktree, and must keep the VM run of the suite inside the memory limit (split the generated cases into groups like std/yaml if a single run is too slow). Tier B is not triggered (no compiler/src/ir, compiler/src/backend or runtime/ change).

Implementer. sonnet / medium (std package and docs only). Not an owner question: nothing public-facing beyond the new package page, no cost, no account, no breaking change, nothing reserved by a design record.

next: implement

[loop:torbscript] refine: ready. **Goal.** A new package `std/toml`: TOML as a format of `std/encoding`, written in TorbScript like `std/yaml`/`std/json`. `Toml` reads and writes any `Encode`/`Decode` type, and `TomlValue` is the tree of a document for a file nobody has a type for. Versions per the ROADMAP table: reads 1.0 and every later 1.x (the 0.x drafts are not supported), writes the current version unless the caller asks for an earlier one. **Implements.** ROADMAP part 3 ("Formats", the row `TOML | 1.0 and every later 1.x`) and ENCODING.md section 9 (family "text tree", baseline, a naming option). No new design record: the decisions below go into the package page and a "built" note in the ROADMAP; ENCODING.md stays unchanged. **Seed impact: none.** New std package only: no syntax, no native, no driver flag, no std name the compiler looks up by string, and the compiler does not import `std/toml`. One PR, no seed refresh. **Size.** One PR, two Conventional Commits (the first is large because the reader is one unit, but the tests are mostly one generated file): 1. `feat(std/toml): read and write TOML 1.x as a tree and through Encode and Decode` 2. `docs(std/toml): add the package page and refresh the skills` **Files and directories touched.** - `std/toml/project.trb` (`name = "std/toml"`; the workspace member glob `std/*` picks it up) - `std/toml/src/`: `lib.trb` (public exports + package doc), `toml.trb` (`Toml`, `TomlVersion`), `error.trb` (`TomlError` with line and column), `value.trb` (tree: `TomlValue`, `TomlTable`, date/time types), `scanner.trb`/`parser.trb` (text -> tree, TOML ABNF), `writer.trb` (tree -> text), `encoder.trb` (`Encoder` building the tree), `decoder.trb` (tree -> `Decoder`, reusing `ValueDecoder` of `std/encoding` where it fits) - `std/toml/tests/`: `suite-cases.trb` (generated, vendored), `suite.test.trb`, `toml.test.trb` - `docs/standard-library/toml.md` (new), `docs/standard-library/index.md` (one entry), `docs/ROADMAP.md` (part 3: "`json` and `toml` exist") - `skills/torbscript-standard-library/**` regenerated with `build/release/torb docs skill docs skills` Model after `std/yaml` (same file split, same page layout with `trb check` fragments, `order` in the front matter after yaml/markdown). **API (match yaml/json conventions).** - `Toml { naming: Naming = Unchanged, strict: Bool = false, version: TomlVersion = current }` with `encode<Value: Encode>(value): Result<String, TomlError>` (a top-level value must be a table; `None` in a field is omitted; `None` inside an array is an error, because TOML has no null), `decode<Value: Decode>(text): Result<Value, TomlError>`, `parse(text): Result<TomlValue, TomlError>`, `write(tree): String`. - `TomlVersion { case Toml10, Toml11 }` selects what the writer emits (1.0 output uses no 1.1-only syntax); the reader accepts the superset of every released 1.x grammar. - `TomlValue` cases: `String`, `Integer(Int64)`, `Float(Float64)`, `Boolean`, `OffsetDateTime`, `LocalDateTime`, `LocalDate`, `LocalTime`, `Array`, `Table` (keys in document order). The tree does not keep comments (not required by the ROADMAP or this issue). - Date/time: values are `TomlDate`, `TomlTime`, `TomlLocalDateTime`, `TomlOffsetDateTime` value types owned by `std/toml` (nanosecond precision, offset in minutes, `show()` gives the RFC 3339 text). They encode/decode as wrappers named by type (ENCODING.md wrapper rule), so a typed field can hold one; `TomlOffsetDateTime.toTimestamp()` converts to `Timestamp`. A `String` field reads the text of a date/time value. **Decided by the loop (record).** - `[loop:torbscript] decided by the loop: date/time values are std/toml's own small value types, not a dependency on #111 (calendar types). Why: #111 is open and unrefined and TOML needs local date/time forms that `Timestamp` cannot hold; the package is self-contained and a bridge to #111's types is a follow-up once it lands.` - `[loop:torbscript] decided by the loop: encode returns Result<String, TomlError>, not String. Why: TOML cannot express a top-level scalar or a null in an array, so writing can fail; a panic would break the PANICS.md rule.` - `[loop:torbscript] decided by the loop: no Format<TomlError> impl and no framer in this issue. Why: a TOML file is one document, nothing in the acceptance criteria needs streaming; add it if a user needs it.` - `[loop:torbscript] decided by the loop: the reader accepts the superset grammar of all released 1.x versions and `version` only controls writing. Why: the ROADMAP rule (read the old, write the current unless asked) and one grammar is smaller than two.` - If the pinned toml-test release has no 1.1 file list, the package reads and writes 1.0 only and the page says so; do not invent 1.1 behaviour from memory. **Acceptance criteria (testable).** 1. `Toml().decode<T>` / `encode` round-trip records, nested records, `List`, `Map<String, V>`, `Option` fields (omitted when `None`, default when absent), variants, and `Naming.SnakeCase`; `strict: true` refuses unknown keys. Errors carry line and column. 2. The reader passes the public suite: every `valid` case of toml-test (https://github.com/toml-lang/toml-test, MIT) for the supported versions yields the suite's tagged JSON (compared numerically for floats, `nan`/`inf` handled), every `invalid` case is refused. The cases are vendored as a generated `std/toml/tests/suite-cases.trb` with the suite's MIT notice and the pinned release tag in the header, nothing hand-edited (same as `std/yaml/tests/suite-cases.trb`). 3. Every valid case written back by the writer and parsed again gives an equal tree. 4. Dotted keys, super-tables, arrays of tables, inline tables, duplicate-key and redefinition errors, all four string kinds, integer bases and separators, and `inf`/`nan` have tests of their own in `toml.test.trb` beyond the suite where the suite is silent (typed decode, naming, strict, version option, date/time wrappers, `toTimestamp`). 5. `docs/standard-library/toml.md` exists, its `trb check` blocks pass, the front matter satisfies `torb docs check`, the skills are regenerated (`torb docs skill --check` clean). **Test plan (forge CI, tier A, `.forgejo/workflows/gates.yml`).** `torb format --check .`, `torb check .`, `manifest --check` (new `project.trb`), `torb test --native std/toml/tests` and `torb test std/toml/tests` (VM) via the std/example package loop in `tools/gates.sh`, the docs checks (`docs check`, `docs skill --check`, signature blocks of the std pages). Nothing runs locally in the loop; the implementer may run `sh tools/gates.sh a` in the worktree, and must keep the VM run of the suite inside the memory limit (split the generated cases into groups like `std/yaml` if a single run is too slow). Tier B is not triggered (no `compiler/src/ir`, `compiler/src/backend` or `runtime/` change). **Implementer.** sonnet / medium (std package and docs only). Not an owner question: nothing public-facing beyond the new package page, no cost, no account, no breaking change, nothing reserved by a design record. next: implement
Author
Collaborator

[loop:torbscript] claim implement by session:loop-torbscript-20261009 until 2026-10-10T04:40Z

[loop:torbscript] claim implement by session:loop-torbscript-20261009 until 2026-10-10T04:40Z
argus closed this issue 2026-10-09 22:18:12 +00:00
Author
Collaborator

[loop:torbscript] implement: PR #285 open, CI green (feat a626a476, docs c06f0383).

Tokens: roughly 2 sessions of one worker (about 1.5M tokens incl. suite generator, parser/writer and the CI round for the declarations gate); exact figures are in the loop budget report.

next: waiting (merge gate review of PR #285)

[loop:torbscript] implement: PR #285 open, CI green (feat a626a476, docs c06f0383). Tokens: roughly 2 sessions of one worker (about 1.5M tokens incl. suite generator, parser/writer and the CI round for the declarations gate); exact figures are in the loop budget report. next: waiting (merge gate review of PR #285)
Sign in to join this conversation.
No labels
loop
ready
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#77
No description provided.