Skip to content

Commit

Permalink
Document the Pass module
Browse files Browse the repository at this point in the history
Signed-off-by: reidspencer <reid.spencer@yoppworks.com>
  • Loading branch information
reid-spencer committed Oct 21, 2023
1 parent cfd220f commit 5d302ad
Showing 1 changed file with 108 additions and 20 deletions.
128 changes: 108 additions & 20 deletions passes/src/main/scala/com/reactific/riddl/passes/Pass.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,17 @@ import scala.annotation.unused
import scala.collection.mutable
import scala.util.control.NonFatal

/**
* Information a pass must provide, basically its name
*/
trait PassInfo {
def name: String
}

/**
* Information that a Pass must produce, currently just any messages it generated.
* Passes should derive their own concrete PassOutput classes from this trait
*/
trait PassOutput {
def messages: Messages.Messages
}
Expand All @@ -31,6 +38,14 @@ object PassOutput {
def empty: PassOutput = new PassOutput { val messages: Messages.Messages = Messages.empty }
}

/**
* The input to a Pass in order to do its work. This consists of just the parsed model
* and the common options. Passes cannot extend this.
* @param root
* The result of the parsing run, consisting of the RootContainer from which all AST content can be reached
* @param commonOptions
* THe common options that should be used to run the pass
*/
case class PassInput(
root: RootContainer,
commonOptions: CommonOptions = CommonOptions.empty
Expand All @@ -39,6 +54,11 @@ object PassInput {
val empty: PassInput = PassInput(RootContainer.empty)
}

/**
* The output from running a set of Passes. This collects the PassOutput
* instances from each Pass run and provides utility messages for getting
* that information.
*/
case class PassesOutput() {

private val outputs: mutable.HashMap[String, PassOutput] = mutable.HashMap.empty
Expand All @@ -65,6 +85,18 @@ case class PassesOutput() {
outputs.toMap.filterNot(Pass.standardPassNames.contains(_))
}

/**
* The result of running a set of passes. This provides the input and ouputs
* of the run as well as any additional messages (likely from an exception)
* This provides convenience methods for accessing the various output content
*
* @param input
* The input provided to the run of the passes
* @param outputs
* The PassesOutput collected from teh run of the Passes
* @param additionalMessages
* Any additional messages, likely from an exception or other unusual circumstance.
*/
case class PassesResult(
input: PassInput = PassInput.empty, outputs: PassesOutput = PassesOutput(), additionalMessages: Messages = Messages
.empty
Expand All @@ -74,11 +106,11 @@ case class PassesResult(
lazy val messages: Messages = {
outputs.getAllMessages ++ additionalMessages
}
lazy val symbols: SymbolsOutput =
lazy val symbols: SymbolsOutput =
outputs.outputOf[SymbolsOutput](SymbolsPass.name).getOrElse(SymbolsOutput())
lazy val resolution: ResolutionOutput =
lazy val resolution: ResolutionOutput =
outputs.outputOf[ResolutionOutput](ResolutionPass.name).getOrElse(ResolutionOutput())
lazy val validation: ValidationOutput =
lazy val validation: ValidationOutput =
outputs.outputOf[ValidationOutput](ValidationPass.name).getOrElse(ValidationOutput())

def refMap: ReferenceMap = resolution.refMap
Expand All @@ -97,10 +129,18 @@ object PassesResult {
val empty: PassesResult = PassesResult()
}

/** Abstract Pass definition */
/**
* Abstract Pass definition.
* @param in
* The input to the pass. This provides the data over which the pass is executed
* @param out
* The output from previous runs of OTHER passes, which is a form of input to
* the pass, perhaps.
*/
abstract class Pass(@unused val in: PassInput, val out: PassesOutput) {

/** THe name of the pass for inclusion in messages it produces
/** THe name of the pass for inclusion in messages it produces. This must
* be implemented by the subclass
* @return
* A string value giving the name of this pass
*/
Expand All @@ -115,11 +155,29 @@ abstract class Pass(@unused val in: PassInput, val out: PassesOutput) {
require(out.hasPassOutput(passInfo.name), s"Required pass '${passInfo.name}' was not run prior to $name'")
}

/** The main implementation of the Pass. The AST is walked in a depth first manner calling this
* function for each definition it encounters.
*
* @param definition
* The definition to be processed
* @param parents
* The stack of definitions that are the parents of [[definition]]. This stack goes from immediate parent towards
* the root. The root is deepest in the stack.
*/
protected def process(
definition: Definition,
parents: mutable.Stack[Definition]
): Unit

/** A signal that the processing is complete and no more calls to [[process]] will be made. This also gives the
* Pass subclass a chance to do post-processing as some computations can only be done after collecting data from
* the entire AST
*
* @param root
* The [[in]].root field just as a convenience
* @return
* Unit
*/
def postProcess(root: RootContainer): Unit

/** Generate the output of this Pass. This will only be called after all the calls to process have completed.
Expand All @@ -144,20 +202,21 @@ abstract class Pass(@unused val in: PassInput, val out: PassesOutput) {
protected val messages: Messages.Accumulator = Messages.Accumulator(in.commonOptions)
}

/** A pass base class that allows the node processing to be done in a depth first hierarchical order by calling:
/** A Pass base class that allows the processing to be done based on containers, and calling these methods:
* - openContainer at the start of container's processing
* - processLeaf for any leaf node
* - closeContainer after all the container's contents have been processed This kind of Pass allows the processing to
* follow the AST hierarchy so that container nodes can run before all their content (openContainer) and also after
* all its content (closeContainer). This is necessary for passes that must maintain the hierarchical structure of
* the AST model in their processing
* - processLeaf for any leaf nodes within the container
* - closeContainer after all the container's contents have been processed
*
* This kind of Pass allows the processing to follow the AST hierarchy so that container nodes can run before all
* their content (openContainer) and also after all its content (closeContainer). This is necessary for passes that
* must maintain the hierarchical structure of the AST model in their processing.
*
* @param input
* The PassInput to process
*/
abstract class HierarchyPass(input: PassInput, outputs: PassesOutput) extends Pass(input, outputs) {

// not required in this kind of pass, final override it
// not required in this kind of pass, final override it as a result
override final def process(definition: AST.Definition, parents: mutable.Stack[AST.Definition]): Unit = ()

// Instead traverse will use these three methods:
Expand All @@ -167,6 +226,7 @@ abstract class HierarchyPass(input: PassInput, outputs: PassesOutput) extends Pa

protected def closeContainer(definition: Definition, parents: Seq[Definition]): Unit

// Redefine traverse to make the three calls
override protected def traverse(definition: Definition, parents: mutable.Stack[Definition]): Unit = {
definition match {
case leaf: LeafDefinition =>
Expand All @@ -183,28 +243,46 @@ abstract class HierarchyPass(input: PassInput, outputs: PassesOutput) extends Pa
}
}

/** An abstract PassOutput for use with passes that derive from CollectingPass.
* This just provides a standard field name for the data that is collected,
* being `collected`.
*
* @param messages
* The required messages field from the PassOutput trait
* @param collected
* The data that was collected from the CollectingPass's run
* @tparam T
* The element type of the collected data
*/
abstract class CollectingPassOutput[T](
messages: Messages = Messages.empty,
collected: Seq[T] = Seq.empty[T]
) extends PassOutput

/** A pass base class that allows the node processing to be done in a depth first hierarchical order by calling:
* - openContainer at the start of container's processing
* - processLeaf for any leaf node
* - closeContainer after all the container's contents have been processed This kind of Pass allows the processing to
* follow the AST hierarchy so that container nodes can run before all their content (openContainer) and also after
* all its content (closeContainer). This is necessary for passes that must maintain the hierarchical structure of
* the AST model in their processing
/** A Pass subclass that processes the AST exactly the same as the depth first search that
* the Pass class uses. The only difference is that
*
* @param input
* The PassInput to process
* @param outputs
* The outputs from previous pass runs in case they are needed as input to this CollectingPass
* @tparam F
* The element type of the collected values
*/
abstract class CollectingPass[F](input: PassInput, outputs: PassesOutput) extends Pass(input, outputs) {

// not required in this kind of pass, final override it
override final def process(definition: AST.Definition, parents: mutable.Stack[AST.Definition]): Unit = ()

// Instead traverse will use this fold method
/** The processing method called at each node, similar to [[Pass.process]] but modified to return an
*
* @param definition
* The definition from which an [[F]] value is collected.
* @param parents
* The parents of the definition
* @return
* One of the collected values, an [[F]]
*/
protected def collect(definition: Definition, parents: mutable.Stack[AST.Definition]): F

@SuppressWarnings(Array("org.wartremover.warts.Var"))
Expand All @@ -222,15 +300,24 @@ abstract class CollectingPass[F](input: PassInput, outputs: PassesOutput) extend

object Pass {

/** A function type that creates a Pass instance */
type PassCreator = (PassInput, PassesOutput) => Pass

/** A sequence of PassCreator. This is used to run a set of passes */
type PassesCreator = Seq[PassCreator]

/** A PassesCreator of the standard passes that should be run on every
* AST pass. These generate the symbol table, resolve path references, and
* validate the input. Only after these three have passed successfull should
* the model be considered processable by other passes
*/
val standardPasses: PassesCreator = Seq(
{ (input: PassInput, outputs: PassesOutput) => SymbolsPass(input, outputs) },
{ (input: PassInput, outputs: PassesOutput) => ResolutionPass(input, outputs) },
{ (input: PassInput, outputs: PassesOutput) => ValidationPass(input, outputs) }
)

/** The name of the standard passes */
val standardPassNames: Seq[String] = Seq(SymbolsPass.name, ResolutionPass.name, ValidationPass.name)

/** Run a set of passes against some input to obtain a result
Expand Down Expand Up @@ -307,6 +394,7 @@ object Pass {
val parents: mutable.Stack[Definition] = mutable.Stack.empty
pass.traverse(in.root, parents)
pass.postProcess(in.root)
pass.close()
val output = pass.result
outs.outputIs(pass.name, output)
output
Expand Down

0 comments on commit 5d302ad

Please sign in to comment.