From 31b7afcb3e9fc9257fba7f84cc3dace7b31d2a10 Mon Sep 17 00:00:00 2001 From: Jamie Willis Date: Tue, 9 Apr 2024 18:04:35 +0100 Subject: [PATCH] added ConsolePrettyPrinter debug frontend --- .../frontend/ConsolePrettyPrinter.scala | 31 ++++++++ .../frontend/internal/consolepretty.scala | 71 +++++++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 parsley-debug/shared/src/main/scala/parsley/debugger/frontend/ConsolePrettyPrinter.scala create mode 100644 parsley-debug/shared/src/main/scala/parsley/debugger/frontend/internal/consolepretty.scala diff --git a/parsley-debug/shared/src/main/scala/parsley/debugger/frontend/ConsolePrettyPrinter.scala b/parsley-debug/shared/src/main/scala/parsley/debugger/frontend/ConsolePrettyPrinter.scala new file mode 100644 index 000000000..5cb424281 --- /dev/null +++ b/parsley-debug/shared/src/main/scala/parsley/debugger/frontend/ConsolePrettyPrinter.scala @@ -0,0 +1,31 @@ +/* + * Copyright 2020 Parsley Contributors + * + * SPDX-License-Identifier: BSD-3-Clause + */ +package parsley.debugger.frontend + +import parsley.debugger.DebugTree +import parsley.debugger.frontend.internal.consolepretty.* + +private [frontend] sealed class ConsolePrettyPrinterImpl private[frontend] (ioF: String => Unit) extends ReusableFrontend { + override protected def processImpl(input: => String, tree: => DebugTree): Unit = { + ioF(s"${tree.parserName}'s parse tree for input:\n\n$input\n\n") + ioF(tree.pretty + "\n") + } +} + +/** A console pretty-printer for the debugger. + * + * It is recommended that all memory-heavy types (e.g. closures) are not stored explicitly. Consult the documentation + * on attaching debuggers to find out how to prevent that. + * + * @since 5.0.0 + */ +object ConsolePrettyPrinter extends ConsolePrettyPrinterImpl(println(_)) { + /** Create a string pretty-printer that takes an arbitrary impure string function. + * + * @since 5.0.0 + */ + def apply(ioF: String => Unit): ReusableFrontend = new ConsolePrettyPrinterImpl(ioF) +} diff --git a/parsley-debug/shared/src/main/scala/parsley/debugger/frontend/internal/consolepretty.scala b/parsley-debug/shared/src/main/scala/parsley/debugger/frontend/internal/consolepretty.scala new file mode 100644 index 000000000..8e6cf3c46 --- /dev/null +++ b/parsley-debug/shared/src/main/scala/parsley/debugger/frontend/internal/consolepretty.scala @@ -0,0 +1,71 @@ +/* + * Copyright 2020 Parsley Contributors + * + * SPDX-License-Identifier: BSD-3-Clause + */ +package parsley.debugger.frontend.internal + +import scala.annotation.tailrec +import scala.collection.mutable + +import parsley.debugger.{DebugTree, ParseAttempt} + +object consolepretty { + // Utility class for aiding in the toString method for debug trees. + private [frontend] case class PrettyPrintHelper(acc: mutable.StringBuilder, indents: Vector[String]) { + // Indent a string with the given indenting delimiters. + def bury(str: String, withMark: Boolean = true): Unit = { + val pretty = + if (indents.isEmpty) str + else if (withMark) indents.init.mkString + "+-" + str + else indents.mkString + str + + acc.append(pretty + "\n") + } + + // Add a new indent delimiter to the current helper instance. + // The accumulator is shared between new instances. + def addIndent: PrettyPrintHelper = PrettyPrintHelper(acc, indents :+ "| ") + + // Adds a two-blank-space indent instead for the last child of a node. + def addBlankIndent: PrettyPrintHelper = PrettyPrintHelper(acc, indents :+ " ") + } + + implicit final class TreePrinter(val dt: DebugTree) extends AnyVal { + def pretty: String = { + val acc = new mutable.StringBuilder + prettyPrint(PrettyPrintHelper(acc, Vector.empty)) + acc.init.toString + } + + private [TreePrinter] def prettyPrint(helper: PrettyPrintHelper): Unit = { + val uname = + if (dt.parserName != dt.internalName) + s"${dt.parserName} (${dt.internalName}${if (dt.childNumber.isDefined) s" (${dt.childNumber.get})" else ""})" + else + s"${dt.internalName}${if (dt.childNumber.isDefined) s" (${dt.childNumber.get})" else ""}" + val results = dt.parseResults.map(printParseAttempt).mkString + + helper.bury(s"[ $uname ]: $results") + printChildren(helper, dt.nodeChildren.toList) + } + + // Print a parse attempt in a human-readable way. + private [TreePrinter] def printParseAttempt(attempt: ParseAttempt): String = + s"(\"${attempt.rawInput}\" [${attempt.fromPos} -> ${attempt.toPos}], ${if (attempt.success) + s"Success - [ ${attempt.result.get} ]" + else "Failure"})" + + // Print all the children, remembering to add a blank indent for the last child. + @tailrec private def printChildren(helper: PrettyPrintHelper, children: List[(String, DebugTree)]): Unit = children match { + case (_, t) :: Nil => + helper.bury("|", withMark = false) + t.prettyPrint(helper.addBlankIndent) + case (_, t) :: xs => + helper.bury("|", withMark = false) + t.prettyPrint(helper.addIndent) + printChildren(helper, xs) + case Nil => + } + } +}