264 lines
9.0 KiB
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"
|
|
|
|
// TypeBool identifies a boolean value (true or false).
|
|
TypeBool ValueType = "bool"
|
|
|
|
// 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)
|
|
}
|