parsley.internal.machine.instructions.DebugInstrs.scala Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2020 Parsley Contributors
*
* SPDX-License-Identifier: BSD-3-Clause
*/
// $COVERAGE-OFF$
package parsley.internal.machine.instructions
import parsley.XAssert._
import parsley.debug.Profiler
import parsley.errors.ErrorBuilder
import parsley.internal.errors.{ExpectItem, FancyError, ParseError, TrivialError}
import parsley.internal.machine.Context
import parsley.internal.machine.XAssert._
import Indenter.indentAndUnlines
import InputSlicer.Pad
import PrettyPortal.{Direction, Enter, Exit}
import org.typelevel.scalaccompat.annotation.{nowarn, unused}
private [instructions] trait Colours {
val ascii: Boolean
final val newline = green("↙")
final val space = white("·")
final val endOfInput = red("•")
final val carriageReturn = green("←")
final private def colour(str: String, colour: String): String = {
if (ascii || parsley.debug.renderAscii) str else s"$colour$str${Console.RESET}"
}
final protected def green(str: String): String = colour(str, Console.GREEN)
final protected def blue(str: String): String = colour(str, Console.BLUE)
final protected def red(str: String): String = colour(str, Console.RED)
final protected def white(str: String): String = colour(str, Console.WHITE)
}
private [instructions] trait PrettyPortal {
val name: String
final protected def portal(dir: Direction, @unused ctx: Context) = s"${dir.render}$name${dir.render}"
}
private [instructions] object PrettyPortal {
sealed abstract class Direction {
def render: String
}
case object Enter extends Direction {
override val render: String = ">"
}
case object Exit extends Direction {
override val render: String = "<"
}
}
private [instructions] object Indenter {
final val IndentWidth: Int = 2
private def indent(ctx: Context) = " " * (ctx.debuglvl * 2)
def indentAndUnlines(ctx: Context, lines: String*): String = {
lines.map(line => s"${indent(ctx)}$line").mkString("\n")
}
}
private [instructions] trait InputSlicer { this: Colours =>
private def start(ctx: Context): Int = Math.max(ctx.offset - Pad, 0)
private def end(ctx: Context): Int = Math.min(ctx.offset + Pad + 1, ctx.inputsz)
protected final def slice(ctx: Context): String = {
val end = this.end(ctx)
val s = ctx.input.mkString.substring(start(ctx), end).replace("\n", newline)
.replace(" ", space)
.replace("\r", carriageReturn)
if (end == ctx.inputsz) s"$s$endOfInput" else s
}
protected final def caret(ctx: Context): String = {
" " * (ctx.offset - start(ctx)) + blue("^")
}
}
private [instructions] object InputSlicer {
final val Pad: Int = 5
}
private [instructions] trait Logger extends PrettyPortal with InputSlicer with Colours {
final protected def preludeString(dir: Direction, ctx: Context, ends: String, watchedRegs: Seq[(Int, String)]) = {
val input = this.slice(ctx)
val prelude = s"${portal(dir, ctx)} (${ctx.line}, ${ctx.col}): "
val caret = (" " * prelude.length) + this.caret(ctx)
val regSummary = if (watchedRegs.isEmpty) Seq.empty else {
"watched registers:" +: watchedRegs.map {
case (addr, name) => s" $name = ${ctx.regs(addr)}"
} :+ ""
}
indentAndUnlines(ctx, s"$prelude$input$ends" +: caret +: regSummary: _*)
}
final protected def doBreak(ctx: Context): Unit = {
print(indentAndUnlines(ctx,
s"{stack: ${ctx.stack.mkString(", ")}}",
s"{registers: ${ctx.regs.zipWithIndex.map{case (x, i) => s"r$i: $x"}.mkString("[", ", ", "])}")}}",
"..."))
Console.in.read(): @nowarn
}
}
private [internal] final class LogBegin(var label: Int, override val name: String, override val ascii: Boolean, break: Boolean, watchedRegs: Seq[(Int, String)])
extends InstrWithLabel with Logger {
override def apply(ctx: Context): Unit = {
ensureRegularInstruction(ctx)
println(preludeString(Enter, ctx, "", watchedRegs))
if (break) doBreak(ctx)
ctx.debuglvl += 1
ctx.pushHandler(label)
ctx.inc()
}
override def toString: String = s"LogBegin($label, $name)"
}
private [internal] final class LogEnd(val name: String, val ascii: Boolean, break: Boolean, watchedRegs: Seq[(Int, String)]) extends Instr with Logger {
override def apply(ctx: Context): Unit = {
assert(ctx.running, "cannot wrap a Halt with a debug")
ctx.debuglvl -= 1
ctx.handlers = ctx.handlers.tail
val end = " " + {
if (ctx.good) {
ctx.inc()
green("Good")
}
else {
ctx.fail()
red("Fail")
}
}
println(preludeString(Exit, ctx, end, watchedRegs))
if (break) doBreak(ctx)
}
override def toString: String = s"LogEnd($name)"
}
private [instructions] trait ErrLogger extends PrettyPortal with Colours {
final protected def preludeString(dir: Direction, ctx: Context, ends: String) = {
val prelude = s"${portal(dir, ctx)} (offset ${ctx.offset}, line ${ctx.line}, col ${ctx.col})"
indentAndUnlines(ctx, s"$prelude$ends")
}
/*
final protected def doBreak(ctx: Context): Unit = {
print(indentAndUnlines(ctx,
s"{stack: ${ctx.stack.mkString(", ")}}",
s"{registers: ${ctx.regs.zipWithIndex
.map{case (x, i) => s"r$i: $x"}.mkString("[", ", ", "])}")}}",
"..."))
Console.in.read()
}
*/
}
private [instructions] final case class ErrLogData(hintsOffset: Int, hints: Set[ExpectItem]) {
def newHints(newHintsOffset: Int, newHints: Set[ExpectItem]): Set[ExpectItem] = {
if (stillValid(newHintsOffset)) newHints &~ hints
else newHints
}
def stillValid(newHintsOffset: Int): Boolean = hintsOffset == newHintsOffset
}
private [internal] final class LogErrBegin(var label: Int, override val name: String, override val ascii: Boolean)(implicit errBuilder: ErrorBuilder[_])
extends InstrWithLabel with ErrLogger {
override def apply(ctx: Context): Unit = {
ensureRegularInstruction(ctx)
val inFlightHints = ctx.inFlightHints.toSet
// This should print out a classic opening line, followed by the currently in-flight hints
println(preludeString(Enter, ctx, s": current hints are ${inFlightHints.map(_.formatExpect)} (valid at offset ${ctx.currentHintsValidOffset})"))
ctx.debuglvl += 1
ctx.stack.push(ErrLogData(ctx.currentHintsValidOffset, inFlightHints))
ctx.pushHandler(label)
ctx.inc()
}
override def toString: String = s"LogErrBegin($label, $name)"
}
private [internal] final class LogErrEnd(override val name: String, override val ascii: Boolean)(implicit errBuilder: ErrorBuilder[_])
extends Instr with ErrLogger {
override def apply(ctx: Context): Unit = {
assert(ctx.running, "cannot wrap a Halt with a debug")
ctx.debuglvl -= 1
ctx.handlers = ctx.handlers.tail
@unused val currentHintsValidOffset = ctx.currentHintsValidOffset
if (ctx.good) {
// In this case, the currently in-flight hints should be reported
val oldData = ctx.stack.pop[ErrLogData]()
val inFlightHints = ctx.inFlightHints.toSet
val formattedInFlight = inFlightHints.map(_.formatExpect)
val msgInit = s": ${green("Good")}, current hints are $formattedInFlight with"
if (!oldData.stillValid(ctx.currentHintsValidOffset)) {
println(preludeString(Exit, ctx, s"$msgInit old hints discarded (valid at offset ${ctx.currentHintsValidOffset})"))
}
else {
val newHints = oldData.newHints(ctx.currentHintsValidOffset, inFlightHints)
if (newHints.size == inFlightHints.size) {
println(preludeString(Exit, ctx, s"$msgInit all added since entry to debug (valid at offset ${ctx.currentHintsValidOffset})"))
}
else {
val formattedNewHints = oldData.newHints(ctx.currentHintsValidOffset, inFlightHints).map(_.formatExpect)
val msg = s"$msgInit $formattedNewHints added since entry to debug (valid at offset ${ctx.currentHintsValidOffset})"
println(preludeString(Exit, ctx, msg))
}
}
ctx.inc()
}
else {
// In this case, the current top of stack error message is reported
// For this, there needs to be a verbose mode that prints out the composite error
// as opposed to the simplified error (post-merge)
// there should be different levels of verbose flags, such that we can also display the expecteds _under_ a label
@unused val oldData = ctx.stack.peek[ErrLogData]
val defuncErr = ctx.inFlightError
val err = defuncErr.asParseError(ctx.errorItemBuilder)
println(preludeString(Exit, ctx, s": ${red("Fail")}"))
println(Indenter.indentAndUnlines(ctx, LogErrEnd.format(err): _*))
ctx.fail()
}
}
override def toString: String = s"LogErrEnd($name)"
}
private [instructions] object LogErrEnd {
// TODO: We want to mark errors that are behind the current context offsets as amended in some way
// ideally we can provide meta-information about merges and so on too: this needs to be extracted by
// analysing the DefuncError itself! (Note that StringTok does not amend its error via operation,
// it does so by direct manipulation. It should be treated as an amend regardless here).
// Surpressed labels can be presumably captured by calling asParseErrorSlow under a label node
// again, requiring a deeper traversal of the tree
def format(err: ParseError): Seq[String] = err match {
case FancyError(offset, line, col, msgs, _) => s"generated specialised error (offset $offset, line $line, col $col) {" +: msgs :+ "}"
case TrivialError(offset, line, col, unexpected, expecteds, reasons, lexicalError) =>
Seq(s"generated vanilla error (offset $offset, line $line, col $col) {",
s" unexpected item = ${unexpected.fold(_ => "missing", _.formatUnexpect(lexicalError)._1.toString)}",
s" expected item(s) = ${expecteds.map(_.formatExpect)}",
s" reasons =${if (reasons.isEmpty) " no reasons given" else ""}") ++
reasons.map(" " + _) :+
"}"
}
}
private [internal] final class ProfileEnter(var label: Int, name: String, profiler: Profiler) extends InstrWithLabel {
private [this] val entries = profiler.entriesFor(name)
override def apply(ctx: Context): Unit = {
ensureRegularInstruction(ctx)
ctx.pushHandler(label)
ctx.inc()
entries += profiler.monotone(System.nanoTime())
}
override def toString: String = s"ProfileEnter($label, $name)"
}
private [internal] final class ProfileExit(name: String, profiler: Profiler) extends Instr {
private [this] val exits = profiler.exitsFor(name)
override def apply(ctx: Context): Unit = {
exits += profiler.monotone(System.nanoTime())
ctx.handlers = ctx.handlers.tail
if (ctx.good) ctx.inc()
else ctx.fail()
}
override def toString: String = s"ProfileExit($name)"
}
// $COVERAGE-ON$
© 2015 - 2024 Weber Informatics LLC | Privacy Policy