answerkey/parsing/16.answer.md
case class ParseError(stack: List[(Location, String)] = Nil):
def push(loc: Location, msg: String): ParseError =
copy(stack = (loc, msg) :: stack)
def label(s: String): ParseError =
ParseError(latestLoc.map((_, s)).toList)
def latest: Option[(Location,String)] =
stack.lastOption
def latestLoc: Option[Location] =
latest map (_._1)
/**
Display collapsed error stack - any adjacent stack elements with the
same location are combined on one line. For the bottommost error, we
display the full line, with a caret pointing to the column of the error.
Example:
1.1 file 'companies.json'; array
5.1 object
5.2 key-value
5.10 ':'
{ "MSFT" ; 24,
^
*/
override def toString =
if stack.isEmpty then "no error message"
else
val collapsed = collapseStack(stack)
val context =
collapsed.lastOption.map("\n\n" + _._1.currentLine).getOrElse("") +
collapsed.lastOption.map("\n" + _._1.columnCaret).getOrElse("")
collapsed.map((loc, msg) => s"${formatLoc(loc)} $msg").mkString("\n") + context
/* Builds a collapsed version of the given error stack -
* messages at the same location have their messages merged,
* separated by semicolons */
def collapseStack(s: List[(Location, String)]): List[(Location, String)] =
s.groupBy(_._1).
view.
mapValues(_.map(_._2).mkString("; ")).
toList.sortBy(_._1.offset)
def formatLoc(l: Location): String = s"${l.line}.${l.col}"