-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
147 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
{% | ||
laika.title = "`parsley.debug`" | ||
parsley.tabname = "Basic Debug Combinators (parsley.debug)" | ||
laika.site.metadata.description = "This page describes the built-in basic debugging combinators." | ||
%} | ||
# Basic Debug Combinators (`parsley.debug`) | ||
Parsley has a collection of basic debugging utilities found within `parsley.debug`. These can help | ||
debug errant parsers, understand how error messages have been generated, and provide a rough sense | ||
of what parsers take the most time to execute. | ||
|
||
The combinators themselves are all contained within `parsley.debug.DebugCombinators`. | ||
|
||
@:callout(info) | ||
*The Scaladoc for this page can be found at [`parsley.debug`](@:api(parsley.debug$)) and [`parsley.debug.DebugCombinators`](@:api(parsley.debug$$DebugCombinators))* | ||
@:@ | ||
|
||
## Debugging Problematic Parsers (`debug`) | ||
The most common quick debugging combinator is `debug`, which at its simplest prints some information | ||
on entering and exiting a combinator: | ||
|
||
```scala mdoc:to-string | ||
import parsley.Parsley.atomic | ||
import parsley.character.string | ||
import parsley.debug, debug._ | ||
|
||
val hello = ( atomic(string("hello").debug("hello")).debug("atomic1") | ||
| string("hey").debug("hey") | ||
| string("hi").debug("hi") | ||
) | ||
|
||
debug.disableColourRendering() | ||
|
||
hello.parse("hey") | ||
|
||
hello.parse("hi") | ||
``` | ||
|
||
In the above example, an unexpected failure to parse the input `"hi"` is being debugged using | ||
`debug`. Each of the `string` combinators are annotated, as well as the `atomic`. This allows | ||
us to see the control flow of the parser as it executes, as well as where the input was read up to. | ||
In this case, we can see that `atomic` has undone input consumption, but that doesn't apply to `hey`. | ||
In other words, there is another `atomic` missing! We could have added `debug` to the `|` combinator | ||
as well to make it even clearer, of course. Though not visible above, the output is usually coloured. | ||
If this causes problems, `debug.disableColourRendering()` will disable it, or the `coloured` parameter | ||
can be set to `false` on the combinator. | ||
|
||
### Breakpoints | ||
The `Breakpoint` type has values `EntryBreak`, `ExitBreak`, `FullBreak`, and `NoBreak`, which is the | ||
default. `FullBreak` has the effect of both `EntryBreak` and `ExitBreak` combined: `EntryBreak` will | ||
pause execution on the entry to the combinator, requiring input on the console to proceed, and `ExitBreak` | ||
will do the same during the exit. | ||
|
||
### Watching Registers | ||
The `debug` combinator takes a variadic number of register/name pairs as its last argument. These | ||
allow you to watch the values stored in registers as well during the debugging process. For instance: | ||
|
||
```scala mdoc:to-string | ||
import parsley.Parsley.atomic | ||
import parsley.registers._ | ||
import parsley.character.string | ||
import parsley.debug._ | ||
|
||
val p = 0.makeReg { r1 => | ||
false.makeReg { r2 => | ||
val p = ( string("hello") | ||
~> r1.modify(_ + 5) | ||
~> ( string("!") | ||
| r2.put(true) ~> string("?") | ||
).debug("punctuation", r2 -> "r2") | ||
) | ||
r1.rollback(atomic(p).debug("hello!", r1 -> "r1", r2 -> "r2")) | ||
.debug("rollback", r1 -> "r1") | ||
} | ||
} | ||
|
||
p.parse("hello world") | ||
``` | ||
|
||
## Debugging Error Messages (`debugError`) | ||
The `debugError` is a slightly more experimental combinator that aims to provide some (lower-level) | ||
insight into how an error message came to be. For instance: | ||
|
||
```scala mdoc:to-string | ||
import parsley.character.{letter, digit, char} | ||
import parsley.combinator.many | ||
import parsley.debug._ | ||
|
||
val q = (many( ( digit.debugError("digit") | ||
| letter.debugError("letter") | ||
).debugError("letter or digit") | ||
).debugError("many letterOrDigit") | ||
~> many(char('@').debugError("@")).debugError("many @") | ||
~> char('#').debugError("#")) | char('!').debugError("!") | ||
|
||
q.parse("$") | ||
``` | ||
|
||
In the above example, you can see how each individual error is raised, as well as evidence of merging, | ||
and how errors can be turned into "hints" if the error is successfully recovered from: this means | ||
that the label may be re-incorporated into the error again later if they are at the valid offset, | ||
as seen in the errors for `char('#')`. | ||
|
||
## Profiling Parser (`Profiler`) | ||
The `Profiler` class, and the accompanying `profile` combinator, provide a *rough* guideline of how | ||
much of the runtime a parser might be taking up. The execution of each combinator is measured with | ||
a resolution of 100ns. First, a `Profiler` object must be set up and implicitly available in scope: | ||
its role is to collect the profiling samples. Then, a parser annotated with `profile` combinators | ||
is ran, and the results can be displayed with `profiler.summary()`. | ||
|
||
@:callout(warning) | ||
There is a disclaimer that the profiler "just provides data", no guarantee about its statistical | ||
significance is given. Multiple runs can be performed and these will be aggregated, the `profiler` | ||
can be cleared using `clear()`. | ||
@:@ | ||
|
||
```scala mdoc:height=0 | ||
import parsley.Parsley | ||
import parsley.character.{string, char} | ||
import parsley.combinator.traverse | ||
import parsley.debug._ | ||
|
||
def classicString(s: String): Parsley[String] = | ||
traverse(char(_), s.toList: _*).map(_.mkString) | ||
|
||
implicit val profiler: Profiler = new Profiler | ||
val strings = many(classicString("...").profile("classic string") | ||
<~> string("!!!").profile("optimised string")) | ||
val stringsVoid = many(classicString("...").profile("voided classic string") | ||
<~> string("!!!").profile("voided optimised string")).void | ||
|
||
strings.parse("...!!!" * 10000) | ||
stringsVoid.parse("...!!!" * 10000) | ||
|
||
profiler.summary() | ||
``` | ||
|
||
The above example shows that the `string` combinator is much faster than the "classic" definition | ||
in terms of `traverse` and `char` (not even accounting for its improved error messages!). However, | ||
it also shows that when the results are not required (as indicated by the `void` combinator, which | ||
_aggressively_ suppresses result generation underneath it), the combinators perform much more similarly. | ||
You will, however, notice variance depending on when you visit this page: these results are generated | ||
each publish, and sometimes the non-voided `string` can outperform the voided one by pure chance! | ||
|
||
The `profile` combinator will account for other profiled combinators underneath it, accounting for | ||
their "self time" only. This helps to measure the impact of a specific sub-parser more accurately. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,7 @@ laika.navigationOrder = [ | |
syntax.md | ||
position.md | ||
state.md | ||
debug.md | ||
expr | ||
token | ||
errors | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters