Skip to content

Commit

Permalink
refactor(debug): improved internals of ConsolePrettyPrinter
Browse files Browse the repository at this point in the history
  • Loading branch information
j-mie6 committed Dec 28, 2024
1 parent 62b03c8 commit c75cccb
Show file tree
Hide file tree
Showing 4 changed files with 25 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,14 @@
*/
package parsley.debugger

import parsley.XAssert

import ParseAttempt._ // scalastyle:ignore underscore.import

/** A representation of the attempts a parser has made during parse-time.
*
* @since 4.5.0
*/
private [parsley] final class ParseAttempt private [parsley] (inp: Input, fof: Offset, tof: Offset, fps: Pos, tps: Pos, scs: Success, res: Result) {
/** The input parsed, as raw text. */
private [parsley] final class ParseAttempt private [parsley] (inp: Input, fof: Offset, tof: Offset, fps: Pos, tps: Pos, res: Result) {
/** The input parsed, as raw text. */ //TODO: I think this can be removed as it can be derived from the full input...
val rawInput: Input = inp

/** This offset is where the parse attempt started in the input. */
Expand All @@ -33,7 +31,7 @@ private [parsley] final class ParseAttempt private [parsley] (inp: Input, fof: O
*
* @note [[success]] if and only if [[result]] is defined (contains a value).
*/
val success: Success = scs
def success: Success = result.isDefined

/** If this parse attempt was successful, what did it return? It is guaranteed that `result.isDefined` is true
* if and only if the attempt is successful.
Expand All @@ -42,12 +40,9 @@ private [parsley] final class ParseAttempt private [parsley] (inp: Input, fof: O
*/
val result: Result = res

// Make sure this class has not been used improperly.
XAssert.assert(success == result.isDefined)

// Utility copy method only to be used internally.
private [parsley] def copy(inp: Input = rawInput, fof: Offset = fromOffset, tof: Offset = toOffset, fps: Pos = fromPos, tps: Pos = toPos,
scs: Success = success, res: Result = result): ParseAttempt = new ParseAttempt(inp, fof, tof, fps, tps, scs, res)
res: Result = result): ParseAttempt = new ParseAttempt(inp, fof, tof, fps, tps, res)
}

// Ideally, this would be public. However, that introduces potential binary incompatibilities later down the line
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@ import scala.annotation.tailrec

import parsley.debugger.{DebugTree, ParseAttempt}

/** A console pretty-printer for the debugger.
/** A (reusable) 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 ConsolePrettyPrinter(Console.out) {
object ConsolePrettyPrinter extends ReusableFrontend with ConsolePrettyPrinter {
override protected val out: PrintStream = Console.out
/** Create a string pretty-printer that outputs to an arbitrary place
*
* @since 5.0.0
Expand All @@ -28,29 +29,28 @@ object ConsolePrettyPrinter extends ConsolePrettyPrinter(Console.out) {
*
* @since 5.0.0
*/
def apply(out: PrintStream): ReusableFrontend = new ConsolePrettyPrinter(out)
def apply(print: PrintStream): ReusableFrontend = new ConsolePrettyPrinter {
override protected val out: PrintStream = print
}
}

private [frontend] sealed class ConsolePrettyPrinter private[frontend] (out: PrintStream) extends ReusableFrontend {
override private [debugger] def process(input: => String, tree: => DebugTree): Unit = {
private [frontend] sealed trait ConsolePrettyPrinter extends ReusableFrontend {
protected val out: PrintStream
override private [debugger] def process(input: =>String, tree: =>DebugTree): Unit = {
out.println(s"${tree.parserName}'s parse tree for input:\n\n$input\n\n")
pretty(tree)
}

private def bury(str: String, withMark: Boolean, indents: Vector[String]): Unit = out.println {
if (indents.isEmpty) str
else if (withMark) s"${indents.init.mkString}+-$str"
else s"${indents.mkString}$str"
indents match {
case is :+ _ if withMark => s"${is.mkString}+-$str"
case is => s"${is.mkString}$str"
}
}

private def pretty(dt: DebugTree): Unit = pretty(dt, Vector.empty)

private def pretty(dt: DebugTree, indents: Vector[String]): 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 ""}"
private def pretty(dt: DebugTree, indents: Vector[String] = Vector.empty): Unit = {
val childName = dt.childNumber.fold("")(n => s"($n)")
val uname = if (dt.parserName != dt.internalName) s"${dt.parserName} (${dt.internalName}$childName)" else s"${dt.internalName}$childName"
val results = dt.parseResults.map(printParseAttempt).mkString

bury(s"[ $uname ]: $results", withMark = true, indents)
Expand All @@ -59,7 +59,7 @@ private [frontend] sealed class ConsolePrettyPrinter private[frontend] (out: Pri

// Print a parse attempt in a human-readable way.
private def printParseAttempt(attempt: ParseAttempt): String = {
val status = if (attempt.success) s"Success - [ ${attempt.result.get} ]" else "Failure"
val status = attempt.result.fold("Failure")(x => s"Success - [ $x ]")
s"""(\"${attempt.rawInput}\" [${attempt.fromPos} -> ${attempt.toPos}], $status)"""
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ sealed trait DebugFrontend {
* @param input The full input of the parse.
* @param tree Debug tree to process.
*/
private [debugger] def process(input: =>String, tree: => DebugTree): Unit
private [debugger] def process(input: =>String, tree: =>DebugTree): Unit
}

/** Signifies that the frontend inheriting from this can be used multiple times.
Expand All @@ -48,7 +48,7 @@ trait ReusableFrontend extends DebugFrontend
*/
trait SingleUseFrontend extends DebugFrontend {
private var hasBeenRun = false
final override private[debugger] def process(input: => String, tree: => DebugTree): Unit = {
final override private[debugger] def process(input: =>String, tree: =>DebugTree): Unit = {
if (hasBeenRun) {
// XXX: There isn't really another way to enforce not running a stateful frontend more than once that isn't just "do nothing".
// Especially since doing nothing turns that action into a silent error, which is generally less preferable to "loud"
Expand All @@ -60,5 +60,5 @@ trait SingleUseFrontend extends DebugFrontend {
}
}
/** The implementation of the process method above */
private[debugger] def processImpl(input: => String, tree: => DebugTree): Unit
private[debugger] def processImpl(input: =>String, tree: =>DebugTree): Unit
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ private [internal] class AddAttemptAndLeave(dbgCtx: DebugContext) extends Instr
case f if dbgCtx.shouldString(f) => f.toString // Closures and lambdas are expensive!
case x => x
}) else None
new ParseAttempt(inp = input, fof = prevOffset, tof = currentOff, fps = prevPos, tps = (ctx.line, ctx.col), scs = success, res = res)
new ParseAttempt(inp = input, fof = prevOffset, tof = currentOff, fps = prevPos, tps = (ctx.line, ctx.col), res = res)
}

dbgCtx.pop()
Expand Down

0 comments on commit c75cccb

Please sign in to comment.