All Downloads are FREE. Search and download functionalities are using the official Maven repository.

parsley.debugger.frontend.DebugFrontend.scala Maven / Gradle / Ivy

/*
 * Copyright 2020 Parsley Contributors 
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */
package parsley.debugger.frontend

import parsley.debugger.DebugTree
import parsley.debugger.internal.XIllegalStateException

/** A common interface for a debug frontend for a debugger to present the debug tree. Inherit from
  * one of the two provided subtraits to use.
  *
  * Any compliant implementation that handles all nodes of a [[parsley.debugger.DebugTree]] can be
  * used in place of any other implementation (e.g. a serialiser to JSON, a GUI, etc.).
  *
  * If a frontend is reusable, one can implement it as either an `object` or a `class`, but an `object`
  * is recommended. Either way, it should inherit [[ReusableFrontend]].
  *
  * If a frontend is single-use (e.g. it has some non-reusable state), never implement it as an `object`. Always
  * implement single-use frontends as a `class` of some sort inheriting from [[SingleUseFrontend]].
  *
  * If the results of some processing of a tree are needed out of a frontend, create a frontend class that accepts
  * some continuation parameter of `ReturnType => Unit` and call it somewhere within the implementation.
  *
  * @since 4.5.0
  */
sealed trait DebugFrontend {
    // Is this frontend stateful (and should only be able to run once)?
    protected [frontend] val reusable: Boolean

    // Tracks if this frontend has run already.
    private var hasRun: Boolean = false

    // This function localises the use of synchronized down to this expression only.
    @inline private def hasBeenRun: Boolean = this.synchronized {
        (reusable && hasRun) || { hasRun = true; false }
    }

    /** Process a debug tree using whatever the frontend is doing to present the tree in some way.
      *
      * @param input The full input of the parse.
      * @param tree  Debug tree to process.
      */
    final def process(input: => String, tree: => DebugTree): Unit = {
        if (hasBeenRun) {
            // XXX: There isn't really another way to enforce not running a stateful frontend more than once that isn't just "do nothing".
            //      Especially since doing nothing turns that action into a silent error, which is generally less preferable to "loud"
            //      errors. Failing fast may be better for some frontends.
            throw new XIllegalStateException("Stateful frontend has already been run.").except // scalastyle:ignore throw
        } else {
            processImpl(input, tree)
        }
    }

    /** The actual method that does the processing of the tree.
      * Override this to process a tree in a custom way.
      */
    protected def processImpl(input: => String, tree: => DebugTree): Unit
}

/** Signifies that the frontend inheriting from this can be used multiple times.
  *
  * @see [[DebugFrontend]]
  * @since 4.5.0
  */
trait ReusableFrontend extends DebugFrontend {
    override protected [frontend] final val reusable: Boolean = false
}

/** Signifies that the frontend inheriting from this can only be run once.
  *
  * @see [[DebugFrontend]]
  * @since 4.5.0
  */
trait SingleUseFrontend extends DebugFrontend {
    override protected [frontend] final val reusable: Boolean = true
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy