Implement parser

This commit is contained in:
Thomas Pelletier
2013-02-24 21:09:35 +01:00
parent 6dd3db3d59
commit 89919c7041
5 changed files with 215 additions and 22 deletions
+2
View File
@@ -1,3 +1,5 @@
# go-toml
Go library for the [TOML](https://github.com/mojombo/toml) format.
This library supports TOML version [e3656ad493400895f4460f1244a25f8f8e31a32a](https://github.com/mojombo/toml/tree/e3656ad493400895f4460f1244a25f8f8e31a32a)
+4
View File
@@ -39,6 +39,7 @@ const (
tokenDate
tokenKeyGroup
tokenComma
tokenEOL
)
type token struct {
@@ -196,6 +197,9 @@ func lexRvalue(l *lexer) stateFn {
case ',':
return lexComma
case '\n':
l.ignore()
l.pos += 1
l.emit(tokenEOF)
return lexVoid
}
+155 -17
View File
@@ -3,27 +3,165 @@
package toml
import (
"strings"
"fmt"
"strconv"
"time"
)
// createSubTree takes a tree and a key andcreate the necessary intermediate
// subtrees to create a subtree at that point. In-place.
//
// e.g. passing a.b.c will create (assuming tree is empty) tree[a], tree[a][b]
// and tree[a][b][c]
func createSubTree(tree *TomlTree, key string) {
subtree := tree
for _, intermediate_key := range strings.Split(key, ".") {
_, exists := (*subtree)[intermediate_key]
if !exists {
(*subtree)[intermediate_key] = make(TomlTree)
}
subtree = (*subtree)[intermediate_key].(*TomlTree)
type parser struct {
flow chan token
tree *TomlTree
tokensBuffer []token
currentGroup string
}
type parserStateFn func(*parser) parserStateFn
func (p *parser) run() {
for state := parseStart; state != nil; {
state = state(p)
}
}
func (p *parser) peek() *token {
if len(p.tokensBuffer) != 0 {
return &(p.tokensBuffer[0])
}
func parse(chan token) *TomlTree {
result := make(TomlTree)
return &result
tok, ok := <- p.flow
if !ok {
return nil
}
p.tokensBuffer = append(p.tokensBuffer, tok)
return &tok
}
func (p *parser) assume(typ tokenType) {
tok := p.getToken()
if tok == nil {
panic(fmt.Sprintf("was expecting token %s, but token stream is empty", typ))
}
if tok.typ != typ {
panic(fmt.Sprintf("was expecting token %s, but got %s", typ, tok.typ))
}
}
func (p *parser) getToken() *token {
if len(p.tokensBuffer) != 0 {
tok := p.tokensBuffer[0]
p.tokensBuffer = p.tokensBuffer[1:]
return &tok
}
tok, ok := <- p.flow
if !ok {
return nil
}
return &tok
}
func parseStart(p *parser) parserStateFn {
tok := p.peek()
// end of stream, parsing is finished
if tok == nil {
return nil
}
switch tok.typ {
case tokenLeftBracket:
return parseGroup
case tokenKey:
return parseAssign
default:
panic("unexpected token")
}
return nil
}
func parseGroup(p *parser) parserStateFn {
p.getToken() // discard the [
key := p.getToken()
if key.typ != tokenKey {
panic(fmt.Sprintf("unexpected token %s, was expecting a key", key))
}
p.tree.createSubTree(key.val)
p.assume(tokenRightBracket)
p.currentGroup = key.val
return parseStart(p)
}
func parseAssign(p *parser) parserStateFn {
key := p.getToken()
p.assume(tokenEqual)
value := parseRvalue(p)
p.tree.Set(key.val, value)
return parseStart(p)
}
func parseRvalue(p *parser) interface{} {
tok := p.getToken()
if tok == nil {
panic("expecting a value")
}
switch tok.typ {
case tokenString:
return tok.val
case tokenTrue:
return true
case tokenFalse:
return false
case tokenInteger:
val, err := strconv.ParseInt(tok.val, 10, 64)
if err != nil { panic(err) }
return val
case tokenFloat:
val, err := strconv.ParseFloat(tok.val, 64)
if err != nil { panic(err) }
return val
case tokenDate:
val, err := time.Parse(time.RFC3339, tok.val)
if err != nil { panic(err) }
return val
case tokenLeftBracket:
return parseArray(p)
}
panic("never reached")
return nil
}
func parseArray(p *parser) []interface{} {
array := make([]interface{}, 0)
for {
follow := p.peek()
if follow == nil { panic("unterminated array") }
if follow.typ == tokenRightBracket {
p.getToken()
return array
}
val := parseRvalue(p)
array = append(array, val)
follow = p.peek()
if follow == nil { panic("unterminated array") }
if follow.typ != tokenRightBracket && follow.typ != tokenComma {
panic("missing comma")
}
}
return array
}
func parse(flow chan token) *TomlTree {
result := make(TomlTree)
parser := &parser {
flow: flow,
tree: &result,
tokensBuffer: make([]token, 0),
currentGroup: "",
}
parser.run()
return parser.tree
}
+34 -1
View File
@@ -3,11 +3,44 @@ package toml
import "testing"
func assertTree(t *testing.T, tree *TomlTree, ref map[string]interface{}) {
for k, v := range ref {
if tree.Get(k) != v {
t.Log("was expecting", v, "at", k, "but got", tree.Get(k))
t.Fail()
}
}
}
func testCreateSubTree(t *testing.T) {
tree := make(TomlTree)
createSubTree(&tree, "a.b.c")
tree.createSubTree("a.b.c")
tree.Set("a.b.c", 42)
if tree.Get("a.b.c") != 42 {
t.Fail()
}
}
func testSimpleKV(t *testing.T) {
tree := Load("a = 42")
assertTree(t, tree, map[string]interface{}{
"a": 42,
})
tree = Load("a = 42\nb = 21")
assertTree(t, tree, map[string]interface{}{
"a": 42,
"b": 21,
})
}
func testSimpleIntegers(t *testing.T) {
tree := Load("a = +42\nb = -21\nc = +4.2\nd = -2.1")
assertTree(t, tree, map[string]interface{}{
"a": 42,
"b": -21,
"c": 4.2,
"d": -4.2,
})
}
+20 -4
View File
@@ -39,8 +39,24 @@ func (t *TomlTree) Set(key string, value interface{}) {
(*subtree)[keys[len(key) - 1]] = value
}
func Load() TomlTree {
result := make(TomlTree)
return result
// createSubTree takes a tree and a key andcreate the necessary intermediate
// subtrees to create a subtree at that point. In-place.
//
// e.g. passing a.b.c will create (assuming tree is empty) tree[a], tree[a][b]
// and tree[a][b][c]
func (t *TomlTree) createSubTree(key string) {
subtree := t
for _, intermediate_key := range strings.Split(key, ".") {
_, exists := (*subtree)[intermediate_key]
if !exists {
(*subtree)[intermediate_key] = make(TomlTree)
}
subtree = (*subtree)[intermediate_key].(*TomlTree)
}
}
func Load(content string) *TomlTree {
_, flow := lex(content)
return parse(flow)
}