From ac805806f801b985fd1a9ac1f9245a8890f2281e Mon Sep 17 00:00:00 2001 From: daniel Date: Mon, 5 Jul 2021 01:49:38 -0700 Subject: [PATCH] add Eval function (#338) --- README.md | 30 +++++++++++++++++--------- eval.go | 35 +++++++++++++++++++++++++++++++ eval_test.go | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 114 insertions(+), 10 deletions(-) create mode 100644 eval.go create mode 100644 eval_test.go diff --git a/README.md b/README.md index b84cf80..fd214cd 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ # The Tengo Language -[![GoDoc](https://godoc.org/github.com/d5/tengo?status.svg)](https://godoc.org/github.com/d5/tengo) +[![GoDoc](https://godoc.org/github.com/d5/tengo/v2?status.svg)](https://godoc.org/github.com/d5/tengo/v2) ![test](https://github.com/d5/tengo/workflows/test/badge.svg) [![Go Report Card](https://goreportcard.com/badge/github.com/d5/tengo)](https://goreportcard.com/report/github.com/d5/tengo) @@ -93,21 +93,18 @@ import ( ) func main() { - // Tengo script code - src := ` -each := func(seq, fn) { + // create a new Script instance + script := tengo.NewScript([]byte( +`each := func(seq, fn) { for x in seq { fn(x) } } sum := 0 mul := 1 each([a, b, c, d], func(x) { - sum += x - mul *= x -})` - - // create a new Script instance - script := tengo.NewScript([]byte(src)) + sum += x + mul *= x +})`)) // set values _ = script.Add("a", 1) @@ -128,6 +125,19 @@ each([a, b, c, d], func(x) { } ``` +Or, if you need to evaluate a simple expression, you can use [Eval](https://pkg.go.dev/github.com/d5/tengo/v2#Eval) function instead: + + +```golang +res, err := tengo.Eval(ctx, + `input ? "success" : "fail"`, + map[string]interface{}{"input": 1}) +if err != nil { + panic(err) +} +fmt.Println(res) // "success" +``` + ## References - [Language Syntax](https://github.com/d5/tengo/blob/master/docs/tutorial.md) diff --git a/eval.go b/eval.go new file mode 100644 index 0000000..9ce9b2a --- /dev/null +++ b/eval.go @@ -0,0 +1,35 @@ +package tengo + +import ( + "context" + "fmt" + "strings" +) + +// Eval compiles and executes given expr with params, and returns an +// evaluated value. expr must be an expression. Otherwise it will fail to +// compile. Expression must not use or define variable "__res__" as it's +// reserved for the internal usage. +func Eval( + ctx context.Context, + expr string, + params map[string]interface{}, +) (interface{}, error) { + expr = strings.TrimSpace(expr) + if expr == "" { + return nil, fmt.Errorf("empty expression") + } + + script := NewScript([]byte(fmt.Sprintf("__res__ := (%s)", expr))) + for pk, pv := range params { + err := script.Add(pk, pv) + if err != nil { + return nil, fmt.Errorf("script add: %w", err) + } + } + compiled, err := script.RunContext(ctx) + if err != nil { + return nil, fmt.Errorf("script run: %w", err) + } + return compiled.Get("__res__").Value(), nil +} diff --git a/eval_test.go b/eval_test.go new file mode 100644 index 0000000..ac9dc28 --- /dev/null +++ b/eval_test.go @@ -0,0 +1,59 @@ +package tengo_test + +import ( + "context" + "testing" + + "github.com/d5/tengo/v2" + "github.com/d5/tengo/v2/require" +) + +func TestEval(t *testing.T) { + eval := func( + expr string, + params map[string]interface{}, + expected interface{}, + ) { + ctx := context.Background() + actual, err := tengo.Eval(ctx, expr, params) + require.NoError(t, err) + require.Equal(t, expected, actual) + } + + eval(`undefined`, nil, nil) + eval(`1`, nil, int64(1)) + eval(`19 + 23`, nil, int64(42)) + eval(`"foo bar"`, nil, "foo bar") + eval(`[1, 2, 3][1]`, nil, int64(2)) + + eval( + `5 + p`, + map[string]interface{}{ + "p": 7, + }, + int64(12), + ) + eval( + `"seven is " + p`, + map[string]interface{}{ + "p": 7, + }, + "seven is 7", + ) + eval( + `"" + a + b`, + map[string]interface{}{ + "a": 7, + "b": " is seven", + }, + "7 is seven", + ) + + eval( + `a ? "success" : "fail"`, + map[string]interface{}{ + "a": 1, + }, + "success", + ) +}