diff --git a/passes/src/main/scala/com/reactific/riddl/passes/Pass.scala b/passes/src/main/scala/com/reactific/riddl/passes/Pass.scala index 5f3095c13..41f4a9624 100644 --- a/passes/src/main/scala/com/reactific/riddl/passes/Pass.scala +++ b/passes/src/main/scala/com/reactific/riddl/passes/Pass.scala @@ -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 } @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 */ @@ -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. @@ -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: @@ -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 => @@ -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")) @@ -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 @@ -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