From 61793bdc7f1e1466cc1534e7e288fd46a0141603 Mon Sep 17 00:00:00 2001 From: Jamie Willis Date: Thu, 2 Jan 2025 16:50:56 +0000 Subject: [PATCH] doc: added page for PrintView and attachment --- docs/laika/versionInfo.json | 2 + docs/parsley-debug/attachment.md | 43 +++++++++++++++++ docs/parsley-debug/debug-views/PrintView.md | 47 +++++++++++++++++++ docs/parsley-debug/debug-views/directory.conf | 4 ++ docs/parsley-debug/directory.conf | 2 + .../scala/parsley/debug/ParseAttempt.scala | 2 +- .../main/scala/parsley/debug/PrintView.scala | 6 ++- .../parsley/debug/internal/DebugContext.scala | 5 +- .../debug/internal/TransientDebugTree.scala | 8 ++-- 9 files changed, 110 insertions(+), 9 deletions(-) create mode 100644 docs/parsley-debug/attachment.md create mode 100644 docs/parsley-debug/debug-views/PrintView.md create mode 100644 docs/parsley-debug/debug-views/directory.conf diff --git a/docs/laika/versionInfo.json b/docs/laika/versionInfo.json index 4d1c57c7a..2c80a794f 100644 --- a/docs/laika/versionInfo.json +++ b/docs/laika/versionInfo.json @@ -36,6 +36,8 @@ { "path": "/tutorial/interlude-1-haskell.html", "versions": ["4.4", "4.5", "5.0"] }, { "path": "/tutorial/parser-bridge-pattern.html", "versions": ["4.4", "4.5", "5.0"] }, { "path": "/parsley-debug/debuggable.html", "versions": ["5.0"]}, + { "path": "/parsley-debug/attachment.html", "versions": ["5.0"]}, + { "path": "/parsley-debug/debug-views/PrintView.html", "versions": ["5.0"]}, { "path": "/parsley-debug/recursion-detection.html", "versions": ["5.0"]} ] } diff --git a/docs/parsley-debug/attachment.md b/docs/parsley-debug/attachment.md new file mode 100644 index 000000000..e6bc78b3f --- /dev/null +++ b/docs/parsley-debug/attachment.md @@ -0,0 +1,43 @@ +{% +laika.versioned = true +laika.site.metadata.description = "How attachment works for debug views." +%} + +# Attaching Debugger Views +The new functionality in `parsley-debug` involves attaching a debugger to a parser +that will be ran. There are two combinators to do this found in `parsley.debug.combinator`: +`attach`, and `attachReusable`. Before we see these, it will be useful to understand how +`DebugView`s work. + +@:callout(info) +By importing `parsley.debug.combinator.DebuggerOps`, you will have access to the +method-style implementation of these combinators. +@:@ + +## `DebugView` +The `DebugView.SingleUse` and `DebugView.Reusable` traits represent renderers for debug information. +By default, `parsley-debug` includes `PrintView`, which can be used to "render" debug information +out onto the console, or perhaps to a file (or any `PrintStream`/`OutputStream`). The reusability +aspect refers to whether or not multiple such views can work simultaneously: see `attachReusable` +below. + +The views themselves do not necessarily have to directly render the debugging content themselves, +they could be remote connections, for instance; but the point is that they are what is attached +to a specific parser. When the parser is ran, the debug combinator collects the information, and +the view processes that information in some way. This process is done via *attachment*. + +## `attach` and `attachReusable` +The `attach` combinator takes a `DebugView` (reusable or otherwise) and optionally a "string rules" +function, which determines whether or not a value is eagerly turned into a string or preserved in +its original form within the internal "`DebugTree`". In return, you get a new parser, which will +feed debugging information to the provided `DebugView`. + +@:callout(warning) +You should only use the parser returned by `attach` in one place! If you want to debug a parser +that appears in more than one place, you must use `attachReusable`. +@:@ + +In contrast, `attachReusable` is similar, but takes a *by-name* `DebugView`, which may be recreated +each use, and returns a function that produces new `Parsley` values. The idea is that if you need +to use the parser in more than one place, you call the function more than once to produce independently +bound parsers. diff --git a/docs/parsley-debug/debug-views/PrintView.md b/docs/parsley-debug/debug-views/PrintView.md new file mode 100644 index 000000000..349224520 --- /dev/null +++ b/docs/parsley-debug/debug-views/PrintView.md @@ -0,0 +1,47 @@ +{% +laika.versioned = true +laika.title = "`PrintView`" +laika.site.metadata.description = "The built-in view for plain console debugging" +%} + +# `PrintView` (`parsley-debug`) +The core `parsley-debug` library contains one view, namely `PrintView`. This can be +used in place of more advanced functionality present in the other "companion" libraries. +It is perhaps less featureful than the "vanilla" `.debug` combinator, which supports breakpoints, +but does work cross platform and across all Scala versions. The advantage over `.debug`, of course, +is that for large-scale debugging, `@debuggable` is far more ergonomic to apply to a whole parser, +and gives more intermediate information. With no internal state, this view is reusable, however, +concurrent executions within a parser (if used with `attachReusable`) may interleave and be confusing. + +@:callout(info) +Please see the [`@parsley.debuggable`](../debuggable.md) page first, as this is used +to provide meaningful names for this functionality. +@:@ + +## Configuration +The `PrintView` configuration is very simple. By default, the `PrintView` object will print the +debug trace directly to `Console.out`. It is possible to use the `PrintView.apply` methods to +produce new views that print their output elsewhere: for instance to a file. + +## What does it look like? +As an example, here is the trace without `@debuggable`: + +```scala mdoc:to-string +import parsley.quick.* +import parsley.debug.combinator.* +import parsley.debug.PrintView + +val hello = ( atomic(string("hello")) + | (string("hey") + | string("hi")) + ) + +hello.attach(PrintView).parse("hey") + +hello.attach(PrintView).parse("hi") +``` + +As you can see, without an `@debuggable` annotation, this just prints the names of the combinators +used to construct the parser, which is ok here, but would be undecipherable for more complex parsers. +The rendering style is slightly different to the vanilla combinator as well, rendering in a tree +shape as opposed to using explicit entry/exit markers. diff --git a/docs/parsley-debug/debug-views/directory.conf b/docs/parsley-debug/debug-views/directory.conf new file mode 100644 index 000000000..374a16874 --- /dev/null +++ b/docs/parsley-debug/debug-views/directory.conf @@ -0,0 +1,4 @@ +laika.title = "Debug Views" +laika.navigationOrder = [ + PrintView.md +] diff --git a/docs/parsley-debug/directory.conf b/docs/parsley-debug/directory.conf index 4079e9c89..621e0cbbb 100644 --- a/docs/parsley-debug/directory.conf +++ b/docs/parsley-debug/directory.conf @@ -1,5 +1,7 @@ laika.title = "parsley-debug" laika.navigationOrder = [ debuggable.md + attachment.md + debug-views recursion-detection.md ] diff --git a/parsley-debug/shared/src/main/scala/parsley/debug/ParseAttempt.scala b/parsley-debug/shared/src/main/scala/parsley/debug/ParseAttempt.scala index 87d556d04..6ad8411f0 100644 --- a/parsley-debug/shared/src/main/scala/parsley/debug/ParseAttempt.scala +++ b/parsley-debug/shared/src/main/scala/parsley/debug/ParseAttempt.scala @@ -12,7 +12,7 @@ import ParseAttempt._ // scalastyle:ignore underscore.import * @since 4.5.0 */ 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... + /** The input parsed, as raw text. */ //TODO: I think this can be removed as it can be derived from the full input... (apparently, these are used for the "augments") val rawInput: Input = inp /** This offset is where the parse attempt started in the input. */ diff --git a/parsley-debug/shared/src/main/scala/parsley/debug/PrintView.scala b/parsley-debug/shared/src/main/scala/parsley/debug/PrintView.scala index 813dee6f8..55ef3769c 100644 --- a/parsley-debug/shared/src/main/scala/parsley/debug/PrintView.scala +++ b/parsley-debug/shared/src/main/scala/parsley/debug/PrintView.scala @@ -18,7 +18,7 @@ import scala.annotation.tailrec * @group debugview */ object PrintView extends DebugView.Reusable with PrintView { - override protected val out: PrintStream = Console.out + override protected def out: PrintStream = Console.out /** Create a string pretty-printer that outputs to an arbitrary place * * @since 5.0.0 @@ -29,12 +29,14 @@ object PrintView extends DebugView.Reusable with PrintView { * @since 5.0.0 */ def apply(print: PrintStream): DebugView.Reusable = new PrintView { + // this could be a file-handle, so we override as a val override protected val out: PrintStream = print } } private [debug] sealed trait PrintView extends DebugView.Reusable { - protected val out: PrintStream + // this could be a stream that updates, like Console.out, so eval on demand by default + protected def out: PrintStream override private [debug] def render(input: =>String, tree: =>DebugTree): Unit = { out.println(s"${tree.parserName}'s parse tree for input:\n\n$input\n\n") pretty(tree) diff --git a/parsley-debug/shared/src/main/scala/parsley/debug/internal/DebugContext.scala b/parsley-debug/shared/src/main/scala/parsley/debug/internal/DebugContext.scala index 18f1d5aa6..25b3fed83 100644 --- a/parsley-debug/shared/src/main/scala/parsley/debug/internal/DebugContext.scala +++ b/parsley-debug/shared/src/main/scala/parsley/debug/internal/DebugContext.scala @@ -7,7 +7,6 @@ package parsley.debug.internal import scala.collection.mutable -import org.typelevel.scalaccompat.annotation.unused import parsley.XAssert import parsley.debug.ParseAttempt import parsley.internal.deepembedding.frontend.LazyParsley @@ -44,7 +43,7 @@ private [parsley] class DebugContext(private val toStringRules: PartialFunction[ XAssert.assert(!(ch.size > 1), s"The root tree has somehow gained multiple children. (${ch.size})") // This should never fail. - ch.collectFirst { case (_, x) => x }.get + ch.valuesIterator.next() } // Add an attempt of parsing at the current stack point. @@ -92,6 +91,6 @@ private [parsley] class DebugContext(private val toStringRules: PartialFunction[ assert(builderStack.nonEmpty, "Parser stack underflow on pop.") // $COVERAGE-ON$ // Remove first parser off stack, as if returning from that parser. - val _ = builderStack.remove(0).applyInputAugments(): @unused + builderStack.remove(0).applyInputAugments() } } diff --git a/parsley-debug/shared/src/main/scala/parsley/debug/internal/TransientDebugTree.scala b/parsley-debug/shared/src/main/scala/parsley/debug/internal/TransientDebugTree.scala index 4846544c1..3f4be54ae 100644 --- a/parsley-debug/shared/src/main/scala/parsley/debug/internal/TransientDebugTree.scala +++ b/parsley-debug/shared/src/main/scala/parsley/debug/internal/TransientDebugTree.scala @@ -58,7 +58,7 @@ private [parsley] class TransientDebugTree(var name: String = "", var internal: uuid } - private [parsley] def applyInputAugments(): TransientDebugTree = { + private [parsley] def applyInputAugments(): Unit = { parse = parse.map { p => // Augments are single-use. val ua = augments.toList @@ -66,8 +66,10 @@ private [parsley] class TransientDebugTree(var name: String = "", var internal: def basis(int: Int): Int = int - p.fromOffset - p.copy(inp = ua.foldRight(p.rawInput) { case ((aid, (ast, aen)), st) => st.slice(0, basis(ast)) + s"{$aid}" + st.drop(basis(aen)) }) + p.copy(inp = ua.foldRight(p.rawInput) { + // FIXME: don't augment input that wasn't an exact match (i.e. failed) + case ((aid, (ast, aen)), st) => s"${st.take(basis(ast))}{$aid}${st.drop(basis(aen))}" + }) } - this } }