diff --git a/arithmetic.go b/arithmetic.go index b7167b5..8f3ed6d 100644 --- a/arithmetic.go +++ b/arithmetic.go @@ -34,25 +34,23 @@ func arithmetic(v, vx, vy *Visitor, binaryExpr *ast.BinaryExpr) { } switch v.options.numericType { + case NumericTypeAuto: + if vx.kind == KindImag || vy.kind == KindImag { + calculateComplex(v, parseComplex(vx.value), parseComplex(vy.value), binaryExpr.Op, binaryExpr.OpPos) + return + } + calculateFloat(v, parseFloat(vx.value), parseFloat(vy.value), binaryExpr.Op) + return case NumericTypeComplex: - calculateComplex(v, vx, vy, binaryExpr) + calculateComplex(v, parseComplex(vx.value), parseComplex(vy.value), binaryExpr.Op, binaryExpr.OpPos) return case NumericTypeFloat: - calculateFloat(v, vx, vy, binaryExpr) + calculateFloat(v, parseFloat(vx.value), parseFloat(vy.value), binaryExpr.Op) return case NumericTypeInt: - calculateInt(v, vx, vy, binaryExpr) - return - } - - // NumericTypeAuto: Auto figure out value type - if vx.kind == KindImag || vy.kind == KindImag { - calculateComplex(v, vx, vy, binaryExpr) + calculateInt(v, parseInt(vx.value), parseInt(vy.value), vy.pos, binaryExpr.Op) return } - - // calculate other types as float64 - calculateFloat(v, vx, vy, binaryExpr) } func newArithmeticNonNumericError(v *Visitor, e ast.Expr) error { @@ -64,12 +62,9 @@ func newArithmeticNonNumericError(v *Visitor, e ast.Expr) error { } } -func calculateComplex(v, vx, vy *Visitor, binaryExpr *ast.BinaryExpr) { +func calculateComplex(v *Visitor, x, y complex128, op token.Token, opPos token.Pos) { v.kind = KindImag - x := parseComplex(vx.value, vx.kind) - y := parseComplex(vy.value, vy.kind) - - switch binaryExpr.Op { + switch op { case token.ADD: v.value = x + y case token.SUB: @@ -79,22 +74,17 @@ func calculateComplex(v, vx, vy *Visitor, binaryExpr *ast.BinaryExpr) { case token.QUO: v.value = x / y case token.REM: - v.kind = KindIllegal v.err = &SyntaxError{ - Msg: "operator \"" + binaryExpr.Op.String() + "\" is not supported to do arithmetic on complex number", - Pos: int(binaryExpr.OpPos), + Msg: "operator \"" + op.String() + "\" is not supported to do arithmetic on complex number", + Pos: int(opPos), Err: ErrArithmeticOperation, } - return } } -func calculateFloat(v, vx, vy *Visitor, binaryExpr *ast.BinaryExpr) { +func calculateFloat(v *Visitor, x, y float64, op token.Token) { v.kind = KindFloat - x := parseFloat(vx.value, vx.kind) - y := parseFloat(vy.value, vy.kind) - - switch binaryExpr.Op { + switch op { case token.ADD: v.value = x + y case token.SUB: @@ -108,12 +98,9 @@ func calculateFloat(v, vx, vy *Visitor, binaryExpr *ast.BinaryExpr) { } } -func calculateInt(v, vx, vy *Visitor, binaryExpr *ast.BinaryExpr) { +func calculateInt(v *Visitor, x, y int64, yPos int, op token.Token) { v.kind = KindInt - x := parseInt(vx.value, vx.kind) - y := parseInt(vy.value, vy.kind) - - switch binaryExpr.Op { + switch op { case token.ADD: v.value = x + y case token.SUB: @@ -126,10 +113,9 @@ func calculateInt(v, vx, vy *Visitor, binaryExpr *ast.BinaryExpr) { v.value = int64(0) return } - v.kind = KindIllegal v.err = &SyntaxError{ Msg: "could not divide x with zero y, allowIntegerDividedByZero == false", - Pos: vy.pos, + Pos: yPos, Err: ErrIntegerDividedByZero, } return @@ -140,44 +126,38 @@ func calculateInt(v, vx, vy *Visitor, binaryExpr *ast.BinaryExpr) { } } -func parseComplex(v interface{}, kind Kind) complex128 { // kind must be numeric - switch kind { - case KindImag: - v := v.(complex128) - return v - case KindFloat: - v := v.(float64) - return complex(v, 0) - default: // INT: 0xFF, 0b1010, 0o77, 071, 90 - v := v.(int64) - return complex(float64(v), 0) +func parseComplex(value interface{}) complex128 { // kind must be numeric + switch val := value.(type) { + case complex128: + return val + case float64: + return complex(val, 0) + case int64: + return complex(float64(val), 0) } + return 0 } -func parseFloat(v interface{}, kind Kind) float64 { // kind must be numeric - switch kind { - case KindImag: - v := v.(complex128) - return real(v) - case KindFloat: - v := v.(float64) - return v - default: // INT: 0xFF, 0b1010, 0o77, 071, 90 - v := v.(int64) - return float64(v) +func parseFloat(value interface{}) float64 { + switch val := value.(type) { + case complex128: + return real(val) + case float64: + return val + case int64: + return float64(val) } + return 0 } -func parseInt(v interface{}, kind Kind) int64 { // kind must be numeric - switch kind { - case KindImag: - v := v.(complex128) - return int64(real(v)) - case KindFloat: - v := v.(float64) - return int64(v) - default: // INT: 0xFF, 0b1010, 0o77, 071, 90 - v := v.(int64) - return v +func parseInt(value interface{}) int64 { + switch val := value.(type) { + case complex128: + return int64(real(val)) + case float64: + return int64(val) + case int64: + return val } + return 0 } diff --git a/arithmetic_test.go b/arithmetic_test.go index c9d553f..0c68e65 100644 --- a/arithmetic_test.go +++ b/arithmetic_test.go @@ -122,7 +122,7 @@ func TestArithmetic(t *testing.T) { be := &ast.BinaryExpr{Op: tc.op} arithmetic(tc.v, tc.vx, tc.vy, be) if !errors.Is(tc.v.err, tc.expectedErr) { - t.Fatalf("expected err: %s, got: %s", tc.expectedErr, tc.v.err) + t.Fatalf("expected err: %v, got: %v", tc.expectedErr, tc.v.err) } if tc.v.value != tc.expectedValue { t.Fatalf("expected value: %v (%T), got: %v (%T)", tc.expectedValue, tc.expectedValue, @@ -189,9 +189,9 @@ func TestCalculateComplex(t *testing.T) { name := fmt.Sprintf("%v%s%v", tc.vx.value, op, tc.vy.value) t.Run(name, func(t *testing.T) { be := &ast.BinaryExpr{Op: op} - calculateComplex(tc.v, tc.vx, tc.vy, be) + calculateComplex(tc.v, parseComplex(tc.vx.value), parseComplex(tc.vy.value), be.Op, be.OpPos) if !errors.Is(tc.v.err, tc.expectedErrs[i]) { - t.Fatalf("expected err: %s, got: %s", tc.expectedErrs[i], tc.v.err) + t.Fatalf("expected err: %v, got: %v", tc.expectedErrs[i], tc.v.err) } if tc.v.value != tc.expectedValues[i] { t.Fatalf("expected value: %v (%T), got: % (%T)", tc.expectedValues[i], tc.expectedValues[i], @@ -252,9 +252,9 @@ func TestCalculateFloat(t *testing.T) { name := fmt.Sprintf("%v%s%v", tc.vx.value, op, tc.vy.value) t.Run(name, func(t *testing.T) { be := &ast.BinaryExpr{Op: op} - calculateFloat(tc.v, tc.vx, tc.vy, be) + calculateFloat(tc.v, parseFloat(tc.vx.value), parseFloat(tc.vy.value), be.Op) if !errors.Is(tc.v.err, tc.expectedErrs[i]) { - t.Fatalf("expected err: %s, got: %s", tc.expectedErrs[i], tc.v.err) + t.Fatalf("expected err: %v, got: %v", tc.expectedErrs[i], tc.v.err) } if tc.v.value != tc.expectedValues[i] { t.Fatalf("expected value: %v (%T), got: %s (%T)", tc.expectedValues[i], tc.expectedValues[i], @@ -336,9 +336,9 @@ func TestCalculateInt(t *testing.T) { name := fmt.Sprintf("%v%s%v", tc.vx.value, op, tc.vy.value) t.Run(name, func(t *testing.T) { be := &ast.BinaryExpr{Op: op} - calculateInt(tc.v, tc.vx, tc.vy, be) + calculateInt(tc.v, parseInt(tc.vx.value), parseInt(tc.vy.value), tc.vy.pos, be.Op) if !errors.Is(tc.v.err, tc.expectedErrs[i]) { - t.Fatalf("expected err: %s, got: %s", tc.expectedErrs[i], tc.v.err) + t.Fatalf("expected err: %v, got: %v", tc.expectedErrs[i], tc.v.err) } if tc.v.value != tc.expectedValues[i] { t.Fatalf("expected value: %v (%T), got: %v (%T)", tc.expectedValues[i], tc.expectedValues[i], @@ -349,3 +349,18 @@ func TestCalculateInt(t *testing.T) { }) } } + +func TestParseInvalidValue(t *testing.T) { + i64 := parseInt("invalid") + if i64 != 0 { + t.Fatalf("expected 0, got: %v", i64) + } + f64 := parseFloat(true) + if f64 != 0 { + t.Fatalf("expected 0, got: %v", f64) + } + c128 := parseComplex(false) + if c128 != 0 { + t.Fatalf("expected 0, got: %v", c128) + } +} diff --git a/bitwise.go b/bitwise.go index 09ca22a..4b104e2 100644 --- a/bitwise.go +++ b/bitwise.go @@ -22,39 +22,41 @@ import ( ) func bitwise(v, vx, vy *Visitor, binaryExpr *ast.BinaryExpr) { - // No matters what options, having bolean here is invalid. - if vx.kind == KindBoolean { + // numeric guards: + if vx.kind <= numeric_beg || vx.kind >= numeric_end { v.err = newBitwiseNonIntegerError(vx, binaryExpr.X) return } - if vy.kind == KindBoolean { - v.err = newBitwiseNonIntegerError(vx, binaryExpr.Y) + if vy.kind <= numeric_beg || vy.kind >= numeric_end { + v.err = newBitwiseNonIntegerError(vy, binaryExpr.Y) return } + var x, y int64 switch v.options.numericType { + case NumericTypeFloat: + v.err = newBitwiseNonIntegerError(v, binaryExpr) + return + case NumericTypeComplex: + v.err = newBitwiseNonIntegerError(v, binaryExpr) + return + case NumericTypeInt: + x = parseInt(vx.value) + y = parseInt(vy.value) case NumericTypeAuto: - // NumericTypeAuto: check whether both values are represent integers - x := parseFloat(vx.value, vx.kind) - y := parseFloat(vy.value, vy.kind) - - if x != float64(int64(x)) { + var ok bool + x, ok = convertToInt64(vx.value) + if !ok { v.err = newBitwiseNonIntegerError(vx, binaryExpr.X) return } - - if y != float64(int64(y)) { + y, ok = convertToInt64(vy.value) + if !ok { v.err = newBitwiseNonIntegerError(vy, binaryExpr.Y) return } - case NumericTypeFloat, NumericTypeComplex: - v.err = newBitwiseNonIntegerError(vy, binaryExpr.Y) - return } - x := parseInt(vx.value, vx.kind) - y := parseInt(vy.value, vy.kind) - v.kind = KindInt switch binaryExpr.Op { case token.AND: @@ -80,3 +82,15 @@ func newBitwiseNonIntegerError(v *Visitor, e ast.Expr) error { Err: ErrBitwiseOperation, } } + +func convertToInt64(value interface{}) (int64, bool) { + switch val := value.(type) { + case float64: + if float64(int64(val)) == val { // only if it doesn't have decimal. + return int64(val), true + } + case int64: + return val, true + } + return 0, false +} diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..8d90966 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,7 @@ +ignore: + # Example: + # - "path/to/folder" # ignore folders and all its contents + # - "test_*.rb" # wildcards accepted + # - "**/*.py" # glob accepted + # Please specify the rules below: + - "exp" diff --git a/comparison.go b/comparison.go index cd0287b..c9b75e1 100644 --- a/comparison.go +++ b/comparison.go @@ -21,76 +21,91 @@ import ( "github.com/muktihari/expr/internal/conv" ) +// comparison compares visitor X and visitor Y values. Numeric hierarchy will apply: complex128 > float64 > int64. func comparison(v, vx, vy *Visitor, binaryExpr *ast.BinaryExpr) { v.kind = KindBoolean - if vx.kind == KindBoolean && vy.kind == KindBoolean { - compareMustBoolean(v, vx, vy, binaryExpr) - return - } - if vx.kind == KindString && vy.kind == KindString { - compareMustString(v, vx, vy, binaryExpr) - return - } - - // numeric can be compare one another e.g. 0.4 < 1 -> true - // numeric guards: - if vx.kind <= numeric_beg || vx.kind >= numeric_end { - v.err = newComparisonNonNumericError(vx, binaryExpr.X) - return - } - if vy.kind <= numeric_beg || vy.kind >= numeric_end { - v.err = newComparisonNonNumericError(vy, binaryExpr.Y) - return - } - - if vx.kind == KindImag || vy.kind == KindImag { - compareComplex(v, vx, vy, binaryExpr) - return - } - if vx.kind == KindFloat || vy.kind == KindFloat { - compareFloat(v, vx, vy, binaryExpr) - return - } - if vx.kind == KindInt || vy.kind == KindInt { - compareInt(v, vx, vy, binaryExpr) - return + switch x := vx.value.(type) { + case complex128: + // Treat y as complex number since x is a complex number. + switch y := vy.value.(type) { + case complex128: + compareComplex(v, x, y, binaryExpr.Op, binaryExpr.OpPos) + return + case float64: + compareComplex(v, x, complex(y, 0), binaryExpr.Op, binaryExpr.OpPos) + return + case int64: + compareComplex(v, x, complex(float64(y), 0), binaryExpr.Op, binaryExpr.OpPos) + return + } + case float64: + switch y := vy.value.(type) { + case complex128: // Treat x as complex number since y is a complex number. + compareComplex(v, complex(x, 0), y, binaryExpr.Op, binaryExpr.OpPos) + return + case float64: + compareFloat(v, x, y, binaryExpr.Op) + return + case int64: // Treat y as float64 since x is a float64. + compareFloat(v, x, float64(y), binaryExpr.Op) + return + } + case int64: + // Treat x as y's type, since int64 hierarchy is at the bottom. + switch y := vy.value.(type) { + case complex128: + compareComplex(v, complex(float64(x), 0), y, binaryExpr.Op, binaryExpr.OpPos) + return + case float64: + compareFloat(v, float64(x), y, binaryExpr.Op) + return + case int64: + compareInt(v, x, y, binaryExpr) + return + } + case bool: + y, ok := vy.value.(bool) + if ok { + compareBoolean(v, x, y, binaryExpr.Op, binaryExpr.OpPos) + return + } + case string: + y, ok := vy.value.(string) + if ok { + compareString(v, x, y, binaryExpr.Op) + return + } } + v.kind = KindIllegal + v.err = newComparisonNonComparableError(v, binaryExpr) // Catch non-comparable values. } -func newComparisonNonNumericError(v *Visitor, e ast.Expr) error { - s := conv.FormatExpr(e) +func newComparisonNonComparableError(v *Visitor, e ast.Expr) error { return &SyntaxError{ - Msg: "result of \"" + s + "\" is \"" + fmt.Sprintf("%v", v.value) + "\" which is not a number", + Msg: fmt.Sprintf("expression %q is not comparable", conv.FormatExpr(e)), Pos: v.pos, Err: ErrComparisonOperation, } } -func compareMustBoolean(v, vx, vy *Visitor, binaryExpr *ast.BinaryExpr) { - x := vx.value.(bool) - y := vy.value.(bool) - - switch binaryExpr.Op { +func compareBoolean(v *Visitor, x, y bool, op token.Token, opPos token.Pos) { + switch op { case token.EQL: v.value = x == y case token.NEQ: v.value = x != y default: - v.kind = KindIllegal v.err = &SyntaxError{ - Msg: "operator \"" + binaryExpr.Op.String() + "\" is not supported for comparing boolean values", - Pos: int(binaryExpr.OpPos), + Msg: "operator \"" + op.String() + "\" is not supported for comparing boolean values", + Pos: int(opPos), Err: ErrUnsupportedOperator, } return } } -func compareMustString(v, vx, vy *Visitor, binaryExpr *ast.BinaryExpr) { - x := vx.value.(string) - y := vy.value.(string) - - switch binaryExpr.Op { +func compareString(v *Visitor, x, y string, op token.Token) { + switch op { case token.EQL: v.value = x == y case token.NEQ: @@ -107,31 +122,24 @@ func compareMustString(v, vx, vy *Visitor, binaryExpr *ast.BinaryExpr) { } // IEEE 754 says that only NaNs satisfy f != f. -func compareComplex(v, vx, vy *Visitor, binaryExpr *ast.BinaryExpr) { - x := parseComplex(vx.value, vx.kind) - y := parseComplex(vy.value, vy.kind) - - switch binaryExpr.Op { +func compareComplex(v *Visitor, x, y complex128, op token.Token, opPos token.Pos) { + switch op { case token.EQL: v.value = x == y case token.NEQ: v.value = x != y default: - v.kind = KindIllegal v.err = &SyntaxError{ - Msg: "operator \"" + binaryExpr.Op.String() + "\" is not supported for comparing complex numbers", - Pos: int(binaryExpr.OpPos), + Msg: "operator \"" + op.String() + "\" is not supported for comparing complex numbers", + Pos: int(opPos), Err: ErrUnsupportedOperator, } return } } -func compareFloat(v, vx, vy *Visitor, binaryExpr *ast.BinaryExpr) { - x := parseFloat(vx.value, vx.kind) - y := parseFloat(vy.value, vy.kind) - - switch binaryExpr.Op { +func compareFloat(v *Visitor, x, y float64, op token.Token) { + switch op { case token.EQL: v.value = x == y case token.NEQ: @@ -147,10 +155,7 @@ func compareFloat(v, vx, vy *Visitor, binaryExpr *ast.BinaryExpr) { } } -func compareInt(v, vx, vy *Visitor, binaryExpr *ast.BinaryExpr) { - x := parseInt(vx.value, vx.kind) - y := parseInt(vy.value, vy.kind) - +func compareInt(v *Visitor, x, y int64, binaryExpr *ast.BinaryExpr) { switch binaryExpr.Op { case token.EQL: v.value = x == y diff --git a/comparison_test.go b/comparison_test.go index 6c6bc28..4e53553 100644 --- a/comparison_test.go +++ b/comparison_test.go @@ -89,16 +89,52 @@ func TestComparison(t *testing.T) { expectedValues: []interface{}{true, false, false, true, false, true}, expectedErrs: []error{nil, nil, nil, nil, nil, nil}, }, - // compare boolean to int + // compare imag to float { v: &Visitor{}, - vx: &Visitor{value: true, kind: KindBoolean}, + vx: &Visitor{value: (2 + 0i), kind: KindImag}, ops: []token.Token{token.EQL, token.NEQ}, - vy: &Visitor{value: int64(10), kind: KindInt}, - expectedValues: []interface{}{nil, nil}, - expectedErrs: []error{ErrComparisonOperation, ErrComparisonOperation}, + vy: &Visitor{value: 2.0, kind: KindFloat}, + expectedValues: []interface{}{true, false}, + expectedErrs: []error{nil, nil}, + }, + // compare imag to int + { + v: &Visitor{}, + vx: &Visitor{value: (2 + 0i), kind: KindImag}, + ops: []token.Token{token.EQL, token.NEQ}, + vy: &Visitor{value: int64(2), kind: KindInt}, + expectedValues: []interface{}{true, false}, + expectedErrs: []error{nil, nil}, + }, + // compare float to imag + { + v: &Visitor{}, + vx: &Visitor{value: 2.0, kind: KindInt}, + ops: []token.Token{token.EQL, token.NEQ}, + vy: &Visitor{value: (2 + 0i), kind: KindImag}, + expectedValues: []interface{}{true, false}, + expectedErrs: []error{nil, nil}, }, - // compareInt to boolean + // compare int to complex + { + v: &Visitor{}, + vx: &Visitor{value: int64(2), kind: KindInt}, + ops: []token.Token{token.EQL, token.NEQ}, + vy: &Visitor{value: 2 + 0i, kind: KindImag}, + expectedValues: []interface{}{true, false}, + expectedErrs: []error{nil, nil}, + }, + // compare int to float64 + { + v: &Visitor{}, + vx: &Visitor{value: int64(2), kind: KindInt}, + ops: []token.Token{token.EQL, token.NEQ}, + vy: &Visitor{value: 2.0, kind: KindFloat}, + expectedValues: []interface{}{true, false}, + expectedErrs: []error{nil, nil}, + }, + // compare int to boolean { v: &Visitor{}, vx: &Visitor{value: int64(10), kind: KindInt}, @@ -107,6 +143,15 @@ func TestComparison(t *testing.T) { expectedValues: []interface{}{nil, nil}, expectedErrs: []error{ErrComparisonOperation, ErrComparisonOperation}, }, + // compare boolean to int + { + v: &Visitor{}, + vx: &Visitor{value: true, kind: KindBoolean}, + ops: []token.Token{token.EQL, token.NEQ}, + vy: &Visitor{value: int64(10), kind: KindInt}, + expectedValues: []interface{}{nil, nil}, + expectedErrs: []error{ErrComparisonOperation, ErrComparisonOperation}, + }, // compare boolean to string { v: &Visitor{}, diff --git a/exp/explain/README.md b/exp/explain/README.md index bc01ec2..5752772 100644 --- a/exp/explain/README.md +++ b/exp/explain/README.md @@ -1,6 +1,6 @@ # Explain -Explain is a standalone package aimed to explain step by step operation in expr. +Explain is an EXPERIMENTAL and a standalone package aimed to explain step by step operation in expr. ```go s := "1 + 2 + 3" diff --git a/exp/explain/explain.go b/exp/explain/explain.go index e3b122d..2eced51 100644 --- a/exp/explain/explain.go +++ b/exp/explain/explain.go @@ -22,6 +22,7 @@ import ( // One step can have multiple equivalent forms, starts with original form until the final form. type Step struct { EquivalentForms []string + Explaination string Result string } @@ -37,17 +38,18 @@ func Explain(s string) ([]Step, error) { if err := v.err; err != nil { return nil, err } - // sanitize results - explains := make([]Step, len(v.transforms)) - for i, transform := range v.transforms { - explains[i] = Step{ + explains := make([]Step, 0, len(v.transforms)) + for _, transform := range v.transforms { + step := Step{ EquivalentForms: []string{transform.Segmented}, + Explaination: transform.Explaination, Result: transform.Evaluated, } if transform.Segmented != transform.EquivalentForm { - explains[i].EquivalentForms = append(explains[i].EquivalentForms, transform.EquivalentForm) + step.EquivalentForms = append(step.EquivalentForms, transform.EquivalentForm) } + explains = append(explains, step) } return explains, nil diff --git a/exp/explain/explain_test.go b/exp/explain/explain_test.go index 9f093d7..25de44a 100644 --- a/exp/explain/explain_test.go +++ b/exp/explain/explain_test.go @@ -31,24 +31,24 @@ func TestExplain(t *testing.T) { { str: "1 + 2", explains: []explain.Step{ - {[]string{"1 + 2"}, "3"}, + {EquivalentForms: []string{"1 + 2"}, Result: "3"}, }, }, { str: "1 + 2 + 3", explains: []explain.Step{ - {[]string{"1 + 2"}, "3"}, - {[]string{"(1 + 2) + 3", "3 + 3"}, "6"}, + {EquivalentForms: []string{"1 + 2"}, Result: "3"}, + {EquivalentForms: []string{"(1 + 2) + 3", "3 + 3"}, Result: "6"}, }, }, { str: "!true || ((5 > 3) && 1 == 1)", explains: []explain.Step{ - {[]string{"!true"}, "false"}, - {[]string{"5 > 3"}, "true"}, - {[]string{"1 == 1"}, "true"}, - {[]string{"(5 > 3) && (1 == 1)", "true && true"}, "true"}, - {[]string{"false || (true && true)", "false || true"}, "true"}, + {EquivalentForms: []string{"!true"}, Result: "false"}, + {EquivalentForms: []string{"5 > 3"}, Result: "true"}, + {EquivalentForms: []string{"1 == 1"}, Result: "true"}, + {EquivalentForms: []string{"(5 > 3) && (1 == 1)", "true && true"}, Result: "true"}, + {EquivalentForms: []string{"false || (true && true)", "false || true"}, Result: "true"}, }, }, { diff --git a/exp/explain/visitor.go b/exp/explain/visitor.go index eb61eb4..e4834a7 100644 --- a/exp/explain/visitor.go +++ b/exp/explain/visitor.go @@ -16,6 +16,9 @@ package explain import ( "fmt" "go/ast" + "go/token" + "strconv" + "strings" "github.com/muktihari/expr" "github.com/muktihari/expr/internal/conv" @@ -36,6 +39,7 @@ const ( type Transform struct { Segmented string EquivalentForm string + Explaination string Evaluated string } @@ -140,13 +144,24 @@ func (v *Visitor) Visit(node ast.Node) ast.Visitor { evy := newExprVisitor() ast.Walk(evy, d.Y) - v.value = fmt.Sprintf("%s %s %s", evx.Value(), d.Op, evy.Value()) + xValue := evx.Value() + yValue := evy.Value() - v.transforms = append(v.transforms, Transform{ + v.value = fmt.Sprintf("%s %s %s", xValue, d.Op, yValue) + + transform := Transform{ Segmented: fmt.Sprintf("%s %s %s", vx.value, d.Op, vy.value), EquivalentForm: v.value, Evaluated: ev.Value(), - }) + } + + // Special case for explaining bitwise + switch d.Op { + case token.AND, token.OR, token.XOR, token.AND_NOT, token.SHL, token.SHR: + explainBitwise(&transform, xValue, yValue, d.Op) + } + + v.transforms = append(v.transforms, transform) case *ast.BasicLit: v.value, v.exprType = d.Value, basicLit case *ast.Ident: @@ -162,3 +177,58 @@ func newExprVisitor() *expr.Visitor { expr.WithAllowIntegerDividedByZero(true), ) } + +var operatorStringMap = map[token.Token]string{ + token.AND: "AND", + token.OR: "OR", + token.XOR: "XOR", + token.AND_NOT: "AND NOT", +} + +func explainBitwise(transform *Transform, xValue, yValue string, op token.Token) { + fx, _ := strconv.ParseFloat(xValue, 64) + fy, _ := strconv.ParseFloat(yValue, 64) + + var result int64 + switch op { + case token.AND: + result = int64(fx) & int64(fy) + case token.OR: + result = int64(fx) | int64(fy) + case token.XOR: + result = int64(fx) ^ int64(fy) + case token.AND_NOT: + result = int64(fx) &^ int64(fy) + } + + size := len(fmt.Sprintf("%.8b", int64(fx))) + sizeY := len(fmt.Sprintf("%.8b", int64(fy))) + if sizeY > size { + size = sizeY + } + formatter := fmt.Sprintf("0b%%.%db", size) + + xbits := fmt.Sprintf(formatter, int64(fx)) + ybits := fmt.Sprintf(formatter, int64(fy)) + + if op != token.SHL && op != token.SHR { + transform.Explaination = fmt.Sprintf("%s\n%s\n%s %s\n%s", + xbits, ybits, + strings.Repeat("-", (size*2)-(size*2/10)), operatorStringMap[op], + fmt.Sprintf(formatter, result)) + return + } + + var shiftDirection string + switch op { + case token.SHL: + shiftDirection = "left" + result = int64(fx) << int64(fy) + case token.SHR: + shiftDirection = "right" + result = int64(fx) >> int64(fy) + } + + transform.Explaination = fmt.Sprintf("%s %s-shifted by %d = %s", + xbits, shiftDirection, int64(fy), fmt.Sprintf(formatter, result)) +} diff --git a/exp/explain/visitor_test.go b/exp/explain/visitor_test.go index e192507..794a1d2 100644 --- a/exp/explain/visitor_test.go +++ b/exp/explain/visitor_test.go @@ -99,6 +99,42 @@ func TestVisit(t *testing.T) { str: "1.2 & 1", err: expr.ErrBitwiseOperation, }, + { + str: "4 << 10", + transforms: []Transform{ + {Segmented: "4 << 10", EquivalentForm: "4 << 10", Evaluated: "4096"}, + }, + }, + { + str: "4 >> 10", + transforms: []Transform{ + {Segmented: "4 >> 10", EquivalentForm: "4 >> 10", Evaluated: "0"}, + }, + }, + { + str: "4 & 10", + transforms: []Transform{ + {Segmented: "4 & 10", EquivalentForm: "4 & 10", Evaluated: "0"}, + }, + }, + { + str: "4 | 1000000000", + transforms: []Transform{ + {Segmented: "4 | 1000000000", EquivalentForm: "4 | 1000000000", Evaluated: "1000000004"}, + }, + }, + { + str: "4 ^ 10", + transforms: []Transform{ + {Segmented: "4 ^ 10", EquivalentForm: "4 ^ 10", Evaluated: "14"}, + }, + }, + { + str: "4 &^ 10", + transforms: []Transform{ + {Segmented: "4 &^ 10", EquivalentForm: "4 &^ 10", Evaluated: "4"}, + }, + }, } // test nil node @@ -120,7 +156,14 @@ func TestVisit(t *testing.T) { return } - if diff := cmp.Diff(v.Value(), tc.transforms); diff != "" { + transforms := v.Value() + + // Ignore Explaination + for i := range transforms { + transforms[i].Explaination = "" + } + + if diff := cmp.Diff(transforms, tc.transforms); diff != "" { t.Fatal(diff) } }) diff --git a/expr.go b/expr.go index 882ce01..d592854 100644 --- a/expr.go +++ b/expr.go @@ -49,26 +49,14 @@ func Any(str string) (interface{}, error) { return nil, err } - switch v.Kind() { - case KindInt: - v := v.ValueAny().(int64) - return v, nil - case KindFloat: - v := v.ValueAny().(float64) - vInt := int64(v) - if v == float64(vInt) { - return vInt, nil + switch val := v.value.(type) { + case float64: + if val == float64(int64(val)) { + return int64(val), nil } - return v, nil - case KindImag: - v := v.ValueAny().(complex128) - return v, nil - case KindBoolean: - v := v.ValueAny().(bool) - return v, nil - default: // must be string - v := v.ValueAny().(string) - return v, nil + return val, nil + default: + return val, nil } } @@ -100,9 +88,7 @@ func Bool(str string) (bool, error) { return false, err } - switch v.kind { - case KindBoolean: - val := v.ValueAny().(bool) + if val, ok := v.value.(bool); ok { return val, nil } @@ -129,16 +115,13 @@ func Complex128(str string) (complex128, error) { return 0, err } - switch v.Kind() { - case KindImag: - v := v.ValueAny().(complex128) - return v, nil - case KindInt: - v := v.ValueAny().(int64) - return complex(float64(v), 0), nil - case KindFloat: - v := v.ValueAny().(float64) - return complex(v, 0), nil + switch val := v.value.(type) { + case complex128: + return val, nil + case float64: + return complex(val, 0), nil + case int64: + return complex(float64(val), 0), nil } return 0, ErrValueTypeMismatch @@ -165,13 +148,13 @@ func Float64(str string) (float64, error) { return 0, err } - switch v.Kind() { - case KindInt: - v := v.ValueAny().(int64) - return float64(v), nil - case KindFloat: - v := v.ValueAny().(float64) - return v, nil + switch val := v.value.(type) { + case complex128: + return real(val), nil + case float64: + return val, nil + case int64: + return float64(val), nil } return 0, ErrValueTypeMismatch @@ -219,13 +202,13 @@ func parseStringExprIntoInt64(str string, allowIntegerDividedByZero bool) (int64 return 0, err } - switch v.Kind() { - case KindInt: - v := v.ValueAny().(int64) - return v, nil - case KindFloat: - v := v.ValueAny().(float64) - return int64(v), nil + switch val := v.value.(type) { + case complex128: + return int64(real(val)), nil + case float64: + return int64(val), nil + case int64: + return val, nil } return 0, ErrValueTypeMismatch diff --git a/expr_test.go b/expr_test.go index 095f934..9a1f664 100644 --- a/expr_test.go +++ b/expr_test.go @@ -222,6 +222,7 @@ func TestFloat64(t *testing.T) { Eq float64 Err error }{ + {In: "1i", Eq: 0}, {In: "2", Eq: 2}, {In: "4 == 2", Err: expr.ErrValueTypeMismatch}, {In: "1 + 1 + (4 == 2)", Err: expr.ErrArithmeticOperation}, @@ -255,6 +256,7 @@ func TestFloat64(t *testing.T) { {In: "10.0 % 2.6", Eq: 2.2}, {In: "12.5 | 4.3", Err: expr.ErrBitwiseOperation}, {In: "12 | 4", Err: expr.ErrBitwiseOperation}, + {In: "(2 + 2i) + (2 + 2i)", Eq: 4}, } for _, tc := range tt { @@ -279,6 +281,7 @@ func TestInt(t *testing.T) { Eq int Err error }{ + {In: "1i", Eq: 0}, {In: "4.23", Eq: 4}, {In: "4 == 2", Err: expr.ErrValueTypeMismatch}, {In: "1 + 1 + (4 == 2)", Err: expr.ErrArithmeticOperation}, diff --git a/logical.go b/logical.go index ef354fa..cdd419f 100644 --- a/logical.go +++ b/logical.go @@ -26,7 +26,6 @@ func logical(v, vx, vy *Visitor, binaryExpr *ast.BinaryExpr) { v.err = newLogicalNonBooleanError(vx, binaryExpr.X) return } - if vy.kind != KindBoolean { v.err = newLogicalNonBooleanError(vy, binaryExpr.Y) return diff --git a/visitor.go b/visitor.go index 5a369fc..a94056f 100644 --- a/visitor.go +++ b/visitor.go @@ -25,7 +25,7 @@ import ( ) // Kind of value (value's type) -type Kind int +type Kind byte const ( KindIllegal Kind = iota @@ -51,7 +51,7 @@ var kinds = [...]string{ } func (k Kind) String() string { - if k >= 0 && k < Kind(len(kinds)) { + if k < Kind(len(kinds)) { return kinds[k] } return "kind(" + strconv.Itoa(int(k)) + ")" @@ -60,7 +60,7 @@ func (k Kind) String() string { type Option interface{ apply(o *options) } // NumericType determines what type of number represented in the expr string -type NumericType int +type NumericType byte const ( NumericTypeAuto NumericType = iota // [1 * 2 = 2] [1 * 2.5 = 2.5] @@ -90,12 +90,11 @@ var _ ast.Visitor = &Visitor{} // Visitor satisfies ast.Visitor interface. type Visitor struct { + value interface{} + err error + pos int + kind Kind // used to reduce the need to do type assertion on numeric operation. options options // Visitor's Option - - kind Kind - value interface{} - pos int - err error } func defaultOptions() *options { @@ -176,7 +175,8 @@ func (v *Visitor) visitUnary(unaryExpr *ast.UnaryExpr) ast.Visitor { v.kind = vx.kind switch unaryExpr.Op { case token.NOT: // negation: !true -> false, !false -> true - if vx.kind != KindBoolean { + res, ok := vx.value.(bool) + if !ok { s := conv.FormatExpr(unaryExpr.X) v.err = &SyntaxError{ Msg: "could not do negation: result of \"" + s + "\" is \"" + fmt.Sprintf("%v", vx.value) + "\" not a boolean", @@ -185,7 +185,6 @@ func (v *Visitor) visitUnary(unaryExpr *ast.UnaryExpr) ast.Visitor { } return nil } - res := vx.value.(bool) v.value = !res case token.ADD: v.value = vx.value @@ -259,8 +258,8 @@ func (v *Visitor) visitBasicLit(basicLit *ast.BasicLit) ast.Visitor { case token.CHAR: fallthrough // treat as string case token.STRING: - v.value = strings.TrimFunc(basicLit.Value, func(r rune) bool { return r == '\'' || r == '`' || r == '"' }) v.kind = KindString + v.value = strings.TrimFunc(basicLit.Value, func(r rune) bool { return r == '\'' || r == '`' || r == '"' }) } return nil } @@ -271,7 +270,6 @@ func (v *Visitor) visitIndent(indent *ast.Ident) ast.Visitor { if err != nil { return nil } - v.kind, v.value = KindBoolean, vb return nil } diff --git a/visitor_test.go b/visitor_test.go index bb747b1..a6dc70c 100644 --- a/visitor_test.go +++ b/visitor_test.go @@ -176,8 +176,7 @@ func TestKindString(t *testing.T) { kind expr.Kind expected string }{ - {kind: -100, expected: "kind(-100)"}, - {kind: 1000, expected: "kind(1000)"}, + {kind: 255, expected: "kind(255)"}, } for _, tc := range tt {