# interpres A dependency-free **TOML 1.0 parser for Go** — standard library only, no third-party modules. Passes the entire [toml-test](https://github.com/toml-lang/toml-test) suite (185 valid and 371 invalid cases). `interpres` (Latin for *interpreter*) reads TOML documents into Go values with a small, `encoding/json`-style API. It is built for embedding in zero-dependency Go programs that still want their configuration in TOML. ## Install ```sh go get sourcedock.dev/petrbalvin/interpres ``` Requires Go 1.26 or newer. The module imports only the standard library. ## Usage ### Decode into a struct ```go package main import ( "fmt" "sourcedock.dev/petrbalvin/interpres" ) type Config struct { Title string `toml:"title"` Server struct { Host string `toml:"host"` Port int `toml:"port"` } `toml:"server"` } func main() { data := []byte(` title = "example" [server] host = "127.0.0.1" port = 9090 `) var cfg Config if err := interpres.Unmarshal(data, &cfg); err != nil { panic(err) } fmt.Printf("%+v\n", cfg) } ``` ### Encode from a struct ```go out, err := interpres.Marshal(cfg) if err != nil { panic(err) } fmt.Println(string(out)) ``` writes the same struct back to TOML: ```toml title = "example" [server] host = "127.0.0.1" port = 9090 ``` ### Untyped tree ```go tree, err := interpres.Parse(data) // []byte → map[string]any out, err := interpres.Marshal(tree) // map[string]any → []byte ``` ### Custom encoding Types that need a non-default TOML shape can implement `Marshaler`: ```go type Port int func (p Port) MarshalTOML() (any, error) { return int64(p), nil } ``` The returned value is encoded just like any other value passed to `Marshal`, which means it can be any type `Marshal` itself understands. ### Strict decoding Reject keys that have no matching struct field — useful for catching typos in configuration files: ```go err := interpres.NewDecoder(). DisallowUnknownFields(). Decode(data, &cfg) ``` ## Struct mapping Fields are matched to TOML keys by the `toml:"name"` tag, or by a case-insensitive match on the field name when no tag is present. A tag of `"-"` skips the field. | TOML value | Go type (untyped tree) | |------------|------------------------| | string | `string` | | integer | `int64` | | float | `float64` | | boolean | `bool` | | offset date-time | `time.Time` | | local date-time | `LocalDateTime` | | local date | `LocalDate` | | local time | `LocalTime` | | array | `[]any` | | table / inline table | `map[string]any` | | array of tables | `[]map[string]any` | When decoding into a struct, these map onto the destination's concrete types (any integer/unsigned/float width, slices, nested structs, and `map[string]T`). ## Encoding `Marshal` produces valid TOML 1.0 from any `struct` or `map[string]V` value, applying these rules: - The top-level value must be a struct or `map[string]V`. Pointers are followed; a nil top-level pointer is an error. - Struct fields are matched by `toml:"name"` tag (case-insensitive fallback to field name; `toml:"-"` skips). Anonymous (embedded) fields without a tag are inlined. - Maps emit keys in sorted order for deterministic output. - **Output is grouped by kind at every level**: scalars come first, then sub-tables, then arrays of tables. Within each group the order matches struct field declaration order (or, for maps, sorted key order). This means in TOML terms a struct that mixes scalars and sub-tables always lays out scalars at the top of the section followed by the table headers — there is no way to interleave them and still produce a document that re-parses to the same tree. - Slices and arrays of structs or maps become `[[a]]` arrays of tables. Empty or nil arrays of tables are omitted (TOML forbids an empty `[[a]]`); empty arrays of scalars emit as `key = []`. - Other slices and arrays become TOML arrays, including mixed-type `[]any`. - Scalars map to TOML scalars: bool, int64, float64, string, `time.Time` (offset date-time), and the local variants (`LocalDateTime`, `LocalDate`, `LocalTime`). - Strings are always emitted as basic `"..."` strings with the escapes TOML requires (`\"`, `\\`, control characters as `\uXXXX`). - Keys are emitted as bare keys when they match `[A-Za-z0-9_-]+`, quoted otherwise. - Floats always carry a `.` or an exponent (so `1` is always emitted as `1.0`), which preserves the distinction between an integer-typed value and a float-typed value across a `Marshal` → `Parse` round-trip. - Values implementing `Marshaler` are encoded by calling `MarshalTOML` and using its result. - `nil` pointer fields are omitted. `Marshal` cannot encode cyclic data structures. The output is not guaranteed to be byte-identical to any TOML document that produced the input value: comments, whitespace, key order (for maps), the choice between `[table]` headers and inline tables, and the basic-vs-literal quoting style are not preserved. The emitted document is, however, guaranteed to be re-parsable by `interpres.Parse` back into an equivalent value tree. ## TOML 1.0 support `interpres` implements the full [TOML 1.0](https://toml.io/en/v1.0.0) grammar: - Comments, and bare / quoted / dotted keys. - Tables `[a.b]` and arrays of tables `[[a]]`. - Basic and literal strings, including multiline (`"""` / `'''`) with escape sequences (`\n`, `\t`, `\uXXXX`, `\UXXXXXXXX`, line-ending backslash). - Integers in decimal, hex (`0x`), octal (`0o`), and binary (`0b`) with `_` separators; floats with exponents and `inf` / `nan`. - Booleans, offset/local date-times, dates, and times. - Arrays (multiline, mixed types) and inline tables. Invalid documents are rejected per the specification (malformed numbers and floats, control characters, non-UTF-8 input, out-of-range escapes, table and inline-table redefinitions, and more). ## Compliance The parser is verified against the official [toml-test](https://github.com/toml-lang/toml-test) suite via `cmd/interpres-decode`: ```sh go build -o interpres-decode ./cmd/interpres-decode toml-test ./interpres-decode ``` ## License MIT — see [LICENSE](LICENSE). Copyright © 2026 [Petr Balvín](https://petrbalvin.org)