From 5dfa50027f8f6c756d88ab5c5372fde02570746b Mon Sep 17 00:00:00 2001 From: reidspencer Date: Wed, 3 Apr 2024 10:32:12 -0400 Subject: [PATCH 1/5] Initial structural changes for Codify Pass * Add Codify module * Add skeleton code for pass and command * Factor Pass base classes to separate compilation units * Fix a comment in AST * Add dependency for codify module on OpenAI Scala Client --- build.sbt | 13 ++ .../com.ossuminc.riddl.commands.CommandPlugin | 1 + .../ossuminc/riddl/codify/CodifyCommand.scala | 96 ++++++++++++ .../ossuminc/riddl/codify/CodifyPass.scala | 67 +++++++++ .../com/ossuminc/riddl/language/AST.scala | 2 +- .../riddl/passes/CollectingPass.scala | 66 ++++++++ .../ossuminc/riddl/passes/HierarchyPass.scala | 91 +++++++++++ .../com/ossuminc/riddl/passes/Pass.scala | 142 ------------------ ...nslatePass.scala => TranslatingPass.scala} | 32 ++-- project/Dependencies.scala | 2 + 10 files changed, 357 insertions(+), 155 deletions(-) create mode 100644 codify/src/main/resources/META-INF/services/com.ossuminc.riddl.commands.CommandPlugin create mode 100644 codify/src/main/scala/com/ossuminc/riddl/codify/CodifyCommand.scala create mode 100644 codify/src/main/scala/com/ossuminc/riddl/codify/CodifyPass.scala create mode 100644 passes/src/main/scala/com/ossuminc/riddl/passes/CollectingPass.scala create mode 100644 passes/src/main/scala/com/ossuminc/riddl/passes/HierarchyPass.scala rename passes/src/main/scala/com/ossuminc/riddl/passes/{translate/TranslatePass.scala => TranslatingPass.scala} (50%) diff --git a/build.sbt b/build.sbt index 5a784ee8a..5072575cd 100644 --- a/build.sbt +++ b/build.sbt @@ -19,6 +19,7 @@ lazy val riddl: Project = Root("riddl", startYr = startYear) testkit, prettify, stats, + codify, riddlc, docsite, plugin @@ -107,6 +108,17 @@ lazy val prettify = Module("prettify", "riddl-prettify") ) .dependsOn(commands, testkit % "test->compile", utils) +val Codify = config("codify") +lazy val codify = Module("codify", "riddl-codify") + .configure(With.typical) + .configure(With.coverage(65)) + .configure(With.publishing) + .settings( + description := "Implementation for the RIDDL codify command, that replaces statements with code", + libraryDependencies ++= Dep.testing :+ Dep.openAISC + ) + .dependsOn(commands, testkit % "test->compile", utils) + lazy val docProjects = List( (utils, Utils), (language, Language), @@ -114,6 +126,7 @@ lazy val docProjects = List( (commands, Commands), (testkit, TestKit), (prettify, Prettify), + (codify, Codify), (stats, Stats), (riddlc, Riddlc) ) diff --git a/codify/src/main/resources/META-INF/services/com.ossuminc.riddl.commands.CommandPlugin b/codify/src/main/resources/META-INF/services/com.ossuminc.riddl.commands.CommandPlugin new file mode 100644 index 000000000..a6411e662 --- /dev/null +++ b/codify/src/main/resources/META-INF/services/com.ossuminc.riddl.commands.CommandPlugin @@ -0,0 +1 @@ +com.ossuminc.riddl.stats.CodifyCommand diff --git a/codify/src/main/scala/com/ossuminc/riddl/codify/CodifyCommand.scala b/codify/src/main/scala/com/ossuminc/riddl/codify/CodifyCommand.scala new file mode 100644 index 000000000..de9ac0939 --- /dev/null +++ b/codify/src/main/scala/com/ossuminc/riddl/codify/CodifyCommand.scala @@ -0,0 +1,96 @@ +/* + * Copyright 2019 Ossum, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.ossuminc.riddl.codify + +import com.ossuminc.riddl.commands.{CommandOptions, InputFileCommandPlugin, PassCommand, PassCommandOptions} +import com.ossuminc.riddl.language.Messages.Messages +import com.ossuminc.riddl.language.CommonOptions +import com.ossuminc.riddl.passes.Pass.standardPasses +import com.ossuminc.riddl.passes.{Pass, PassInput, PassesCreator, PassesOutput, PassesResult, Riddl} +import com.ossuminc.riddl.utils.Logger +import scopt.OParser +import pureconfig.{ConfigCursor, ConfigReader} + +import java.io.File +import java.nio.file.Path +import java.net.URI + +object CodifyCommand { + val cmdName: String = "codify" + case class Options( + inputFile: Option[Path] = None, + endPoint: Option[URI] = None, + apiKey: Option[String] = None, + orgId: Option[String] = None + ) extends PassCommandOptions + with CommandOptions { + def command: String = cmdName + def outputDir: Option[Path] = None + } +} + +/** Codify Command */ +class CodifyCommand extends PassCommand[CodifyCommand.Options](CodifyCommand.cmdName) { + import CodifyCommand.Options + + // Members declared in com.ossuminc.riddl.commands.CommandPlugin + def getConfigReader: ConfigReader[Options] = { (cur: ConfigCursor) => + for + topCur <- cur.asObjectCursor + topRes <- topCur.atKey(pluginName) + objCur <- topRes.asObjectCursor + inFileRes <- objCur.atKey("input-file").map(_.asString) + inFile <- inFileRes + endPointRes <- objCur.atKey("end-point").map(_.asString) + endPoint <- endPointRes + apiKeyRes <- objCur.atKey("api-key").map(_.asString) + apiKey <- apiKeyRes + orgIdRes <- objCur.atKey("org-id").map(_.asString) + orgId <- orgIdRes + yield { + Options(Some(Path.of(inFile)), Some(URI.create(endPoint)), Some(apiKey), Some(orgId) ) + } + } + + def getOptions: (OParser[Unit, Options], CodifyCommand.Options) = { + import builder.* + cmd(CodifyCommand.cmdName) + .children( + arg[File]("input-file") + .action { (file, opt) => + opt.copy(inputFile = Some(file.toPath)) + } + .text("The main input file on which to generate statistics.") + ) + .text("Loads a configuration file and executes the command in it") -> + CodifyCommand.Options() + } + + // Members declared in com.ossuminc.riddl.commands.PassCommand + def overrideOptions(options: Options, newOutputDir: Path): Options = { + options // we don't support overriding the output dir + } + + override def replaceInputFile( + opts: Options, + inputFile: Path + ): Options = { opts.copy(inputFile = Some(inputFile)) } + + override def loadOptionsFrom( + configFile: Path, + commonOptions: CommonOptions + ): Either[Messages, Options] = { + super.loadOptionsFrom(configFile, commonOptions).map { options => + resolveInputFileToConfigFile(options, commonOptions, configFile) + } + } + + override def getPasses(log: Logger, commonOptions: CommonOptions, options: Options): PassesCreator = { + standardPasses :+ CodifyPass.creator + } + +} diff --git a/codify/src/main/scala/com/ossuminc/riddl/codify/CodifyPass.scala b/codify/src/main/scala/com/ossuminc/riddl/codify/CodifyPass.scala new file mode 100644 index 000000000..fa9dea1d6 --- /dev/null +++ b/codify/src/main/scala/com/ossuminc/riddl/codify/CodifyPass.scala @@ -0,0 +1,67 @@ +/* + * Copyright 2023 Ossum, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package com.ossuminc.riddl.codify + +import com.ossuminc.riddl.language.{AST, Messages} +import com.ossuminc.riddl.language.AST.* +import com.ossuminc.riddl.language.Messages.Messages +import com.ossuminc.riddl.passes.{ + PassCreator, + PassInfo, + PassInput, + PassesOutput, + TranslatingPass, + TranslatingPassOutput +} +import com.ossuminc.riddl.passes.resolve.ResolutionPass +import com.ossuminc.riddl.passes.symbols.SymbolsPass +import com.ossuminc.riddl.passes.symbols.Symbols.ParentStack +import com.ossuminc.riddl.passes.validate.ValidationPass + +object CodifyPass extends PassInfo { + val name: String = "codify" + val creator: PassCreator = { (in: PassInput, out: PassesOutput) => CodifyPass(in, out) } +} + +/** The output from running the CodifyPass */ +case class CodifyOutput( + messages: Messages = Messages.empty, + newASTRoot: Root +) extends TranslatingPassOutput(messages, newASTRoot) + +/** A pass that translates RIDDL definitions into RIDDL code statements */ +case class CodifyPass(input: PassInput, outputs: PassesOutput) extends TranslatingPass(input, outputs) { + + def name: String = CodifyPass.name + + requires(SymbolsPass) + requires(ResolutionPass) + requires(ValidationPass) + + protected def process( + definition: RiddlValue, + parents: ParentStack + ): Unit = () + + private var newRootAST: Root = ??? + + def postProcess(root: Root): Unit = { + // TODO: Postprocess root to transform it into newRootAST + newRootAST = root + } + + /** Generate the output of this Pass. This will only be called after all the calls to process have completed. + * + * @return + * an instance of the output type + */ + override def result: CodifyOutput = { + CodifyOutput( + Messages.empty, + newRootAST + ) + } +} diff --git a/language/src/main/scala/com/ossuminc/riddl/language/AST.scala b/language/src/main/scala/com/ossuminc/riddl/language/AST.scala index 45bb185e4..e942a92df 100644 --- a/language/src/main/scala/com/ossuminc/riddl/language/AST.scala +++ b/language/src/main/scala/com/ossuminc/riddl/language/AST.scala @@ -795,7 +795,7 @@ object AST { def format: String = s"user ${id.format} is ${is_a.format}" } - ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// USER + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// TERM /** A term definition for the glossary */ case class Term( diff --git a/passes/src/main/scala/com/ossuminc/riddl/passes/CollectingPass.scala b/passes/src/main/scala/com/ossuminc/riddl/passes/CollectingPass.scala new file mode 100644 index 000000000..ba5a4307b --- /dev/null +++ b/passes/src/main/scala/com/ossuminc/riddl/passes/CollectingPass.scala @@ -0,0 +1,66 @@ +package com.ossuminc.riddl.passes + +import com.ossuminc.riddl.language.AST.RiddlValue +import com.ossuminc.riddl.language.Messages +import com.ossuminc.riddl.language.Messages.Messages +import com.ossuminc.riddl.passes.symbols.Symbols.ParentStack + +import scala.collection.mutable + +/** 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 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) { + + /** The method usually called for each definition that is to be processed but our implementation of traverse instead + * calls collect so a value can be returned. This implementation is final because it is meant to be ignored. + * + * @param definition + * The definition to be processed + * @param parents + * The stack of definitions that are the parents of [[com.ossuminc.riddl.language.AST.Definition]]. This stack goes + * from immediate parent towards the root. The root is deepest in the stack. + */ + override final def process(definition: RiddlValue, parents: ParentStack): Unit = { + val collected: Seq[F] = collect(definition, parents) + collectedValues ++= collected + } + + protected val collectedValues: mutable.ArrayBuffer[F] = mutable.ArrayBuffer.empty[F] + + /** The processing method called at each node, similar to [[com.ossuminc.riddl.passes.Pass.process]] but modified to + * return an sequence of the collectable, [[F]]. + * + * @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: RiddlValue, parents: ParentStack): Seq[F] + + override def result: CollectingPassOutput[F] +} \ No newline at end of file diff --git a/passes/src/main/scala/com/ossuminc/riddl/passes/HierarchyPass.scala b/passes/src/main/scala/com/ossuminc/riddl/passes/HierarchyPass.scala new file mode 100644 index 000000000..f7a9b4c37 --- /dev/null +++ b/passes/src/main/scala/com/ossuminc/riddl/passes/HierarchyPass.scala @@ -0,0 +1,91 @@ +package com.ossuminc.riddl.passes + +import com.ossuminc.riddl.language.AST.{Definition, Include, LeafDefinition, RiddlValue} +import com.ossuminc.riddl.passes.symbols.Symbols.{ParentStack, Parents} + +/** 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 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 as a result + * + * @param definition + * The definition to be processed + * @param parents + * The stack of definitions that are the parents of [[com.ossuminc.riddl.language.AST.Definition]]. This stack goes + * from immediate parent towards the root. The root is deepest in the stack. + */ + override final def process(definition: RiddlValue, parents: ParentStack): Unit = () + + /** Called by traverse when a new container is started Subclasses must implement this method. + * @param definition + * The definition that was opened + * @param parents + * The parents of the definition opened + */ + protected def openContainer(definition: Definition, parents: Parents): Unit + + /** Called by traverse when a leaf node is encountered Subclasses must implement this method + * @param definition + * The leaf definition that was found + * @param parents + * THe parents of the leaf node + */ + protected def processLeaf(definition: LeafDefinition, parents: Parents): Unit + + /** Process a non-definition, non-include, value + * + * @param value + * The value to be processed + * @param parents + * The parent definitions of value + */ + protected def processValue(value: RiddlValue, parents: Parents): Unit = () + + /** Called by traverse after all leaf nodes of an opened node have been processed and the opened node is now being + * closed. Subclasses must implement this method. + * @param definition + * The opened node that now needs to be closed + * @param parents + * THe parents of the node to be closed; should be the same as when it was opened + */ + protected def closeContainer(definition: Definition, parents: Parents): Unit + + /** Redefine traverse to make the three calls + * + * @param definition + * The RiddlValue being considered + * @param parents + * The definition parents of the value + */ + override protected def traverse(definition: RiddlValue, parents: ParentStack): Unit = { + definition match { + case leaf: LeafDefinition => + processLeaf(leaf, parents.toSeq) + case container: Definition => + openContainer(container, parents.toSeq) + parents.push(container) + container.contents.foreach { + case leaf: LeafDefinition => processLeaf(leaf, parents.toSeq) + case definition: Definition => traverse(definition, parents) + case include: Include[?] => traverse(include, parents) + case value: RiddlValue => processValue(value, parents.toSeq) + } + parents.pop() + closeContainer(container, parents.toSeq) + case include: Include[?] => + include.contents.foreach { item => traverse(item, parents) } + case value: RiddlValue => processValue(value, parents.toSeq) + } + } +} diff --git a/passes/src/main/scala/com/ossuminc/riddl/passes/Pass.scala b/passes/src/main/scala/com/ossuminc/riddl/passes/Pass.scala index fdcd0e93d..23a6e4a9d 100644 --- a/passes/src/main/scala/com/ossuminc/riddl/passes/Pass.scala +++ b/passes/src/main/scala/com/ossuminc/riddl/passes/Pass.scala @@ -227,150 +227,8 @@ 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 processing to be done based on containers, and calling these methods: - * - openContainer at the start of container's 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 as a result - * - * @param definition - * The definition to be processed - * @param parents - * The stack of definitions that are the parents of [[com.ossuminc.riddl.language.AST.Definition]]. This stack goes - * from immediate parent towards the root. The root is deepest in the stack. - */ - override final def process(definition: RiddlValue, parents: ParentStack): Unit = () - - /** Called by traverse when a new container is started Subclasses must implement this method. - * @param definition - * The definition that was opened - * @param parents - * The parents of the definition opened - */ - protected def openContainer(definition: Definition, parents: Parents): Unit - - /** Called by traverse when a leaf node is encountered Subclasses must implement this method - * @param definition - * The leaf definition that was found - * @param parents - * THe parents of the leaf node - */ - protected def processLeaf(definition: LeafDefinition, parents: Parents): Unit - /** Process a non-definition, non-include, value - * - * @param value - * The value to be processed - * @param parents - * The parent definitions of value - */ - protected def processValue(value: RiddlValue, parents: Parents): Unit = () - /** Called by traverse after all leaf nodes of an opened node have been processed and the opened node is now being - * closed. Subclasses must implement this method. - * @param definition - * The opened node that now needs to be closed - * @param parents - * THe parents of the node to be closed; should be the same as when it was opened - */ - protected def closeContainer(definition: Definition, parents: Parents): Unit - - /** Redefine traverse to make the three calls - * - * @param definition - * The RiddlValue being considered - * @param parents - * The definition parents of the value - */ - override protected def traverse(definition: RiddlValue, parents: ParentStack): Unit = { - definition match { - case leaf: LeafDefinition => - processLeaf(leaf, parents.toSeq) - case container: Definition => - openContainer(container, parents.toSeq) - parents.push(container) - container.contents.foreach { - case leaf: LeafDefinition => processLeaf(leaf, parents.toSeq) - case definition: Definition => traverse(definition, parents) - case include: Include[?] => traverse(include, parents) - case value: RiddlValue => processValue(value, parents.toSeq) - } - parents.pop() - closeContainer(container, parents.toSeq) - case include: Include[?] => - include.contents.foreach { item => traverse(item, parents) } - case value: RiddlValue => processValue(value, parents.toSeq) - } - } -} - -/** 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 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) { - - /** The method usually called for each definition that is to be processed but our implementation of traverse instead - * calls collect so a value can be returned. This implementation is final because it is meant to be ignored. - * - * @param definition - * The definition to be processed - * @param parents - * The stack of definitions that are the parents of [[com.ossuminc.riddl.language.AST.Definition]]. This stack goes - * from immediate parent towards the root. The root is deepest in the stack. - */ - override final def process(definition: RiddlValue, parents: ParentStack): Unit = { - val collected: Seq[F] = collect(definition, parents) - collectedValues ++= collected - } - - protected val collectedValues: mutable.ArrayBuffer[F] = mutable.ArrayBuffer.empty[F] - - /** The processing method called at each node, similar to [[com.ossuminc.riddl.passes.Pass.process]] but modified to - * return an sequence of the collectable, [[F]]. - * - * @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: RiddlValue, parents: ParentStack): Seq[F] - - override def result: CollectingPassOutput[F] -} object Pass { diff --git a/passes/src/main/scala/com/ossuminc/riddl/passes/translate/TranslatePass.scala b/passes/src/main/scala/com/ossuminc/riddl/passes/TranslatingPass.scala similarity index 50% rename from passes/src/main/scala/com/ossuminc/riddl/passes/translate/TranslatePass.scala rename to passes/src/main/scala/com/ossuminc/riddl/passes/TranslatingPass.scala index b72ce7314..4a653d7fd 100644 --- a/passes/src/main/scala/com/ossuminc/riddl/passes/translate/TranslatePass.scala +++ b/passes/src/main/scala/com/ossuminc/riddl/passes/TranslatingPass.scala @@ -4,36 +4,44 @@ * SPDX-License-Identifier: Apache-2.0 */ -package com.ossuminc.riddl.passes.translate +package com.ossuminc.riddl.passes +import com.ossuminc.riddl.language.AST.Root +import com.ossuminc.riddl.language.{CommonOptions, Messages} import com.ossuminc.riddl.language.Messages.Messages import com.ossuminc.riddl.language.parsing.RiddlParserInput import com.ossuminc.riddl.passes.{Pass, PassCreator, PassInput, PassesCreator, PassesOutput, PassesResult, Riddl} -import com.ossuminc.riddl.language.CommonOptions import com.ossuminc.riddl.utils.Logger import java.nio.file.Path -trait TranslatingOptions { - def inputFile: Option[Path] - def outputDir: Option[Path] - def projectName: Option[String] -} - +/** An abstract PassOutput for use with passes that derive from TranslatingPass. This just provides a standard + * field name for the data that is collected. + * + * @param messages + * The required messages field from the PassOutput trait + * @param newASTRoot + * The new AST Root after the AST was translated + * @tparam T + * The element type of the collected data + */ +abstract class TranslatingPassOutput( + messages: Messages = Messages.empty, + newASTRoot: Root +) extends PassOutput /** Base class of all Translators - * */ -abstract class TranslationPass(input: PassInput, outputs: PassesOutput) extends Pass(input, outputs) { +abstract class TranslatingPass(input: PassInput, outputs: PassesOutput) extends Pass(input, outputs) { def passes: PassesCreator = Seq.empty[PassCreator] final def parseValidateTranslate( input: RiddlParserInput, log: Logger, - commonOptions: CommonOptions, + commonOptions: CommonOptions ): Either[Messages, PassesResult] = { val allPasses = Pass.standardPasses ++ passes - Riddl.parseAndValidate(input, commonOptions, shouldFailOnError=true, allPasses, log ) + Riddl.parseAndValidate(input, commonOptions, shouldFailOnError = true, allPasses, log) } } diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 47dcb2523..0523d391b 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -10,6 +10,7 @@ object V { val fastparse = "3.0.2" val jgit = "6.5.0" val lang3 = "3.14.0" + val openAISC = "1.0.0.RC.1" val pureconfig = "0.17.6" val riddl_hugo = "0.2.0-2-95f3c45a" val scalacheck = "1.17.0" @@ -24,6 +25,7 @@ object Dep { val fastparse = "com.lihaoyi" %% "fastparse" % V.fastparse val jgit = "org.eclipse.jgit" % "org.eclipse.jgit" % V.jgit val lang3 = "org.apache.commons" % "commons-lang3" % V.lang3 + val openAISC = "io.cequence" %% "openai-scala-client" % V.openAISC val pureconfig = "com.github.pureconfig" %% "pureconfig-core" % V.pureconfig val riddl_hugo = "com.ossuminc" %% "riddl-hugo" % V.riddl_hugo val scalactic = "org.scalactic" %% "scalactic" % V.scalatest From 29e9193b41362ad434426cb94e952569a67b9442 Mon Sep 17 00:00:00 2001 From: reidspencer Date: Wed, 3 Apr 2024 12:55:43 -0400 Subject: [PATCH 2/5] Start work on testing the codify pass --- codify/src/test/input/VendingMachine.riddl | 46 +++++++++++++++++++ .../riddl/codify/CodifyPassTest.scala | 31 +++++++++++++ testkit/src/test/input/dokn.riddl | 8 ++-- 3 files changed, 81 insertions(+), 4 deletions(-) create mode 100644 codify/src/test/input/VendingMachine.riddl create mode 100644 codify/src/test/scala/com/ossuminc/riddl/codify/CodifyPassTest.scala diff --git a/codify/src/test/input/VendingMachine.riddl b/codify/src/test/input/VendingMachine.riddl new file mode 100644 index 000000000..8a3a40385 --- /dev/null +++ b/codify/src/test/input/VendingMachine.riddl @@ -0,0 +1,46 @@ +domain VendingMachine { + context Transactions { + entity Machine { + command InsertCoin(amount: Money) + command SelectItem(item: Item) + + state Idle { + handler CustomerInteraction { + on InsertCoin(amount: Money) { + "Validate coin" + "Update machine balance" + morph entity Machine to AwaitingSelection + } + } + } + + state AwaitingSelection { + handler CustomerInteraction { + on SelectItem(item: Item) { + "Check if item is available" + "Check if inserted money is sufficient" + morph entity Machine to DispensingItem + } + } + } + + state DispensingItem { + handler CustomerInteraction { + on DispenseItem { + // Activate mechanism to dispense the selected item + morph entity Machine to DispensingChange + } + } + } + + state DispensingChange { + handler CustomerInteraction { + on DispenseChange { + // Calculate and return change + morph entity Machine to Idle + } + } + } + } + } +} diff --git a/codify/src/test/scala/com/ossuminc/riddl/codify/CodifyPassTest.scala b/codify/src/test/scala/com/ossuminc/riddl/codify/CodifyPassTest.scala new file mode 100644 index 000000000..98ae2e290 --- /dev/null +++ b/codify/src/test/scala/com/ossuminc/riddl/codify/CodifyPassTest.scala @@ -0,0 +1,31 @@ +package com.ossuminc.riddl.codify + +import com.ossuminc.riddl.language.parsing.RiddlParserInput +import com.ossuminc.riddl.language.Messages.* +import com.ossuminc.riddl.testkit.ValidatingTest + +import java.nio.file.Path + +class CodifyPassTest extends ValidatingTest { + + def input(path: String): RiddlParserInput = { + RiddlParserInput(Path.of("codify/src/test/input/", path)) + } + + "CodifyPass" must { + + "run" in { + val rpi = input("VendingMachine.riddl") + parseTopLevelDomains(rpi) match { + case Left(messages) => + val errors = messages.justErrors + if errors.nonEmpty then fail(errors.format) + info(messages.justWarnings.format) + succeed + case Right(root) => + succeed + } + } + } + +} diff --git a/testkit/src/test/input/dokn.riddl b/testkit/src/test/input/dokn.riddl index bb3110f44..922aaad7d 100644 --- a/testkit/src/test/input/dokn.riddl +++ b/testkit/src/test/input/dokn.riddl @@ -1,5 +1,5 @@ domain dokn is { - type Address = { + type Address is { line1: String, line2: String, line3: String, @@ -15,9 +15,9 @@ domain dokn is { "^[a-zA-Z0-9_+&*-] + (?:\\.[a-zA-Z0-9_+&*-] + )*@(?:[a-zA-Z0-9-]+\\.) + [a-zA-Z]{2,7}" ) type LoadingMethod = any of { dock, tailgate, forklift, none } - type CompanyId = Id(dokn.Company.Company) - type DriverId = MobileNumber - type NoteId = Id(dokn.Note.Note) + type CompanyId is Id(dokn.Company.Company) + type DriverId is MobileNumber + type NoteId is Id(dokn.Note.Note) type MediaId is Id(dokn.Media.Media) type LocationId is Id(dokn.Location.Location) type NoteList is reference to dokn.Note.Note* From 9093718ed61e13bc29a6eca3ccdbb7702a8b23d1 Mon Sep 17 00:00:00 2001 From: reidspencer Date: Sun, 14 Apr 2024 10:17:00 -0400 Subject: [PATCH 3/5] Use scala 3.4.1 in the codify module --- build.sbt | 1 + 1 file changed, 1 insertion(+) diff --git a/build.sbt b/build.sbt index b724baece..c14c9657e 100644 --- a/build.sbt +++ b/build.sbt @@ -124,6 +124,7 @@ lazy val codify = Module("codify", "riddl-codify") .configure(With.coverage(65)) .configure(With.publishing) .settings( + scalaVersion := "3.4.1", description := "Implementation for the RIDDL codify command, that replaces statements with code", libraryDependencies ++= Dep.testing :+ Dep.openAISC ) From 5977696060632ec68ca310bedb14699cc970942c Mon Sep 17 00:00:00 2001 From: reidspencer Date: Mon, 15 Apr 2024 10:03:23 -0400 Subject: [PATCH 4/5] Fix syntax of test example code --- codify/src/test/input/VendingMachine.riddl | 33 ++++++++++++++-------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/codify/src/test/input/VendingMachine.riddl b/codify/src/test/input/VendingMachine.riddl index 8a3a40385..3a6af11a6 100644 --- a/codify/src/test/input/VendingMachine.riddl +++ b/codify/src/test/input/VendingMachine.riddl @@ -1,43 +1,52 @@ domain VendingMachine { context Transactions { entity Machine { + record Money(amount: Real) + record Item(which: Integer) command InsertCoin(amount: Money) command SelectItem(item: Item) + command DispenseItem(item: Item) + command DIspenseChange(change: Money) - state Idle { + record IdleState is { ??? } + record AwaitingSelectionState is { ??? } + record DispensingItemState is { ??? } + record DispensingChangeState is { ??? } + + state Idle of IdleState { handler CustomerInteraction { - on InsertCoin(amount: Money) { + on command InsertCoin { "Validate coin" "Update machine balance" - morph entity Machine to AwaitingSelection + morph entity Machine to state AwaitingSelection with record AwaitingSelectionState } } } - state AwaitingSelection { + state AwaitingSelection of AwaitingSelectionState { handler CustomerInteraction { - on SelectItem(item: Item) { + on command SelectItem { "Check if item is available" "Check if inserted money is sufficient" - morph entity Machine to DispensingItem + morph entity Machine to state DispensingItem with record DispensingItemState } } } - state DispensingItem { + state DispensingItem of DispensingItemState { handler CustomerInteraction { - on DispenseItem { + on command DispenseItem { // Activate mechanism to dispense the selected item - morph entity Machine to DispensingChange + morph entity Machine to state DispensingChange with record DispensingChangeState } } } - state DispensingChange { + state DispensingChange of DispensingChangeState { handler CustomerInteraction { - on DispenseChange { + on command DispenseChange { // Calculate and return change - morph entity Machine to Idle + morph entity Machine to state Idle with record IdleState } } } From 67881050b9d3231263b643a47cf87ad593337299 Mon Sep 17 00:00:00 2001 From: reidspencer Date: Mon, 15 Apr 2024 13:15:13 -0400 Subject: [PATCH 5/5] Start to expand test case --- build.sbt | 2 +- .../ossuminc/riddl/codify/CodifyCommand.scala | 6 +++--- .../ossuminc/riddl/codify/CodifyPass.scala | 21 +++++++++++++++++-- .../riddl/codify/CodifyPassTest.scala | 15 ++++++++++--- 4 files changed, 35 insertions(+), 9 deletions(-) diff --git a/build.sbt b/build.sbt index c14c9657e..181a8405e 100644 --- a/build.sbt +++ b/build.sbt @@ -128,7 +128,7 @@ lazy val codify = Module("codify", "riddl-codify") description := "Implementation for the RIDDL codify command, that replaces statements with code", libraryDependencies ++= Dep.testing :+ Dep.openAISC ) - .dependsOn(commands, testkit % "test->compile", utils) + .dependsOn(language, passes, commands, testkit % "test->compile", utils) lazy val docProjects = List( (utils, Utils), diff --git a/codify/src/main/scala/com/ossuminc/riddl/codify/CodifyCommand.scala b/codify/src/main/scala/com/ossuminc/riddl/codify/CodifyCommand.scala index de9ac0939..32f496101 100644 --- a/codify/src/main/scala/com/ossuminc/riddl/codify/CodifyCommand.scala +++ b/codify/src/main/scala/com/ossuminc/riddl/codify/CodifyCommand.scala @@ -6,11 +6,11 @@ package com.ossuminc.riddl.codify -import com.ossuminc.riddl.commands.{CommandOptions, InputFileCommandPlugin, PassCommand, PassCommandOptions} +import com.ossuminc.riddl.commands.{CommandOptions, PassCommand, PassCommandOptions} import com.ossuminc.riddl.language.Messages.Messages import com.ossuminc.riddl.language.CommonOptions import com.ossuminc.riddl.passes.Pass.standardPasses -import com.ossuminc.riddl.passes.{Pass, PassInput, PassesCreator, PassesOutput, PassesResult, Riddl} +import com.ossuminc.riddl.passes.{Pass, PassesCreator} import com.ossuminc.riddl.utils.Logger import scopt.OParser import pureconfig.{ConfigCursor, ConfigReader} @@ -52,7 +52,7 @@ class CodifyCommand extends PassCommand[CodifyCommand.Options](CodifyCommand.cmd orgIdRes <- objCur.atKey("org-id").map(_.asString) orgId <- orgIdRes yield { - Options(Some(Path.of(inFile)), Some(URI.create(endPoint)), Some(apiKey), Some(orgId) ) + Options(Some(Path.of(inFile)), Some(URI.create(endPoint)), Some(apiKey), Some(orgId)) } } diff --git a/codify/src/main/scala/com/ossuminc/riddl/codify/CodifyPass.scala b/codify/src/main/scala/com/ossuminc/riddl/codify/CodifyPass.scala index fa9dea1d6..89b2c3eab 100644 --- a/codify/src/main/scala/com/ossuminc/riddl/codify/CodifyPass.scala +++ b/codify/src/main/scala/com/ossuminc/riddl/codify/CodifyPass.scala @@ -7,7 +7,7 @@ package com.ossuminc.riddl.codify import com.ossuminc.riddl.language.{AST, Messages} import com.ossuminc.riddl.language.AST.* -import com.ossuminc.riddl.language.Messages.Messages +import com.ossuminc.riddl.language.Messages.{Messages, error} import com.ossuminc.riddl.passes.{ PassCreator, PassInfo, @@ -21,6 +21,8 @@ import com.ossuminc.riddl.passes.symbols.SymbolsPass import com.ossuminc.riddl.passes.symbols.Symbols.ParentStack import com.ossuminc.riddl.passes.validate.ValidationPass +import scala.collection.mutable + object CodifyPass extends PassInfo { val name: String = "codify" val creator: PassCreator = { (in: PassInput, out: PassesOutput) => CodifyPass(in, out) } @@ -41,13 +43,28 @@ case class CodifyPass(input: PassInput, outputs: PassesOutput) extends Translati requires(ResolutionPass) requires(ValidationPass) + val handlers: mutable.HashMap[Processor[?, ?], Handler] = mutable.HashMap.empty[Processor[?, ?], Handler] + protected def process( definition: RiddlValue, parents: ParentStack - ): Unit = () + ): Unit = { + definition match { + case s: State => + val proc = parents.find(_.isProcessor).get.asInstanceOf[Processor[?, ?]] + handlers.addAll(s.handlers.map(proc -> _)) + case h: Handler => + val proc = parents.find(_.isProcessor).get.asInstanceOf[Processor[?, ?]] + handlers.addOne(proc -> h) + case _ => () + } + } private var newRootAST: Root = ??? + def getStatements(h: Handler, p: Processor[?,?]): Seq[Statement] = { + + } def postProcess(root: Root): Unit = { // TODO: Postprocess root to transform it into newRootAST newRootAST = root diff --git a/codify/src/test/scala/com/ossuminc/riddl/codify/CodifyPassTest.scala b/codify/src/test/scala/com/ossuminc/riddl/codify/CodifyPassTest.scala index 98ae2e290..25878a45c 100644 --- a/codify/src/test/scala/com/ossuminc/riddl/codify/CodifyPassTest.scala +++ b/codify/src/test/scala/com/ossuminc/riddl/codify/CodifyPassTest.scala @@ -1,10 +1,11 @@ package com.ossuminc.riddl.codify +import com.ossuminc.riddl.language.CommonOptions import com.ossuminc.riddl.language.parsing.RiddlParserInput import com.ossuminc.riddl.language.Messages.* -import com.ossuminc.riddl.testkit.ValidatingTest +import com.ossuminc.riddl.testkit.ValidatingTest -import java.nio.file.Path +import java.nio.file.Path class CodifyPassTest extends ValidatingTest { @@ -14,6 +15,8 @@ class CodifyPassTest extends ValidatingTest { "CodifyPass" must { + val commonOptions = CommonOptions() + "run" in { val rpi = input("VendingMachine.riddl") parseTopLevelDomains(rpi) match { @@ -23,7 +26,13 @@ class CodifyPassTest extends ValidatingTest { info(messages.justWarnings.format) succeed case Right(root) => - succeed + runStandardPasses(root, commonOptions) match + case Left(messages) => + val errors = messages.justErrors + if errors.nonEmpty then fail(errors.format) + info(messages.justWarnings.format) + succeed + case Right(passesResult) => } } }