Skip to content

Commit 8249ea0

Browse files
committed
Add expr.WithContext()
1 parent 9a60147 commit 8249ea0

File tree

4 files changed

+118
-0
lines changed

4 files changed

+118
-0
lines changed

expr.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/antonmedv/expr/file"
1212
"github.com/antonmedv/expr/optimizer"
1313
"github.com/antonmedv/expr/parser"
14+
"github.com/antonmedv/expr/patcher"
1415
"github.com/antonmedv/expr/vm"
1516
)
1617

@@ -152,6 +153,13 @@ func EnableBuiltin(name string) Option {
152153
}
153154
}
154155

156+
// WithContext passes context to all functions calls with a context.Context argument.
157+
func WithContext(name string) Option {
158+
return Patch(patcher.WithContext{
159+
Name: name,
160+
})
161+
}
162+
155163
// Compile parses and compiles given input expression to bytecode program.
156164
func Compile(input string, ops ...Option) (*vm.Program, error) {
157165
config := conf.CreateNew()

expr_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package expr_test
22

33
import (
4+
"context"
45
"encoding/json"
56
"fmt"
67
"reflect"
@@ -488,6 +489,47 @@ func ExamplePatch() {
488489
// Output : Hello, you, world!
489490
}
490491

492+
func ExampleWithContext() {
493+
env := map[string]any{
494+
"fn": func(ctx context.Context, _, _ int) int {
495+
// An infinite loop that can be canceled by context.
496+
for {
497+
select {
498+
case <-ctx.Done():
499+
return 42
500+
}
501+
}
502+
},
503+
"ctx": context.TODO(), // Context should be passed as a variable.
504+
}
505+
506+
program, err := expr.Compile(`fn(1, 2)`,
507+
expr.Env(env),
508+
expr.WithContext("ctx"), // Pass context variable name.
509+
)
510+
if err != nil {
511+
fmt.Printf("%v", err)
512+
return
513+
}
514+
515+
// Cancel context after 100 milliseconds.
516+
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*100)
517+
defer cancel()
518+
519+
// After program is compiled, context can be passed to Run.
520+
env["ctx"] = ctx
521+
522+
// Run will return 42 after 100 milliseconds.
523+
output, err := expr.Run(program, env)
524+
if err != nil {
525+
fmt.Printf("%v", err)
526+
return
527+
}
528+
529+
fmt.Printf("%v", output)
530+
// Output: 42
531+
}
532+
491533
func TestExpr_readme_example(t *testing.T) {
492534
env := map[string]any{
493535
"greet": "Hello, %v!",

patcher/with_context.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package patcher
2+
3+
import (
4+
"reflect"
5+
6+
"github.com/antonmedv/expr/ast"
7+
)
8+
9+
// WithContext adds WithContext.Name argument to all functions calls with a context.Context argument.
10+
type WithContext struct {
11+
Name string
12+
}
13+
14+
// Visit adds WithContext.Name argument to all functions calls with a context.Context argument.
15+
func (w WithContext) Visit(node *ast.Node) {
16+
switch (*node).(type) {
17+
case *ast.CallNode:
18+
call := (*node).(*ast.CallNode)
19+
fn := call.Callee.Type()
20+
if fn.Kind() != reflect.Func {
21+
return
22+
}
23+
if fn.NumIn() == 0 {
24+
return
25+
}
26+
if fn.In(0).String() != "context.Context" {
27+
return
28+
}
29+
ast.Patch(node, &ast.CallNode{
30+
Callee: call.Callee,
31+
Arguments: append([]ast.Node{
32+
&ast.IdentifierNode{Value: w.Name},
33+
}, call.Arguments...),
34+
})
35+
}
36+
}

patcher/with_context_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package patcher_test
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/stretchr/testify/require"
8+
9+
"github.com/antonmedv/expr"
10+
"github.com/antonmedv/expr/patcher"
11+
)
12+
13+
func TestWithContext(t *testing.T) {
14+
env := map[string]any{
15+
"fn": func(ctx context.Context, a int) int {
16+
return ctx.Value("value").(int) + a
17+
},
18+
"ctx": context.TODO(),
19+
}
20+
21+
withContext := patcher.WithContext{Name: "ctx"}
22+
23+
program, err := expr.Compile(`fn(40)`, expr.Env(env), expr.Patch(withContext))
24+
require.NoError(t, err)
25+
26+
ctx := context.WithValue(context.Background(), "value", 2)
27+
env["ctx"] = ctx
28+
29+
output, err := expr.Run(program, env)
30+
require.NoError(t, err)
31+
require.Equal(t, 42, output)
32+
}

0 commit comments

Comments
 (0)