198 lines
5.7 KiB
Go
198 lines
5.7 KiB
Go
package toml
|
|
|
|
import (
|
|
"git.makaay.nl/mauricem/go-parsekit/parse"
|
|
)
|
|
|
|
var (
|
|
// Opener and closer for [table].
|
|
tableOpen = c.Seq(dropBlanks, a.SquareOpen, dropBlanks)
|
|
tableClose = c.Seq(dropBlanks, a.SquareClose, dropBlanks, a.EndOfLine.Or(comment))
|
|
|
|
// Opener and closer for [[array.of.tables]].
|
|
tableArrayOpen = c.Seq(dropBlanks, a.SquareOpen, a.SquareOpen, dropBlanks)
|
|
tableArrayClose = c.Seq(dropBlanks, a.SquareClose, a.SquareClose, dropBlanks, a.EndOfLine.Or(comment))
|
|
|
|
// Opener, separator and closer for { inline: "tables" }.
|
|
inlineTableOpen = c.Seq(dropBlanks, a.CurlyOpen, dropBlanks)
|
|
inlineTableSeparator = c.Seq(dropBlanks, a.Comma, dropBlanks)
|
|
inlineTableClose = c.Seq(dropBlanks, a.CurlyClose, dropBlanks)
|
|
)
|
|
|
|
func (t *parser) startTable(p *parse.API) {
|
|
switch {
|
|
case p.Accept(tableArrayOpen):
|
|
p.Handle(t.startArrayOfTables)
|
|
case p.Accept(tableOpen):
|
|
p.Handle(t.startPlainTable)
|
|
default:
|
|
p.Expected("a table")
|
|
}
|
|
}
|
|
|
|
// Arrays of tables can be expressed by using a table name in double brackets.
|
|
// Each table with the same double bracketed name will be an element in the
|
|
// array. The tables are inserted in the order encountered. A double bracketed
|
|
// table without any key/value pairs will be considered an empty table.
|
|
//
|
|
// [[products]]
|
|
// name = "Hammer"
|
|
// sku = 738594937
|
|
//
|
|
// [[products]]
|
|
//
|
|
// [[products]]
|
|
// name = "Nail"
|
|
// sku = 284758393
|
|
// color = "gray"
|
|
//
|
|
// You can create nested arrays of tables as well. Just use the same double
|
|
// bracket syntax on sub-tables. Each double-bracketed sub-table will belong
|
|
// to the most recently defined table element above it.
|
|
//
|
|
// [[fruit]]
|
|
// name = "apple"
|
|
//
|
|
// [fruit.physical]
|
|
// color = "red"
|
|
// shape = "round"
|
|
//
|
|
// [[fruit.variety]]
|
|
// name = "red delicious"
|
|
//
|
|
// [[fruit.variety]]
|
|
// name = "granny smith"
|
|
//
|
|
// [[fruit]]
|
|
// name = "banana"
|
|
//
|
|
// [[fruit.variety]]
|
|
// name = "plantain"
|
|
func (t *parser) startArrayOfTables(p *parse.API) {
|
|
if key, ok := t.parseKey(p, []string{}); ok {
|
|
if !p.Accept(tableArrayClose) {
|
|
p.Expected("closing ']]' for array of tables name")
|
|
return
|
|
}
|
|
if err := t.openArrayOfTables(key); err != nil {
|
|
p.Error("%s", err)
|
|
return
|
|
}
|
|
p.Handle(t.startKeyValuePair)
|
|
}
|
|
}
|
|
|
|
// Tables (also known as hash tables or dictionaries) are collections of
|
|
// key/value pairs. They appear in square brackets on a line by themselves.
|
|
// You can tell them apart from arrays because arrays are only ever values.
|
|
//
|
|
// Under that, and until the next table or EOF are the key/values of that
|
|
// table. Key/value pairs within tables are not guaranteed to be in any
|
|
// specific order.
|
|
//
|
|
// [table-1]
|
|
// key1 = "some string"
|
|
// key2 = 123
|
|
//
|
|
// [table-2]
|
|
// key1 = "another string"
|
|
// key2 = 456
|
|
//
|
|
// Naming rules for tables are the same as for keys.
|
|
//
|
|
// [dog."tater.man"]
|
|
// type.name = "pug"
|
|
//
|
|
// Whitespace around the key is ignored, however, best practice is to not
|
|
// use any extraneous whitespace.
|
|
//
|
|
// [a.b.c] # this is best practice
|
|
// [ d.e.f ] # same as [d.e.f]
|
|
// [ g . h . i ] # same as [g.h.i]
|
|
// [ j . "ʞ" . 'l' ] # same as [j."ʞ".'l']
|
|
//
|
|
// You don't need to specify all the super-tables if you don't want to.
|
|
// TOML knows how to do it for you.
|
|
//
|
|
// # [x] you
|
|
// # [x.y] don't
|
|
// # [x.y.z] need these
|
|
// [x.y.z.w] # for this to work
|
|
//
|
|
// Empty tables are allowed and simply have no key/value pairs within them.
|
|
func (t *parser) startPlainTable(p *parse.API) {
|
|
if key, ok := t.parseKey(p, []string{}); ok {
|
|
if !p.Accept(tableClose) {
|
|
p.Expected("closing ']' for table name")
|
|
return
|
|
}
|
|
if err := t.openTable(key); err != nil {
|
|
p.Error("%s", err)
|
|
return
|
|
}
|
|
p.Handle(t.startKeyValuePair)
|
|
}
|
|
}
|
|
|
|
// Inline tables provide a more compact syntax for expressing tables.
|
|
// They are especially useful for grouped data that can otherwise quickly
|
|
// become verbose. Inline tables are enclosed in curly braces { and }.
|
|
// Within the braces, zero or more comma separated key/value pairs may appear.
|
|
// Key/value pairs take the same form as key/value pairs in standard tables.
|
|
// All value types are allowed, including inline tables.
|
|
//
|
|
// Inline tables are intended to appear on a single line. No newlines are
|
|
// allowed between the curly braces unless they are valid within a value.
|
|
// Even so, it is strongly discouraged to break an inline table onto multiple
|
|
// lines. If you find yourself gripped with this desire, it means you should
|
|
// be using standard tables.
|
|
//
|
|
// name = { first = "Tom", last = "Preston-Werner" }
|
|
// point = { x = 1, y = 2 }
|
|
// animal = { type.name = "pug" }
|
|
func (t *parser) parseInlineTable(p *parse.API) (*item, bool) {
|
|
// Check for the start of the array.
|
|
if !p.Accept(inlineTableOpen) {
|
|
p.Expected("an inline table")
|
|
return nil, false
|
|
}
|
|
|
|
subt := newParser()
|
|
|
|
// Check for an empty inline table.
|
|
if p.Accept(inlineTableClose) {
|
|
return newItem(tTable, subt.Root), true
|
|
}
|
|
|
|
// Not an empty table, parse the table data.
|
|
for {
|
|
key, ok := subt.parseKey(p, []string{})
|
|
if !ok {
|
|
return nil, false
|
|
}
|
|
if !p.Handle(subt.startAssignment) {
|
|
return nil, false
|
|
}
|
|
value, ok := subt.parseValue(p)
|
|
if !ok {
|
|
return nil, false
|
|
}
|
|
err := subt.setKeyValuePair(key, value)
|
|
if err != nil {
|
|
p.Error("%s", err)
|
|
return nil, false
|
|
}
|
|
|
|
// Check for the end of the inline table.
|
|
if p.Accept(inlineTableClose) {
|
|
return newItem(tTable, subt.Root), true
|
|
}
|
|
|
|
// Not the end of the inline table? Then we should find a key/value pair separator.
|
|
if !p.Accept(inlineTableSeparator) {
|
|
p.Expected("an array separator")
|
|
return nil, false
|
|
}
|
|
}
|
|
}
|