go-toml/ast.go

200 lines
6.1 KiB
Go

package toml
import (
"fmt"
"strings"
"time"
)
// item represents a TOML item.
type item struct {
Type itemType
Data []interface{}
}
// table represents a TOML table.
type table map[string]item
// itemType identifies the semantic role of a TOML item.
type itemType string
const (
// TODO Would be nice to not need these in the end.
pComment itemType = "comment"
pKey itemType = "key"
pAssign itemType = "assign"
// TODO and just use these data types.
pString itemType = "string" // "various", 'types', """of""", '''strings'''
pInteger itemType = "integer" // 12345, 0xffee12a0, 0o0755, 0b101101011
pFloat itemType = "float" // 10.1234, 143E-12, 43.28377e+4, +inf, -inf, nan
pBoolean itemType = "boolean" // true or false
pOffsetDateTime itemType = "offset datetime" // 2019-06-18 10:32:15.173645362+0200
pLocalDateTime itemType = "datetime" // 2018-12-25 12:12:18.876772533
pLocalDate itemType = "date" // 2017-05-17
pLocalTime itemType = "time" // 23:01:22
pArray itemType = "array" // defined using an [[array.of.tables]]
pStaticArray itemType = "static array" // defined using ["an", "inline", "array"]
pTable itemType = "table" // defined using { "inline" = "table" } or [standard.table]
)
// newItem instantiates a new item struct.
func newItem(itemType itemType, data ...interface{}) item {
return item{Type: itemType, Data: data}
}
func (t table) String() string {
return newItem(pTable, t).String()
}
func (parseItem item) String() string {
switch parseItem.Type {
case pString:
return fmt.Sprintf("%q", parseItem.Data[0])
case pInteger:
return fmt.Sprintf("%v", parseItem.Data[0])
case pFloat:
return fmt.Sprintf("%v", parseItem.Data[0])
case pBoolean:
return fmt.Sprintf("%v", parseItem.Data[0])
case pOffsetDateTime:
return parseItem.Data[0].(time.Time).Format(time.RFC3339Nano)
case pLocalDateTime:
return parseItem.Data[0].(time.Time).Format("2006-01-02 15:04:05.999999999")
case pLocalDate:
return parseItem.Data[0].(time.Time).Format("2006-01-02")
case pLocalTime:
return parseItem.Data[0].(time.Time).Format("15:04:05.999999999")
case pArray:
fallthrough
case pStaticArray:
items := make([]string, len(parseItem.Data[0].([]item)))
for i, d := range parseItem.Data[0].([]item) {
items[i] = d.String()
}
return fmt.Sprintf("[%s]", strings.Join(items, ", "))
case pTable:
items := make([]string, len(parseItem.Data))
pairs := parseItem.Data[0].(table)
i := 0
for k, v := range pairs {
items[i] = fmt.Sprintf("%q: %s", k, v.String())
i++
}
return fmt.Sprintf("{%s}", strings.Join(items, ", "))
case pComment:
return fmt.Sprintf("comment(%q)", parseItem.Data[0])
case pKey:
items := make([]string, len(parseItem.Data))
for i, e := range parseItem.Data {
items[i] = fmt.Sprintf("%q", e)
}
return fmt.Sprintf("key(%s)", strings.Join(items, ", "))
case pAssign:
return "="
default:
panic(fmt.Sprintf("Missing String() formatting for item type '%s'", parseItem.Type))
}
}
// parser holds the state for the TOML parser. All parsing functions are
// methods of this struct.
type parser struct {
Items []item // a buffer for holding parsed items
Root table // the root-level TOML table (each TOML doc is implicitly a table)
Current table // the currently active TOML table
}
func newParser() *parser {
p := &parser{Root: make(table)}
p.Current = p.Root
return p
}
func (t *parser) addParsedItem(itemType itemType, data ...interface{}) {
t.Items = append(t.Items, newItem(itemType, data...))
}
func (t *parser) setValue(key item, value item) error {
// When the key has multiple elements, then first make sure the table structure
// for storing the value exists.
var valueKey string
node := t.Current
l := len(key.Data)
if l > 1 {
pathKeys := key.Data[0 : l-1]
valueKey = key.Data[l-1].(string)
for i, name := range pathKeys {
name := name.(string)
if subItem, ok := node[name]; ok {
// An item was found at the current key. It is expected to be a table.
if subItem.Type != pTable {
path := formatKeyPath(key, i)
return fmt.Errorf("invalid key used: %s item already exists at key %s", subItem.Type, path)
}
node = subItem.Data[0].(table)
} else {
// No item was found at the current key. Create a new subtable.
subTable := make(table)
node[name] = newItem(pTable, subTable)
node = subTable
}
}
} else {
valueKey = key.Data[0].(string)
node = t.Current
}
if existing, ok := node[valueKey]; ok {
path := "moetnog"
return fmt.Errorf("Cannot store value: %s item already exists at key %s", existing.Type, path)
}
node[valueKey] = value
return nil
}
func (t *parser) newTable(key item) (table, error) {
node := t.Root
// Go over all requested levels of the key. For all levels, except the last
// one, it is okay if a table already exists. For at least the last level,
// no table or value must exist, because that would mean we are overwriting
// an existing key/value pair, which is not allowed.
for i, e := range key.Data {
name := e.(string)
if subItem, ok := node[name]; ok {
// You cannot overwrite an already defined key, regardless its value.
if subItem.Type != pTable {
path := formatKeyPath(key, i)
return nil, fmt.Errorf("Cannot create table: %s item already exists at key %s", subItem.Type, path)
}
// Like keys, you cannot define any table more than once. Doing so is invalid.
isLast := i == len(key.Data)-1
if isLast {
path := formatKeyPath(key, i)
return nil, fmt.Errorf("Cannot create table: table for key %s already exists", path)
}
node = subItem.Data[0].(table)
} else {
// Create the subtable.
subTable := make(table)
node[name] = newItem(pTable, subTable)
node = subTable
}
}
// From here on, key/value pairs are added to the newly defined table.
t.Current = node
return node, nil
}
func formatKeyPath(key item, end int) string {
var sb strings.Builder
for i := 0; i <= end; i++ {
if i > 0 {
sb.WriteRune('.')
}
sb.WriteString(fmt.Sprintf("%q", key.Data[i].(string)))
}
return sb.String()
}