2026-07-13 02:45:49 -04:00
2026-07-13 02:45:49 -04:00
2026-07-13 02:45:49 -04:00
2026-07-13 02:45:49 -04:00
2026-07-13 02:45:49 -04:00
2026-07-13 02:45:49 -04:00
2026-07-13 02:45:49 -04:00
2026-07-13 02:45:49 -04:00
2026-07-13 02:45:49 -04:00
2026-07-13 02:45:49 -04:00
2026-07-13 02:45:49 -04:00
2026-07-13 02:45:49 -04:00
2026-07-13 02:45:49 -04:00
2026-07-13 02:45:49 -04:00
2026-07-13 02:45:49 -04:00
2026-07-13 02:45:49 -04:00

toml

Zero-dependency TOML encoder/decoder for Go. Implements a practical subset of TOML v1.0 with documented deviations. Standard library only.

Install

go get github.com/lixenwraith/toml

Usage

Unmarshal

input := []byte(`
initial = "Idle"

[states.Idle]
parent = "Root"
transitions = [
    { trigger = "Start", target = "Active" }
]
`)

type Transition struct {
    Trigger string `toml:"trigger"`
    Target  string `toml:"target"`
    Guard   string `toml:"guard,omitempty"`
}
type State struct {
    Parent      string       `toml:"parent"`
    Transitions []Transition `toml:"transitions"`
}
type Config struct {
    Initial string            `toml:"initial"`
    States  map[string]*State `toml:"states"`
}

var cfg Config
err := toml.Unmarshal(input, &cfg)

Marshal

out, err := toml.Marshal(cfg) // deterministic: keys sorted alphabetically

Decode

Decode(data any, v any) maps an already-parsed map[string]any onto a target. Unmarshal = Parse + Decode.

Architecture

Unmarshal: []byte → Lexer → tokens → Parser → map[string]any → Decode (reflection) → target
Marshal:   value → encoder (reflection, two-pass) → []byte

Lexer (lexer.go)

  • Single-pass, UTF-8 aware byte scanner with rune lookahead (peekAt).
  • Emits: ident, string, integer, float, bool, punctuation, newline, comment.
  • Number/key disambiguation happens at the token level: 1.5 → Float, a.b → Ident Dot Ident, 1.2.3 → Error (multi-dot). Hex/octal/binary prefixes and exponent forms are validated during scanning.
  • Basic strings only. Escapes: \" \\ \n \t \r. Unknown escape sequences are preserved verbatim (spec requires an error — see Deviations).

Parser (parser.go)

  • Recursive descent with a two-token window (curToken/peekToken). Comments are skipped during token advance.
  • Output model: tables → map[string]any, arrays → []any, arrays of tables → []map[string]any.
  • Table headers ([a.b]) always resolve from the root; a current cursor tracks the active table scope for subsequent key/value pairs.
  • Dotted keys create intermediate maps. Traversal through an existing [[array]] descends into its last element (TOML semantics).
  • Conflict detection at assignment: duplicate keys, scalar-vs-table redefinition, table-vs-array-of-tables shadowing. Numeric keys are rejected in all positions (bare, quoted, dotted segments).
  • Inline tables are immutable (later dotted keys or [headers] targeting them error); value nesting (arrays/inline tables) capped at 1000 levels.

Decoder (decode.go)

  • Kind-switch reflection. Pointers are auto-allocated at any depth (******int works). Nil maps and slices are materialized.
  • Struct fields match by toml tag first, then exact (case-sensitive) field name. toml:"-" skips. Unexported fields are skipped.
  • Numeric coercion: any parser-produced int/uint/float64 converts to the target numeric kind. Unknown keys in input are ignored.
  • Numeric decode errors on target overflow (300int8 errors); non-empty interface targets error unless assignable; unsupported target kinds (chan, func, complex, fixed arrays) error; uint/uint64 sources above MaxInt64 are rejected.

Encoder (encode.go)

  • Two-pass emission per table: scalars and inline arrays first, then [tables] and [[arrays of tables]]. Guarantees keys are defined before sub-tables, i.e. output is always valid TOML.
  • Keys sorted alphabetically → deterministic output for diffing/VCS.
  • Bare-key validation mirrors the lexer's rules: keys that would lex as numbers or booleans (true, 123a, -1x) are quoted, so output always re-parses (round-trip safe).
  • omitempty honored; nil pointers and toml:"-" fields skipped; root must be a struct or map.
  • Floats use shortest 'g' form at the source bit width (large magnitudes emit exponent notation); NaN/±Inf return an error; arrays mixing tables and scalars return an error (parser accepts them — encode-side limitation); struct keys sort by resolved tag name.

Type Mapping

TOML Parser output (map[string]any) Decode targets
string string string, any
integer int any int/uint kind, float kinds
float float64 float32, float64
boolean bool bool
array []any []T, any
table map[string]any struct, map[string]T, any
array of tables []map[string]any []T, []*T

Deviations from TOML v1.0

Intentional restrictions:

  • Numeric keys are forbidden everywhere: 123 = 1, [456], "789", a.1.b all error. Stricter than spec.

Extensions (accepted, though spec forbids):

  • Newlines inside inline tables (multi-line inline tables parse).
  • Trailing commas in inline tables.

Relaxations:

  • Unknown string escapes are preserved instead of erroring.
  • Explicit table redefinition ([a][a]) is not rejected; the second header reopens the table. Value/table conflicts are still caught.

Limitations

Not supported:

  • Date/time types (all four TOML forms). Workaround: store RFC 3339 strings and convert with time.Parse at the call site; strings round-trip cleanly.
  • Literal strings '...' and multi-line strings """...""", '''...'''.
  • Underscore digit separators (1_000).
  • Arrays mixing tables and scalar values are not encodable.
  • inf / nan float literals.
  • Custom marshaling interfaces (Marshaler/Unmarshaler, encoding.TextMarshaler).
  • Embedded struct field promotion in decode.

Platform note: TOML integers parse to Go int; 64-bit platforms assumed (amd64/arm64) and enforced at compile time (32-bit build fails). Overflow of int64 during parse errors; 32-bit truncation of int(int64) is not guarded.

License

BSD-3-Clause (see LICENSE).

S
Description
No description provided
Readme
64 KiB
Languages
Go 100%