Skip to content

Commit

Permalink
Merge pull request #125 from szeiger/error-handling
Browse files Browse the repository at this point in the history
Error handling improvements
  • Loading branch information
szeiger authored Aug 9, 2021
2 parents d013527 + d516b17 commit 6aa300a
Show file tree
Hide file tree
Showing 14 changed files with 313 additions and 297 deletions.
8 changes: 3 additions & 5 deletions sjsonnet/src-jvm-native/sjsonnet/SjsonnetMain.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,11 @@ import java.io.{BufferedOutputStream, InputStream, OutputStreamWriter, PrintStre
import java.nio.charset.StandardCharsets
import java.nio.file.NoSuchFileException


import scala.collection.mutable
import scala.util.Try
import scala.util.control.NonFatal

object SjsonnetMain {
def createParseCache() = collection.mutable.HashMap[(Path, String), Either[String, (Expr, FileScope)]]()
def createParseCache() = collection.mutable.HashMap[(Path, String), Either[Error, (Expr, FileScope)]]()

def resolveImport(searchRoots0: Seq[Path], allowedInputs: Option[Set[os.Path]] = None) = new Importer {
def resolve(docBase: Path, importName: String): Option[Path] =
Expand Down Expand Up @@ -46,7 +44,7 @@ object SjsonnetMain {
}

def main0(args: Array[String],
parseCache: collection.mutable.HashMap[(Path, String), Either[String, (Expr, FileScope)]],
parseCache: collection.mutable.HashMap[(Path, String), Either[Error, (Expr, FileScope)]],
stdin: InputStream,
stdout: PrintStream,
stderr: PrintStream,
Expand Down Expand Up @@ -129,7 +127,7 @@ object SjsonnetMain {

def mainConfigured(file: String,
config: Config,
parseCache: collection.mutable.HashMap[(Path, String), Either[String, (Expr, FileScope)]],
parseCache: collection.mutable.HashMap[(Path, String), Either[Error, (Expr, FileScope)]],
wd: os.Path,
allowedInputs: Option[Set[os.Path]] = None,
importer: Option[(Path, String) => Option[os.Path]] = None): Either[String, String] = {
Expand Down
119 changes: 74 additions & 45 deletions sjsonnet/src/sjsonnet/Error.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,61 +9,90 @@ import scala.util.control.NonFatal
* propagating upwards. This helps provide good error messages with line
* numbers pointing towards user code.
*/
case class Error(msg: String,
stack: List[StackTraceElement],
underlying: Option[Throwable])
extends Exception(msg, underlying.orNull){
setStackTrace(stack.toArray.reverse)
def addFrame(pos: Position, wd: Path)(implicit ev: EvalErrorScope) = {
val newFrame = ev.prettyIndex(pos) match {
case None =>
new StackTraceElement(
"", "",
pos.currentFile.relativeToString(wd) + " offset:",
pos.offset
)
case Some((line, col)) =>

new StackTraceElement(
"", "",
pos.currentFile.relativeToString(wd) + ":" + line,
col.toInt
)
}
class Error(msg: String, stack: List[Error.Frame] = Nil, underlying: Option[Throwable] = None)
extends Exception(msg, underlying.orNull) {

setStackTrace(stack.reverseIterator.map(_.ste).toArray)

override def fillInStackTrace: Throwable = this

this.copy(stack = newFrame :: this.stack)
def addFrame(pos: Position, expr: Expr = null)(implicit ev: EvalErrorScope): Error = {
if(stack.isEmpty || alwaysAddPos(expr)) {
val exprErrorString = if(expr == null) null else expr.exprErrorString
val newFrame = new Error.Frame(pos, exprErrorString)
stack match {
case s :: ss if s.pos == pos =>
if(s.exprErrorString == null && exprErrorString != null) copy(stack = newFrame :: ss)
else this
case _ => copy(stack = newFrame :: stack)
}
} else this
}
}

def asSeenFrom(ev: EvalErrorScope): Error =
copy(stack = stack.map(_.asSeenFrom(ev)))

protected[this] def copy(msg: String = msg, stack: List[Error.Frame] = stack,
underlying: Option[Throwable] = underlying) =
new Error(msg, stack, underlying)

private[this] def alwaysAddPos(expr: Expr): Boolean = expr match {
case _: Expr.LocalExpr | _: Expr.Arr | _: Expr.ObjExtend | _: Expr.ObjBody | _: Expr.IfElse => false
case _ => true
}
}

object Error {
def tryCatch[T](pos: Position)
(implicit evaluator: EvalErrorScope): PartialFunction[Throwable, Nothing] = {
case e: Error => throw e
case e: Error.Delegate =>
throw new Error(e.msg, Nil, None).addFrame(pos, evaluator.wd)
case NonFatal(e) =>
throw new Error("Internal Error", Nil, Some(e)).addFrame(pos, evaluator.wd)
final class Frame(val pos: Position, val exprErrorString: String)(implicit ev: EvalErrorScope) {
val ste: StackTraceElement = {
val cl = if(exprErrorString == null) "" else s"[${exprErrorString}]"
val (frameFile, frameLine) = ev.prettyIndex(pos) match {
case None => (pos.currentFile.relativeToString(ev.wd) + " offset:", pos.offset)
case Some((line, col)) => (pos.currentFile.relativeToString(ev.wd) + ":" + line, col.toInt)
}
new StackTraceElement(cl, "", frameFile, frameLine)
}

def asSeenFrom(ev: EvalErrorScope): Frame =
if(ev eq this.ev) this else new Frame(pos, exprErrorString)(ev)
}
def tryCatchWrap[T](pos: Position)
(implicit evaluator: EvalErrorScope): PartialFunction[Throwable, Nothing] = {
case e: Error => throw e.addFrame(pos, evaluator.wd)
case e: Error.Delegate =>
throw new Error(e.msg, Nil, None).addFrame(pos, evaluator.wd)

def withStackFrame[T](expr: Expr)
(implicit evaluator: EvalErrorScope): PartialFunction[Throwable, Nothing] = {
case e: Error => throw e.addFrame(expr.pos, expr)
case NonFatal(e) =>
throw new Error("Internal Error", Nil, Some(e)).addFrame(pos, evaluator.wd)
}
def fail(msg: String, pos: Position)
(implicit evaluator: EvalErrorScope) = {
throw Error(msg, Nil, None).addFrame(pos, evaluator.wd)
throw new Error("Internal Error", Nil, Some(e)).addFrame(expr.pos, expr)
}

/**
* An exception containing a message, which is expected to get caught by
* the nearest enclosing try-catch and converted into an [[Error]]
*/
case class Delegate(msg: String) extends Exception(msg)
def fail(msg: String, expr: Expr)(implicit ev: EvalErrorScope): Nothing =
fail(msg, expr.pos, expr.exprErrorString)

def fail(msg: String, pos: Position, cl: String = null)(implicit ev: EvalErrorScope): Nothing =
throw new Error(msg, new Frame(pos, cl) :: Nil, None)

def fail(msg: String): Nothing =
throw new Error(msg)
}

class ParseError(msg: String, stack: List[Error.Frame] = Nil, underlying: Option[Throwable] = None)
extends Error(msg, stack, underlying) {

override protected[this] def copy(msg: String = msg, stack: List[Error.Frame] = stack,
underlying: Option[Throwable] = underlying) =
new ParseError(msg, stack, underlying)
}

class StaticError(msg: String, stack: List[Error.Frame] = Nil, underlying: Option[Throwable] = None)
extends Error(msg, stack, underlying) {

override protected[this] def copy(msg: String = msg, stack: List[Error.Frame] = stack,
underlying: Option[Throwable] = underlying) =
new StaticError(msg, stack, underlying)
}

object StaticError {
def fail(msg: String, expr: Expr)(implicit ev: EvalErrorScope): Nothing =
throw new StaticError(msg, new Error.Frame(expr.pos, expr.exprErrorString) :: Nil, None)
}

trait EvalErrorScope {
Expand Down
Loading

0 comments on commit 6aa300a

Please sign in to comment.