Skip to content

Commit c84d5d3

Browse files
authored
Adding SourceCollector to enable debuggers to access source files. (#263)
Enables the collection of all source files that use the @debuggable annotation.
2 parents 8cff7e8 + 7c97519 commit c84d5d3

File tree

5 files changed

+78
-11
lines changed

5 files changed

+78
-11
lines changed

docs/parsley-debug/recursion-detection.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ object parsers {
3434
}
3535
```
3636
```scala mdoc:invisible
37-
parsley.debug.util.Collector.registerNames(Map(parsers.p -> "p", parsers.q -> "q", parsers.r -> "r"))
37+
parsley.debug.util.Collector.registerNames(Map(parsers.p -> "p", parsers.q -> "q", parsers.r -> "r"), None)
3838
```
3939

4040
While `parsers.top.parse("a")` would work fine, consider what happens if we pass the empty string:
@@ -97,7 +97,7 @@ object stateful {
9797
}
9898
```
9999
```scala mdoc:invisible
100-
parsley.debug.util.Collector.registerNames(Map(stateful.p -> "p", stateful.q -> "q", stateful.top -> "top"))
100+
parsley.debug.util.Collector.registerNames(Map(stateful.p -> "p", stateful.q -> "q", stateful.top -> "top"), None)
101101
```
102102

103103
This parser is a contrived example, admittedly, but illustrates what `detectDivergence` is capable of.
@@ -134,7 +134,7 @@ object nameless {
134134
}
135135
```
136136
```scala mdoc:invisible
137-
parsley.debug.util.Collector.registerNames(Map(nameless.top -> "top"))
137+
parsley.debug.util.Collector.registerNames(Map(nameless.top -> "top"), None)
138138
```
139139

140140
While we might expect this to work out much like the `stateful` example above, in reality we see

parsley-debug/shared/src/main/scala-2/parsley/debuggable.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ private object debuggable {
113113
// the idea is we inject a call to Collector.registerNames with a constructed
114114
// map from these identifiers to their compile-time names
115115
val listOfParsers = q"List(..${parsers.map(tr => q"${Ident(tr)} -> ${tr.toString}")})"
116-
val registration = q"parsley.debug.util.Collector.registerNames($listOfParsers.toMap)"
116+
val registration = q"parsley.debug.util.Collector.registerNames($listOfParsers.toMap, $None)"
117117
// add the registration as the last statement in the object
118118
// TODO: in future, we want to modify all `def`s with a top level `opaque` combinator
119119
// that will require a bit more modification of the overall `body`, unfortunately

parsley-debug/shared/src/main/scala-3/parsley/debuggable.scala

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,22 @@ import scala.quoted.*
2626
case cls@ClassDef(clsName, constr, parents, selfOpt, body) =>
2727
val parsleyTy = TypeRepr.of[Parsley[?]].typeSymbol
2828
val fields = cls.symbol.fieldMembers.view.map(_.termRef)
29-
val parsers = fields.filter(_.typeSymbol == parsleyTy)
30-
29+
val parsers = fields.filter(_.typeSymbol == parsleyTy).toList
3130
// the idea is we inject a call to Collector.registerNames with a constructed
3231
// map from these identifiers to their compile-time names
3332
val listOfParsers = Expr.ofList {
34-
parsers.map(tr => Expr.ofTuple((Ident(tr).asExprOf[parsley.Parsley[?]], Expr(tr.name)))).toList
33+
parsers.map(tr => Expr.ofTuple((Ident(tr).asExprOf[parsley.Parsley[?]], Expr(tr.name))))
3534
}
36-
val registration = '{parsley.debug.util.Collector.registerNames($listOfParsers.toMap)}.asTerm
35+
36+
val filePath = Expr(cls.pos.sourceFile.path)
37+
val positionInfo = Expr.ofList(
38+
for termRef <- parsers
39+
pos <- termRef.termSymbol.pos
40+
yield Expr.ofTuple(Expr(pos.start), Expr(pos.end))
41+
)
42+
val parserInfo = '{Some(($filePath, $positionInfo))}
43+
44+
val registration = '{parsley.debug.util.Collector.registerNames($listOfParsers.toMap, $parserInfo)}.asTerm
3745

3846
// add the registration as the last statement in the object
3947
// TODO: in future, we want to modify all `def`s with a top level `opaque` combinator
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright 2020 Parsley Contributors <https://github.com/j-mie6/Parsley/graphs/contributors>
3+
*
4+
* SPDX-License-Identifier: BSD-3-Clause
5+
*/
6+
package parsley.debug.internal
7+
8+
import scala.collection.mutable
9+
10+
/**
11+
* The ParserInfo class represents metadata about a parser module obtained
12+
* using the debuggable macro.
13+
*
14+
* @param path The absolute path to a parser.
15+
* @param positions A list of (start, end) positions of parsers within a
16+
* module.
17+
*/
18+
final private [parsley] case class ParserInfo(val path: String, val positions: List[(Int, Int)])
19+
20+
/**
21+
* Object with methods to add detected parser information to a mutable set,
22+
* allowing debugged source files and the locations of their parsers to be
23+
* known at runtime.
24+
*
25+
* This is mainly to enable a debugger to display source files inside of the
26+
* application so that debugging faulty code can easily be done in the same
27+
* application.
28+
*/
29+
private [parsley] object ParserInfoCollector {
30+
// The set of collected source file paths
31+
private lazy val collected: mutable.Set[ParserInfo] = mutable.Set.empty
32+
33+
/**
34+
* Add an iterable set of parser information to the collection.
35+
*
36+
* @param info The parser information to add.
37+
*/
38+
private [parsley] def addInfos(info: Seq[ParserInfo]): Unit = collected ++= info
39+
40+
/**
41+
* Add a single parser information class to the collection.
42+
*
43+
* @param info The parser info to add.
44+
*/
45+
private [parsley] def addInfo(info: ParserInfo): Unit = collected += info
46+
47+
// Obtain the collected information.
48+
private [parsley] lazy val info: Set[ParserInfo] = collected.toSet
49+
}

parsley-debug/shared/src/main/scala/parsley/debug/util/Collector.scala

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ package parsley.debug.util
88
//import scala.annotation.nowarn
99

1010
import parsley.Parsley
11-
import parsley.debug.internal.Renamer
11+
import parsley.debug.internal.{Renamer, ParserInfoCollector, ParserInfo}
1212
import parsley.token.Lexer
1313

1414
import parsley.internal.deepembedding.frontend.LazyParsley
@@ -35,8 +35,18 @@ object Collector {
3535
Renamer.addNames(XCollector.collectNames(obj))
3636
}*/
3737

38-
/** This is an internal method used by the `parsley.debuggable` annotation */
39-
def registerNames(names: Map[Parsley[_], String]): Unit = {
38+
/**
39+
* This is an internal method used by the `parsley.debuggable` annotation.
40+
*
41+
* This function registers the names collected by the annotation to the
42+
* `Renamer` object, and also adds parser information the annotation has
43+
* found inside of the the `ParserInfoCollector` object.
44+
*
45+
* @param names List of parsers mapped to their string representations.
46+
* @param parserInfo Parser info collected from the annotation.
47+
*/
48+
def registerNames(names: Map[Parsley[_], String], parserInfo: Option[(String, List[(Int, Int)])]): Unit = {
49+
parserInfo.map { case (filename, positions) => ParserInfoCollector.addInfo(ParserInfo(filename, positions)) }
4050
Renamer.addNames(names.map { case (k, v) => k.internal -> v })
4151
}
4252

0 commit comments

Comments
 (0)