11package sjsonnet
22
33import java .io .{PrintWriter , StringWriter }
4- import scala .util .control .NonFatal
54
65/**
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 .
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 .
98 */
10- class Error (msg : String , stack : List [Error .Frame ] = Nil , underlying : Option [Throwable ] = None )
11- extends Exception (msg, underlying.orNull) {
9+ final class StackTrace (val name : String , val file : String , val line : Int , val col : Int )
1210
13- setStackTrace(stack.reverseIterator.map(_.ste).toArray)
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 )
19+ extends Exception (msg, underlying.orNull) {
1420
1521 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- }
4822}
4923
5024object Error {
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- }
25+ def fail (msg : String , pos : Position )(implicit ev : EvalErrorScope ): Nothing =
26+ throw new Error (msg, ev.captureTrace(pos))
7127
7228 def fail (msg : String , expr : Expr )(implicit ev : EvalErrorScope ): Nothing =
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 )
29+ fail(msg, expr.pos)
7730
7831 def fail (msg : String ): Nothing =
7932 throw new Error (msg)
8033
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- }
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()
9065 }
9166}
9267
9368class ParseError (
9469 msg : String ,
95- stack : List [ Error . Frame ] = Nil ,
70+ _trace : Array [ StackTrace ] = Array .empty ,
9671 underlying : Option [Throwable ] = None ,
9772 val offset : Int = - 1 )
98- extends Error (msg, stack, underlying) {
99-
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- }
73+ extends Error (msg, _trace, underlying)
10674
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- }
75+ class StaticError (
76+ msg : String ,
77+ _trace : Array [StackTrace ] = Array .empty,
78+ underlying : Option [Throwable ] = None )
79+ extends Error (msg, _trace, underlying)
11680
11781object StaticError {
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 )
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+ }
120100}
121101
122102trait EvalErrorScope {
@@ -134,4 +114,6 @@ trait EvalErrorScope {
134114 (splitted(0 ).toInt, splitted(1 ).toInt)
135115 }
136116 }
117+
118+ def captureTrace (throwPos : Position ): Array [StackTrace ] = Array .empty
137119}
0 commit comments