11package sjsonnet
22
33import java .io .{PrintWriter , StringWriter }
4+ import scala .util .control .NonFatal
45
56/**
6- * Resolved stack frame captured at error-throw time. Positions are pre-resolved to file:line:col so
7- * that rendering does not need access to the evaluator .
7+ * An exception that can keep track of the Sjsonnet call-stack while it is propagating upwards. This
8+ * helps provide good error messages with line numbers pointing towards user code .
89 */
9- final class StackTrace (val name : String , val file : String , val line : Int , val col : Int )
10-
11- /**
12- * An exception carrying a Jsonnet-level stack trace. The trace is captured from the evaluator's
13- * live call stack when the error is thrown (via [[Error.fail ]]).
14- */
15- class Error (
16- msg : String ,
17- private [sjsonnet] val trace : Array [StackTrace ] = Array .empty,
18- underlying : Option [Throwable ] = None )
10+ class Error (msg : String , stack : List [Error .Frame ] = Nil , underlying : Option [Throwable ] = None )
1911 extends Exception (msg, underlying.orNull) {
2012
13+ setStackTrace(stack.reverseIterator.map(_.ste).toArray)
14+
2115 override def fillInStackTrace : Throwable = this
16+
17+ def addFrame (pos : Position , expr : Expr = null )(implicit ev : EvalErrorScope ): Error = {
18+ if (stack.isEmpty || alwaysAddPos(expr)) {
19+ val exprErrorString = if (expr == null ) null else expr.exprErrorString
20+ addFrameString(pos, exprErrorString)
21+ } else this
22+ }
23+
24+ def addFrameString (pos : Position , exprErrorString : String )(implicit ev : EvalErrorScope ): Error = {
25+ val newFrame = new Error .Frame (pos, exprErrorString)
26+ stack match {
27+ case s :: ss if s.pos == pos =>
28+ if (s.exprErrorString == null && exprErrorString != null ) copy(stack = newFrame :: ss)
29+ else this
30+ case _ => copy(stack = newFrame :: stack)
31+ }
32+ }
33+
34+ def asSeenFrom (ev : EvalErrorScope ): Error =
35+ copy(stack = stack.map(_.asSeenFrom(ev)))
36+
37+ protected def copy (
38+ msg : String = msg,
39+ stack : List [Error .Frame ] = stack,
40+ underlying : Option [Throwable ] = underlying) =
41+ new Error (msg, stack, underlying)
42+
43+ private def alwaysAddPos (expr : Expr ): Boolean = expr match {
44+ case _ : Expr .LocalExpr | _ : Expr .Arr | _ : Expr .ObjExtend | _ : Expr .ObjBody | _ : Expr .IfElse =>
45+ false
46+ case _ => true
47+ }
2248}
2349
2450object Error {
25- def fail (msg : String , pos : Position )(implicit ev : EvalErrorScope ): Nothing =
26- throw new Error (msg, ev.captureTrace(pos))
51+ final class Frame (val pos : Position , val exprErrorString : String )(implicit ev : EvalErrorScope ) {
52+ val ste : StackTraceElement = {
53+ val cl = if (exprErrorString == null ) " " else s " [ $exprErrorString] "
54+ val (frameFile, frameLine) = ev.prettyIndex(pos) match {
55+ case None => (pos.currentFile.relativeToString(ev.wd) + " offset" , pos.offset)
56+ case Some ((line, col)) => (pos.currentFile.relativeToString(ev.wd) + " :" + line, col)
57+ }
58+ new StackTraceElement (cl, " " , frameFile, frameLine)
59+ }
60+
61+ def asSeenFrom (ev : EvalErrorScope ): Frame =
62+ if (ev eq this .ev) this else new Frame (pos, exprErrorString)(ev)
63+ }
64+
65+ def withStackFrame [T ](expr : Expr )(implicit
66+ evaluator : EvalErrorScope ): PartialFunction [Throwable , Nothing ] = {
67+ case e : Error => throw e.addFrame(expr.pos, expr)
68+ case NonFatal (e) =>
69+ throw new Error (" Internal Error" , Nil , Some (e)).addFrame(expr.pos, expr)
70+ }
2771
2872 def fail (msg : String , expr : Expr )(implicit ev : EvalErrorScope ): Nothing =
29- fail(msg, expr.pos)
73+ fail(msg, expr.pos, expr.exprErrorString)
74+
75+ def fail (msg : String , pos : Position , cl : String = null )(implicit ev : EvalErrorScope ): Nothing =
76+ throw new Error (msg, new Frame (pos, cl) :: Nil , None )
3077
3178 def fail (msg : String ): Nothing =
3279 throw new Error (msg)
3380
34- private def errorPrefix (err : Error ): String = err match {
35- case _ : ParseError => " sjsonnet.ParseError: "
36- case _ : StaticError => " sjsonnet.StaticError: "
37- case _ => " sjsonnet.Error: "
38- }
39-
40- def formatError (e : Throwable ): String = e match {
41- case err : Error if err.trace.nonEmpty =>
42- val sb = new StringBuilder
43- sb.append(errorPrefix(err)).append(err.getMessage)
44- for (frame <- err.trace) {
45- sb.append(" \n at [" ).append(frame.name).append(']' )
46- if (frame.file != null ) {
47- sb.append(" .(" )
48- .append(frame.file)
49- .append(':' )
50- .append(frame.line)
51- .append(':' )
52- .append(frame.col)
53- .append(')' )
54- }
55- }
56- sb.append('\n ' )
57- sb.toString
58- case err : Error =>
59- errorPrefix(err) + err.getMessage + '\n '
60- case _ =>
61- val s = new StringWriter ()
62- val p = new PrintWriter (s)
63- try { e.printStackTrace(p); s.toString.replace(" \t " , " " ) }
64- finally p.close()
81+ def formatError (e : Throwable ): String = {
82+ val s = new StringWriter ()
83+ val p = new PrintWriter (s)
84+ try {
85+ e.printStackTrace(p)
86+ s.toString.replace(" \t " , " " )
87+ } finally {
88+ p.close()
89+ }
6590 }
6691}
6792
6893class ParseError (
6994 msg : String ,
70- _trace : Array [ StackTrace ] = Array .empty ,
95+ stack : List [ Error . Frame ] = Nil ,
7196 underlying : Option [Throwable ] = None ,
7297 val offset : Int = - 1 )
73- extends Error (msg, _trace , underlying)
98+ extends Error (msg, stack , underlying) {
7499
75- class StaticError (
76- msg : String ,
77- _trace : Array [StackTrace ] = Array .empty,
78- underlying : Option [Throwable ] = None )
79- extends Error (msg, _trace, underlying)
100+ override protected def copy (
101+ msg : String = msg,
102+ stack : List [Error .Frame ] = stack,
103+ underlying : Option [Throwable ] = underlying): sjsonnet.ParseError =
104+ new ParseError (msg, stack, underlying, offset)
105+ }
106+
107+ class StaticError (msg : String , stack : List [Error .Frame ] = Nil , underlying : Option [Throwable ] = None )
108+ extends Error (msg, stack, underlying) {
109+
110+ override protected def copy (
111+ msg : String = msg,
112+ stack : List [Error .Frame ] = stack,
113+ underlying : Option [Throwable ] = underlying): sjsonnet.StaticError =
114+ new StaticError (msg, stack, underlying)
115+ }
80116
81117object StaticError {
82- def fail (msg : String , expr : Expr )(implicit ev : EvalErrorScope ): Nothing = {
83- var trace = ev.captureTrace(expr.pos)
84- if (trace.isEmpty && expr.pos != null ) {
85- ev.prettyIndex(expr.pos) match {
86- case Some ((line, col)) =>
87- trace = Array (
88- new StackTrace (
89- Util .wrapInLessThanGreaterThan(" root" ),
90- expr.pos.currentFile.relativeToString(ev.wd),
91- line,
92- col
93- )
94- )
95- case None =>
96- }
97- }
98- throw new StaticError (msg, trace)
99- }
118+ def fail (msg : String , expr : Expr )(implicit ev : EvalErrorScope ): Nothing =
119+ throw new StaticError (msg, new Error .Frame (expr.pos, expr.exprErrorString) :: Nil , None )
100120}
101121
102122trait EvalErrorScope {
@@ -114,6 +134,4 @@ trait EvalErrorScope {
114134 (splitted(0 ).toInt, splitted(1 ).toInt)
115135 }
116136 }
117-
118- def captureTrace (throwPos : Position ): Array [StackTrace ] = Array .empty
119137}
0 commit comments