feat: parse into a Document that keeps order and comments
Test / test (push) Successful in 1m34s

Assisted-by: DeepSeek V4.1 Flash
This commit is contained in:
2026-09-20 10:40:57 +02:00
parent a6e3e3fe31
commit 72f8b21ac4
16 changed files with 939 additions and 119 deletions
+84 -7
View File
@@ -13,23 +13,95 @@ and trailing commas. The encoder emits TOML 1.1.
## Functions
### `func Parse(data []byte) (map[string]any, error)`
### `func Parse(data []byte) (*Document, error)`
Decodes a TOML document into an untyped tree, using the value mapping in the
[Decoding](#decoding) section below. Returns `*SyntaxError` on a malformed
document. Input that is not valid UTF-8 is rejected before the parser runs.
Equivalent to `ParseContext(context.Background(), data)`.
Decodes a TOML document into a [Document](#documents): the values, the order the
keys were written in, whether a table was written inline, and the comments.
The values follow the mapping in the [Decoding](#decoding) section below.
Returns `*SyntaxError` on a malformed document. Input that is not valid UTF-8
is rejected before the parser runs. Equivalent to
`ParseContext(context.Background(), data)`.
```go
tree, err := interpres.Parse([]byte("title = \"x\"\nport = 8080\n"))
doc, err := interpres.Parse([]byte("title = \"x\"\nport = 8080\n"))
tree := doc.Map()
```
### `func ParseContext(ctx context.Context, data []byte) (map[string]any, error)`
### `func ParseContext(ctx context.Context, data []byte) (*Document, error)`
The cancellable variant of `Parse`. An already-cancelled context returns
`ctx.Err()` before any work. During parsing the context is checked every 64
top-level statements, so a long document aborts without running to completion.
### `func ParseMap(data []byte) (map[string]any, error)`
Decodes a TOML document into an untyped tree, the shape this package parsed
into before [Document](#documents) existed: the order of the keys and the
comments are not part of a map, so they are dropped. Use it when only the
values matter, or when the extra bookkeeping of a document is not wanted.
Equivalent to `ParseMapContext(context.Background(), data)`.
```go
tree, err := interpres.ParseMap([]byte("title = \"x\"\nport = 8080\n"))
```
### `func ParseMapContext(ctx context.Context, data []byte) (map[string]any, error)`
The cancellable variant of `ParseMap`.
## Documents
`Parse` returns a `Document`: the value tree together with what a map cannot
carry, which is the order the keys were written in, whether a table was written
as an inline table or under a header, and the comments. `ParseMap` gives the
plain tree when none of that is wanted.
```go
doc, err := interpres.Parse(data)
if err != nil {
return err
}
root := doc.Root()
for _, key := range root.Keys() { // written order, not sorted
entry, _ := root.Get(key)
fmt.Println(key, entry.Value())
}
```
The values are shared with the tree `ParseMap` returns, so a value read from a
document and from `doc.Map()` is the same value.
| Type | Meaning |
|---|---|
| `Document` | the parsed document: `Root()` for the top-level table, `Map()` for the value tree, `Footer()` for a comment block at the end |
| `Table` | one TOML table: `Keys()` and `Entries()` in written order, `Get(key)`, `Values()` for its part of the value tree, `Inline()` |
| `Entry` | one key: `Value()`, `Inline()`, `Table()` when the value is a table, `Elements()` for the tables of an array value |
`Elements()` holds one node per element of an array value: the tables of an
array of tables, and the inline tables inside a value array, with `nil` for the
elements that are not tables.
### Comments
A comment belongs to the line it precedes or follows, and to the node that line
introduced:
| Written | Carried by |
|---|---|
| lines above a key | that key's `Entry`, through `Comments()` |
| a comment beside a key | that key's `Entry`, through `Trailing()` |
| lines above a `[header]` or `[[header]]` | that `Table`, through `Comments()` |
| a comment beside a header | that `Table`, through `Trailing()` |
| a comment block after the last statement | the `Document`, through `Footer()` |
`SetComments` and `SetTrailing` replace them. A line carries no leading `#`
and no surrounding space, so `# note` is stored as `note` and a bare `#` as
`""`.
A `Document` is not a value to marshal: `Marshal` writes values, so it refuses
one and points at `doc.Map()`. Writing a document back, with its order and its
comments, belongs with the editing API.
### `func Unmarshal(data []byte, v any) error`
Parses `data` and stores the result in the value pointed to by `v`, typically a
@@ -602,6 +674,11 @@ A configured `Encoder` holds no per-call state; each `Marshal` or
`MarshalContext` call copies the options and is safe for concurrent use, as
long as no setter races with a call.
### `type Document`, `type Table`, `type Entry`
See [Documents](#documents). A `Document` is what `Parse` returns, and it is
not a value `Marshal` accepts.
### `type Marshaler interface{ MarshalTOML() (any, error) }`
See [Custom encoding](#custom-encoding-marshaler).
+2 -1
View File
@@ -43,7 +43,8 @@ Inside the library package, one file owns one concern:
| File | Responsibility |
|---|---|
| `parser.go` | The recursive-descent parser. Produces the `map[string]any` tree and enforces the structural rules of TOML 1.1 (table redefinitions, dotted keys, arrays of tables, multi-line inline tables). Reports a 1-based line on failure. |
| `parser.go` | The recursive-descent parser. Produces the `map[string]any` tree, records the nodes a [Document](API.md#documents) is built from, and enforces the structural rules of TOML 1.1 (table redefinitions, dotted keys, arrays of tables, multi-line inline tables). Reports a 1-based line on failure. |
| `document.go` | The parsed-document types: `Document`, `Table` and `Entry`, which carry the key order, whether a table was written inline, and the comments. The values they expose are the parser's own tree, not a copy. |
| `number.go` | Strict numeric tokens: integers in the four radixes with `_` separators, and floats including `inf` and `nan`. Rejects leading zeros, misplaced underscores and malformed fractions. |
| `datetime.go` | The three local date-time wrapper types and `parseDateTime`, which classifies a token into the four date-time kinds under the strict TOML grammar. |
| `decode.go` | Maps the parsed tree onto Go values by reflection: struct fields, maps, slices, scalar conversion with overflow checks, `Unmarshaler` dispatch. |