7337a63f5a
This is causing an integer overflow on 386 go builds, because ints are int32 and not int64 on this platform.
277 lines
6.0 KiB
Go
277 lines
6.0 KiB
Go
/*
|
|
Based on the "jsonpath" spec/concept.
|
|
|
|
http://goessner.net/articles/JsonPath/
|
|
https://code.google.com/p/json-path/
|
|
*/
|
|
|
|
package toml
|
|
|
|
import (
|
|
"fmt"
|
|
)
|
|
|
|
const MaxInt = int(^uint(0) >> 1)
|
|
|
|
type queryParser struct {
|
|
flow chan token
|
|
tokensBuffer []token
|
|
query *Query
|
|
union []pathFn
|
|
err error
|
|
}
|
|
|
|
type queryParserStateFn func() queryParserStateFn
|
|
|
|
// Formats and panics an error message based on a token
|
|
func (p *queryParser) parseError(tok *token, msg string, args ...interface{}) queryParserStateFn {
|
|
p.err = fmt.Errorf(tok.Position.String()+": "+msg, args...)
|
|
return nil // trigger parse to end
|
|
}
|
|
|
|
func (p *queryParser) run() {
|
|
for state := p.parseStart; state != nil; {
|
|
state = state()
|
|
}
|
|
}
|
|
|
|
func (p *queryParser) backup(tok *token) {
|
|
p.tokensBuffer = append(p.tokensBuffer, *tok)
|
|
}
|
|
|
|
func (p *queryParser) 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 *queryParser) lookahead(types ...tokenType) bool {
|
|
result := true
|
|
buffer := []token{}
|
|
|
|
for _, typ := range types {
|
|
tok := p.getToken()
|
|
if tok == nil {
|
|
result = false
|
|
break
|
|
}
|
|
buffer = append(buffer, *tok)
|
|
if tok.typ != typ {
|
|
result = false
|
|
break
|
|
}
|
|
}
|
|
// add the tokens back to the buffer, and return
|
|
p.tokensBuffer = append(p.tokensBuffer, buffer...)
|
|
return result
|
|
}
|
|
|
|
func (p *queryParser) 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 *queryParser) parseStart() queryParserStateFn {
|
|
tok := p.getToken()
|
|
|
|
if tok == nil || tok.typ == tokenEOF {
|
|
return nil
|
|
}
|
|
|
|
if tok.typ != tokenDollar {
|
|
return p.parseError(tok, "Expected '$' at start of expression")
|
|
}
|
|
|
|
return p.parseMatchExpr
|
|
}
|
|
|
|
// handle '.' prefix, '[]', and '..'
|
|
func (p *queryParser) parseMatchExpr() queryParserStateFn {
|
|
tok := p.getToken()
|
|
switch tok.typ {
|
|
case tokenDotDot:
|
|
p.query.appendPath(&matchRecursiveFn{})
|
|
// nested parse for '..'
|
|
tok := p.getToken()
|
|
switch tok.typ {
|
|
case tokenKey:
|
|
p.query.appendPath(newMatchKeyFn(tok.val))
|
|
return p.parseMatchExpr
|
|
case tokenLeftBracket:
|
|
return p.parseBracketExpr
|
|
case tokenStar:
|
|
// do nothing - the recursive predicate is enough
|
|
return p.parseMatchExpr
|
|
}
|
|
|
|
case tokenDot:
|
|
// nested parse for '.'
|
|
tok := p.getToken()
|
|
switch tok.typ {
|
|
case tokenKey:
|
|
p.query.appendPath(newMatchKeyFn(tok.val))
|
|
return p.parseMatchExpr
|
|
case tokenStar:
|
|
p.query.appendPath(&matchAnyFn{})
|
|
return p.parseMatchExpr
|
|
}
|
|
|
|
case tokenLeftBracket:
|
|
return p.parseBracketExpr
|
|
|
|
case tokenEOF:
|
|
return nil // allow EOF at this stage
|
|
}
|
|
return p.parseError(tok, "expected match expression")
|
|
return nil
|
|
}
|
|
|
|
func (p *queryParser) parseBracketExpr() queryParserStateFn {
|
|
if p.lookahead(tokenInteger, tokenColon) {
|
|
return p.parseSliceExpr
|
|
}
|
|
if p.peek().typ == tokenColon {
|
|
return p.parseSliceExpr
|
|
}
|
|
return p.parseUnionExpr
|
|
}
|
|
|
|
func (p *queryParser) parseUnionExpr() queryParserStateFn {
|
|
var tok *token
|
|
|
|
// this state can be traversed after some sub-expressions
|
|
// so be careful when setting up state in the parser
|
|
if p.union == nil {
|
|
p.union = []pathFn{}
|
|
}
|
|
|
|
loop: // labeled loop for easy breaking
|
|
for {
|
|
if len(p.union) > 0 {
|
|
// parse delimiter or terminator
|
|
tok = p.getToken()
|
|
switch tok.typ {
|
|
case tokenComma:
|
|
// do nothing
|
|
case tokenRightBracket:
|
|
break loop
|
|
default:
|
|
return p.parseError(tok, "expected ',' or ']', not '%s'", tok.val)
|
|
}
|
|
}
|
|
|
|
// parse sub expression
|
|
tok = p.getToken()
|
|
switch tok.typ {
|
|
case tokenInteger:
|
|
p.union = append(p.union, newMatchIndexFn(tok.Int()))
|
|
case tokenKey:
|
|
p.union = append(p.union, newMatchKeyFn(tok.val))
|
|
case tokenString:
|
|
p.union = append(p.union, newMatchKeyFn(tok.val))
|
|
case tokenQuestion:
|
|
return p.parseFilterExpr
|
|
default:
|
|
return p.parseError(tok, "expected union sub expression, not '%s', %d", tok.val, len(p.union))
|
|
}
|
|
}
|
|
|
|
// if there is only one sub-expression, use that instead
|
|
if len(p.union) == 1 {
|
|
p.query.appendPath(p.union[0])
|
|
} else {
|
|
p.query.appendPath(&matchUnionFn{p.union})
|
|
}
|
|
|
|
p.union = nil // clear out state
|
|
return p.parseMatchExpr
|
|
}
|
|
|
|
func (p *queryParser) parseSliceExpr() queryParserStateFn {
|
|
// init slice to grab all elements
|
|
start, end, step := 0, MaxInt, 1
|
|
|
|
// parse optional start
|
|
tok := p.getToken()
|
|
if tok.typ == tokenInteger {
|
|
start = tok.Int()
|
|
tok = p.getToken()
|
|
}
|
|
if tok.typ != tokenColon {
|
|
return p.parseError(tok, "expected ':'")
|
|
}
|
|
|
|
// parse optional end
|
|
tok = p.getToken()
|
|
if tok.typ == tokenInteger {
|
|
end = tok.Int()
|
|
tok = p.getToken()
|
|
}
|
|
if tok.typ == tokenRightBracket {
|
|
p.query.appendPath(newMatchSliceFn(start, end, step))
|
|
return p.parseMatchExpr
|
|
}
|
|
if tok.typ != tokenColon {
|
|
return p.parseError(tok, "expected ']' or ':'")
|
|
}
|
|
|
|
// parse optional step
|
|
tok = p.getToken()
|
|
if tok.typ == tokenInteger {
|
|
step = tok.Int()
|
|
if step < 0 {
|
|
return p.parseError(tok, "step must be a positive value")
|
|
}
|
|
tok = p.getToken()
|
|
}
|
|
if tok.typ != tokenRightBracket {
|
|
return p.parseError(tok, "expected ']'")
|
|
}
|
|
|
|
p.query.appendPath(newMatchSliceFn(start, end, step))
|
|
return p.parseMatchExpr
|
|
}
|
|
|
|
func (p *queryParser) parseFilterExpr() queryParserStateFn {
|
|
tok := p.getToken()
|
|
if tok.typ != tokenLeftParen {
|
|
return p.parseError(tok, "expected left-parenthesis for filter expression")
|
|
}
|
|
tok = p.getToken()
|
|
if tok.typ != tokenKey && tok.typ != tokenString {
|
|
return p.parseError(tok, "expected key or string for filter funciton name")
|
|
}
|
|
name := tok.val
|
|
tok = p.getToken()
|
|
if tok.typ != tokenRightParen {
|
|
return p.parseError(tok, "expected right-parenthesis for filter expression")
|
|
}
|
|
p.union = append(p.union, newMatchFilterFn(name, tok.Position))
|
|
return p.parseUnionExpr
|
|
}
|
|
|
|
func parseQuery(flow chan token) (*Query, error) {
|
|
parser := &queryParser{
|
|
flow: flow,
|
|
tokensBuffer: []token{},
|
|
query: newQuery(),
|
|
}
|
|
parser.run()
|
|
return parser.query, parser.err
|
|
}
|