// 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 // Array represents a TOML array: a list of values. type Array struct { First *ArrayItem Last *ArrayItem ItemType ValueType Length int } // ArrayItem represents a single item from a TOML array. type ArrayItem struct { Value *Value Next *ArrayItem } // NewArray initializes a new Array. func NewArray() *Array { return &Array{} } // Append add a new value to an Array. The values must all be of the same type. // It returns an error when values of different types are added, nil otherwise. func (a *Array) Append(value *Value) error { item := &ArrayItem{Value: value} if a.Length == 0 { a.ItemType = value.Type a.First = item a.Last = item } else { if value.Type != a.ItemType { return fmt.Errorf("type mismatch in array of %ss: found an item of type %s", a.ItemType, value.Type) } a.Last.Next = item a.Last = item } a.Length++ return nil } // 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) } // An array of tables exists. Add a new table to this array. array := node[lastKeyPart].Data[0].(*Array) subTable := make(Table) array.Append(NewValue(TypeTable, subTable)) node = subTable } else { // No value exists at the defined key path. Create a new table array. subTable := make(Table) array := NewArray() array.Append(NewValue(TypeTable, subTable)) node[lastKeyPart] = NewValue(TypeArrayOfTables, array) 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, use the last table in the array. lastTable := subValue.Data[0].(*Array).Last.Value.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) }