go-toml/ast/ast.go

264 lines
9.0 KiB
Go

// Package ast implements an Abstract Syntax Tree that represents a TOML document.
package ast
import (
"fmt"
"regexp"
"strings"
)
// Document is a struct holds the data for a TOML Document.
//
// Methods on this struct provide functionality to construct a full data
// structure that represents a TOML document.
type Document struct {
Root Table // the root-level TOML Table (each TOML doc is implicitly a Table)
Current Table // the currently active TOML Table
CurrentKey Key // the key for the currently active TOML Table
}
// NewDocument instantiates a new TOML Document.
func NewDocument() *Document {
doc := &Document{Root: make(Table)}
doc.Current = doc.Root
return doc
}
// Table represents a TOML table: a set of key/value pairs.
type Table map[string]*Value
// Key represents a TOML table key: one or more strings, where multiple strings
// can be used to represent nested tables.
type Key []string
// NewKey instantiates a new Key.
func NewKey(key ...string) Key {
return key
}
// Value represents a TOML value.
type Value struct {
Type ValueType
Data []interface{}
}
// NewValue instantiates a new Value.
func NewValue(valueType ValueType, data ...interface{}) *Value {
return &Value{
Type: valueType,
Data: data,
}
}
// ValueType identifies the type of a TOML value, as specified by the
// TOML specification. Because these types do not map Go types one-on-one,
// we have to keep track of the TOML type ourselves.
type ValueType string
const (
// TypeString identifies a string value ("various", 'types', """of""", '''strings''').
TypeString ValueType = "string"
// TypeInteger identifies an integer number value (12345, 0xffee12a0, 0o0755, 0b101101011).
TypeInteger ValueType = "integer"
// TypeFloat identifies a floating point number (10.1234, 143E-12, 43.28377e+4, +inf, -inf, nan).
TypeFloat ValueType = "float"
// TypeBoolean identifies a boolean value (true or false).
TypeBoolean ValueType = "boolean"
// TypeOffsetDateTime identifies a date/time value, including timezone info (2019-06-18 10:32:15.173645362+0200).
TypeOffsetDateTime ValueType = "datetime"
// TypeLocalDateTime identifies a date/time value, without timezone info (2018-12-25 12:12:18.876772533).
TypeLocalDateTime ValueType = "datetime-local"
// TypeLocalDate identifies a date value (2017-05-17).
TypeLocalDate ValueType = "date"
// TypeLocalTime identifies a time value (23:01:22).
TypeLocalTime ValueType = "time"
// TypeArrayOfTables identifies an [[array.of.tables]].
TypeArrayOfTables ValueType = "array-of-tables"
// TypeArray identifies ["an", "inline", "static", "array"].
TypeArray ValueType = "array"
// TypeTable identifies an { "inline" = "table" } or [standard.table].
TypeTable ValueType = "table"
// TypeImplicitTable identifies an intermediate table that was created
// using a table definition that looks like [a.b.c.d] (where a, b and c
// would be implicit tables when they hadn't been created explicitly before).
TypeImplicitTable ValueType = "implicit-table"
)
// SetKeyValuePair is used to set a key and an accompanying value in the
// currently active TOML table of the TOML Document.
func (doc *Document) SetKeyValuePair(key Key, value *Value) error {
// First make sure the table structure for storing the value exists.
node, lastKeyPart, err := doc.makeTablePath(key)
if err != nil {
return fmt.Errorf("invalid key/value pair: %s", err)
}
// Check if the key is still free for use.
if existing, ok := node[lastKeyPart]; ok {
path := doc.formatKeyPath(key, len(key)-1)
return fmt.Errorf("invalid key/value pair: %s value already exists at key %s", existing.Type, path)
}
// It is, store the value in the table.
node[lastKeyPart] = value
return nil
}
// OpenTable creates a new table at the provided Key path and makes this the
// active table for the TOML Document. This means that subsequent calls to
// SetKeyValuePair() will add the key/value pairs to this table.
func (doc *Document) OpenTable(key Key) error {
doc.CurrentKey = nil
doc.Current = doc.Root
// Go over all requested levels of the key. For all levels, except the last
// one, it is okay if a Table, implicit Table or TableArray already exists.
// For at least the last level, no table or value must exist, or an implicit
// table must exist. Otherwise we are overwriting an existing value, which
// is not allowed.
node, lastKeyPart, err := doc.makeTablePath(key)
if err != nil {
return fmt.Errorf("invalid table: %s", err)
}
// Check if the key is already in use.
if existing, ok := node[lastKeyPart]; ok {
// It is, but it is an implicit table. Upgrade it to an explicit and use it.
if existing.Type == TypeImplicitTable {
existing.Type = TypeTable
node = existing.Data[0].(Table)
} else {
path := doc.formatKeyPath(key, len(key)-1)
return fmt.Errorf("invalid table: %s value already exists at key %s", existing.Type, path)
}
} else {
// The subtable does not exist yet. Create the subtable.
subTable := make(Table)
node[lastKeyPart] = NewValue(TypeTable, subTable)
node = subTable
}
// From here on, key/value pairs are added to the newly defined table.
doc.Current = node
doc.CurrentKey = key
return nil
}
// OpenArrayOfTables creates a new table and adds it to a (possibly newly
// created) table array at the provided Key path. The new table will be
// made the active table for the TOML Document. This means that subsequent
// calls to SetKeyValuePair() will add the key/value pairs to this table.
func (doc *Document) OpenArrayOfTables(key Key) error {
doc.CurrentKey = nil
doc.Current = doc.Root
// Go over all requested levels of the key. For all levels, except the last
// one, it is okay if a Table or ArrayOfTables already exists. For the last
// level, either no value must exist (in which case a table array will be
// created), or a table array must exist.
// Other cases would mean we are overwriting an existing key/value pair,
// which is not allowed.
node, lastKeyPart, err := doc.makeTablePath(key)
if err != nil {
return fmt.Errorf("invalid table array: %s", err)
}
// At the last key position, there must be either no value yet, or the
// existing value must be a table array. Other values are invalid.
if existing, ok := node[lastKeyPart]; ok {
if existing.Type != TypeArrayOfTables {
path := doc.formatKeyPath(key, len(key)-1)
return fmt.Errorf("invalid table array: %s value already exists at key %s", existing.Type, path)
}
// A table array exists. Add a new table to this array.
array := node[lastKeyPart]
subTable := make(Table)
tables := array.Data
tables = append(tables, NewValue(TypeTable, subTable))
array.Data = tables
node = subTable
} else {
// No value exists at the defined key path. Create a new table array.
subTable := make(Table)
node[lastKeyPart] = NewValue(TypeArrayOfTables, NewValue(TypeTable, subTable))
node = subTable
}
// From here on, key/value pairs are added to the newly defined table.
doc.Current = node
doc.CurrentKey = key
return nil
}
func (doc *Document) makeTablePath(key Key) (Table, string, error) {
node := doc.Current
for i, keyPart := range key {
// Arrived at the last key part? Then the path towards that key is
// setup correctly. Return the last part, so the caller can use it.
isLast := i == len(key)-1
if isLast {
return node, keyPart, nil
}
if subValue, ok := node[keyPart]; ok {
// You cannot overwrite an already defined key, regardless its value.
// When a value already exists at the current key, this can only be a table,
// an implicit table or an array of tables. In case of an array of tables,
// the table that was added last to the array will be used.
if subValue.Type == TypeTable || subValue.Type == TypeImplicitTable {
// A table was found, traverse to that table.
node = subValue.Data[0].(Table)
} else if subValue.Type == TypeArrayOfTables {
// An array of tables was found, traverse to the last table in the array.
lastValue := subValue.Data[len(subValue.Data)-1].(*Value)
lastTable := lastValue.Data[0].(Table)
node = lastTable
} else {
path := doc.formatKeyPath(key, i)
return nil, "", fmt.Errorf("%s value already exists at key %s", subValue.Type, path)
}
} else {
// The subtable does not exist yet. Create the subtable as an implicit table.
subTable := make(Table)
node[keyPart] = NewValue(TypeImplicitTable, subTable)
node = subTable
}
}
panic("makeTablePath(): empty key provided; a key must have at least one key part")
}
func (doc *Document) formatKeyPath(key Key, end int) string {
var sb strings.Builder
sb.WriteRune('[')
if doc.CurrentKey != nil {
for i, keyPart := range doc.CurrentKey {
if i > 0 {
sb.WriteString("->")
}
sb.WriteString(formatKeyName(keyPart))
}
}
for i, keyPart := range key {
if doc.CurrentKey != nil || i > 0 {
sb.WriteString("->")
}
sb.WriteString(formatKeyName(keyPart))
if i == end {
break
}
}
sb.WriteRune(']')
return sb.String()
}
func formatKeyName(key string) string {
if ok, _ := regexp.Match(`^\w+$`, []byte(key)); ok {
return key
}
return fmt.Sprintf("%q", key)
}