mirror of
https://github.com/romychs/Ocean-240.2-Emulator.git
synced 2026-04-21 11:03:21 +03:00
720 lines
17 KiB
Go
720 lines
17 KiB
Go
package gval
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/shopspring/decimal"
|
|
)
|
|
|
|
func TestParameterized(t *testing.T) {
|
|
testEvaluate(
|
|
[]evaluationTest{
|
|
{
|
|
name: "Single parameter modified by constant",
|
|
expression: "foo + 2",
|
|
parameter: map[string]interface{}{
|
|
"foo": 2.0,
|
|
},
|
|
want: 4.0,
|
|
},
|
|
{
|
|
|
|
name: "Single parameter modified by variable",
|
|
expression: "foo * bar",
|
|
parameter: map[string]interface{}{
|
|
"foo": 5.0,
|
|
"bar": 2.0,
|
|
},
|
|
want: 10.0,
|
|
},
|
|
{
|
|
|
|
name: "Single parameter modified by variable",
|
|
expression: `foo["hey"] * bar[1]`,
|
|
parameter: map[string]interface{}{
|
|
"foo": map[string]interface{}{"hey": 5.0},
|
|
"bar": []interface{}{7., 2.0},
|
|
},
|
|
want: 10.0,
|
|
},
|
|
{
|
|
|
|
name: "Multiple multiplications of the same parameter",
|
|
expression: "foo * foo * foo",
|
|
parameter: map[string]interface{}{
|
|
"foo": 10.0,
|
|
},
|
|
want: 1000.0,
|
|
},
|
|
{
|
|
|
|
name: "Multiple additions of the same parameter",
|
|
expression: "foo + foo + foo",
|
|
parameter: map[string]interface{}{
|
|
"foo": 10.0,
|
|
},
|
|
want: 30.0,
|
|
},
|
|
{
|
|
name: "NoSpaceOperator",
|
|
expression: "true&&name",
|
|
parameter: map[string]interface{}{
|
|
"name": true,
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
|
|
name: "Parameter name sensitivity",
|
|
expression: "foo + FoO + FOO",
|
|
parameter: map[string]interface{}{
|
|
"foo": 8.0,
|
|
"FoO": 4.0,
|
|
"FOO": 2.0,
|
|
},
|
|
want: 14.0,
|
|
},
|
|
{
|
|
|
|
name: "Sign prefix comparison against prefixed variable",
|
|
expression: "-1 < -foo",
|
|
parameter: map[string]interface{}{"foo": -8.0},
|
|
want: true,
|
|
},
|
|
{
|
|
|
|
name: "Fixed-point parameter",
|
|
expression: "foo > 1",
|
|
parameter: map[string]interface{}{"foo": 2},
|
|
want: true,
|
|
},
|
|
{
|
|
|
|
name: "Modifier after closing clause",
|
|
expression: "(2 + 2) + 2 == 6",
|
|
want: true,
|
|
},
|
|
{
|
|
|
|
name: "Comparator after closing clause",
|
|
expression: "(2 + 2) >= 4",
|
|
want: true,
|
|
},
|
|
{
|
|
|
|
name: "Two-boolean logical operation (for issue #8)",
|
|
expression: "(foo == true) || (bar == true)",
|
|
parameter: map[string]interface{}{
|
|
"foo": true,
|
|
"bar": false,
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
|
|
name: "Two-variable integer logical operation (for issue #8)",
|
|
expression: "foo > 10 && bar > 10",
|
|
parameter: map[string]interface{}{
|
|
"foo": 1,
|
|
"bar": 11,
|
|
},
|
|
want: false,
|
|
},
|
|
{
|
|
|
|
name: "Regex against right-hand parameter",
|
|
expression: `"foobar" =~ foo`,
|
|
parameter: map[string]interface{}{
|
|
"foo": "obar",
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
|
|
name: "Not-regex against right-hand parameter",
|
|
expression: `"foobar" !~ foo`,
|
|
parameter: map[string]interface{}{
|
|
"foo": "baz",
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
|
|
name: "Regex against two parameter",
|
|
expression: `foo =~ bar`,
|
|
parameter: map[string]interface{}{
|
|
"foo": "foobar",
|
|
"bar": "oba",
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
|
|
name: "Not-regex against two parameter",
|
|
expression: "foo !~ bar",
|
|
parameter: map[string]interface{}{
|
|
"foo": "foobar",
|
|
"bar": "baz",
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
|
|
name: "Pre-compiled regex",
|
|
expression: "foo =~ bar",
|
|
parameter: map[string]interface{}{
|
|
"foo": "foobar",
|
|
"bar": regexp.MustCompile("[fF][oO]+"),
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
|
|
name: "Pre-compiled not-regex",
|
|
expression: "foo !~ bar",
|
|
parameter: map[string]interface{}{
|
|
"foo": "foobar",
|
|
"bar": regexp.MustCompile("[fF][oO]+"),
|
|
},
|
|
want: false,
|
|
},
|
|
{
|
|
|
|
name: "Single boolean parameter",
|
|
expression: "commission ? 10",
|
|
parameter: map[string]interface{}{
|
|
"commission": true},
|
|
want: 10.0,
|
|
},
|
|
{
|
|
|
|
name: "True comparator with a parameter",
|
|
expression: `partner == "amazon" ? 10`,
|
|
parameter: map[string]interface{}{
|
|
"partner": "amazon"},
|
|
want: 10.0,
|
|
},
|
|
{
|
|
|
|
name: "False comparator with a parameter",
|
|
expression: `partner == "amazon" ? 10`,
|
|
parameter: map[string]interface{}{
|
|
"partner": "ebay"},
|
|
want: nil,
|
|
},
|
|
{
|
|
|
|
name: "True comparator with multiple parameters",
|
|
expression: "theft && period == 24 ? 60",
|
|
parameter: map[string]interface{}{
|
|
"theft": true,
|
|
"period": 24,
|
|
},
|
|
want: 60.0,
|
|
},
|
|
{
|
|
|
|
name: "False comparator with multiple parameters",
|
|
expression: "theft && period == 24 ? 60",
|
|
parameter: map[string]interface{}{
|
|
"theft": false,
|
|
"period": 24,
|
|
},
|
|
want: nil,
|
|
},
|
|
{
|
|
|
|
name: "String concat with single string parameter",
|
|
expression: `foo + "bar"`,
|
|
parameter: map[string]interface{}{
|
|
"foo": "baz"},
|
|
want: "bazbar",
|
|
},
|
|
{
|
|
|
|
name: "String concat with multiple string parameter",
|
|
expression: "foo + bar",
|
|
parameter: map[string]interface{}{
|
|
"foo": "baz",
|
|
"bar": "quux",
|
|
},
|
|
want: "bazquux",
|
|
},
|
|
{
|
|
|
|
name: "String concat with float parameter",
|
|
expression: "foo + bar",
|
|
parameter: map[string]interface{}{
|
|
"foo": "baz",
|
|
"bar": 123.0,
|
|
},
|
|
want: "baz123",
|
|
},
|
|
{
|
|
|
|
name: "Mixed multiple string concat",
|
|
expression: `foo + 123 + "bar" + true`,
|
|
parameter: map[string]interface{}{"foo": "baz"},
|
|
want: "baz123bartrue",
|
|
},
|
|
{
|
|
|
|
name: "Integer width spectrum",
|
|
expression: "uint8 + uint16 + uint32 + uint64 + int8 + int16 + int32 + int64",
|
|
parameter: map[string]interface{}{
|
|
"uint8": uint8(0),
|
|
"uint16": uint16(0),
|
|
"uint32": uint32(0),
|
|
"uint64": uint64(0),
|
|
"int8": int8(0),
|
|
"int16": int16(0),
|
|
"int32": int32(0),
|
|
"int64": int64(0),
|
|
},
|
|
want: 0.0,
|
|
},
|
|
{
|
|
|
|
name: "Null coalesce right",
|
|
expression: "foo ?? 1.0",
|
|
parameter: map[string]interface{}{"foo": nil},
|
|
want: 1.0,
|
|
},
|
|
{
|
|
|
|
name: "Multiple comparator/logical operators (#30)",
|
|
expression: "(foo >= 2887057408 && foo <= 2887122943) || (foo >= 168100864 && foo <= 168118271)",
|
|
parameter: map[string]interface{}{"foo": 2887057409},
|
|
want: true,
|
|
},
|
|
{
|
|
|
|
name: "Multiple comparator/logical operators, opposite order (#30)",
|
|
expression: "(foo >= 168100864 && foo <= 168118271) || (foo >= 2887057408 && foo <= 2887122943)",
|
|
parameter: map[string]interface{}{"foo": 2887057409},
|
|
want: true,
|
|
},
|
|
{
|
|
|
|
name: "Multiple comparator/logical operators, small value (#30)",
|
|
expression: "(foo >= 2887057408 && foo <= 2887122943) || (foo >= 168100864 && foo <= 168118271)",
|
|
parameter: map[string]interface{}{"foo": 168100865},
|
|
want: true,
|
|
},
|
|
{
|
|
|
|
name: "Multiple comparator/logical operators, small value, opposite order (#30)",
|
|
expression: "(foo >= 168100864 && foo <= 168118271) || (foo >= 2887057408 && foo <= 2887122943)",
|
|
parameter: map[string]interface{}{"foo": 168100865},
|
|
want: true,
|
|
},
|
|
{
|
|
|
|
name: "Incomparable array equality comparison",
|
|
expression: "arr == arr",
|
|
parameter: map[string]interface{}{"arr": []int{0, 0, 0}},
|
|
want: true,
|
|
},
|
|
{
|
|
|
|
name: "Incomparable array not-equality comparison",
|
|
expression: "arr != arr",
|
|
parameter: map[string]interface{}{"arr": []int{0, 0, 0}},
|
|
want: false,
|
|
},
|
|
{
|
|
|
|
name: "Mixed function and parameters",
|
|
expression: "sum(1.2, amount) + name",
|
|
extension: Function("sum", func(arguments ...interface{}) (interface{}, error) {
|
|
sum := 0.0
|
|
for _, v := range arguments {
|
|
sum += v.(float64)
|
|
}
|
|
return sum, nil
|
|
},
|
|
),
|
|
parameter: map[string]interface{}{"amount": .8,
|
|
"name": "awesome",
|
|
},
|
|
|
|
want: "2awesome",
|
|
},
|
|
{
|
|
|
|
name: "Short-circuit OR",
|
|
expression: "true || fail()",
|
|
extension: Function("fail", func(arguments ...interface{}) (interface{}, error) {
|
|
return nil, fmt.Errorf("Did not short-circuit")
|
|
}),
|
|
want: true,
|
|
},
|
|
{
|
|
|
|
name: "Short-circuit AND",
|
|
expression: "false && fail()",
|
|
extension: Function("fail", func(arguments ...interface{}) (interface{}, error) {
|
|
return nil, fmt.Errorf("Did not short-circuit")
|
|
}),
|
|
want: false,
|
|
},
|
|
{
|
|
|
|
name: "Short-circuit ternary",
|
|
expression: "true ? 1 : fail()",
|
|
extension: Function("fail", func(arguments ...interface{}) (interface{}, error) {
|
|
return nil, fmt.Errorf("Did not short-circuit")
|
|
}),
|
|
want: 1.0,
|
|
},
|
|
{
|
|
|
|
name: "Short-circuit coalesce",
|
|
expression: `"foo" ?? fail()`,
|
|
extension: Function("fail", func(arguments ...interface{}) (interface{}, error) {
|
|
return nil, fmt.Errorf("Did not short-circuit")
|
|
}),
|
|
want: "foo",
|
|
},
|
|
{
|
|
|
|
name: "Simple parameter call",
|
|
expression: "foo.String",
|
|
parameter: map[string]interface{}{"foo": foo},
|
|
want: foo.String,
|
|
},
|
|
{
|
|
|
|
name: "Simple parameter function call",
|
|
expression: "foo.Func()",
|
|
parameter: map[string]interface{}{"foo": foo},
|
|
want: "funk",
|
|
},
|
|
{
|
|
|
|
name: "Simple parameter call from pointer",
|
|
expression: "fooptr.String",
|
|
parameter: map[string]interface{}{"fooptr": &foo},
|
|
want: foo.String,
|
|
},
|
|
{
|
|
|
|
name: "Simple parameter function call from pointer",
|
|
expression: "fooptr.Func()",
|
|
parameter: map[string]interface{}{"fooptr": &foo},
|
|
want: "funk",
|
|
},
|
|
{
|
|
|
|
name: "Simple parameter call",
|
|
expression: `foo.String == "hi"`,
|
|
parameter: map[string]interface{}{"foo": foo},
|
|
want: false,
|
|
},
|
|
{
|
|
|
|
name: "Simple parameter call with modifier",
|
|
expression: `foo.String + "hi"`,
|
|
parameter: map[string]interface{}{"foo": foo},
|
|
want: foo.String + "hi",
|
|
},
|
|
{
|
|
|
|
name: "Simple parameter function call, two-arg return",
|
|
expression: `foo.Func2()`,
|
|
parameter: map[string]interface{}{"foo": foo},
|
|
want: "frink",
|
|
},
|
|
{
|
|
|
|
name: "Simple parameter function call, one arg",
|
|
expression: `foo.FuncArgStr("boop")`,
|
|
parameter: map[string]interface{}{"foo": foo},
|
|
want: "boop",
|
|
},
|
|
{
|
|
|
|
name: "Simple parameter function call, one arg",
|
|
expression: `foo.FuncArgStr("boop") + "hi"`,
|
|
parameter: map[string]interface{}{"foo": foo},
|
|
want: "boophi",
|
|
},
|
|
{
|
|
|
|
name: "Nested parameter function call",
|
|
expression: `foo.Nested.Dunk("boop")`,
|
|
parameter: map[string]interface{}{"foo": foo},
|
|
want: "boopdunk",
|
|
},
|
|
{
|
|
|
|
name: "Nested parameter call",
|
|
expression: "foo.Nested.Funk",
|
|
parameter: map[string]interface{}{"foo": foo},
|
|
want: "funkalicious",
|
|
},
|
|
{
|
|
name: "Nested map call",
|
|
expression: `foo.Nested.Map["a"]`,
|
|
parameter: map[string]interface{}{"foo": foo},
|
|
want: 1,
|
|
},
|
|
{
|
|
name: "Nested slice call",
|
|
expression: `foo.Nested.Slice[1]`,
|
|
parameter: map[string]interface{}{"foo": foo},
|
|
want: 2,
|
|
},
|
|
{
|
|
|
|
name: "Parameter call with + modifier",
|
|
expression: "1 + foo.Int",
|
|
parameter: map[string]interface{}{"foo": foo},
|
|
want: 102.0,
|
|
},
|
|
{
|
|
|
|
name: "Parameter string call with + modifier",
|
|
expression: `"woop" + (foo.String)`,
|
|
parameter: map[string]interface{}{"foo": foo},
|
|
want: "woopstring!",
|
|
},
|
|
{
|
|
|
|
name: "Parameter call with && operator",
|
|
expression: "true && foo.BoolFalse",
|
|
parameter: map[string]interface{}{"foo": foo},
|
|
want: false,
|
|
},
|
|
{
|
|
name: "Null coalesce nested parameter",
|
|
expression: "foo.Nil ?? false",
|
|
parameter: map[string]interface{}{"foo": foo},
|
|
want: false,
|
|
},
|
|
{
|
|
name: "input functions",
|
|
expression: "func1() + func2()",
|
|
parameter: map[string]interface{}{
|
|
"func1": func() float64 { return 2000 },
|
|
"func2": func() float64 { return 2001 },
|
|
},
|
|
want: 4001.0,
|
|
},
|
|
{
|
|
name: "input functions",
|
|
expression: "func1(date1) + func2(date2)",
|
|
parameter: map[string]interface{}{
|
|
"date1": func() interface{} {
|
|
y2k, _ := time.Parse("2006", "2000")
|
|
return y2k
|
|
}(),
|
|
"date2": func() interface{} {
|
|
y2k1, _ := time.Parse("2006", "2001")
|
|
return y2k1
|
|
}(),
|
|
},
|
|
extension: NewLanguage(
|
|
Function("func1", func(arguments ...interface{}) (interface{}, error) {
|
|
return float64(arguments[0].(time.Time).Year()), nil
|
|
}),
|
|
Function("func2", func(arguments ...interface{}) (interface{}, error) {
|
|
return float64(arguments[0].(time.Time).Year()), nil
|
|
}),
|
|
),
|
|
want: 4001.0,
|
|
},
|
|
{
|
|
name: "complex64 number as parameter",
|
|
expression: "complex64",
|
|
parameter: map[string]interface{}{
|
|
"complex64": complex64(0),
|
|
"complex128": complex128(0),
|
|
},
|
|
want: complex64(0),
|
|
},
|
|
{
|
|
name: "complex128 number as parameter",
|
|
expression: "complex128",
|
|
parameter: map[string]interface{}{
|
|
"complex64": complex64(0),
|
|
"complex128": complex128(0),
|
|
},
|
|
want: complex128(0),
|
|
},
|
|
{
|
|
name: "coalesce with undefined",
|
|
expression: "fooz ?? foo",
|
|
parameter: map[string]interface{}{
|
|
"foo": "bar",
|
|
},
|
|
want: "bar",
|
|
},
|
|
{
|
|
name: "map[interface{}]interface{}",
|
|
expression: "foo",
|
|
parameter: map[interface{}]interface{}{
|
|
"foo": "bar",
|
|
},
|
|
want: "bar",
|
|
},
|
|
{
|
|
name: "method on pointer type",
|
|
expression: "foo.PointerFunc()",
|
|
parameter: map[string]interface{}{
|
|
"foo": &dummyParameter{},
|
|
},
|
|
want: "point",
|
|
},
|
|
{
|
|
name: "custom selector",
|
|
expression: "hello.world",
|
|
parameter: "!",
|
|
extension: NewLanguage(Base(), VariableSelector(func(path Evaluables) Evaluable {
|
|
return func(c context.Context, v interface{}) (interface{}, error) {
|
|
keys, err := path.EvalStrings(c, v)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return fmt.Sprintf("%s%s", strings.Join(keys, " "), v), nil
|
|
}
|
|
})),
|
|
want: "hello world!",
|
|
},
|
|
{
|
|
name: "map[int]int",
|
|
expression: `a[0] + a[2]`,
|
|
parameter: map[string]interface{}{
|
|
"a": map[int]int{0: 1, 2: 1},
|
|
},
|
|
want: 2.,
|
|
},
|
|
{
|
|
name: "map[int]string",
|
|
expression: `a[0] * a[2]`,
|
|
parameter: map[string]interface{}{
|
|
"a": map[int]string{0: "1", 2: "1"},
|
|
},
|
|
want: 1.,
|
|
},
|
|
{
|
|
name: "coalesce typed nil 0",
|
|
expression: `ProjectID ?? 0`,
|
|
parameter: struct {
|
|
ProjectID *uint
|
|
}{},
|
|
want: 0.,
|
|
},
|
|
{
|
|
name: "coalesce typed nil 99",
|
|
expression: `ProjectID ?? 99`,
|
|
parameter: struct {
|
|
ProjectID *uint
|
|
}{},
|
|
want: 99.,
|
|
},
|
|
{
|
|
name: "operator with typed nil 99",
|
|
expression: `ProjectID + 99`,
|
|
parameter: struct {
|
|
ProjectID *uint
|
|
}{},
|
|
want: "<nil>99",
|
|
},
|
|
{
|
|
name: "operator with typed nil if",
|
|
expression: `Flag ? 1 : 2`,
|
|
parameter: struct {
|
|
Flag *uint
|
|
}{},
|
|
want: 2.,
|
|
},
|
|
{
|
|
name: "Decimal math doesn't experience rounding error",
|
|
expression: "(x * 12.146) - y",
|
|
extension: decimalArithmetic,
|
|
parameter: map[string]interface{}{
|
|
"x": 12.5,
|
|
"y": -5,
|
|
},
|
|
want: decimal.NewFromFloat(156.825),
|
|
equalityFunc: decimalEqualityFunc,
|
|
},
|
|
{
|
|
name: "Decimal logical operators fractional difference",
|
|
expression: "((x * 12.146) - y) > 156.824999999",
|
|
extension: decimalArithmetic,
|
|
parameter: map[string]interface{}{
|
|
"x": 12.5,
|
|
"y": -5,
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "Decimal logical operators whole number difference",
|
|
expression: "((x * 12.146) - y) > 156",
|
|
extension: decimalArithmetic,
|
|
parameter: map[string]interface{}{
|
|
"x": 12.5,
|
|
"y": -5,
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "Decimal logical operators exact decimal match against GT",
|
|
expression: "((x * 12.146) - y) > 156.825",
|
|
extension: decimalArithmetic,
|
|
parameter: map[string]interface{}{
|
|
"x": 12.5,
|
|
"y": -5,
|
|
},
|
|
want: false,
|
|
},
|
|
{
|
|
name: "Decimal logical operators exact equality",
|
|
expression: "((x * 12.146) - y) == 156.825",
|
|
extension: decimalArithmetic,
|
|
parameter: map[string]interface{}{
|
|
"x": 12.5,
|
|
"y": -5,
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "Decimal mixes with string logic with force fail",
|
|
expression: `(((x * 12.146) - y) == 156.825) && a == "test" && !b && b`,
|
|
extension: decimalArithmetic,
|
|
parameter: map[string]interface{}{
|
|
"x": 12.5,
|
|
"y": -5,
|
|
"a": "test",
|
|
"b": false,
|
|
},
|
|
want: false,
|
|
},
|
|
{
|
|
name: "Typed map with function call",
|
|
expression: `foo.MapWithFunc.Sum("a")`,
|
|
parameter: map[string]interface{}{
|
|
"foo": foo,
|
|
},
|
|
want: 3,
|
|
},
|
|
{
|
|
name: "Types slice with function call",
|
|
expression: `foo.SliceWithFunc.Sum("a")`,
|
|
parameter: map[string]interface{}{
|
|
"foo": foo,
|
|
},
|
|
want: 2,
|
|
},
|
|
},
|
|
t,
|
|
)
|
|
}
|