Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 46 additions & 8 deletions compiler/src/main/scala/edg/compiler/ExprEvaluate.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ object ExprEvaluate {

def evalBinary(binary: expr.BinaryExpr, lhs: ExprValue, rhs: ExprValue): ExprValue = {
import expr.BinaryExpr.Op

ErrorValue.aggregate(Seq(lhs, rhs)) match {
case Some(errorValue) => return errorValue // errors propagation takes priority
case None => () // continue with normal evaluation
}

binary.op match {
// Note promotion rules: range takes precedence, then float, then int
case Op.ADD => (lhs, rhs) match {
Expand Down Expand Up @@ -223,6 +229,12 @@ object ExprEvaluate {

def evalBinarySet(binarySet: expr.BinarySetExpr, lhs: ExprValue, rhs: ExprValue): ExprValue = {
import expr.BinarySetExpr.Op

ErrorValue.aggregate(Seq(lhs, rhs)) match {
case Some(errorValue) => return errorValue // errors propagation takes priority
case None => () // continue with normal evaluation
}

binarySet.op match {
// Note promotion rules: range takes precedence, then float, then int
// TODO: can we deduplicate these cases to delegate them to evalBinary?
Expand Down Expand Up @@ -272,6 +284,12 @@ object ExprEvaluate {

def evalUnary(unary: expr.UnaryExpr, `val`: ExprValue): ExprValue = {
import expr.UnaryExpr.Op

ErrorValue.aggregate(Seq(`val`)) match {
case Some(errorValue) => return errorValue // errors propagation takes priority
case None => () // continue with normal evaluation
}

(unary.op, `val`) match {
case (Op.NEGATE, `val`) => `val` match {
case RangeValue(valMin, valMax) =>
Expand Down Expand Up @@ -307,6 +325,13 @@ object ExprEvaluate {

def evalUnarySet(unarySet: expr.UnarySetExpr, vals: ExprValue, emptyValue: ExprValue): ExprValue = {
import expr.UnarySetExpr.Op

// note this does not short circuit out emptyValue if vals is not empty
ErrorValue.aggregate(Seq(vals, emptyValue)) match {
case Some(errorValue) => return errorValue // errors propagation takes priority
case None => () // continue with normal evaluation
}
Comment on lines +329 to +333

(unarySet.op, vals) match {
case (_, ArrayValue.Empty(_)) => emptyValue
// In this case we don't do numeric promotion
Expand Down Expand Up @@ -402,29 +427,42 @@ object ExprEvaluate {

def evalStruct(struct: expr.StructExpr, vals: Map[String, ExprValue]): ExprValue = ???

def evalRange(range: expr.RangeExpr, minimum: ExprValue, maximum: ExprValue): ExprValue = (minimum, maximum) match {
case (FloatPromotable(lhs), FloatPromotable(rhs)) => if (lhs <= rhs) {
RangeValue(lhs, rhs)
} else {
ErrorValue(Some(s"range($minimum, $maximum) is malformed, $minimum > $maximum"))
}
case _ => throw new ExprEvaluateException(s"Unknown range operands types $minimum $maximum from $range")
def evalRange(range: expr.RangeExpr, minimum: ExprValue, maximum: ExprValue): ExprValue = {
ErrorValue.aggregate(Seq(minimum, maximum)) match {
case Some(errorValue) => return errorValue // errors propagation takes priority
case None => () // continue with normal evaluation
}

(minimum, maximum) match {
case (FloatPromotable(lhs), FloatPromotable(rhs)) => if (lhs <= rhs) {
RangeValue(lhs, rhs)
} else {
ErrorValue(Some(s"range($minimum, $maximum) is malformed, $minimum > $maximum"))
}
case _ => throw new ExprEvaluateException(s"Unknown range operands types $minimum $maximum from $range")
}
}

def evalIfThenElse(ite: expr.IfThenElseExpr, cond: ExprValue, tru: ExprValue, fal: ExprValue): ExprValue =
cond match {
case ErrorValue(_) => cond
case BooleanValue(true) => tru
case BooleanValue(false) => fal
case _ => throw new ExprEvaluateException(s"Unknown condition types if $cond then $tru else $fal from $ite")
}

def evalExtract(extract: expr.ExtractExpr, container: ExprValue, index: ExprValue): ExprValue =
def evalExtract(extract: expr.ExtractExpr, container: ExprValue, index: ExprValue): ExprValue = {
ErrorValue.aggregate(Seq(container, index)) match {
case Some(errorValue) => return errorValue // errors propagation takes priority
case None => () // continue with normal evaluation
}
(container, index) match {
case (ArrayValue(container), IntValue(index)) => container(index.toInt)
case _ => throw new ExprEvaluateException(
s"Unknown operand types for extract element $index from $container from $extract"
)
}
}
}

class ExprEvaluate(refs: ConstProp, root: DesignPath) extends ValueExprMap[ExprValue] {
Expand Down
20 changes: 20 additions & 0 deletions compiler/src/main/scala/edg/compiler/ExprValue.scala
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,26 @@ object ExprValue {
}
}

object ErrorValue {
def aggregate(exprs: Seq[ExprValue]): Option[ErrorValue] = {
val errors = exprs.collect { case error: ErrorValue => error }
errors match {
case Seq() => None
case Seq(single) => Some(single)
case multiple => Some(aggregateErrors(multiple))
}
}

private def aggregateErrors(errors: Seq[ErrorValue]): ErrorValue = {
val msgs = errors.flatMap(_.msg)
if (msgs.isEmpty) {
ErrorValue(None)
} else {
ErrorValue(Some(msgs.mkString("; ")))
}
}
}

case class ErrorValue(msg: Option[String]) extends ExprValue {
def toLit: lit.ValueLit = Literal.Error(msg)
def toStringValue: String = s"error(${msg.getOrElse("")})"
Expand Down
19 changes: 18 additions & 1 deletion compiler/src/test/scala/edg/compiler/ConstPropAssignTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ class ConstPropAssignTest extends AnyFlatSpec {
assert(constProp.getAllSolved(IndirectDesignPath() + "a").isInstanceOf[ErrorValue])
}

it should "not propagate generated ErrorValues" in {
it should "not propagate ErrorValue-valued parameters" in {
import edgir.expr.expr.BinaryExpr.Op
val constProp = new ConstProp()
constProp.addDeclaration(DesignPath() + "x", ValInit.Range)
Expand All @@ -208,4 +208,21 @@ class ConstPropAssignTest extends AnyFlatSpec {
assert(constProp.getValue(IndirectDesignPath() + "a").get.isInstanceOf[ErrorValue])
constProp.getValue(IndirectDesignPath() + "b") shouldBe None
}

it should "propagate ErrorValues" in {
import edgir.expr.expr.BinaryExpr.Op
val constProp = new ConstProp()
constProp.addDeclaration(DesignPath() + "a", ValInit.Range)
constProp.addAssignExpr(
IndirectDesignPath() + "a",
ValueExpr.BinOp(
Op.ADD,
ValueExpr.BinOp(Op.SHRINK_MULT, ValueExpr.Literal(1, 1), ValueExpr.Literal(0, 2)),
ValueExpr.Literal(0, 0)
),
)

constProp.getErrors should not be empty
assert(constProp.getValue(IndirectDesignPath() + "a").get.isInstanceOf[ErrorValue])
}
}
Binary file modified edg/core/resources/edg-compiler-precompiled.jar
Binary file not shown.
Loading