mirror of
https://github.com/hanzoai/expr.git
synced 2026-09-24 16:58:58 +00:00
Add builtin functions
This commit is contained in:
@@ -1,8 +1,14 @@
|
||||
package builtin
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/antonmedv/expr/vm/runtime"
|
||||
)
|
||||
|
||||
type Function struct {
|
||||
@@ -97,4 +103,321 @@ var Functions = []*Function{
|
||||
Builtin1: String,
|
||||
Types: types(new(func(any interface{}) string)),
|
||||
},
|
||||
{
|
||||
Name: "trim",
|
||||
Func: func(args ...interface{}) (interface{}, error) {
|
||||
if len(args) == 1 {
|
||||
return strings.TrimSpace(args[0].(string)), nil
|
||||
} else if len(args) == 2 {
|
||||
return strings.Trim(args[0].(string), args[1].(string)), nil
|
||||
} else {
|
||||
return nil, fmt.Errorf("invalid number of arguments for trim (expected 1 or 2, got %d)", len(args))
|
||||
}
|
||||
},
|
||||
Types: types(
|
||||
strings.TrimSpace,
|
||||
strings.Trim,
|
||||
),
|
||||
},
|
||||
{
|
||||
Name: "trimPrefix",
|
||||
Func: func(args ...interface{}) (interface{}, error) {
|
||||
return strings.TrimPrefix(args[0].(string), args[1].(string)), nil
|
||||
},
|
||||
Types: types(strings.TrimPrefix),
|
||||
},
|
||||
{
|
||||
Name: "trimSuffix",
|
||||
Func: func(args ...interface{}) (interface{}, error) {
|
||||
return strings.TrimSuffix(args[0].(string), args[1].(string)), nil
|
||||
},
|
||||
Types: types(strings.TrimSuffix),
|
||||
},
|
||||
{
|
||||
Name: "upper",
|
||||
Builtin1: func(arg interface{}) interface{} {
|
||||
return strings.ToUpper(arg.(string))
|
||||
},
|
||||
Types: types(strings.ToUpper),
|
||||
},
|
||||
{
|
||||
Name: "lower",
|
||||
Builtin1: func(arg interface{}) interface{} {
|
||||
return strings.ToLower(arg.(string))
|
||||
},
|
||||
Types: types(strings.ToLower),
|
||||
},
|
||||
{
|
||||
Name: "split",
|
||||
Func: func(args ...interface{}) (interface{}, error) {
|
||||
return strings.Split(args[0].(string), args[1].(string)), nil
|
||||
},
|
||||
Types: types(strings.Split),
|
||||
},
|
||||
{
|
||||
Name: "splitN",
|
||||
Func: func(args ...interface{}) (interface{}, error) {
|
||||
return strings.SplitN(args[0].(string), args[1].(string), runtime.ToInt(args[2])), nil
|
||||
},
|
||||
Types: types(strings.SplitN),
|
||||
},
|
||||
{
|
||||
Name: "splitAfter",
|
||||
Func: func(args ...interface{}) (interface{}, error) {
|
||||
return strings.SplitAfter(args[0].(string), args[1].(string)), nil
|
||||
},
|
||||
Types: types(strings.SplitAfter),
|
||||
},
|
||||
{
|
||||
Name: "splitAfterN",
|
||||
Func: func(args ...interface{}) (interface{}, error) {
|
||||
return strings.SplitAfterN(args[0].(string), args[1].(string), runtime.ToInt(args[2])), nil
|
||||
},
|
||||
Types: types(strings.SplitAfterN),
|
||||
},
|
||||
{
|
||||
Name: "replace",
|
||||
Func: func(args ...interface{}) (interface{}, error) {
|
||||
if len(args) == 4 {
|
||||
return strings.Replace(args[0].(string), args[1].(string), args[2].(string), runtime.ToInt(args[3])), nil
|
||||
} else if len(args) == 3 {
|
||||
return strings.ReplaceAll(args[0].(string), args[1].(string), args[2].(string)), nil
|
||||
} else {
|
||||
return nil, fmt.Errorf("invalid number of arguments for replace (expected 3 or 4, got %d)", len(args))
|
||||
}
|
||||
},
|
||||
Types: types(
|
||||
strings.Replace,
|
||||
strings.ReplaceAll,
|
||||
),
|
||||
},
|
||||
{
|
||||
Name: "repeat",
|
||||
Func: func(args ...interface{}) (interface{}, error) {
|
||||
return strings.Repeat(args[0].(string), runtime.ToInt(args[1])), nil
|
||||
},
|
||||
Types: types(strings.Repeat),
|
||||
},
|
||||
{
|
||||
Name: "join",
|
||||
Func: func(args ...interface{}) (interface{}, error) {
|
||||
glue := ""
|
||||
if len(args) == 2 {
|
||||
glue = args[1].(string)
|
||||
}
|
||||
switch args[0].(type) {
|
||||
case []string:
|
||||
return strings.Join(args[0].([]string), glue), nil
|
||||
case []interface{}:
|
||||
var s []string
|
||||
for _, arg := range args[0].([]interface{}) {
|
||||
s = append(s, arg.(string))
|
||||
}
|
||||
return strings.Join(s, glue), nil
|
||||
}
|
||||
return nil, fmt.Errorf("invalid argument for join (type %s)", reflect.TypeOf(args[0]))
|
||||
},
|
||||
Types: types(
|
||||
strings.Join,
|
||||
new(func([]interface{}, string) string),
|
||||
new(func([]string) string),
|
||||
new(func([]interface{}) string),
|
||||
),
|
||||
},
|
||||
{
|
||||
Name: "indexOf",
|
||||
Func: func(args ...interface{}) (interface{}, error) {
|
||||
return strings.Index(args[0].(string), args[1].(string)), nil
|
||||
},
|
||||
Types: types(strings.Index),
|
||||
},
|
||||
{
|
||||
Name: "lastIndexOf",
|
||||
Func: func(args ...interface{}) (interface{}, error) {
|
||||
return strings.LastIndex(args[0].(string), args[1].(string)), nil
|
||||
},
|
||||
Types: types(strings.LastIndex),
|
||||
},
|
||||
{
|
||||
Name: "hasPrefix",
|
||||
Func: func(args ...interface{}) (interface{}, error) {
|
||||
return strings.HasPrefix(args[0].(string), args[1].(string)), nil
|
||||
},
|
||||
Types: types(strings.HasPrefix),
|
||||
},
|
||||
{
|
||||
Name: "hasSuffix",
|
||||
Func: func(args ...interface{}) (interface{}, error) {
|
||||
return strings.HasSuffix(args[0].(string), args[1].(string)), nil
|
||||
},
|
||||
Types: types(strings.HasSuffix),
|
||||
},
|
||||
{
|
||||
Name: "max",
|
||||
Func: Max,
|
||||
Validate: func(args []reflect.Type) (reflect.Type, error) {
|
||||
if len(args) == 0 {
|
||||
return anyType, fmt.Errorf("not enough arguments to call max")
|
||||
}
|
||||
for _, arg := range args {
|
||||
switch kind(arg) {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64:
|
||||
default:
|
||||
return anyType, fmt.Errorf("invalid argument for max (type %s)", arg)
|
||||
}
|
||||
}
|
||||
return args[0], nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "min",
|
||||
Func: Min,
|
||||
Validate: func(args []reflect.Type) (reflect.Type, error) {
|
||||
if len(args) == 0 {
|
||||
return anyType, fmt.Errorf("not enough arguments to call min")
|
||||
}
|
||||
for _, arg := range args {
|
||||
switch kind(arg) {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64:
|
||||
default:
|
||||
return anyType, fmt.Errorf("invalid argument for min (type %s)", arg)
|
||||
}
|
||||
}
|
||||
return args[0], nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "toJSON",
|
||||
Func: func(args ...interface{}) (interface{}, error) {
|
||||
b, err := json.MarshalIndent(args[0], "", " ")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return string(b), nil
|
||||
},
|
||||
Types: types(new(func(interface{}) string)),
|
||||
},
|
||||
{
|
||||
Name: "fromJSON",
|
||||
Func: func(args ...interface{}) (interface{}, error) {
|
||||
var v interface{}
|
||||
err := json.Unmarshal([]byte(args[0].(string)), &v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return v, nil
|
||||
},
|
||||
Types: types(new(func(string) interface{})),
|
||||
},
|
||||
{
|
||||
Name: "toBase64",
|
||||
Func: func(args ...interface{}) (interface{}, error) {
|
||||
return base64.StdEncoding.EncodeToString([]byte(args[0].(string))), nil
|
||||
},
|
||||
Types: types(new(func(string) string)),
|
||||
},
|
||||
{
|
||||
Name: "fromBase64",
|
||||
Func: func(args ...interface{}) (interface{}, error) {
|
||||
b, err := base64.StdEncoding.DecodeString(args[0].(string))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return string(b), nil
|
||||
},
|
||||
Types: types(new(func(string) string)),
|
||||
},
|
||||
{
|
||||
Name: "now",
|
||||
Func: func(args ...interface{}) (interface{}, error) {
|
||||
return time.Now(), nil
|
||||
},
|
||||
Types: types(new(func() time.Time)),
|
||||
},
|
||||
{
|
||||
Name: "duration",
|
||||
Func: func(args ...interface{}) (interface{}, error) {
|
||||
return time.ParseDuration(args[0].(string))
|
||||
},
|
||||
Types: types(time.ParseDuration),
|
||||
},
|
||||
{
|
||||
Name: "date",
|
||||
Func: func(args ...interface{}) (interface{}, error) {
|
||||
date := args[0].(string)
|
||||
if len(args) == 2 {
|
||||
layout := args[1].(string)
|
||||
return time.Parse(layout, date)
|
||||
}
|
||||
if len(args) == 3 {
|
||||
layout := args[1].(string)
|
||||
timeZone := args[2].(string)
|
||||
tz, err := time.LoadLocation(timeZone)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t, err := time.ParseInLocation(layout, date, tz)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
layouts := []string{
|
||||
time.DateOnly,
|
||||
time.TimeOnly,
|
||||
time.DateTime,
|
||||
time.RFC3339,
|
||||
time.RFC822,
|
||||
time.RFC850,
|
||||
time.RFC1123,
|
||||
}
|
||||
for _, layout := range layouts {
|
||||
t, err := time.Parse(layout, date)
|
||||
if err == nil {
|
||||
return t, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("invalid date %s", date)
|
||||
},
|
||||
Types: types(
|
||||
new(func(string) time.Time),
|
||||
new(func(string, string) time.Time),
|
||||
new(func(string, string, string) time.Time),
|
||||
),
|
||||
},
|
||||
{
|
||||
Name: "first",
|
||||
Func: func(args ...interface{}) (interface{}, error) {
|
||||
return runtime.Fetch(args[0], 0), nil
|
||||
},
|
||||
Types: types(new(func([]interface{}) interface{})),
|
||||
},
|
||||
{
|
||||
Name: "last",
|
||||
Func: func(args ...interface{}) (interface{}, error) {
|
||||
return runtime.Fetch(args[0], -1), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "get",
|
||||
Func: func(args ...interface{}) (out interface{}, err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
return
|
||||
}
|
||||
}()
|
||||
return runtime.Fetch(args[0], args[1]), nil
|
||||
},
|
||||
Validate: func(args []reflect.Type) (reflect.Type, error) {
|
||||
if len(args) != 2 {
|
||||
return anyType, fmt.Errorf("invalid number of arguments for get (expected 2, got %d)", len(args))
|
||||
}
|
||||
switch kind(args[0]) {
|
||||
case reflect.Map, reflect.Struct, reflect.Slice, reflect.Array, reflect.Interface:
|
||||
return anyType, nil
|
||||
}
|
||||
return anyType, fmt.Errorf("cannot get %s from %s", args[1], args[0])
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
+62
-1
@@ -2,6 +2,7 @@ package builtin_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/antonmedv/expr"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -26,12 +27,63 @@ var tests = []struct {
|
||||
{`string(5)`, "5"},
|
||||
{`string(5.5)`, "5.5"},
|
||||
{`string("5.5")`, "5.5"},
|
||||
{`trim(" foo ")`, "foo"},
|
||||
{`trim("__foo___", "_")`, "foo"},
|
||||
{`trimPrefix("prefix_foo", "prefix_")`, "foo"},
|
||||
{`trimSuffix("foo_suffix", "_suffix")`, "foo"},
|
||||
{`upper("foo")`, "FOO"},
|
||||
{`lower("FOO")`, "foo"},
|
||||
{`split("foo,bar,baz", ",")`, []string{"foo", "bar", "baz"}},
|
||||
{`splitN("foo,bar,baz", ",", 2)`, []string{"foo", "bar,baz"}},
|
||||
{`splitAfter("foo,bar,baz", ",")`, []string{"foo,", "bar,", "baz"}},
|
||||
{`splitAfterN("foo,bar,baz", ",", 2)`, []string{"foo,", "bar,baz"}},
|
||||
{`replace("foo,bar,baz", ",", ";")`, "foo;bar;baz"},
|
||||
{`replace("foo,bar,baz,goo", ",", ";", 2)`, "foo;bar;baz,goo"},
|
||||
{`repeat("foo", 3)`, "foofoofoo"},
|
||||
{`join(ArrayOfString, ",")`, "foo,bar,baz"},
|
||||
{`join(ArrayOfString)`, "foobarbaz"},
|
||||
{`join(["foo", "bar", "baz"], ",")`, "foo,bar,baz"},
|
||||
{`join(["foo", "bar", "baz"])`, "foobarbaz"},
|
||||
{`indexOf("foo,bar,baz", ",")`, 3},
|
||||
{`lastIndexOf("foo,bar,baz", ",")`, 7},
|
||||
{`hasPrefix("foo,bar,baz", "foo")`, true},
|
||||
{`hasSuffix("foo,bar,baz", "baz")`, true},
|
||||
{`max(1, 2, 3)`, 3},
|
||||
{`max(1.5, 2.5, 3.5)`, 3.5},
|
||||
{`min(1, 2, 3)`, 1},
|
||||
{`min(1.5, 2.5, 3.5)`, 1.5},
|
||||
{`toJSON({foo: 1, bar: 2})`, "{\n \"bar\": 2,\n \"foo\": 1\n}"},
|
||||
{`fromJSON("[1, 2, 3]")`, []interface{}{1.0, 2.0, 3.0}},
|
||||
{`toBase64("hello")`, "aGVsbG8="},
|
||||
{`fromBase64("aGVsbG8=")`, "hello"},
|
||||
{`now().Format("2006-01-02T15:04:05Z")`, time.Now().Format("2006-01-02T15:04:05Z")},
|
||||
{`duration("1h")`, time.Hour},
|
||||
{`date("2006-01-02T15:04:05Z")`, time.Date(2006, 1, 2, 15, 4, 5, 0, time.UTC)},
|
||||
{`date("2006.01.02", "2006.01.02")`, time.Date(2006, 1, 2, 0, 0, 0, 0, time.UTC)},
|
||||
{`first(ArrayOfString)`, "foo"},
|
||||
{`first(ArrayOfInt)`, 1},
|
||||
{`first(ArrayOfAny)`, 1},
|
||||
{`last(ArrayOfString)`, "baz"},
|
||||
{`last(ArrayOfInt)`, 3},
|
||||
{`last(ArrayOfAny)`, true},
|
||||
{`get(ArrayOfString, 1)`, "bar"},
|
||||
{`get(ArrayOfString, 99)`, nil},
|
||||
{`get(ArrayOfInt, 1)`, 2},
|
||||
{`get(ArrayOfInt, -1)`, 3},
|
||||
{`get(ArrayOfAny, 1)`, "2"},
|
||||
{`get({foo: 1, bar: 2}, "foo")`, 1},
|
||||
{`get({foo: 1, bar: 2}, "unknown")`, nil},
|
||||
}
|
||||
|
||||
func TestBuiltin(t *testing.T) {
|
||||
env := map[string]interface{}{
|
||||
"ArrayOfString": []string{"foo", "bar", "baz"},
|
||||
"ArrayOfInt": []int{1, 2, 3},
|
||||
"ArrayOfAny": []interface{}{1, "2", true},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.input, func(t *testing.T) {
|
||||
out, err := expr.Eval(test.input, nil)
|
||||
out, err := expr.Eval(test.input, env)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, test.want, out)
|
||||
})
|
||||
@@ -52,6 +104,15 @@ var errorTests = []struct {
|
||||
{`float()`, `invalid number of arguments for float (expected 1, got 0)`},
|
||||
{`float(1, 2)`, `invalid number of arguments for float (expected 1, got 2)`},
|
||||
{`string(1, 2)`, `too many arguments to call string`},
|
||||
{`trim()`, `not enough arguments to call trim`},
|
||||
{`max()`, `not enough arguments to call max`},
|
||||
{`max(1, "2")`, `invalid argument for max (type string)`},
|
||||
{`min()`, `not enough arguments to call min`},
|
||||
{`min(1, "2")`, `invalid argument for min (type string)`},
|
||||
{`duration("error")`, `invalid duration`},
|
||||
{`date("error")`, `invalid date`},
|
||||
{`get()`, `invalid number of arguments for get (expected 2, got 0)`},
|
||||
{`get(1, 2)`, `cannot get int from int`},
|
||||
}
|
||||
|
||||
func TestBuiltinErrors(t *testing.T) {
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
||||
"github.com/antonmedv/expr/vm/runtime"
|
||||
)
|
||||
|
||||
func Len(x interface{}) interface{} {
|
||||
@@ -171,3 +173,23 @@ func Float(x interface{}) interface{} {
|
||||
func String(arg interface{}) interface{} {
|
||||
return fmt.Sprintf("%v", arg)
|
||||
}
|
||||
|
||||
func Max(args ...interface{}) (interface{}, error) {
|
||||
var max interface{}
|
||||
for _, arg := range args {
|
||||
if max == nil || runtime.Less(max, arg) {
|
||||
max = arg
|
||||
}
|
||||
}
|
||||
return max, nil
|
||||
}
|
||||
|
||||
func Min(args ...interface{}) (interface{}, error) {
|
||||
var min interface{}
|
||||
for _, arg := range args {
|
||||
if min == nil || runtime.More(min, arg) {
|
||||
min = arg
|
||||
}
|
||||
}
|
||||
return min, nil
|
||||
}
|
||||
|
||||
+30
-19
@@ -127,16 +127,34 @@ func (c *compiler) addConstant(constant interface{}) int {
|
||||
return p
|
||||
}
|
||||
|
||||
func (c *compiler) addFunction(node *ast.CallNode) int {
|
||||
if node.Func == nil {
|
||||
// emitFunction adds builtin.Function.Func to the program.Functions and emits call opcode.
|
||||
func (c *compiler) emitFunction(fn *builtin.Function, argsLen int) {
|
||||
switch argsLen {
|
||||
case 0:
|
||||
c.emit(OpCall0, c.addFunction(fn))
|
||||
case 1:
|
||||
c.emit(OpCall1, c.addFunction(fn))
|
||||
case 2:
|
||||
c.emit(OpCall2, c.addFunction(fn))
|
||||
case 3:
|
||||
c.emit(OpCall3, c.addFunction(fn))
|
||||
default:
|
||||
c.emit(OpLoadFunc, c.addFunction(fn))
|
||||
c.emit(OpCallN, argsLen)
|
||||
}
|
||||
}
|
||||
|
||||
// addFunction adds builtin.Function.Func to the program.Functions and returns its index.
|
||||
func (c *compiler) addFunction(fn *builtin.Function) int {
|
||||
if fn == nil {
|
||||
panic("function is nil")
|
||||
}
|
||||
if p, ok := c.functionsIndex[node.Func.Name]; ok {
|
||||
if p, ok := c.functionsIndex[fn.Name]; ok {
|
||||
return p
|
||||
}
|
||||
p := len(c.functions)
|
||||
c.functions = append(c.functions, node.Func.Func)
|
||||
c.functionsIndex[node.Func.Name] = p
|
||||
c.functions = append(c.functions, fn.Func)
|
||||
c.functionsIndex[fn.Name] = p
|
||||
return p
|
||||
}
|
||||
|
||||
@@ -564,19 +582,7 @@ func (c *compiler) CallNode(node *ast.CallNode) {
|
||||
c.compile(arg)
|
||||
}
|
||||
if node.Func != nil {
|
||||
switch len(node.Arguments) {
|
||||
case 0:
|
||||
c.emit(OpCall0, c.addFunction(node))
|
||||
case 1:
|
||||
c.emit(OpCall1, c.addFunction(node))
|
||||
case 2:
|
||||
c.emit(OpCall2, c.addFunction(node))
|
||||
case 3:
|
||||
c.emit(OpCall3, c.addFunction(node))
|
||||
default:
|
||||
c.emit(OpLoadFunc, c.addFunction(node))
|
||||
c.emit(OpCallN, len(node.Arguments))
|
||||
}
|
||||
c.emitFunction(node.Func, len(node.Arguments))
|
||||
return
|
||||
}
|
||||
c.compile(node.Callee)
|
||||
@@ -691,10 +697,15 @@ func (c *compiler) BuiltinNode(node *ast.BuiltinNode) {
|
||||
}
|
||||
|
||||
if id, ok := builtin.Index[node.Name]; ok {
|
||||
f := builtin.Functions[id]
|
||||
for _, arg := range node.Arguments {
|
||||
c.compile(arg)
|
||||
}
|
||||
c.emit(OpCallBuiltin, id)
|
||||
if f.Builtin1 != nil {
|
||||
c.emit(OpCallBuiltin1, id)
|
||||
} else if f.Func != nil {
|
||||
c.emitFunction(f, len(node.Arguments))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -119,15 +119,15 @@ func TestOptimize_const_range(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestOptimize_const_expr(t *testing.T) {
|
||||
tree, err := parser.Parse(`upper("hello")`)
|
||||
tree, err := parser.Parse(`toUpper("hello")`)
|
||||
require.NoError(t, err)
|
||||
|
||||
env := map[string]interface{}{
|
||||
"upper": strings.ToUpper,
|
||||
"toUpper": strings.ToUpper,
|
||||
}
|
||||
|
||||
config := conf.New(env)
|
||||
config.ConstExpr("upper")
|
||||
config.ConstExpr("toUpper")
|
||||
|
||||
err = optimizer.Optimize(&tree.Node, config)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/antonmedv/expr"
|
||||
"github.com/antonmedv/expr/checker"
|
||||
"github.com/antonmedv/expr/compiler"
|
||||
"github.com/antonmedv/expr/conf"
|
||||
@@ -100,3 +101,45 @@ func TestTime(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTime_duration(t *testing.T) {
|
||||
env := map[string]interface{}{
|
||||
"date": time.Date(2000, time.Month(1), 1, 0, 0, 0, 0, time.UTC),
|
||||
}
|
||||
program, err := expr.Compile(`now() - duration("1h") < now() && date + duration("24h") < now()`, expr.Env(env))
|
||||
require.NoError(t, err)
|
||||
|
||||
output, err := expr.Run(program, env)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, output)
|
||||
}
|
||||
|
||||
func TestTime_date(t *testing.T) {
|
||||
var tests = []struct {
|
||||
input string
|
||||
want time.Time
|
||||
}{
|
||||
{
|
||||
`'2017-10-23' | date()`,
|
||||
time.Date(2017, 10, 23, 0, 0, 0, 0, time.UTC),
|
||||
},
|
||||
{
|
||||
`'24.11.1987 20:30' | date("02.01.2006 15:04", "Europe/Zurich")`,
|
||||
time.Date(1987, 11, 24, 20, 30, 0, 0, time.FixedZone("Europe/Zurich", 3600)),
|
||||
},
|
||||
{
|
||||
`'24.11.1987 20:30 MSK' | date("02.01.2006 15:04 MST", "Europe/Zurich")`,
|
||||
time.Date(1987, 11, 24, 20, 30, 0, 0, time.FixedZone("MSK", 0)),
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.input, func(t *testing.T) {
|
||||
program, err := expr.Compile(test.input)
|
||||
require.NoError(t, err)
|
||||
|
||||
output, err := expr.Run(program, nil)
|
||||
require.NoError(t, err)
|
||||
require.Truef(t, test.want.Equal(output.(time.Time)), "want %v, got %v", test.want, output)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -57,7 +57,7 @@ const (
|
||||
OpCallN
|
||||
OpCallFast
|
||||
OpCallTyped
|
||||
OpCallBuiltin
|
||||
OpCallBuiltin1
|
||||
OpArray
|
||||
OpMap
|
||||
OpLen
|
||||
|
||||
+2
-2
@@ -232,8 +232,8 @@ func (program *Program) Disassemble() string {
|
||||
signature := reflect.TypeOf(FuncTypes[arg]).Elem().String()
|
||||
_, _ = fmt.Fprintf(w, "%v\t%v\t<%v>\t%v\n", pp, "OpCallTyped", arg, signature)
|
||||
|
||||
case OpCallBuiltin:
|
||||
builtin("OpCallBuiltin")
|
||||
case OpCallBuiltin1:
|
||||
builtin("OpCallBuiltin1")
|
||||
|
||||
case OpArray:
|
||||
code("OpArray")
|
||||
|
||||
@@ -264,7 +264,7 @@ func In(needle interface{}, array interface{}) bool {
|
||||
panic(fmt.Sprintf(`operator "in"" not defined on %T`, array))
|
||||
}
|
||||
|
||||
func Len(a interface{}) interface{} {
|
||||
func Len(a interface{}) int {
|
||||
v := reflect.ValueOf(a)
|
||||
switch v.Kind() {
|
||||
case reflect.Array, reflect.Slice, reflect.Map, reflect.String:
|
||||
|
||||
Reference in New Issue
Block a user