Repository: d5/tengo
Pull request #338 (2021-07-05 08:43:14)

add Eval function


Patch:
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
+}


Test patch:
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",
+	)
+}