Implement parser
This commit is contained in:
+155
-17
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user