Skip to content

Commit

Permalink
doc: added page for PrintView and attachment
Browse files Browse the repository at this point in the history
  • Loading branch information
j-mie6 committed Jan 2, 2025
1 parent 3f3d8ef commit 61793bd
Show file tree
Hide file tree
Showing 9 changed files with 110 additions and 9 deletions.
2 changes: 2 additions & 0 deletions docs/laika/versionInfo.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"]}
]
}
43 changes: 43 additions & 0 deletions docs/parsley-debug/attachment.md
Original file line number Diff line number Diff line change
@@ -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.
47 changes: 47 additions & 0 deletions docs/parsley-debug/debug-views/PrintView.md
Original file line number Diff line number Diff line change
@@ -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.
4 changes: 4 additions & 0 deletions docs/parsley-debug/debug-views/directory.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
laika.title = "Debug Views"
laika.navigationOrder = [
PrintView.md
]
2 changes: 2 additions & 0 deletions docs/parsley-debug/directory.conf
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
laika.title = "parsley-debug"
laika.navigationOrder = [
debuggable.md
attachment.md
debug-views
recursion-detection.md
]
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,16 +58,18 @@ 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
augments.clear()

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
}
}

0 comments on commit 61793bd

Please sign in to comment.