Skip to content

Commit 4a4bfbf

Browse files
Parsing upcoming let-expression (#66)
* Fixed mismatched pretty printed parens Signed-off-by: Springcomp <[email protected]> * [lexical-scoping] Parsing let-expression. Signed-off-by: Springcomp <[email protected]> * [lexical-scoping] Refactor comma-separated lists. Signed-off-by: Springcomp <[email protected]> * [lexical-scoping] 'let' and 'in' should be valid identifiers as well. Signed-off-by: Springcomp <[email protected]> * [lexical-scoping] Fixed file is not 'gofumpt' ed Signed-off-by: Springcomp <[email protected]> * [lexical-scoping] Refactored tests. Signed-off-by: Springcomp <[email protected]> * [lexical-scoping] Ensure sustained code coverage. Signed-off-by: Springcomp <[email protected]> * lexing Signed-off-by: Charles-Edouard Brétéché <[email protected]> * interpreter Signed-off-by: Charles-Edouard Brétéché <[email protected]> * error Signed-off-by: Charles-Edouard Brétéché <[email protected]> * fix Signed-off-by: Charles-Edouard Brétéché <[email protected]> * Re-introduced tests that now succeed. Signed-off-by: Springcomp <[email protected]> * Include compliance tests. Signed-off-by: Springcomp <[email protected]> * let() is deprecated. Signed-off-by: Springcomp <[email protected]> * unit tests Signed-off-by: Charles-Edouard Brétéché <[email protected]> --------- Signed-off-by: Springcomp <[email protected]> Signed-off-by: Charles-Edouard Brétéché <[email protected]> Co-authored-by: Charles-Edouard Brétéché <[email protected]>
1 parent 7d8d162 commit 4a4bfbf

File tree

14 files changed

+532
-54
lines changed

14 files changed

+532
-54
lines changed

jp_test.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,6 @@ type TestCase struct {
2828
var excludeList = []string{
2929
"legacy/legacy-literal.json",
3030
"benchmarks.json",
31-
"function_let.json",
32-
"lexical_scoping.json",
33-
34-
// this test currently fails
35-
"literal.json",
3631
}
3732

3833
func excluded(path string) bool {

pkg/api/api.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ func MustCompile(expression string, funcs ...functions.FunctionEntry) JMESPath {
5353

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

pkg/api/api_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,14 @@ func TestSearch(t *testing.T) {
130130
want: map[string]interface{}{
131131
"foo": nil,
132132
},
133+
}, {
134+
args: args{
135+
expression: "let $root = @ in $root.a",
136+
data: map[string]interface{}{
137+
"a": 42.0,
138+
},
139+
},
140+
want: 42.0,
133141
}}
134142
for _, tt := range tests {
135143
t.Run(tt.name, func(t *testing.T) {

pkg/binding/binding.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package binding
2+
3+
import "fmt"
4+
5+
// Bindings stores let expression bindings by name.
6+
type Bindings interface {
7+
// Get returns the value bound for a given name.
8+
Get(string) (interface{}, error)
9+
// Register registers a value associated with a given name, it returns a new binding
10+
Register(string, interface{}) Bindings
11+
}
12+
13+
type bindings struct {
14+
values map[string]interface{}
15+
}
16+
17+
func NewBindings() Bindings {
18+
return bindings{}
19+
}
20+
21+
func (b bindings) Get(name string) (interface{}, error) {
22+
if value, ok := b.values[name]; ok {
23+
return value, nil
24+
}
25+
return nil, fmt.Errorf("variable not defined: %s", name)
26+
}
27+
28+
func (b bindings) Register(name string, value interface{}) Bindings {
29+
values := map[string]interface{}{}
30+
for k, v := range b.values {
31+
values[k] = v
32+
}
33+
values[name] = value
34+
return bindings{
35+
values: values,
36+
}
37+
}

pkg/binding/binding_test.go

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
package binding
2+
3+
import (
4+
"reflect"
5+
"testing"
6+
)
7+
8+
func TestNewBindings(t *testing.T) {
9+
tests := []struct {
10+
name string
11+
want Bindings
12+
}{{
13+
want: bindings{},
14+
}}
15+
for _, tt := range tests {
16+
t.Run(tt.name, func(t *testing.T) {
17+
if got := NewBindings(); !reflect.DeepEqual(got, tt.want) {
18+
t.Errorf("NewBindings() = %v, want %v", got, tt.want)
19+
}
20+
})
21+
}
22+
}
23+
24+
func Test_bindings_Get(t *testing.T) {
25+
type fields struct {
26+
values map[string]interface{}
27+
}
28+
type args struct {
29+
name string
30+
}
31+
tests := []struct {
32+
name string
33+
fields fields
34+
args args
35+
want interface{}
36+
wantErr bool
37+
}{{
38+
fields: fields{
39+
values: nil,
40+
},
41+
args: args{
42+
name: "$root",
43+
},
44+
wantErr: true,
45+
}, {
46+
fields: fields{
47+
values: map[string]interface{}{},
48+
},
49+
args: args{
50+
name: "$root",
51+
},
52+
wantErr: true,
53+
}, {
54+
fields: fields{
55+
values: map[string]interface{}{
56+
"$root": 42.0,
57+
},
58+
},
59+
args: args{
60+
name: "$root",
61+
},
62+
want: 42.0,
63+
}, {
64+
fields: fields{
65+
values: map[string]interface{}{
66+
"$foot": 42.0,
67+
},
68+
},
69+
args: args{
70+
name: "$root",
71+
},
72+
wantErr: true,
73+
}}
74+
for _, tt := range tests {
75+
t.Run(tt.name, func(t *testing.T) {
76+
b := bindings{
77+
values: tt.fields.values,
78+
}
79+
got, err := b.Get(tt.args.name)
80+
if (err != nil) != tt.wantErr {
81+
t.Errorf("bindings.Get() error = %v, wantErr %v", err, tt.wantErr)
82+
return
83+
}
84+
if !reflect.DeepEqual(got, tt.want) {
85+
t.Errorf("bindings.Get() = %v, want %v", got, tt.want)
86+
}
87+
})
88+
}
89+
}
90+
91+
func Test_bindings_Register(t *testing.T) {
92+
type fields struct {
93+
values map[string]interface{}
94+
}
95+
type args struct {
96+
name string
97+
value interface{}
98+
}
99+
tests := []struct {
100+
name string
101+
fields fields
102+
args args
103+
want Bindings
104+
}{{
105+
fields: fields{
106+
values: nil,
107+
},
108+
args: args{
109+
name: "$root",
110+
value: 42.0,
111+
},
112+
want: bindings{
113+
values: map[string]interface{}{
114+
"$root": 42.0,
115+
},
116+
},
117+
}, {
118+
fields: fields{
119+
values: map[string]interface{}{
120+
"$root": 21.0,
121+
},
122+
},
123+
args: args{
124+
name: "$root",
125+
value: 42.0,
126+
},
127+
want: bindings{
128+
values: map[string]interface{}{
129+
"$root": 42.0,
130+
},
131+
},
132+
}}
133+
for _, tt := range tests {
134+
t.Run(tt.name, func(t *testing.T) {
135+
b := bindings{
136+
values: tt.fields.values,
137+
}
138+
if got := b.Register(tt.args.name, tt.args.value); !reflect.DeepEqual(got, tt.want) {
139+
t.Errorf("bindings.Register() = %v, want %v", got, tt.want)
140+
}
141+
})
142+
}
143+
}

pkg/interpreter/interpreter.go

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"unicode"
88
"unicode/utf8"
99

10+
"github.com/jmespath-community/go-jmespath/pkg/binding"
1011
"github.com/jmespath-community/go-jmespath/pkg/parsing"
1112
"github.com/jmespath-community/go-jmespath/pkg/util"
1213
)
@@ -21,14 +22,19 @@ type Interpreter interface {
2122
}
2223

2324
type treeInterpreter struct {
24-
caller FunctionCaller
25-
root interface{}
25+
caller FunctionCaller
26+
root interface{}
27+
bindings binding.Bindings
2628
}
2729

28-
func NewInterpreter(data interface{}, caller FunctionCaller) Interpreter {
30+
func NewInterpreter(data interface{}, caller FunctionCaller, bindings binding.Bindings) Interpreter {
31+
if bindings == nil {
32+
bindings = binding.NewBindings()
33+
}
2934
return &treeInterpreter{
30-
caller: caller,
31-
root: data,
35+
caller: caller,
36+
root: data,
37+
bindings: bindings,
3238
}
3339
}
3440

@@ -211,6 +217,39 @@ func (intr *treeInterpreter) Execute(node parsing.ASTNode, value interface{}) (i
211217
return value, nil
212218
case parsing.ASTRootNode:
213219
return intr.root, nil
220+
case parsing.ASTBindings:
221+
bindings := intr.bindings
222+
for _, child := range node.Children {
223+
if value, err := intr.Execute(child.Children[1], value); err != nil {
224+
return nil, err
225+
} else {
226+
bindings = bindings.Register(child.Children[0].Value.(string), value)
227+
}
228+
}
229+
intr.bindings = bindings
230+
// doesn't mutate value
231+
return value, nil
232+
case parsing.ASTLetExpression:
233+
// save bindings state
234+
bindings := intr.bindings
235+
// retore bindings state
236+
defer func() {
237+
intr.bindings = bindings
238+
}()
239+
// evalute bindings first, then evaluate expression
240+
if _, err := intr.Execute(node.Children[0], value); err != nil {
241+
return nil, err
242+
} else if value, err := intr.Execute(node.Children[1], value); err != nil {
243+
return nil, err
244+
} else {
245+
return value, nil
246+
}
247+
case parsing.ASTVariable:
248+
if value, err := intr.bindings.Get(node.Value.(string)); err != nil {
249+
return nil, err
250+
} else {
251+
return value, nil
252+
}
214253
case parsing.ASTIndex:
215254
if sliceType, ok := value.([]interface{}); ok {
216255
index := node.Value.(int)

pkg/interpreter/interpreter_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ func search(t *testing.T, expression string, data interface{}) (interface{}, err
5252
return nil, err
5353
}
5454
caller := NewFunctionCaller(functions.GetDefaultFunctions()...)
55-
intr := NewInterpreter(nil, caller)
55+
intr := NewInterpreter(nil, caller, nil)
5656
return intr.Execute(ast, data)
5757
}
5858

@@ -198,7 +198,7 @@ func TestCanSupportSliceOfStructsWithFunctions(t *testing.T) {
198198
func BenchmarkInterpretSingleFieldStruct(b *testing.B) {
199199
assert := assert.New(b)
200200
caller := NewFunctionCaller(functions.GetDefaultFunctions()...)
201-
intr := NewInterpreter(nil, caller)
201+
intr := NewInterpreter(nil, caller, nil)
202202
parser := parsing.NewParser()
203203
ast, _ := parser.Parse("fooasdfasdfasdfasdf")
204204
data := benchmarkStruct{"foobarbazqux"}
@@ -213,7 +213,7 @@ func BenchmarkInterpretSingleFieldStruct(b *testing.B) {
213213
func BenchmarkInterpretNestedStruct(b *testing.B) {
214214
assert := assert.New(b)
215215
caller := NewFunctionCaller(functions.GetDefaultFunctions()...)
216-
intr := NewInterpreter(nil, caller)
216+
intr := NewInterpreter(nil, caller, nil)
217217
parser := parsing.NewParser()
218218
ast, _ := parser.Parse("fooasdfasdfasdfasdf.fooasdfasdfasdfasdf.fooasdfasdfasdfasdf.fooasdfasdfasdfasdf")
219219
data := benchmarkNested{
@@ -238,7 +238,7 @@ func BenchmarkInterpretNestedMaps(b *testing.B) {
238238
err := json.Unmarshal(jsonData, &data)
239239
assert.Nil(err)
240240
caller := NewFunctionCaller(functions.GetDefaultFunctions()...)
241-
intr := NewInterpreter(nil, caller)
241+
intr := NewInterpreter(nil, caller, nil)
242242
parser := parsing.NewParser()
243243
ast, _ := parser.Parse("fooasdfasdfasdfasdf.fooasdfasdfasdfasdf.fooasdfasdfasdfasdf.fooasdfasdfasdfasdf")
244244
for i := 0; i < b.N; i++ {

pkg/parsing/astnodetype_string.go

Lines changed: 6 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)