From fccb9d9d143ebd60a6cf9d745ee5b794b2b355b8 Mon Sep 17 00:00:00 2001 From: Jonathan Hogg Date: Fri, 20 Dec 2024 17:24:31 +0000 Subject: [PATCH] New `null` rule for simplifying maths binary operations If either side is `null` then the expression will always evaluate to a `null`. --- src/flitter/language/tree.pyx | 30 ++++++++++++++++++++++++++++++ tests/test_simplifier.py | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/src/flitter/language/tree.pyx b/src/flitter/language/tree.pyx index dba18a15..7582ee27 100644 --- a/src/flitter/language/tree.pyx +++ b/src/flitter/language/tree.pyx @@ -630,6 +630,8 @@ cdef class Add(MathsBinaryOperation): program.add() cdef Expression constant_left(self, Vector left, Expression right): + if left.eq(null_): + return NoOp if left.eq(false_): return Positive(right) @@ -653,6 +655,8 @@ cdef class Subtract(MathsBinaryOperation): program.sub() cdef Expression constant_left(self, Vector left, Expression right): + if left.eq(null_): + return NoOp if left.eq(false_): return Negative(right) @@ -674,6 +678,8 @@ cdef class Multiply(MathsBinaryOperation): program.mul() cdef Expression constant_left(self, Vector left, Expression right): + if left.eq(null_): + return NoOp if left.eq(true_): return Positive(right) if left.eq(minusone_): @@ -707,7 +713,13 @@ cdef class Divide(MathsBinaryOperation): cdef void _compile_op(self, Program program): program.truediv() + cdef Expression constant_left(self, Vector left, Expression right): + if left.eq(null_): + return NoOp + cdef Expression constant_right(self, Expression left, Vector right): + if right.eq(null_): + return NoOp if right.eq(true_): return Positive(left) return Multiply(Literal(true_.truediv(right)), left) @@ -720,7 +732,13 @@ cdef class FloorDivide(MathsBinaryOperation): cdef void _compile_op(self, Program program): program.floordiv() + cdef Expression constant_left(self, Vector left, Expression right): + if left.eq(null_): + return NoOp + cdef Expression constant_right(self, Expression left, Vector right): + if right.eq(null_): + return NoOp if right.eq(true_): return Floor(left) @@ -732,7 +750,13 @@ cdef class Modulo(MathsBinaryOperation): cdef void _compile_op(self, Program program): program.mod() + cdef Expression constant_left(self, Vector left, Expression right): + if left.eq(null_): + return NoOp + cdef Expression constant_right(self, Expression left, Vector right): + if right.eq(null_): + return NoOp if right.eq(true_): return Fract(left) @@ -750,7 +774,13 @@ cdef class Power(MathsBinaryOperation): else: program.pow() + cdef Expression constant_left(self, Vector left, Expression right): + if left.eq(null_): + return NoOp + cdef Expression constant_right(self, Expression left, Vector right): + if right.eq(null_): + return NoOp if right.eq(true_): return Positive(left) diff --git a/tests/test_simplifier.py b/tests/test_simplifier.py index 71017035..bb9e8f63 100644 --- a/tests/test_simplifier.py +++ b/tests/test_simplifier.py @@ -281,6 +281,11 @@ def test_literal(self): """Literal left and right is evaluated""" self.assertSimplifiesTo(Add(Literal(5), Literal(10)), Literal(15)) + def test_null(self): + """Literal null on either side simplifies to null""" + self.assertSimplifiesTo(Add(Name('x'), Literal(null)), Literal(null), dynamic={'x'}) + self.assertSimplifiesTo(Add(Literal(null), Name('x')), Literal(null), dynamic={'x'}) + def test_zero(self): """Adding literal zero becomes Positive""" self.assertSimplifiesTo(Add(Literal(0), Name('x')), Positive(Name('x')), dynamic={'x'}) @@ -312,6 +317,11 @@ def test_literal(self): """Literal left and right is evaluated""" self.assertSimplifiesTo(Subtract(Literal(5), Literal(10)), Literal(-5)) + def test_null(self): + """Literal null on either side simplifies to null""" + self.assertSimplifiesTo(Subtract(Name('x'), Literal(null)), Literal(null), dynamic={'x'}) + self.assertSimplifiesTo(Subtract(Literal(null), Name('x')), Literal(null), dynamic={'x'}) + def test_subtract_zero(self): """Subtracting literal zero becomes Positive""" self.assertSimplifiesTo(Subtract(Name('x'), Literal(0)), Positive(Name('x')), dynamic={'x'}) @@ -339,6 +349,11 @@ def test_literal(self): """Literal left and right is evaluated""" self.assertSimplifiesTo(Multiply(Literal(5), Literal(10)), Literal(50)) + def test_null(self): + """Literal null on either side simplifies to null""" + self.assertSimplifiesTo(Multiply(Name('x'), Literal(null)), Literal(null), dynamic={'x'}) + self.assertSimplifiesTo(Multiply(Literal(null), Name('x')), Literal(null), dynamic={'x'}) + def test_multiply_one(self): """Multiplying by literal 1 becomes Positive""" self.assertSimplifiesTo(Multiply(Name('x'), Literal(1)), Positive(Name('x')), dynamic={'x'}) @@ -394,6 +409,11 @@ def test_literal(self): """Literal left and right is evaluated""" self.assertSimplifiesTo(Divide(Literal(5), Literal(10)), Literal(0.5)) + def test_null(self): + """Literal null on either side simplifies to null""" + self.assertSimplifiesTo(Divide(Name('x'), Literal(null)), Literal(null), dynamic={'x'}) + self.assertSimplifiesTo(Divide(Literal(null), Name('x')), Literal(null), dynamic={'x'}) + def test_divide_by_one(self): """Dividing by literal 1 becomes Positive""" self.assertSimplifiesTo(Divide(Name('x'), Literal(1)), Positive(Name('x')), dynamic={'x'}) @@ -417,6 +437,11 @@ def test_literal(self): """Literal left and right is evaluated""" self.assertSimplifiesTo(FloorDivide(Literal(5), Literal(10)), Literal(0)) + def test_null(self): + """Literal null on either side simplifies to null""" + self.assertSimplifiesTo(FloorDivide(Name('x'), Literal(null)), Literal(null), dynamic={'x'}) + self.assertSimplifiesTo(FloorDivide(Literal(null), Name('x')), Literal(null), dynamic={'x'}) + def test_divide_by_one(self): """Dividing by literal 1 becomes Floor""" self.assertSimplifiesTo(FloorDivide(Name('x'), Literal(1)), Floor(Name('x')), dynamic={'x'}) @@ -436,6 +461,11 @@ def test_literal(self): """Literal left and right is evaluated""" self.assertSimplifiesTo(Modulo(Literal(5), Literal(10)), Literal(5)) + def test_null(self): + """Literal null on either side simplifies to null""" + self.assertSimplifiesTo(Modulo(Name('x'), Literal(null)), Literal(null), dynamic={'x'}) + self.assertSimplifiesTo(Modulo(Literal(null), Name('x')), Literal(null), dynamic={'x'}) + def test_modulo_one(self): """Modulo literal 1 becomes Fract""" self.assertSimplifiesTo(Modulo(Name('x'), Literal(1)), Fract(Name('x')), dynamic={'x'}) @@ -455,6 +485,11 @@ def test_literal(self): """Literal left and right is evaluated""" self.assertSimplifiesTo(Power(Literal(5), Literal(2)), Literal(25)) + def test_null(self): + """Literal null on either side simplifies to null""" + self.assertSimplifiesTo(Power(Name('x'), Literal(null)), Literal(null), dynamic={'x'}) + self.assertSimplifiesTo(Power(Literal(null), Name('x')), Literal(null), dynamic={'x'}) + def test_raise_to_power_of_one(self): """Power to literal 1 becomes Positive""" self.assertSimplifiesTo(Power(Name('x'), Literal(1)), Positive(Name('x')), dynamic={'x'})