Commit 73647263 authored by Kim "BKC" Carlbäcker's avatar Kim "BKC" Carlbäcker

Update BurntSushi/toml

parent 9e4f081f
TAGS
tags
.*.swp
tomlcheck/tomlcheck
toml.test
language: go
go:
- 1.1
- 1.2
- 1.3
- 1.4
- 1.5
- 1.6
- tip
install:
- go install ./...
- go get github.com/BurntSushi/toml-test
script:
- export PATH="$PATH:$HOME/gopath/bin"
- make test
Compatible with TOML version Compatible with TOML version
[v0.2.0](https://github.com/mojombo/toml/blob/master/versions/toml-v0.2.0.md) [v0.4.0](https://github.com/toml-lang/toml/blob/v0.4.0/versions/en/toml-v0.4.0.md)
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE The MIT License (MIT)
Version 2, December 2004
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net> Copyright (c) 2013 TOML authors
Everyone is permitted to copy and distribute verbatim or modified Permission is hereby granted, free of charge, to any person obtaining a copy
copies of this license document, and changing it is allowed as long of this software and associated documentation files (the "Software"), to deal
as the name is changed. in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE The above copyright notice and this permission notice shall be included in
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION all copies or substantial portions of the Software.
0. You just DO WHAT THE FUCK YOU WANT TO.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
## TOML parser and encoder for Go with reflection ## TOML parser and encoder for Go with reflection
TOML stands for Tom's Obvious, Minimal Language. This Go package provides a TOML stands for Tom's Obvious, Minimal Language. This Go package provides a
reflection interface similar to Go's standard library `json` and `xml` reflection interface similar to Go's standard library `json` and `xml`
packages. This package also supports the `encoding.TextUnmarshaler` and packages. This package also supports the `encoding.TextUnmarshaler` and
`encoding.TextMarshaler` interfaces so that you can define custom data `encoding.TextMarshaler` interfaces so that you can define custom data
representations. (There is an example of this below.) representations. (There is an example of this below.)
Spec: https://github.com/mojombo/toml Spec: https://github.com/toml-lang/toml
Compatible with TOML version Compatible with TOML version
[v0.2.0](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.2.0.md) [v0.4.0](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.4.0.md)
Documentation: http://godoc.org/github.com/BurntSushi/toml Documentation: https://godoc.org/github.com/BurntSushi/toml
Installation: Installation:
...@@ -26,8 +26,7 @@ go get github.com/BurntSushi/toml/cmd/tomlv ...@@ -26,8 +26,7 @@ go get github.com/BurntSushi/toml/cmd/tomlv
tomlv some-toml-file.toml tomlv some-toml-file.toml
``` ```
[![Build status](https://api.travis-ci.org/BurntSushi/toml.png)](https://travis-ci.org/BurntSushi/toml) [![Build Status](https://travis-ci.org/BurntSushi/toml.svg?branch=master)](https://travis-ci.org/BurntSushi/toml) [![GoDoc](https://godoc.org/github.com/BurntSushi/toml?status.svg)](https://godoc.org/github.com/BurntSushi/toml)
### Testing ### Testing
...@@ -87,7 +86,7 @@ type TOML struct { ...@@ -87,7 +86,7 @@ type TOML struct {
### Using the `encoding.TextUnmarshaler` interface ### Using the `encoding.TextUnmarshaler` interface
Here's an example that automatically parses duration strings into Here's an example that automatically parses duration strings into
`time.Duration` values: `time.Duration` values:
```toml ```toml
...@@ -120,7 +119,7 @@ for _, s := range favorites.Song { ...@@ -120,7 +119,7 @@ for _, s := range favorites.Song {
} }
``` ```
And you'll also need a `duration` type that satisfies the And you'll also need a `duration` type that satisfies the
`encoding.TextUnmarshaler` interface: `encoding.TextUnmarshaler` interface:
```go ```go
...@@ -217,4 +216,3 @@ Note that a case insensitive match will be tried if an exact match can't be ...@@ -217,4 +216,3 @@ Note that a case insensitive match will be tried if an exact match can't be
found. found.
A working example of the above can be found in `_examples/example.{go,toml}`. A working example of the above can be found in `_examples/example.{go,toml}`.
...@@ -4,7 +4,7 @@ files via reflection. There is also support for delaying decoding with ...@@ -4,7 +4,7 @@ files via reflection. There is also support for delaying decoding with
the Primitive type, and querying the set of keys in a TOML document with the the Primitive type, and querying the set of keys in a TOML document with the
MetaData type. MetaData type.
The specification implemented: https://github.com/mojombo/toml The specification implemented: https://github.com/toml-lang/toml
The sub-command github.com/BurntSushi/toml/cmd/tomlv can be used to verify The sub-command github.com/BurntSushi/toml/cmd/tomlv can be used to verify
whether a file is a valid TOML document. It can also be used to print the whether a file is a valid TOML document. It can also be used to print the
......
...@@ -241,7 +241,7 @@ func (enc *Encoder) eArrayOfTables(key Key, rv reflect.Value) { ...@@ -241,7 +241,7 @@ func (enc *Encoder) eArrayOfTables(key Key, rv reflect.Value) {
func (enc *Encoder) eTable(key Key, rv reflect.Value) { func (enc *Encoder) eTable(key Key, rv reflect.Value) {
panicIfInvalidKey(key) panicIfInvalidKey(key)
if len(key) == 1 { if len(key) == 1 {
// Output an extra new line between top-level tables. // Output an extra newline between top-level tables.
// (The newline isn't written if nothing else has been written though.) // (The newline isn't written if nothing else has been written though.)
enc.newline() enc.newline()
} }
......
...@@ -30,24 +30,28 @@ const ( ...@@ -30,24 +30,28 @@ const (
itemArrayTableEnd itemArrayTableEnd
itemKeyStart itemKeyStart
itemCommentStart itemCommentStart
itemInlineTableStart
itemInlineTableEnd
) )
const ( const (
eof = 0 eof = 0
tableStart = '[' comma = ','
tableEnd = ']' tableStart = '['
arrayTableStart = '[' tableEnd = ']'
arrayTableEnd = ']' arrayTableStart = '['
tableSep = '.' arrayTableEnd = ']'
keySep = '=' tableSep = '.'
arrayStart = '[' keySep = '='
arrayEnd = ']' arrayStart = '['
arrayValTerm = ',' arrayEnd = ']'
commentStart = '#' commentStart = '#'
stringStart = '"' stringStart = '"'
stringEnd = '"' stringEnd = '"'
rawStringStart = '\'' rawStringStart = '\''
rawStringEnd = '\'' rawStringEnd = '\''
inlineTableStart = '{'
inlineTableEnd = '}'
) )
type stateFn func(lx *lexer) stateFn type stateFn func(lx *lexer) stateFn
...@@ -56,11 +60,18 @@ type lexer struct { ...@@ -56,11 +60,18 @@ type lexer struct {
input string input string
start int start int
pos int pos int
width int
line int line int
state stateFn state stateFn
items chan item items chan item
// Allow for backing up up to three runes.
// This is necessary because TOML contains 3-rune tokens (""" and ''').
prevWidths [3]int
nprev int // how many of prevWidths are in use
// If we emit an eof, we can still back up, but it is not OK to call
// next again.
atEOF bool
// A stack of state functions used to maintain context. // A stack of state functions used to maintain context.
// The idea is to reuse parts of the state machine in various places. // The idea is to reuse parts of the state machine in various places.
// For example, values can appear at the top level or within arbitrarily // For example, values can appear at the top level or within arbitrarily
...@@ -88,7 +99,7 @@ func (lx *lexer) nextItem() item { ...@@ -88,7 +99,7 @@ func (lx *lexer) nextItem() item {
func lex(input string) *lexer { func lex(input string) *lexer {
lx := &lexer{ lx := &lexer{
input: input + "\n", input: input,
state: lexTop, state: lexTop,
line: 1, line: 1,
items: make(chan item, 10), items: make(chan item, 10),
...@@ -103,7 +114,7 @@ func (lx *lexer) push(state stateFn) { ...@@ -103,7 +114,7 @@ func (lx *lexer) push(state stateFn) {
func (lx *lexer) pop() stateFn { func (lx *lexer) pop() stateFn {
if len(lx.stack) == 0 { if len(lx.stack) == 0 {
return lx.errorf("BUG in lexer: no states to pop.") return lx.errorf("BUG in lexer: no states to pop")
} }
last := lx.stack[len(lx.stack)-1] last := lx.stack[len(lx.stack)-1]
lx.stack = lx.stack[0 : len(lx.stack)-1] lx.stack = lx.stack[0 : len(lx.stack)-1]
...@@ -125,16 +136,25 @@ func (lx *lexer) emitTrim(typ itemType) { ...@@ -125,16 +136,25 @@ func (lx *lexer) emitTrim(typ itemType) {
} }
func (lx *lexer) next() (r rune) { func (lx *lexer) next() (r rune) {
if lx.atEOF {
panic("next called after EOF")
}
if lx.pos >= len(lx.input) { if lx.pos >= len(lx.input) {
lx.width = 0 lx.atEOF = true
return eof return eof
} }
if lx.input[lx.pos] == '\n' { if lx.input[lx.pos] == '\n' {
lx.line++ lx.line++
} }
r, lx.width = utf8.DecodeRuneInString(lx.input[lx.pos:]) lx.prevWidths[2] = lx.prevWidths[1]
lx.pos += lx.width lx.prevWidths[1] = lx.prevWidths[0]
if lx.nprev < 3 {
lx.nprev++
}
r, w := utf8.DecodeRuneInString(lx.input[lx.pos:])
lx.prevWidths[0] = w
lx.pos += w
return r return r
} }
...@@ -143,9 +163,20 @@ func (lx *lexer) ignore() { ...@@ -143,9 +163,20 @@ func (lx *lexer) ignore() {
lx.start = lx.pos lx.start = lx.pos
} }
// backup steps back one rune. Can be called only once per call of next. // backup steps back one rune. Can be called only twice between calls to next.
func (lx *lexer) backup() { func (lx *lexer) backup() {
lx.pos -= lx.width if lx.atEOF {
lx.atEOF = false
return
}
if lx.nprev < 1 {
panic("backed up too far")
}
w := lx.prevWidths[0]
lx.prevWidths[0] = lx.prevWidths[1]
lx.prevWidths[1] = lx.prevWidths[2]
lx.nprev--
lx.pos -= w
if lx.pos < len(lx.input) && lx.input[lx.pos] == '\n' { if lx.pos < len(lx.input) && lx.input[lx.pos] == '\n' {
lx.line-- lx.line--
} }
...@@ -182,7 +213,7 @@ func (lx *lexer) skip(pred func(rune) bool) { ...@@ -182,7 +213,7 @@ func (lx *lexer) skip(pred func(rune) bool) {
// errorf stops all lexing by emitting an error and returning `nil`. // errorf stops all lexing by emitting an error and returning `nil`.
// Note that any value that is a character is escaped if it's a special // Note that any value that is a character is escaped if it's a special
// character (new lines, tabs, etc.). // character (newlines, tabs, etc.).
func (lx *lexer) errorf(format string, values ...interface{}) stateFn { func (lx *lexer) errorf(format string, values ...interface{}) stateFn {
lx.items <- item{ lx.items <- item{
itemError, itemError,
...@@ -198,7 +229,6 @@ func lexTop(lx *lexer) stateFn { ...@@ -198,7 +229,6 @@ func lexTop(lx *lexer) stateFn {
if isWhitespace(r) || isNL(r) { if isWhitespace(r) || isNL(r) {
return lexSkip(lx, lexTop) return lexSkip(lx, lexTop)
} }
switch r { switch r {
case commentStart: case commentStart:
lx.push(lexTop) lx.push(lexTop)
...@@ -207,7 +237,7 @@ func lexTop(lx *lexer) stateFn { ...@@ -207,7 +237,7 @@ func lexTop(lx *lexer) stateFn {
return lexTableStart return lexTableStart
case eof: case eof:
if lx.pos > lx.start { if lx.pos > lx.start {
return lx.errorf("Unexpected EOF.") return lx.errorf("unexpected EOF")
} }
lx.emit(itemEOF) lx.emit(itemEOF)
return nil return nil
...@@ -222,12 +252,12 @@ func lexTop(lx *lexer) stateFn { ...@@ -222,12 +252,12 @@ func lexTop(lx *lexer) stateFn {
// lexTopEnd is entered whenever a top-level item has been consumed. (A value // lexTopEnd is entered whenever a top-level item has been consumed. (A value
// or a table.) It must see only whitespace, and will turn back to lexTop // or a table.) It must see only whitespace, and will turn back to lexTop
// upon a new line. If it sees EOF, it will quit the lexer successfully. // upon a newline. If it sees EOF, it will quit the lexer successfully.
func lexTopEnd(lx *lexer) stateFn { func lexTopEnd(lx *lexer) stateFn {
r := lx.next() r := lx.next()
switch { switch {
case r == commentStart: case r == commentStart:
// a comment will read to a new line for us. // a comment will read to a newline for us.
lx.push(lexTop) lx.push(lexTop)
return lexCommentStart return lexCommentStart
case isWhitespace(r): case isWhitespace(r):
...@@ -236,11 +266,11 @@ func lexTopEnd(lx *lexer) stateFn { ...@@ -236,11 +266,11 @@ func lexTopEnd(lx *lexer) stateFn {
lx.ignore() lx.ignore()
return lexTop return lexTop
case r == eof: case r == eof:
lx.ignore() lx.emit(itemEOF)
return lexTop return nil
} }
return lx.errorf("Expected a top-level item to end with a new line, "+ return lx.errorf("expected a top-level item to end with a newline, "+
"comment or EOF, but got %q instead.", r) "comment, or EOF, but got %q instead", r)
} }
// lexTable lexes the beginning of a table. Namely, it makes sure that // lexTable lexes the beginning of a table. Namely, it makes sure that
...@@ -267,8 +297,8 @@ func lexTableEnd(lx *lexer) stateFn { ...@@ -267,8 +297,8 @@ func lexTableEnd(lx *lexer) stateFn {
func lexArrayTableEnd(lx *lexer) stateFn { func lexArrayTableEnd(lx *lexer) stateFn {
if r := lx.next(); r != arrayTableEnd { if r := lx.next(); r != arrayTableEnd {
return lx.errorf("Expected end of table array name delimiter %q, "+ return lx.errorf("expected end of table array name delimiter %q, "+
"but got %q instead.", arrayTableEnd, r) "but got %q instead", arrayTableEnd, r)
} }
lx.emit(itemArrayTableEnd) lx.emit(itemArrayTableEnd)
return lexTopEnd return lexTopEnd
...@@ -278,11 +308,11 @@ func lexTableNameStart(lx *lexer) stateFn { ...@@ -278,11 +308,11 @@ func lexTableNameStart(lx *lexer) stateFn {
lx.skip(isWhitespace) lx.skip(isWhitespace)
switch r := lx.peek(); { switch r := lx.peek(); {
case r == tableEnd || r == eof: case r == tableEnd || r == eof:
return lx.errorf("Unexpected end of table name. (Table names cannot " + return lx.errorf("unexpected end of table name " +
"be empty.)") "(table names cannot be empty)")
case r == tableSep: case r == tableSep:
return lx.errorf("Unexpected table separator. (Table names cannot " + return lx.errorf("unexpected table separator " +
"be empty.)") "(table names cannot be empty)")
case r == stringStart || r == rawStringStart: case r == stringStart || r == rawStringStart:
lx.ignore() lx.ignore()
lx.push(lexTableNameEnd) lx.push(lexTableNameEnd)
...@@ -317,8 +347,8 @@ func lexTableNameEnd(lx *lexer) stateFn { ...@@ -317,8 +347,8 @@ func lexTableNameEnd(lx *lexer) stateFn {
case r == tableEnd: case r == tableEnd:
return lx.pop() return lx.pop()
default: default:
return lx.errorf("Expected '.' or ']' to end table name, but got %q "+ return lx.errorf("expected '.' or ']' to end table name, "+
"instead.", r) "but got %q instead", r)
} }
} }
...@@ -328,7 +358,7 @@ func lexKeyStart(lx *lexer) stateFn { ...@@ -328,7 +358,7 @@ func lexKeyStart(lx *lexer) stateFn {
r := lx.peek() r := lx.peek()
switch { switch {
case r == keySep: case r == keySep:
return lx.errorf("Unexpected key separator %q.", keySep) return lx.errorf("unexpected key separator %q", keySep)
case isWhitespace(r) || isNL(r): case isWhitespace(r) || isNL(r):
lx.next() lx.next()
return lexSkip(lx, lexKeyStart) return lexSkip(lx, lexKeyStart)
...@@ -359,7 +389,7 @@ func lexBareKey(lx *lexer) stateFn { ...@@ -359,7 +389,7 @@ func lexBareKey(lx *lexer) stateFn {
lx.emit(itemText) lx.emit(itemText)
return lexKeyEnd return lexKeyEnd
default: default:
return lx.errorf("Bare keys cannot contain %q.", r) return lx.errorf("bare keys cannot contain %q", r)
} }
} }
...@@ -372,7 +402,7 @@ func lexKeyEnd(lx *lexer) stateFn { ...@@ -372,7 +402,7 @@ func lexKeyEnd(lx *lexer) stateFn {
case isWhitespace(r): case isWhitespace(r):
return lexSkip(lx, lexKeyEnd) return lexSkip(lx, lexKeyEnd)
default: default:
return lx.errorf("Expected key separator %q, but got %q instead.", return lx.errorf("expected key separator %q, but got %q instead",
keySep, r) keySep, r)
} }
} }
...@@ -381,9 +411,8 @@ func lexKeyEnd(lx *lexer) stateFn { ...@@ -381,9 +411,8 @@ func lexKeyEnd(lx *lexer) stateFn {
// lexValue will ignore whitespace. // lexValue will ignore whitespace.
// After a value is lexed, the last state on the next is popped and returned. // After a value is lexed, the last state on the next is popped and returned.
func lexValue(lx *lexer) stateFn { func lexValue(lx *lexer) stateFn {
// We allow whitespace to precede a value, but NOT new lines. // We allow whitespace to precede a value, but NOT newlines.
// In array syntax, the array states are responsible for ignoring new // In array syntax, the array states are responsible for ignoring newlines.
// lines.
r := lx.next() r := lx.next()
switch { switch {
case isWhitespace(r): case isWhitespace(r):
...@@ -397,6 +426,10 @@ func lexValue(lx *lexer) stateFn { ...@@ -397,6 +426,10 @@ func lexValue(lx *lexer) stateFn {
lx.ignore() lx.ignore()
lx.emit(itemArray) lx.emit(itemArray)
return lexArrayValue return lexArrayValue
case inlineTableStart:
lx.ignore()
lx.emit(itemInlineTableStart)
return lexInlineTableValue
case stringStart: case stringStart:
if lx.accept(stringStart) { if lx.accept(stringStart) {
if lx.accept(stringStart) { if lx.accept(stringStart) {
...@@ -420,7 +453,7 @@ func lexValue(lx *lexer) stateFn { ...@@ -420,7 +453,7 @@ func lexValue(lx *lexer) stateFn {
case '+', '-': case '+', '-':
return lexNumberStart return lexNumberStart
case '.': // special error case, be kind to users case '.': // special error case, be kind to users
return lx.errorf("Floats must start with a digit, not '.'.") return lx.errorf("floats must start with a digit, not '.'")
} }
if unicode.IsLetter(r) { if unicode.IsLetter(r) {
// Be permissive here; lexBool will give a nice error if the // Be permissive here; lexBool will give a nice error if the
...@@ -430,11 +463,11 @@ func lexValue(lx *lexer) stateFn { ...@@ -430,11 +463,11 @@ func lexValue(lx *lexer) stateFn {
lx.backup() lx.backup()
return lexBool return lexBool
} }
return lx.errorf("Expected value but found %q instead.", r) return lx.errorf("expected value but found %q instead", r)
} }
// lexArrayValue consumes one value in an array. It assumes that '[' or ',' // lexArrayValue consumes one value in an array. It assumes that '[' or ','
// have already been consumed. All whitespace and new lines are ignored. // have already been consumed. All whitespace and newlines are ignored.
func lexArrayValue(lx *lexer) stateFn { func lexArrayValue(lx *lexer) stateFn {
r := lx.next() r := lx.next()
switch { switch {
...@@ -443,10 +476,11 @@ func lexArrayValue(lx *lexer) stateFn { ...@@ -443,10 +476,11 @@ func lexArrayValue(lx *lexer) stateFn {
case r == commentStart: case r == commentStart:
lx.push(lexArrayValue) lx.push(lexArrayValue)
return lexCommentStart return lexCommentStart
case r == arrayValTerm: case r == comma:
return lx.errorf("Unexpected array value terminator %q.", return lx.errorf("unexpected comma")
arrayValTerm)
case r == arrayEnd: case r == arrayEnd:
// NOTE(caleb): The spec isn't clear about whether you can have
// a trailing comma or not, so we'll allow it.
return lexArrayEnd return lexArrayEnd
} }
...@@ -455,8 +489,9 @@ func lexArrayValue(lx *lexer) stateFn { ...@@ -455,8 +489,9 @@ func lexArrayValue(lx *lexer) stateFn {
return lexValue return lexValue
} }
// lexArrayValueEnd consumes the cruft between values of an array. Namely, // lexArrayValueEnd consumes everything between the end of an array value and
// it ignores whitespace and expects either a ',' or a ']'. // the next value (or the end of the array): it ignores whitespace and newlines
// and expects either a ',' or a ']'.
func lexArrayValueEnd(lx *lexer) stateFn { func lexArrayValueEnd(lx *lexer) stateFn {
r := lx.next() r := lx.next()
switch { switch {
...@@ -465,31 +500,88 @@ func lexArrayValueEnd(lx *lexer) stateFn { ...@@ -465,31 +500,88 @@ func lexArrayValueEnd(lx *lexer) stateFn {
case r == commentStart: case r == commentStart:
lx.push(lexArrayValueEnd) lx.push(lexArrayValueEnd)
return lexCommentStart return lexCommentStart
case r == arrayValTerm: case r == comma:
lx.ignore() lx.ignore()
return lexArrayValue // move on to the next value return lexArrayValue // move on to the next value
case r == arrayEnd: case r == arrayEnd:
return lexArrayEnd return lexArrayEnd
} }
return lx.errorf("Expected an array value terminator %q or an array "+ return lx.errorf(
"terminator %q, but got %q instead.", arrayValTerm, arrayEnd, r) "expected a comma or array terminator %q, but got %q instead",
arrayEnd, r,
)
} }
// lexArrayEnd finishes the lexing of an array. It assumes that a ']' has // lexArrayEnd finishes the lexing of an array.
// just been consumed. // It assumes that a ']' has just been consumed.
func lexArrayEnd(lx *lexer) stateFn { func lexArrayEnd(lx *lexer) stateFn {
lx.ignore() lx.ignore()
lx.emit(itemArrayEnd) lx.emit(itemArrayEnd)
return lx.pop() return lx.pop()
} }
// lexInlineTableValue consumes one key/value pair in an inline table.
// It assumes that '{' or ',' have already been consumed. Whitespace is ignored.
func lexInlineTableValue(lx *lexer) stateFn {
r := lx.next()
switch {
case isWhitespace(r):
return lexSkip(lx, lexInlineTableValue)
case isNL(r):
return lx.errorf("newlines not allowed within inline tables")
case r == commentStart:
lx.push(lexInlineTableValue)
return lexCommentStart
case r == comma:
return lx.errorf("unexpected comma")
case r == inlineTableEnd:
return lexInlineTableEnd
}
lx.backup()
lx.push(lexInlineTableValueEnd)
return lexKeyStart
}
// lexInlineTableValueEnd consumes everything between the end of an inline table
// key/value pair and the next pair (or the end of the table):
// it ignores whitespace and expects either a ',' or a '}'.
func lexInlineTableValueEnd(lx *lexer) stateFn {
r := lx.next()
switch {
case isWhitespace(r):
return lexSkip(lx, lexInlineTableValueEnd)
case isNL(r):
return lx.errorf("newlines not allowed within inline tables")
case r == commentStart:
lx.push(lexInlineTableValueEnd)
return lexCommentStart
case r == comma:
lx.ignore()
return lexInlineTableValue
case r == inlineTableEnd:
return lexInlineTableEnd
}
return lx.errorf("expected a comma or an inline table terminator %q, "+
"but got %q instead", inlineTableEnd, r)
}
// lexInlineTableEnd finishes the lexing of an inline table.
// It assumes that a '}' has just been consumed.
func lexInlineTableEnd(lx *lexer) stateFn {
lx.ignore()
lx.emit(itemInlineTableEnd)
return lx.pop()
}
// lexString consumes the inner contents of a string. It assumes that the // lexString consumes the inner contents of a string. It assumes that the
// beginning '"' has already been consumed and ignored. // beginning '"' has already been consumed and ignored.
func lexString(lx *lexer) stateFn { func lexString(lx *lexer) stateFn {
r := lx.next() r := lx.next()
switch { switch {
case r == eof:
return lx.errorf("unexpected EOF")
case isNL(r): case isNL(r):
return lx.errorf("Strings cannot contain new lines.") return lx.errorf("strings cannot contain newlines")
case r == '\\': case r == '\\':
lx.push(lexString) lx.push(lexString)
return lexStringEscape return lexStringEscape
...@@ -506,11 +598,12 @@ func lexString(lx *lexer) stateFn { ...@@ -506,11 +598,12 @@ func lexString(lx *lexer) stateFn {
// lexMultilineString consumes the inner contents of a string. It assumes that // lexMultilineString consumes the inner contents of a string. It assumes that
// the beginning '"""' has already been consumed and ignored. // the beginning '"""' has already been consumed and ignored.
func lexMultilineString(lx *lexer) stateFn { func lexMultilineString(lx *lexer) stateFn {
r := lx.next() switch lx.next() {
switch { case eof:
case r == '\\': return lx.errorf("unexpected EOF")
case '\\':
return lexMultilineStringEscape return lexMultilineStringEscape
case r == stringEnd: case stringEnd:
if lx.accept(stringEnd) { if lx.accept(stringEnd) {
if lx.accept(stringEnd) { if lx.accept(stringEnd) {
lx.backup() lx.backup()
...@@ -534,8 +627,10 @@ func lexMultilineString(lx *lexer) stateFn { ...@@ -534,8 +627,10 @@ func lexMultilineString(lx *lexer) stateFn {
func lexRawString(lx *lexer) stateFn { func lexRawString(lx *lexer) stateFn {
r := lx.next() r := lx.next()
switch { switch {
case r == eof:
return lx.errorf("unexpected EOF")
case isNL(r): case isNL(r):
return lx.errorf("Strings cannot contain new lines.") return lx.errorf("strings cannot contain newlines")
case r == rawStringEnd: case r == rawStringEnd:
lx.backup() lx.backup()
lx.emit(itemRawString) lx.emit(itemRawString)
...@@ -547,12 +642,13 @@ func lexRawString(lx *lexer) stateFn { ...@@ -547,12 +642,13 @@ func lexRawString(lx *lexer) stateFn {
} }
// lexMultilineRawString consumes a raw string. Nothing can be escaped in such // lexMultilineRawString consumes a raw string. Nothing can be escaped in such
// a string. It assumes that the beginning "'" has already been consumed and // a string. It assumes that the beginning "'''" has already been consumed and
// ignored. // ignored.
func lexMultilineRawString(lx *lexer) stateFn { func lexMultilineRawString(lx *lexer) stateFn {
r := lx.next() switch lx.next() {
switch { case eof:
case r == rawStringEnd: return lx.errorf("unexpected EOF")
case rawStringEnd:
if lx.accept(rawStringEnd) { if lx.accept(rawStringEnd) {
if lx.accept(rawStringEnd) { if lx.accept(rawStringEnd) {
lx.backup() lx.backup()
...@@ -605,10 +701,9 @@ func lexStringEscape(lx *lexer) stateFn { ...@@ -605,10 +701,9 @@ func lexStringEscape(lx *lexer) stateFn {
case 'U': case 'U':
return lexLongUnicodeEscape return lexLongUnicodeEscape
} }
return lx.errorf("Invalid escape character %q. Only the following "+ return lx.errorf("invalid escape character %q; only the following "+
"escape characters are allowed: "+ "escape characters are allowed: "+
"\\b, \\t, \\n, \\f, \\r, \\\", \\/, \\\\, "+ `\b, \t, \n, \f, \r, \", \\, \uXXXX, and \UXXXXXXXX`, r)
"\\uXXXX and \\UXXXXXXXX.", r)
} }
func lexShortUnicodeEscape(lx *lexer) stateFn { func lexShortUnicodeEscape(lx *lexer) stateFn {
...@@ -616,8 +711,8 @@ func lexShortUnicodeEscape(lx *lexer) stateFn { ...@@ -616,8 +711,8 @@ func lexShortUnicodeEscape(lx *lexer) stateFn {
for i := 0; i < 4; i++ { for i := 0; i < 4; i++ {
r = lx.next() r = lx.next()
if !isHexadecimal(r) { if !isHexadecimal(r) {
return lx.errorf("Expected four hexadecimal digits after '\\u', "+ return lx.errorf(`expected four hexadecimal digits after '\u', `+
"but got '%s' instead.", lx.current()) "but got %q instead", lx.current())
} }
} }
return lx.pop() return lx.pop()
...@@ -628,8 +723,8 @@ func lexLongUnicodeEscape(lx *lexer) stateFn { ...@@ -628,8 +723,8 @@ func lexLongUnicodeEscape(lx *lexer) stateFn {
for i := 0; i < 8; i++ { for i := 0; i < 8; i++ {
r = lx.next() r = lx.next()
if !isHexadecimal(r) { if !isHexadecimal(r) {
return lx.errorf("Expected eight hexadecimal digits after '\\U', "+ return lx.errorf(`expected eight hexadecimal digits after '\U', `+
"but got '%s' instead.", lx.current()) "but got %q instead", lx.current())
} }
} }
return lx.pop() return lx.pop()
...@@ -647,9 +742,9 @@ func lexNumberOrDateStart(lx *lexer) stateFn { ...@@ -647,9 +742,9 @@ func lexNumberOrDateStart(lx *lexer) stateFn {
case 'e', 'E': case 'e', 'E':
return lexFloat return lexFloat
case '.': case '.':
return lx.errorf("Floats must start with a digit, not '.'.") return lx.errorf("floats must start with a digit, not '.'")
} }
return lx.errorf("Expected a digit but got %q.", r) return lx.errorf("expected a digit but got %q", r)
} }
// lexNumberOrDate consumes either an integer, float or datetime. // lexNumberOrDate consumes either an integer, float or datetime.
...@@ -680,7 +775,7 @@ func lexDatetime(lx *lexer) stateFn { ...@@ -680,7 +775,7 @@ func lexDatetime(lx *lexer) stateFn {
return lexDatetime return lexDatetime
} }
switch r { switch r {
case '-', 'T', ':', '.', 'Z': case '-', 'T', ':', '.', 'Z', '+':
return lexDatetime return lexDatetime
} }
...@@ -697,9 +792,9 @@ func lexNumberStart(lx *lexer) stateFn { ...@@ -697,9 +792,9 @@ func lexNumberStart(lx *lexer) stateFn {
r := lx.next() r := lx.next()
if !isDigit(r) { if !isDigit(r) {
if r == '.' { if r == '.' {
return lx.errorf("Floats must start with a digit, not '.'.") return lx.errorf("floats must start with a digit, not '.'")
} }
return lx.errorf("Expected a digit but got %q.", r) return lx.errorf("expected a digit but got %q", r)
} }
return lexNumber return lexNumber
} }
...@@ -745,7 +840,7 @@ func lexBool(lx *lexer) stateFn { ...@@ -745,7 +840,7 @@ func lexBool(lx *lexer) stateFn {
var rs []rune var rs []rune
for { for {
r := lx.next() r := lx.next()
if r == eof || isWhitespace(r) || isNL(r) { if !unicode.IsLetter(r) {
lx.backup() lx.backup()
break break
} }
...@@ -757,7 +852,7 @@ func lexBool(lx *lexer) stateFn { ...@@ -757,7 +852,7 @@ func lexBool(lx *lexer) stateFn {
lx.emit(itemBool) lx.emit(itemBool)
return lx.pop() return lx.pop()
} }
return lx.errorf("Expected value but found %q instead.", s) return lx.errorf("expected value but found %q instead", s)
} }
// lexCommentStart begins the lexing of a comment. It will emit // lexCommentStart begins the lexing of a comment. It will emit
...@@ -769,7 +864,7 @@ func lexCommentStart(lx *lexer) stateFn { ...@@ -769,7 +864,7 @@ func lexCommentStart(lx *lexer) stateFn {
} }
// lexComment lexes an entire comment. It assumes that '#' has been consumed. // lexComment lexes an entire comment. It assumes that '#' has been consumed.
// It will consume *up to* the first new line character, and pass control // It will consume *up to* the first newline character, and pass control
// back to the last state on the stack. // back to the last state on the stack.
func lexComment(lx *lexer) stateFn { func lexComment(lx *lexer) stateFn {
r := lx.peek() r := lx.peek()
......
...@@ -269,6 +269,41 @@ func (p *parser) value(it item) (interface{}, tomlType) { ...@@ -269,6 +269,41 @@ func (p *parser) value(it item) (interface{}, tomlType) {
types = append(types, typ) types = append(types, typ)
} }
return array, p.typeOfArray(types) return array, p.typeOfArray(types)
case itemInlineTableStart:
var (
hash = make(map[string]interface{})
outerContext = p.context
outerKey = p.currentKey
)
p.context = append(p.context, p.currentKey)
p.currentKey = ""
for it := p.next(); it.typ != itemInlineTableEnd; it = p.next() {
if it.typ != itemKeyStart {
p.bug("Expected key start but instead found %q, around line %d",
it.val, p.approxLine)
}
if it.typ == itemCommentStart {
p.expect(itemText)
continue
}
// retrieve key
k := p.next()
p.approxLine = k.line
kname := p.keyString(k)
// retrieve value
p.currentKey = kname
val, typ := p.value(p.next())
// make sure we keep metadata up to date
p.setType(kname, typ)
p.ordered = append(p.ordered, p.context.add(p.currentKey))
hash[kname] = val
}
p.context = outerContext
p.currentKey = outerKey
return hash, tomlHash
} }
p.bug("Unexpected value type: %s", it.typ) p.bug("Unexpected value type: %s", it.typ)
panic("unreachable") panic("unreachable")
......
...@@ -2,6 +2,12 @@ ...@@ -2,6 +2,12 @@
"comment": "", "comment": "",
"ignore": "test", "ignore": "test",
"package": [ "package": [
{
"checksumSHA1": "Pc2ORQp+VY3Un/dkh4QwLC7R6lE=",
"path": "github.com/BurntSushi/toml",
"revision": "a368813c5e648fee92e5f6c30e3944ff9d5e8895",
"revisionTime": "2017-06-26T11:06:00Z"
},
{ {
"checksumSHA1": "spyv5/YFBjYyZLZa1U2LBfDR8PM=", "checksumSHA1": "spyv5/YFBjYyZLZa1U2LBfDR8PM=",
"path": "github.com/beorn7/perks/quantile", "path": "github.com/beorn7/perks/quantile",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment