diff --git a/compiler/src/main/scala/edg/ExprBuilder.scala b/compiler/src/main/scala/edg/ExprBuilder.scala index 905604c7f..4b2ef1756 100644 --- a/compiler/src/main/scala/edg/ExprBuilder.scala +++ b/compiler/src/main/scala/edg/ExprBuilder.scala @@ -34,6 +34,9 @@ object ExprBuilder { def Literal(valueMin: Double, valueMax: Double): expr.ValueExpr = // convenience method Literal(valueMin.toFloat, valueMax.toFloat) + def LiteralRangeEmpty(): expr.ValueExpr = + Literal(Float.NaN, Float.NaN) + def BinOp(op: expr.BinaryExpr.Op, lhs: expr.ValueExpr, rhs: expr.ValueExpr): expr.ValueExpr = expr.ValueExpr( expr = expr.ValueExpr.Expr.Binary(expr.BinaryExpr(op = op, lhs = Some(lhs), rhs = Some(rhs))) ) diff --git a/compiler/src/main/scala/edg/compiler/ExprEvaluate.scala b/compiler/src/main/scala/edg/compiler/ExprEvaluate.scala index d458db777..2d53cd4ab 100644 --- a/compiler/src/main/scala/edg/compiler/ExprEvaluate.scala +++ b/compiler/src/main/scala/edg/compiler/ExprEvaluate.scala @@ -21,9 +21,8 @@ object ExprEvaluate { case (RangeValue(lhsMin, lhsMax), RangeValue(rhsMin, rhsMax)) => val all = Seq(lhsMin + rhsMin, lhsMin + rhsMax, lhsMax + rhsMin, lhsMax + rhsMax) RangeValue(all.min, all.max) - case (RangeEmpty, RangeEmpty) => RangeEmpty - case (lhs: RangeValue, RangeEmpty) => lhs - case (RangeEmpty, rhs: RangeValue) => rhs + case (_: RangeType, RangeEmpty) => RangeEmpty + case (RangeEmpty, _: RangeType) => RangeEmpty case (RangeValue(lhsMin, lhsMax), FloatPromotable(rhs)) => RangeValue(lhsMin + rhs, lhsMax + rhs) case (FloatPromotable(lhs), RangeValue(rhsMin, rhsMax)) => @@ -39,9 +38,8 @@ object ExprEvaluate { case (RangeValue(lhsMin, lhsMax), RangeValue(rhsMin, rhsMax)) => val all = Seq(lhsMin * rhsMin, lhsMin * rhsMax, lhsMax * rhsMin, lhsMax * rhsMax) RangeValue(all.min, all.max) - case (RangeEmpty, RangeEmpty) => RangeEmpty - case (lhs: RangeValue, RangeEmpty) => RangeEmpty - case (RangeEmpty, rhs: RangeValue) => RangeEmpty + case (_: RangeType, RangeEmpty) => RangeEmpty + case (RangeEmpty, _: RangeType) => RangeEmpty case (RangeValue(lhsMin, lhsMax), FloatPromotable(rhs)) if rhs >= 0 => RangeValue(lhsMin * rhs, lhsMax * rhs) case (RangeValue(lhsMin, lhsMax), FloatPromotable(rhs)) if rhs < 0 => @@ -170,8 +168,8 @@ object ExprEvaluate { } case Op.INTERSECTION => (lhs, rhs) match { - case (RangeEmpty, _) => RangeEmpty // anything intersecting with empty is empty - case (_, RangeEmpty) => RangeEmpty + case (RangeEmpty, _: RangeType) => RangeEmpty // anything intersecting with empty is empty + case (_: RangeType, RangeEmpty) => RangeEmpty case (RangeValue(lhsMin, lhsMax), RangeValue(rhsMin, rhsMax)) => val (minMax, maxMin) = (math.min(lhsMax, rhsMax), math.max(lhsMin, rhsMin)) if (maxMin <= minMax) { @@ -305,7 +303,10 @@ object ExprEvaluate { case (Op.SUM, ArrayValue.ExtractBoolean(vals)) => IntValue(vals.count(_ == true)) case (Op.SUM, ArrayValue.UnpackRange(extracted)) => extracted match { case ArrayValue.UnpackRange.FullRange(valMins, valMaxs) => RangeValue(valMins.sum, valMaxs.sum) - case _ => ErrorValue("unpack_range(empty) is undefined") + case ArrayValue.UnpackRange.RangeWithEmpty(_, _) => RangeEmpty + case ArrayValue.UnpackRange.EmptyRange() => RangeEmpty + case ArrayValue.UnpackRange.EmptyArray() => + RangeValue(0, 0) // unreachable in practice, superseded by float 0 case } case (Op.ALL_TRUE, ArrayValue.Empty(_)) => BooleanValue(true) @@ -340,8 +341,9 @@ object ExprEvaluate { } else { // does not intersect, null set ErrorValue(f"intersection($extracted) produces empty set") } + case ArrayValue.UnpackRange.RangeWithEmpty(_, _) => RangeEmpty + case ArrayValue.UnpackRange.EmptyRange() => RangeEmpty // The implicit initial value of intersect is the full range - // TODO are these good semantics? case ArrayValue.UnpackRange.EmptyArray() => RangeValue(Float.NegativeInfinity, Float.PositiveInfinity) case _ => ErrorValue(f"intersection($vals) is undefined") } @@ -350,8 +352,8 @@ object ExprEvaluate { case (Op.HULL, ArrayValue.UnpackRange(extracted)) => extracted match { case ArrayValue.UnpackRange.FullRange(valMins, valMaxs) => RangeValue(valMins.min, valMaxs.max) case ArrayValue.UnpackRange.RangeWithEmpty(valMins, valMaxs) => RangeValue(valMins.min, valMaxs.max) - case ArrayValue.UnpackRange.EmptyArray() => RangeEmpty // TODO: should this be an error? case ArrayValue.UnpackRange.EmptyRange() => RangeEmpty + case ArrayValue.UnpackRange.EmptyArray() => RangeEmpty // TODO: should this be an error? } case (Op.HULL, ArrayValue.ExtractFloat(vals)) => RangeValue(vals.min, vals.max) diff --git a/compiler/src/test/scala/edg/compiler/ExprEvaluateTest.scala b/compiler/src/test/scala/edg/compiler/ExprEvaluateTest.scala index 7ce069716..1667a507d 100644 --- a/compiler/src/test/scala/edg/compiler/ExprEvaluateTest.scala +++ b/compiler/src/test/scala/edg/compiler/ExprEvaluateTest.scala @@ -12,8 +12,6 @@ class ExprEvaluateTest extends AnyFlatSpec { val constProp = new ConstProp() val evalTest = new ExprEvaluate(constProp, DesignPath()) - // TODO: add array tests once there is an array literal - it should "handle literals" in { evalTest.map(ValueExpr.Literal(Literal.Floating(2.0))) should equal(FloatValue(2.0)) evalTest.map(ValueExpr.Literal(Literal.Integer(42))) should equal(IntValue(42)) @@ -97,6 +95,17 @@ class ExprEvaluateTest extends AnyFlatSpec { it should "handle binary range ops" in { import edgir.expr.expr.BinaryExpr.Op + + evalTest.map( + ValueExpr.BinOp(Op.ADD, ValueExpr.Literal(4.0, 6.0), ValueExpr.Literal(5.0, 7.0)) + ) should equal(RangeValue(9.0, 13.0)) + evalTest.map( + ValueExpr.BinOp(Op.ADD, ValueExpr.Literal(5.0, 7.0), ValueExpr.LiteralRangeEmpty()) + ) should equal(RangeEmpty) + evalTest.map( + ValueExpr.BinOp(Op.ADD, ValueExpr.LiteralRangeEmpty(), ValueExpr.LiteralRangeEmpty()) + ) should equal(RangeEmpty) + evalTest.map( ValueExpr.BinOp(Op.INTERSECTION, ValueExpr.Literal(4.0, 6.0), ValueExpr.Literal(5.0, 7.0)) ) should equal(RangeValue(5.0, 6.0)) @@ -109,7 +118,12 @@ class ExprEvaluateTest extends AnyFlatSpec { evalTest.map( ValueExpr.BinOp(Op.INTERSECTION, ValueExpr.Literal(5.0, 7.0), ValueExpr.Literal(8.0, 10.0)) ) should equal(RangeEmpty) - // TODO test with empty ranges? + evalTest.map( + ValueExpr.BinOp(Op.INTERSECTION, ValueExpr.Literal(5.0, 7.0), ValueExpr.LiteralRangeEmpty()) + ) should equal(RangeEmpty) + evalTest.map( + ValueExpr.BinOp(Op.INTERSECTION, ValueExpr.LiteralRangeEmpty(), ValueExpr.LiteralRangeEmpty()) + ) should equal(RangeEmpty) evalTest.map(ValueExpr.BinOp(Op.HULL, ValueExpr.Literal(4.0, 6.0), ValueExpr.Literal(5.0, 7.0))) should equal( RangeValue(4.0, 7.0) @@ -123,7 +137,12 @@ class ExprEvaluateTest extends AnyFlatSpec { evalTest.map(ValueExpr.BinOp(Op.HULL, ValueExpr.Literal(5.0, 7.0), ValueExpr.Literal(8.0, 10.0))) should equal( RangeValue(5.0, 10.0) ) - // TODO test with empty ranges? + evalTest.map( + ValueExpr.BinOp(Op.HULL, ValueExpr.Literal(5.0, 7.0), ValueExpr.LiteralRangeEmpty()) + ) should equal(RangeValue(5.0, 7.0)) + evalTest.map( + ValueExpr.BinOp(Op.HULL, ValueExpr.LiteralRangeEmpty(), ValueExpr.LiteralRangeEmpty()) + ) should equal(RangeEmpty) evalTest.map(ValueExpr.BinOp(Op.WITHIN, ValueExpr.Literal(6.0), ValueExpr.Literal(5.0, 7.0))) should equal( BooleanValue(true) @@ -150,6 +169,13 @@ class ExprEvaluateTest extends AnyFlatSpec { evalTest.map(ValueExpr.BinOp(Op.WITHIN, ValueExpr.Literal(5.5, 7.5), ValueExpr.Literal(5.0, 7.0))) should equal( BooleanValue(false) ) + + evalTest.map(ValueExpr.BinOp(Op.WITHIN, ValueExpr.Literal(5.5, 7.5), ValueExpr.LiteralRangeEmpty())) should equal( + BooleanValue(false) + ) + evalTest.map(ValueExpr.BinOp(Op.WITHIN, ValueExpr.LiteralRangeEmpty(), ValueExpr.Literal(5.0, 7.0))) should equal( + BooleanValue(true) + ) } it should "handle arithmetic promotions" in { diff --git a/edg/core/resources/edg-compiler-precompiled.jar b/edg/core/resources/edg-compiler-precompiled.jar index 15a0eb459..5aa815ab5 100644 Binary files a/edg/core/resources/edg-compiler-precompiled.jar and b/edg/core/resources/edg-compiler-precompiled.jar differ