parsley.debugger.util.Collector.scala Maven / Gradle / Ivy
/*
* Copyright 2020 Parsley Contributors
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package parsley.debugger.util
import parsley.Parsley
import parsley.debugger.internal.Rename
import parsley.token.Lexer
import parsley.internal.deepembedding.frontend.LazyParsley
/** Attempt to collect all the fields in a class or object that contain a
* parser of type [[parsley.Parsley]], or from a [[parsley.token.Lexer]].
*
* This information is used later in the debug tree-building process to rename certain parsers
* so that they do not end up being named things like "packageanon".
*
* You only need to run this once per parser-holding object.
*
* @since 4.5.0
*/
object Collector {
/** Collect names of parsers from an object.
*
* @note For Scala 3 on the JVM, it is advised that all parsers in objects being introspected are
* marked `public`, as otherwise, a semi-deprecated call to `setAccessible` will be called on
* a private object, which may be restricted or removed in a future version of the JVM. It may
* be advised to manually name one's parsers (to be debugged) using [[assignName]] or
* [[parsley.debugger.combinator.named]] if that warning is not desirable.
*/
def names(obj: Any): Unit = {
collectDefault() // Runs only once, ever, for a program execution.
Rename.addNames(XCollector.collectNames(obj))
}
/** Collect names of parsers from a [[parsley.token.Lexer]].
*
* @note For Scala 3 on the JVM, this may trigger a warning about `setAccessible` for private members
* being deprecated.
* @see [[names]] for more information regarding the warning.
*/
def lexer(lexer: Lexer): Unit = {
collectDefault()
Rename.addNames(XCollector.collectLexer(lexer))
}
// $COVERAGE-OFF$
/** Manually add a name for a parser by reference.
*
* Can also be used if a more informative name for a parser is wanted.
* In this case, use this method after using [[names]] or [[lexer]] to override the automatically
* collected / found name.
*
* @note Names assigned using this will take precedence over names assigned using [[parsley.debugger.combinator.named]].
*/
def assignName(par: Parsley[_], name: String): Unit =
Rename.addName(par.internal, name)
/** Does the implementation of the collector for the current Scala platform actually work in
* automatically finding parsers in objects and getting their field names as written in your
* parser code?
*
* @note Manually named parsers using [[assignName]] or [[parsley.debugger.combinator.named]]
* will still work regardless if the platform is supported or not.
*/
@inline def isSupported: Boolean =
XCollector.supported
/** Collect the names of Parsley's various default singleton parsers. */
private var defaultCollected: Boolean = false
private def collectDefault(): Unit =
if (isSupported) {
this.synchronized {
if (!defaultCollected) {
defaultCollected = true
names(parsley.character)
names(parsley.combinator)
names(parsley.Parsley)
names(parsley.position)
}
}
}
// $COVERAGE-ON$
}
/** A representation of the current implementation that [[Collector]] uses in order to
* actually collect the names of parsers. One of these will need to be implemented under the name
* `XCollector` under `parsley.debugger.util` for each different Scala runtime.
*
* @note This is an internal detail, so users do not have to interact with this if not necessary.
*/
// $COVERAGE-OFF$
private [parsley] abstract class CollectorImpl {
/** Collect names of parsers from an object. */
def collectNames(obj: Any): Map[LazyParsley[_], String]
/** Collect names of parsers from a [[parsley.token.Lexer]]. */
def collectLexer(lexer: Lexer): Map[LazyParsley[_], String]
/** Does the current platform's [[CollectorImpl]] actually get parsers from objects? */
val supported: Boolean
// Try grabbing a parser from a LazyParsley or Parsley instance.
// XXX: Doing a direct type test with match will cause Parsley objects to be instantiated.
// XXX: Using a match-case expression without @unchecked causes an error in CI as these matches are not exhaustive.
protected def tryExtract(par: Any): LazyParsley[_] = (par: @unchecked) match {
case l: LazyParsley[_] => l
case p: Parsley[_] => p.internal
}
// All of these objects inside a lexer are exposed, so are easy to collect parser names from.
// The rest will need to be handled by reflection.
// If any public objects are added to Lexer, please add them to this list.
@inline protected final def safeLexerObjects(lexer: Lexer): List[Any] = List(
lexer,
lexer.space,
lexer.lexeme,
lexer.lexeme.names,
lexer.lexeme.symbol,
lexer.nonlexeme,
lexer.nonlexeme.names,
lexer.nonlexeme.symbol,
)
// All of these objects inside a lexer have private sub-objects which contain parsers.
// They require special handling where those sub-objects must be exposed, and then the parsers inside will be
// extracted in a second step.
@deprecated("This method is no longer needed, because the lexer does not nest anymore for numeric/test/enclosing/separators", "4.5.0")
@inline protected final def unsafeLexerObjects(lexer: Lexer): List[Any] = Nil
}
// $COVERAGE-ON$