semi-functional prototype
This commit is contained in:
+225
@@ -0,0 +1,225 @@
|
||||
package jpath
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type parser struct {
|
||||
flow chan token
|
||||
tokensBuffer []token
|
||||
path []PathFn
|
||||
}
|
||||
|
||||
type parserStateFn func(*parser) parserStateFn
|
||||
|
||||
// Formats and panics an error message based on a token
|
||||
func (p *parser) raiseError(tok *token, msg string, args ...interface{}) {
|
||||
panic(tok.Position.String() + ": " + fmt.Sprintf(msg, args...))
|
||||
}
|
||||
|
||||
func (p *parser) run() {
|
||||
for state := parseStart; state != nil; {
|
||||
state = state(p)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *parser) backup(tok *token) {
|
||||
p.tokensBuffer = append(p.tokensBuffer, *tok)
|
||||
}
|
||||
|
||||
func (p *parser) peek() *token {
|
||||
if len(p.tokensBuffer) != 0 {
|
||||
return &(p.tokensBuffer[0])
|
||||
}
|
||||
|
||||
tok, ok := <-p.flow
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
p.backup(&tok)
|
||||
return &tok
|
||||
}
|
||||
|
||||
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 (p *parser) appendPath(fn PathFn) {
|
||||
p.path = append(p.path, fn)
|
||||
}
|
||||
|
||||
func parseStart(p *parser) parserStateFn {
|
||||
tok := p.getToken()
|
||||
|
||||
if tok == nil || tok.typ == tokenEOF {
|
||||
return nil
|
||||
}
|
||||
|
||||
if tok.typ != tokenDollar {
|
||||
p.raiseError(tok, "Expected '$' at start of expression")
|
||||
}
|
||||
|
||||
return parseMatchExpr
|
||||
}
|
||||
|
||||
func parseMatchExpr(p *parser) parserStateFn {
|
||||
tok := p.getToken()
|
||||
switch tok.typ {
|
||||
case tokenDot:
|
||||
p.appendPath(matchKeyFn(tok.val))
|
||||
return parseMatchExpr
|
||||
case tokenDotDot:
|
||||
p.appendPath(matchRecurseFn())
|
||||
return parseSimpleMatchExpr
|
||||
case tokenLBracket:
|
||||
return parseBracketExpr
|
||||
case tokenStar:
|
||||
p.appendPath(matchAnyFn())
|
||||
return parseMatchExpr
|
||||
case tokenEOF:
|
||||
return nil // allow EOF at this stage
|
||||
}
|
||||
p.raiseError(tok, "expected match expression")
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseSimpleMatchExpr(p *parser) parserStateFn {
|
||||
tok := p.getToken()
|
||||
switch tok.typ {
|
||||
case tokenLBracket:
|
||||
return parseBracketExpr
|
||||
case tokenKey:
|
||||
p.appendPath(matchKeyFn(tok.val))
|
||||
return parseMatchExpr
|
||||
case tokenStar:
|
||||
p.appendPath(matchAnyFn())
|
||||
return parseMatchExpr
|
||||
}
|
||||
p.raiseError(tok, "expected match expression")
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseBracketExpr(p *parser) parserStateFn {
|
||||
tok := p.peek()
|
||||
switch tok.typ {
|
||||
case tokenInteger:
|
||||
// look ahead for a ':'
|
||||
p.getToken()
|
||||
next := p.peek()
|
||||
p.backup(tok)
|
||||
if next.typ == tokenColon {
|
||||
return parseSliceExpr
|
||||
}
|
||||
return parseUnionExpr
|
||||
case tokenColon:
|
||||
return parseSliceExpr
|
||||
}
|
||||
return parseUnionExpr
|
||||
}
|
||||
|
||||
func parseUnionExpr(p *parser) parserStateFn {
|
||||
union := []PathFn{}
|
||||
for {
|
||||
// parse sub expression
|
||||
tok := p.getToken()
|
||||
switch tok.typ {
|
||||
case tokenInteger:
|
||||
idx, _ := strconv.Atoi(tok.val)
|
||||
union = append(union, matchIndexFn(idx))
|
||||
case tokenKey:
|
||||
union = append(union, matchKeyFn(tok.val))
|
||||
case tokenQuestion:
|
||||
return parseFilterExpr
|
||||
case tokenLParen:
|
||||
return parseScriptExpr
|
||||
default:
|
||||
p.raiseError(tok, "expected union sub expression")
|
||||
}
|
||||
// parse delimiter or terminator
|
||||
tok = p.getToken()
|
||||
switch tok.typ {
|
||||
case tokenComma:
|
||||
continue
|
||||
case tokenRBracket:
|
||||
break
|
||||
default:
|
||||
p.raiseError(tok, "expected ',' or ']'")
|
||||
}
|
||||
}
|
||||
p.appendPath(matchUnionFn(union))
|
||||
return parseMatchExpr
|
||||
}
|
||||
|
||||
func parseSliceExpr(p *parser) parserStateFn {
|
||||
// init slice to grab all elements
|
||||
start, end, step := 0, math.MaxInt64, 1
|
||||
|
||||
// parse optional start
|
||||
tok := p.getToken()
|
||||
if tok.typ == tokenInteger {
|
||||
start, _ = strconv.Atoi(tok.val)
|
||||
tok = p.getToken()
|
||||
}
|
||||
if tok.typ != tokenColon {
|
||||
p.raiseError(tok, "expected ':'")
|
||||
}
|
||||
|
||||
// parse optional end
|
||||
tok = p.getToken()
|
||||
if tok.typ == tokenInteger {
|
||||
end, _ = strconv.Atoi(tok.val)
|
||||
tok = p.getToken()
|
||||
}
|
||||
if tok.typ != tokenColon || tok.typ != tokenRBracket {
|
||||
p.raiseError(tok, "expected ']' or ':'")
|
||||
}
|
||||
|
||||
// parse optional step
|
||||
tok = p.getToken()
|
||||
if tok.typ == tokenInteger {
|
||||
step, _ = strconv.Atoi(tok.val)
|
||||
if step < 0 {
|
||||
p.raiseError(tok, "step must be a positive value")
|
||||
}
|
||||
tok = p.getToken()
|
||||
}
|
||||
if tok.typ != tokenRBracket {
|
||||
p.raiseError(tok, "expected ']'")
|
||||
}
|
||||
|
||||
p.appendPath(matchSliceFn(start, end, step))
|
||||
return parseMatchExpr
|
||||
}
|
||||
|
||||
func parseFilterExpr(p *parser) parserStateFn {
|
||||
p.raiseError(p.peek(), "filter expressions are unsupported")
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseScriptExpr(p *parser) parserStateFn {
|
||||
p.raiseError(p.peek(), "script expressions are unsupported")
|
||||
return nil
|
||||
}
|
||||
|
||||
func parse(flow chan token) []PathFn {
|
||||
result := []PathFn{}
|
||||
parser := &parser{
|
||||
flow: flow,
|
||||
tokensBuffer: []token{},
|
||||
path: result,
|
||||
}
|
||||
parser.run()
|
||||
return result
|
||||
}
|
||||
Reference in New Issue
Block a user