// Copyright (c) 2026 Petr Balvín (https://petrbalvin.org) // SPDX-License-Identifier: MIT package interpres // A Document is a parsed TOML document: the values, plus what the map shape // cannot carry, which is the order the keys were written in, whether a table // was written inline or under a header, and the comments. // // The values are the tree ParseMap returns, shared rather than copied, so a // value read from a Document and from that map is the same value. A comment // belongs to the statement it precedes: the lines above a key belong to the // key, the lines above a header belong to the header's table, and a comment // block after the last statement belongs to the Document. // // Comments inside array and inline-table values are not carried yet; the // parser skips them as it always has. type Document struct { root *Table footer []string } // Root returns the document's root table. func (d *Document) Root() *Table { return d.root } // Map returns the value tree, the shape ParseMap gives. It is the tree the // document was parsed into, not a copy. func (d *Document) Map() map[string]any { return d.root.values } // Footer returns the comment lines that follow the last statement, and every // line of a document that holds no statement at all. func (d *Document) Footer() []string { return d.footer } // SetFooter replaces those lines. func (d *Document) SetFooter(lines []string) { d.footer = lines } // A Table is one TOML table: its values, its keys in written order, and the // comments around the header or the key that introduced it. type Table struct { values map[string]any entries []*Entry index map[string]*Entry // inline records that the table was written as an inline table, `{…}`, // rather than under a header or as a dotted key. inline bool // comments are the lines above the table's header, trailing is the comment // on the header's own line. Both are empty for a table a dotted key // introduced, which has no line of its own. comments []string trailing string } func newTable(values map[string]any) *Table { return &Table{values: values, index: map[string]*Entry{}} } // Keys returns the table's keys in the order they were written. func (t *Table) Keys() []string { keys := make([]string, len(t.entries)) for i, e := range t.entries { keys[i] = e.key } return keys } // Values returns the table's values, which is the map the value tree holds for // it. func (t *Table) Values() map[string]any { return t.values } // Entries returns the table's entries in written order. func (t *Table) Entries() []*Entry { return t.entries } // Get returns the entry for key, and whether the table has one. func (t *Table) Get(key string) (*Entry, bool) { e, ok := t.index[key] return e, ok } // Inline reports whether the table was written as an inline table, `{…}`, // rather than under a header or introduced by a dotted key. func (t *Table) Inline() bool { return t.inline } // Comments returns the comment lines above the table's header, or above the // key that introduced it. Lines carry no leading '#' and no surrounding space. func (t *Table) Comments() []string { return t.comments } // SetComments replaces those lines. Each line is written back with a "# " in // front of it, so a line should not carry one. func (t *Table) SetComments(lines []string) { t.comments = lines } // Trailing returns the comment on the header's own line, without the '#'. func (t *Table) Trailing() string { return t.trailing } // SetTrailing replaces that comment. func (t *Table) SetTrailing(line string) { t.trailing = line } // addValue records a key of the table, in written order. func (t *Table) addValue(key string, val any, inline bool) *Entry { e := &Entry{table: t, key: key, inline: inline} t.entries = append(t.entries, e) t.index[key] = e if node, ok := val.(map[string]any); ok { e.child = newTable(node) } return e } // addTable records a key whose value is a table introduced by a header or a // dotted key, and returns the table's node. func (t *Table) addTable(key string, values map[string]any) *Table { if e, ok := t.index[key]; ok { // The key was seen before, as the leaf of an earlier dotted key. if e.child == nil { e.child = newTable(values) } return e.child } e := t.addValue(key, values, false) e.child = newTable(values) return e.child } // addElement records one element of an array of tables, and returns its node. func (t *Table) addElement(key string, values map[string]any) *Table { e, ok := t.index[key] if !ok { e = t.addValue(key, nil, false) e.elements = []*Table{} } el := newTable(values) e.elements = append(e.elements, el) return el } // child returns the node of a table-valued key, or nil. func (t *Table) child(key string) *Table { if e, ok := t.index[key]; ok { return e.child } return nil } // lastElement returns the node of the newest element of an array of tables. func (t *Table) lastElement(key string) *Table { if e, ok := t.index[key]; ok && len(e.elements) > 0 { return e.elements[len(e.elements)-1] } return nil } // An Entry is one key of a table: the value and the comments around the key. type Entry struct { table *Table key string inline bool comments []string trailing string // child is the table the value is, and elements are the tables of an array // of tables; one of them is set only when the value has that shape. child *Table elements []*Table } // Key returns the key as it was written. func (e *Entry) Key() string { return e.key } // Value returns the value the key holds. It is read from the table's map, so // it stays current if that map is changed. func (e *Entry) Value() any { return e.table.values[e.key] } // Inline reports whether the value was written as an inline table, `{…}`. func (e *Entry) Inline() bool { return e.inline } // Table returns the table the value is, or nil when it is not a table. func (e *Entry) Table() *Table { return e.child } // Elements returns the tables of an array of tables, or nil when the value is // not one. func (e *Entry) Elements() []*Table { return e.elements } // Comments returns the comment lines above the key. Lines carry no leading '#' // and no surrounding space. func (e *Entry) Comments() []string { return e.comments } // SetComments replaces those lines. Each line is written back with a "# " in // front of it, so a line should not carry one. func (e *Entry) SetComments(lines []string) { e.comments = lines } // Trailing returns the comment on the key's own line, without the '#'. func (e *Entry) Trailing() string { return e.trailing } // SetTrailing replaces that comment. func (e *Entry) SetTrailing(line string) { e.trailing = line }