Repository: tomarrell/lbadd
Pull request #207 (2020-07-20 09:01:01)

engine: implement arithmetic expressions
Implement arithmetic operations in the engine.

The actual operations are not implemented by the engine, but by the types themselves. This means, that when e.g. adding two integers, not the engine will perform the addition, but the `IntegerType`.

Closes nothing.

`Add`, `Sub`, `Mul`, `Div`, `Mod` and `Pow` are implemented for:
- [x] `IntegerType`
- [x] `RealType`
- [x] `StringType` (only `Add`, which is not arithmetic, but explicitly within the scope of this PR)

## Definition of done
- [ ] Code correctness
- [ ] Documentation
- [ ] Test cases



Patch:
diff --git a/internal/engine/arithmetic.go b/internal/engine/arithmetic.go
new file mode 100644
index 0000000..cb1f38f
--- /dev/null
+++ b/internal/engine/arithmetic.go
@@ -0,0 +1,97 @@
+package engine
+
+import (
+	"fmt"
+
+	"github.com/tomarrell/lbadd/internal/engine/types"
+)
+
+func (e Engine) add(ctx ExecutionContext, left, right types.Value) (types.Value, error) {
+	if left == nil || right == nil {
+		return nil, fmt.Errorf("cannot add %T and %T", left, right)
+	}
+
+	if adder, ok := left.Type().(types.ArithmeticAdder); ok {
+		result, err := adder.Add(left, right)
+		if err != nil {
+			return nil, fmt.Errorf("add: %w", err)
+		}
+		return result, nil
+	}
+	return nil, fmt.Errorf("%v does not support addition", left.Type())
+}
+
+func (e Engine) sub(ctx ExecutionContext, left, right types.Value) (types.Value, error) {
+	if left == nil || right == nil {
+		return nil, fmt.Errorf("cannot subtract %T and %T", left, right)
+	}
+
+	if subtractor, ok := left.Type().(types.ArithmeticSubtractor); ok {
+		result, err := subtractor.Sub(left, right)
+		if err != nil {
+			return nil, fmt.Errorf("sub: %w", err)
+		}
+		return result, nil
+	}
+	return nil, fmt.Errorf("%v does not support subtraction", left.Type())
+}
+
+func (e Engine) mul(ctx ExecutionContext, left, right types.Value) (types.Value, error) {
+	if left == nil || right == nil {
+		return nil, fmt.Errorf("cannot multiplicate %T and %T", left, right)
+	}
+
+	if multiplicator, ok := left.Type().(types.ArithmeticMultiplicator); ok {
+		result, err := multiplicator.Mul(left, right)
+		if err != nil {
+			return nil, fmt.Errorf("mul: %w", err)
+		}
+		return result, nil
+	}
+	return nil, fmt.Errorf("%v does not support multiplication", left.Type())
+}
+
+func (e Engine) div(ctx ExecutionContext, left, right types.Value) (types.Value, error) {
+	if left == nil || right == nil {
+		return nil, fmt.Errorf("cannot divide %T and %T", left, right)
+	}
+
+	if divider, ok := left.Type().(types.ArithmeticDivider); ok {
+		result, err := divider.Div(left, right)
+		if err != nil {
+			return nil, fmt.Errorf("div: %w", err)
+		}
+		return result, nil
+	}
+	return nil, fmt.Errorf("%v does not support division", left.Type())
+}
+
+func (e Engine) mod(ctx ExecutionContext, left, right types.Value) (types.Value, error) {
+	if left == nil || right == nil {
+		return nil, fmt.Errorf("cannot modulo %T and %T", left, right)
+	}
+
+	if modulator, ok := left.Type().(types.ArithmeticModulator); ok {
+		result, err := modulator.Mod(left, right)
+		if err != nil {
+			return nil, fmt.Errorf("mod: %w", err)
+		}
+		return result, nil
+	}
+	return nil, fmt.Errorf("%v does not support modulo", left.Type())
+}
+
+func (e Engine) pow(ctx ExecutionContext, left, right types.Value) (types.Value, error) {
+	if left == nil || right == nil {
+		return nil, fmt.Errorf("cannot exponentiate %T and %T", left, right)
+	}
+
+	if exponentiator, ok := left.Type().(types.ArithmeticExponentiator); ok {
+		result, err := exponentiator.Pow(left, right)
+		if err != nil {
+			return nil, fmt.Errorf("pow: %w", err)
+		}
+		return result, nil
+	}
+	return nil, fmt.Errorf("%v does not support exponentiation", left.Type())
+}
diff --git a/internal/engine/builtin.go b/internal/engine/builtin.go
index 77d01d5..ac0ca6d 100644
--- a/internal/engine/builtin.go
+++ b/internal/engine/builtin.go
@@ -22,6 +22,7 @@ var (
 	_ = builtinUCase
 	_ = builtinLCase
 	_ = builtinMin
+	_ = builtinMax
 )
 
 // builtinNow returns a new date value, containing the timestamp provided by the
diff --git a/internal/engine/expression.go b/internal/engine/expression.go
index 209d964..75fdf7a 100644
--- a/internal/engine/expression.go
+++ b/internal/engine/expression.go
@@ -23,6 +23,8 @@ func (e Engine) evaluateExpression(ctx ExecutionContext, expr command.Expr) (typ
 		return e.evaluateLiteralExpr(ctx, ex)
 	case command.FunctionExpr:
 		return e.evaluateFunctionExpr(ctx, ex)
+	case command.BinaryExpr:
+		return e.evaluateBinaryExpr(ctx, ex)
 	}
 	return nil, ErrUnimplemented(fmt.Sprintf("evaluate %T", expr))
 }
@@ -66,3 +68,30 @@ func (e Engine) evaluateFunctionExpr(ctx ExecutionContext, expr command.Function
 	function := types.NewFunction(expr.Name, exprs...)
 	return e.evaluateFunction(ctx, function)
 }
+
+func (e Engine) evaluateBinaryExpr(ctx ExecutionContext, expr command.BinaryExpr) (types.Value, error) {
+	left, err := e.evaluateExpression(ctx, expr.Left)
+	if err != nil {
+		return nil, fmt.Errorf("left: %w", err)
+	}
+	right, err := e.evaluateExpression(ctx, expr.Right)
+	if err != nil {
+		return nil, fmt.Errorf("right: %w", err)
+	}
+
+	switch expr.Operator {
+	case "+":
+		return e.add(ctx, left, right)
+	case "-":
+		return e.sub(ctx, left, right)
+	case "*":
+		return e.mul(ctx, left, right)
+	case "/":
+		return e.div(ctx, left, right)
+	case "%":
+		return e.mod(ctx, left, right)
+	case "**":
+		return e.pow(ctx, left, right)
+	}
+	return nil, ErrUnimplemented(expr.Operator)
+}
diff --git a/internal/engine/types/arithmetic.go b/internal/engine/types/arithmetic.go
new file mode 100644
index 0000000..f23d62f
--- /dev/null
+++ b/internal/engine/types/arithmetic.go
@@ -0,0 +1,45 @@
+package types
+
+type (
+	// ArithmeticAdder wraps the arithmetic add operation, usually represented
+	// by a '+'. The actual addition is defined and must be documented by the
+	// implementing type.
+	ArithmeticAdder interface {
+		Add(Value, Value) (Value, error)
+	}
+
+	// ArithmeticSubtractor wraps the arithmetic sub operation, usually
+	// represented by a '-'. The actual subtraction is defined and must be
+	// documented by the implementing type.
+	ArithmeticSubtractor interface {
+		Sub(Value, Value) (Value, error)
+	}
+
+	// ArithmeticMultiplicator wraps the arithmetic mul operation, usually
+	// represented by a '*'. The actual multiplication is defined and must be
+	// documented by the implementing type.
+	ArithmeticMultiplicator interface {
+		Mul(Value, Value) (Value, error)
+	}
+
+	// ArithmeticDivider wraps the arithmetic div operation, usually represented
+	// by a '/'. The actual division is defined and must be documented by the
+	// implementing type.
+	ArithmeticDivider interface {
+		Div(Value, Value) (Value, error)
+	}
+
+	// ArithmeticModulator wraps the arithmetic mod operation, usually
+	// represented by a '%'. The actual modulation is defined and must be
+	// documented by the implementing type.
+	ArithmeticModulator interface {
+		Mod(Value, Value) (Value, error)
+	}
+
+	// ArithmeticExponentiator wraps the arithmetic pow operation, usually
+	// represented by a '**'. The actual exponentiation is defined and must be
+	// documented by the implementing type.
+	ArithmeticExponentiator interface {
+		Pow(Value, Value) (Value, error)
+	}
+)
diff --git a/internal/engine/types/integer_type.go b/internal/engine/types/integer_type.go
index 963591b..df4b9bd 100644
--- a/internal/engine/types/integer_type.go
+++ b/internal/engine/types/integer_type.go
@@ -1,5 +1,7 @@
 package types
 
+import "math"
+
 var (
 	// Integer is the date type. Integers are comparable. The name of this type
 	// is "Integer".
@@ -40,3 +42,81 @@ func (t IntegerType) Compare(left, right Value) (int, error) {
 	}
 	return 0, nil
 }
+
+// Add adds the left and right value, producing a new integer value. This only
+// works, if left and right are of type integer.
+func (t IntegerType) Add(left, right Value) (Value, error) {
+	if err := t.ensureHaveThisType(left, right); err != nil {
+		return nil, err
+	}
+
+	leftInteger := left.(IntegerValue).Value
+	rightInteger := right.(IntegerValue).Value
+
+	return NewInteger(leftInteger + rightInteger), nil
+}
+
+// Sub subtracts the right from the left value, producing a new integer value.
+// This only works, if left and right are of type integer.
+func (t IntegerType) Sub(left, right Value) (Value, error) {
+	if err := t.ensureHaveThisType(left, right); err != nil {
+		return nil, err
+	}
+
+	leftInteger := left.(IntegerValue).Value
+	rightInteger := right.(IntegerValue).Value
+
+	return NewInteger(leftInteger - rightInteger), nil
+}
+
+// Mul multiplicates the left and right value, producing a new integer value.
+// This only works, if left and right are of type integer.
+func (t IntegerType) Mul(left, right Value) (Value, error) {
+	if err := t.ensureHaveThisType(left, right); err != nil {
+		return nil, err
+	}
+
+	leftInteger := left.(IntegerValue).Value
+	rightInteger := right.(IntegerValue).Value
+
+	return NewInteger(leftInteger * rightInteger), nil
+}
+
+// Div divides the left by the right value, producing a new real value. This
+// only works, if left and right are of type integer.
+func (t IntegerType) Div(left, right Value) (Value, error) {
+	if err := t.ensureHaveThisType(left, right); err != nil {
+		return nil, err
+	}
+
+	leftInteger := left.(IntegerValue).Value
+	rightInteger := right.(IntegerValue).Value
+
+	return NewReal(float64(leftInteger) / float64(rightInteger)), nil
+}
+
+// Mod modulates the left and right value, producing a new integer value. This
+// only works, if left and right are of type integer.
+func (t IntegerType) Mod(left, right Value) (Value, error) {
+	if err := t.ensureHaveThisType(left, right); err != nil {
+		return nil, err
+	}
+
+	leftInteger := left.(IntegerValue).Value
+	rightInteger := right.(IntegerValue).Value
+
+	return NewInteger(leftInteger % rightInteger), nil
+}
+
+// Pow exponentiates the left and right value, producing a new integer value.
+// This only works, if left and right are of type integer.
+func (t IntegerType) Pow(left, right Value) (Value, error) {
+	if err := t.ensureHaveThisType(left, right); err != nil {
+		return nil, err
+	}
+
+	leftInteger := left.(IntegerValue).Value
+	rightInteger := right.(IntegerValue).Value
+
+	return NewInteger(int64(math.Pow(float64(leftInteger), float64(rightInteger)))), nil
+}
diff --git a/internal/engine/types/real_type.go b/internal/engine/types/real_type.go
index 833781b..1974337 100644
--- a/internal/engine/types/real_type.go
+++ b/internal/engine/types/real_type.go
@@ -1,5 +1,7 @@
 package types
 
+import "math"
+
 var (
 	// Real is the date type. Reals are comparable. The name of this type
 	// is "Real".
@@ -38,3 +40,68 @@ func (t RealType) Compare(left, right Value) (int, error) {
 	}
 	return 0, nil
 }
+
+// Add adds the left and right value, producing a new real value. This only
+// works, if left and right are of type real.
+func (t RealType) Add(left, right Value) (Value, error) {
+	if err := t.ensureHaveThisType(left, right); err != nil {
+		return nil, err
+	}
+
+	leftReal := left.(RealValue).Value
+	rightReal := right.(RealValue).Value
+
+	return NewReal(leftReal + rightReal), nil
+}
+
+// Sub subtracts the right from the left value, producing a new real value.
+// This only works, if left and right are of type real.
+func (t RealType) Sub(left, right Value) (Value, error) {
+	if err := t.ensureHaveThisType(left, right); err != nil {
+		return nil, err
+	}
+
+	leftReal := left.(RealValue).Value
+	rightReal := right.(RealValue).Value
+
+	return NewReal(leftReal - rightReal), nil
+}
+
+// Mul multiplicates the left and right value, producing a new real value.
+// This only works, if left and right are of type real.
+func (t RealType) Mul(left, right Value) (Value, error) {
+	if err := t.ensureHaveThisType(left, right); err != nil {
+		return nil, err
+	}
+
+	leftReal := left.(RealValue).Value
+	rightReal := right.(RealValue).Value
+
+	return NewReal(leftReal * rightReal), nil
+}
+
+// Div divides the left by the right value, producing a new real value. This
+// only works, if left and right are of type real.
+func (t RealType) Div(left, right Value) (Value, error) {
+	if err := t.ensureHaveThisType(left, right); err != nil {
+		return nil, err
+	}
+
+	leftReal := left.(RealValue).Value
+	rightReal := right.(RealValue).Value
+
+	return NewReal(float64(leftReal) / float64(rightReal)), nil
+}
+
+// Pow exponentiates the left and right value, producing a new real value.
+// This only works, if left and right are of type real.
+func (t RealType) Pow(left, right Value) (Value, error) {
+	if err := t.ensureHaveThisType(left, right); err != nil {
+		return nil, err
+	}
+
+	leftReal := left.(RealValue).Value
+	rightReal := right.(RealValue).Value
+
+	return NewReal(math.Pow(leftReal, rightReal)), nil
+}
diff --git a/internal/engine/types/string_type.go b/internal/engine/types/string_type.go
index 31a83f4..f730d06 100644
--- a/internal/engine/types/string_type.go
+++ b/internal/engine/types/string_type.go
@@ -77,3 +77,15 @@ func (t StringType) Deserialize(data []byte) (Value, error) {
 	}
 	return NewString(string(data[4:])), nil
 }
+
+// Add concatenates the left and right right value. This only works, if left and
+// right are string values.
+func (t StringType) Add(left, right Value) (Value, error) {
+	if err := t.ensureHaveThisType(left, right); err != nil {
+		return nil, err
+	}
+
+	leftString := left.(StringValue).Value
+	rightString := right.(StringValue).Value
+	return NewString(leftString + rightString), nil
+}


Test patch:
diff --git a/internal/engine/arithmetic_test.go b/internal/engine/arithmetic_test.go
new file mode 100644
index 0000000..a5b2424
--- /dev/null
+++ b/internal/engine/arithmetic_test.go
@@ -0,0 +1,137 @@
+package engine
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/tomarrell/lbadd/internal/engine/types"
+)
+
+func TestEngine_add(t *testing.T) {
+	type args struct {
+		ctx   ExecutionContext
+		left  types.Value
+		right types.Value
+	}
+	tests := []struct {
+		name    string
+		args    args
+		want    types.Value
+		wantErr string
+	}{
+		{
+			"nils",
+			args{
+				newEmptyExecutionContext(),
+				nil,
+				nil,
+			},
+			nil,
+			"cannot add <nil> and <nil>",
+		},
+		{
+			"left nil",
+			args{
+				newEmptyExecutionContext(),
+				nil,
+				types.NewInteger(5),
+			},
+			nil,
+			"cannot add <nil> and types.IntegerValue",
+		},
+		{
+			"right nil",
+			args{
+				newEmptyExecutionContext(),
+				types.NewInteger(5),
+				nil,
+			},
+			nil,
+			"cannot add types.IntegerValue and <nil>",
+		},
+		{
+			"simple",
+			args{
+				newEmptyExecutionContext(),
+				types.NewInteger(5),
+				types.NewInteger(6),
+			},
+			types.NewInteger(11),
+			"",
+		},
+		{
+			"zero",
+			args{
+				newEmptyExecutionContext(),
+				types.NewInteger(0),
+				types.NewInteger(0),
+			},
+			types.NewInteger(0),
+			"",
+		},
+		{
+			"both negative",
+			args{
+				newEmptyExecutionContext(),
+				types.NewInteger(-5),
+				types.NewInteger(-5),
+			},
+			types.NewInteger(-10),
+			"",
+		},
+		{
+			"left negative",
+			args{
+				newEmptyExecutionContext(),
+				types.NewInteger(-5),
+				types.NewInteger(10),
+			},
+			types.NewInteger(5),
+			"",
+		},
+		{
+			"right negative",
+			args{
+				newEmptyExecutionContext(),
+				types.NewInteger(10),
+				types.NewInteger(-5),
+			},
+			types.NewInteger(5),
+			"",
+		},
+		{
+			"overflow",
+			args{
+				newEmptyExecutionContext(),
+				types.NewInteger((1 << 63) - 1),
+				types.NewInteger(5),
+			},
+			types.NewInteger(-(1 << 63) + 4),
+			"",
+		},
+		{
+			"negative overflow",
+			args{
+				newEmptyExecutionContext(),
+				types.NewInteger(-(1 << 63)),
+				types.NewInteger(-1),
+			},
+			types.NewInteger((1 << 63) - 1),
+			"",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			assert := assert.New(t)
+
+			e := createEngineOnEmptyDatabase(t)
+			got, err := e.add(tt.args.ctx, tt.args.left, tt.args.right)
+			if tt.wantErr != "" {
+				assert.EqualError(err, tt.wantErr)
+			} else {
+				assert.NoError(err)
+			}
+			assert.Equal(tt.want, got)
+		})
+	}
+}
diff --git a/internal/engine/expression_test.go b/internal/engine/expression_test.go
index 9b3553f..af6f47d 100644
--- a/internal/engine/expression_test.go
+++ b/internal/engine/expression_test.go
@@ -10,23 +10,25 @@ import (
 	"github.com/tomarrell/lbadd/internal/engine/types"
 )
 
+type evaluateExpressionTest struct {
+	name    string
+	e       Engine
+	ctx     ExecutionContext
+	expr    command.Expr
+	want    types.Value
+	wantErr string
+}
+
 func TestEngine_evaluateExpression(t *testing.T) {
 	fixedTimestamp, err := time.Parse("2006-01-02T15:04:05", "2020-06-01T14:05:12")
 	assert.NoError(t, err)
 	fixedTimeProvider := func() time.Time { return fixedTimestamp }
 
-	tests := []struct {
-		name    string
-		e       Engine
-		ctx     ExecutionContext
-		expr    command.Expr
-		want    types.Value
-		wantErr string
-	}{
+	testEvaluateExpressionTest(t, []evaluateExpressionTest{
 		{
 			"nil",
 			builder().build(),
-			ExecutionContext{},
+			newEmptyExecutionContext(),
 			nil,
 			nil,
 			"cannot evaluate expression of type <nil>",
@@ -34,7 +36,7 @@ func TestEngine_evaluateExpression(t *testing.T) {
 		{
 			"true",
 			builder().build(),
-			ExecutionContext{},
+			newEmptyExecutionContext(),
 			command.ConstantBooleanExpr{Value: true},
 			types.NewBool(true),
 			"",
@@ -42,34 +44,222 @@ func TestEngine_evaluateExpression(t *testing.T) {
 		{
 			"false",
 			builder().build(),
-			ExecutionContext{},
+			newEmptyExecutionContext(),
 			command.ConstantBooleanExpr{Value: false},
 			types.NewBool(false),
 			"",
 		},
-		{
-			"function NOW",
-			builder().
-				timeProvider(fixedTimeProvider).
-				build(),
-			ExecutionContext{},
-			command.FunctionExpr{
-				Name: "NOW",
-			},
-			types.NewDate(fixedTimestamp),
-			"",
-		},
-		{
-			"unknown function",
-			builder().build(),
-			ExecutionContext{},
-			command.FunctionExpr{
-				Name: "NOTEXIST",
+	})
+	t.Run("functions", func(t *testing.T) {
+		testEvaluateExpressionTest(t, []evaluateExpressionTest{
+			{
+				"function NOW",
+				builder().
+					timeProvider(fixedTimeProvider).
+					build(),
+				newEmptyExecutionContext(),
+				command.FunctionExpr{
+					Name: "NOW",
+				},
+				types.NewDate(fixedTimestamp),
+				"",
 			},
-			nil,
-			"no function for name NOTEXIST(...)",
-		},
-	}
+			{
+				"unknown function",
+				builder().build(),
+				newEmptyExecutionContext(),
+				command.FunctionExpr{
+					Name: "NOTEXIST",
+				},
+				nil,
+				"no function for name NOTEXIST(...)",
+			}})
+	})
+	t.Run("arithmetic", func(t *testing.T) {
+		t.Run("op=add", func(t *testing.T) {
+			testEvaluateExpressionTest(t, []evaluateExpressionTest{
+				{
+					"simple integral addition",
+					builder().build(),
+					newEmptyExecutionContext(),
+					command.BinaryExpr{
+						Left:     command.LiteralExpr{Value: "5"},
+						Operator: "+",
+						Right:    command.LiteralExpr{Value: "6"},
+					},
+					types.NewInteger(11),
+					"",
+				},
+				{
+					"simple real addition",
+					builder().build(),
+					newEmptyExecutionContext(),
+					command.BinaryExpr{
+						Left:     command.LiteralExpr{Value: "5.5"},
+						Operator: "+",
+						Right:    command.LiteralExpr{Value: "6.7"},
+					},
+					types.NewReal(12.2),
+					"",
+				},
+				{
+					"simple string addition/concatenation",
+					builder().build(),
+					newEmptyExecutionContext(),
+					command.BinaryExpr{
+						Left:     command.LiteralExpr{Value: `"abc"`},
+						Operator: "+",
+						Right:    command.LiteralExpr{Value: `"def"`},
+					},
+					types.NewString("abcdef"),
+					"",
+				},
+			})
+		})
+		t.Run("op=sub", func(t *testing.T) {
+			testEvaluateExpressionTest(t, []evaluateExpressionTest{
+				{
+					"simple integral subtraction",
+					builder().build(),
+					newEmptyExecutionContext(),
+					command.BinaryExpr{
+						Left:     command.LiteralExpr{Value: "6"},
+						Operator: "-",
+						Right:    command.LiteralExpr{Value: "5"},
+					},
+					types.NewInteger(1),
+					"",
+				},
+				{
+					"simple real subtraction",
+					builder().build(),
+					newEmptyExecutionContext(),
+					command.BinaryExpr{
+						Left:     command.LiteralExpr{Value: "12.2"},
+						Operator: "-",
+						Right:    command.LiteralExpr{Value: "7.6"},
+					},
+					types.NewReal(4.6),
+					"",
+				},
+			})
+		})
+		t.Run("op=mul", func(t *testing.T) {
+			testEvaluateExpressionTest(t, []evaluateExpressionTest{
+				{
+					"simple integral multiplication",
+					builder().build(),
+					newEmptyExecutionContext(),
+					command.BinaryExpr{
+						Left:     command.LiteralExpr{Value: "6"},
+						Operator: "*",
+						Right:    command.LiteralExpr{Value: "5"},
+					},
+					types.NewInteger(30),
+					"",
+				},
+				{
+					"simple real multiplication",
+					builder().build(),
+					newEmptyExecutionContext(),
+					command.BinaryExpr{
+						Left:     command.LiteralExpr{Value: "6.2"},
+						Operator: "*",
+						Right:    command.LiteralExpr{Value: "5.7"},
+					},
+					types.NewReal(35.34),
+					"",
+				},
+			})
+		})
+		t.Run("op=div", func(t *testing.T) {
+			testEvaluateExpressionTest(t, []evaluateExpressionTest{
+				{
+					"simple integral division",
+					builder().build(),
+					newEmptyExecutionContext(),
+					command.BinaryExpr{
+						Left:     command.LiteralExpr{Value: "15"},
+						Operator: "/",
+						Right:    command.LiteralExpr{Value: "5"},
+					},
+					types.NewReal(3),
+					"",
+				},
+				{
+					"simple real division",
+					builder().build(),
+					newEmptyExecutionContext(),
+					command.BinaryExpr{
+						Left:     command.LiteralExpr{Value: "35.34"},
+						Operator: "/",
+						Right:    command.LiteralExpr{Value: "5.7"},
+					},
+					types.NewReal(6.2),
+					"",
+				},
+			})
+		})
+		t.Run("op=mod", func(t *testing.T) {
+			testEvaluateExpressionTest(t, []evaluateExpressionTest{
+				{
+					"simple integral modulo",
+					builder().build(),
+					newEmptyExecutionContext(),
+					command.BinaryExpr{
+						Left:     command.LiteralExpr{Value: "7"},
+						Operator: "%",
+						Right:    command.LiteralExpr{Value: "5"},
+					},
+					types.NewInteger(2),
+					"",
+				},
+				{
+					"real modulo",
+					builder().build(),
+					newEmptyExecutionContext(),
+					command.BinaryExpr{
+						Left:     command.LiteralExpr{Value: "7.2"},
+						Operator: "%",
+						Right:    command.LiteralExpr{Value: "5.2"},
+					},
+					nil,
+					"Real does not support modulo",
+				},
+			})
+		})
+		t.Run("op=pow", func(t *testing.T) {
+			testEvaluateExpressionTest(t, []evaluateExpressionTest{
+				{
+					"simple integral exponentiation",
+					builder().build(),
+					newEmptyExecutionContext(),
+					command.BinaryExpr{
+						Left:     command.LiteralExpr{Value: "2"},
+						Operator: "**",
+						Right:    command.LiteralExpr{Value: "4"},
+					},
+					types.NewInteger(16),
+					"",
+				},
+				{
+					"simple real exponentiation",
+					builder().build(),
+					newEmptyExecutionContext(),
+					command.BinaryExpr{
+						Left:     command.LiteralExpr{Value: "2.2"},
+						Operator: "**",
+						Right:    command.LiteralExpr{Value: "1.5"},
+					},
+					types.NewReal(3.2631273343220926),
+					"",
+				},
+			})
+		})
+	})
+}
+
+func testEvaluateExpressionTest(t *testing.T, tests []evaluateExpressionTest) {
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
 			assert := assert.New(t)