package parse import ( "git.makaay.nl/mauricem/go-parsekit/parse" "git.makaay.nl/mauricem/go-toml/ast" ) var ( // Opener and closer for [table]. tableOpen = c.Seq(optionalBlanks, a.SquareOpen, optionalBlanks) tableClose = c.Seq(optionalBlanks, a.SquareClose, optionalBlanks) // Opener and closer for [[array.of.tables]]. tableArrayOpen = c.Seq(optionalBlanks, a.SquareOpen, a.SquareOpen, optionalBlanks) tableArrayClose = c.Seq(optionalBlanks, a.SquareClose, a.SquareClose, optionalBlanks) // Opener, separator and closer for { inline: "tables" }. inlineTableOpen = c.Seq(optionalBlanks, a.CurlyOpen, optionalBlanks) inlineTableSeparator = c.Seq(optionalBlanks, a.Comma, optionalBlanks) inlineTableClose = c.Seq(optionalBlanks, a.CurlyClose, optionalBlanks) ) 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 !p.Accept(endOfLineOrComment) { p.Expected("end of line or comment") return } if err := t.OpenArrayOfTables(key); err != nil { p.Error("%s", err) return } p.Handle(t.startDocument) } } // 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 !p.Accept(endOfLineOrComment) { p.Expected("end of line or comment") return } if err := t.OpenTable(key); err != nil { p.Error("%s", err) return } p.Handle(t.startDocument) } } // 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) (*ast.Value, bool) { // Check for the start of the array. if !p.Accept(inlineTableOpen) { p.Expected("an inline table") return nil, false } subdoc := newParser() // Check for an empty inline table. if p.Accept(inlineTableClose) { return ast.NewValue(ast.TypeTable, subdoc.Root), true } // Not an empty table, parse the table data. for { key, ok := subdoc.parseKey(p, []string{}) if !ok { return nil, false } if !p.Handle(subdoc.startAssignment) { return nil, false } value, ok := subdoc.parseValue(p) if !ok { return nil, false } err := subdoc.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 ast.NewValue(ast.TypeTable, subdoc.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 } } }