1
0
Fork 0
kifflom/parser/parser.go

204 lines
3.6 KiB
Go

package parser
import (
"fmt"
"strconv"
"strings"
"github.com/localhots/kifflom/buffer"
"github.com/localhots/kifflom/lexer"
)
type (
// Holds the state of parser
Parser struct {
lex *lexer.Lexer
ctx *context
sels map[string]*context
res chan Match
verbose bool
}
Match struct {
Sel string
Val interface{}
}
)
// Creates a new parser
func New(buf *buffer.Buffer, sels []string) *Parser {
return &Parser{
lex: lexer.New(buf),
ctx: &context{
exps: []expectation{},
},
sels: parseSelectors(sels),
res: make(chan Match),
}
}
func (p *Parser) Debug() {
p.verbose = true
}
// Parse all and return matches
func (p *Parser) Parse() map[string][]interface{} {
p.ParseStream()
out := map[string][]interface{}{}
for {
if m, ok := <-p.res; ok {
out[m.Sel] = append(out[m.Sel], m.Val)
} else {
break
}
}
return out
}
// Starts parsing
func (p *Parser) ParseStream() <-chan Match {
go p.lex.Run()
go func() {
defer func() {
if err := recover(); err != nil {
fmt.Println("\nParse error! Yay!")
fmt.Println(err)
}
close(p.res)
}()
for {
if item := p.next(); item.Token != lexer.EOF {
p.parseValue(item)
} else {
break
}
}
}()
return p.res
}
func (p *Parser) parseValue(item lexer.Item) {
switch item.Token {
case lexer.Null, lexer.Bool, lexer.Number, lexer.String:
p.pushValue(item)
case lexer.BraceOpen:
p.ctx.push(object)
p.parseObject()
p.ctx.pop()
case lexer.BracketOpen:
p.ctx.push(array)
p.parseArray(0)
p.ctx.pop()
default:
unexpected(item)
}
}
// Parses array recursively part by part
// Is called after '[' and ',' tokens
// Expects a value followed by ']' or ',' tokens
func (p *Parser) parseArray(i int64) {
item := p.next()
if item.Token == lexer.BracketClose {
// Neither a bug nor a feature
// This allows an array to have a trailing comma
// [1, 2, 3, ]
return
}
p.ctx.setIndex(i)
p.parseValue(item)
switch item := p.next(); item.Token {
case lexer.BracketClose:
return
case lexer.Comma:
p.parseArray(i + 1)
}
}
func (p *Parser) parseObject() {
item := p.next()
switch item.Token {
case lexer.BraceClose:
// Neither a bug nor a feature
// This allows an object to have a trailing comma
// {"foo": 1, "bar": 2, }
return
case lexer.String:
p.ctx.setKey(item.Val)
default:
unexpected(item)
}
if item := p.next(); item.Token != lexer.Colon {
unexpected(item)
}
p.parseValue(p.next())
switch item := p.next(); item.Token {
case lexer.BraceClose:
return
case lexer.Comma:
p.parseObject()
default:
unexpected(item)
}
}
func (p *Parser) pushValue(item lexer.Item) {
for sel, exp := range p.sels {
if ok := exp.compare(p.ctx); ok {
if val, err := castValue(item); err == nil {
p.res <- Match{
Sel: sel,
Val: val,
}
} else {
p.res <- Match{
Sel: sel,
Val: err,
}
}
return
}
}
}
func (p *Parser) next() lexer.Item {
if item, ok := p.lex.NextItem(); ok {
if item.Token == lexer.Error {
panic(item)
}
if p.verbose {
fmt.Println(item)
}
return item
} else {
panic("EOF reached")
}
}
func castValue(item lexer.Item) (val interface{}, err error) {
switch item.Token {
case lexer.Null:
val = nil
case lexer.Bool:
val = (item.Val == "true")
case lexer.String:
val = item.Val
case lexer.Number:
if strings.Index(item.Val, ".") > -1 {
val, err = strconv.ParseFloat(item.Val, 64)
} else {
val, err = strconv.ParseInt(item.Val, 10, 64)
}
}
return
}
func unexpected(item lexer.Item) {
panic(fmt.Errorf("Unexpected token: %s", item.String()))
}