jsonpath/eval.go

349 lines
6.7 KiB
Go

package jsonpath
import (
"bytes"
"fmt"
)
type queryStateFn func(*query, *Eval, *Item) queryStateFn
type query struct {
Path
state queryStateFn
start int
pos int
firstType int // first json token type in buffer
buffer bytes.Buffer
resultQueue *Results
valLoc stack // capture the current location stack at capture
errors []error
buckets stack // stack of exprBucket
}
type exprBucket struct {
operatorLoc int
expression []Item
queries []*query
results *Results
}
type evalStateFn func(*Eval, *Item) evalStateFn
type Eval struct {
tr tokenReader
levelStack intStack
location stack
queries map[string]*query
state evalStateFn
prevIndex int
nextKey []byte
copyValues bool
resultQueue *Results
Error error
}
func newEvaluation(tr tokenReader, paths ...*Path) *Eval {
e := &Eval{
tr: tr,
location: *newStack(),
levelStack: *newIntStack(),
state: evalRoot,
queries: make(map[string]*query),
prevIndex: -1,
nextKey: nil,
copyValues: true, // depends on which lexer is used
resultQueue: newResults(),
}
for _, p := range paths {
e.queries[p.stringValue] = newQuery(p)
}
// Determine whether to copy emitted item values ([]byte) from lexer
switch tr.(type) {
case *ReaderLexer:
e.copyValues = true
default:
e.copyValues = false
}
return e
}
func newQuery(p *Path) *query {
return &query{
Path: *p,
state: pathMatchOp,
start: -1,
pos: -1,
buffer: *bytes.NewBuffer(make([]byte, 0, 50)),
valLoc: *newStack(),
errors: make([]error, 0),
resultQueue: newResults(),
buckets: *newStack(),
}
}
func (e *Eval) Iterate() (*Results, bool) {
e.resultQueue.clear()
t, ok := e.tr.next()
if !ok || e.state == nil {
return nil, false
}
// run evaluator function
e.state = e.state(e, t)
anyRunning := false
// run path function for each path
for str, query := range e.queries {
anyRunning = true
query.state = query.state(query, e, t)
if query.state == nil {
delete(e.queries, str)
}
if query.resultQueue.len() > 0 {
e.resultQueue.push(query.resultQueue.Pop())
}
for _, b := range query.buckets.values {
bucket := b.(exprBucket)
for _, dq := range bucket.queries {
dq.state = dq.state(dq, e, t)
if query.resultQueue.len() > 0 {
e.resultQueue.push(query.resultQueue.Pop())
}
}
}
}
if !anyRunning {
return nil, false
}
if e.Error != nil {
return nil, false
}
return e.resultQueue, true
}
func (e *Eval) Next() (*Result, bool) {
if e.resultQueue.len() > 0 {
return e.resultQueue.Pop(), true
}
for {
if _, ok := e.Iterate(); ok {
if e.resultQueue.len() > 0 {
return e.resultQueue.Pop(), true
}
} else {
break
}
}
return nil, false
}
func (q *query) loc() int {
return abs(q.pos-q.start) + q.start
}
func (q *query) trySpillOver() {
if b, ok := q.buckets.peek(); ok {
bucket := b.(exprBucket)
if q.loc() < bucket.operatorLoc {
q.buckets.pop()
exprRes, err := bucket.evaluate()
if err != nil {
q.errors = append(q.errors, err)
}
if exprRes {
next, ok := q.buckets.peek()
var spillover *Results
if !ok {
// fmt.Println("Spilling over into end queue")
spillover = q.resultQueue
} else {
// fmt.Println("Spilling over into lower bucket")
nextBucket := next.(exprBucket)
spillover = nextBucket.results
}
for {
v := bucket.results.Pop()
if v != nil {
spillover.push(v)
} else {
break
}
}
}
}
}
}
func pathMatchOp(q *query, e *Eval, i *Item) queryStateFn {
curLocation := e.location.len() - 1
if q.loc() > curLocation {
q.pos--
q.trySpillOver()
} else if q.loc() <= curLocation {
if q.loc() == curLocation-1 {
if len(q.operators)+q.start >= curLocation {
current, _ := e.location.peek()
nextOp := q.operators[abs(q.loc()-q.start)]
if itemMatchOperator(current, nextOp) {
q.pos++
if nextOp.whereClauseBytes != nil && len(nextOp.whereClause) > 0 {
bucket := exprBucket{
operatorLoc: q.loc(),
expression: nextOp.whereClause,
queries: make([]*query, len(nextOp.dependentPaths)),
results: newResults(),
}
for i, p := range nextOp.dependentPaths {
bucket.queries[i] = newQuery(p)
bucket.queries[i].pos = q.loc()
bucket.queries[i].start = q.loc()
bucket.queries[i].captureEndValue = true
}
q.buckets.push(bucket)
}
}
}
}
}
if q.loc() == len(q.operators)+q.start && q.loc() <= curLocation {
if q.captureEndValue {
q.firstType = i.typ
q.buffer.Write(i.val)
}
q.valLoc = *e.location.clone()
return pathEndValue
}
if q.loc() < -1 {
return nil
}
return pathMatchOp
}
func pathEndValue(q *query, e *Eval, i *Item) queryStateFn {
if e.location.len()-1 >= q.loc() {
if q.captureEndValue {
q.buffer.Write(i.val)
}
} else {
r := &Result{Keys: q.valLoc.toArray()}
if q.buffer.Len() > 0 {
val := make([]byte, q.buffer.Len())
copy(val, q.buffer.Bytes())
r.Value = val
switch q.firstType {
case jsonBraceLeft:
r.Type = JSONObject
case jsonString:
r.Type = JSONString
case jsonBracketLeft:
r.Type = JSONArray
case jsonNull:
r.Type = JSONNull
case jsonBool:
r.Type = JSONBool
case jsonNumber:
r.Type = JSONNumber
default:
r.Type = -1
}
}
if q.buckets.len() == 0 {
q.resultQueue.push(r)
} else {
b, _ := q.buckets.peek()
b.(exprBucket).results.push(r)
}
q.valLoc = *newStack()
q.buffer.Truncate(0)
q.pos--
return pathMatchOp
}
return pathEndValue
}
func (b *exprBucket) evaluate() (bool, error) {
values := make(map[string]Item)
for _, q := range b.queries {
result := q.resultQueue.Pop()
if result != nil {
t, err := getJSONTokenType(result.Value)
if err != nil {
return false, err
}
i := Item{
typ: t,
val: result.Value,
}
values[q.Path.stringValue] = i
}
}
res, err := evaluatePostFix(b.expression, values)
if err != nil {
return false, err
}
resBool, ok := res.(bool)
if !ok {
return false, fmt.Errorf(exprErrorFinalValueNotBool, res)
}
return resBool, nil
}
func itemMatchOperator(loc interface{}, op *operator) bool {
topBytes, isKey := loc.([]byte)
topInt, isIndex := loc.(int)
if isKey {
switch op.typ {
case opTypeNameWild:
return true
case opTypeName, opTypeNameList:
_, found := op.keyStrings[string(topBytes)]
return found
}
} else if isIndex {
switch op.typ {
case opTypeIndexWild:
return true
case opTypeIndex, opTypeIndexRange:
return topInt >= op.indexStart && (!op.hasIndexEnd || topInt <= op.indexEnd)
}
}
return false
}