package toml import ( "git.makaay.nl/mauricem/go-parsekit/parse" ) // The primary building block of a TOML document is the key/value pair. var ( // Keys are on the left of the equals sign and values are on the right. // Blank is ignored around key names and values. The key, equals // sign, and value must be on the same line (though some values can be // broken over multiple lines). keyAssignment = c.Seq(dropBlanks, a.Equal, dropBlanks) // A key may be either bare, quoted or dotted. Bare keys may only // contain ASCII letters, ASCII digits, underscores, and dashes // (A-Za-z0-9_-). Note that bare keys are allowed to be composed of only // ASCII digits, e.g. 1234, but are always interpreted as strings. bareKeyRune = c.Any(a.ASCIILower, a.ASCIIUpper, a.Digit, a.Underscore, a.Minus) bareKey = c.OneOrMore(bareKeyRune) // Quoted keys follow the exact same rules as either basic strings or // literal strings and allow you to use a much broader set of key names. // Best practice is to use bare keys except when absolutely necessary. // A bare key must be non-empty, but an empty quoted key is allowed // (though discouraged). startOfKey = c.Any(bareKeyRune, a.SingleQuote, a.DoubleQuote) // Dotted keys are a sequence of bare or quoted keys joined with a dot. // This allows for grouping similar properties together. Blanks // around dot-separated parts are ignored, however, best practice is to // not use any extraneous blanks. keySeparatorDot = c.Seq(dropBlanks, a.Dot, dropBlanks) ) func (t *parser) startKeyValuePair(p *parse.API) { for { p.Accept(dropWhitespace) switch { case p.Peek(a.Hash): p.Handle(t.startComment) case p.Peek(startOfKey): p.Handle(t.startKey, t.startAssignment, t.startValue) default: p.ExpectEndOfFile() return } if p.IsStoppedOrInError() { return } } } // Bare keys may only contain ASCII letters, ASCII digits, underscores, and // dashes (A-Za-z0-9_-). Note that bare keys are allowed to be composed of only // ASCII digits, e.g. 1234, but are always interpreted as strings. // // Quoted keys follow the exact same rules as either basic strings or literal // strings and allow you to use a much broader set of key names. Best practice // is to use bare keys except when absolutely necessary. // A bare key must be non-empty, but an empty quoted key is allowed (though // discouraged). func (t *parser) startKey(p *parse.API) { var key string var ok bool switch { case p.Accept(bareKey): key, ok = p.Result().String(), true case p.Peek(a.SingleQuote): key, ok = t.parseLiteralString("key", p) case p.Peek(a.DoubleQuote): key, ok = t.parseBasipString("key", p) default: p.Expected("a key name") return } if ok { t.addParsedItem(pKey, key) p.Handle(t.endOfKeyOrDot) } } // Dotted keys are a sequence of bare or quoted keys joined with a dot. // This allows for grouping similar properties together. // Whitespace around dot-separated parts is ignored, however, best // practice is to not use any extraneous whitespace. func (t *parser) endOfKeyOrDot(p *parse.API) { if p.Accept(keySeparatorDot) { p.Handle(t.startKey) return } // TODO not sure if we really need this index. // Can't we alway simply use the full item list, given that we'll feed all // results to the parser's table state? // Do we even need to emit a key here? Shouldn't we just create the // table structure in the parser, ready for followup calls to fill the data? keyStart := len(t.Items) - 1 for keyStart > 0 && t.Items[keyStart-1].Type == pKey { keyStart-- } keyLen := len(t.Items) - keyStart key := make([]interface{}, keyLen) for i := 0; i < keyLen; i++ { key[i] = t.Items[keyStart+i].Data[0].(string) } t.Items = t.Items[0:keyStart] t.addParsedItem(pKey, key...) } func (t *parser) startAssignment(p *parse.API) { if p.Accept(keyAssignment) { t.addParsedItem(pAssign) } else { p.Expected("a value assignment") } }