From e38deb46fca48e769306a69cc91d99628b4893ab Mon Sep 17 00:00:00 2001
From: dbaumgarten <mail@dbaumgarten.net>
Date: Fri, 29 Oct 2021 12:11:40 +0200
Subject: [PATCH] Exploid binop identities when optimizing #115

---
 pkg/optimizers/static_expressions.go      | 74 +++++++++++++++++++++++
 pkg/optimizers/static_expressions_test.go | 12 ++++
 2 files changed, 86 insertions(+)

diff --git a/pkg/optimizers/static_expressions.go b/pkg/optimizers/static_expressions.go
index 5301459..844940d 100644
--- a/pkg/optimizers/static_expressions.go
+++ b/pkg/optimizers/static_expressions.go
@@ -81,6 +81,11 @@ func (o *StaticExpressionOptimizer) OptimizeExpressionNonRecursive(exp ast.Expre
 
 		return varToConst(res, n.Exp1.Start())
 	}
+
+	if binop, is := exp.(*ast.BinaryOperation); is {
+		return simplifyIdentities(binop)
+	}
+
 	return nil
 }
 
@@ -130,3 +135,72 @@ func varToConst(v *vm.Variable, pos ast.Position) ast.Expression {
 		Position: pos,
 	}
 }
+
+func simplifyIdentities(binop *ast.BinaryOperation) ast.Expression {
+
+	if _, is := binop.Exp1.(*ast.StringConstant); is {
+		return binop
+	}
+	if _, is := binop.Exp2.(*ast.StringConstant); is {
+		return binop
+	}
+
+	switch binop.Operator {
+	case "+":
+		if isNumConstWithValue(binop.Exp1, "0") {
+			return binop.Exp2
+		}
+		if isNumConstWithValue(binop.Exp2, "0") {
+			return binop.Exp1
+		}
+	case "-":
+		if isNumConstWithValue(binop.Exp1, "0") {
+			return &ast.UnaryOperation{
+				Position: binop.Start(),
+				Operator: "-",
+				Exp:      binop.Exp2,
+			}
+		}
+		if isNumConstWithValue(binop.Exp2, "0") {
+			return binop.Exp1
+		}
+	case "*":
+		if isNumConstWithValue(binop.Exp1, "1") {
+			return binop.Exp2
+		}
+		if isNumConstWithValue(binop.Exp2, "1") {
+			return binop.Exp1
+		}
+		if isNumConstWithValue(binop.Exp1, "0") {
+			return &ast.NumberConstant{Position: binop.Start(), Value: "0"}
+		}
+		if isNumConstWithValue(binop.Exp2, "0") {
+			return &ast.NumberConstant{Position: binop.Start(), Value: "0"}
+		}
+	case "/":
+		if isNumConstWithValue(binop.Exp2, "1") {
+			return binop.Exp1
+		}
+	case "^":
+		if isNumConstWithValue(binop.Exp2, "0") {
+			return &ast.NumberConstant{Position: binop.Start(), Value: "1"}
+		}
+		if isNumConstWithValue(binop.Exp2, "1") {
+			return binop.Exp1
+		}
+	}
+
+	return binop
+}
+
+// checkis if check is a NumberConstant and if it's value matches expected
+func isNumConstWithValue(check ast.Expression, expected string) bool {
+
+	if num, is := check.(*ast.NumberConstant); is {
+		if num.Value == expected {
+			return true
+		}
+	}
+
+	return false
+}
diff --git a/pkg/optimizers/static_expressions_test.go b/pkg/optimizers/static_expressions_test.go
index f68e1e1..733ab82 100644
--- a/pkg/optimizers/static_expressions_test.go
+++ b/pkg/optimizers/static_expressions_test.go
@@ -13,6 +13,18 @@ var staticCases = map[string]string{
 	"a=123+100+a":             "a=223+a",
 	"a=a+(123+100)+b":         "a=a+223+b",
 	"a=a+(123+100)+b*(10*10)": "a=a+223+b*100",
+	"a=0+b":                   "a=b",
+	"a=b+0":                   "a=b",
+	"a=b-0":                   "a=b",
+	"a=0-b":                   "a=-b",
+	"a=0*b":                   "a=0",
+	"a=b*0":                   "a=0",
+	"a=1*b":                   "a=b",
+	"a=b*1":                   "a=b",
+	"a=b/1":                   "a=b",
+	"a=b^0":                   "a=1",
+	"a=b^1":                   "a=b",
+	"a=\"a\"*1":               "a=\"a\"*1",
 }
 
 func TestStaticExpressions(t *testing.T) {