// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // All rights reserved. // parser_test.go package expr import ( "errors" "fmt" "strings" "testing" ) type testContext struct { store map[string]any } func newTestContext() *testContext { return &testContext{ store: make(map[string]any), } } func (ctx *testContext) GetValue(varName string) (v any, exists bool) { v, exists = ctx.store[varName] return } func (ctx *testContext) SetValue(varName string, value any) { ctx.store[varName] = value } func (ctx *testContext) GetFuncInfo(name string) (f exprFunc) { if name == "ADD" { f = &testAddFunc{} } return } func (ctx *testContext) Call(name string, args []any) (result any, err error) { if name == "ADD" { funcObj := &testAddFunc{} result, err = funcObj.Invoke(ctx, args) } else { err = fmt.Errorf("unknown function %q", name) } return } type testAddFunc struct { } func (self *testAddFunc) Name() string { return "ADD" } func (self *testAddFunc) MinArgs() int { return 1 } func (self *testAddFunc) MaxArgs() int { return -1 } func (self *testAddFunc) Invoke(ctx exprContext, args []any) (result any, err error) { var sumAsFloat = false var floatSum float64 = 0.0 var intSum int64 = 0 for i, v := range args { if !isNumber(v) { err = fmt.Errorf("add(): param nr %d has wrong type %T, number expected", i+1, v) break } if !sumAsFloat && isFloat(v) { sumAsFloat = true floatSum = float64(intSum) } if sumAsFloat { floatSum += numAsFloat(v) } else { iv, _ := v.(int64) intSum += iv } } if err == nil { if sumAsFloat { result = floatSum } else { result = intSum } } return } func TestParser(t *testing.T) { type inputType struct { source string wantResult any wantErr error } ctx := newTestContext() ctx.SetValue("var1", int64(123)) ctx.SetValue("var2", "abc") inputs := []inputType{ {`123`, int64(123), nil}, {`1.`, float64(1.0), nil}, {`1.E-2`, float64(0.01), nil}, {`1E2`, float64(100), nil}, /* 1 */ {`1+/*5*/2`, int64(3), nil}, /* 2 */ {`3 == 4`, false, nil}, /* 3 */ {`3 != 4`, true, nil}, /* 4 */ {`4 == (3-1)*(10/5)`, true, nil}, /* 5 */ {`3 <= 4`, true, nil}, /* 6 */ {`3 < 4`, true, nil}, /* 7 */ {`4 < 3`, false, nil}, /* 8 */ {`1+5 < 4`, false, nil}, /* 9 */ {`3 > 4`, false, nil}, /* 10 */ {`4 >= 4`, true, nil}, /* 11 */ {`4 > 3`, true, nil}, /* 12 */ {`1+5 > 4`, true, nil}, /* 13 */ {`true`, true, nil}, /* 14 */ {`not true`, false, nil}, /* 15 */ {`true and false`, false, nil}, /* 16 */ {`true or false`, true, nil}, /* 17 */ {`"uno" + "due"`, `unodue`, nil}, /* 18 */ {`"uno" + 2`, `uno2`, nil}, /* 19 */ {`"uno" + (2+1)`, `uno3`, nil}, /* 20 */ {`"uno" * (2+1)`, `unounouno`, nil}, /* 21 */ {"1", int64(1), nil}, /* 22 */ {"1.5", float64(1.5), nil}, /* 23 */ {"1.5*2", float64(3.0), nil}, /* 24 */ {"+1", int64(1), nil}, /* 25 */ {"-1", int64(-1), nil}, /* 26 */ {"435 + 105 * 2 - 1", int64(644), nil}, /* 27 */ {"200 / 2 - 1", int64(99), nil}, /* 28 */ {"(1+1)", int64(2), nil}, /* 29 */ {"-(1+1)", int64(-2), nil}, /* 30 */ {"-(-2+1)", int64(1), nil}, /* 31 */ {"(1+1)*5", int64(10), nil}, /* 32 */ {"200 / (1+1) - 1", int64(99), nil}, /* 33 */ {`add(1,2,3)`, int64(6), nil}, /* 34 */ {`mul(1,2,3)`, nil, errors.New(`unknown function "MUL"`)}, /* 35 */ {`add(1+4,3+2,5*(3-2))`, int64(15), nil}, /* 36 */ {`add(add(1+4),3+2,5*(3-2))`, int64(15), nil}, /* 37 */ {`add(add(1+4),/*3+2,*/5*(3-2))`, int64(10), nil}, /* 38 */ {`1+(1+(1+(1)+1)+1)+1`, int64(7), nil}, /* 39 */ {`(((1)))`, int64(1), nil}, /* 40 */ {`"uno_" + var2`, `uno_abc`, nil}, /* 41 */ {`0 || 0.0 && "hello"`, false, nil}, /* 42 */ {`"s" + true`, nil, errors.New(`left operand 's' [string] is not compatible with right operand 'true' [bool] with respect to operator "+"`)}, /* 43 */ {`+false`, nil, errors.New(`prefix/postfix operator "+" do not support operand 'false' [bool]`)}, /* 44 */ {`false // very simple expression`, false, nil}, /* 45 */ {`1 + // Missing right operator`, nil, errors.New(`infix operator "+" requires two operands, got 1`)}, /* 46 */ {"", nil, errors.New(`empty expression`)}, /* 47 */ {"4!", int64(24), nil}, /* 48 */ {"(-4)!", nil, errors.New(`factorial of a negative integer (-4) is not allowed`)}, /* 49 */ {"-4!", int64(-24), nil}, /* 50 */ {"1.5 < 7", true, nil}, /* 51 */ {"1.5 > 7", false, nil}, /* 52 */ {"1.5 <= 7", true, nil}, /* 53 */ {"1.5 >= 7", false, nil}, /* 54 */ {"1.5 != 7", true, nil}, /* 55 */ {"1.5 == 7", false, nil}, /* 56 */ {`"1.5" < "7"`, true, nil}, /* 57 */ {`"1.5" > "7"`, false, nil}, /* 58 */ {`"1.5" == "7"`, false, nil}, /* 59 */ {`"1.5" != "7"`, true, nil}, /* 60 */ {"1.5 < ", nil, errors.New(`infix operator "<" requires two operands, got 1`)}, /* 61 */ {"1.5 > ", nil, errors.New(`infix operator ">" requires two operands, got 1`)}, /* 62 */ {"1.5 <= ", nil, errors.New(`infix operator "<=" requires two operands, got 1`)}, /* 63 */ {"1.5 >= ", nil, errors.New(`infix operator ">=" requires two operands, got 1`)}, /* 64 */ {"1.5 != ", nil, errors.New(`infix operator "!=" requires two operands, got 1`)}, /* 65 */ {"1.5 == ", nil, errors.New(`infix operator "==" requires two operands, got 1`)}, /* 66 */ {`"1.5" < `, nil, errors.New(`infix operator "<" requires two operands, got 1`)}, /* 67 */ {`"1.5" > `, nil, errors.New(`infix operator ">" requires two operands, got 1`)}, /* 68 */ {`"1.5" == `, nil, errors.New(`infix operator "==" requires two operands, got 1`)}, /* 69 */ {`"1.5" != `, nil, errors.New(`infix operator "!=" requires two operands, got 1`)}, /* 70 */ {"+1.5", float64(1.5), nil}, /* 71 */ {"+", nil, errors.New(`prefix operator "+" requires one operand`)}, /* 72 */ {"4 / 0", nil, errors.New(`division by zero`)}, /* 73 */ {"4.0 / 0", nil, errors.New(`division by zero`)}, /* 74 */ {"4.0 / \n2", float64(2.0), nil}, } parser := NewParser(ctx) for i, input := range inputs { var expr *ast var gotResult any var gotErr error if input.wantErr == nil { t.Log(fmt.Sprintf("[+]Test nr %2d -- %q", i+1, input.source)) } else { t.Log(fmt.Sprintf("[-]Test nr %2d -- %q", i+1, input.source)) } r := strings.NewReader(input.source) scanner := NewScanner(r, DefaultTranslations()) if expr, gotErr = parser.parse(scanner); gotErr == nil { gotResult, gotErr = expr.eval(ctx) } if gotResult != input.wantResult { t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult) } if gotErr != input.wantErr { if input.wantErr == nil || gotErr == nil || (gotErr.Error() != input.wantErr.Error()) { t.Errorf("%d: %q -> err = <%v>, want <%v>", i+1, input.source, gotErr, input.wantErr) } } } }