Add lexer
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
# go-toml
|
||||
|
||||
Go library for the [TOML](https://github.com/mojombo/toml) format.
|
||||
@@ -0,0 +1,29 @@
|
||||
# This is a TOML document. Boom.
|
||||
|
||||
title = "TOML Example"
|
||||
|
||||
[owner]
|
||||
name = "Tom Preston-Werner"
|
||||
organization = "GitHub"
|
||||
bio = "GitHub Cofounder & CEO\nLikes tater tots and beer."
|
||||
dob = 1979-05-27T07:32:00Z # First class dates? Why not?
|
||||
|
||||
[database]
|
||||
server = "192.168.1.1"
|
||||
ports = [ 8001, 8001, 8002 ]
|
||||
connection_max = 5000
|
||||
enabled = true
|
||||
|
||||
[servers]
|
||||
|
||||
# You can indent as you please. Tabs or spaces. TOML don't care.
|
||||
[servers.alpha]
|
||||
ip = "10.0.0.1"
|
||||
dc = "eqdc10"
|
||||
|
||||
[servers.beta]
|
||||
ip = "10.0.0.2"
|
||||
dc = "eqdc10"
|
||||
|
||||
[clients]
|
||||
data = [ ["gamma", "delta"], [1, 2] ] # just an update to make sure parsers support it
|
||||
@@ -0,0 +1,389 @@
|
||||
// TOML lexer.// Written using the principles developped by Rob Pike in
|
||||
// http://www.youtube.com/watch?v=HxaD_trXwRE
|
||||
|
||||
package toml
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
|
||||
var dateRegexp *regexp.Regexp
|
||||
|
||||
// Define tokens
|
||||
type tokenType int
|
||||
|
||||
const (
|
||||
EOF = - (iota + 1)
|
||||
)
|
||||
|
||||
const (
|
||||
tokenError tokenType = iota
|
||||
tokenEOF
|
||||
tokenComment
|
||||
tokenKey
|
||||
tokenEqual
|
||||
tokenString
|
||||
tokenInteger
|
||||
tokenTrue
|
||||
tokenFalse
|
||||
tokenFloat
|
||||
tokenLeftBracket
|
||||
tokenRightBracket
|
||||
tokenDate
|
||||
tokenKeyGroup
|
||||
tokenComma
|
||||
)
|
||||
|
||||
type token struct {
|
||||
typ tokenType
|
||||
val string
|
||||
}
|
||||
|
||||
|
||||
func (i token) String() string {
|
||||
switch i.typ {
|
||||
case tokenEOF:
|
||||
return "EOF"
|
||||
case tokenError:
|
||||
return i.val
|
||||
}
|
||||
|
||||
if len(i.val) > 10 {
|
||||
return fmt.Sprintf("%.10q...", i.val);
|
||||
}
|
||||
return fmt.Sprintf("%q", i.val)
|
||||
}
|
||||
|
||||
|
||||
func isSpace(r rune) bool {
|
||||
return r == ' ' || r == '\t'
|
||||
}
|
||||
|
||||
func isAlpha(r rune) bool {
|
||||
return r >= 'a' && r <= 'z'
|
||||
}
|
||||
|
||||
func isDigit(r rune) bool {
|
||||
return r >= '0' && r <= '9'
|
||||
}
|
||||
|
||||
|
||||
// Define lexer
|
||||
type lexer struct {
|
||||
input string
|
||||
start int
|
||||
pos int
|
||||
width int
|
||||
tokens chan token
|
||||
}
|
||||
|
||||
|
||||
func (l *lexer) run() {
|
||||
for state := lexVoid; state != nil; {
|
||||
fmt.Println("going in state", runtime.FuncForPC(reflect.ValueOf(state).Pointer()).Name())
|
||||
state = state(l)
|
||||
}
|
||||
fmt.Println("closing...")
|
||||
close (l.tokens)
|
||||
}
|
||||
|
||||
func (l *lexer) emit(t tokenType) {
|
||||
l.tokens <- token{t, l.input[l.start:l.pos]}
|
||||
l.start = l.pos
|
||||
}
|
||||
|
||||
func (l *lexer) emitWithValue(t tokenType, value string) {
|
||||
l.tokens <- token{t, value}
|
||||
l.start = l.pos
|
||||
}
|
||||
|
||||
|
||||
func (l *lexer) next() (rune) {
|
||||
if l.pos >= len(l.input) {
|
||||
l.width = 0
|
||||
return EOF
|
||||
}
|
||||
var r rune
|
||||
r, l.width = utf8.DecodeRuneInString(l.input[l.pos:])
|
||||
l.pos += l.width
|
||||
return r
|
||||
}
|
||||
|
||||
func (l *lexer) ignore() {
|
||||
l.start = l.pos
|
||||
}
|
||||
|
||||
func (l *lexer) backup() {
|
||||
l.pos -= l.width
|
||||
}
|
||||
|
||||
func (l *lexer) errorf(format string, args ...interface{}) stateFn {
|
||||
l.tokens <- token{
|
||||
tokenError,
|
||||
fmt.Sprintf(format, args...),
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *lexer) peek() rune {
|
||||
r := l.next()
|
||||
l.backup()
|
||||
return r
|
||||
}
|
||||
|
||||
func (l *lexer) accept(valid string) bool {
|
||||
if strings.IndexRune(valid, l.next()) >= 0 {
|
||||
return true
|
||||
}
|
||||
l.backup()
|
||||
return false
|
||||
}
|
||||
|
||||
func (l *lexer) follow(next string) bool {
|
||||
return strings.HasPrefix(l.input[l.pos:], next)
|
||||
}
|
||||
|
||||
|
||||
// Define state functions
|
||||
type stateFn func(*lexer) stateFn
|
||||
|
||||
func lexVoid(l *lexer) stateFn {
|
||||
for {
|
||||
next := l.peek()
|
||||
switch next {
|
||||
case '[':
|
||||
return lexKeyGroup
|
||||
case '#':
|
||||
return lexComment
|
||||
case '=':
|
||||
return lexEqual
|
||||
}
|
||||
|
||||
if isAlpha(next) {
|
||||
return lexKey
|
||||
}
|
||||
|
||||
if isSpace(next) {
|
||||
l.ignore()
|
||||
}
|
||||
|
||||
if l.next() == EOF { break }
|
||||
}
|
||||
|
||||
l.emit(tokenEOF)
|
||||
return nil
|
||||
}
|
||||
|
||||
func lexRvalue(l *lexer) stateFn {
|
||||
for {
|
||||
next := l.peek()
|
||||
switch next {
|
||||
case '[':
|
||||
return lexLeftBracket
|
||||
case ']':
|
||||
return lexRightBracket
|
||||
case '#':
|
||||
return lexComment
|
||||
case '"':
|
||||
return lexString
|
||||
case ',':
|
||||
return lexComma
|
||||
case '\n':
|
||||
return lexVoid
|
||||
}
|
||||
|
||||
if l.follow("true") {
|
||||
return lexTrue
|
||||
}
|
||||
|
||||
if l.follow("false") {
|
||||
return lexFalse
|
||||
}
|
||||
|
||||
if isAlpha(next) {
|
||||
return lexKey
|
||||
}
|
||||
|
||||
if dateRegexp.FindString(l.input[l.pos:]) != "" {
|
||||
return lexDate
|
||||
}
|
||||
|
||||
if next == '+' || next == '-' || isDigit(next) {
|
||||
return lexNumber
|
||||
}
|
||||
|
||||
if isSpace(next) {
|
||||
l.ignore()
|
||||
}
|
||||
|
||||
if l.next() == EOF { break }
|
||||
}
|
||||
|
||||
l.emit(tokenEOF)
|
||||
return nil
|
||||
}
|
||||
|
||||
func lexDate(l *lexer) stateFn {
|
||||
l.ignore()
|
||||
l.pos += 20 // Fixed size of a date in TOML
|
||||
l.emit(tokenDate)
|
||||
return lexRvalue
|
||||
}
|
||||
|
||||
func lexTrue(l *lexer) stateFn {
|
||||
l.ignore()
|
||||
l.pos += 4
|
||||
l.emit(tokenTrue)
|
||||
return lexRvalue
|
||||
}
|
||||
|
||||
func lexFalse(l *lexer) stateFn {
|
||||
l.ignore()
|
||||
l.pos += 5
|
||||
l.emit(tokenFalse)
|
||||
return lexRvalue
|
||||
}
|
||||
|
||||
func lexEqual(l *lexer) stateFn {
|
||||
l.ignore()
|
||||
l.accept("=")
|
||||
l.emit(tokenEqual)
|
||||
return lexRvalue
|
||||
}
|
||||
|
||||
func lexComma(l *lexer) stateFn {
|
||||
l.ignore()
|
||||
l.accept(",")
|
||||
l.emit(tokenComma)
|
||||
return lexRvalue
|
||||
}
|
||||
|
||||
func lexKey(l *lexer) stateFn {
|
||||
for isAlpha(l.next()) {
|
||||
}
|
||||
l.backup()
|
||||
l.emit(tokenKey)
|
||||
return lexVoid
|
||||
}
|
||||
|
||||
func lexComment(l *lexer) stateFn {
|
||||
for {
|
||||
next := l.next()
|
||||
if next == '\n' || next == EOF {
|
||||
break
|
||||
}
|
||||
}
|
||||
l.ignore()
|
||||
return lexVoid
|
||||
}
|
||||
|
||||
func lexLeftBracket(l *lexer) stateFn {
|
||||
l.ignore()
|
||||
l.pos += 1
|
||||
l.emit(tokenLeftBracket)
|
||||
return lexRvalue
|
||||
}
|
||||
|
||||
func lexString(l *lexer) stateFn {
|
||||
l.pos += 1
|
||||
l.ignore()
|
||||
growing_string := ""
|
||||
|
||||
for {
|
||||
fmt.Println("peek:", strconv.QuoteRune(l.peek()))
|
||||
if l.peek() == '"' {
|
||||
l.emitWithValue(tokenString, growing_string)
|
||||
l.pos += 1
|
||||
l.ignore()
|
||||
return lexVoid
|
||||
}
|
||||
|
||||
if l.follow("\\\"") {
|
||||
fmt.Println("follow")
|
||||
l.pos += 1
|
||||
growing_string += "\""
|
||||
} else {
|
||||
growing_string += string(l.peek())
|
||||
}
|
||||
|
||||
if l.next() == EOF { break }
|
||||
}
|
||||
|
||||
return l.errorf("unclosed string")
|
||||
}
|
||||
|
||||
func lexKeyGroup(l *lexer) stateFn {
|
||||
l.ignore()
|
||||
l.pos += 1
|
||||
l.emit(tokenLeftBracket)
|
||||
return lexInsideKeyGroup
|
||||
}
|
||||
|
||||
func lexInsideKeyGroup(l *lexer) stateFn {
|
||||
for {
|
||||
if l.peek() == ']' {
|
||||
if l.pos > l.start {
|
||||
l.emit(tokenKeyGroup)
|
||||
}
|
||||
l.ignore()
|
||||
l.pos += 1
|
||||
l.emit(tokenRightBracket)
|
||||
return lexVoid
|
||||
}
|
||||
|
||||
if l.next() == EOF { break }
|
||||
}
|
||||
return l.errorf("unclosed key group")
|
||||
}
|
||||
|
||||
func lexRightBracket(l *lexer) stateFn {
|
||||
l.ignore()
|
||||
l.pos += 1
|
||||
l.emit(tokenRightBracket)
|
||||
return lexRvalue
|
||||
}
|
||||
|
||||
func lexNumber(l *lexer) stateFn {
|
||||
l.ignore()
|
||||
if !l.accept("+") { l.accept("-") }
|
||||
point_seen := false
|
||||
digit_seen := false
|
||||
for {
|
||||
next := l.next()
|
||||
if next == '.' { point_seen = true
|
||||
} else if isDigit(next) { digit_seen = true
|
||||
} else { break }
|
||||
}
|
||||
|
||||
if !digit_seen {
|
||||
return l.errorf("no digit in that number")
|
||||
}
|
||||
if point_seen {
|
||||
l.emit(tokenFloat)
|
||||
} else {
|
||||
l.emit(tokenInteger)
|
||||
}
|
||||
return lexRvalue
|
||||
}
|
||||
|
||||
func init() {
|
||||
dateRegexp = regexp.MustCompile("^\\d{1,4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z")
|
||||
}
|
||||
|
||||
|
||||
// Entry point
|
||||
func lex(input string) (*lexer, chan token) {
|
||||
l := &lexer {
|
||||
input: input,
|
||||
tokens: make(chan token),
|
||||
}
|
||||
go l.run()
|
||||
return l, l.tokens
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
package toml
|
||||
|
||||
import "testing"
|
||||
|
||||
func testFlow(t *testing.T, input string, expectedFlow []token) {
|
||||
_, ch := lex(input)
|
||||
for _, expected := range expectedFlow {
|
||||
token := <- ch
|
||||
if token != expected {
|
||||
t.Log("compared", token, "to", expected)
|
||||
t.Log(token.val, "<->", expected.val)
|
||||
t.Log(token.typ, "<->", expected.typ)
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
tok, ok := <- ch
|
||||
if ok {
|
||||
t.Log("channel is not closed!")
|
||||
t.Log(len(ch) + 1, "tokens remaining:")
|
||||
|
||||
t.Log("token ->", tok)
|
||||
for token := range ch {
|
||||
t.Log("token ->", token)
|
||||
}
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidKeyGroup(t *testing.T) {
|
||||
testFlow(t, "[hello world]", []token{
|
||||
token{tokenLeftBracket, "["},
|
||||
token{tokenKeyGroup, "hello world"},
|
||||
token{tokenRightBracket, "]"},
|
||||
token{tokenEOF, ""},
|
||||
})
|
||||
}
|
||||
|
||||
func TestUnclosedKeyGroup(t *testing.T) {
|
||||
testFlow(t, "[hello world", []token{
|
||||
token{tokenLeftBracket, "["},
|
||||
token{tokenError, "unclosed key group"},
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
func TestComment(t *testing.T) {
|
||||
testFlow(t, "# blahblah", []token{
|
||||
token{tokenEOF, ""},
|
||||
})
|
||||
}
|
||||
|
||||
func TestKeyGroupComment(t *testing.T) {
|
||||
testFlow(t, "[hello world] # blahblah", []token{
|
||||
token{tokenLeftBracket, "["},
|
||||
token{tokenKeyGroup, "hello world"},
|
||||
token{tokenRightBracket, "]"},
|
||||
token{tokenEOF, ""},
|
||||
})
|
||||
}
|
||||
|
||||
func TestMultipleKeyGroupsComment(t *testing.T) {
|
||||
testFlow(t, "[hello world] # blahblah\n[test]", []token{
|
||||
token{tokenLeftBracket, "["},
|
||||
token{tokenKeyGroup, "hello world"},
|
||||
token{tokenRightBracket, "]"},
|
||||
token{tokenLeftBracket, "["},
|
||||
token{tokenKeyGroup, "test"},
|
||||
token{tokenRightBracket, "]"},
|
||||
token{tokenEOF, ""},
|
||||
})
|
||||
}
|
||||
|
||||
func TestBasicKey(t *testing.T) {
|
||||
testFlow(t, "hello", []token{
|
||||
token{tokenKey, "hello"},
|
||||
token{tokenEOF, ""},
|
||||
})
|
||||
}
|
||||
|
||||
func TestBasicKeyAndEqual(t *testing.T) {
|
||||
testFlow(t, "hello =", []token{
|
||||
token{tokenKey, "hello"},
|
||||
token{tokenEqual, "="},
|
||||
token{tokenEOF, ""},
|
||||
})
|
||||
}
|
||||
|
||||
func TestKeyEqualStringEscape(t *testing.T) {
|
||||
testFlow(t, "foo = \"hello\\\"\"", []token{
|
||||
token{tokenKey, "foo"},
|
||||
token{tokenEqual, "="},
|
||||
token{tokenString, "hello\""},
|
||||
token{tokenEOF, ""},
|
||||
})
|
||||
}
|
||||
|
||||
func TestKeyEqualStringUnfinished(t *testing.T) {
|
||||
testFlow(t, "foo = \"bar", []token{
|
||||
token{tokenKey, "foo"},
|
||||
token{tokenEqual, "="},
|
||||
token{tokenError, "unclosed string"},
|
||||
})
|
||||
}
|
||||
|
||||
func TestKeyEqualString(t *testing.T) {
|
||||
testFlow(t, "foo = \"bar\"", []token{
|
||||
token{tokenKey, "foo"},
|
||||
token{tokenEqual, "="},
|
||||
token{tokenString, "bar"},
|
||||
token{tokenEOF, ""},
|
||||
})
|
||||
}
|
||||
|
||||
func TestKeyEqualTrue(t *testing.T) {
|
||||
testFlow(t, "foo = true", []token{
|
||||
token{tokenKey, "foo"},
|
||||
token{tokenEqual, "="},
|
||||
token{tokenTrue, "true"},
|
||||
token{tokenEOF, ""},
|
||||
})
|
||||
}
|
||||
|
||||
func TestKeyEqualFalse(t *testing.T) {
|
||||
testFlow(t, "foo = false", []token{
|
||||
token{tokenKey, "foo"},
|
||||
token{tokenEqual, "="},
|
||||
token{tokenFalse, "false"},
|
||||
token{tokenEOF, ""},
|
||||
})
|
||||
}
|
||||
|
||||
func TestKeyEqualArrayBools(t *testing.T) {
|
||||
testFlow(t, "foo = [true, false, true]", []token{
|
||||
token{tokenKey, "foo"},
|
||||
token{tokenEqual, "="},
|
||||
token{tokenLeftBracket, "["},
|
||||
token{tokenTrue, "true"},
|
||||
token{tokenComma, ","},
|
||||
token{tokenFalse, "false"},
|
||||
token{tokenComma, ","},
|
||||
token{tokenTrue, "true"},
|
||||
token{tokenRightBracket, "]"},
|
||||
token{tokenEOF, ""},
|
||||
})
|
||||
}
|
||||
|
||||
func TestKeyEqualArrayBoolsWithComments(t *testing.T) {
|
||||
testFlow(t, "foo = [true, false, true] # YEAH", []token{
|
||||
token{tokenKey, "foo"},
|
||||
token{tokenEqual, "="},
|
||||
token{tokenLeftBracket, "["},
|
||||
token{tokenTrue, "true"},
|
||||
token{tokenComma, ","},
|
||||
token{tokenFalse, "false"},
|
||||
token{tokenComma, ","},
|
||||
token{tokenTrue, "true"},
|
||||
token{tokenRightBracket, "]"},
|
||||
token{tokenEOF, ""},
|
||||
})
|
||||
}
|
||||
|
||||
func TestDateRegexp(t *testing.T) {
|
||||
if dateRegexp.FindString("1979-05-27T07:32:00Z") == "" {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyEqualDate(t *testing.T) {
|
||||
testFlow(t, "foo = 1979-05-27T07:32:00Z", []token{
|
||||
token{tokenKey, "foo"},
|
||||
token{tokenEqual, "="},
|
||||
token{tokenDate, "1979-05-27T07:32:00Z"},
|
||||
token{tokenEOF, ""},
|
||||
})
|
||||
}
|
||||
|
||||
func TestKeyEqualNumber(t *testing.T) {
|
||||
testFlow(t, "foo = 42", []token{
|
||||
token{tokenKey, "foo"},
|
||||
token{tokenEqual, "="},
|
||||
token{tokenInteger, "42"},
|
||||
token{tokenEOF, ""},
|
||||
})
|
||||
|
||||
testFlow(t, "foo = +42", []token{
|
||||
token{tokenKey, "foo"},
|
||||
token{tokenEqual, "="},
|
||||
token{tokenInteger, "+42"},
|
||||
token{tokenEOF, ""},
|
||||
})
|
||||
|
||||
testFlow(t, "foo = -42", []token{
|
||||
token{tokenKey, "foo"},
|
||||
token{tokenEqual, "="},
|
||||
token{tokenInteger, "-42"},
|
||||
token{tokenEOF, ""},
|
||||
})
|
||||
|
||||
testFlow(t, "foo = 4.2", []token{
|
||||
token{tokenKey, "foo"},
|
||||
token{tokenEqual, "="},
|
||||
token{tokenFloat, "4.2"},
|
||||
token{tokenEOF, ""},
|
||||
})
|
||||
|
||||
testFlow(t, "foo = +4.2", []token{
|
||||
token{tokenKey, "foo"},
|
||||
token{tokenEqual, "="},
|
||||
token{tokenFloat, "+4.2"},
|
||||
token{tokenEOF, ""},
|
||||
})
|
||||
|
||||
testFlow(t, "foo = -4.2", []token{
|
||||
token{tokenKey, "foo"},
|
||||
token{tokenEqual, "="},
|
||||
token{tokenFloat, "-4.2"},
|
||||
token{tokenEOF, ""},
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
// TOML Parser.
|
||||
|
||||
package toml
|
||||
@@ -0,0 +1,8 @@
|
||||
// TOML interface.
|
||||
|
||||
package toml
|
||||
|
||||
func Load() map[string]interface{} {
|
||||
result := make(map[string]interface{})
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
package toml
|
||||
Reference in New Issue
Block a user