|
Packit |
63bb0d |
package toml
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
import (
|
|
Packit |
63bb0d |
"fmt"
|
|
Packit |
63bb0d |
"strings"
|
|
Packit |
63bb0d |
"unicode"
|
|
Packit |
63bb0d |
"unicode/utf8"
|
|
Packit |
63bb0d |
)
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
type itemType int
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
const (
|
|
Packit |
63bb0d |
itemError itemType = iota
|
|
Packit |
63bb0d |
itemNIL // used in the parser to indicate no type
|
|
Packit |
63bb0d |
itemEOF
|
|
Packit |
63bb0d |
itemText
|
|
Packit |
63bb0d |
itemString
|
|
Packit |
63bb0d |
itemRawString
|
|
Packit |
63bb0d |
itemMultilineString
|
|
Packit |
63bb0d |
itemRawMultilineString
|
|
Packit |
63bb0d |
itemBool
|
|
Packit |
63bb0d |
itemInteger
|
|
Packit |
63bb0d |
itemFloat
|
|
Packit |
63bb0d |
itemDatetime
|
|
Packit |
63bb0d |
itemArray // the start of an array
|
|
Packit |
63bb0d |
itemArrayEnd
|
|
Packit |
63bb0d |
itemTableStart
|
|
Packit |
63bb0d |
itemTableEnd
|
|
Packit |
63bb0d |
itemArrayTableStart
|
|
Packit |
63bb0d |
itemArrayTableEnd
|
|
Packit |
63bb0d |
itemKeyStart
|
|
Packit |
63bb0d |
itemCommentStart
|
|
Packit |
63bb0d |
itemInlineTableStart
|
|
Packit |
63bb0d |
itemInlineTableEnd
|
|
Packit |
63bb0d |
)
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
const (
|
|
Packit |
63bb0d |
eof = 0
|
|
Packit |
63bb0d |
comma = ','
|
|
Packit |
63bb0d |
tableStart = '['
|
|
Packit |
63bb0d |
tableEnd = ']'
|
|
Packit |
63bb0d |
arrayTableStart = '['
|
|
Packit |
63bb0d |
arrayTableEnd = ']'
|
|
Packit |
63bb0d |
tableSep = '.'
|
|
Packit |
63bb0d |
keySep = '='
|
|
Packit |
63bb0d |
arrayStart = '['
|
|
Packit |
63bb0d |
arrayEnd = ']'
|
|
Packit |
63bb0d |
commentStart = '#'
|
|
Packit |
63bb0d |
stringStart = '"'
|
|
Packit |
63bb0d |
stringEnd = '"'
|
|
Packit |
63bb0d |
rawStringStart = '\''
|
|
Packit |
63bb0d |
rawStringEnd = '\''
|
|
Packit |
63bb0d |
inlineTableStart = '{'
|
|
Packit |
63bb0d |
inlineTableEnd = '}'
|
|
Packit |
63bb0d |
)
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
type stateFn func(lx *lexer) stateFn
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
type lexer struct {
|
|
Packit |
63bb0d |
input string
|
|
Packit |
63bb0d |
start int
|
|
Packit |
63bb0d |
pos int
|
|
Packit |
63bb0d |
line int
|
|
Packit |
63bb0d |
state stateFn
|
|
Packit |
63bb0d |
items chan item
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// Allow for backing up up to three runes.
|
|
Packit |
63bb0d |
// This is necessary because TOML contains 3-rune tokens (""" and ''').
|
|
Packit |
63bb0d |
prevWidths [3]int
|
|
Packit |
63bb0d |
nprev int // how many of prevWidths are in use
|
|
Packit |
63bb0d |
// If we emit an eof, we can still back up, but it is not OK to call
|
|
Packit |
63bb0d |
// next again.
|
|
Packit |
63bb0d |
atEOF bool
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// A stack of state functions used to maintain context.
|
|
Packit |
63bb0d |
// The idea is to reuse parts of the state machine in various places.
|
|
Packit |
63bb0d |
// For example, values can appear at the top level or within arbitrarily
|
|
Packit |
63bb0d |
// nested arrays. The last state on the stack is used after a value has
|
|
Packit |
63bb0d |
// been lexed. Similarly for comments.
|
|
Packit |
63bb0d |
stack []stateFn
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
type item struct {
|
|
Packit |
63bb0d |
typ itemType
|
|
Packit |
63bb0d |
val string
|
|
Packit |
63bb0d |
line int
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
func (lx *lexer) nextItem() item {
|
|
Packit |
63bb0d |
for {
|
|
Packit |
63bb0d |
select {
|
|
Packit |
63bb0d |
case item := <-lx.items:
|
|
Packit |
63bb0d |
return item
|
|
Packit |
63bb0d |
default:
|
|
Packit |
63bb0d |
lx.state = lx.state(lx)
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
func lex(input string) *lexer {
|
|
Packit |
63bb0d |
lx := &lexer{
|
|
Packit |
63bb0d |
input: input,
|
|
Packit |
63bb0d |
state: lexTop,
|
|
Packit |
63bb0d |
line: 1,
|
|
Packit |
63bb0d |
items: make(chan item, 10),
|
|
Packit |
63bb0d |
stack: make([]stateFn, 0, 10),
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
return lx
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
func (lx *lexer) push(state stateFn) {
|
|
Packit |
63bb0d |
lx.stack = append(lx.stack, state)
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
func (lx *lexer) pop() stateFn {
|
|
Packit |
63bb0d |
if len(lx.stack) == 0 {
|
|
Packit |
63bb0d |
return lx.errorf("BUG in lexer: no states to pop")
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
last := lx.stack[len(lx.stack)-1]
|
|
Packit |
63bb0d |
lx.stack = lx.stack[0 : len(lx.stack)-1]
|
|
Packit |
63bb0d |
return last
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
func (lx *lexer) current() string {
|
|
Packit |
63bb0d |
return lx.input[lx.start:lx.pos]
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
func (lx *lexer) emit(typ itemType) {
|
|
Packit |
63bb0d |
lx.items <- item{typ, lx.current(), lx.line}
|
|
Packit |
63bb0d |
lx.start = lx.pos
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
func (lx *lexer) emitTrim(typ itemType) {
|
|
Packit |
63bb0d |
lx.items <- item{typ, strings.TrimSpace(lx.current()), lx.line}
|
|
Packit |
63bb0d |
lx.start = lx.pos
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
func (lx *lexer) next() (r rune) {
|
|
Packit |
63bb0d |
if lx.atEOF {
|
|
Packit |
63bb0d |
panic("next called after EOF")
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
if lx.pos >= len(lx.input) {
|
|
Packit |
63bb0d |
lx.atEOF = true
|
|
Packit |
63bb0d |
return eof
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
if lx.input[lx.pos] == '\n' {
|
|
Packit |
63bb0d |
lx.line++
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
lx.prevWidths[2] = lx.prevWidths[1]
|
|
Packit |
63bb0d |
lx.prevWidths[1] = lx.prevWidths[0]
|
|
Packit |
63bb0d |
if lx.nprev < 3 {
|
|
Packit |
63bb0d |
lx.nprev++
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
r, w := utf8.DecodeRuneInString(lx.input[lx.pos:])
|
|
Packit |
63bb0d |
lx.prevWidths[0] = w
|
|
Packit |
63bb0d |
lx.pos += w
|
|
Packit |
63bb0d |
return r
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// ignore skips over the pending input before this point.
|
|
Packit |
63bb0d |
func (lx *lexer) ignore() {
|
|
Packit |
63bb0d |
lx.start = lx.pos
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// backup steps back one rune. Can be called only twice between calls to next.
|
|
Packit |
63bb0d |
func (lx *lexer) backup() {
|
|
Packit |
63bb0d |
if lx.atEOF {
|
|
Packit |
63bb0d |
lx.atEOF = false
|
|
Packit |
63bb0d |
return
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
if lx.nprev < 1 {
|
|
Packit |
63bb0d |
panic("backed up too far")
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
w := lx.prevWidths[0]
|
|
Packit |
63bb0d |
lx.prevWidths[0] = lx.prevWidths[1]
|
|
Packit |
63bb0d |
lx.prevWidths[1] = lx.prevWidths[2]
|
|
Packit |
63bb0d |
lx.nprev--
|
|
Packit |
63bb0d |
lx.pos -= w
|
|
Packit |
63bb0d |
if lx.pos < len(lx.input) && lx.input[lx.pos] == '\n' {
|
|
Packit |
63bb0d |
lx.line--
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// accept consumes the next rune if it's equal to `valid`.
|
|
Packit |
63bb0d |
func (lx *lexer) accept(valid rune) bool {
|
|
Packit |
63bb0d |
if lx.next() == valid {
|
|
Packit |
63bb0d |
return true
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
lx.backup()
|
|
Packit |
63bb0d |
return false
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// peek returns but does not consume the next rune in the input.
|
|
Packit |
63bb0d |
func (lx *lexer) peek() rune {
|
|
Packit |
63bb0d |
r := lx.next()
|
|
Packit |
63bb0d |
lx.backup()
|
|
Packit |
63bb0d |
return r
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// skip ignores all input that matches the given predicate.
|
|
Packit |
63bb0d |
func (lx *lexer) skip(pred func(rune) bool) {
|
|
Packit |
63bb0d |
for {
|
|
Packit |
63bb0d |
r := lx.next()
|
|
Packit |
63bb0d |
if pred(r) {
|
|
Packit |
63bb0d |
continue
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
lx.backup()
|
|
Packit |
63bb0d |
lx.ignore()
|
|
Packit |
63bb0d |
return
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// errorf stops all lexing by emitting an error and returning `nil`.
|
|
Packit |
63bb0d |
// Note that any value that is a character is escaped if it's a special
|
|
Packit |
63bb0d |
// character (newlines, tabs, etc.).
|
|
Packit |
63bb0d |
func (lx *lexer) errorf(format string, values ...interface{}) stateFn {
|
|
Packit |
63bb0d |
lx.items <- item{
|
|
Packit |
63bb0d |
itemError,
|
|
Packit |
63bb0d |
fmt.Sprintf(format, values...),
|
|
Packit |
63bb0d |
lx.line,
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
return nil
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// lexTop consumes elements at the top level of TOML data.
|
|
Packit |
63bb0d |
func lexTop(lx *lexer) stateFn {
|
|
Packit |
63bb0d |
r := lx.next()
|
|
Packit |
63bb0d |
if isWhitespace(r) || isNL(r) {
|
|
Packit |
63bb0d |
return lexSkip(lx, lexTop)
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
switch r {
|
|
Packit |
63bb0d |
case commentStart:
|
|
Packit |
63bb0d |
lx.push(lexTop)
|
|
Packit |
63bb0d |
return lexCommentStart
|
|
Packit |
63bb0d |
case tableStart:
|
|
Packit |
63bb0d |
return lexTableStart
|
|
Packit |
63bb0d |
case eof:
|
|
Packit |
63bb0d |
if lx.pos > lx.start {
|
|
Packit |
63bb0d |
return lx.errorf("unexpected EOF")
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
lx.emit(itemEOF)
|
|
Packit |
63bb0d |
return nil
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// At this point, the only valid item can be a key, so we back up
|
|
Packit |
63bb0d |
// and let the key lexer do the rest.
|
|
Packit |
63bb0d |
lx.backup()
|
|
Packit |
63bb0d |
lx.push(lexTopEnd)
|
|
Packit |
63bb0d |
return lexKeyStart
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// lexTopEnd is entered whenever a top-level item has been consumed. (A value
|
|
Packit |
63bb0d |
// or a table.) It must see only whitespace, and will turn back to lexTop
|
|
Packit |
63bb0d |
// upon a newline. If it sees EOF, it will quit the lexer successfully.
|
|
Packit |
63bb0d |
func lexTopEnd(lx *lexer) stateFn {
|
|
Packit |
63bb0d |
r := lx.next()
|
|
Packit |
63bb0d |
switch {
|
|
Packit |
63bb0d |
case r == commentStart:
|
|
Packit |
63bb0d |
// a comment will read to a newline for us.
|
|
Packit |
63bb0d |
lx.push(lexTop)
|
|
Packit |
63bb0d |
return lexCommentStart
|
|
Packit |
63bb0d |
case isWhitespace(r):
|
|
Packit |
63bb0d |
return lexTopEnd
|
|
Packit |
63bb0d |
case isNL(r):
|
|
Packit |
63bb0d |
lx.ignore()
|
|
Packit |
63bb0d |
return lexTop
|
|
Packit |
63bb0d |
case r == eof:
|
|
Packit |
63bb0d |
lx.emit(itemEOF)
|
|
Packit |
63bb0d |
return nil
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
return lx.errorf("expected a top-level item to end with a newline, "+
|
|
Packit |
63bb0d |
"comment, or EOF, but got %q instead", r)
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// lexTable lexes the beginning of a table. Namely, it makes sure that
|
|
Packit |
63bb0d |
// it starts with a character other than '.' and ']'.
|
|
Packit |
63bb0d |
// It assumes that '[' has already been consumed.
|
|
Packit |
63bb0d |
// It also handles the case that this is an item in an array of tables.
|
|
Packit |
63bb0d |
// e.g., '[[name]]'.
|
|
Packit |
63bb0d |
func lexTableStart(lx *lexer) stateFn {
|
|
Packit |
63bb0d |
if lx.peek() == arrayTableStart {
|
|
Packit |
63bb0d |
lx.next()
|
|
Packit |
63bb0d |
lx.emit(itemArrayTableStart)
|
|
Packit |
63bb0d |
lx.push(lexArrayTableEnd)
|
|
Packit |
63bb0d |
} else {
|
|
Packit |
63bb0d |
lx.emit(itemTableStart)
|
|
Packit |
63bb0d |
lx.push(lexTableEnd)
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
return lexTableNameStart
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
func lexTableEnd(lx *lexer) stateFn {
|
|
Packit |
63bb0d |
lx.emit(itemTableEnd)
|
|
Packit |
63bb0d |
return lexTopEnd
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
func lexArrayTableEnd(lx *lexer) stateFn {
|
|
Packit |
63bb0d |
if r := lx.next(); r != arrayTableEnd {
|
|
Packit |
63bb0d |
return lx.errorf("expected end of table array name delimiter %q, "+
|
|
Packit |
63bb0d |
"but got %q instead", arrayTableEnd, r)
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
lx.emit(itemArrayTableEnd)
|
|
Packit |
63bb0d |
return lexTopEnd
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
func lexTableNameStart(lx *lexer) stateFn {
|
|
Packit |
63bb0d |
lx.skip(isWhitespace)
|
|
Packit |
63bb0d |
switch r := lx.peek(); {
|
|
Packit |
63bb0d |
case r == tableEnd || r == eof:
|
|
Packit |
63bb0d |
return lx.errorf("unexpected end of table name " +
|
|
Packit |
63bb0d |
"(table names cannot be empty)")
|
|
Packit |
63bb0d |
case r == tableSep:
|
|
Packit |
63bb0d |
return lx.errorf("unexpected table separator " +
|
|
Packit |
63bb0d |
"(table names cannot be empty)")
|
|
Packit |
63bb0d |
case r == stringStart || r == rawStringStart:
|
|
Packit |
63bb0d |
lx.ignore()
|
|
Packit |
63bb0d |
lx.push(lexTableNameEnd)
|
|
Packit |
63bb0d |
return lexValue // reuse string lexing
|
|
Packit |
63bb0d |
default:
|
|
Packit |
63bb0d |
return lexBareTableName
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// lexBareTableName lexes the name of a table. It assumes that at least one
|
|
Packit |
63bb0d |
// valid character for the table has already been read.
|
|
Packit |
63bb0d |
func lexBareTableName(lx *lexer) stateFn {
|
|
Packit |
63bb0d |
r := lx.next()
|
|
Packit |
63bb0d |
if isBareKeyChar(r) {
|
|
Packit |
63bb0d |
return lexBareTableName
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
lx.backup()
|
|
Packit |
63bb0d |
lx.emit(itemText)
|
|
Packit |
63bb0d |
return lexTableNameEnd
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// lexTableNameEnd reads the end of a piece of a table name, optionally
|
|
Packit |
63bb0d |
// consuming whitespace.
|
|
Packit |
63bb0d |
func lexTableNameEnd(lx *lexer) stateFn {
|
|
Packit |
63bb0d |
lx.skip(isWhitespace)
|
|
Packit |
63bb0d |
switch r := lx.next(); {
|
|
Packit |
63bb0d |
case isWhitespace(r):
|
|
Packit |
63bb0d |
return lexTableNameEnd
|
|
Packit |
63bb0d |
case r == tableSep:
|
|
Packit |
63bb0d |
lx.ignore()
|
|
Packit |
63bb0d |
return lexTableNameStart
|
|
Packit |
63bb0d |
case r == tableEnd:
|
|
Packit |
63bb0d |
return lx.pop()
|
|
Packit |
63bb0d |
default:
|
|
Packit |
63bb0d |
return lx.errorf("expected '.' or ']' to end table name, "+
|
|
Packit |
63bb0d |
"but got %q instead", r)
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// lexKeyStart consumes a key name up until the first non-whitespace character.
|
|
Packit |
63bb0d |
// lexKeyStart will ignore whitespace.
|
|
Packit |
63bb0d |
func lexKeyStart(lx *lexer) stateFn {
|
|
Packit |
63bb0d |
r := lx.peek()
|
|
Packit |
63bb0d |
switch {
|
|
Packit |
63bb0d |
case r == keySep:
|
|
Packit |
63bb0d |
return lx.errorf("unexpected key separator %q", keySep)
|
|
Packit |
63bb0d |
case isWhitespace(r) || isNL(r):
|
|
Packit |
63bb0d |
lx.next()
|
|
Packit |
63bb0d |
return lexSkip(lx, lexKeyStart)
|
|
Packit |
63bb0d |
case r == stringStart || r == rawStringStart:
|
|
Packit |
63bb0d |
lx.ignore()
|
|
Packit |
63bb0d |
lx.emit(itemKeyStart)
|
|
Packit |
63bb0d |
lx.push(lexKeyEnd)
|
|
Packit |
63bb0d |
return lexValue // reuse string lexing
|
|
Packit |
63bb0d |
default:
|
|
Packit |
63bb0d |
lx.ignore()
|
|
Packit |
63bb0d |
lx.emit(itemKeyStart)
|
|
Packit |
63bb0d |
return lexBareKey
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// lexBareKey consumes the text of a bare key. Assumes that the first character
|
|
Packit |
63bb0d |
// (which is not whitespace) has not yet been consumed.
|
|
Packit |
63bb0d |
func lexBareKey(lx *lexer) stateFn {
|
|
Packit |
63bb0d |
switch r := lx.next(); {
|
|
Packit |
63bb0d |
case isBareKeyChar(r):
|
|
Packit |
63bb0d |
return lexBareKey
|
|
Packit |
63bb0d |
case isWhitespace(r):
|
|
Packit |
63bb0d |
lx.backup()
|
|
Packit |
63bb0d |
lx.emit(itemText)
|
|
Packit |
63bb0d |
return lexKeyEnd
|
|
Packit |
63bb0d |
case r == keySep:
|
|
Packit |
63bb0d |
lx.backup()
|
|
Packit |
63bb0d |
lx.emit(itemText)
|
|
Packit |
63bb0d |
return lexKeyEnd
|
|
Packit |
63bb0d |
default:
|
|
Packit |
63bb0d |
return lx.errorf("bare keys cannot contain %q", r)
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// lexKeyEnd consumes the end of a key and trims whitespace (up to the key
|
|
Packit |
63bb0d |
// separator).
|
|
Packit |
63bb0d |
func lexKeyEnd(lx *lexer) stateFn {
|
|
Packit |
63bb0d |
switch r := lx.next(); {
|
|
Packit |
63bb0d |
case r == keySep:
|
|
Packit |
63bb0d |
return lexSkip(lx, lexValue)
|
|
Packit |
63bb0d |
case isWhitespace(r):
|
|
Packit |
63bb0d |
return lexSkip(lx, lexKeyEnd)
|
|
Packit |
63bb0d |
default:
|
|
Packit |
63bb0d |
return lx.errorf("expected key separator %q, but got %q instead",
|
|
Packit |
63bb0d |
keySep, r)
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// lexValue starts the consumption of a value anywhere a value is expected.
|
|
Packit |
63bb0d |
// lexValue will ignore whitespace.
|
|
Packit |
63bb0d |
// After a value is lexed, the last state on the next is popped and returned.
|
|
Packit |
63bb0d |
func lexValue(lx *lexer) stateFn {
|
|
Packit |
63bb0d |
// We allow whitespace to precede a value, but NOT newlines.
|
|
Packit |
63bb0d |
// In array syntax, the array states are responsible for ignoring newlines.
|
|
Packit |
63bb0d |
r := lx.next()
|
|
Packit |
63bb0d |
switch {
|
|
Packit |
63bb0d |
case isWhitespace(r):
|
|
Packit |
63bb0d |
return lexSkip(lx, lexValue)
|
|
Packit |
63bb0d |
case isDigit(r):
|
|
Packit |
63bb0d |
lx.backup() // avoid an extra state and use the same as above
|
|
Packit |
63bb0d |
return lexNumberOrDateStart
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
switch r {
|
|
Packit |
63bb0d |
case arrayStart:
|
|
Packit |
63bb0d |
lx.ignore()
|
|
Packit |
63bb0d |
lx.emit(itemArray)
|
|
Packit |
63bb0d |
return lexArrayValue
|
|
Packit |
63bb0d |
case inlineTableStart:
|
|
Packit |
63bb0d |
lx.ignore()
|
|
Packit |
63bb0d |
lx.emit(itemInlineTableStart)
|
|
Packit |
63bb0d |
return lexInlineTableValue
|
|
Packit |
63bb0d |
case stringStart:
|
|
Packit |
63bb0d |
if lx.accept(stringStart) {
|
|
Packit |
63bb0d |
if lx.accept(stringStart) {
|
|
Packit |
63bb0d |
lx.ignore() // Ignore """
|
|
Packit |
63bb0d |
return lexMultilineString
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
lx.backup()
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
lx.ignore() // ignore the '"'
|
|
Packit |
63bb0d |
return lexString
|
|
Packit |
63bb0d |
case rawStringStart:
|
|
Packit |
63bb0d |
if lx.accept(rawStringStart) {
|
|
Packit |
63bb0d |
if lx.accept(rawStringStart) {
|
|
Packit |
63bb0d |
lx.ignore() // Ignore """
|
|
Packit |
63bb0d |
return lexMultilineRawString
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
lx.backup()
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
lx.ignore() // ignore the "'"
|
|
Packit |
63bb0d |
return lexRawString
|
|
Packit |
63bb0d |
case '+', '-':
|
|
Packit |
63bb0d |
return lexNumberStart
|
|
Packit |
63bb0d |
case '.': // special error case, be kind to users
|
|
Packit |
63bb0d |
return lx.errorf("floats must start with a digit, not '.'")
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
if unicode.IsLetter(r) {
|
|
Packit |
63bb0d |
// Be permissive here; lexBool will give a nice error if the
|
|
Packit |
63bb0d |
// user wrote something like
|
|
Packit |
63bb0d |
// x = foo
|
|
Packit |
63bb0d |
// (i.e. not 'true' or 'false' but is something else word-like.)
|
|
Packit |
63bb0d |
lx.backup()
|
|
Packit |
63bb0d |
return lexBool
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
return lx.errorf("expected value but found %q instead", r)
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// lexArrayValue consumes one value in an array. It assumes that '[' or ','
|
|
Packit |
63bb0d |
// have already been consumed. All whitespace and newlines are ignored.
|
|
Packit |
63bb0d |
func lexArrayValue(lx *lexer) stateFn {
|
|
Packit |
63bb0d |
r := lx.next()
|
|
Packit |
63bb0d |
switch {
|
|
Packit |
63bb0d |
case isWhitespace(r) || isNL(r):
|
|
Packit |
63bb0d |
return lexSkip(lx, lexArrayValue)
|
|
Packit |
63bb0d |
case r == commentStart:
|
|
Packit |
63bb0d |
lx.push(lexArrayValue)
|
|
Packit |
63bb0d |
return lexCommentStart
|
|
Packit |
63bb0d |
case r == comma:
|
|
Packit |
63bb0d |
return lx.errorf("unexpected comma")
|
|
Packit |
63bb0d |
case r == arrayEnd:
|
|
Packit |
63bb0d |
// NOTE(caleb): The spec isn't clear about whether you can have
|
|
Packit |
63bb0d |
// a trailing comma or not, so we'll allow it.
|
|
Packit |
63bb0d |
return lexArrayEnd
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
lx.backup()
|
|
Packit |
63bb0d |
lx.push(lexArrayValueEnd)
|
|
Packit |
63bb0d |
return lexValue
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// lexArrayValueEnd consumes everything between the end of an array value and
|
|
Packit |
63bb0d |
// the next value (or the end of the array): it ignores whitespace and newlines
|
|
Packit |
63bb0d |
// and expects either a ',' or a ']'.
|
|
Packit |
63bb0d |
func lexArrayValueEnd(lx *lexer) stateFn {
|
|
Packit |
63bb0d |
r := lx.next()
|
|
Packit |
63bb0d |
switch {
|
|
Packit |
63bb0d |
case isWhitespace(r) || isNL(r):
|
|
Packit |
63bb0d |
return lexSkip(lx, lexArrayValueEnd)
|
|
Packit |
63bb0d |
case r == commentStart:
|
|
Packit |
63bb0d |
lx.push(lexArrayValueEnd)
|
|
Packit |
63bb0d |
return lexCommentStart
|
|
Packit |
63bb0d |
case r == comma:
|
|
Packit |
63bb0d |
lx.ignore()
|
|
Packit |
63bb0d |
return lexArrayValue // move on to the next value
|
|
Packit |
63bb0d |
case r == arrayEnd:
|
|
Packit |
63bb0d |
return lexArrayEnd
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
return lx.errorf(
|
|
Packit |
63bb0d |
"expected a comma or array terminator %q, but got %q instead",
|
|
Packit |
63bb0d |
arrayEnd, r,
|
|
Packit |
63bb0d |
)
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// lexArrayEnd finishes the lexing of an array.
|
|
Packit |
63bb0d |
// It assumes that a ']' has just been consumed.
|
|
Packit |
63bb0d |
func lexArrayEnd(lx *lexer) stateFn {
|
|
Packit |
63bb0d |
lx.ignore()
|
|
Packit |
63bb0d |
lx.emit(itemArrayEnd)
|
|
Packit |
63bb0d |
return lx.pop()
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// lexInlineTableValue consumes one key/value pair in an inline table.
|
|
Packit |
63bb0d |
// It assumes that '{' or ',' have already been consumed. Whitespace is ignored.
|
|
Packit |
63bb0d |
func lexInlineTableValue(lx *lexer) stateFn {
|
|
Packit |
63bb0d |
r := lx.next()
|
|
Packit |
63bb0d |
switch {
|
|
Packit |
63bb0d |
case isWhitespace(r):
|
|
Packit |
63bb0d |
return lexSkip(lx, lexInlineTableValue)
|
|
Packit |
63bb0d |
case isNL(r):
|
|
Packit |
63bb0d |
return lx.errorf("newlines not allowed within inline tables")
|
|
Packit |
63bb0d |
case r == commentStart:
|
|
Packit |
63bb0d |
lx.push(lexInlineTableValue)
|
|
Packit |
63bb0d |
return lexCommentStart
|
|
Packit |
63bb0d |
case r == comma:
|
|
Packit |
63bb0d |
return lx.errorf("unexpected comma")
|
|
Packit |
63bb0d |
case r == inlineTableEnd:
|
|
Packit |
63bb0d |
return lexInlineTableEnd
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
lx.backup()
|
|
Packit |
63bb0d |
lx.push(lexInlineTableValueEnd)
|
|
Packit |
63bb0d |
return lexKeyStart
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// lexInlineTableValueEnd consumes everything between the end of an inline table
|
|
Packit |
63bb0d |
// key/value pair and the next pair (or the end of the table):
|
|
Packit |
63bb0d |
// it ignores whitespace and expects either a ',' or a '}'.
|
|
Packit |
63bb0d |
func lexInlineTableValueEnd(lx *lexer) stateFn {
|
|
Packit |
63bb0d |
r := lx.next()
|
|
Packit |
63bb0d |
switch {
|
|
Packit |
63bb0d |
case isWhitespace(r):
|
|
Packit |
63bb0d |
return lexSkip(lx, lexInlineTableValueEnd)
|
|
Packit |
63bb0d |
case isNL(r):
|
|
Packit |
63bb0d |
return lx.errorf("newlines not allowed within inline tables")
|
|
Packit |
63bb0d |
case r == commentStart:
|
|
Packit |
63bb0d |
lx.push(lexInlineTableValueEnd)
|
|
Packit |
63bb0d |
return lexCommentStart
|
|
Packit |
63bb0d |
case r == comma:
|
|
Packit |
63bb0d |
lx.ignore()
|
|
Packit |
63bb0d |
return lexInlineTableValue
|
|
Packit |
63bb0d |
case r == inlineTableEnd:
|
|
Packit |
63bb0d |
return lexInlineTableEnd
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
return lx.errorf("expected a comma or an inline table terminator %q, "+
|
|
Packit |
63bb0d |
"but got %q instead", inlineTableEnd, r)
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// lexInlineTableEnd finishes the lexing of an inline table.
|
|
Packit |
63bb0d |
// It assumes that a '}' has just been consumed.
|
|
Packit |
63bb0d |
func lexInlineTableEnd(lx *lexer) stateFn {
|
|
Packit |
63bb0d |
lx.ignore()
|
|
Packit |
63bb0d |
lx.emit(itemInlineTableEnd)
|
|
Packit |
63bb0d |
return lx.pop()
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// lexString consumes the inner contents of a string. It assumes that the
|
|
Packit |
63bb0d |
// beginning '"' has already been consumed and ignored.
|
|
Packit |
63bb0d |
func lexString(lx *lexer) stateFn {
|
|
Packit |
63bb0d |
r := lx.next()
|
|
Packit |
63bb0d |
switch {
|
|
Packit |
63bb0d |
case r == eof:
|
|
Packit |
63bb0d |
return lx.errorf("unexpected EOF")
|
|
Packit |
63bb0d |
case isNL(r):
|
|
Packit |
63bb0d |
return lx.errorf("strings cannot contain newlines")
|
|
Packit |
63bb0d |
case r == '\\':
|
|
Packit |
63bb0d |
lx.push(lexString)
|
|
Packit |
63bb0d |
return lexStringEscape
|
|
Packit |
63bb0d |
case r == stringEnd:
|
|
Packit |
63bb0d |
lx.backup()
|
|
Packit |
63bb0d |
lx.emit(itemString)
|
|
Packit |
63bb0d |
lx.next()
|
|
Packit |
63bb0d |
lx.ignore()
|
|
Packit |
63bb0d |
return lx.pop()
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
return lexString
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// lexMultilineString consumes the inner contents of a string. It assumes that
|
|
Packit |
63bb0d |
// the beginning '"""' has already been consumed and ignored.
|
|
Packit |
63bb0d |
func lexMultilineString(lx *lexer) stateFn {
|
|
Packit |
63bb0d |
switch lx.next() {
|
|
Packit |
63bb0d |
case eof:
|
|
Packit |
63bb0d |
return lx.errorf("unexpected EOF")
|
|
Packit |
63bb0d |
case '\\':
|
|
Packit |
63bb0d |
return lexMultilineStringEscape
|
|
Packit |
63bb0d |
case stringEnd:
|
|
Packit |
63bb0d |
if lx.accept(stringEnd) {
|
|
Packit |
63bb0d |
if lx.accept(stringEnd) {
|
|
Packit |
63bb0d |
lx.backup()
|
|
Packit |
63bb0d |
lx.backup()
|
|
Packit |
63bb0d |
lx.backup()
|
|
Packit |
63bb0d |
lx.emit(itemMultilineString)
|
|
Packit |
63bb0d |
lx.next()
|
|
Packit |
63bb0d |
lx.next()
|
|
Packit |
63bb0d |
lx.next()
|
|
Packit |
63bb0d |
lx.ignore()
|
|
Packit |
63bb0d |
return lx.pop()
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
lx.backup()
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
return lexMultilineString
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// lexRawString consumes a raw string. Nothing can be escaped in such a string.
|
|
Packit |
63bb0d |
// It assumes that the beginning "'" has already been consumed and ignored.
|
|
Packit |
63bb0d |
func lexRawString(lx *lexer) stateFn {
|
|
Packit |
63bb0d |
r := lx.next()
|
|
Packit |
63bb0d |
switch {
|
|
Packit |
63bb0d |
case r == eof:
|
|
Packit |
63bb0d |
return lx.errorf("unexpected EOF")
|
|
Packit |
63bb0d |
case isNL(r):
|
|
Packit |
63bb0d |
return lx.errorf("strings cannot contain newlines")
|
|
Packit |
63bb0d |
case r == rawStringEnd:
|
|
Packit |
63bb0d |
lx.backup()
|
|
Packit |
63bb0d |
lx.emit(itemRawString)
|
|
Packit |
63bb0d |
lx.next()
|
|
Packit |
63bb0d |
lx.ignore()
|
|
Packit |
63bb0d |
return lx.pop()
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
return lexRawString
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// lexMultilineRawString consumes a raw string. Nothing can be escaped in such
|
|
Packit |
63bb0d |
// a string. It assumes that the beginning "'''" has already been consumed and
|
|
Packit |
63bb0d |
// ignored.
|
|
Packit |
63bb0d |
func lexMultilineRawString(lx *lexer) stateFn {
|
|
Packit |
63bb0d |
switch lx.next() {
|
|
Packit |
63bb0d |
case eof:
|
|
Packit |
63bb0d |
return lx.errorf("unexpected EOF")
|
|
Packit |
63bb0d |
case rawStringEnd:
|
|
Packit |
63bb0d |
if lx.accept(rawStringEnd) {
|
|
Packit |
63bb0d |
if lx.accept(rawStringEnd) {
|
|
Packit |
63bb0d |
lx.backup()
|
|
Packit |
63bb0d |
lx.backup()
|
|
Packit |
63bb0d |
lx.backup()
|
|
Packit |
63bb0d |
lx.emit(itemRawMultilineString)
|
|
Packit |
63bb0d |
lx.next()
|
|
Packit |
63bb0d |
lx.next()
|
|
Packit |
63bb0d |
lx.next()
|
|
Packit |
63bb0d |
lx.ignore()
|
|
Packit |
63bb0d |
return lx.pop()
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
lx.backup()
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
return lexMultilineRawString
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// lexMultilineStringEscape consumes an escaped character. It assumes that the
|
|
Packit |
63bb0d |
// preceding '\\' has already been consumed.
|
|
Packit |
63bb0d |
func lexMultilineStringEscape(lx *lexer) stateFn {
|
|
Packit |
63bb0d |
// Handle the special case first:
|
|
Packit |
63bb0d |
if isNL(lx.next()) {
|
|
Packit |
63bb0d |
return lexMultilineString
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
lx.backup()
|
|
Packit |
63bb0d |
lx.push(lexMultilineString)
|
|
Packit |
63bb0d |
return lexStringEscape(lx)
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
func lexStringEscape(lx *lexer) stateFn {
|
|
Packit |
63bb0d |
r := lx.next()
|
|
Packit |
63bb0d |
switch r {
|
|
Packit |
63bb0d |
case 'b':
|
|
Packit |
63bb0d |
fallthrough
|
|
Packit |
63bb0d |
case 't':
|
|
Packit |
63bb0d |
fallthrough
|
|
Packit |
63bb0d |
case 'n':
|
|
Packit |
63bb0d |
fallthrough
|
|
Packit |
63bb0d |
case 'f':
|
|
Packit |
63bb0d |
fallthrough
|
|
Packit |
63bb0d |
case 'r':
|
|
Packit |
63bb0d |
fallthrough
|
|
Packit |
63bb0d |
case '"':
|
|
Packit |
63bb0d |
fallthrough
|
|
Packit |
63bb0d |
case '\\':
|
|
Packit |
63bb0d |
return lx.pop()
|
|
Packit |
63bb0d |
case 'u':
|
|
Packit |
63bb0d |
return lexShortUnicodeEscape
|
|
Packit |
63bb0d |
case 'U':
|
|
Packit |
63bb0d |
return lexLongUnicodeEscape
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
return lx.errorf("invalid escape character %q; only the following "+
|
|
Packit |
63bb0d |
"escape characters are allowed: "+
|
|
Packit |
63bb0d |
`\b, \t, \n, \f, \r, \", \\, \uXXXX, and \UXXXXXXXX`, r)
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
func lexShortUnicodeEscape(lx *lexer) stateFn {
|
|
Packit |
63bb0d |
var r rune
|
|
Packit |
63bb0d |
for i := 0; i < 4; i++ {
|
|
Packit |
63bb0d |
r = lx.next()
|
|
Packit |
63bb0d |
if !isHexadecimal(r) {
|
|
Packit |
63bb0d |
return lx.errorf(`expected four hexadecimal digits after '\u', `+
|
|
Packit |
63bb0d |
"but got %q instead", lx.current())
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
return lx.pop()
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
func lexLongUnicodeEscape(lx *lexer) stateFn {
|
|
Packit |
63bb0d |
var r rune
|
|
Packit |
63bb0d |
for i := 0; i < 8; i++ {
|
|
Packit |
63bb0d |
r = lx.next()
|
|
Packit |
63bb0d |
if !isHexadecimal(r) {
|
|
Packit |
63bb0d |
return lx.errorf(`expected eight hexadecimal digits after '\U', `+
|
|
Packit |
63bb0d |
"but got %q instead", lx.current())
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
return lx.pop()
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// lexNumberOrDateStart consumes either an integer, a float, or datetime.
|
|
Packit |
63bb0d |
func lexNumberOrDateStart(lx *lexer) stateFn {
|
|
Packit |
63bb0d |
r := lx.next()
|
|
Packit |
63bb0d |
if isDigit(r) {
|
|
Packit |
63bb0d |
return lexNumberOrDate
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
switch r {
|
|
Packit |
63bb0d |
case '_':
|
|
Packit |
63bb0d |
return lexNumber
|
|
Packit |
63bb0d |
case 'e', 'E':
|
|
Packit |
63bb0d |
return lexFloat
|
|
Packit |
63bb0d |
case '.':
|
|
Packit |
63bb0d |
return lx.errorf("floats must start with a digit, not '.'")
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
return lx.errorf("expected a digit but got %q", r)
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// lexNumberOrDate consumes either an integer, float or datetime.
|
|
Packit |
63bb0d |
func lexNumberOrDate(lx *lexer) stateFn {
|
|
Packit |
63bb0d |
r := lx.next()
|
|
Packit |
63bb0d |
if isDigit(r) {
|
|
Packit |
63bb0d |
return lexNumberOrDate
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
switch r {
|
|
Packit |
63bb0d |
case '-':
|
|
Packit |
63bb0d |
return lexDatetime
|
|
Packit |
63bb0d |
case '_':
|
|
Packit |
63bb0d |
return lexNumber
|
|
Packit |
63bb0d |
case '.', 'e', 'E':
|
|
Packit |
63bb0d |
return lexFloat
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
lx.backup()
|
|
Packit |
63bb0d |
lx.emit(itemInteger)
|
|
Packit |
63bb0d |
return lx.pop()
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// lexDatetime consumes a Datetime, to a first approximation.
|
|
Packit |
63bb0d |
// The parser validates that it matches one of the accepted formats.
|
|
Packit |
63bb0d |
func lexDatetime(lx *lexer) stateFn {
|
|
Packit |
63bb0d |
r := lx.next()
|
|
Packit |
63bb0d |
if isDigit(r) {
|
|
Packit |
63bb0d |
return lexDatetime
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
switch r {
|
|
Packit |
63bb0d |
case '-', 'T', ':', '.', 'Z', '+':
|
|
Packit |
63bb0d |
return lexDatetime
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
lx.backup()
|
|
Packit |
63bb0d |
lx.emit(itemDatetime)
|
|
Packit |
63bb0d |
return lx.pop()
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// lexNumberStart consumes either an integer or a float. It assumes that a sign
|
|
Packit |
63bb0d |
// has already been read, but that *no* digits have been consumed.
|
|
Packit |
63bb0d |
// lexNumberStart will move to the appropriate integer or float states.
|
|
Packit |
63bb0d |
func lexNumberStart(lx *lexer) stateFn {
|
|
Packit |
63bb0d |
// We MUST see a digit. Even floats have to start with a digit.
|
|
Packit |
63bb0d |
r := lx.next()
|
|
Packit |
63bb0d |
if !isDigit(r) {
|
|
Packit |
63bb0d |
if r == '.' {
|
|
Packit |
63bb0d |
return lx.errorf("floats must start with a digit, not '.'")
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
return lx.errorf("expected a digit but got %q", r)
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
return lexNumber
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// lexNumber consumes an integer or a float after seeing the first digit.
|
|
Packit |
63bb0d |
func lexNumber(lx *lexer) stateFn {
|
|
Packit |
63bb0d |
r := lx.next()
|
|
Packit |
63bb0d |
if isDigit(r) {
|
|
Packit |
63bb0d |
return lexNumber
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
switch r {
|
|
Packit |
63bb0d |
case '_':
|
|
Packit |
63bb0d |
return lexNumber
|
|
Packit |
63bb0d |
case '.', 'e', 'E':
|
|
Packit |
63bb0d |
return lexFloat
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
lx.backup()
|
|
Packit |
63bb0d |
lx.emit(itemInteger)
|
|
Packit |
63bb0d |
return lx.pop()
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// lexFloat consumes the elements of a float. It allows any sequence of
|
|
Packit |
63bb0d |
// float-like characters, so floats emitted by the lexer are only a first
|
|
Packit |
63bb0d |
// approximation and must be validated by the parser.
|
|
Packit |
63bb0d |
func lexFloat(lx *lexer) stateFn {
|
|
Packit |
63bb0d |
r := lx.next()
|
|
Packit |
63bb0d |
if isDigit(r) {
|
|
Packit |
63bb0d |
return lexFloat
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
switch r {
|
|
Packit |
63bb0d |
case '_', '.', '-', '+', 'e', 'E':
|
|
Packit |
63bb0d |
return lexFloat
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
lx.backup()
|
|
Packit |
63bb0d |
lx.emit(itemFloat)
|
|
Packit |
63bb0d |
return lx.pop()
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// lexBool consumes a bool string: 'true' or 'false.
|
|
Packit |
63bb0d |
func lexBool(lx *lexer) stateFn {
|
|
Packit |
63bb0d |
var rs []rune
|
|
Packit |
63bb0d |
for {
|
|
Packit |
63bb0d |
r := lx.next()
|
|
Packit |
63bb0d |
if !unicode.IsLetter(r) {
|
|
Packit |
63bb0d |
lx.backup()
|
|
Packit |
63bb0d |
break
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
rs = append(rs, r)
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
s := string(rs)
|
|
Packit |
63bb0d |
switch s {
|
|
Packit |
63bb0d |
case "true", "false":
|
|
Packit |
63bb0d |
lx.emit(itemBool)
|
|
Packit |
63bb0d |
return lx.pop()
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
return lx.errorf("expected value but found %q instead", s)
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// lexCommentStart begins the lexing of a comment. It will emit
|
|
Packit |
63bb0d |
// itemCommentStart and consume no characters, passing control to lexComment.
|
|
Packit |
63bb0d |
func lexCommentStart(lx *lexer) stateFn {
|
|
Packit |
63bb0d |
lx.ignore()
|
|
Packit |
63bb0d |
lx.emit(itemCommentStart)
|
|
Packit |
63bb0d |
return lexComment
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// lexComment lexes an entire comment. It assumes that '#' has been consumed.
|
|
Packit |
63bb0d |
// It will consume *up to* the first newline character, and pass control
|
|
Packit |
63bb0d |
// back to the last state on the stack.
|
|
Packit |
63bb0d |
func lexComment(lx *lexer) stateFn {
|
|
Packit |
63bb0d |
r := lx.peek()
|
|
Packit |
63bb0d |
if isNL(r) || r == eof {
|
|
Packit |
63bb0d |
lx.emit(itemText)
|
|
Packit |
63bb0d |
return lx.pop()
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
lx.next()
|
|
Packit |
63bb0d |
return lexComment
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// lexSkip ignores all slurped input and moves on to the next state.
|
|
Packit |
63bb0d |
func lexSkip(lx *lexer, nextState stateFn) stateFn {
|
|
Packit |
63bb0d |
return func(lx *lexer) stateFn {
|
|
Packit |
63bb0d |
lx.ignore()
|
|
Packit |
63bb0d |
return nextState
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// isWhitespace returns true if `r` is a whitespace character according
|
|
Packit |
63bb0d |
// to the spec.
|
|
Packit |
63bb0d |
func isWhitespace(r rune) bool {
|
|
Packit |
63bb0d |
return r == '\t' || r == ' '
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
func isNL(r rune) bool {
|
|
Packit |
63bb0d |
return r == '\n' || r == '\r'
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
func isDigit(r rune) bool {
|
|
Packit |
63bb0d |
return r >= '0' && r <= '9'
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
func isHexadecimal(r rune) bool {
|
|
Packit |
63bb0d |
return (r >= '0' && r <= '9') ||
|
|
Packit |
63bb0d |
(r >= 'a' && r <= 'f') ||
|
|
Packit |
63bb0d |
(r >= 'A' && r <= 'F')
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
func isBareKeyChar(r rune) bool {
|
|
Packit |
63bb0d |
return (r >= 'A' && r <= 'Z') ||
|
|
Packit |
63bb0d |
(r >= 'a' && r <= 'z') ||
|
|
Packit |
63bb0d |
(r >= '0' && r <= '9') ||
|
|
Packit |
63bb0d |
r == '_' ||
|
|
Packit |
63bb0d |
r == '-'
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
func (itype itemType) String() string {
|
|
Packit |
63bb0d |
switch itype {
|
|
Packit |
63bb0d |
case itemError:
|
|
Packit |
63bb0d |
return "Error"
|
|
Packit |
63bb0d |
case itemNIL:
|
|
Packit |
63bb0d |
return "NIL"
|
|
Packit |
63bb0d |
case itemEOF:
|
|
Packit |
63bb0d |
return "EOF"
|
|
Packit |
63bb0d |
case itemText:
|
|
Packit |
63bb0d |
return "Text"
|
|
Packit |
63bb0d |
case itemString, itemRawString, itemMultilineString, itemRawMultilineString:
|
|
Packit |
63bb0d |
return "String"
|
|
Packit |
63bb0d |
case itemBool:
|
|
Packit |
63bb0d |
return "Bool"
|
|
Packit |
63bb0d |
case itemInteger:
|
|
Packit |
63bb0d |
return "Integer"
|
|
Packit |
63bb0d |
case itemFloat:
|
|
Packit |
63bb0d |
return "Float"
|
|
Packit |
63bb0d |
case itemDatetime:
|
|
Packit |
63bb0d |
return "DateTime"
|
|
Packit |
63bb0d |
case itemTableStart:
|
|
Packit |
63bb0d |
return "TableStart"
|
|
Packit |
63bb0d |
case itemTableEnd:
|
|
Packit |
63bb0d |
return "TableEnd"
|
|
Packit |
63bb0d |
case itemKeyStart:
|
|
Packit |
63bb0d |
return "KeyStart"
|
|
Packit |
63bb0d |
case itemArray:
|
|
Packit |
63bb0d |
return "Array"
|
|
Packit |
63bb0d |
case itemArrayEnd:
|
|
Packit |
63bb0d |
return "ArrayEnd"
|
|
Packit |
63bb0d |
case itemCommentStart:
|
|
Packit |
63bb0d |
return "CommentStart"
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
panic(fmt.Sprintf("BUG: Unknown type '%d'.", int(itype)))
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
func (item item) String() string {
|
|
Packit |
63bb0d |
return fmt.Sprintf("(%s, %s)", item.typ.String(), item.val)
|
|
Packit |
63bb0d |
}
|