Skip to content

Commit be179d1

Browse files
committed
Merge branch 'master' into smuv3.2
2 parents 39019a3 + 99b9e76 commit be179d1

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+817
-515
lines changed

compiler/src/main/scala/edg/compiler/Compiler.scala

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ class AssignNamer() {
117117
}
118118

119119
object Compiler {
120-
final val kExpectedProtoVersion = 6
120+
final val kExpectedProtoVersion = 7
121121
}
122122

123123
/** Compiler for a particular design, with an associated library to elaborate references from.
@@ -258,7 +258,7 @@ class Compiler private (
258258
// Returns all errors, by scanning the design tree for errors and adding errors accumulated through the compile
259259
// process
260260
def getErrors(): Seq[CompilerError] = {
261-
val pendingErrors = elaboratePending.getMissingValue.map { missingNode =>
261+
val pendingErrors = elaboratePending.getMissingValues.map { missingNode =>
262262
CompilerError.Unelaborated(missingNode, elaboratePending.nodeMissing(missingNode))
263263
}.toSeq
264264

@@ -1628,15 +1628,18 @@ class Compiler private (
16281628
val partialCompileIgnoredRecords = mutable.Set[ElaborateRecord]()
16291629

16301630
// repeat as long as there is work ready, and all the ready work isn't marked to be ignored
1631-
var readyList = Set[ElaborateRecord]()
1631+
var readyList = Iterable[ElaborateRecord]()
16321632
do {
1633-
readyList = elaboratePending.getReady -- partialCompileIgnoredRecords
1633+
// TODO this is kind of ugly and expensive in that it keeps filtering the ignored records
1634+
// ideally this should be done at the getReady side, but these also need to be restored
1635+
// when the compiler forks
1636+
readyList = elaboratePending.getReady.filter(!partialCompileIgnoredRecords.contains(_))
16341637
readyList.foreach { elaborateRecord =>
16351638
try {
16361639
elaborateRecord match {
16371640
case elaborateRecord @ ElaborateRecord.ExpandBlock(blockPath, blockClass, blockProgress) =>
16381641
if (partial.blocks.contains(blockPath) || partial.classes.contains(blockClass)) {
1639-
partialCompileIgnoredRecords.add(elaborateRecord)
1642+
partialCompileIgnoredRecords += elaborateRecord
16401643
} else {
16411644
expandBlock(blockPath, blockProgress)
16421645
elaboratePending.setValue(elaborateRecord, None)
@@ -1653,7 +1656,7 @@ class Compiler private (
16531656
case elaborateRecord @ ElaborateRecord.Parameter(root, rootClasses, postfix, param) =>
16541657
val container = resolveBlock(root).asInstanceOf[wir.HasParams]
16551658
if (paramMatchesPartial(root, rootClasses, postfix)) {
1656-
partialCompileIgnoredRecords.add(elaborateRecord)
1659+
partialCompileIgnoredRecords += elaborateRecord
16571660
} else {
16581661
constProp.addDeclaration(root ++ postfix, param)
16591662
elaboratePending.setValue(elaborateRecord, None)
@@ -1695,7 +1698,7 @@ class Compiler private (
16951698
}
16961699

16971700
def evaluateExpr(root: DesignPath, value: expr.ValueExpr): ExprResult = {
1698-
new ExprEvaluatePartial(constProp, root).map(value)
1701+
new ExprEvaluatePartial(constProp.getValue, root).map(value)
16991702
}
17001703

17011704
// Primarily used for unit tests, TODO clean up this API?

compiler/src/main/scala/edg/compiler/CompilerError.scala

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ sealed trait CompilerError {
1313
}
1414

1515
object CompilerError {
16-
case class Unelaborated(record: ElaborateRecord, missing: Set[ElaborateRecord]) extends CompilerError {
16+
case class Unelaborated(record: ElaborateRecord, missing: Iterable[ElaborateRecord]) extends CompilerError {
1717
// These errors may be redundant with below, but provides dependency data
1818
override def toString: String = s"Unelaborated missing dependencies $record:\n" +
1919
s"${missing.map(x => s"- $x").mkString("\n")}"
@@ -104,16 +104,15 @@ object CompilerError {
104104
}
105105
}
106106

107-
case class OverAssign(target: IndirectDesignPath, causes: Seq[OverAssignCause]) extends CompilerError {
108-
override def toString: String = s"Overassign to $target:\n" +
109-
s"${causes.map(x => s"- $x").mkString("\n")}"
107+
case class ExprError(target: IndirectDesignPath, msg: String) extends CompilerError {
108+
override def toString: String = s"Expr error at $target: $msg"
110109

111110
override def toIr: edgcompiler.ErrorRecord = {
112111
edgcompiler.ErrorRecord(
113112
path = Some(target.toLocalPath),
114-
kind = "Overassign",
113+
kind = "Expr",
115114
name = "",
116-
details = causes.map(_.toString).mkString(", ")
115+
details = msg
117116
)
118117
}
119118
}
@@ -166,7 +165,7 @@ object CompilerError {
166165
root: DesignPath,
167166
constrName: String,
168167
value: expr.ValueExpr,
169-
missing: Set[IndirectDesignPath]
168+
missing: Iterable[IndirectDesignPath]
170169
) extends AssertionError {
171170
override def toString: String =
172171
s"Unevaluated assertion: $root.$constrName: missing ${missing.mkString(", ")} in ${ExprToString.apply(value)}"

compiler/src/main/scala/edg/compiler/ConstProp.scala

Lines changed: 40 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
11
package edg.compiler
22

3-
import scala.collection.mutable
4-
import scala.collection.Set
3+
import edg.ExprBuilder
4+
import edg.compiler.CompilerError.ExprError
5+
import edg.util.DependencyGraph
6+
import edg.wir._
57
import edgir.expr.expr
68
import edgir.init.init
7-
import edg.wir._
8-
import edg.util.{DependencyGraph, MutableBiMap}
9-
import edg.ExprBuilder
109
import edgir.ref.ref.LocalPath
1110

12-
case class AssignRecord(target: IndirectDesignPath, root: DesignPath, value: expr.ValueExpr)
11+
import scala.collection.{Set, mutable}
1312

14-
case class OverassignRecord(assigns: mutable.Set[(DesignPath, String, expr.ValueExpr)] = mutable.Set())
13+
case class AssignRecord(target: IndirectDesignPath, root: DesignPath, value: expr.ValueExpr)
1514

1615
sealed trait ConnectedLinkRecord // a record in the connected link directed graph
1716
object ConnectedLinkRecord {
@@ -43,6 +42,7 @@ class ConstProp() {
4342
// Assign statements are added to the dependency graph only when arrays are ready
4443
// This is the authoritative source for the state of any param - in the graph (and its dependencies), or value solved
4544
// CONNECTED_LINK has an empty value but indicates that the path was resolved in that data structure
45+
// NAME has an empty value but indicates declaration (existence in paramTypes)
4646
private val params = DependencyGraph[IndirectDesignPath, ExprValue]()
4747
// Parameter types are used to track declared parameters
4848
// Undeclared parameters cannot have values set, but can be forced (though the value is not effective until declared)
@@ -53,21 +53,20 @@ class ConstProp() {
5353
// Params that have a forced/override value, so they arent over-assigned.
5454
private val forcedParams = mutable.Set[IndirectDesignPath]()
5555

56-
// Overassigns, for error tracking
57-
// This only tracks overassigns that were discarded, not including assigns that took effect.
58-
// Additional analysis is needed to get the full set of conflicting assigns.
59-
private val discardOverassigns = mutable.HashMap[IndirectDesignPath, OverassignRecord]()
56+
// Errors that were generated during the process of resolving parameters, including overassigns
57+
// A value may or may not exist (and may or not have been propagated) in the param dependency graph
58+
private val paramErrors = mutable.HashMap[IndirectDesignPath, mutable.ListBuffer[ErrorValue]]()
6059

6160
def initFrom(that: ConstProp): Unit = {
6261
require(paramAssign.isEmpty && paramSource.isEmpty && paramTypes.isEmpty && forcedParams.isEmpty
63-
&& discardOverassigns.isEmpty)
62+
&& paramErrors.isEmpty)
6463
paramAssign.addAll(that.paramAssign)
6564
paramSource.addAll(that.paramSource)
6665
params.initFrom(that.params)
6766
paramTypes.addAll(that.paramTypes)
6867
connectedLink.initFrom(that.connectedLink)
6968
forcedParams.addAll(that.forcedParams)
70-
discardOverassigns.addAll(that.discardOverassigns)
69+
paramErrors.addAll(that.paramErrors)
7170
}
7271

7372
//
@@ -108,21 +107,20 @@ class ConstProp() {
108107
connectedLink.setValue(ready, DesignPath())
109108
}
110109

111-
var readyList = Set[IndirectDesignPath]()
110+
var readyList = Iterable[IndirectDesignPath]()
112111
do {
113-
// ignore params where we haven't seen the decl yet, to allow forced-assign when the block is expanded
114-
// TODO support this for all params, including indirect ones (eg, name)
115-
readyList = params.getReady.filter { elt =>
116-
DesignPath.fromIndirectOption(elt) match {
117-
case Some(elt) => paramTypes.keySet.contains(elt.asIndirect)
118-
case None => true
119-
}
120-
}
112+
readyList = params.getReady
121113
readyList.foreach { constrTarget =>
122114
val assign = paramAssign(constrTarget)
123-
new ExprEvaluatePartial(this, assign.root).map(assign.value) match {
115+
new ExprEvaluatePartial(getValue, assign.root).map(assign.value) match {
124116
case ExprResult.Result(result) =>
125-
params.setValue(constrTarget, result)
117+
result match {
118+
case result @ ErrorValue(_) =>
119+
paramErrors.getOrElseUpdate(constrTarget, mutable.ListBuffer()).append(result)
120+
params.clearReadyNode(constrTarget)
121+
case result => params.setValue(constrTarget, result)
122+
}
123+
126124
onParamSolved(constrTarget, result)
127125
case ExprResult.Missing(missing) => // account for CONNECTED_LINK prefix
128126
val missingCorrected = missing.map { path =>
@@ -161,6 +159,7 @@ class ConstProp() {
161159
case _ => throw new NotImplementedError(s"Unknown param declaration / init $decl")
162160
}
163161
paramTypes.put(target.asIndirect, paramType)
162+
params.setValue(target.asIndirect + IndirectStep.Name, BooleanValue(false)) // dummy value
164163
update()
165164
}
166165

@@ -192,22 +191,29 @@ class ConstProp() {
192191
require(target.splitConnectedLink.isEmpty, "cannot set CONNECTED_LINK")
193192
val paramSourceRecord = (root, constrName, targetExpr)
194193

194+
// ignore params where we haven't seen the decl yet, to allow forced-assign when the block is expanded
195+
val paramTypesDep = DesignPath.fromIndirectOption(target) match {
196+
case Some(path) => Seq(path.asIndirect + IndirectStep.Name)
197+
case None => Seq() // has indirect step, no direct decl
198+
}
195199
if (forced) {
196200
require(!forcedParams.contains(target), s"attempt to re-force $target")
197201
forcedParams.add(target)
198202
require(
199203
!params.valueDefinedAt(target),
200204
s"forced value must be set before value is resolved, prior ${paramSource(target)}"
201205
)
202-
params.addNode(target, Seq(), overwrite = true) // forced can overwrite other records
206+
params.addNode(target, paramTypesDep, overwrite = true) // forced can overwrite other records
203207
} else {
204208
if (!forcedParams.contains(target)) {
205-
if (params.nodeDefinedAt(target)) {
206-
val record = discardOverassigns.getOrElseUpdate(target, OverassignRecord())
207-
record.assigns.add(paramSourceRecord)
209+
if (params.nodeDefinedAt(target)) { // TODO add propagated assign
210+
val (prevRoot, prevConstr, _) = paramSource.get(target).getOrElse("?", "?", "")
211+
paramErrors.getOrElseUpdate(target, mutable.ListBuffer()).append(
212+
ErrorValue(s"over-assign from $root.$constrName, prev assigned from $prevRoot.$prevConstr")
213+
)
208214
return // first set "wins"
209215
}
210-
params.addNode(target, Seq())
216+
params.addNode(target, paramTypesDep)
211217
} else {
212218
return // ignored - param was forced, discard the new assign
213219
}
@@ -254,7 +260,7 @@ class ConstProp() {
254260
}
255261

256262
/** Returns the value of a parameter, or None if it does not have a value (yet?). Can be used to check if parameters
257-
* are resolved yet by testing against None.
263+
* are resolved yet by testing against None. Cannot return an ErrorValue
258264
*/
259265
def getValue(param: IndirectDesignPath): Option[ExprValue] = {
260266
resolveConnectedLink(param) match {
@@ -264,7 +270,6 @@ class ConstProp() {
264270

265271
}
266272
def getValue(param: DesignPath): Option[ExprValue] = {
267-
// TODO should this be an implicit conversion?
268273
getValue(param.asIndirect)
269274
}
270275

@@ -282,20 +287,14 @@ class ConstProp() {
282287
* references.
283288
*/
284289
def getUnsolved: Set[IndirectDesignPath] = {
285-
paramTypes.keySet.toSet -- params.knownValueKeys
290+
paramTypes.keySet.toSet -- params.knownValueKeys -- paramErrors.keys
286291
}
287292

288293
def getAllSolved: Map[IndirectDesignPath, ExprValue] = params.toMap
289294

290-
def getErrors: Seq[CompilerError] = {
291-
discardOverassigns.map { case (target, record) =>
292-
val propagatedAssign = paramSource.get(target).map { case (root, constrName, value) =>
293-
CompilerError.OverAssignCause.Assign(target, root, constrName, value)
294-
}.toSeq
295-
val discardedAssigns = record.assigns.map { case (root, constrName, value) =>
296-
CompilerError.OverAssignCause.Assign(target, root, constrName, value)
297-
}
298-
CompilerError.OverAssign(target, propagatedAssign ++ discardedAssigns)
295+
def getErrors: Seq[ExprError] = {
296+
paramErrors.flatMap { case (target, errors) =>
297+
errors.map(error => ExprError(target, error.msg))
299298
}.toSeq
300299
}
301300
}

compiler/src/main/scala/edg/compiler/ExprEvaluate.scala

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,21 @@ object ExprEvaluate {
5656
case _ =>
5757
throw new ExprEvaluateException(s"Unknown binary operand types in $lhs ${binary.op} $rhs from $binary")
5858
}
59+
case Op.SHRINK_MULT => (lhs, rhs) match {
60+
case (RangeValue(targetMin, targetMax), RangeValue(contribMin, contribMax)) =>
61+
val lower = contribMax * targetMin
62+
val upper = contribMin * targetMax
63+
if (lower > upper) {
64+
ErrorValue(s"shrink_mult($lhs, $rhs) produces empty range, target $lhs tighter tol than contrib $rhs")
65+
} else {
66+
RangeValue(lower, upper)
67+
}
68+
case (RangeEmpty, RangeEmpty) => RangeEmpty
69+
case (lhs: RangeValue, RangeEmpty) => RangeEmpty
70+
case (RangeEmpty, rhs: RangeValue) => RangeEmpty
71+
case _ =>
72+
throw new ExprEvaluateException(s"Unknown binary operand types in $lhs ${binary.op} $rhs from $binary")
73+
}
5974

6075
case Op.AND => (lhs, rhs) match {
6176
case (BooleanValue(lhs), BooleanValue(rhs)) => BooleanValue(lhs && rhs)
@@ -270,10 +285,8 @@ object ExprEvaluate {
270285
case (Op.MIN, RangeValue(valMin, _)) => FloatValue(valMin)
271286
case (Op.MAX, RangeValue(_, valMax)) => FloatValue(valMax)
272287

273-
// TODO can we have stricter semantics to avoid min(RangeEmpty) and max(RangeEmpty)?
274-
// This just NaNs out so at least it propagates
275-
case (Op.MAX, RangeEmpty) => FloatValue(Float.NaN)
276-
case (Op.MIN, RangeEmpty) => FloatValue(Float.NaN)
288+
case (Op.MAX, RangeEmpty) => ErrorValue("max(RangeEmpty) is undefined")
289+
case (Op.MIN, RangeEmpty) => ErrorValue("min(RangeEmpty) is undefined")
277290

278291
case (Op.CENTER, RangeValue(valMin, valMax)) => FloatValue((valMin + valMax) / 2)
279292
case (Op.WIDTH, RangeValue(valMin, valMax)) => FloatValue(math.abs(valMax - valMin))
@@ -292,7 +305,7 @@ object ExprEvaluate {
292305
case (Op.SUM, ArrayValue.ExtractBoolean(vals)) => IntValue(vals.count(_ == true))
293306
case (Op.SUM, ArrayValue.UnpackRange(extracted)) => extracted match {
294307
case ArrayValue.UnpackRange.FullRange(valMins, valMaxs) => RangeValue(valMins.sum, valMaxs.sum)
295-
case _ => RangeEmpty // TODO how should sum behave on empty ranges?
308+
case _ => ErrorValue("unpack_range(empty) is undefined")
296309
}
297310

298311
case (Op.ALL_TRUE, ArrayValue.Empty(_)) => BooleanValue(true)
@@ -311,13 +324,11 @@ object ExprEvaluate {
311324
case (Op.MINIMUM, ArrayValue.ExtractFloat(vals)) => FloatValue(vals.min)
312325
case (Op.MINIMUM, ArrayValue.ExtractInt(vals)) => IntValue(vals.min)
313326

314-
// TODO this should be a user-level assertion instead of a compiler error
315-
case (Op.SET_EXTRACT, ArrayValue.Empty(_)) =>
316-
throw new ExprEvaluateException(s"SetExtract with empty values from $unarySet")
327+
case (Op.SET_EXTRACT, ArrayValue.Empty(_)) => ErrorValue("set_extract(empty) is undefined")
317328
case (Op.SET_EXTRACT, ArrayValue(vals)) => if (vals.forall(_ == vals.head)) {
318329
vals.head
319330
} else {
320-
throw new ExprEvaluateException(s"SetExtract with non-equal values $vals from $unarySet")
331+
ErrorValue(f"set_extract($vals) with non-equal values")
321332
}
322333

323334
// Any empty value means the expression result is empty
@@ -327,19 +338,20 @@ object ExprEvaluate {
327338
if (maxMin <= minMax) {
328339
RangeValue(maxMin, minMax)
329340
} else { // does not intersect, null set
330-
RangeEmpty
341+
ErrorValue(f"intersection($extracted) produces empty set")
331342
}
332343
// The implicit initial value of intersect is the full range
333344
// TODO are these good semantics?
334345
case ArrayValue.UnpackRange.EmptyArray() => RangeValue(Float.NegativeInfinity, Float.PositiveInfinity)
335-
case _ => RangeEmpty
346+
case _ => ErrorValue(f"intersection($vals) is undefined")
336347
}
337348

338349
// Any value is used (empty effectively discarded)
339350
case (Op.HULL, ArrayValue.UnpackRange(extracted)) => extracted match {
340351
case ArrayValue.UnpackRange.FullRange(valMins, valMaxs) => RangeValue(valMins.min, valMaxs.max)
341352
case ArrayValue.UnpackRange.RangeWithEmpty(valMins, valMaxs) => RangeValue(valMins.min, valMaxs.max)
342-
case _ => RangeEmpty
353+
case ArrayValue.UnpackRange.EmptyArray() => RangeEmpty // TODO: should this be an error?
354+
case ArrayValue.UnpackRange.EmptyRange() => RangeEmpty
343355
}
344356
case (Op.HULL, ArrayValue.ExtractFloat(vals)) => RangeValue(vals.min, vals.max)
345357

@@ -382,7 +394,7 @@ object ExprEvaluate {
382394
case (FloatPromotable(lhs), FloatPromotable(rhs)) => if (lhs <= rhs) {
383395
RangeValue(lhs, rhs)
384396
} else {
385-
throw new ExprEvaluateException(s"Range with min $minimum not <= max $maximum from $range")
397+
ErrorValue(s"range($minimum, $maximum) is malformed, $minimum > $maximum")
386398
}
387399
case _ => throw new ExprEvaluateException(s"Unknown range operands types $minimum $maximum from $range")
388400
}

compiler/src/main/scala/edg/compiler/ExprEvaluatePartial.scala

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ object ExprResult {
1616
* missing set may increase as more parameters are solved, because some expressions have dynamic references. For
1717
* example, if-then-else additionally depends on either true or false subexprs once the condition is known.
1818
*/
19-
class ExprEvaluatePartial(refs: ConstProp, root: DesignPath) extends ValueExprMap[ExprResult] {
19+
class ExprEvaluatePartial(refResolver: IndirectDesignPath => Option[ExprValue], root: DesignPath)
20+
extends ValueExprMap[ExprResult] {
2021
override def mapLiteral(literal: lit.ValueLit): ExprResult =
2122
ExprResult.Result(ExprEvaluate.evalLiteral(literal))
2223

@@ -126,11 +127,11 @@ class ExprEvaluatePartial(refs: ConstProp, root: DesignPath) extends ValueExprMa
126127
val container = mapExtract.getContainer.expr.ref.getOrElse( // TODO restrict allowed types in proto
127128
throw new ExprEvaluateException(s"Non-ref container type in mapExtract $mapExtract"))
128129
val containerPath = root ++ container
129-
refs.getValue(containerPath.asIndirect + IndirectStep.Elements) match {
130+
refResolver(containerPath.asIndirect + IndirectStep.Elements) match {
130131
case Some(paramValue) =>
131132
val eltsVals = ArrayValue.ExtractText(paramValue).map { elt =>
132133
val eltPath = containerPath.asIndirect + elt ++ mapExtract.getPath
133-
refs.getValue(eltPath) match {
134+
refResolver(eltPath) match {
134135
case Some(value) => ExprResult.Result(value)
135136
case None => ExprResult.Missing(Set(eltPath))
136137
}
@@ -151,7 +152,7 @@ class ExprEvaluatePartial(refs: ConstProp, root: DesignPath) extends ValueExprMa
151152

152153
override def mapRef(path: ref.LocalPath): ExprResult = {
153154
val refPath = root.asIndirect ++ path
154-
refs.getValue(refPath) match {
155+
refResolver(refPath) match {
155156
case Some(value) => ExprResult.Result(value)
156157
case None => ExprResult.Missing(Set(refPath))
157158
}

0 commit comments

Comments
 (0)