Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion compliance
5 changes: 0 additions & 5 deletions jp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,6 @@ type TestCase struct {
var excludeList = []string{
"legacy/legacy-literal.json",
"benchmarks.json",
"function_let.json",
"lexical_scoping.json",

// this test currently fails
"literal.json",
}

func excluded(path string) bool {
Expand Down
2 changes: 1 addition & 1 deletion pkg/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func MustCompile(expression string, funcs ...functions.FunctionEntry) JMESPath {

// Search evaluates a JMESPath expression against input data and returns the result.
func (jp jmesPath) Search(data interface{}) (interface{}, error) {
intr := interpreter.NewInterpreter(data, jp.caller)
intr := interpreter.NewInterpreter(data, jp.caller, nil)
return intr.Execute(jp.node, data)
}

Expand Down
8 changes: 8 additions & 0 deletions pkg/api/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,14 @@ func TestSearch(t *testing.T) {
want: map[string]interface{}{
"foo": nil,
},
}, {
args: args{
expression: "let $root = @ in $root.a",
data: map[string]interface{}{
"a": 42.0,
},
},
want: 42.0,
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
37 changes: 37 additions & 0 deletions pkg/binding/binding.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package binding

import "fmt"

// Bindings stores let expression bindings by name.
type Bindings interface {
// Get returns the value bound for a given name.
Get(string) (interface{}, error)
// Register registers a value associated with a given name, it returns a new binding
Register(string, interface{}) Bindings
}

type bindings struct {
values map[string]interface{}
}

func NewBindings() Bindings {
return bindings{}
}

func (b bindings) Get(name string) (interface{}, error) {
if value, ok := b.values[name]; ok {
return value, nil
}
return nil, fmt.Errorf("variable not defined: %s", name)
}

func (b bindings) Register(name string, value interface{}) Bindings {
values := map[string]interface{}{}
for k, v := range b.values {
values[k] = v
}
values[name] = value
return bindings{
values: values,
}
}
143 changes: 143 additions & 0 deletions pkg/binding/binding_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package binding

import (
"reflect"
"testing"
)

func TestNewBindings(t *testing.T) {
tests := []struct {
name string
want Bindings
}{{
want: bindings{},
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := NewBindings(); !reflect.DeepEqual(got, tt.want) {
t.Errorf("NewBindings() = %v, want %v", got, tt.want)
}
})
}
}

func Test_bindings_Get(t *testing.T) {
type fields struct {
values map[string]interface{}
}
type args struct {
name string
}
tests := []struct {
name string
fields fields
args args
want interface{}
wantErr bool
}{{
fields: fields{
values: nil,
},
args: args{
name: "$root",
},
wantErr: true,
}, {
fields: fields{
values: map[string]interface{}{},
},
args: args{
name: "$root",
},
wantErr: true,
}, {
fields: fields{
values: map[string]interface{}{
"$root": 42.0,
},
},
args: args{
name: "$root",
},
want: 42.0,
}, {
fields: fields{
values: map[string]interface{}{
"$foot": 42.0,
},
},
args: args{
name: "$root",
},
wantErr: true,
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
b := bindings{
values: tt.fields.values,
}
got, err := b.Get(tt.args.name)
if (err != nil) != tt.wantErr {
t.Errorf("bindings.Get() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("bindings.Get() = %v, want %v", got, tt.want)
}
})
}
}

func Test_bindings_Register(t *testing.T) {
type fields struct {
values map[string]interface{}
}
type args struct {
name string
value interface{}
}
tests := []struct {
name string
fields fields
args args
want Bindings
}{{
fields: fields{
values: nil,
},
args: args{
name: "$root",
value: 42.0,
},
want: bindings{
values: map[string]interface{}{
"$root": 42.0,
},
},
}, {
fields: fields{
values: map[string]interface{}{
"$root": 21.0,
},
},
args: args{
name: "$root",
value: 42.0,
},
want: bindings{
values: map[string]interface{}{
"$root": 42.0,
},
},
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
b := bindings{
values: tt.fields.values,
}
if got := b.Register(tt.args.name, tt.args.value); !reflect.DeepEqual(got, tt.want) {
t.Errorf("bindings.Register() = %v, want %v", got, tt.want)
}
})
}
}
49 changes: 44 additions & 5 deletions pkg/interpreter/interpreter.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"unicode"
"unicode/utf8"

"github.com/jmespath-community/go-jmespath/pkg/binding"
"github.com/jmespath-community/go-jmespath/pkg/parsing"
"github.com/jmespath-community/go-jmespath/pkg/util"
)
Expand All @@ -21,14 +22,19 @@ type Interpreter interface {
}

type treeInterpreter struct {
caller FunctionCaller
root interface{}
caller FunctionCaller
root interface{}
bindings binding.Bindings
}

func NewInterpreter(data interface{}, caller FunctionCaller) Interpreter {
func NewInterpreter(data interface{}, caller FunctionCaller, bindings binding.Bindings) Interpreter {
if bindings == nil {
bindings = binding.NewBindings()
}
return &treeInterpreter{
caller: caller,
root: data,
caller: caller,
root: data,
bindings: bindings,
}
}

Expand Down Expand Up @@ -211,6 +217,39 @@ func (intr *treeInterpreter) Execute(node parsing.ASTNode, value interface{}) (i
return value, nil
case parsing.ASTRootNode:
return intr.root, nil
case parsing.ASTBindings:
bindings := intr.bindings
for _, child := range node.Children {
if value, err := intr.Execute(child.Children[1], value); err != nil {
return nil, err
} else {
bindings = bindings.Register(child.Children[0].Value.(string), value)
}
}
intr.bindings = bindings
// doesn't mutate value
return value, nil
case parsing.ASTLetExpression:
// save bindings state
bindings := intr.bindings
// retore bindings state
defer func() {
intr.bindings = bindings
}()
// evalute bindings first, then evaluate expression
if _, err := intr.Execute(node.Children[0], value); err != nil {
return nil, err
} else if value, err := intr.Execute(node.Children[1], value); err != nil {
return nil, err
} else {
return value, nil
}
case parsing.ASTVariable:
if value, err := intr.bindings.Get(node.Value.(string)); err != nil {
return nil, err
} else {
return value, nil
}
case parsing.ASTIndex:
if sliceType, ok := value.([]interface{}); ok {
index := node.Value.(int)
Expand Down
8 changes: 4 additions & 4 deletions pkg/interpreter/interpreter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func search(t *testing.T, expression string, data interface{}) (interface{}, err
return nil, err
}
caller := NewFunctionCaller(functions.GetDefaultFunctions()...)
intr := NewInterpreter(nil, caller)
intr := NewInterpreter(nil, caller, nil)
return intr.Execute(ast, data)
}

Expand Down Expand Up @@ -198,7 +198,7 @@ func TestCanSupportSliceOfStructsWithFunctions(t *testing.T) {
func BenchmarkInterpretSingleFieldStruct(b *testing.B) {
assert := assert.New(b)
caller := NewFunctionCaller(functions.GetDefaultFunctions()...)
intr := NewInterpreter(nil, caller)
intr := NewInterpreter(nil, caller, nil)
parser := parsing.NewParser()
ast, _ := parser.Parse("fooasdfasdfasdfasdf")
data := benchmarkStruct{"foobarbazqux"}
Expand All @@ -213,7 +213,7 @@ func BenchmarkInterpretSingleFieldStruct(b *testing.B) {
func BenchmarkInterpretNestedStruct(b *testing.B) {
assert := assert.New(b)
caller := NewFunctionCaller(functions.GetDefaultFunctions()...)
intr := NewInterpreter(nil, caller)
intr := NewInterpreter(nil, caller, nil)
parser := parsing.NewParser()
ast, _ := parser.Parse("fooasdfasdfasdfasdf.fooasdfasdfasdfasdf.fooasdfasdfasdfasdf.fooasdfasdfasdfasdf")
data := benchmarkNested{
Expand All @@ -238,7 +238,7 @@ func BenchmarkInterpretNestedMaps(b *testing.B) {
err := json.Unmarshal(jsonData, &data)
assert.Nil(err)
caller := NewFunctionCaller(functions.GetDefaultFunctions()...)
intr := NewInterpreter(nil, caller)
intr := NewInterpreter(nil, caller, nil)
parser := parsing.NewParser()
ast, _ := parser.Parse("fooasdfasdfasdfasdf.fooasdfasdfasdfasdf.fooasdfasdfasdfasdf.fooasdfasdfasdfasdf")
for i := 0; i < b.N; i++ {
Expand Down
8 changes: 6 additions & 2 deletions pkg/parsing/astnodetype_string.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading