blob: a85a668e07576d1cd33e091f512f59333bcf6857 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.daffodil.oolag
import org.apache.daffodil.exceptions.Assert
import org.apache.daffodil.util.Logging
import org.apache.daffodil.util._
import org.apache.daffodil.exceptions.UnsuppressableException
import org.apache.daffodil.api.Diagnostic
import org.apache.daffodil.api.WithDiagnostics
import org.apache.daffodil.util.Misc
import org.apache.daffodil.exceptions.ThinException
import org.apache.daffodil.util.Maybe._
import scala.collection.mutable
/**
* OOLAG = Object-oriented Lazy Attribute Grammars
*
* It's a collection of techniques for compilation/transformation
* in a functional programming style. You can find an article about
* Attribute Grammars on Wikipedia.
*/
object OOLAG extends Logging {
private val indent_ = new ThreadLocal[Int] {
override def initialValue = 0
}
final def indent = indent_.get()
final def setIndent(i: Int) = indent_.set(i)
/**
* For when we can continue to gather more errors after one error
* has occurred.
*
* The point of keepGoing: users don't have to build try/catches or
* know about which exceptions being thrown are ones that should or
* should not be caught. If the body fails, the alt is computed and
* returned.
*
* If body fails with an OOLAG exception of the right kind, then
* alt is evaluated and returned instead.
*
* Anyplace that calls keepGoing should have a comment saying why it
* is using it. There should be very few places that use it.
*/
def keepGoing[T](alt: => T)(body: => T) = {
try {
body
} catch {
// We suppress errors here because a rethrow indicates that somebody else
// has already recorded the exception in the diagnostics, and it was of the
// kind that can be recorded and issued later as a compile-time diagnostic.
case e: OOLAGRethrowException =>
log(LogLevel.OOLAGDebug, "OOLAG.keepGoing is suppressing exception already recorded: %s", e)
alt
}
}
sealed abstract class Args
case object OneArg extends Args
case object ZeroArgs extends Args
/**
* Convenience class for implementing OOLAGHost trait.
*
* Insures context is set by way of construction. Use the trait and
* implement the context yourself if you want to make use of setting the
* context after the objects are constructed.
*/
abstract class OOLAGHostImpl private (
oolagContextArg: OOLAGHost,
final override val nArgs: Args)
extends OOLAGHost {
def this(oolagContext: OOLAGHost) = this(oolagContext, OneArg)
def this() = this(null, ZeroArgs)
final protected override def oolagContextViaArgs = {
Option(oolagContextArg) // Option since Option(null) is None.
}
}
/**
* An OOLAGHost, or OOLAG for short, is a collection of OOLAGValues
* or LVs for short.
*
* The way these are generally used now is like this
* {{{
* lazy val foo = LV('foo) {...calculation...}.value
* }}}
* Why scala needs 'real' Lisp-style macros: Well wouldn't it be
* nicer if I could write:
* {{{
* defAttribute foo {... calculation ...}
* }}}
* and have that be equivalent the more verbose stuff above it?
* But I digress...
*
* OOLAGHosts support a feature called requiredEvaluations.
* This is a lazy function the arguments to which are only evaluated
* when the isError summary operation is invoked.
*
* The requiredEvaluations function takes any number of arguments, they are
* of type Any. Suppose we have the LV named 'foo' from above. If
* we want to say that our particular OOLAGHost-derived class must
* evaluate the 'foo' LV otherwise we don't know if it has errors or not,
* then we write:
* {{{
* requiredEvaluationsAlways(foo)
* }}}
* This goes at the top of the class definition. When isError is invoked
* the value of 'foo' will be computed if it has not been attempted
* already. This insures that the value exists for 'foo', or any errors/warnings
* to be determined by its calculation have been recorded.
*
* There is a variant called requiredEvaluationsIfActivated(...) which
* is similar, but the evaluations only occur if the object is marked as active
* via setRequiredEvaluationsActive().
*/
trait OOLAGHost
extends Logging with WithDiagnostics
with NamedMixinBase {
protected def oolagContextViaArgs: Option[OOLAGHost] = None
protected def nArgs: Args = OneArg
private var oolagContextViaSet: Option[OOLAGHost] = None
final def setOOLAGContext(oolagContextArg: OOLAGHost): Unit = {
Assert.usage(nArgs == ZeroArgs, "Cannot set oolag context if it was provided as a constructor arg.")
if (oolagContextViaSet != None)
Assert.usageError("Cannot set oolag context more than once.")
oolagContextViaSet = Option(oolagContextArg) // Option since Option(null) is None
centralizeEvalFunctionsWhenReady()
}
final def hasOOLAGRootSetup: Boolean = {
if (nArgs == OneArg) true
else if (oolagContextViaSet.isDefined) true
else
false
}
/**
* Used to check things that OOLAG doesn't protect against such as
* index out of bounds.
*
* When we rationalized some of the try/catches so they catch less-broad
* catagories of exceptions, well now some things are ambiguous failures.
* If lazy val foo = schemas(0).schemaDocs(0) fails with index-out-of-range,
* is that a programmer error, or just the computation failing because
* there aren't any schemas, so the lazy val foo should fail, causing an
* OOLAG capture of the error.
*
* In the past, OOLAG would have caught this indexing exception,
* converted it to an SDE, and in the case where it was a programmer
* mistake, it would have masked that mistake.
*
* Now we've flipped it. Now OOLAG won't catch this, but we need a way to
* say explicitly, that we're assuming there are schemas, so schema(0)
* should work, and if it doesn't, OOLAG should catch this and abandon
* computing whatever attribute we were trying to compute, and put that
* attribute in the 'already been tried' state.
*/
final def assuming(pred: Boolean) = {
if (!pred) throw AssumptionFailed
}
/**
* Either we were called with a constructor arg that provides the oolagContext,
* or we were constructed with no args, and in that case someone must call
* setOOLAGContext before we access the oolagContext.
*/
final lazy val optOolagContext = nArgs match {
case ZeroArgs => {
Assert.usage(oolagContextViaSet != None, "Must call setOOLAGContext before accessing when OOLAGHost is constructed with no args.")
oolagContextViaSet
}
case OneArg => {
val oc = oolagContextViaArgs
Assert.invariant(oc.isEmpty || (oc.get ne null))
oc
}
}
final lazy val oolagContext = optOolagContext.get
/**
* My parent, unless I am the root and have no parent. In that
* case this aborts with a usage error. Programmer should have
* called isOOLAGRoot first.
*/
protected final def oolagParent: OOLAGHost = {
if (!isOOLAGRoot) oolagContext
else Assert.usageError("parent of root OOLAGHost not allowed.")
}
/**
* Take me to the root of this OOLAG nest.
*/
private lazy val oolagRoot: OOLAGHost = {
if (isOOLAGRoot) this
else oolagContext.oolagRoot
}
private def isOOLAGRoot = {
val res = optOolagContext.isEmpty
res
}
lazy val path: String = {
if (isOOLAGRoot) diagnosticDebugName else oolagParent.path + ":" + diagnosticDebugName
}
/**
* Detect circular relationships among LVs.
*/
// I don't like this var for all those thread-safety reasons, but otherwise we have to
// carry a context list with us throughout the computation
final var currentOVList: Seq[OOLAGValueBase] = Nil
/**
* The factory for OOLAGValues is LV.
*/
protected final def LV[T](sym: Symbol)(body: => T): OOLAGValue[T] = {
val lv = new OOLAGValue[T](this, sym.name, body)
lv
}
/*
* requiredEvaluations feature
*
* An object either uses requiredEvaluationsAlways, or requiredEvaluationsIfActivated.
*
* requiredEvaluationsIfActivated is for objects in the abstract syntax tree (AST) where
* the mere construction of them is not sufficient to know if the required evaluations
* should occur. The objects may or may not ultimately be needed.
*
* requiredEvaluationsAlways is for top level things like the OOLAG Root object,
* and objects where their construction is enough to know that they should have
* these required evaluations occur.
*
* They are maintained as two separate lists of eval functions aka thunks to be run.
*
* If it uses requiredEvaluationsAlways, then the expressions will be evaluated
* as part of compilation even if nothing else demands their values. Just constructing
* the object queues these thunks for evaluation.
*
* If it uses requiredEvaluationsIfActivated, then the expresions are ignored
* unless setRequiredEvaluationsActive() is called on the same object. After that
* call, all expressions from calls to requiredEvaluationsIfActivated are queued for
* evaluation, and any additional calls toe requiredEvaluationsIfActive will also be
* queued for evaluation, and all will be evaluated even if nothing else demands their
* values.
*
* If no call to setRequiredEvaluationsActive() occurs, then expression args for
* requiredEvaluationsIfActivated will not be evaluated.
*/
private var requiredEvalCount = 0 // used to generate unique names.
private val requiredEvalName = Misc.getNameFromClass(this) + "_requiredEvaluation_"
private var requiredEvalFunctions: List[OOLAGValueBase] = Nil // for evaluation unconditionally
private var requiredEvalIfActivatedFunctions: List[OOLAGValueBase] = Nil // for evaluation only if activated.
/**
* Used to force evaluation for error checking unconditionally.
*
* Creating the object will queue up the expression for evaluation later
* when checkErrors is called.
*
* This is for root-of-the-AST objects, where we don't conditionally construct them.
* Rather, upon construction we know we positively ARE adding them to the AST.
*/
protected final def requiredEvaluationsAlways(arg: => Any): Unit = {
requiredEvaluationsAlways(thunk(arg))
}
/**
* Unconditionally, evaluate the LV arg in order to insure all checks for this
* object are performed.
*/
private def requiredEvaluationsAlways(lv: OOLAGValueBase) : Unit = {
val accumPoint =
if (this.hasOOLAGRootSetup)
oolagRoot
else
this
accumPoint.requiredEvalFunctions +:= lv
}
/**
* Wraps an LV around an expression that is a non LV
*/
private def thunk(arg: => Any) : OOLAGValueBase = {
val lv = LV(Symbol(requiredEvalName + requiredEvalCount)) {
arg
}
requiredEvalCount += 1
lv
}
/**
* When it is possible to centralize (on the OOLAG Root object) all the
* locally enqueued required evaluation thunks, do so. The root needs to be established first,
* and the object activated if the requiredEvaluationsIfActive method is used.
*/
private def centralizeEvalFunctionsWhenReady(): Unit = {
if (optOolagContext.isDefined) {
//
// If the context is now available centralize the required eval functions that are always
// to be evaluated.
//
oolagRoot.requiredEvalFunctions = this.requiredEvalFunctions ::: oolagRoot.requiredEvalFunctions
this.requiredEvalFunctions = Nil
//
// If this object is activated, then also centralize the conditional eval functions.
//
if (requiredEvalStatus eq Active) {
oolagRoot.requiredEvalFunctions = this.requiredEvalIfActivatedFunctions ::: oolagRoot.requiredEvalFunctions
this.requiredEvalIfActivatedFunctions = Nil
}
}
}
private sealed trait ActivityStatus
private case object Active extends ActivityStatus
private case object Inactive extends ActivityStatus
private var requiredEvalStatus: ActivityStatus = Inactive
/**
* Saves the arg expression, and insures it is evaluated later only
* if setRequiredEvaluationActive() is called for this object.
*
* This is for use when constructing a abstract syntax tree (AST)
* such as the DSOM tree (really a directed graph) when
* the object creation itself is not enough to know if the object will or
* will not be attached to the tree.
*
* The code constructing the AST must traverse the final tree and activate
* the nodes that ultimately want all these things evaluated on them.
*
* If using this, you should know that the object is either discarded
* (in which case these will never get evaluated), or somehow activated
* in which case they will be evaluated later when checkErrors is called.
*/
protected final def requiredEvaluationsIfActivated(arg: => Any): Unit = {
requiredEvaluationsIfActivated(thunk(arg))
}
/**
* Saves the arg LV, and insures it is evaluated later only if
* setRequiredEvaluationActive() is called for this object.
*/
private def requiredEvaluationsIfActivated(lv: OOLAGValueBase) : Unit = {
if (requiredEvalStatus eq Active)
if (hasOOLAGRootSetup)
oolagRoot.requiredEvalFunctions +:= lv // active. Rooted. Accumulate centrally.
else
requiredEvalIfActivatedFunctions +:= lv // active, but no root yet. Accumulate locally.
else
requiredEvalIfActivatedFunctions +:= lv // not active. Accumulate locally.
}
/**
* Call to activate an object so that deferred requiredEvaluationsIfActivated
* expressions are evaluated.
*
* Note that this can be called as part of a unconditional requiredEvaluationsAlways
* expression/LV, and if so, all the conditional evaluations will be captured and
* evaluated as part of the ongoing evaluation of requiredEvaluations expressions.
*/
final def setRequiredEvaluationsActive() : Unit = setRequiredEvaluationsActiveOnceOnly
private lazy val setRequiredEvaluationsActiveOnceOnly = {
requiredEvalStatus = Active
centralizeEvalFunctionsWhenReady()
}
/**
* Evaluate all requiredEvaluation functions/expressions.
*
* Evaluates all currently activated (or always activated)
* required evaluations, as well as those for any objects that
* become activated during evaluation of required evaluations of
* any object recursively.
*
* This is called only on the OOLAGRoot object. It depends on
* all other object's required evaluations having been moved up
* onto the root, which happens automatically via the calls to
* centralizeEvalFunctionsWhenReady().
*/
private def checkErrors: Unit = {
Assert.usage(this.isOOLAGRoot || requiredEvalFunctions == Nil)
while (oolagRoot.requiredEvalFunctions != Nil) { // while there is an accumulated crop of eval functions
// grab the current crop of eval functions
var evFuncs = oolagRoot.requiredEvalFunctions
// and clear the current accumulation point
oolagRoot.requiredEvalFunctions = Nil
// evaluate the current crop of eval functions.
// this evaluation may cause new eval functions to be accumulated.
// which will cause the outer loop to iterate again.
while (evFuncs != Nil) {
val lv = evFuncs.head
evFuncs = evFuncs.tail
OOLAG.keepGoing { () } {
lv.valueAsAny // useful place for a breakpoint
}
}
}
}
/**
* Accumulate diagnostics on the root of the hierarchy.
* (Likely the SchemaSet object.)
*/
private val errors_ = new mutable.LinkedHashSet[Diagnostic]()
private val warnings_ = new mutable.LinkedHashSet[Diagnostic]()
final def errors: Seq[Diagnostic] = oolagRoot.errors_.toSeq
final def warnings: Seq[Diagnostic] = oolagRoot.warnings_.toSeq
def getDiagnostics: Seq[Diagnostic] = diagnostics
def warn(th: Diagnostic): Unit = {
oolagRoot.oolagWarn(th)
}
def error(th: Diagnostic): Unit = {
oolagRoot.oolagError(th)
}
/**
* Implementor of OOLAGHost constructs Diagnostic objects
* because it has other context (schema components)
* needed in order to provide a good diagnostic.
*
* So we have this abstract method that is implemented
* by things that can actually construct Diagnostic
* objects (Anything that ThrowsSDE)
*/
protected def oolagWarn(th: Diagnostic): Unit = {
if (!warnings_.contains(th))
warnings_ += th
}
protected def oolagError(th: Diagnostic): Unit = {
if (!errors_.contains(th))
errors_ += th
}
/**
* Currently we depend on being able to evaluate these
* repeatedly, and get different answers.
*
* because it forces evaluation of all the requiredEvaluationsAlways(...)
* or requiredEvaluationsIfActivated(...)
* on all objects first, but that is only for the objects
* that have been created and activated at the time this is called.
*/
def isError: Boolean = {
oolagRoot.checkErrors
val errorCount = oolagRoot.errors.size
errorCount > 0
}
def diagnostics: Seq[Diagnostic] = (errors ++ warnings).toSeq
}
/**
* An OOLAG value is what would be called an "attribute" in attribute-grammar terminology.
* It is evaluated lazily, once, and either produces a value, or causes an error, or
* both produces a value and also creates zero or more warnings.
*
* It detects evaluation more than once, and disallows multiple evaluations.
* It detects and disallows cyclic definitions (oolag value defined in terms of itself).
* This does not prevent recursive definitions (different objects, but same oolag value 'name'),
* just cyclic loops (exact same oolag value object).
*
* An OOLAG value is created as a val of an OOLAGHost class.
*/
sealed abstract class OOLAGValueBase(
val oolagContext: OOLAGHost,
nameArg: String,
body: => Any)
extends Logging {
Assert.usage(oolagContext != null)
final lazy val name = nameArg
private var alreadyTriedThis = false
protected final def hasValue: Boolean = value_.isDefined
private var value_ : Maybe[AnyRef] = Nope
protected final def wasTried = alreadyTriedThis
// private def warn(th: Diagnostic): Unit = oolagContext.warn(th)
private def error(th: Diagnostic): Unit = oolagContext.error(th)
private def thisThing = this.name + valuePart + "@" + this.hashCode()
private def valuePart = {
if (hasValue) "(" + toStringIfSimpleEnough(value_.get) + ")"
else ""
}
private def toStringIfSimpleEnough(x: Any): String = {
x match {
case None => "None"
case Some(y) => "Some(" + toStringIfSimpleEnough(y) + ")"
case s: String => "'" + s + "'"
case av: Long => av.toString
case av: Int => av.toString
case av: Short => av.toString
case av: Byte => av.toString
case av: scala.xml.Node => av.toString
case av: Boolean => av.toString
case _ => Misc.getNameFromClass(x)
}
}
override def toString = thisThing
private val catchMsg = "%s has no value due to %s."
protected final def toss(th: Throwable) = {
throw th
}
protected final def oolagCatch(th: Throwable): Nothing = {
Assert.invariant(!hasValue)
th match {
case le: scala.Error => { // note that Exception does NOT inherit from Error
log(LogLevel.Error, " " * indent + catchMsg, thisThing, le) // tell us which lazy attribute it was
toss(le)
}
//
// Don't catch RuntimeException as this makes using the debugger
// to isolate bugs harder. You just end up converting them into unsuppressible,
// but with loss of context.
//
case ue @ (_: IllegalArgumentException | _: UnsuppressableException) => {
val ex = ue
log(LogLevel.OOLAGDebug, " " * indent + catchMsg, this.getClass.getName, ex) // tell us which lazy attribute it was
toss(ex)
}
//
// These are OOLAGs own Throwables.
// ErrorAlreadyHandled means we are headed back to some top-level that
// can tolerate errors and go on with compilation.
case eah: ErrorAlreadyHandled => {
log(LogLevel.OOLAGDebug, " " * indent + catchMsg, thisThing, eah)
toss(eah)
}
//
// Already tried means we got to this same OOLAG value evaluation again,
// and as values, they can't behave differently, so the same error will
// just get reported again.
case at: AlreadyTried => {
log(LogLevel.OOLAGDebug, " " * indent + "Caught %s", at)
toss(at)
}
case AssumptionFailed => {
toss(AssumptionFailed)
}
case e: Diagnostic => {
// we threw, instead of producing a value
//
// Typically this will be for a Schema Definition Error
//
Assert.invariant(hasValue == false)
Assert.invariant(alreadyTriedThis == true)
log(LogLevel.OOLAGDebug, " " * indent + catchMsg, thisThing, e)
error(e)
//
// Catch this if you can carry on with more error gathering
// from other contexts. Otherwise just let it propagate.
//
toss(new ErrorAlreadyHandled(e, this))
}
case e @ ErrorsNotYetRecorded(diags) => {
diags.foreach { error(_) }
toss(new AlreadyTried(this))
}
case th => toss(th)
}
}
private def initialize = {
val now = oolagContext.currentOVList
oolagContext.currentOVList = this +: oolagContext.currentOVList
setIndent(indent + 2)
now
}
protected final def oolagBefore(): Unit = {
Assert.invariant(!hasValue)
if (initialize.contains(this)) {
// System.err.println("Circular OOLAG Value Definition")
// This next println was causing problems because toString was
// itself causing circular evaluation. The abort above
// System.err.println("OOLAGValues (aka 'LVs') on stack are: " + currentOVList.mkString(", "))
val c = CircularDefinition(this, oolagContext.currentOVList)
log(LogLevel.OOLAGDebug, " " * indent + "LV: " + thisThing + " CIRCULAR")
toss(c)
}
if (alreadyTriedThis) {
log(LogLevel.OOLAGDebug, " " * indent + "LV: %s was tried and failed", thisThing)
val e = AlreadyTried(this)
toss(e)
}
alreadyTriedThis = true
log(LogLevel.OOLAGDebug, " " * indent + "Evaluating %s", thisThing)
}
protected final def oolagAfterValue(res: AnyRef): Unit = {
log(LogLevel.OOLAGDebug, " " * indent + "Evaluated %s", thisThing)
value_ = Maybe(res)
}
protected final def oolagFinalize = {
setIndent(indent - 2)
log(LogLevel.OOLAGDebug, " " * indent + "pop: " + thisThing)
if (oolagContext.currentOVList.nonEmpty)
oolagContext.currentOVList = oolagContext.currentOVList.tail
}
final def hasError = alreadyTriedThis && !hasValue
/**
* forces the value, then boolean result tells you if
* a value was computed or errors, 1 or more, occurred
* which prevent a value from being computed.
*/
final def isError = {
val res =
if (alreadyTriedThis) !hasValue
else {
try {
valueAsAny
!hasValue
} catch {
case oe: OOLAGRethrowException => true
}
}
if (res == true) {
log(LogLevel.OOLAGDebug, " " * indent + "LV %s has an error", this)
}
res
}
final lazy val valueAsAny: Any = {
if (hasValue) value_.get
val res =
try {
oolagBefore
val v = body // good place for a breakpoint
oolagAfterValue(v.asInstanceOf[AnyRef])
v
} catch {
case npe: NullPointerException => throw npe
case s: scala.util.control.ControlThrowable => throw s
case u: UnsuppressableException => throw u
case e: Error => throw e
case th: Throwable => oolagCatch(th)
} finally {
oolagFinalize
}
res
}
protected lazy val toOptionAny: Option[Any] = {
if (wasTried) {
if (hasValue) Some(value_.get)
else None
} else {
val res = try {
val v = valueAsAny
Some(v)
} catch {
case e: OOLAGRethrowException => None
}
res
}
}
}
final class OOLAGValue[T](ctxt: OOLAGHost, nameArg: String, body: => T)
extends OOLAGValueBase(ctxt, nameArg, body) {
/**
* Converts LV into an option type.
*
* If the logical value of the LV is already an option type,
* keep in mind you have to flatten to get back to an Option\[T\].
*
* Use this if you are getting stack-overflows or circular value problems
* caused by an SDE and the SDE infrastructure needing the value of some LV.
*/
final lazy val toOption: Option[T] = toOptionAny.asInstanceOf[Option[T]]
final lazy val value: T = valueAsAny.asInstanceOf[T]
}
} // end object
/**
* These exception types are all private because they are supposed
* to be caught by wrappers defined here and contained by them.
*
* If they get thrown all the way to top level it's a bug.
* If code someplace needs to see/understand them, then it
* either needs to use a wrapper from here (e.g., see keepGoing(){})
* or we need to evolve a new wrapper.
*/
// allow outside world to see this base, for maintainability catches
//
private[oolag] abstract class OOLAGException(th: Throwable) extends ThinException(th) {
def lv: OOLAG.OOLAGValueBase
}
private[oolag] abstract class OOLAGRethrowException(th: Throwable) extends OOLAGException(th) {
def cause1: Option[Throwable]
def this() = this(null)
}
/**
* Used to carry diagnostics from one oolag context (DPath expression compiling)
* to another (Schema Components). You throw this bag of diagnostics, and they
* get recorded individually on the enclosing oolag context.
*/
case class ErrorsNotYetRecorded(diags: Seq[Diagnostic]) extends OOLAGRethrowException {
Assert.invariant(diags.length > 0)
def cause1: Option[Throwable] = Some(diags(0))
def lv: OOLAG.OOLAGValueBase = null
override def getMessage() = {
if (diags.length == 1) diags(0).getMessage()
else diags.map { Misc.getSomeMessage(_).get }.mkString("\n")
}
}
private[oolag] case class AlreadyTried(val lv: OOLAG.OOLAGValueBase)
extends OOLAGRethrowException {
override def getMessage() = lv.toString
override val cause1 = None
}
/**
* Catch this if you can carry on with more evaluations after an
* error has occurred. Otherwise just let it propagate.
*/
// I'd like this package private, but they leak out due to compile time errors
// that are not being seen until runtime.
final case class ErrorAlreadyHandled(val th: Diagnostic, lv: OOLAG.OOLAGValueBase)
extends OOLAGRethrowException(th) {
override val cause1 = Some(th)
}
private[oolag] case object AssumptionFailed extends OOLAGRethrowException {
def lv: OOLAG.OOLAGValue[Any] = Assert.usageError("Should not call lv on AssumptionFailed.")
def cause1: Option[Throwable] = None
}
final case class CircularDefinition(val lv: OOLAG.OOLAGValueBase, list: Seq[OOLAG.OOLAGValueBase]) extends Exception {
override def getMessage() = {
"OOLAG Cycle (of " + list.length + ") through " + list.mkString(", ")
}
}