From 7ce792e3e0ba25b8bcf2efa090794fd9752c6342 Mon Sep 17 00:00:00 2001 From: Reid Spencer Date: Thu, 23 Nov 2023 21:43:49 -0500 Subject: [PATCH 1/3] 486 output parsing (#490) * WIP: Add comment parsing support, change whitespace handling Signed-off-by: reidspencer * WIP: Get language module working Signed-off-by: reidspencer * WIP: Commit to merge comment support branch Signed-off-by: reidspencer * WIP: Merge and implement end-of-definition comment Signed-off-by: reidspencer * All tests pass Summary of changes: * Issue #6 is now implemented. Single line comments are allowed at the start and end of the parse and after every kind of definition. The comments are captured in the AST * Keywords are not required to have non-alphanumeric characters after them and they introduce a cut Signed-off-by: reidspencer --------- Signed-off-by: reidspencer --- .../riddl/commands/CommandOptions.scala | 2 +- ...sTest.scala => ReadRiddlOptionsTest.scala} | 6 +- ...st.scala => RiddlOptionsReadingTest.scala} | 4 +- .../mermaid/EntityRelationshipDiagram.scala | 9 +- .../reactific/riddl/hugo/MarkdownWriter.scala | 8 +- .../com/reactific/riddl/language/AST.scala | 402 ++++++++------- .../language/parsing/AdaptorParser.scala | 15 +- .../language/parsing/ApplicationParser.scala | 60 ++- .../riddl/language/parsing/CommonParser.scala | 139 +++--- .../language/parsing/ContextParser.scala | 37 +- .../riddl/language/parsing/DomainParser.scala | 33 +- .../riddl/language/parsing/EntityParser.scala | 56 +-- .../riddl/language/parsing/EpicParser.scala | 102 ++-- .../language/parsing/FunctionParser.scala | 21 +- .../language/parsing/HandlerParser.scala | 31 +- .../riddl/language/parsing/Keywords.scala | 368 ++++++++++++++ .../parsing/NoWhiteSpaceParsers.scala | 11 +- .../language/parsing/ParsingContext.scala | 15 +- .../riddl/language/parsing/PredefTypes.scala | 112 +++++ .../language/parsing/ProjectorParser.scala | 21 +- .../riddl/language/parsing/Punctuation.scala | 37 ++ .../riddl/language/parsing/Readability.scala | 37 ++ .../language/parsing/ReferenceParser.scala | 31 +- .../language/parsing/RepositoryParser.scala | 18 +- .../riddl/language/parsing/RiddlOptions.scala | 88 ++++ .../riddl/language/parsing/SagaParser.scala | 23 +- .../language/parsing/StatementParser.scala | 10 +- .../language/parsing/StreamingParser.scala | 44 +- .../riddl/language/parsing/Terminals.scala | 319 ------------ .../language/parsing/TopLevelParser.scala | 51 +- .../riddl/language/parsing/TypeParser.scala | 221 ++++----- language/src/test/input/domains/rbbq.riddl | 32 +- language/src/test/input/everything.riddl | 31 +- .../reactific/riddl/language/ASTTest.scala | 34 +- .../riddl/language/HandlerTest.scala | 7 +- .../riddl/language/ParsingTest.scala | 15 +- .../riddl/language/TypeExpressionTest.scala | 167 ++++--- .../riddl/language/parsing/ParserTest.scala | 42 +- .../parsing/StreamingParserTest.scala | 10 +- .../language/parsing/TopLevelParserTest.scala | 14 +- .../language/parsing/TypeParserTest.scala | 44 +- .../com/reactific/riddl/passes/Folding.scala | 6 +- .../riddl/passes/resolve/ResolutionPass.scala | 8 +- .../passes/validate/BasicValidation.scala | 18 +- .../validate/DefinitionValidation.scala | 2 +- .../passes/validate/TypeValidation.scala | 2 +- .../passes/validate/ValidationPass.scala | 16 +- .../resolve/PathResolutionPassTest.scala | 1 + .../passes/validate/EntityValidatorTest.scala | 14 +- .../validate/FunctionValidatorTest.scala | 9 +- .../passes/validate/RegressionTests.scala | 21 +- .../passes/validate/ValidatingTest.scala | 2 +- .../passes/validate/ValidationTest.scala | 4 +- .../riddl/prettify/PrettifyPass.scala | 41 +- .../riddl/prettify/RiddlFileEmitter.scala | 7 +- .../riddl/prettify/PrettifyTest.scala | 26 +- project/plugins.sbt | 1 - ...st.scala => RiddlOptionsReadingTest.scala} | 2 +- .../riddl/RunRiddlcOnArbitraryTest.scala | 2 + .../com/reactific/riddl/stats/StatsPass.scala | 2 +- .../reactific/riddl/testkit/ParsingTest.scala | 14 +- .../testkit/RunCommandOnExamplesTest.scala | 2 +- .../input/check/everything/everything.check | 456 +++++++++--------- .../input/check/everything/everything.riddl | 22 - testkit/src/test/input/dokn.riddl | 16 +- testkit/src/test/input/domains/badstyle.riddl | 4 +- testkit/src/test/input/everything.riddl | 21 +- testkit/src/test/input/issues/486.riddl | 12 + testkit/src/test/input/rbbq.riddl | 19 +- .../riddl/testkit/ReportedIssuesTest.scala | 10 + 70 files changed, 1901 insertions(+), 1586 deletions(-) rename commands/src/test/scala/com/reactific/riddl/commands/{ReadOptionsTest.scala => ReadRiddlOptionsTest.scala} (94%) rename commands/src/test/scala/com/reactific/riddl/commands/{OptionsReadingTest.scala => RiddlOptionsReadingTest.scala} (93%) create mode 100644 language/src/main/scala/com/reactific/riddl/language/parsing/Keywords.scala create mode 100644 language/src/main/scala/com/reactific/riddl/language/parsing/PredefTypes.scala create mode 100644 language/src/main/scala/com/reactific/riddl/language/parsing/Punctuation.scala create mode 100644 language/src/main/scala/com/reactific/riddl/language/parsing/Readability.scala create mode 100644 language/src/main/scala/com/reactific/riddl/language/parsing/RiddlOptions.scala delete mode 100644 language/src/main/scala/com/reactific/riddl/language/parsing/Terminals.scala rename riddlc/src/test/scala/com/reactific/riddl/{OptionsReadingTest.scala => RiddlOptionsReadingTest.scala} (95%) create mode 100644 testkit/src/test/input/issues/486.riddl diff --git a/commands/src/main/scala/com/reactific/riddl/commands/CommandOptions.scala b/commands/src/main/scala/com/reactific/riddl/commands/CommandOptions.scala index 09a471b85..0cae4e97d 100644 --- a/commands/src/main/scala/com/reactific/riddl/commands/CommandOptions.scala +++ b/commands/src/main/scala/com/reactific/riddl/commands/CommandOptions.scala @@ -235,7 +235,7 @@ object CommandOptions { case Right(cmd) => cmd.parseOptions(args) match { case Some(options) => Right(options) - case None => Left(errors("Option parsing failed")) + case None => Left(errors("RiddlOption parsing failed")) } case Left(messages) => Left(messages) } diff --git a/commands/src/test/scala/com/reactific/riddl/commands/ReadOptionsTest.scala b/commands/src/test/scala/com/reactific/riddl/commands/ReadRiddlOptionsTest.scala similarity index 94% rename from commands/src/test/scala/com/reactific/riddl/commands/ReadOptionsTest.scala rename to commands/src/test/scala/com/reactific/riddl/commands/ReadRiddlOptionsTest.scala index 41020cf0d..75a5e109c 100644 --- a/commands/src/test/scala/com/reactific/riddl/commands/ReadOptionsTest.scala +++ b/commands/src/test/scala/com/reactific/riddl/commands/ReadRiddlOptionsTest.scala @@ -4,10 +4,10 @@ import org.scalatest.exceptions.TestFailedException import java.nio.file.Path -/** Unit Tests For ReadOptionsTest */ -class ReadOptionsTest extends CommandTestBase { +/** Unit Tests For ReadRiddlOptionsTest */ +class ReadRiddlOptionsTest extends CommandTestBase { - "Options" should { + "RiddlOptions" should { "read for dump" in { val expected = InputFileCommandPlugin .Options(Some(Path.of(s"$inputDir/dump.riddl")), "dump") diff --git a/commands/src/test/scala/com/reactific/riddl/commands/OptionsReadingTest.scala b/commands/src/test/scala/com/reactific/riddl/commands/RiddlOptionsReadingTest.scala similarity index 93% rename from commands/src/test/scala/com/reactific/riddl/commands/OptionsReadingTest.scala rename to commands/src/test/scala/com/reactific/riddl/commands/RiddlOptionsReadingTest.scala index 4554d2d34..935373d6b 100644 --- a/commands/src/test/scala/com/reactific/riddl/commands/OptionsReadingTest.scala +++ b/commands/src/test/scala/com/reactific/riddl/commands/RiddlOptionsReadingTest.scala @@ -12,9 +12,9 @@ import org.scalatest.wordspec.AnyWordSpec import java.nio.file.Path import scala.concurrent.duration.DurationInt -class OptionsReadingTest extends AnyWordSpec with Matchers { +class RiddlOptionsReadingTest extends AnyWordSpec with Matchers { - "Options Reading" must { + "RiddlOptions Reading" must { "load repeat options from a file" in { val optionFile = Path.of("commands/src/test/input/repeat-options.conf") CommandOptions.loadCommonOptions(optionFile) match { diff --git a/diagrams/src/main/scala/com/reactific/riddl/diagrams/mermaid/EntityRelationshipDiagram.scala b/diagrams/src/main/scala/com/reactific/riddl/diagrams/mermaid/EntityRelationshipDiagram.scala index e75761a44..be65f7754 100644 --- a/diagrams/src/main/scala/com/reactific/riddl/diagrams/mermaid/EntityRelationshipDiagram.scala +++ b/diagrams/src/main/scala/com/reactific/riddl/diagrams/mermaid/EntityRelationshipDiagram.scala @@ -4,7 +4,7 @@ import com.reactific.riddl.language.AST.* import com.reactific.riddl.passes.PassesResult import com.reactific.riddl.passes.resolve.ReferenceMap -class EntityRelationshipDiagram(refMap: ReferenceMap ) { +class EntityRelationshipDiagram(refMap: ReferenceMap) { private def makeTypeName( pid: PathIdentifier, @@ -24,7 +24,7 @@ class EntityRelationshipDiagram(refMap: ReferenceMap ) { parents: Seq[Definition] ): String = { val name = typeEx match { - case AliasedTypeExpression(_, pid) => makeTypeName(pid, parents) + case AliasedTypeExpression(_, _, pid) => makeTypeName(pid, parents) case EntityReferenceTypeExpression(_, pid) => makeTypeName(pid, parents) case UniqueId(_, pid) => makeTypeName(pid, parents) case Alternation(_, of) => @@ -38,7 +38,6 @@ class EntityRelationshipDiagram(refMap: ReferenceMap ) { name.replace(" ", "-") } - private def makeERDRelationship( from: String, to: Field, @@ -76,11 +75,11 @@ class EntityRelationshipDiagram(refMap: ReferenceMap ) { val comment = "\"" + f.brief.map(_.s).getOrElse("") + "\"" s" $typeName $fieldName $comment" } :+ "}" - + val relationships: Seq[String] = fields .map(makeERDRelationship(name, _, parents)) .filter(_.nonEmpty) - + Seq("erDiagram") ++ typ ++ relationships } } diff --git a/hugo/src/main/scala/com/reactific/riddl/hugo/MarkdownWriter.scala b/hugo/src/main/scala/com/reactific/riddl/hugo/MarkdownWriter.scala index a1b17230b..22911f303 100644 --- a/hugo/src/main/scala/com/reactific/riddl/hugo/MarkdownWriter.scala +++ b/hugo/src/main/scala/com/reactific/riddl/hugo/MarkdownWriter.scala @@ -13,7 +13,7 @@ import com.reactific.riddl.utils.TextFileWriter import java.nio.file.Path import scala.annotation.unused -import com.reactific.riddl.language.parsing.Terminals +import com.reactific.riddl.language.parsing.Keywords import com.reactific.riddl.passes.PassesResult import com.reactific.riddl.passes.resolve.{ReferenceMap, Usages} import com.reactific.riddl.passes.symbols.SymbolsOutput @@ -391,7 +391,7 @@ case class MarkdownWriter( // This substitutions domain contains context referenced - private val keywords: String = Terminals.definition_keywords.mkString("(", "|", ")") + private val keywords: String = Keywords.definition_keywords.mkString("(", "|", ")") private val pathIdRegex = s" ($keywords) (\\w+(\\.\\w+)*)".r private def substituteIn(lineToReplace: String): String = { val matches = pathIdRegex.findAllMatchIn(lineToReplace).toSeq.reverse @@ -440,7 +440,7 @@ case class MarkdownWriter( options: Seq[OT], level: Int = 2 ): this.type = { - list("Options", options.map(_.format), level) + list("RiddlOptions", options.map(_.format), level) this } @@ -499,7 +499,7 @@ case class MarkdownWriter( parents: Seq[Definition] ): String = { val name = typeEx match { - case AliasedTypeExpression(_, pid) => makeTypeName(pid, parents) + case AliasedTypeExpression(_, _, pid) => makeTypeName(pid, parents) case EntityReferenceTypeExpression(_, pid) => makeTypeName(pid, parents) case UniqueId(_, pid) => makeTypeName(pid, parents) case Alternation(_, of) => diff --git a/language/src/main/scala/com/reactific/riddl/language/AST.scala b/language/src/main/scala/com/reactific/riddl/language/AST.scala index 1d1782e9e..0e9888b8c 100644 --- a/language/src/main/scala/com/reactific/riddl/language/AST.scala +++ b/language/src/main/scala/com/reactific/riddl/language/AST.scala @@ -6,8 +6,7 @@ package com.reactific.riddl.language -import com.reactific.riddl.language.parsing.RiddlParserInput -import com.reactific.riddl.language.parsing.Terminals.{Keywords, Options, Predefined, Readability} +import com.reactific.riddl.language.parsing.{PredefType, RiddlParserInput} import java.net.URL import java.nio.file.Path @@ -18,7 +17,7 @@ import scala.reflect.{ClassTag, classTag} * produced from parsing are syntactically correct but have no semantic validation. The Transformation passes convert * RawAST model to AST model which is referentially and semantically consistent (or the user gets an error). */ -object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Options with ast.Types with ast.Statements +object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.RiddlOptions with ast.Types with ast.Statements ///////////////////////////////////////////////////////////////////////////////////////////// ABSTRACT DEFINITIONS /** The root trait of all things RIDDL AST. Every node in the tree is a RiddlNode. */ @@ -165,6 +164,23 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op def hasDescription: Boolean = description.nonEmpty } + sealed trait CommentedValue extends RiddlValue { + def comments: Seq[Comment] + def commentText: String = comments.map(_.text).mkString("\n") + def hasComment: Boolean = comments.nonEmpty + } + + /** The AST Representation of a comment in the input. Comments can only occur after the closing brace, }, of a + * definition. The comment is stored within the [[Definition]] + * @param loc + * Location in the input of the // comment introducer + * @param text + * The text of the comment, everything after the // to the end of line + */ + case class Comment(loc: At, text: String = "") extends RiddlValue { + def format: String = "//" + text + } + /** Base trait of any definition that is also a ContainerValue * * @tparam D @@ -183,7 +199,11 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op /** Base trait for all definitions requiring an identifier for the definition and providing the identify method to * yield a string that provides the kind and name */ - sealed trait Definition extends DescribedValue with BrieflyDescribedValue with Container[Definition] { + sealed trait Definition + extends DescribedValue + with BrieflyDescribedValue + with CommentedValue + with Container[Definition] { def id: Identifier def kind: String @@ -411,8 +431,8 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op * @param pathId * The path identifier to the aliased type */ - case class AliasedTypeExpression(loc: At, pathId: PathIdentifier) extends TypeExpression { - override def format: String = pathId.format + case class AliasedTypeExpression(loc: At, keyword: String, pathId: PathIdentifier) extends TypeExpression { + override def format: String = s"$keyword ${pathId.format}" } /** A utility function for getting the kind of a type expression. @@ -424,18 +444,18 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op */ def errorDescription(te: TypeExpression): String = { te match { - case AliasedTypeExpression(_, pid) => pid.format - case Optional(_, typeExp) => errorDescription(typeExp) + "?" - case ZeroOrMore(_, typeExp) => errorDescription(typeExp) + "*" - case OneOrMore(_, typeExp) => errorDescription(typeExp) + "+" - case e: Enumeration => s"Enumeration of ${e.enumerators.size} values" - case a: Alternation => s"Alternation of ${a.of.size} types" - case a: Aggregation => s"Aggregation of ${a.fields.size} fields" + case AliasedTypeExpression(_, keyword, pid) => s"$keyword ${pid.format}" + case Optional(_, typeExp) => errorDescription(typeExp) + "?" + case ZeroOrMore(_, typeExp) => errorDescription(typeExp) + "*" + case OneOrMore(_, typeExp) => errorDescription(typeExp) + "+" + case e: Enumeration => s"Enumeration of ${e.enumerators.size} values" + case a: Alternation => s"Alternation of ${a.of.size} types" + case a: Aggregation => s"Aggregation of ${a.fields.size} fields" case Mapping(_, from, to) => s"Map from ${errorDescription(from)} to ${errorDescription(to)}" case EntityReferenceTypeExpression(_, entity) => s"Reference to entity ${entity.format}" - case _: Pattern => Predefined.Pattern + case p: Pattern => p.format case Decimal(_, whl, frac) => s"Decimal($whl,$frac)" case RangeType(_, min, max) => s"Range($min,$max)" case UniqueId(_, entityPath) => s"Id(${entityPath.format})" @@ -471,13 +491,16 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op /** An enumerator value for result types */ case object ResultCase extends AggregateUseCase { @inline def kind: String = "Result" - } case object RecordCase extends AggregateUseCase { @inline def kind: String = "Record" } + case object TypeCase extends AggregateUseCase { + @inline def kind: String = "Type" + } + /** Base trait of the cardinality type expressions */ sealed trait Cardinality extends TypeExpression { def typeExp: TypeExpression @@ -554,7 +577,8 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op id: Identifier, enumVal: Option[Long] = None, brief: Option[LiteralString] = Option.empty[LiteralString], - description: Option[Description] = None + description: Option[Description] = None, + comments: Seq[Comment] = Seq.empty[Comment] ) extends LeafDefinition with TypeDefinition { override def format: String = id.format @@ -608,7 +632,8 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op id: Identifier, typeEx: TypeExpression, brief: Option[LiteralString] = Option.empty[LiteralString], - description: Option[Description] = None + description: Option[Description] = None, + comments: Seq[Comment] = Seq.empty[Comment] ) extends LeafDefinition with AggregateDefinition with AlwaysEmpty @@ -653,7 +678,8 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op args: Seq[MethodArgument] = Seq.empty[MethodArgument], typeEx: TypeExpression, brief: Option[LiteralString] = Option.empty[LiteralString], - description: Option[Description] = None + description: Option[Description] = None, + comments: Seq[Comment] = Seq.empty[Comment] ) extends LeafDefinition with AggregateDefinition with AlwaysEmpty @@ -754,7 +780,7 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op * The type of entity referenced by this type expression. */ case class EntityReferenceTypeExpression(loc: At, entity: PathIdentifier) extends TypeExpression { - override def format: String = s"${Keywords.entity} ${entity.format}" + override def format: String = s"entity ${entity.format}" } /** A type expression that defines a string value constrained by a Java Regular Expression @@ -767,9 +793,9 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op * https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/regex/Pattern.html */ case class Pattern(loc: At, pattern: Seq[LiteralString]) extends PredefinedType { - override def kind: String = Predefined.Pattern + override def kind: String = "Pattern" override def format: String = - s"${Predefined.Pattern}(${pattern.map(_.format).mkString(", ")})" + s"$kind(${pattern.map(_.format).mkString(", ")})" @SuppressWarnings(Array("org.wartremover.warts.IsInstanceOf")) override def isAssignmentCompatible(other: TypeExpression): Boolean = { @@ -785,7 +811,8 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op * The path identifier of the entity type */ case class UniqueId(loc: At, entityPath: PathIdentifier) extends PredefinedType { - @inline def kind: String = Predefined.Id + @inline def kind: String = "Id" + override def format: String = s"$kind(${entityPath.format})" @SuppressWarnings(Array("org.wartremover.warts.IsInstanceOf")) @@ -843,7 +870,7 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op * The maximum length of the string (default: MaxInt) */ case class Strng(loc: At, min: Option[Long] = None, max: Option[Long] = None) extends PredefinedType { - override lazy val kind: String = Predefined.String + override lazy val kind: String = PredefType.String override def format: String = { if min.isEmpty && max.isEmpty then kind else s"$kind(${min.getOrElse("")},${max.getOrElse("")})" } @@ -855,13 +882,13 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op } case class Currency(loc: At, country: String) extends PredefinedType { - @inline def kind: String = Predefined.Currency + @inline def kind: String = PredefType.Currency } /** A type expression that is unknown at compile type but will be resolved before validation time. */ case class UnknownType(loc: At) extends PredefinedType { - @inline def kind: String = Predefined.Unknown + @inline def kind: String = PredefType.Unknown } /** The simplest type expression: Abstract An abstract type expression is one that is not defined explicitly. It is @@ -872,13 +899,13 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op * The location of the Bool type expression */ case class Abstract(loc: At) extends PredefinedType { - @inline def kind: String = Predefined.Abstract + @inline def kind: String = PredefType.Abstract override def isAssignmentCompatible(other: TypeExpression): Boolean = true } case class UserId(loc: At) extends PredefinedType { - @inline def kind: String = Predefined.UserId + @inline def kind: String = PredefType.UserId @SuppressWarnings(Array("org.wartremover.warts.IsInstanceOf")) override def isAssignmentCompatible(other: TypeExpression): Boolean = { @@ -896,7 +923,7 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op * The location of the Bool type expression */ case class Bool(loc: At) extends PredefinedType with IntegerTypeExpression { - @inline def kind: String = Predefined.Boolean + @inline def kind: String = PredefType.Boolean } /** A predefined type expression for an arbitrary number value @@ -905,7 +932,7 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op * The location of the number type expression */ case class Number(loc: At) extends PredefinedType with IntegerTypeExpression with RealTypeExpression { - @inline def kind: String = Predefined.Number + @inline def kind: String = PredefType.Number } /** A predefined type expression for an integer value @@ -914,15 +941,15 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op * The location of the integer type expression */ case class Integer(loc: At) extends PredefinedType with IntegerTypeExpression { - @inline def kind: String = Predefined.Integer + @inline def kind: String = PredefType.Integer } case class Whole(loc: At) extends PredefinedType with IntegerTypeExpression { - @inline def kind: String = Predefined.Whole + @inline def kind: String = PredefType.Whole } case class Natural(loc: At) extends PredefinedType with IntegerTypeExpression { - @inline def kind: String = Predefined.Whole + @inline def kind: String = PredefType.Whole } /** A type expression that defines a set of integer values from a minimum value to a maximum value, inclusively. @@ -936,7 +963,7 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op */ case class RangeType(loc: At, min: Long, max: Long) extends IntegerTypeExpression { override def format: String = s"$kind($min,$max)" - @inline def kind: String = Predefined.Range + @inline def kind: String = PredefType.Range @SuppressWarnings(Array("org.wartremover.warts.IsInstanceOf")) override def isAssignmentCompatible(other: TypeExpression): Boolean = { @@ -950,7 +977,7 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op * The location of the decimal integer type expression */ case class Decimal(loc: At, whole: Long, fractional: Long) extends RealTypeExpression { - @inline def kind: String = Predefined.Decimal + @inline def kind: String = PredefType.Decimal /** Format the node to a string */ override def format: String = s"Decimal($whole,$fractional)" @@ -962,7 +989,7 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op * The location of the real number type expression */ case class Real(loc: At) extends PredefinedType with RealTypeExpression { - @inline def kind: String = Predefined.Real + @inline def kind: String = PredefType.Real } /** A predefined type expression for the SI Base unit for Current (amperes) @@ -970,7 +997,7 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op * \- The locaitonof the current type expression */ case class Current(loc: At) extends PredefinedType with RealTypeExpression { - @inline def kind: String = Predefined.Current + @inline def kind: String = PredefType.Current } /** A predefined type expression for the SI Base unit for Length (meters) @@ -978,7 +1005,7 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op * The location of the current type expression */ case class Length(loc: At) extends PredefinedType with RealTypeExpression { - @inline def kind: String = Predefined.Length + @inline def kind: String = PredefType.Length } /** A predefined type expression for the SI Base Unit for Luminosity (candela) @@ -986,11 +1013,11 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op * The location of the luminosity expression */ case class Luminosity(loc: At) extends PredefinedType with RealTypeExpression { - @inline def kind: String = Predefined.Luminosity + @inline def kind: String = PredefType.Luminosity } case class Mass(loc: At) extends PredefinedType with RealTypeExpression { - @inline def kind: String = Predefined.Mass + @inline def kind: String = PredefType.Mass } /** A predefined type expression for the SI Base Unit for Mole (mole) @@ -998,7 +1025,7 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op * \- The location of the mass type expression */ case class Mole(loc: At) extends PredefinedType with RealTypeExpression { - @inline def kind: String = Predefined.Mole + @inline def kind: String = PredefType.Mole } /** A predefined type expression for the SI Base Unit for Temperature (Kelvin) @@ -1006,7 +1033,7 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op * \- The location of the mass type expression */ case class Temperature(loc: At) extends PredefinedType with RealTypeExpression { - @inline def kind: String = Predefined.Temperature + @inline def kind: String = PredefType.Temperature } sealed trait TimeType extends PredefinedType @@ -1017,7 +1044,7 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op * The location of the date type expression. */ case class Date(loc: At) extends TimeType { - @inline def kind: String = Predefined.Date + @inline def kind: String = PredefType.Date @SuppressWarnings(Array("org.wartremover.warts.IsInstanceOf")) override def isAssignmentCompatible(other: TypeExpression): Boolean = { @@ -1033,7 +1060,7 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op * The location of the time type expression. */ case class Time(loc: At) extends TimeType { - @inline def kind: String = Predefined.Time + @inline def kind: String = PredefType.Time @SuppressWarnings(Array("org.wartremover.warts.IsInstanceOf")) override def isAssignmentCompatible(other: TypeExpression): Boolean = { @@ -1049,7 +1076,7 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op * The location of the datetime type expression. */ case class DateTime(loc: At) extends TimeType { - @inline def kind: String = Predefined.DateTime + @inline def kind: String = PredefType.DateTime @SuppressWarnings(Array("org.wartremover.warts.IsInstanceOf")) override def isAssignmentCompatible(other: TypeExpression): Boolean = { @@ -1065,7 +1092,7 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op * The location of the timestamp */ case class TimeStamp(loc: At) extends TimeType { - @inline def kind: String = Predefined.TimeStamp + @inline def kind: String = PredefType.TimeStamp @SuppressWarnings(Array("org.wartremover.warts.IsInstanceOf")) override def isAssignmentCompatible(other: TypeExpression): Boolean = { @@ -1082,7 +1109,7 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op * The location of the duration type expression */ case class Duration(loc: At) extends TimeType { - @inline def kind: String = Predefined.Duration + @inline def kind: String = PredefType.Duration } /** A predefined type expression for a universally unique identifier as defined by the Java Virtual Machine. @@ -1091,7 +1118,7 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op * The location of the UUID type expression */ case class UUID(loc: At) extends PredefinedType { - @inline def kind: String = Predefined.UUID + @inline def kind: String = PredefType.UUID } /** A predefined type expression for a Uniform Resource Locator of a specific schema. @@ -1102,7 +1129,7 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op * The scheme to which the URL is constrained. */ case class URL(loc: At, scheme: Option[LiteralString] = None) extends PredefinedType { - @inline def kind: String = Predefined.URL + @inline def kind: String = PredefType.URL } /** A predefined type expression for a location on earth given in latitude and longitude. @@ -1111,7 +1138,7 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op * The location of the LatLong type expression. */ case class Location(loc: At) extends PredefinedType { - @inline def kind: String = Predefined.Location + @inline def kind: String = PredefType.Location } /** A predefined type expression for a type that can have no values @@ -1120,7 +1147,7 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op * The location of the nothing type expression. */ case class Nothing(loc: At) extends PredefinedType { - @inline def kind: String = Predefined.Nothing + @inline def kind: String = PredefType.Nothing override def isAssignmentCompatible(other: TypeExpression): Boolean = false } @@ -1340,7 +1367,7 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op sealed abstract class StreamletOption(val name: String) extends OptionValue case class StreamletTechnologyOption(loc: At, override val args: Seq[LiteralString]) - extends StreamletOption(Options.technology) + extends StreamletOption("technology") //////////////////////////////////////////////////////////////////// PIPE @@ -1411,12 +1438,13 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op loc: At, id: Identifier, brief: Option[LiteralString] = None, - description: Option[Description] = None + description: Option[Description] = None, + comments: Seq[Comment] = Seq.empty[Comment] ) extends LeafDefinition with VitalDefinitionDefinition { override def isEmpty: Boolean = description.isEmpty - def format: String = s"${Keywords.term} ${id.format}" + def format: String = s"term ${id.format}" final val kind: String = "Term" } @@ -1440,7 +1468,8 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op case class Include[T <: Definition]( loc: At = At(RiddlParserInput.empty), contents: Seq[T] = Seq.empty[T], - source: Option[String] = None + source: Option[String] = None, + comments: Seq[Comment] = Seq.empty[Comment] ) extends Definition with VitalDefinitionDefinition with RootDefinition { @@ -1491,7 +1520,8 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op title: Option[LiteralString] = None, url: Option[java.net.URL] = None, brief: Option[LiteralString] = None, - description: Option[Description] = None + description: Option[Description] = None, + comments: Seq[Comment] = Seq.empty[Comment] ) extends LeafDefinition with RootDefinition with DomainDefinition { @@ -1501,11 +1531,11 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op final val kind: String = "Author" - def format: String = s"${Keywords.author} ${id.format}" + def format: String = s"author ${id.format}" } case class AuthorRef(loc: At, pathId: PathIdentifier) extends Reference[Author] { - override def format: String = s"${Keywords.author} ${pathId.format}" + override def format: String = s"author ${pathId.format}" def kind: String = "" } @@ -1566,11 +1596,14 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op * The inputs for this root scope */ case class RootContainer( + preComments: Seq[Comment] = Seq.empty[Comment], contents: Seq[RootDefinition] = Seq.empty[RootDefinition], + postComments: Seq[Comment] = Seq.empty[Comment], inputs: Seq[RiddlParserInput] = Nil ) extends Definition { lazy val domains: Seq[Domain] = contents.filter(_.getClass == classOf[Domain]).asInstanceOf[Seq[Domain]] lazy val authors: Seq[Author] = contents.filter(_.getClass == classOf[Author]).asInstanceOf[Seq[Author]] + def comments: Seq[Comment] = Seq.empty[Comment] override def isRootContainer: Boolean = true @@ -1592,8 +1625,13 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op } object RootContainer { - val empty: RootContainer = - RootContainer(Seq.empty[RootDefinition], Seq.empty[RiddlParserInput]) + val empty: RootContainer = apply(Seq.empty[RootDefinition], Seq.empty[RiddlParserInput]) + def apply( + contents: Seq[RootDefinition], + inputs: Seq[RiddlParserInput] + ): RootContainer = { + RootContainer(Seq.empty[Comment], contents, Seq.empty[Comment], inputs) + } } /** Base trait for the four kinds of message references */ @@ -1711,7 +1749,8 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op typeEx: TypeExpression, value: LiteralString, brief: Option[LiteralString], - description: Option[Description] + description: Option[Description], + comments: Seq[Comment] = Seq.empty[Comment] ) extends LeafDefinition with ProcessorDefinition with DomainDefinition { @@ -1719,14 +1758,14 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op /** Format the node to a string */ override def format: String = - s"${Keywords.const} ${id.format} is ${typeEx.format} = ${value.format}" + s"const ${id.format} is ${typeEx.format} = ${value.format}" } case class ConstantRef( loc: At = At.empty, pathId: PathIdentifier = PathIdentifier.empty ) extends Reference[Field] { - override def format: String = s"${Keywords.const} ${pathId.format}" + override def format: String = s"const ${pathId.format}" } /** A type definition which associates an identifier with a type expression. @@ -1747,7 +1786,8 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op id: Identifier, typ: TypeExpression, brief: Option[LiteralString] = Option.empty[LiteralString], - description: Option[Description] = None + description: Option[Description] = None, + comments: Seq[Comment] = Seq.empty[Comment] ) extends Definition with ProcessorDefinition with ProjectorDefinition @@ -1781,16 +1821,17 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op */ case class TypeRef( loc: At = At.empty, + keyword: String = "type", pathId: PathIdentifier = PathIdentifier.empty ) extends Reference[Type] { - override def format: String = s"${Keywords.`type`} ${pathId.format}" + override def format: String = s"$keyword ${pathId.format}" } case class FieldRef( loc: At = At.empty, pathId: PathIdentifier = PathIdentifier.empty ) extends Reference[Field] { - override def format: String = s"${Keywords.field} ${pathId.format}" + override def format: String = s"field ${pathId.format}" } ////////////////////////////////////////////////////////////////////////////////////////////////////////// STATEMENTS @@ -1995,7 +2036,7 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op * The path identifier of the referenced entity. */ case class EntityRef(loc: At, pathId: PathIdentifier) extends ProcessorRef[Entity] { - override def format: String = s"${Keywords.entity} ${pathId.format}" + override def format: String = s"entity ${pathId.format}" } /** A reference to a function. @@ -2006,7 +2047,7 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op * The path identifier of the referenced function. */ case class FunctionRef(loc: At, pathId: PathIdentifier) extends Reference[Function] { - override def format: String = s"${Keywords.function} ${pathId.format}" + override def format: String = s"function ${pathId.format}" } /** A function definition which can be part of a bounded context or an entity. @@ -2052,7 +2093,8 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op options: Seq[FunctionOption] = Seq.empty[FunctionOption], terms: Seq[Term] = Seq.empty[Term], brief: Option[LiteralString] = Option.empty[LiteralString], - description: Option[Description] = None + description: Option[Description] = None, + comments: Seq[Comment] = Seq.empty[Comment] ) extends VitalDefinition[FunctionOption, FunctionDefinition] with WithTypes with AdaptorDefinition @@ -2095,7 +2137,8 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op id: Identifier, condition: Option[LiteralString] = Option.empty[LiteralString], brief: Option[LiteralString] = Option.empty[LiteralString], - description: Option[Description] = None + description: Option[Description] = None, + comments: Seq[Comment] = Seq.empty[Comment] ) extends LeafDefinition with ProcessorDefinition with StateDefinition { @@ -2128,7 +2171,8 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op loc: At, statements: Seq[Statement] = Seq.empty[Statement], brief: Option[LiteralString] = Option.empty[LiteralString], - description: Option[Description] = None + description: Option[Description] = None, + comments: Seq[Comment] = Seq.empty[Comment] ) extends OnClause { def id: Identifier = Identifier(loc, s"Other") @@ -2154,7 +2198,8 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op loc: At, statements: Seq[Statement] = Seq.empty[Statement], brief: Option[LiteralString] = Option.empty[LiteralString], - description: Option[Description] = None + description: Option[Description] = None, + comments: Seq[Comment] = Seq.empty[Comment] ) extends OnClause { def id: Identifier = Identifier(loc, s"Init") @@ -2187,7 +2232,8 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op from: Option[Reference[Definition]], statements: Seq[Statement] = Seq.empty[Statement], brief: Option[LiteralString] = Option.empty[LiteralString], - description: Option[Description] = None + description: Option[Description] = None, + comments: Seq[Comment] = Seq.empty[Comment] ) extends OnClause { def id: Identifier = Identifier(msg.loc, s"On ${msg.format}") @@ -2217,7 +2263,8 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op loc: At, statements: Seq[Statement] = Seq.empty[Statement], brief: Option[LiteralString] = Option.empty[LiteralString], - description: Option[Description] = None + description: Option[Description] = None, + comments: Seq[Comment] = Seq.empty[Comment] ) extends OnClause { def id: Identifier = Identifier(loc, s"Term") @@ -2249,7 +2296,8 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op clauses: Seq[OnClause] = Seq.empty[OnClause], authors: Seq[AuthorRef] = Seq.empty[AuthorRef], brief: Option[LiteralString] = Option.empty[LiteralString], - description: Option[Description] = None + description: Option[Description] = None, + comments: Seq[Comment] = Seq.empty[Comment] ) extends Container[HandlerDefinition] with AdaptorDefinition with ApplicationDefinition @@ -2265,7 +2313,7 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op final val kind: String = "Handler" - def format: String = s"${Keywords.handler} ${id.format}" + def format: String = s"handler ${id.format}" } /** A reference to a Handler @@ -2276,7 +2324,7 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op * The path identifier of the referenced handler */ case class HandlerRef(loc: At, pathId: PathIdentifier) extends Reference[Handler] { - override def format: String = s"${Keywords.handler} ${pathId.format}" + override def format: String = s"handler ${pathId.format}" } /** Represents the state of an entity. The MorphAction can cause the state definition of an entity to change. @@ -2304,12 +2352,13 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op handlers: Seq[Handler] = Seq.empty[Handler], invariants: Seq[Invariant] = Seq.empty[Invariant], brief: Option[LiteralString] = Option.empty[LiteralString], - description: Option[Description] = None + description: Option[Description] = None, + comments: Seq[Comment] = Seq.empty[Comment] ) extends EntityDefinition { override def contents: Seq[StateDefinition] = handlers ++ invariants - def format: String = s"${Keywords.state} ${id.format}" + def format: String = s"state ${id.format}" final val kind: String = "State" } @@ -2322,7 +2371,7 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op * The path identifier of the referenced state definition */ case class StateRef(loc: At, pathId: PathIdentifier) extends Reference[State] { - override def format: String = s"${Keywords.state} ${pathId.format}" + override def format: String = s"state ${pathId.format}" } /** Definition of an Entity @@ -2365,7 +2414,8 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op authors: Seq[AuthorRef] = Seq.empty[AuthorRef], terms: Seq[Term] = Seq.empty[Term], brief: Option[LiteralString] = Option.empty[LiteralString], - description: Option[Description] = None + description: Option[Description] = None, + comments: Seq[Comment] = Seq.empty[Comment] ) extends Processor[EntityOption, EntityDefinition] with ContextDefinition { @@ -2427,7 +2477,8 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op options: Seq[AdaptorOption] = Seq.empty[AdaptorOption], terms: Seq[Term] = Seq.empty[Term], brief: Option[LiteralString] = Option.empty[LiteralString], - description: Option[Description] = None + description: Option[Description] = None, + comments: Seq[Comment] = Seq.empty[Comment] ) extends Processor[AdaptorOption, AdaptorDefinition] with ContextDefinition { override lazy val contents: Seq[AdaptorDefinition] = { @@ -2438,7 +2489,7 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op } case class AdaptorRef(loc: At, pathId: PathIdentifier) extends ProcessorRef[Adaptor] { - override def format: String = s"${Keywords.adaptor} ${pathId.format}" + override def format: String = s"adaptor ${pathId.format}" } /** A RIDDL repository is an abstraction for anything that can retain information(e.g. messages for retrieval at a @@ -2462,7 +2513,7 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op * @param includes * Included files * @param options - * Options that can be used by the translators + * RiddlOptions that can be used by the translators * @param terms * Definitions of terms about this repository * @param brief @@ -2486,7 +2537,8 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op options: Seq[RepositoryOption] = Seq.empty[RepositoryOption], terms: Seq[Term] = Seq.empty[Term], brief: Option[LiteralString] = Option.empty[LiteralString], - description: Option[Description] = None + description: Option[Description] = None, + comments: Seq[Comment] = Seq.empty[Comment] ) extends Processor[RepositoryOption, RepositoryDefinition] with ContextDefinition { override def kind: String = "Repository" @@ -2504,7 +2556,7 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op * The path identifier of the referenced projector definition */ case class RepositoryRef(loc: At, pathId: PathIdentifier) extends ProcessorRef[Projector] { - override def format: String = s"${Keywords.repository} ${pathId.format}" + override def format: String = s"repository ${pathId.format}" } /** Projectors get their name from Euclidean Geometry but are probably more analogous to a relational database view. @@ -2522,7 +2574,7 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op * @param authors * The authors of this definition * @param options - * Options that can be used by the translators + * RiddlOptions that can be used by the translators * @param types * The type definitions necessary to construct the query results * @param handlers @@ -2549,7 +2601,8 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op invariants: Seq[Invariant] = Seq.empty[Invariant], terms: Seq[Term] = Seq.empty[Term], brief: Option[LiteralString] = Option.empty[LiteralString], - description: Option[Description] = None + description: Option[Description] = None, + comments: Seq[Comment] = Seq.empty[Comment] ) extends Processor[ProjectorOption, ProjectorDefinition] with ContextDefinition with WithTypes { @@ -2571,11 +2624,12 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op id: Identifier, typeExp: TypeExpression, brief: Option[LiteralString] = Option.empty[LiteralString], - description: Option[Description] = None + description: Option[Description] = None, + comments: Seq[Comment] = Seq.empty[Comment] ) extends LeafDefinition with ContextDefinition { final val kind: String = "Replica" - final val format: String = s"$kind ${id.format}" + final val format: String = s"replica ${id.format}" } /** A reference to an context's projector definition @@ -2586,7 +2640,7 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op * The path identifier of the referenced projector definition */ case class ProjectorRef(loc: At, pathId: PathIdentifier) extends ProcessorRef[Projector] { - override def format: String = s"${Keywords.projector} ${pathId.format}" + override def format: String = s"projector ${pathId.format}" } /** A bounded context definition. Bounded contexts provide a definitional boundary on the language used to describe @@ -2638,7 +2692,8 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op replicas: Seq[Replica] = Seq.empty[Replica], authors: Seq[AuthorRef] = Seq.empty[AuthorRef], brief: Option[LiteralString] = Option.empty[LiteralString], - description: Option[Description] = None + description: Option[Description] = None, + comments: Seq[Comment] = Seq.empty[Comment] ) extends Processor[ContextOption, ContextDefinition] with DomainDefinition { override lazy val contents: Seq[ContextDefinition] = super.contents ++ @@ -2687,14 +2742,13 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op id: Identifier, type_ : Reference[Type], brief: Option[LiteralString] = None, - description: Option[Description] = None + description: Option[Description] = None, + comments: Seq[Comment] = Seq.empty[Comment] ) extends Portlet with LeafDefinition with ProcessorDefinition with AlwaysEmpty { - def format: String = - s"${Keywords.inlet} ${id.format} is ${type_.format}" - + def format: String = s"inlet ${id.format} is ${type_.format}" final val kind: String = "Inlet" } @@ -2716,13 +2770,13 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op id: Identifier, type_ : Reference[Type], brief: Option[LiteralString] = None, - description: Option[Description] = None + description: Option[Description] = None, + comments: Seq[Comment] = Seq.empty[Comment] ) extends Portlet with LeafDefinition with ProcessorDefinition with AlwaysEmpty { - def format: String = s"${Keywords.outlet} ${id.format} is ${type_.format}" - + def format: String = s"outlet ${id.format} is ${type_.format}" final val kind: String = "Outlet" } @@ -2734,7 +2788,8 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op from: Option[OutletRef] = Option.empty[OutletRef], to: Option[InletRef] = Option.empty[InletRef], brief: Option[LiteralString] = Option.empty[LiteralString], - description: Option[Description] = Option.empty[Description] + description: Option[Description] = Option.empty[Description], + comments: Seq[Comment] = Seq.empty[Comment] ) extends LeafDefinition with ContextDefinition with WithOptions[ConnectorOption] { @@ -2742,7 +2797,7 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op from.isEmpty && to.isEmpty final val kind: String = "Connector" - override def format: String = s"${Keywords.connector}" + override def format: String = s"connector" } sealed trait StreamletShape extends RiddlValue { @@ -2750,45 +2805,45 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op } case class Void(loc: At) extends StreamletShape { - def format: String = Keywords.void + def format: String = "void" - def keyword: String = Keywords.void + def keyword: String = "void" } case class Source(loc: At) extends StreamletShape { - def format: String = Keywords.source + def format: String = "source" - def keyword: String = Keywords.source + def keyword: String = "source" } case class Sink(loc: At) extends StreamletShape { - def format: String = Keywords.sink + def format: String = "sink" - def keyword: String = Keywords.sink + def keyword: String = "sink" } case class Flow(loc: At) extends StreamletShape { - def format: String = Keywords.flow + def format: String = "flow" - def keyword: String = Keywords.flow + def keyword: String = "flow" } case class Merge(loc: At) extends StreamletShape { - def format: String = Keywords.merge + def format: String = "merge" - def keyword: String = Keywords.merge + def keyword: String = "merge" } case class Split(loc: At) extends StreamletShape { - def format: String = Keywords.split + def format: String = "split" - def keyword: String = Keywords.split + def keyword: String = "split" } case class Router(loc: At) extends StreamletShape { - def format: String = Keywords.router + def format: String = "router" - def keyword: String = Keywords.router + def keyword: String = "router" } /** Definition of a Streamlet. A computing element for processing data from [[Inlet]]s to [[Outlet]]s. A processor's @@ -2829,7 +2884,8 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op options: Seq[StreamletOption] = Seq.empty[StreamletOption], terms: Seq[Term] = Seq.empty[Term], brief: Option[LiteralString] = Option.empty[LiteralString], - description: Option[Description] = None + description: Option[Description] = None, + comments: Seq[Comment] = Seq.empty[Comment] ) extends Processor[StreamletOption, StreamletDefinition] with ContextDefinition { override def contents: Seq[StreamletDefinition] = super.contents ++ @@ -2884,8 +2940,8 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op * @param pathId * The path identifier of the referenced projector definition */ - case class StreamletRef(loc: At, pathId: PathIdentifier) extends ProcessorRef[Streamlet] { - override def format: String = s"${Keywords.streamlet} ${pathId.format}" + case class StreamletRef(loc: At, keyword: String, pathId: PathIdentifier) extends ProcessorRef[Streamlet] { + override def format: String = s"$keyword ${pathId.format}" } /** Sealed base trait of references to [[Inlet]]s or [[Outlet]]s @@ -2903,7 +2959,7 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op * The path identifier of the referenced [[Inlet]] */ case class InletRef(loc: At, pathId: PathIdentifier) extends PortletRef[Inlet] { - override def format: String = s"${Keywords.inlet} ${pathId.format}" + override def format: String = s"inlet ${pathId.format}" } /** A reference to an [[Outlet]] @@ -2914,7 +2970,7 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op * The path identifier of the referenced [[Outlet]] */ case class OutletRef(loc: At, pathId: PathIdentifier) extends PortletRef[Outlet] { - override def format: String = s"${Keywords.outlet} ${pathId.format}" + override def format: String = s"outlet ${pathId.format}" } /** The definition of one step in a saga with its undo step and example. @@ -2938,10 +2994,11 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op doStatements: Seq[Statement] = Seq.empty[Statement], undoStatements: Seq[Statement] = Seq.empty[Statement], brief: Option[LiteralString] = Option.empty[LiteralString], - description: Option[Description] = None + description: Option[Description] = None, + comments: Seq[Comment] = Seq.empty[Comment] ) extends LeafDefinition with SagaDefinition { - def format: String = s"${Keywords.step} ${id.format}" + def format: String = s"step ${id.format}" final val kind: String = "SagaStep" } @@ -2981,7 +3038,8 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op includes: Seq[Include[SagaDefinition]] = Seq.empty[Include[SagaDefinition]], terms: Seq[Term] = Seq.empty[Term], brief: Option[LiteralString] = Option.empty[LiteralString], - description: Option[Description] = None + description: Option[Description] = None, + comments: Seq[Comment] = Seq.empty[Comment] ) extends VitalDefinition[SagaOption, SagaDefinition] with ContextDefinition with DomainDefinition { @@ -2997,7 +3055,7 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op } case class SagaRef(loc: At, pathId: PathIdentifier) extends Reference[Saga] { - def format: String = s"${Keywords.saga} ${pathId.format}" + def format: String = s"saga ${pathId.format}" } /** An User (Role) who is the initiator of the user story. Users may be persons or machines @@ -3018,10 +3076,11 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op id: Identifier, is_a: LiteralString, brief: Option[LiteralString] = None, - description: Option[Description] = None + description: Option[Description] = None, + comments: Seq[Comment] = Seq.empty[Comment] ) extends LeafDefinition with DomainDefinition { - def format: String = s"${Keywords.user} ${id.format} is ${is_a.format}" + def format: String = s"user ${id.format} is ${is_a.format}" override def kind: String = "User" } @@ -3034,7 +3093,7 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op * The path identifier that locates the User */ case class UserRef(loc: At, pathId: PathIdentifier) extends Reference[User] { - def format: String = s"${Keywords.user} ${pathId.format}" + def format: String = s"user ${pathId.format}" } sealed trait Interaction extends UseCaseDefinition { @@ -3057,7 +3116,8 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op id: Identifier = Identifier.empty, contents: Seq[Interaction] = Seq.empty[Interaction], brief: Option[LiteralString] = None, - description: Option[Description] = None + description: Option[Description] = None, + comments: Seq[Comment] = Seq.empty[Comment] ) extends Interaction { override def kind: String = "Parallel Interaction" } @@ -3081,7 +3141,8 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op id: Identifier = Identifier.empty, contents: Seq[Interaction] = Seq.empty[Interaction], brief: Option[LiteralString], - description: Option[Description] = None + description: Option[Description] = None, + comments: Seq[Comment] = Seq.empty[Comment] ) extends Interaction { override def kind: String = "Sequential Interaction" } @@ -3100,7 +3161,8 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op id: Identifier = Identifier.empty, contents: Seq[Interaction] = Seq.empty[Interaction], brief: Option[LiteralString], - description: Option[Description] = None + description: Option[Description] = None, + comments: Seq[Comment] = Seq.empty[Comment] ) extends Interaction { override def kind: String = "Optional Interaction" } @@ -3111,7 +3173,8 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op id: Identifier = Identifier.empty, relationship: LiteralString, brief: Option[LiteralString] = None, - description: Option[Description] = None + description: Option[Description] = None, + comments: Seq[Comment] = Seq.empty[Comment] ) extends Interaction { override def kind: String = "Vague Interaction" @@ -3149,7 +3212,8 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op relationship: LiteralString, to: Reference[Definition], brief: Option[LiteralString] = None, - description: Option[Description] = None + description: Option[Description] = None, + comments: Seq[Comment] = Seq.empty[Comment] ) extends GenericInteraction { override def kind: String = "Arbitrary Interaction" } @@ -3160,7 +3224,8 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op from: Reference[Definition], relationship: LiteralString, brief: Option[LiteralString] = None, - description: Option[Description] = None + description: Option[Description] = None, + comments: Seq[Comment] = Seq.empty[Comment] ) extends GenericInteraction { override def kind: String = "Self Interaction" override def to: Reference[Definition] = from @@ -3185,7 +3250,8 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op relationship: LiteralString, to: UserRef, brief: Option[LiteralString] = None, - description: Option[Description] = None + description: Option[Description] = None, + comments: Seq[Comment] = Seq.empty[Comment] ) extends GenericInteraction { override def kind: String = "Show Output Interaction" } @@ -3210,7 +3276,8 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op relationship: LiteralString, to: InputRef, brief: Option[LiteralString] = None, - description: Option[Description] = None + description: Option[Description] = None, + comments: Seq[Comment] = Seq.empty[Comment] ) extends GenericInteraction { override def kind: String = "Take Input Interaction" } @@ -3234,11 +3301,12 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op userStory: Option[UserStory] = None, contents: Seq[Interaction] = Seq.empty[Interaction], brief: Option[LiteralString] = None, - description: Option[Description] = None + description: Option[Description] = None, + comments: Seq[Comment] = Seq.empty[Comment] ) extends EpicDefinition with Container[Interaction] { override def kind: String = "UseCase" - override def format: String = s"${Keywords.case_} ${id.format}" + override def format: String = s"case ${id.format}" } /** An agile user story definition in the usual "As a {role} I want {capability} so that {benefit}" style. @@ -3258,7 +3326,7 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op capability: LiteralString = LiteralString.empty, benefit: LiteralString = LiteralString.empty ) extends RiddlValue { - def format: String = "" + def format: String = "" // FIXME: Format a UserStory override def isEmpty: Boolean = false } @@ -3293,7 +3361,8 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op options: Seq[EpicOption] = Seq.empty[EpicOption], terms: Seq[Term] = Seq.empty[Term], brief: Option[LiteralString] = Option.empty[LiteralString], - description: Option[Description] = None + description: Option[Description] = None, + comments: Seq[Comment] = Seq.empty[Comment] ) extends VitalDefinition[EpicOption, EpicDefinition] with DomainDefinition { override def contents: Seq[EpicDefinition] = { @@ -3306,7 +3375,7 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op final val kind: String = "Epic" - override def format: String = s"${Keywords.epic} ${id.format}" + override def format: String = s"epic ${id.format}" } /** A reference to a Story definintion. @@ -3316,7 +3385,7 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op * The path id of the referenced Story */ case class EpicRef(loc: At, pathId: PathIdentifier) extends Reference[Epic] { - def format: String = s"${Keywords.epic} ${pathId.format}" + def format: String = s"epic ${pathId.format}" } /** A group of GroupDefinition that can be treated as a whole. For example, a form, a button group, etc. @@ -3337,7 +3406,8 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op id: Identifier, elements: Seq[GroupDefinition] = Seq.empty[GroupDefinition], brief: Option[LiteralString] = None, - description: Option[Description] = None + description: Option[Description] = None, + comments: Seq[Comment] = Seq.empty[Comment] ) extends ApplicationDefinition with GroupDefinition { override def kind: String = "Group" @@ -3350,24 +3420,26 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op } /** A Group contained within a group - * - * @param loc - * Location of the contained group - * @param id - * The name of the group contained - * @param group - * The contained group as a reference to that group - */ + * + * @param loc + * Location of the contained group + * @param id + * The name of the group contained + * @param group + * The contained group as a reference to that group + */ case class ContainedGroup( loc: At, id: Identifier, group: GroupRef, brief: Option[LiteralString] = None, - description: Option[Description] = None - ) extends LeafDefinition with GroupDefinition { + description: Option[Description] = None, + comments: Seq[Comment] = Seq.empty[Comment] + ) extends LeafDefinition + with GroupDefinition { def kind: String = "ContainedGroup" - def format: String = s"contains ${id.format} ${Readability.as} ${group.format}" + def format: String = s"contains ${id.format} as ${group.format}" } /** A Reference to a Group @@ -3377,7 +3449,7 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op * The path to the referenced group */ case class GroupRef(loc: At, pathId: PathIdentifier) extends Reference[Group] { - def format: String = s"${Keywords.group} ${pathId.format}" + def format: String = s"group ${pathId.format}" } /** A UI Element that presents some information to the user @@ -3403,7 +3475,8 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op putOut: TypeRef, outputs: Seq[OutputDefinition] = Seq.empty[OutputDefinition], brief: Option[LiteralString] = None, - description: Option[Description] = None + description: Option[Description] = None, + comments: Seq[Comment] = Seq.empty[Comment] ) extends ApplicationDefinition with OutputDefinition with GroupDefinition { @@ -3413,7 +3486,7 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op override lazy val contents: Seq[OutputDefinition] = outputs /** Format the node to a string */ - override def format: String = s"$kind $verbAlias ${putOut.format}" + override def format: String = s"$kind ${id.value} $verbAlias ${putOut.format}" } /** A reference to an View using a path identifier @@ -3424,7 +3497,7 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op * The path identifier that refers to the View */ case class OutputRef(loc: At, pathId: PathIdentifier) extends Reference[Output] { - def format: String = s"${Keywords.output} ${pathId.format}" + def format: String = s"output ${pathId.format}" } /** A Give is a UI Element to allow the user to 'give' some data to the application. It is analogous to a form in HTML @@ -3448,7 +3521,8 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op putIn: TypeRef, inputs: Seq[InputDefinition] = Seq.empty[InputDefinition], brief: Option[LiteralString] = None, - description: Option[Description] = None + description: Option[Description] = None, + comments: Seq[Comment] = Seq.empty[Comment] ) extends ApplicationDefinition with GroupDefinition with InputDefinition { @@ -3471,7 +3545,7 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op * The path identifier that refers to the Give */ case class InputRef(loc: At, pathId: PathIdentifier) extends Reference[Input] { - def format: String = s"${Keywords.input} ${pathId.format}" + def format: String = s"input ${pathId.format}" } /** An application from which a person, robot, or other active agent (the user) will obtain information, or to which @@ -3519,7 +3593,8 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op terms: Seq[Term] = Seq.empty[Term], includes: Seq[Include[ApplicationDefinition]] = Seq.empty, brief: Option[LiteralString] = None, - description: Option[Description] = None + description: Option[Description] = None, + comments: Seq[Comment] = Seq.empty[Comment] ) extends Processor[ApplicationOption, ApplicationDefinition] with DomainDefinition { override def kind: String = "Application" @@ -3537,7 +3612,7 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op * The path identifier that refers to the Application */ case class ApplicationRef(loc: At, pathId: PathIdentifier) extends ProcessorRef[Application] { - def format: String = s"${Keywords.application} ${pathId.format}" + def format: String = s"application ${pathId.format}" } /** The definition of a domain. Domains are the highest building block in RIDDL and may be nested inside each other to @@ -3549,7 +3624,7 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op * @param id * The name of the domain * @param options - * Options for the domain + * RiddlOptions for the domain * @param types * Type definitions with a domain (nearly global) scope, with applicability to many contexts or subdomains * @param contexts @@ -3587,7 +3662,8 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op includes: Seq[Include[DomainDefinition]] = Seq .empty[Include[DomainDefinition]], brief: Option[LiteralString] = Option.empty[LiteralString], - description: Option[Description] = None + description: Option[Description] = None, + comments: Seq[Comment] = Seq.empty[Comment] ) extends VitalDefinition[DomainOption, DomainDefinition] with RootDefinition with WithTypes @@ -3609,6 +3685,6 @@ object AST { // extends ast.AbstractDefinitions with ast.Definitions with ast.Op * The path identifier for the referenced domain. */ case class DomainRef(loc: At, pathId: PathIdentifier) extends Reference[Domain] { - override def format: String = s"${Keywords.domain} ${pathId.format}" + override def format: String = s"domain ${pathId.format}" } } diff --git a/language/src/main/scala/com/reactific/riddl/language/parsing/AdaptorParser.scala b/language/src/main/scala/com/reactific/riddl/language/parsing/AdaptorParser.scala index e4d8dd9c2..3e210762e 100644 --- a/language/src/main/scala/com/reactific/riddl/language/parsing/AdaptorParser.scala +++ b/language/src/main/scala/com/reactific/riddl/language/parsing/AdaptorParser.scala @@ -6,11 +6,10 @@ package com.reactific.riddl.language.parsing -import Terminals.* - import com.reactific.riddl.language.AST.* import fastparse.* -import fastparse.ScalaWhitespace.* +import fastparse.MultiLineWhitespace.* +import Readability.* /** Parser rules for Adaptors */ private[parsing] trait AdaptorParser { @@ -23,7 +22,7 @@ private[parsing] trait AdaptorParser { with CommonParser => private def adaptorOptions[u: P]: P[Seq[AdaptorOption]] = { - options[u, AdaptorOption](StringIn(Options.technology).!) { case (loc, Options.technology, args) => + options[u, AdaptorOption](StringIn(RiddlOption.technology).!) { case (loc, RiddlOption.technology, args) => AdaptorTechnologyOption(loc, args) } } @@ -57,7 +56,7 @@ private[parsing] trait AdaptorParser { P( location ~ Keywords.adaptor ~/ identifier ~ authorRefs ~ adaptorDirection ~ contextRef ~ is ~ open ~ adaptorOptions ~ - adaptorBody ~ close ~ briefly ~ description + adaptorBody ~ close ~ briefly ~ description ~ comments ).map { case ( loc, @@ -68,7 +67,8 @@ private[parsing] trait AdaptorParser { options, defs, brief, - description + description, + comments ) => val groups = defs.groupBy(_.getClass) val includes = mapTo[Include[AdaptorDefinition]]( @@ -101,7 +101,8 @@ private[parsing] trait AdaptorParser { options, terms, brief, - description + description, + comments ) } } diff --git a/language/src/main/scala/com/reactific/riddl/language/parsing/ApplicationParser.scala b/language/src/main/scala/com/reactific/riddl/language/parsing/ApplicationParser.scala index dec697989..ffb4d091d 100644 --- a/language/src/main/scala/com/reactific/riddl/language/parsing/ApplicationParser.scala +++ b/language/src/main/scala/com/reactific/riddl/language/parsing/ApplicationParser.scala @@ -7,9 +7,9 @@ package com.reactific.riddl.language.parsing import com.reactific.riddl.language.AST.* -import Terminals.{Keywords, *} import fastparse.* -import fastparse.ScalaWhitespace.* +import fastparse.MultiLineWhitespace.* +import Readability.* private[parsing] trait ApplicationParser { this: StreamingParser @@ -21,22 +21,16 @@ private[parsing] trait ApplicationParser { with CommonParser => private def applicationOptions[u: P]: P[Seq[ApplicationOption]] = { - options[u, ApplicationOption](StringIn(Options.technology).!) { case (loc, Options.technology, args) => + options[u, ApplicationOption](StringIn(RiddlOption.technology).!) { case (loc, RiddlOption.technology, args) => ApplicationTechnologyOption(loc, args) } } - //loc: At, - // id: Identifier, - // group: GroupRef, - // brief: Option[LiteralString] = None, - // description: Option[Description] = None private def containedGroup[u: P]: P[ContainedGroup] = { P( - location ~ Keywords.contains ~ identifier ~ Readability.as ~ groupRef ~ briefly ~ description - ).map { - case (at, identifier, ref, brief, description) => - ContainedGroup(at, identifier, ref, brief, description) + location ~ Keywords.contains ~ identifier ~ Readability.as ~ groupRef ~ briefly ~ description ~ comments + ).map { case (loc, id, group, brief, description, comments) => + ContainedGroup(loc, id, group, brief, description, comments) } } @@ -48,14 +42,18 @@ private[parsing] trait ApplicationParser { P( location ~ groupAliases ~ identifier ~/ is ~ open ~ (undefined(Seq.empty[GroupDefinition]) | groupDefinitions) ~ - close ~ briefly ~ description - ).map { case (loc, alias, id, elements, brief, description) => + close ~ briefly ~ description ~ comments + ).map { case (loc, alias, id, elements, brief, description, comments) => Group(loc, alias, id, elements, brief, description) } } private def presentationAliases[u: P]: P[String] = { - StringIn("presents", "shows", "displays", "writes", "emits").! + Keywords + .keywords( + StringIn("presents", "shows", "displays", "writes", "emits") + ) + .! } private def outputDefinitions[u: P]: P[Seq[OutputDefinition]] = { @@ -69,13 +67,12 @@ private[parsing] trait ApplicationParser { } } - private def appOutput[u: P]: P[Output] = { P( location ~ outputAliases ~/ identifier ~ presentationAliases ~/ typeRef ~ - outputDefinitions ~ briefly ~ description - ).map { case (loc, nounAlias, id, verbAlias, putOut, outputs, brief, description) => - Output(loc, nounAlias, id, verbAlias, putOut, outputs, brief, description) + outputDefinitions ~ briefly ~ description ~ comments + ).map { case (loc, nounAlias, id, verbAlias, putOut, outputs, brief, description, comments) => + Output(loc, nounAlias, id, verbAlias, putOut, outputs, brief, description, comments) } } @@ -91,15 +88,25 @@ private[parsing] trait ApplicationParser { } private def acquisitionAliases[u: P]: P[String] = { - StringIn("acquires", "reads", "takes", "accepts", "admits", - "initiates", "submits", "triggers", "activates", "starts").! + StringIn( + "acquires", + "reads", + "takes", + "accepts", + "admits", + "initiates", + "submits", + "triggers", + "activates", + "starts" + ).! } private def appInput[u: P]: P[Input] = { P( location ~ inputAliases ~/ identifier ~/ acquisitionAliases ~/ typeRef ~ - inputDefinitions ~ briefly ~ description - ).map { case (loc, inputAlias, id, acquisitionAlias, putIn, inputs, brief, description) => + inputDefinitions ~ briefly ~ description ~ comments + ).map { case (loc, inputAlias, id, acquisitionAlias, putIn, inputs, brief, description, comments) => Input(loc, inputAlias, id, acquisitionAlias, putIn, inputs, brief, description) } } @@ -131,8 +138,8 @@ private[parsing] trait ApplicationParser { P( location ~ Keywords.application ~/ identifier ~ authorRefs ~ is ~ open ~ (emptyApplication | (applicationOptions ~ applicationDefinitions)) ~ - close ~ briefly ~ description - ).map { case (loc, id, authors, (options, content), brief, description) => + close ~ briefly ~ description ~ comments + ).map { case (loc, id, authors, (options, content), brief, description, comments) => val groups = content.groupBy(_.getClass) val types = mapTo[Type](groups.get(classOf[Type])) val constants = mapTo[Constant](groups.get(classOf[Constant])) @@ -165,7 +172,8 @@ private[parsing] trait ApplicationParser { terms, includes, brief, - description + description, + comments ) } diff --git a/language/src/main/scala/com/reactific/riddl/language/parsing/CommonParser.scala b/language/src/main/scala/com/reactific/riddl/language/parsing/CommonParser.scala index 2efb71d54..8c03a0c41 100644 --- a/language/src/main/scala/com/reactific/riddl/language/parsing/CommonParser.scala +++ b/language/src/main/scala/com/reactific/riddl/language/parsing/CommonParser.scala @@ -5,13 +5,12 @@ */ package com.reactific.riddl.language.parsing - -import com.reactific.riddl.language.parsing.Terminals.* import com.reactific.riddl.language.AST.* import com.reactific.riddl.language.At import fastparse.* -import fastparse.ScalaWhitespace.* +import fastparse.MultiLineWhitespace.* +import java.lang.Character.{isLetter, isLetterOrDigit} import java.net.URI import java.nio.file.Files import scala.reflect.{ClassTag, classTag} @@ -22,7 +21,7 @@ private[parsing] trait CommonParser extends NoWhiteSpaceParsers { def author[u: P]: P[Author] = { P( - location ~ Keywords.author ~/ identifier ~ is ~ open ~ + location ~ Keywords.author ~/ identifier ~ Readability.is ~ open ~ (undefined( ( LiteralString(At(), ""), @@ -32,12 +31,12 @@ private[parsing] trait CommonParser extends NoWhiteSpaceParsers { Option.empty[java.net.URL] ) ) | - (Keywords.name ~ is ~ literalString ~ Keywords.email ~ is ~ - literalString ~ (Keywords.organization ~ is ~ literalString).? ~ - (Keywords.title ~ is ~ literalString).? ~ - (Keywords.url ~ is ~ httpUrl).?)) ~ close ~ briefly ~ description - ).map { case (loc, id, (name, email, org, title, url), brief, desc) => - Author(loc, id, name, email, org, title, url, brief, desc) + (Keywords.name ~ Readability.is ~ literalString ~ Keywords.email ~ Readability.is ~ + literalString ~ (Keywords.organization ~ Readability.is ~ literalString).? ~ + (Keywords.title ~ Readability.is ~ literalString).? ~ + (Keywords.url ~ Readability.is ~ httpUrl).?)) ~ close ~ briefly ~ description ~ comments + ).map { case (loc, id, (name, email, org, title, url), brief, description, comments) => + Author(loc, id, name, email, org, title, url, brief, description, comments) } } @@ -51,8 +50,7 @@ private[parsing] trait CommonParser extends NoWhiteSpaceParsers { def importDef[u: P]: P[DomainDefinition] = { P( - location ~ Keywords.import_ ~ Keywords.domain ~ identifier ~ - Readability.from ~ literalString + location ~ Keywords.import_ ~ Keywords.domain ~ identifier ~ Readability.from ~ literalString ).map { tuple => doImport(tuple._1, tuple._2, tuple._3) } } @@ -60,14 +58,16 @@ private[parsing] trait CommonParser extends NoWhiteSpaceParsers { P(Punctuation.undefinedMark./).map(_ => f) } + def whiteSpaceChar[u: P]: P[Unit] = { + CharIn(" \t\n") + } + def literalStrings[u: P]: P[Seq[LiteralString]] = { P(literalString.rep(1)) } - def markdownLines[u: P]: P[Seq[LiteralString]] = { + private def markdownLines[u: P]: P[Seq[LiteralString]] = { P(markdownLine.rep(1)) } - def as[u: P]: P[Unit] = { P(StringIn(Readability.as, Readability.by).?) } - def maybe[u: P](keyword: String): P[Unit] = P(keyword).? def docBlock[u: P]: P[Seq[LiteralString]] = { @@ -101,21 +101,27 @@ private[parsing] trait CommonParser extends NoWhiteSpaceParsers { } def briefly[u: P]: P[Option[LiteralString]] = { - P(StringIn(Keywords.brief, Keywords.briefly) ~ literalString).? - } + P(Keywords.briefly ~/ literalString) + }.? def description[u: P]: P[Option[Description]] = P( - StringIn(Keywords.described, Keywords.explained) ~/ - ((as ~ blockDescription) | (Readability.in ~ fileDescription) | + P(Keywords.described ~/ + ((Readability.byAs ~ blockDescription) | (Readability.in ~ fileDescription) | (Readability.at ~ urlDescription)) - ).? + )).? + + def comments[u: P]: P[Seq[Comment]] = { + P( + location ~ "//" ~ toEndOfLine + ).rep(0).map { _.map(c => Comment(c._1, c._2)) } + } private def wholeNumber[u: P]: P[Long] = { CharIn("0-9").rep(1).!.map(_.toLong) } def integer[u: P]: P[Long] = { - StringIn(Operators.plus, Operators.minus).? ~ wholeNumber + CharIn("+\\-").? ~~ wholeNumber } private def simpleIdentifier[u: P]: P[String] = { @@ -123,7 +129,7 @@ private[parsing] trait CommonParser extends NoWhiteSpaceParsers { } private def quotedIdentifier[u: P]: P[String] = { - P("'" ~ CharsWhileIn("a-zA-Z0-9_+\\-|/@$%&, :", 1).! ~ "'") + P("'" ~~ CharsWhileIn("a-zA-Z0-9_+\\-|/@$%&, :", 1).! ~~ "'") } private def anyIdentifier[u: P]: P[String] = { @@ -131,28 +137,15 @@ private[parsing] trait CommonParser extends NoWhiteSpaceParsers { } def identifier[u: P]: P[Identifier] = { - P(location ~ anyIdentifier).map(tpl => (Identifier.apply _).tupled(tpl)) + P(location ~ anyIdentifier).map { case (loc, value) => Identifier(loc, value) } } def pathIdentifier[u: P]: P[PathIdentifier] = { - P(location ~ anyIdentifier ~ (Punctuation.dot ~ anyIdentifier).repX(0)).map { case (loc, first, strings) => + P(location ~ anyIdentifier ~~ (Punctuation.dot ~~ anyIdentifier).repX(0)).map { case (loc, first, strings) => PathIdentifier(loc, first +: strings) } } - def is[u: P]: P[Unit] = { - P( - StringIn( - Readability.is, - Readability.are, - Punctuation.colon, - Punctuation.equalsSign - ) - ).? - } - - def by[u: P]: P[Unit] = { P(StringIn(Readability.by, Readability.from)).? } - def open[u: P]: P[Unit] = { P(Punctuation.curlyOpen) } def close[u: P]: P[Unit] = { P(Punctuation.curlyClose) } @@ -224,52 +217,60 @@ private[parsing] trait CommonParser extends NoWhiteSpaceParsers { } def term[u: P]: P[Term] = { - P(location ~ Keywords.term ~ identifier ~ is ~ briefly ~ description)./.map(tpl => (Term.apply _).tupled(tpl)) + P(location ~ Keywords.term ~ identifier ~ Readability.is ~ briefly ~ description ~ comments)./.map(tpl => + (Term.apply _).tupled(tpl) + ) } def groupAliases[u: P]: P[String] = { P( - StringIn( - Keywords.group, - "page", - "pane", - "dialog", - "popup", - "frame", - "column", - "window", - "section", - "tab", - "flow" - ).! + Keywords.keywords( + StringIn( + Keyword.group, + "page", + "pane", + "dialog", + "popup", + "frame", + "column", + "window", + "section", + "tab", + "flow" + ).! + ) ) } def outputAliases[u: P]: P[String] = { P( - StringIn( - Keywords.output, - "document", - "list", - "table", - "graph", - "animation", - "picture" - ).! + Keywords.keywords( + StringIn( + Keyword.output, + "document", + "list", + "table", + "graph", + "animation", + "picture" + ).! + ) ) } def inputAliases[u: P]: P[String] = { P( - StringIn( - Keywords.input, - "form", - "text", - "button", - "picklist", - "selector", - "menu" - ).! + Keywords.keywords( + StringIn( + Keyword.input, + "form", + "text", + "button", + "picklist", + "selector", + "menu" + ).! + ) ) } diff --git a/language/src/main/scala/com/reactific/riddl/language/parsing/ContextParser.scala b/language/src/main/scala/com/reactific/riddl/language/parsing/ContextParser.scala index c49762918..4d38b21c3 100644 --- a/language/src/main/scala/com/reactific/riddl/language/parsing/ContextParser.scala +++ b/language/src/main/scala/com/reactific/riddl/language/parsing/ContextParser.scala @@ -7,9 +7,9 @@ package com.reactific.riddl.language.parsing import com.reactific.riddl.language.AST.* -import Terminals.* import fastparse.* -import fastparse.ScalaWhitespace.* +import fastparse.MultiLineWhitespace.* +import Readability.* /** Parsing rules for Context definitions */ private[parsing] trait ContextParser { @@ -26,20 +26,12 @@ private[parsing] trait ContextParser { with TypeParser => private def contextOptions[X: P]: P[Seq[ContextOption]] = { - options[X, ContextOption]( - StringIn( - Options.wrapper, - Options.gateway, - Options.service, - Options.package_, - Options.technology - ).! - ) { - case (loc, Options.wrapper, _) => WrapperOption(loc) - case (loc, Options.gateway, _) => GatewayOption(loc) - case (loc, Options.service, _) => ServiceOption(loc) - case (loc, Options.package_, args) => ContextPackageOption(loc, args) - case (loc, Options.technology, args) => ContextTechnologyOption(loc, args) + options[X, ContextOption](RiddlOptions.contextOptions) { + case (loc, RiddlOption.wrapper, _) => WrapperOption(loc) + case (loc, RiddlOption.gateway, _) => GatewayOption(loc) + case (loc, RiddlOption.service, _) => ServiceOption(loc) + case (loc, RiddlOption.package_, args) => ContextPackageOption(loc, args) + case (loc, RiddlOption.technology, args) => ContextTechnologyOption(loc, args) } } @@ -49,9 +41,9 @@ private[parsing] trait ContextParser { private def replica[x: P]: P[Replica] = { P( - location ~ Keywords.replica ~ identifier ~ is ~ replicaTypeExpression ~ briefly ~ description - ).map { case (loc, id, typeExp, brief, description) => - Replica(loc, id, typeExp, brief, description) + location ~ Keywords.replica ~ identifier ~ is ~ replicaTypeExpression ~ briefly ~ description ~ comments + ).map { case (loc, id, typeExp, brief, description, comments) => + Replica(loc, id, typeExp, brief, description, comments) } } private def contextDefinitions[u: P]: P[Seq[ContextDefinition]] = { @@ -71,8 +63,8 @@ private[parsing] trait ContextParser { def context[u: P]: P[Context] = { P( location ~ Keywords.context ~/ identifier ~ authorRefs ~ is ~ open ~ - contextOptions ~ contextBody ~ close ~ briefly ~ description - ).map { case (loc, id, authors, options, definitions, brief, description) => + contextOptions ~ contextBody ~ close ~ briefly ~ description ~ comments + ).map { case (loc, id, authors, options, definitions, brief, description, comments) => val groups = definitions.groupBy(_.getClass) val types = mapTo[Type](groups.get(classOf[Type])) val constants = mapTo[Constant](groups.get(classOf[Constant])) @@ -118,7 +110,8 @@ private[parsing] trait ContextParser { replicas, authors, brief, - description + description, + comments ) } } diff --git a/language/src/main/scala/com/reactific/riddl/language/parsing/DomainParser.scala b/language/src/main/scala/com/reactific/riddl/language/parsing/DomainParser.scala index 7a0d285f6..4ed115497 100644 --- a/language/src/main/scala/com/reactific/riddl/language/parsing/DomainParser.scala +++ b/language/src/main/scala/com/reactific/riddl/language/parsing/DomainParser.scala @@ -6,12 +6,12 @@ package com.reactific.riddl.language.parsing -import com.reactific.riddl.language.AST.* import com.reactific.riddl.language.AST -import Terminals.* +import com.reactific.riddl.language.AST.* +import Readability.* import fastparse.* -import fastparse.ScalaWhitespace.* +import fastparse.MultiLineWhitespace.* /** Parsing rules for domains. */ private[parsing] trait DomainParser { @@ -26,9 +26,9 @@ private[parsing] trait DomainParser { with CommonParser => private def domainOptions[X: P]: P[Seq[DomainOption]] = { - options[X, DomainOption](StringIn(Options.package_, Options.technology).!) { - case (loc, Options.package_, args) => DomainPackageOption(loc, args) - case (loc, Options.technology, args) => DomainTechnologyOption(loc, args) + options[X, DomainOption](StringIn(RiddlOption.package_, RiddlOption.technology).!) { + case (loc, RiddlOption.package_, args) => DomainPackageOption(loc, args) + case (loc, RiddlOption.technology, args) => DomainTechnologyOption(loc, args) } } @@ -38,10 +38,10 @@ private[parsing] trait DomainParser { private def user[u: P]: P[User] = { P( - location ~ Keywords.user ~ identifier ~/ is ~ literalString ~ briefly ~ - description - ).map { case (loc, id, is_a, brief, description) => - User(loc, id, is_a, brief, description) + location ~ Keywords.user ~ identifier ~/ is ~ literalString ~/ briefly ~/ + description ~ comments + ).map { case (loc, id, is_a, brief, description, comments) => + User(loc, id, is_a, brief, description, comments) } } @@ -58,10 +58,10 @@ private[parsing] trait DomainParser { def domain[u: P]: P[Domain] = { P( - location ~ Keywords.domain ~/ identifier ~ authorRefs ~ is ~ open ~/ - domainOptions ~ domainBody ~ close ~/ - briefly ~ description - ).map { case (loc, id, authorRefs, options, defs, briefly, description) => + location ~ Keywords.domain ~/ identifier ~/ authorRefs ~ is ~ open ~/ + domainOptions ~/ domainBody ~ close ~/ + briefly ~ description ~ comments + ).map { case (loc, id, authorRefs, options, defs, brief, description, comments) => val groups = defs.groupBy(_.getClass) val authors = mapTo[Author](groups.get(classOf[Author])) val subdomains = mapTo[Domain](groups.get(classOf[Domain])) @@ -94,8 +94,9 @@ private[parsing] trait DomainParser { subdomains, terms, includes, - briefly, - description + brief, + description, + comments ) } } diff --git a/language/src/main/scala/com/reactific/riddl/language/parsing/EntityParser.scala b/language/src/main/scala/com/reactific/riddl/language/parsing/EntityParser.scala index 84dacb210..a7bdc6feb 100644 --- a/language/src/main/scala/com/reactific/riddl/language/parsing/EntityParser.scala +++ b/language/src/main/scala/com/reactific/riddl/language/parsing/EntityParser.scala @@ -8,8 +8,8 @@ package com.reactific.riddl.language.parsing import com.reactific.riddl.language.AST.* import fastparse.* -import fastparse.ScalaWhitespace.* -import Terminals.* +import fastparse.MultiLineWhitespace.* +import Readability.* /** Parsing rules for entity definitions */ private[parsing] trait EntityParser { @@ -22,32 +22,19 @@ private[parsing] trait EntityParser { private def entityOptions[X: P]: P[Seq[EntityOption]] = { options[X, EntityOption]( - StringIn( - Options.eventSourced, - Options.value, - Options.aggregate, - Options.transient, - Options.consistent, - Options.available, - Options.finiteStateMachine, - Options.kind, - Options.messageQueue, - Options.device, - Options.technology - ).! + RiddlOptions.entityOptions ) { - case (loc, Options.eventSourced, _) => EntityEventSourced(loc) - case (loc, Options.value, _) => EntityValueOption(loc) - case (loc, Options.aggregate, _) => EntityIsAggregate(loc) - case (loc, Options.transient, _) => EntityTransient(loc) - case (loc, Options.consistent, _) => EntityIsConsistent(loc) - case (loc, Options.available, _) => EntityIsAvailable(loc) - case (loc, Options.`finiteStateMachine`, _) => - EntityIsFiniteStateMachine(loc) - case (loc, Options.kind, args) => EntityKind(loc, args) - case (loc, Options.messageQueue, _) => EntityMessageQueue(loc) - case (loc, Options.device, _) => EntityIsDevice(loc) - case (loc, Options.technology, args) => EntityTechnologyOption(loc, args) + case (loc, RiddlOption.event_sourced, _) => EntityEventSourced(loc) + case (loc, RiddlOption.value, _) => EntityValueOption(loc) + case (loc, RiddlOption.aggregate, _) => EntityIsAggregate(loc) + case (loc, RiddlOption.transient, _) => EntityTransient(loc) + case (loc, RiddlOption.consistent, _) => EntityIsConsistent(loc) + case (loc, RiddlOption.available, _) => EntityIsAvailable(loc) + case (loc, RiddlOption.finite_state_machine, _) => EntityIsFiniteStateMachine(loc) + case (loc, RiddlOption.kind, args) => EntityKind(loc, args) + case (loc, RiddlOption.message_queue, _) => EntityMessageQueue(loc) + case (loc, RiddlOption.device, _) => EntityIsDevice(loc) + case (loc, RiddlOption.technology, args) => EntityTechnologyOption(loc, args) } } @@ -63,16 +50,16 @@ private[parsing] trait EntityParser { P( location ~ Keywords.state ~ identifier ~/ Readability.of ~ typeRef ~/ is ~ (open ~ stateBody ~ close).? ~/ - briefly ~ description - )./.map { case (loc, id, typRef, body, brief, desc) => + briefly ~ description ~ comments + )./.map { case (loc, id, typRef, body, brief, description, comments) => body match { case Some(defs) => val groups = defs.groupBy(_.getClass) val handlers = mapTo[Handler](groups.get(classOf[Handler])) val invariants = mapTo[Invariant](groups.get(classOf[Invariant])) - State(loc, id, typRef, handlers, invariants, brief, desc) + State(loc, id, typRef, handlers, invariants, brief, description, comments) case None => - State(loc, id, typRef, brief = brief, description = desc) + State(loc, id, typRef, brief = brief, description, comments) } } } @@ -97,8 +84,8 @@ private[parsing] trait EntityParser { def entity[u: P]: P[Entity] = { P( location ~ Keywords.entity ~/ identifier ~ authorRefs ~ is ~ open ~/ - entityOptions ~ entityBody ~ close ~ briefly ~ description - ).map { case (loc, id, authors, options, entityDefs, brief, description) => + entityOptions ~ entityBody ~ close ~ briefly ~ description ~ comments + ).map { case (loc, id, authors, options, entityDefs, brief, description, comments) => val groups = entityDefs.groupBy(_.getClass) val types = mapTo[Type](groups.get(classOf[Type])) val constants = mapTo[Constant](groups.get(classOf[Constant])) @@ -130,7 +117,8 @@ private[parsing] trait EntityParser { authors, terms, brief, - description + description, + comments ) } } diff --git a/language/src/main/scala/com/reactific/riddl/language/parsing/EpicParser.scala b/language/src/main/scala/com/reactific/riddl/language/parsing/EpicParser.scala index 6288c8da2..6086f19f7 100644 --- a/language/src/main/scala/com/reactific/riddl/language/parsing/EpicParser.scala +++ b/language/src/main/scala/com/reactific/riddl/language/parsing/EpicParser.scala @@ -8,8 +8,8 @@ package com.reactific.riddl.language.parsing import com.reactific.riddl.language.AST.* import fastparse.* -import fastparse.ScalaWhitespace.* -import Terminals.* +import fastparse.MultiLineWhitespace.* +import Readability.* import java.net.URL private[parsing] trait EpicParser { @@ -23,79 +23,91 @@ private[parsing] trait EpicParser { private def vagueStep[u: P]: P[VagueInteraction] = { P( - location ~ Keywords.step ~ optionalIdentifier("") ~ is ~ literalString ~/ briefly ~ description - )./.map { case (loc, id, ls, brief, desc) => - VagueInteraction(loc, id, ls, brief, desc) + location ~ optionalIdentifier("") ~ is ~ literalString ~/ briefly ~ description ~ comments + )./.map { case (loc, id, relationship, brief, description, comments) => + VagueInteraction(loc, id, relationship, brief, description, comments) } } private def arbitraryStep[u: P]: P[ArbitraryInteraction] = { P( - location ~ Keywords.step ~ optionalIdentifier(Readability.from) ~/ anyInteractionRef ~ - literalString ~ Readability.to.? ~ anyInteractionRef ~/ briefly ~ description - )./.map { case (loc, id, from, ls, to, brief, desc) => - ArbitraryInteraction(loc, id, from, ls, to, brief, desc) + location ~ optionalIdentifier(Keyword.from) ~/ anyInteractionRef ~ + literalString ~ Readability.to.? ~ anyInteractionRef ~/ briefly ~ description ~ comments + )./.map { case (loc, id, from, ls, to, brief, desc, comments) => + ArbitraryInteraction(loc, id, from, ls, to, brief, desc, comments) } } private def selfProcessingStep[u: P]: P[SelfInteraction] = { P( - location ~ Keywords.step ~ optionalIdentifier(Readability.for_) ~/ - anyInteractionRef ~ is ~ literalString ~/ briefly ~ description - )./.map { case (loc, id, fromTo, proc, brief, desc) => - SelfInteraction(loc, id, fromTo, proc, brief, desc) + location ~ optionalIdentifier(Keyword.for_) ~/ + anyInteractionRef ~ is ~ literalString ~/ briefly ~ description ~ comments + )./.map { case (loc, id, fromTo, proc, brief, description, comments) => + SelfInteraction(loc, id, fromTo, proc, brief, description, comments) } } private def showOutputStep[u: P]: P[ShowOutputInteraction] = { P( - location ~ Keywords.step ~ optionalIdentifier(Keywords.show) ~/ - outputRef ~ Readability.to ~ userRef ~/ briefly ~ description - )./.map { case (loc, id, outRef, userRef, brief, desc) => - ShowOutputInteraction(loc, id, outRef, LiteralString.empty, userRef, brief, desc) + location ~ optionalIdentifier(Keyword.show) ~/ + outputRef ~ Readability.to ~ userRef ~/ briefly ~ description ~ comments + )./.map { case (loc, id, from, to, brief, description, comments) => + ShowOutputInteraction(loc, id, from, LiteralString.empty, to, brief, description, comments) } } private def takeInputStep[u: P]: P[TakeInputInteraction] = { P( - location ~ Keywords.step ~ optionalIdentifier(Keywords.take) ~/ - inputRef ~ Readability.from ~ userRef ~/ briefly ~ description - )./.map { case (loc, id, input, user, brief, desc) => - TakeInputInteraction(loc, id, from = user, relationship = LiteralString.empty, to = input, brief, desc) + location ~ optionalIdentifier(Keyword.take) ~/ + inputRef ~ Readability.from ~ userRef ~/ briefly ~ description ~ comments + )./.map { case (loc, id, input, user, brief, description, comments) => + TakeInputInteraction( + loc, + id, + from = user, + relationship = LiteralString.empty, + to = input, + brief, + description, + comments + ) } } + private def stepInteractions[u: P]: P[Interaction] = { + P(Keywords.step ~ (takeInputStep | showOutputStep | selfProcessingStep | arbitraryStep | vagueStep)) + } + private def sequentialInteractions[u: P]: P[SequentialInteractions] = { P( - location ~ Keywords.sequence./ ~ identifier.? ~ open ~ interactions ~ close ~ - briefly ~ description - )./.map { case (loc, id, steps, brief, desc) => - SequentialInteractions(loc, id.getOrElse(Identifier.empty), steps, brief, desc) + location ~ Keywords.sequence ~ identifier.? ~ open ~ interactions ~ close ~ + briefly ~ description ~ comments + )./.map { case (loc, id, steps, brief, description, comments) => + SequentialInteractions(loc, id.getOrElse(Identifier.empty), steps, brief, description, comments) } } private def optionalInteractions[u: P]: P[OptionalInteractions] = { P( - location ~ Keywords.optional./ ~ identifier.? ~/ open ~ interactions ~ close ~ - briefly ~ description - )./.map { case (loc, id, steps, brief, desc) => - OptionalInteractions(loc, id.getOrElse(Identifier.empty), steps, brief, desc) + location ~ Keywords.optional ~ identifier.? ~/ open ~ interactions ~ close ~ + briefly ~ description ~ comments + )./.map { case (loc, id, steps, brief, description, comments) => + OptionalInteractions(loc, id.getOrElse(Identifier.empty), steps, brief, description, comments) } } private def parallelInteractions[u: P]: P[ParallelInteractions] = { P( - location ~ Keywords.parallel./ ~ identifier.? ~/ open ~ - interactions ~ close ~ briefly ~ description - )./.map { case (loc, id, steps, brief, desc) => - ParallelInteractions(loc, id.getOrElse(Identifier.empty), steps, brief, desc) + location ~ Keywords.parallel ~ identifier.? ~/ open ~ + interactions ~ close ~ briefly ~ description ~ comments + )./.map { case (loc, id, steps, brief, description, comments) => + ParallelInteractions(loc, id.getOrElse(Identifier.empty), steps, brief, description, comments) } } private def interactions[u: P]: P[Seq[Interaction]] = { P( - parallelInteractions | optionalInteractions | sequentialInteractions | - takeInputStep | showOutputStep | selfProcessingStep | arbitraryStep | vagueStep + parallelInteractions | optionalInteractions | sequentialInteractions | stepInteractions ).rep(0, Punctuation.comma./) } @@ -106,9 +118,9 @@ private[parsing] trait EpicParser { Option.empty[UserStory], Seq.empty[GenericInteraction] ) | (userStory.? ~ interactions)) ~ - close ~ briefly ~ description - ).map { case (loc, id, (userStory, steps), brief, description) => - UseCase(loc, id, userStory, steps, brief, description) + close ~ briefly ~ description ~ comments + ).map { case (loc, id, (userStory, contents), brief, description, comments) => + UseCase(loc, id, userStory, contents, brief, description, comments) } } @@ -129,9 +141,9 @@ private[parsing] trait EpicParser { } private def epicOptions[u: P]: P[Seq[EpicOption]] = { - options[u, EpicOption](StringIn(Options.technology, Options.sync).!) { - case (loc, Options.sync, _) => EpicSynchronousOption(loc) - case (loc, Options.technology, args) => EpicTechnologyOption(loc, args) + options[u, EpicOption](StringIn(RiddlOption.technology, RiddlOption.sync).!) { + case (loc, RiddlOption.sync, _) => EpicSynchronousOption(loc) + case (loc, RiddlOption.technology, args) => EpicTechnologyOption(loc, args) } } @@ -166,7 +178,7 @@ private[parsing] trait EpicParser { P( location ~ Keywords.epic ~/ identifier ~ authorRefs ~ is ~ open ~ epicOptions ~ epicBody ~ close ~ - briefly ~ description + briefly ~ description ~ comments ).map { case ( loc, @@ -175,7 +187,8 @@ private[parsing] trait EpicParser { options, (userStory, shownBy, definitions), briefly, - description + description, + comments ) => val groups = definitions.groupBy(_.getClass) val terms = mapTo[Term](groups.get(classOf[Term])) @@ -197,7 +210,8 @@ private[parsing] trait EpicParser { options, terms, briefly, - description + description, + comments ) } } diff --git a/language/src/main/scala/com/reactific/riddl/language/parsing/FunctionParser.scala b/language/src/main/scala/com/reactific/riddl/language/parsing/FunctionParser.scala index daa100c2c..a080b80fd 100644 --- a/language/src/main/scala/com/reactific/riddl/language/parsing/FunctionParser.scala +++ b/language/src/main/scala/com/reactific/riddl/language/parsing/FunctionParser.scala @@ -8,8 +8,8 @@ package com.reactific.riddl.language.parsing import com.reactific.riddl.language.AST.* import fastparse.* -import fastparse.ScalaWhitespace.* -import Terminals.* +import fastparse.MultiLineWhitespace.* +import Readability.* /** Unit Tests For FunctionParser */ private[parsing] trait FunctionParser { @@ -17,8 +17,8 @@ private[parsing] trait FunctionParser { this: ReferenceParser with TypeParser with StatementParser with CommonParser => private def functionOptions[X: P]: P[Seq[FunctionOption]] = { - options[X, FunctionOption](StringIn(Options.tail_recursive).!) { - case (loc, Options.tail_recursive, _) => TailRecursive(loc) + options[X, FunctionOption](StringIn(RiddlOption.tail_recursive).!) { case (loc, RiddlOption.tail_recursive, _) => + TailRecursive(loc) } } @@ -46,7 +46,6 @@ private[parsing] trait FunctionParser { )./.rep(0) } - private def functionBody[u: P]: P[ ( Option[Aggregation], @@ -73,7 +72,7 @@ private[parsing] trait FunctionParser { def function[u: P]: P[Function] = { P( location ~ Keywords.function ~/ identifier ~ authorRefs ~ is ~ open ~ - functionOptions ~ functionBody ~ close ~ briefly ~ description + functionOptions ~ functionBody ~ close ~ briefly ~ description ~ comments )./.map { case ( loc, @@ -81,8 +80,9 @@ private[parsing] trait FunctionParser { authors, options, (input, output, definitions, statements), - briefly, - description + brief, + description, + comments ) => val groups = definitions.groupBy(_.getClass) val types = mapTo[Type](groups.get(classOf[Type])) @@ -105,8 +105,9 @@ private[parsing] trait FunctionParser { includes, options, terms, - briefly, - description + brief, + description, + comments ) } } diff --git a/language/src/main/scala/com/reactific/riddl/language/parsing/HandlerParser.scala b/language/src/main/scala/com/reactific/riddl/language/parsing/HandlerParser.scala index 32cfa4f0e..420e51673 100644 --- a/language/src/main/scala/com/reactific/riddl/language/parsing/HandlerParser.scala +++ b/language/src/main/scala/com/reactific/riddl/language/parsing/HandlerParser.scala @@ -8,30 +8,30 @@ package com.reactific.riddl.language.parsing import com.reactific.riddl.language.AST.* import fastparse.* -import fastparse.ScalaWhitespace.* -import Terminals.* +import fastparse.MultiLineWhitespace.* +import Readability.* private[parsing] trait HandlerParser { this: ReferenceParser with StatementParser with CommonParser => private def onOtherClause[u: P](set: StatementsSet): P[OnOtherClause] = { P( - location ~ Keywords.on ~ Keywords.other ~ is ~/ pseudoCodeBlock(set) ~ briefly ~ - description + location ~ Keywords.onOther ~ is ~/ pseudoCodeBlock(set) ~ briefly ~ + description ~ comments ).map(t => (OnOtherClause.apply _).tupled(t)) } private def onInitClause[u: P](set: StatementsSet): P[OnInitClause] = { P( - location ~ Keywords.on ~ Keywords.init ~ is ~/ pseudoCodeBlock(set) ~ briefly ~ - description + location ~ Keywords.onInit ~ is ~/ pseudoCodeBlock(set) ~ briefly ~ + description ~ comments ).map(t => (OnInitClause.apply _).tupled(t)) } private def onTermClause[u: P](set: StatementsSet): P[OnTerminationClause] = { P( - location ~ Keywords.on ~ Keywords.term ~ is ~/ pseudoCodeBlock(set) ~ - briefly ~ description + location ~ Keywords.onTerm ~ is ~/ pseudoCodeBlock(set) ~ + briefly ~ description ~ comments ).map(t => (OnTerminationClause.apply _).tupled(t)) } @@ -40,9 +40,9 @@ private[parsing] trait HandlerParser { } private def onMessageClause[u: P](set: StatementsSet): P[OnMessageClause] = { - location ~ Keywords.on ~/ messageRef ~ - (Readability.from./ ~ messageOrigins).? ~ is ~/ pseudoCodeBlock(set) ~ - briefly ~ description + location ~ Keywords.on ~ messageRef ~ + (Readability.from ~ messageOrigins).? ~ is ~/ pseudoCodeBlock(set) ~ + briefly ~ description ~ comments }.map(t => (OnMessageClause.apply _).tupled(t)) private def onClauses[u: P](set: StatementsSet): P[Seq[OnClause]] = { @@ -57,15 +57,16 @@ private[parsing] trait HandlerParser { P( Keywords.handler ~/ location ~ identifier ~ authorRefs ~ is ~ open ~ handlerBody(set) ~ - close ~ briefly ~ description - )./.map { case (loc, id, authors, clauses, briefly, desc) => + close ~ briefly ~ description ~ comments + )./.map { case (loc, id, authors, clauses, brief, description, comments) => Handler( loc, id, clauses, authors, - briefly, - desc + brief, + description, + comments ) } } diff --git a/language/src/main/scala/com/reactific/riddl/language/parsing/Keywords.scala b/language/src/main/scala/com/reactific/riddl/language/parsing/Keywords.scala new file mode 100644 index 000000000..45cc11595 --- /dev/null +++ b/language/src/main/scala/com/reactific/riddl/language/parsing/Keywords.scala @@ -0,0 +1,368 @@ +package com.reactific.riddl.language.parsing + +import fastparse.* +import MultiLineWhitespace.* + +import java.lang.Character.isLetterOrDigit + +/** Keywords must not be followed by other program text so ensure this happens + */ +object Keywords { + + private val keywordChars = (c: Char) => isLetterOrDigit(c) | c == '_' | c == '_' + + // Succeeds if the next character (look ahead without consuming) is not an + // identifier character. This is used with keywords to make sure the keyword + // isn't followed by keyword + private def isNotKeywordChar[u: P]: P[Unit] = { + !CharPred(keywordChars) | End + } + + def whiteSpaceChar[u: P]: P[Unit] = { + CharIn(" \t\n") + } + + def keyword[u: P](key: String): P[Unit] = { + P(key ~~/ (isNotKeywordChar | Fail(s"white space after keyword $key")))./ + } + + def keywords[u: P, T](keywordsRule: P[T]): P[T] = { + P(keywordsRule ~~/ (isNotKeywordChar | Fail(s"white space after a keyword")))./ + } + + def streamlets[u: P]: P[String] = keywords( + StringIn( + Keyword.source, + Keyword.sink, + Keyword.merge, + Keyword.split, + Keyword.void + ).! + ) + + def aggregateTypes[u: P]: P[String] = keywords( + StringIn( + Keyword.type_, + Keyword.command, + Keyword.query, + Keyword.event, + Keyword.result, + Keyword.record, + Keyword.other + ).! + ) + + def acquires[u: P]: P[Unit] = keyword(Keyword.acquires) + def adaptor[u: P]: P[Unit] = keyword(Keyword.adaptor) + def all[u: P]: P[Unit] = keyword(Keyword.all) + def any[u: P]: P[Unit] = keyword(Keyword.any) + def append[u: P]: P[Unit] = keyword(Keyword.append) + def application[u: P]: P[Unit] = keyword(Keyword.application) + def arbitrary[u: P]: P[Unit] = keyword(Keyword.arbitrary) + def author[u: P]: P[Unit] = keyword(Keyword.author) + def become[u: P]: P[Unit] = keyword(Keyword.become) + def benefit[u: P]: P[Unit] = keyword(Keyword.benefit) + def briefly[u: P]: P[Unit] = keywords(StringIn(Keyword.briefly, Keyword.brief)) + def body[u: P]: P[Unit] = keyword(Keyword.body) + def but[u: P]: P[Unit] = keyword(Keyword.but) + def call[u: P]: P[Unit] = keyword(Keyword.call) + def case_[u: P]: P[Unit] = keyword(Keyword.case_) + def capability[u: P]: P[Unit] = keyword(Keyword.capability) + def command[u: P]: P[Unit] = keyword(Keyword.command) + def commands[u: P]: P[Unit] = keyword(Keyword.commands) + def condition[u: P]: P[Unit] = keyword(Keyword.condition) + def connector[u: P]: P[Unit] = keyword(Keyword.connector) + def constant[u: P]: P[Unit] = keyword(Keyword.constant) + def container[u: P]: P[Unit] = keyword(Keyword.container) + def contains[u: P]: P[Unit] = keyword(Keyword.contains) + def context[u: P]: P[Unit] = keyword(Keyword.context) + def create[u: P]: P[Unit] = keyword(Keyword.create) + def described[u: P]: P[Unit] = keywords(StringIn(Keyword.described, Keyword.explained)) + def details[u: P]: P[Unit] = keyword(Keyword.details) + def do_[u: P]: P[Unit] = keyword(Keyword.do_) + def domain[u: P]: P[Unit] = keyword(Keyword.domain) + def each[u: P]: P[Unit] = keyword(Keyword.each) + def else_[u: P]: P[Unit] = keyword(Keyword.else_) + def email[u: P]: P[Unit] = keyword(Keyword.email) + def end_[u: P]: P[Unit] = keyword(Keyword.end_) + def entity[u: P]: P[Unit] = keyword(Keyword.entity) + def epic[u: P]: P[Unit] = keyword(Keyword.epic) + def error[u: P]: P[Unit] = keyword(Keyword.error) + def event[u: P]: P[Unit] = keyword(Keyword.event) + def example[u: P]: P[Unit] = keyword(Keyword.example) + def execute[u: P]: P[Unit] = keyword(Keyword.execute) + def explained[u: P]: P[Unit] = keyword(Keyword.explained) + def false_[u: P]: P[Unit] = keyword(Keyword.false_) + def field[u: P]: P[Unit] = keyword(Keyword.field) + def fields[u: P]: P[Unit] = keyword(Keyword.fields) + def file[u: P]: P[Unit] = keyword(Keyword.file) + def flow[u: P]: P[Unit] = keyword(Keyword.flow) + def flows[u: P]: P[Unit] = keyword(Keyword.flows) + def foreach[u: P]: P[Unit] = keyword(Keyword.foreach) + def form[u: P]: P[Unit] = keyword(Keyword.form) + def function[u: P]: P[Unit] = keyword(Keyword.function) + def given_[u: P]: P[Unit] = keyword(Keyword.given_) + def group[u: P]: P[Unit] = keyword(Keyword.group) + def handler[u: P]: P[Unit] = keyword(Keyword.handler) + def if_[u: P]: P[Unit] = keyword(Keyword.if_) + def import_[u: P]: P[Unit] = keyword(Keyword.import_) + def include[u: P]: P[Unit] = keyword(Keyword.include) + def insert[u: P]: P[Unit] = keyword(Keyword.insert) + def init[u: P]: P[Unit] = keyword(Keyword.init) + def inlet[u: P]: P[Unit] = keyword(Keyword.inlet) + def inlets[u: P]: P[Unit] = keyword(Keyword.inlets) + def input[u: P]: P[Unit] = keyword(Keyword.input) + def invariant[u: P]: P[Unit] = keyword(Keyword.invariant) + def items[u: P]: P[Unit] = keyword(Keyword.items) + def many[u: P]: P[Unit] = keyword(Keyword.many) + def mapping[u: P]: P[Unit] = keyword(Keyword.mapping) + def merge[u: P]: P[Unit] = keyword(Keyword.merge) + def message[u: P]: P[Unit] = keyword(Keyword.message) + def morph[u: P]: P[Unit] = keyword(Keyword.morph) + def name[u: P]: P[Unit] = keyword(Keyword.name) + def new_[u: P]: P[Unit] = keyword(Keyword.new_) + def on[u: P]: P[Unit] = keyword(Keyword.on) + def onInit[u: P]: P[Unit] = keyword("on init") + def onOther[u: P]: P[Unit] = keyword("on other") + def onTerm[u: P]: P[Unit] = keyword("on term") + def one[u: P]: P[Unit] = keyword(Keyword.one) + def organization[u: P]: P[Unit] = keyword(Keyword.organization) + def option[u: P]: P[Unit] = keyword(Keyword.option) + def optional[u: P]: P[Unit] = keyword(Keyword.optional) + def options[u: P]: P[Unit] = keyword(Keyword.options) + def other[u: P]: P[Unit] = keyword(Keyword.other) + def outlet[u: P]: P[Unit] = keyword(Keyword.outlet) + def outlets[u: P]: P[Unit] = keyword(Keyword.outlets) + def output[u: P]: P[Unit] = keyword(Keyword.output) + def parallel[u: P]: P[Unit] = keyword(Keyword.parallel) + def pipe[u: P]: P[Unit] = keyword(Keyword.pipe) + def plant[u: P]: P[Unit] = keyword(Keyword.plant) + def presents[u: P]: P[Unit] = keyword(Keyword.presents) + def projector[u: P]: P[Unit] = keyword(Keyword.projector) + def query[u: P]: P[Unit] = keyword(Keyword.query) + def range[u: P]: P[Unit] = keyword(Keyword.range) + def reference[u: P]: P[Unit] = keyword(Keyword.reference) + def remove[u: P]: P[Unit] = keyword(Keyword.remove) + def replica[u: P]: P[Unit] = keyword(Keyword.replica) + def reply[u: P]: P[Unit] = keyword(Keyword.reply) + def repository[u: P]: P[Unit] = keyword(Keyword.repository) + def requires[u: P]: P[Unit] = keyword(Keyword.requires) + def required[u: P]: P[Unit] = keyword(Keyword.required) + def record[u: P]: P[Unit] = keyword(Keyword.record) + def result[u: P]: P[Unit] = keyword(Keyword.result) + def results[u: P]: P[Unit] = keyword(Keyword.results) + def return_[u: P]: P[Unit] = keyword(Keyword.return_) + def returns[u: P]: P[Unit] = keyword(Keyword.returns) + def reverted[u: P]: P[Unit] = keyword(Keyword.reverted) + def role[u: P]: P[Unit] = keyword(Keyword.role) + def router[u: P]: P[Unit] = keyword(Keyword.router) + def saga[u: P]: P[Unit] = keyword(Keyword.saga) + def scenario[u: P]: P[Unit] = keyword(Keyword.scenario) + def see[u: P]: P[Unit] = keyword(Keyword.see) + def send[u: P]: P[Unit] = keyword(Keyword.send) + def sequence[u: P]: P[Unit] = keyword(Keyword.sequence) + def set[u: P]: P[Unit] = keyword(Keyword.set) + def show[u: P]: P[Unit] = keyword(Keyword.show) + def shown[u: P]: P[Unit] = keyword(Keyword.shown) + def sink[u: P]: P[Unit] = keyword(Keyword.sink) + def source[u: P]: P[Unit] = keyword(Keyword.source) + def split[u: P]: P[Unit] = keyword(Keyword.split) + def state[u: P]: P[Unit] = keyword(Keyword.state) + def step[u: P]: P[Unit] = keyword(Keyword.step) + def stop[u: P]: P[Unit] = keyword(Keyword.stop) + def story[u: P]: P[Unit] = keyword(Keyword.story) + def streamlet[u: P]: P[Unit] = keyword(Keyword.streamlet) + def take[u: P]: P[Unit] = keyword(Keyword.take) + def tell[u: P]: P[Unit] = keyword(Keyword.tell) + def term[u: P]: P[Unit] = keyword(Keyword.term) + def then_[u: P]: P[Unit] = keyword(Keyword.then_) + def title[u: P]: P[Unit] = keyword(Keyword.title) + def transmit[u: P]: P[Unit] = keyword(Keyword.transmit) + def true_[u: P]: P[Unit] = keyword(Keyword.true_) + def type_[u: P]: P[Unit] = keyword(Keyword.type_) + def url[u: P]: P[Unit] = keyword(Keyword.url) + def user[u: P]: P[Unit] = keyword(Keyword.user) + def value[u: P]: P[Unit] = keyword(Keyword.value) + def view[u: P]: P[Unit] = keyword(Keyword.view) + def void[u: P]: P[Unit] = keyword(Keyword.void) + def when[u: P]: P[Unit] = keyword(Keyword.when) + def yields[u: P]: P[Unit] = keyword(Keyword.yields) + + final val definition_keywords: Seq[P[Unit] => P[Unit]] = Seq( + adaptor, + application, + author, + case_, + command, + connector, + constant, + context, + entity, + epic, + field, + flow, + function, + group, + handler, + inlet, + input, + invariant, + outlet, + output, + pipe, + projector, + query, + replica, + reply, + repository, + record, + result, + saga, + sink, + source, + state, + streamlet, + term, + user + ) + +} + +object Keyword { + final val acquires = "acquires" + final val adaptor = "adaptor" + final val all = "all" + final val any = "any" + final val append = "append" + final val application = "application" + final val arbitrary = "arbitrary" + final val author = "author" + final val become = "become" + final val benefit = "benefit" + final val brief = "brief" + final val briefly = "briefly" + final val body = "body" + final val but = "but" + final val call = "call" + final val case_ = "case" + final val capability = "capability" + final val command = "command" + final val commands = "commands" + final val condition = "condition" + final val connector = "connector" + final val constant = "constant" + final val container = "container" + final val contains = "contains" + final val context = "context" + final val create = "create" + final val described = "described" + final val design = "storyCase" + final val details = "details" + final val presents = "presents" + final val do_ = "do" + final val domain = "domain" + final val each = "each" + final val else_ = "else" + final val email = "email" + final val end_ = "end" + final val entity = "entity" + final val epic = "epic" + final val error = "error" + final val event = "event" + final val example = "example" + final val execute = "execute" + final val explained = "explained" + final val false_ = "false" + final val field = "field" + final val fields = "fields" + final val file = "file" + final val flow = "flow" + final val flows = "flows" + final val for_ = "for" + final val foreach = "foreach" + final val form = "form" + final val from = "from" + final val function = "function" + final val given_ = "given" + final val group = "group" + final val handler = "handler" + final val if_ = "if" + final val import_ = "import" + final val include = "include" + final val insert = "insert" + final val init = "init" + final val inlet = "inlet" + final val inlets = "inlets" + final val input = "input" + final val invariant = "invariant" + final val items = "items" + final val many = "many" + final val mapping = "mapping" + final val merge = "merge" + final val message = "message" + final val morph = "morph" + final val name = "name" + final val new_ = "new" + final val on = "on" + final val one = "one" + final val organization = "organization" + final val option = "option" + final val optional = "optional" + final val options = "options" + final val other = "other" + final val outlet = "outlet" + final val outlets = "outlets" + final val output = "output" + final val parallel = "parallel" + final val pipe = "pipe" + final val plant = "plant" + final val projector = "projector" + final val query = "query" + final val range = "range" + final val reference = "reference" + final val remove = "remove" + final val replica = "replica" + final val reply = "reply" + final val repository = "repository" + final val requires = "requires" + final val required = "required" + final val record = "record" + final val result = "result" + final val results = "results" + final val return_ = "return" + final val returns = "returns" + final val reverted = "reverted" + final val role = "role" + final val router = "router" + final val saga = "saga" + final val scenario = "scenario" + final val see = "see" + final val send = "send" + final val sequence = "sequence" + final val set = "set" + final val show = "show" + final val shown = "shown" + final val sink = "sink" + final val source = "source" + final val split = "split" + final val state = "state" + final val step = "step" + final val stop = "stop" + final val story = "story" + final val streamlet = "streamlet" + final val take = "take" + final val tell = "tell" + final val term = "term" + final val then_ = "then" + final val title = "title" + final val transmit = "transmit" + final val true_ = "true" + final val type_ = "type" + final val url = "url" + final val user = "user" + final val value = "final value" + final val view = "view" + final val void = "void" + final val when = "when" + final val yields = "yields" +} diff --git a/language/src/main/scala/com/reactific/riddl/language/parsing/NoWhiteSpaceParsers.scala b/language/src/main/scala/com/reactific/riddl/language/parsing/NoWhiteSpaceParsers.scala index c9344e0e7..9ed73521e 100644 --- a/language/src/main/scala/com/reactific/riddl/language/parsing/NoWhiteSpaceParsers.scala +++ b/language/src/main/scala/com/reactific/riddl/language/parsing/NoWhiteSpaceParsers.scala @@ -5,25 +5,24 @@ */ package com.reactific.riddl.language.parsing - -import com.reactific.riddl.language.parsing.Terminals.* -import com.reactific.riddl.language.AST.{LiteralString} +import com.reactific.riddl.language.AST.{Comment, LiteralString} import fastparse.* import fastparse.NoWhitespace.* + import java.lang.Character.isISOControl /** Parser rules that should not collect white space */ private[parsing] trait NoWhiteSpaceParsers extends ParsingContext { - def line[u:P]: P[String] = { + def toEndOfLine[u:P]: P[String] = { P( - CharsWhile(ch => ch != '\n' && ch != '\r').! ~~ ("\n" | "\r" ~~ "\n").rep(min = 1, max = 2) + CharsWhile(ch => ch != '\n' && ch != '\r').! ) } def markdownLine[u: P]: P[LiteralString] = { P( - location ~ Punctuation.verticalBar ~~ line + location ~ Punctuation.verticalBar ~~ toEndOfLine ).map(tpl => (LiteralString.apply _).tupled(tpl)) } diff --git a/language/src/main/scala/com/reactific/riddl/language/parsing/ParsingContext.scala b/language/src/main/scala/com/reactific/riddl/language/parsing/ParsingContext.scala index 50aa1386a..714e5a3ea 100644 --- a/language/src/main/scala/com/reactific/riddl/language/parsing/ParsingContext.scala +++ b/language/src/main/scala/com/reactific/riddl/language/parsing/ParsingContext.scala @@ -116,13 +116,14 @@ trait ParsingContext { .map { case s: String if s.startsWith("char-pred") => "pattern" case s: String if s.startsWith("chars-with") => "pattern" + case s: String if s == "fail" => "whitespace after keyword" case s: String => s } .distinct .mkString("(", " | ", ")") } - private def makeParseFailureError(failure: Failure): Unit = { + def makeParseFailureError(failure: Failure): Unit = { val location = current.location(failure.index) val trace = failure.trace() val msg = trace.terminals.value.size match { @@ -134,17 +135,18 @@ trait ParsingContext { error(location, msg, context) } - private def makeParseFailureError(exception: Throwable): Unit = { + def makeParseFailureError(exception: Throwable): Unit = { val message = ExceptionUtils.getRootCauseStackTrace(exception).mkString("\n", "\n ", "\n") error(At.empty, message) } def expect[T <: RiddlNode]( - parser: P[?] => P[T] + parser: P[?] => P[T], + withVerboseFailures: Boolean = false ): Either[Messages, (T, RiddlParserInput)] = { val input = current try { - fastparse.parse[T](input, parser(_)) match { + fastparse.parse[T](input, parser(_), withVerboseFailures) match { case Success(content, _) => if errors.nonEmpty then Left(errors.toList) else Right(content -> input) @@ -161,11 +163,12 @@ trait ParsingContext { def expectMultiple[T <: Definition]( source: String, - parser: P[?] => P[Seq[T]] + parser: P[?] => P[Seq[T]], + withVerboseFailures: Boolean = false ): Either[Messages, (Seq[T], RiddlParserInput)] = { val input = current try { - fastparse.parse[Seq[T]](input, parser(_)) match { + fastparse.parse[Seq[T]](input, parser(_), withVerboseFailures) match { case Success(content, index) => if errors.nonEmpty then Left(errors.toList) else if content.isEmpty then diff --git a/language/src/main/scala/com/reactific/riddl/language/parsing/PredefTypes.scala b/language/src/main/scala/com/reactific/riddl/language/parsing/PredefTypes.scala new file mode 100644 index 000000000..d34ceaff2 --- /dev/null +++ b/language/src/main/scala/com/reactific/riddl/language/parsing/PredefTypes.scala @@ -0,0 +1,112 @@ +package com.reactific.riddl.language.parsing + +import fastparse.* +import MultiLineWhitespace.* +import com.reactific.riddl.language.parsing.Keywords.{keyword, keywords} + +object PredefTypes { + + def realTypes[u: P]: P[String] = keywords( + StringIn( + PredefType.Current, + PredefType.Length, + PredefType.Luminosity, + PredefType.Mass, + PredefType.Mole, + PredefType.Number, + PredefType.Real, + PredefType.Temperature + ).! + ) + + def integerTypes[u: P]: P[String] = keywords( + StringIn(PredefType.Boolean, PredefType.Integer, PredefType.Whole, PredefType.Natural).! + ) + + def timeTypes[u:P]: P[String] = keywords( + StringIn( + PredefType.Duration, + PredefType.DateTime, + PredefType.Date, + PredefType.TimeStamp, + PredefType.Time + ).! + ) + + def otherTypes[u:P]: P[String] = keywords( + StringIn( + // order matters in this list, because of common prefixes + PredefType.Abstract, + PredefType.Length, + PredefType.Location, + PredefType.Nothing, + PredefType.Number, + PredefType.UUID, + PredefType.UserId + ).! + ) + + def Abstract[u: P]: P[Unit] = keyword("Abstract") + def Boolean[u: P]: P[Unit] = keyword("Boolean") + def Current[u: P]: P[Unit] = keyword("Current") // in amperes + def Currency[u: P]: P[Unit] = keyword("Currency") // for some nation + def Date[u: P]: P[Unit] = keyword("Date") + def DateTime[u: P]: P[Unit] = keyword("DateTime") + def Decimal[u: P]: P[Unit] = keyword("Decimal") + def Duration[u: P]: P[Unit] = keyword("Duration") + def Id[u: P]: P[Unit] = keyword("Id") + def Integer[u: P]: P[Unit] = keyword("Integer") + def Location[u: P]: P[Unit] = keyword("Location") + def Length[u: P]: P[Unit] = keyword("Length") // in meters + def Luminosity[u: P]: P[Unit] = keyword("Luminosity") // in candelas + def Mass[u: P]: P[Unit] = keyword("Mass") // in kilograms + def Mole[u: P]: P[Unit] = keyword("Mole") // in mol (amount of substance) + def Nothing[u: P]: P[Unit] = keyword("Nothing") + def Natural[u: P]: P[Unit] = keyword("Natural") + def Number[u: P]: P[Unit] = keyword("Number") + def Pattern[u: P]: P[Unit] = keyword("Pattern") + def Range[u: P]: P[Unit] = keyword("Range") + def Real[u: P]: P[Unit] = keyword("Real") + def String_[u: P]: P[Unit] = keyword("String") + def Temperature[u: P]: P[Unit] = keyword("Temperature") // in Kelvin + def Time[u: P]: P[Unit] = keyword("Time") + def TimeStamp[u: P]: P[Unit] = keyword("TimeStamp") + def Unknown[u: P]: P[Unit] = keyword("Unknown") + def URL[u: P]: P[Unit] = keyword("URL") + def UserId[u: P]: P[Unit] = keyword("UserId") + def UUID[u: P]: P[Unit] = keyword("UUID") + def Whole[u: P]: P[Unit] = keyword("Whole") +} + +object PredefType { + final val Abstract = "Abstract" + final val Boolean = "Boolean" + final val Current = "Current" // in amperes + final val Currency = "Currency" // for some nation + final val Date = "Date" + final val DateTime = "DateTime" + final val Decimal = "Decimal" + final val Duration = "Duration" + final val Id = "Id" + final val Integer = "Integer" + final val Location = "Location" + final val Length = "Length" // in meters + final val Luminosity = "Luminosity" // in candelas + final val Mass = "Mass" // in kilograms + final val Mole = "Mole" // in mol (amount of substance) + final val Nothing = "Nothing" + final val Natural = "Natural" + final val Number = "Number" + final val Pattern = "Pattern" + final val Range = "Range" + final val Real = "Real" + final val String = "String" + final val Temperature = "Temperature" // in Kelvin + final val Time = "Time" + final val TimeStamp = "TimeStamp" + final val Unknown = "Unknown" + final val URL = "URL" + final val UserId = "UserId" + final val UUID = "UUID" + final val Whole = "Whole" +} diff --git a/language/src/main/scala/com/reactific/riddl/language/parsing/ProjectorParser.scala b/language/src/main/scala/com/reactific/riddl/language/parsing/ProjectorParser.scala index ec80e20fc..c36ac2f11 100644 --- a/language/src/main/scala/com/reactific/riddl/language/parsing/ProjectorParser.scala +++ b/language/src/main/scala/com/reactific/riddl/language/parsing/ProjectorParser.scala @@ -8,8 +8,8 @@ package com.reactific.riddl.language.parsing import com.reactific.riddl.language.AST.* import fastparse.* -import fastparse.ScalaWhitespace.* -import Terminals.* +import fastparse.MultiLineWhitespace.* +import Readability.* /** Unit Tests For FunctionParser */ private[parsing] trait ProjectorParser { @@ -21,9 +21,8 @@ private[parsing] trait ProjectorParser { with TypeParser => private def projectionOptions[u: P]: P[Seq[ProjectorOption]] = { - options[u, ProjectorOption](StringIn(Options.technology).!) { - case (loc, Options.technology, args) => - ProjectorTechnologyOption(loc, args) + options[u, ProjectorOption](StringIn(RiddlOption.technology).!) { case (loc, RiddlOption.technology, args) => + ProjectorTechnologyOption(loc, args) } } @@ -33,8 +32,8 @@ private[parsing] trait ProjectorParser { private def projectionDefinitions[u: P]: P[Seq[ProjectorDefinition]] = { P( - typeDef | term | projectionInclude | handler(StatementsSet.ProjectorStatements) | - function | inlet | outlet | invariant | constant | typeDef + typeDef | term | projectionInclude | handler(StatementsSet.ProjectorStatements) | + function | inlet | outlet | invariant | constant | typeDef )./.rep(1) } @@ -56,7 +55,7 @@ private[parsing] trait ProjectorParser { def projector[u: P]: P[Projector] = { P( location ~ Keywords.projector ~/ identifier ~ authorRefs ~ is ~ open ~ - projectionOptions ~ projectionBody ~ close ~ briefly ~ description + projectionOptions ~ projectionBody ~ close ~ briefly ~ description ~ comments ).map { case ( loc, @@ -65,7 +64,8 @@ private[parsing] trait ProjectorParser { options, definitions, brief, - description + description, + comments ) => val groups = definitions.groupBy(_.getClass) val types = mapTo[Type](groups.get(classOf[Type])) @@ -96,7 +96,8 @@ private[parsing] trait ProjectorParser { invariants, terms, brief, - description + description, + comments ) } } diff --git a/language/src/main/scala/com/reactific/riddl/language/parsing/Punctuation.scala b/language/src/main/scala/com/reactific/riddl/language/parsing/Punctuation.scala new file mode 100644 index 000000000..e095f1830 --- /dev/null +++ b/language/src/main/scala/com/reactific/riddl/language/parsing/Punctuation.scala @@ -0,0 +1,37 @@ +package com.reactific.riddl.language.parsing + +object Punctuation { + final val asterisk = "*" + final val at = "@" + final val comma = "," + final val colon = ":" + final val curlyOpen = "{" + final val curlyClose = "}" + final val dot = "." + final val equalsSign = "=" + final val ellipsis = "..." + final val ellipsisQuestion = "...?" + final val exclamation = "!" + final val plus = "+" + final val question = "?" + final val quote = "\"" + final val roundOpen = "(" + final val roundClose = ")" + final val undefinedMark = "???" + final val verticalBar = "|" + + val all: Seq[String] = Seq( + comma, + colon, + dot, + equalsSign, + quote, + quote, + curlyOpen, + curlyClose, + roundOpen, + roundClose, + undefinedMark, + verticalBar + ) +} diff --git a/language/src/main/scala/com/reactific/riddl/language/parsing/Readability.scala b/language/src/main/scala/com/reactific/riddl/language/parsing/Readability.scala new file mode 100644 index 000000000..1ae6e1694 --- /dev/null +++ b/language/src/main/scala/com/reactific/riddl/language/parsing/Readability.scala @@ -0,0 +1,37 @@ +package com.reactific.riddl.language.parsing + +import fastparse.* +import MultiLineWhitespace.* +import com.reactific.riddl.language.parsing.Keywords.keyword + +object Readability { + + def and[u: P]: P[Unit] = keyword("and") + def are[u: P]: P[Unit] = keyword("are") + def as[u: P]: P[Unit] = keyword("as") + def at[u: P]: P[Unit] = keyword("at") + def by[u: P]: P[Unit] = keyword("by") + def byAs[u: P]: P[Unit] = Keywords.keywords(StringIn("by", "as")) + def byFromAs[u: P]: P[Unit] = { Keywords.keywords(StringIn("by", "from", "as")).? } + + def for_[u: P]: P[Unit] = keyword("for") + def from[u: P]: P[Unit] = keyword("from") + def in[u: P]: P[Unit] = keyword("in") + + def is[u: P]: P[Unit] = { + Keywords + .keywords( + StringIn("is", "are", ":", "=") + ) + .? + } + + def of[u: P]: P[Unit] = keyword("of") + def on[u: P]: P[Unit] = keyword("on") + def so[u: P]: P[Unit] = keyword("so") + def that[u: P]: P[Unit] = keyword("that") + def to[u: P]: P[Unit] = keyword("to") + def wants[u: P]: P[Unit] = keyword("wants") + def with_[u: P]: P[Unit] = keyword("with") + +} diff --git a/language/src/main/scala/com/reactific/riddl/language/parsing/ReferenceParser.scala b/language/src/main/scala/com/reactific/riddl/language/parsing/ReferenceParser.scala index 7bba2d6e5..93e600084 100644 --- a/language/src/main/scala/com/reactific/riddl/language/parsing/ReferenceParser.scala +++ b/language/src/main/scala/com/reactific/riddl/language/parsing/ReferenceParser.scala @@ -9,8 +9,8 @@ package com.reactific.riddl.language.parsing import com.reactific.riddl.language.AST.* import fastparse.* import fastparse.StringIn -import fastparse.ScalaWhitespace.* -import Terminals.* +import fastparse.MultiLineWhitespace.* +import Readability.* private[parsing] trait ReferenceParser extends CommonParser { @@ -74,18 +74,11 @@ private[parsing] trait ReferenceParser extends CommonParser { def typeRef[u: P]: P[TypeRef] = { P( - location ~ - StringIn( - Keywords.`type`, - Keywords.command, - Keywords.query, - Keywords.event, - Keywords.result, - Keywords.record, - Keywords.other - ).? ~ pathIdentifier - ) - .map(tpl => (TypeRef.apply _).tupled(tpl)) + location ~ Keywords.aggregateTypes.? ~ pathIdentifier + ).map { + case (loc, Some(key), pid) => TypeRef(loc, key, pid) + case (loc, None, pid) => TypeRef(loc, "type", pid) + } } def fieldRef[u: P]: P[FieldRef] = { @@ -94,7 +87,7 @@ private[parsing] trait ReferenceParser extends CommonParser { } def constantRef[u: P]: P[ConstantRef] = { - P(location ~ Keywords.const ~ pathIdentifier) + P(location ~ Keywords.constant ~ pathIdentifier) .map(tpl => (ConstantRef.apply _).tupled(tpl)) } @@ -115,13 +108,7 @@ private[parsing] trait ReferenceParser extends CommonParser { private def streamletRef[u: P]: P[StreamletRef] = { P( - location ~ StringIn( - Keywords.source, - Keywords.sink, - Keywords.merge, - Keywords.split, - Keywords.void - ) ~ pathIdentifier + location ~ Keywords.streamlets ~ pathIdentifier ).map(tpl => (StreamletRef.apply _).tupled(tpl)) } diff --git a/language/src/main/scala/com/reactific/riddl/language/parsing/RepositoryParser.scala b/language/src/main/scala/com/reactific/riddl/language/parsing/RepositoryParser.scala index 6b4e29f63..7c550f82d 100644 --- a/language/src/main/scala/com/reactific/riddl/language/parsing/RepositoryParser.scala +++ b/language/src/main/scala/com/reactific/riddl/language/parsing/RepositoryParser.scala @@ -9,8 +9,8 @@ package com.reactific.riddl.language.parsing import com.reactific.riddl.language.AST.* import fastparse.* -import fastparse.ScalaWhitespace.* -import Terminals.* +import fastparse.MultiLineWhitespace.* +import Readability.* private[parsing] trait RepositoryParser { @@ -22,9 +22,8 @@ private[parsing] trait RepositoryParser { with TypeParser => private def repositoryOptions[u: P]: P[Seq[RepositoryOption]] = { - options[u, RepositoryOption](StringIn(Options.technology).!) { - case (loc, Options.technology, args) => - RepositoryTechnologyOption(loc, args) + options[u, RepositoryOption](StringIn(RiddlOption.technology).!) { case (loc, RiddlOption.technology, args) => + RepositoryTechnologyOption(loc, args) } } @@ -35,7 +34,7 @@ private[parsing] trait RepositoryParser { private def repositoryDefinitions[u: P]: P[Seq[RepositoryDefinition]] = { P( typeDef | handler(StatementsSet.RepositoryStatements) | - function | term | repositoryInclude | inlet | outlet | constant + function | term | repositoryInclude | inlet | outlet | constant ).rep(0) } @@ -44,8 +43,8 @@ private[parsing] trait RepositoryParser { location ~ Keywords.repository ~/ identifier ~ authorRefs ~ is ~ open ~ repositoryOptions ~ (undefined(Seq.empty[RepositoryDefinition]) | repositoryDefinitions) ~ - close ~ briefly ~ description - ).map { case (loc, id, authors, options, defs, brief, description) => + close ~ briefly ~ description ~ comments + ).map { case (loc, id, authors, options, defs, brief, description, comments) => val groups = defs.groupBy(_.getClass) val types = mapTo[Type](groups.get(classOf[Type])) val handlers = mapTo[Handler](groups.get(classOf[Handler])) @@ -76,7 +75,8 @@ private[parsing] trait RepositoryParser { options, terms, brief, - description + description, + comments ) } } diff --git a/language/src/main/scala/com/reactific/riddl/language/parsing/RiddlOptions.scala b/language/src/main/scala/com/reactific/riddl/language/parsing/RiddlOptions.scala new file mode 100644 index 000000000..57f429d5e --- /dev/null +++ b/language/src/main/scala/com/reactific/riddl/language/parsing/RiddlOptions.scala @@ -0,0 +1,88 @@ +package com.reactific.riddl.language.parsing + +import fastparse.* +import MultiLineWhitespace.* +import Keywords.{keyword, keywords} + +object RiddlOptions { + + def entityOptions[u: P]: P[String] = keywords( + StringIn( + RiddlOption.event_sourced, + RiddlOption.value, + RiddlOption.aggregate, + RiddlOption.transient, + RiddlOption.consistent, + RiddlOption.available, + RiddlOption.finite_state_machine, + RiddlOption.kind, + RiddlOption.message_queue, + RiddlOption.device, + RiddlOption.technology + ).! + ) + + def contextOptions[u: P]: P[String] = keywords( + StringIn( + RiddlOption.wrapper, + RiddlOption.gateway, + RiddlOption.service, + RiddlOption.package_, + RiddlOption.technology + ).! + ) + + def aggregate[u: P]: P[Unit] = keyword(RiddlOption.aggregate) + def async[u: P]: P[Unit] = keyword(RiddlOption.async) + def available[u: P]: P[Unit] = keyword(RiddlOption.available) + def concept[u: P]: P[Unit] = keyword(RiddlOption.concept) + def consistent[u: P]: P[Unit] = keyword(RiddlOption.consistent) + def device[u: P]: P[Unit] = keyword(RiddlOption.device) + def events_sourced[u: P]: P[Unit] = keyword(RiddlOption.event_sourced) + def finiteStateMachine[u: P]: P[Unit] = keyword(RiddlOption.finite_state_machine) + def gateway[u: P]: P[Unit] = keyword(RiddlOption.gateway) + def kind[u: P]: P[Unit] = keyword(RiddlOption.kind) + def message_queue[u: P]: P[Unit] = keyword(RiddlOption.message_queue) + def package_[u: P]: P[Unit] = keyword(RiddlOption.package_) + def parallel[u: P]: P[Unit] = keyword(RiddlOption.parallel) + def persistent[u: P]: P[Unit] = keyword(RiddlOption.persistent) + def reply[u: P]: P[Unit] = keyword(RiddlOption.reply) + def sequential[u: P]: P[Unit] = keyword(RiddlOption.sequential) + def service[u: P]: P[Unit] = keyword(RiddlOption.service) + def sync[u: P]: P[Unit] = keyword(RiddlOption.sync) + def value[u: P]: P[Unit] = keyword(RiddlOption.value) + def wrapper[u: P]: P[Unit] = keyword(RiddlOption.wrapper) + def tail_recursive[u: P]: P[Unit] = "tail-recursive" + def technology[u: P]: P[Unit] = keyword(RiddlOption.technology) + def transient[u: P]: P[Unit] = keyword(RiddlOption.transient) + def user[u: P]: P[Unit] = keyword(RiddlOption.user) + +} + +object RiddlOption { + final val aggregate = "aggregate" + final val async = "async" + final val available = "available" + final val concept = "concept" + final val consistent = "consistent" + final val device = "device" + final val event_sourced = "event-sourced" + final val finite_state_machine = "finite-state-machine" + final val gateway = "gateway" + final val kind = "kind" + final val message_queue = "message-queue" + final val package_ = "package" + final val parallel = "parallel" + final val persistent = "persistent" + final val reply = "reply" + final val sequential = "sequential" + final val service = "service" + final val sync = "sync" + final val value = "final value" + final val wrapper = "wrapper" + final val tail_recursive = "tail-recursive" + final val technology = "technology" + final val transient = "transient" + final val user = "user" + +} diff --git a/language/src/main/scala/com/reactific/riddl/language/parsing/SagaParser.scala b/language/src/main/scala/com/reactific/riddl/language/parsing/SagaParser.scala index 8e29a73d7..3b3fc425a 100644 --- a/language/src/main/scala/com/reactific/riddl/language/parsing/SagaParser.scala +++ b/language/src/main/scala/com/reactific/riddl/language/parsing/SagaParser.scala @@ -8,8 +8,8 @@ package com.reactific.riddl.language.parsing import com.reactific.riddl.language.AST.* import fastparse.* -import fastparse.ScalaWhitespace.* -import Terminals.* +import fastparse.MultiLineWhitespace.* +import Readability.* /** SagaParser Implements the parsing of saga definitions in context definitions. */ @@ -21,16 +21,15 @@ private[parsing] trait SagaParser { P( location ~ Keywords.step ~/ identifier ~ is ~ pseudoCodeBlock(StatementsSet.SagaStatements) ~ Keywords.reverted ~ Readability.by.? ~ pseudoCodeBlock(StatementsSet.SagaStatements) ~ - briefly ~ description + briefly ~ description ~ comments ).map(x => (SagaStep.apply _).tupled(x)) } private def sagaOptions[u: P]: P[Seq[SagaOption]] = { - options[u, SagaOption](StringIn(Options.parallel, Options.sequential).!) { - case (loc, option, _) if option == Options.parallel => ParallelOption(loc) - case (loc, option, _) if option == Options.sequential => - SequentialOption(loc) - case (loc, Options.technology, args) => SagaTechnologyOption(loc, args) + options[u, SagaOption](StringIn(RiddlOption.parallel, RiddlOption.sequential).!) { + case (loc, option, _) if option == RiddlOption.parallel => ParallelOption(loc) + case (loc, option, _) if option == RiddlOption.sequential => SequentialOption(loc) + case (loc, RiddlOption.technology, args) => SagaTechnologyOption(loc, args) } } @@ -58,7 +57,7 @@ private[parsing] trait SagaParser { def saga[u: P]: P[Saga] = { P( location ~ Keywords.saga ~ identifier ~ authorRefs ~ is ~ open ~ - sagaOptions ~ sagaBody ~ close ~ briefly ~ description + sagaOptions ~ sagaBody ~ close ~ briefly ~ description ~ comments ).map { case ( location, @@ -67,7 +66,8 @@ private[parsing] trait SagaParser { options, (input, output, definitions), briefly, - description + description, + comments ) => val groups = definitions.groupBy(_.getClass) val functions = mapTo[Function](groups.get(classOf[Function])) @@ -94,7 +94,8 @@ private[parsing] trait SagaParser { includes, terms, briefly, - description + description, + comments ) } } diff --git a/language/src/main/scala/com/reactific/riddl/language/parsing/StatementParser.scala b/language/src/main/scala/com/reactific/riddl/language/parsing/StatementParser.scala index a2cc8d652..2da0f7e96 100644 --- a/language/src/main/scala/com/reactific/riddl/language/parsing/StatementParser.scala +++ b/language/src/main/scala/com/reactific/riddl/language/parsing/StatementParser.scala @@ -6,11 +6,11 @@ package com.reactific.riddl.language.parsing -import com.reactific.riddl.language.parsing.Terminals.* import com.reactific.riddl.language.AST.* import com.reactific.riddl.language.At import fastparse.* -import fastparse.ScalaWhitespace.* +import fastparse.MultiLineWhitespace.* +import Readability.* /** StatementParser Define actions that various constructs can take for modelling behavior in a message-passing system */ @@ -149,9 +149,9 @@ private[parsing] trait StatementParser { P( Keywords.invariant ~/ location ~ identifier ~ is ~ ( undefined(Option.empty[LiteralString]) | literalString.map(Some(_)) - ) ~ briefly ~ description - ).map { case (loc, id, cond, brief, desc) => - Invariant(loc, id, cond, brief, desc) + ) ~ briefly ~ description ~ comments + ).map { case (loc, id, condition, brief, description, comments) => + Invariant(loc, id, condition, brief, description, comments) } } diff --git a/language/src/main/scala/com/reactific/riddl/language/parsing/StreamingParser.scala b/language/src/main/scala/com/reactific/riddl/language/parsing/StreamingParser.scala index 705496046..6a9728732 100644 --- a/language/src/main/scala/com/reactific/riddl/language/parsing/StreamingParser.scala +++ b/language/src/main/scala/com/reactific/riddl/language/parsing/StreamingParser.scala @@ -8,8 +8,8 @@ package com.reactific.riddl.language.parsing import com.reactific.riddl.language.AST.* import fastparse.* -import fastparse.ScalaWhitespace.* -import Terminals.* +import fastparse.MultiLineWhitespace.* +import Readability.* import com.reactific.riddl.language.At /** Unit Tests For StreamingParser */ @@ -19,24 +19,23 @@ private[parsing] trait StreamingParser { def inlet[u: P]: P[Inlet] = { P( location ~ Keywords.inlet ~ identifier ~ is ~ - typeRef ~/ briefly ~ description + typeRef ~/ briefly ~ description ~ comments )./.map { tpl => (Inlet.apply _).tupled(tpl) } } def outlet[u: P]: P[Outlet] = { P( location ~ Keywords.outlet ~ identifier ~ is ~ - typeRef ~/ briefly ~ description + typeRef ~/ briefly ~ description ~ comments )./.map { tpl => (Outlet.apply _).tupled(tpl) } } private def connectorOptions[X: P]: P[Seq[ConnectorOption]] = { options[X, ConnectorOption]( - StringIn(Options.persistent, Options.technology).! + StringIn(RiddlOption.persistent, RiddlOption.technology).! ) { - case (loc, Options.persistent, _) => ConnectorPersistentOption(loc) - case (loc, Options.technology, args) => - ConnectorTechnologyOption(loc, args) + case (loc, RiddlOption.persistent, _) => ConnectorPersistentOption(loc) + case (loc, RiddlOption.technology, args) => ConnectorTechnologyOption(loc, args) } } @@ -51,9 +50,9 @@ private[parsing] trait StreamingParser { Readability.to ~ inletRef ).map { case (typ, out, in) => (typ, Some(out), Some(in)) - }) ~ close ~/ briefly ~ description - )./.map { case (loc, id, opts, (typ, out, in), brief, desc) => - Connector(loc, id, opts, typ, out, in, brief, desc) + }) ~ close ~/ briefly ~ description ~ comments + )./.map { case (loc, id, opts, (typ, out, in), brief, description, comments) => + Connector(loc, id, opts, typ, out, in, brief, description, comments) } } @@ -86,7 +85,7 @@ private[parsing] trait StreamingParser { } private def streamletOptions[u: P]: P[Seq[StreamletOption]] = { - options[u, StreamletOption](StringIn(Options.technology).!) { case (loc, Options.technology, args) => + options[u, StreamletOption](StringIn(RiddlOption.technology).!) { case (loc, RiddlOption.technology, args) => StreamletTechnologyOption(loc, args) } } @@ -125,8 +124,8 @@ private[parsing] trait StreamingParser { P( location ~ keyword ~/ identifier ~ authorRefs ~ is ~ open ~ streamletOptions ~ streamletBody(minInlets, maxInlets, minOutlets, maxOutlets) ~ - close ~ briefly ~ description - )./.map { case (loc, id, authors, options, definitions, brief, description) => + close ~ briefly ~ description ~ comments + )./.map { case (loc, id, authors, options, definitions, brief, description, comments) => val shape = keywordToKind(keyword, loc) val groups = definitions.groupBy(_.getClass) val inlets = mapTo[Inlet](groups.get(classOf[Inlet])) @@ -154,7 +153,8 @@ private[parsing] trait StreamingParser { options, terms, brief, - description + description, + comments ) } } @@ -162,16 +162,16 @@ private[parsing] trait StreamingParser { private val MaxStreamlets = 1000 def source[u: P]: P[Streamlet] = { - streamletTemplate(Keywords.source, minOutlets = 1, maxOutlets = 1) + streamletTemplate(Keyword.source, minOutlets = 1, maxOutlets = 1) } def sink[u: P]: P[Streamlet] = { - streamletTemplate(Keywords.sink, minInlets = 1, maxInlets = 1) + streamletTemplate(Keyword.sink, minInlets = 1, maxInlets = 1) } def flow[u: P]: P[Streamlet] = { streamletTemplate( - Keywords.flow, + Keyword.flow, minInlets = 1, maxInlets = 1, minOutlets = 1, @@ -181,7 +181,7 @@ private[parsing] trait StreamingParser { def split[u: P]: P[Streamlet] = { streamletTemplate( - Keywords.split, + Keyword.split, minInlets = 1, maxInlets = 1, minOutlets = 2, @@ -191,7 +191,7 @@ private[parsing] trait StreamingParser { def merge[u: P]: P[Streamlet] = { streamletTemplate( - Keywords.merge, + Keyword.merge, minInlets = 2, maxInlets = MaxStreamlets, minOutlets = 1, @@ -201,7 +201,7 @@ private[parsing] trait StreamingParser { def router[u: P]: P[Streamlet] = { streamletTemplate( - Keywords.router, + Keyword.router, minInlets = 2, maxInlets = MaxStreamlets, minOutlets = 2, @@ -209,7 +209,7 @@ private[parsing] trait StreamingParser { ) } - def void[u: P]: P[Streamlet] = { streamletTemplate(Keywords.void) } + def void[u: P]: P[Streamlet] = { streamletTemplate(Keyword.void) } def streamlet[u: P]: P[Streamlet] = P(source | flow | sink | merge | split | router | void) diff --git a/language/src/main/scala/com/reactific/riddl/language/parsing/Terminals.scala b/language/src/main/scala/com/reactific/riddl/language/parsing/Terminals.scala deleted file mode 100644 index fd20cf45d..000000000 --- a/language/src/main/scala/com/reactific/riddl/language/parsing/Terminals.scala +++ /dev/null @@ -1,319 +0,0 @@ -/* - * Copyright 2019 Ossum, Inc. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package com.reactific.riddl.language.parsing - -/** Terminal symbol definitions in various categories */ - -object Terminals { - - object Punctuation { - final val asterisk = "*" - final val at = "@" - final val comma = "," - final val colon = ":" - final val curlyOpen = "{" - final val curlyClose = "}" - final val dot = "." - final val equalsSign = "=" - final val ellipsis = "..." - final val ellipsisQuestion = "...?" - final val exclamation = "!" - final val plus = "+" - final val question = "?" - final val quote = "\"" - final val roundOpen = "(" - final val roundClose = ")" - final val squareOpen = "[" - final val squareClose = "]" - final val undefinedMark = "???" - final val verticalBar = "|" - - val all: Seq[String] = Seq( - comma, - colon, - dot, - equalsSign, - quote, - quote, - curlyOpen, - curlyClose, - squareOpen, - squareClose, - roundOpen, - roundClose, - undefinedMark, - verticalBar - ) - } - - object Options { - final val aggregate = "aggregate" - final val async = "async" - final val available = "available" - final val concept = "concept" - final val consistent = "consistent" - final val device = "device" - final val eventSourced = "event-sourced" - final val finiteStateMachine = "fsm" - final val gateway = "gateway" - final val kind = "kind" - final val messageQueue = "mq" - final val package_ = "package" - final val parallel = "parallel" - final val persistent = "persistent" - final val reply = "reply" - final val sequential = "sequential" - final val service = "service" - final val sync = "sync" - final val value = "value" - final val wrapper = "wrapper" - final val tail_recursive = "tail-recursive" - final val technology = "technology" - final val transient = "transient" - final val user = "user" - } - - object Keywords { - final val acquires = "acquires" - final val adaptor = "adaptor" - final val all = "all" - final val any = "any" - final val append = "append" - final val application = "application" - final val arbitrary = "arbitrary" - final val author = "author" - final val become = "become" - final val benefit = "benefit" - final val brief = "brief" - final val briefly = "briefly" - final val body = "body" - final val but = "but" - final val call = "call" - final val case_ = "case" - final val capability = "capability" - final val command = "command" - final val commands = "commands" - final val condition = "condition" - final val connector = "connector" - final val const = "constant" - final val container = "container" - final val contains = "contains" - final val context = "context" - final val create = "create" - final val described = "described" - final val design = "storyCase" - final val details = "details" - final val presents = "presents" - final val do_ = "do" - final val domain = "domain" - final val each = "each" - final val else_ = "else" - final val email = "email" - final val end_ = "end" - final val entity = "entity" - final val epic = "epic" - final val error = "error" - final val event = "event" - final val example = "example" - final val execute = "execute" - final val explained = "explained" - final val false_ = "false" - final val field = "field" - final val fields = "fields" - final val file = "file" - final val flow = "flow" - final val flows = "flows" - final val foreach = "foreach" - final val form = "form" - final val function = "function" - final val given_ = "given" - final val group = "group" - final val handler = "handler" - final val if_ = "if" - final val import_ = "import" - final val include = "include" - final val insert = "insert" - final val init = "init" - final val inlet = "inlet" - final val inlets = "inlets" - final val input = "input" - final val invariant = "invariant" - final val items = "items" - final val many = "many" - final val mapping = "mapping" - final val merge = "merge" - final val message = "message" - final val morph = "morph" - final val name = "name" - final val new_ = "new" - final val on = "on" - final val one = "one" - final val organization = "organization" - final val option = "option" - final val optional = "optional" - final val options = "options" - final val other = "other" - final val outlet = "outlet" - final val outlets = "outlets" - final val output = "output" - final val parallel = "parallel" - final val pipe = "pipe" - final val plant = "plant" - final val projector = "projector" - final val query = "query" - final val range = "range" - final val reference = "reference" - final val remove = "remove" - final val replica = "replica" - final val reply = "reply" - final val repository = "repository" - final val requires = "requires" - final val required = "required" - final val record = "record" - final val result = "result" - final val results = "results" - final val return_ = "return" - final val returns = "returns" - final val reverted = "reverted" - final val role = "role" - final val router = "router" - final val saga = "saga" - final val scenario = "scenario" - final val see = "see" - final val send = "send" - final val sequence = "sequence" - final val set = "set" - final val show = "show" - final val shown = "shown" - final val sink = "sink" - final val source = "source" - final val split = "split" - final val state = "state" - final val step = "step" - final val stop = "stop" - final val story = "story" - final val streamlet = "streamlet" - final val take = "take" - final val tell = "tell" - final val term = "term" - final val then_ = "then" - final val title = "title" - final val transmit = "transmit" - final val true_ = "true" - final val `type` = "type" - final val url = "url" - final val user = "user" - final val value = "value" - final val view = "view" - final val void = "void" - final val when = "when" - final val yields = "yields" - } - - final val definition_keywords: Seq[String] = Seq( - Keywords.adaptor, - Keywords.application, - Keywords.author, - Keywords.case_, - Keywords.command, - Keywords.connector, - Keywords.const, - Keywords.context, - Keywords.entity, - Keywords.epic, - Keywords.field, - Keywords.flow, - Keywords.function, - Keywords.group, - Keywords.handler, - Keywords.inlet, - Keywords.input, - Keywords.invariant, - Keywords.outlet, - Keywords.output, - Keywords.pipe, - Keywords.projector, - Keywords.query, - Keywords.replica, - Keywords.reply, - Keywords.repository, - Keywords.record, - Keywords.result, - Keywords.saga, - Keywords.sink, - Keywords.source, - Keywords.state, - Keywords.streamlet, - Keywords.term, - Keywords.user - ) - - object Predefined { - final val Abstract = "Abstract" - final val Boolean = "Boolean" - final val Current = "Current" // in amperes - final val Currency = "Currency" // for some nation - final val Date = "Date" - final val DateTime = "DateTime" - final val Decimal = "Decimal" - final val Duration = "Duration" - final val Id = "Id" - final val Integer = "Integer" - final val Location = "Location" - final val Length = "Length" // in meters - final val Luminosity = "Luminosity" // in candelas - final val Mass = "Mass" // in kilograms - final val Mole = "Mole" // in mol (amount of substance) - final val Nothing = "Nothing" - final val Natural = "Natural" - final val Number = "Number" - final val Pattern = "Pattern" - final val Range = "Range" - final val Real = "Real" - final val String = "String" - final val Temperature = "Temperature" // in Kelvin - final val Time = "Time" - final val TimeStamp = "TimeStamp" - final val Unknown = "Unknown" - final val URL = "URL" - final val UserId = "UserId" - final val UUID = "UUID" - final val Whole = "Whole" - } - - object Readability { - final val and = "and" - final val are = "are" - final val as = "as" - final val at = "at" - final val by = "by" - final val for_ = "for" - final val from = "from" - final val in = "in" - final val is = "is" - final val of = "of" - final val on = "on" - final val so = "so" - final val that = "that" - final val to = "to" - final val wants = "wants" - final val with_ = "with" - } - - object Operators { - final val and = "and" - final val if_ = "if" - final val not = "not" - final val or = "or" - final val xor = "xor" - final val plus = "+" - final val minus = "-" - final val times = "*" - final val div = "/" - final val mod = "%" - } -} diff --git a/language/src/main/scala/com/reactific/riddl/language/parsing/TopLevelParser.scala b/language/src/main/scala/com/reactific/riddl/language/parsing/TopLevelParser.scala index dae2c4055..617b6297a 100644 --- a/language/src/main/scala/com/reactific/riddl/language/parsing/TopLevelParser.scala +++ b/language/src/main/scala/com/reactific/riddl/language/parsing/TopLevelParser.scala @@ -7,13 +7,17 @@ package com.reactific.riddl.language.parsing import com.reactific.riddl.language.AST.* +import com.reactific.riddl.language.At import com.reactific.riddl.language.Messages.Messages -import fastparse.* -import fastparse.ScalaWhitespace.* +import fastparse.{P, *} +import fastparse.Parsed.Success +import fastparse.Parsed.Failure +import fastparse.MultiLineWhitespace.* import java.io.File import java.net.URL import java.nio.file.Path +import scala.util.control.NonFatal /** Top level parsing rules */ class TopLevelParser(rpi: RiddlParserInput) @@ -32,14 +36,45 @@ class TopLevelParser(rpi: RiddlParserInput) with StreamingParser with StatementParser with TypeParser - with CommonParser { + with CommonParser + with ParsingContext { push(rpi) - def root[u: P]: P[Seq[RootDefinition]] = { - P(Start ~ (domain | author)./.rep(1) ~ End).map { contents => + def root[u: P]: P[RootContainer] = { + val curr_input = current + P( + Start ~ comments ~ (domain | author)./.rep(1) ~ comments ~ End + ).map { case (preComments, content, postComments) => pop - contents + RootContainer(preComments, content, postComments, Seq(curr_input)) + } + } + + def parseRootContainer( + withVerboseFailures: Boolean = false + ): Either[Messages, RootContainer] = { + val input = current + try { + fastparse.parse[RootContainer](input, root(_), withVerboseFailures) match { + case Success(root, index) => + if errors.nonEmpty then Left(errors.toList) + else if root.contents.isEmpty then + error( + At(input, index), + s"Parser could not translate '${input.origin}' after $index characters", + s"while parsing ${input.origin}" + ) + Right(root) + else Right(root) + case failure: Failure => + makeParseFailureError(failure) + Left(errors.toList) + } + } catch { + case NonFatal(exception) => + makeParseFailureError(exception) + Left(errors.toList) } } } @@ -50,9 +85,7 @@ object TopLevelParser { input: RiddlParserInput ): Either[Messages, RootContainer] = { val tlp = new TopLevelParser(input) - tlp.expectMultiple("test case", tlp.root(_)).map { case (defs: Seq[RootDefinition], rpi) => - RootContainer(defs, Seq(rpi)) - } + tlp.parseRootContainer() } def parse(file: File): Either[Messages, RootContainer] = { diff --git a/language/src/main/scala/com/reactific/riddl/language/parsing/TypeParser.scala b/language/src/main/scala/com/reactific/riddl/language/parsing/TypeParser.scala index abb3a7c42..952c2d841 100644 --- a/language/src/main/scala/com/reactific/riddl/language/parsing/TypeParser.scala +++ b/language/src/main/scala/com/reactific/riddl/language/parsing/TypeParser.scala @@ -8,10 +8,9 @@ package com.reactific.riddl.language.parsing import com.reactific.riddl.language.AST.* import com.reactific.riddl.language.{AST, At} -import com.reactific.riddl.language.Messages import fastparse.* -import fastparse.ScalaWhitespace.* -import Terminals.* +import fastparse.MultiLineWhitespace.* +import Readability.* /** Parsing rules for Type definitions */ private[parsing] trait TypeParser extends CommonParser { @@ -19,13 +18,13 @@ private[parsing] trait TypeParser extends CommonParser { private def entityReferenceType[u: P]: P[EntityReferenceTypeExpression] = { P( location ~ Keywords.reference ~ Readability.to.? ~/ - maybe(Keywords.entity) ~ pathIdentifier + maybe(Keyword.entity) ~ pathIdentifier ).map { tpl => (EntityReferenceTypeExpression.apply _).tupled(tpl) } } private def stringType[u: P]: P[Strng] = { P( - location ~ Predefined.String ~/ + location ~ PredefTypes.String_ ~/ (Punctuation.roundOpen ~ integer.? ~ Punctuation.comma ~ integer.? ~ Punctuation.roundClose).? ).map { @@ -208,28 +207,26 @@ private[parsing] trait TypeParser extends CommonParser { private def currencyType[u: P]: P[Currency] = { P( - location ~ Predefined.Currency ~/ + location ~ PredefTypes.Currency ~/ (Punctuation.roundOpen ~ isoCountryCode ~ Punctuation.roundClose) ).map { tpl => (Currency.apply _).tupled(tpl) } } private def urlType[u: P]: P[URL] = { P( - location ~ Predefined.URL ~/ + location ~ PredefTypes.URL ~/ (Punctuation.roundOpen ~ literalString ~ Punctuation.roundClose).? ).map { tpl => (URL.apply _).tupled(tpl) } } private def integerPredefTypes[u: P]: P[IntegerTypeExpression] = { P( - location ~ (StringIn(Predefined.Boolean, Predefined.Integer, Predefined.Whole, Predefined.Natural).! ~~ !CharPred( - _.isLetterOrDigit - )) | rangeType + location ~ PredefTypes.integerTypes | rangeType ).map { - case (at, Predefined.Boolean) => AST.Bool(at) - case (at, Predefined.Integer) => AST.Integer(at) - case (at, Predefined.Natural) => AST.Natural(at) - case (at, Predefined.Whole) => AST.Whole(at) + case (at, PredefType.Boolean) => AST.Bool(at) + case (at, PredefType.Integer) => AST.Integer(at) + case (at, PredefType.Natural) => AST.Natural(at) + case (at, PredefType.Whole) => AST.Whole(at) case (at, _: String) => assert(true) // shouldn't happen AST.Integer(at) @@ -239,82 +236,58 @@ private[parsing] trait TypeParser extends CommonParser { private def realPredefTypes[u: P]: P[RealTypeExpression] = { P( - location ~ (StringIn( - Predefined.Current, - Predefined.Length, - Predefined.Luminosity, - Predefined.Mass, - Predefined.Mole, - Predefined.Number, - Predefined.Real, - Predefined.Temperature - ).! ~~ !CharPred(_.isLetterOrDigit)) + location ~ PredefTypes.realTypes ).map { - case (at: At, Predefined.Current) => Current(at) - case (at: At, Predefined.Length) => Length(at) - case (at: At, Predefined.Luminosity) => Luminosity(at) - case (at: At, Predefined.Mass) => Mass(at) - case (at: At, Predefined.Mole) => Mole(at) - case (at: At, Predefined.Number) => Number(at) - case (at: At, Predefined.Real) => Real(at) - case (at: At, Predefined.Temperature) => Temperature(at) + case (at: At, PredefType.Current) => Current(at) + case (at: At, PredefType.Length) => Length(at) + case (at: At, PredefType.Luminosity) => Luminosity(at) + case (at: At, PredefType.Mass) => Mass(at) + case (at: At, PredefType.Mole) => Mole(at) + case (at: At, PredefType.Number) => Number(at) + case (at: At, PredefType.Real) => Real(at) + case (at: At, PredefType.Temperature) => Temperature(at) } } private def timePredefTypes[u: P]: P[TypeExpression] = { P( - location ~ StringIn( - Predefined.Duration, - Predefined.DateTime, - Predefined.Date, - Predefined.TimeStamp, - Predefined.Time - ).! ~~ !CharPred(_.isLetterOrDigit) + location ~ PredefTypes.timeTypes ).map { - case (at: At, Predefined.Duration) => Duration(at) - case (at, Predefined.DateTime) => DateTime(at) - case (at, Predefined.Date) => Date(at) - case (at, Predefined.TimeStamp) => TimeStamp(at) - case (at, Predefined.Time) => Time(at) + case (at: At, PredefType.Duration) => Duration(at) + case (at, PredefType.DateTime) => DateTime(at) + case (at, PredefType.Date) => Date(at) + case (at, PredefType.TimeStamp) => TimeStamp(at) + case (at, PredefType.Time) => Time(at) } } - private def otherTypes[u: P]: P[TypeExpression] = { + private def otherPredefTypes[u: P]: P[TypeExpression] = { P( - location ~ StringIn( - // order matters in this list, because of common prefixes - Predefined.Abstract, - Predefined.Length, - Predefined.Location, - Predefined.Nothing, - Predefined.Number, - Predefined.UUID, - Predefined.UserId - ).! ~~ !CharPred(_.isLetterOrDigit) + location ~ PredefTypes.otherTypes ).map { - case (at, Predefined.Abstract) => AST.Abstract(at) - case (at, Predefined.Location) => AST.Location(at) - case (at, Predefined.Nothing) => AST.Nothing(at) - case (at, Predefined.Natural) => AST.Natural(at) - case (at, Predefined.Number) => AST.Number(at) - case (at, Predefined.UUID) => AST.UUID(at) - case (at, Predefined.UserId) => AST.UserId(at) + case (at, PredefType.Abstract) => AST.Abstract(at) + case (at, PredefType.Location) => AST.Location(at) + case (at, PredefType.Nothing) => AST.Nothing(at) + case (at, PredefType.Natural) => AST.Natural(at) + case (at, PredefType.Number) => AST.Number(at) + case (at, PredefType.UUID) => AST.UUID(at) + case (at, PredefType.UserId) => AST.UserId(at) case (at, _) => error("Unrecognized predefined type") AST.Abstract(at) } } - private def simplePredefinedTypes[u: P]: P[TypeExpression] = { + private def predefinedTypes[u: P]: P[TypeExpression] = { P( stringType | currencyType | urlType | integerPredefTypes | realPredefTypes | timePredefTypes | - decimalType | otherTypes + decimalType | otherPredefTypes )./ } private def decimalType[u: P]: P[Decimal] = { P( - location ~ Predefined.Decimal ~/ Punctuation.roundOpen ~ + location ~ PredefType.Decimal ~/ Punctuation.roundOpen ~ integer ~ Punctuation.comma ~ integer ~ Punctuation.roundClose )./.map(tpl => (Decimal.apply _).tupled(tpl)) @@ -322,7 +295,7 @@ private[parsing] trait TypeParser extends CommonParser { private def patternType[u: P]: P[Pattern] = { P( - location ~ Predefined.Pattern ~/ Punctuation.roundOpen ~ + location ~ PredefType.Pattern ~/ Punctuation.roundOpen ~ (literalStrings | Punctuation.undefinedMark.!.map(_ => Seq.empty[LiteralString])) ~ Punctuation.roundClose./ @@ -330,8 +303,8 @@ private[parsing] trait TypeParser extends CommonParser { } private def uniqueIdType[u: P]: P[UniqueId] = { - (location ~ Predefined.Id ~ Punctuation.roundOpen ~/ - maybe(Keywords.entity) ~ pathIdentifier ~ Punctuation.roundClose./) map { case (loc, pid) => + (location ~ PredefType.Id ~ Punctuation.roundOpen ~/ + maybe(Keyword.entity) ~ pathIdentifier ~ Punctuation.roundClose./) map { case (loc, pid) => UniqueId(loc, pid) } } @@ -341,7 +314,7 @@ private[parsing] trait TypeParser extends CommonParser { } private def enumerator[u: P]: P[Enumerator] = { - P(location ~ identifier ~ enumValue ~ briefly ~ description).map { tpl => + P(location ~ identifier ~ enumValue ~ briefly ~ description ~ comments).map { tpl => (Enumerator.apply _).tupled(tpl) } } @@ -364,23 +337,18 @@ private[parsing] trait TypeParser extends CommonParser { private def aliasedTypeExpression[u: P]: P[AliasedTypeExpression] = { P( - location ~ - StringIn( - Keywords.command, - Keywords.event, - Keywords.query, - Keywords.result, - Keywords.record, - Keywords.`type` - ).? ~ pathIdentifier - )./.map(tpl => (AliasedTypeExpression.apply _).tupled(tpl)) + location ~ Keywords.aggregateTypes.? ~ pathIdentifier + )./.map { + case (loc, Some(key), pid) => AliasedTypeExpression(loc, key, pid) + case (loc, None, pid) => AliasedTypeExpression(loc, "type", pid) + } } private def fieldTypeExpression[u: P]: P[TypeExpression] = { P( cardinality( - simplePredefinedTypes./ | patternType | uniqueIdType | - enumeration | setType | mappingType | sequenceType | rangeType | + predefinedTypes | patternType | uniqueIdType | + enumeration | aliasedTypeExpression | aliasedTypeExpression | sequenceType | rangeType | alternation | entityReferenceType | aliasedTypeExpression ) ) @@ -388,7 +356,7 @@ private[parsing] trait TypeParser extends CommonParser { def field[u: P]: P[Field] = { P( - location ~ identifier ~ is ~ fieldTypeExpression ~ briefly ~ description + location ~ identifier ~ is ~ fieldTypeExpression ~ briefly ~ description ~ comments ).map(tpl => (Field.apply _).tupled(tpl)) } @@ -403,11 +371,11 @@ private[parsing] trait TypeParser extends CommonParser { def method[u: P]: P[Method] = { P( location ~ identifier ~ Punctuation.roundOpen ~ arguments ~ Punctuation.roundClose ~ - is ~ fieldTypeExpression ~ briefly ~ description + is ~ fieldTypeExpression ~ briefly ~ description ~ comments ).map(tpl => (Method.apply _).tupled(tpl)) } - def aggregateDefinitions[u: P]: P[Seq[AggregateDefinition]] = { + private def aggregateDefinitions[u: P]: P[Seq[AggregateDefinition]] = { P( undefined(Seq.empty[AggregateDefinition]) | (field | method).rep(min = 1, Punctuation.comma) @@ -425,20 +393,15 @@ private[parsing] trait TypeParser extends CommonParser { private def aggregateUseCase[u: P]: P[AggregateUseCase] = { P( - StringIn( - Keywords.command, - Keywords.event, - Keywords.query, - Keywords.result, - Keywords.record - ).! + Keywords.aggregateTypes ).map { mk => mk.toLowerCase() match { - case kind if kind == Keywords.command => CommandCase - case kind if kind == Keywords.event => EventCase - case kind if kind == Keywords.query => QueryCase - case kind if kind == Keywords.result => ResultCase - case kind if kind == Keywords.record => RecordCase + case kind if kind == Keyword.type_ => TypeCase + case kind if kind == Keyword.command => CommandCase + case kind if kind == Keyword.event => EventCase + case kind if kind == Keyword.query => QueryCase + case kind if kind == Keyword.result => ResultCase + case kind if kind == Keyword.record => RecordCase } } } @@ -462,7 +425,7 @@ private[parsing] trait TypeParser extends CommonParser { * mapping from Integer to String * }}} */ - private def mappingType[u: P]: P[Mapping] = { + private def mappingFromTo[u: P]: P[Mapping] = { P( location ~ Keywords.mapping ~ Readability.from ~/ typeExpression ~ Readability.to ~ typeExpression @@ -473,16 +436,18 @@ private[parsing] trait TypeParser extends CommonParser { * {{{ * set of String * }}} - * - * @tparam u - * @return */ - private def setType[u: P]: P[Set] = { + private def set[u: P]: P[Set] = { P( location ~ Keywords.set ~ Readability.of ~ typeExpression )./.map { tpl => (Set.apply _).tupled(tpl) } } + /** Parses sequences, i.e. + * {{{ + * sequence of String + * }}} + */ private def sequenceType[u: P]: P[Sequence] = { P( location ~ Keywords.sequence ~ Readability.of ~ typeExpression @@ -507,32 +472,30 @@ private[parsing] trait TypeParser extends CommonParser { Keywords.many.!.? ~ Keywords.optional.!.? ~ location ~ p ~ StringIn( Punctuation.question, Punctuation.asterisk, - Punctuation.plus, - Punctuation.ellipsisQuestion, - Punctuation.ellipsis + Punctuation.plus ).!.? ).map { - case (None, None, loc, typ, Some("?")) => Optional(loc, typ) - case (None, None, loc, typ, Some("+")) => OneOrMore(loc, typ) - case (None, None, loc, typ, Some("*")) => ZeroOrMore(loc, typ) - case (Some(_), None, loc, typ, None) => OneOrMore(loc, typ) - case (None, Some(_), loc, typ, None) => Optional(loc, typ) - case (Some(_), Some(_), loc, typ, None) => ZeroOrMore(loc, typ) - case (None, Some(_), loc, typ, Some("?")) => Optional(loc, typ) - case (Some(_), None, loc, typ, Some("+")) => OneOrMore(loc, typ) - case (Some(_), Some(_), loc, typ, Some("*")) => ZeroOrMore(loc, typ) - case (None, None, _, typ, None) => typ + case (None, None, loc, typ, Some("?")) => Optional(loc, typ) + case (None, None, loc, typ, Some("+")) => OneOrMore(loc, typ) + case (None, None, loc, typ, Some("*")) => ZeroOrMore(loc, typ) + case (Some("many"), None, loc, typ, None) => OneOrMore(loc, typ) + case (None, Some("optional"), loc, typ, None) => Optional(loc, typ) + case (Some("many"), Some("optional"), loc, typ, None) => ZeroOrMore(loc, typ) + case (None, Some("optional"), loc, typ, Some("?")) => Optional(loc, typ) + case (Some("many"), None, loc, typ, Some("+")) => OneOrMore(loc, typ) + case (Some("many"), Some("optional"), loc, typ, Some("*")) => ZeroOrMore(loc, typ) + case (None, None, _, typ, None) => typ case (_, _, loc, typ, _) => error(loc, s"Cannot determine cardinality for $typ") typ } } - def typeExpression[u: P]: P[TypeExpression] = { + private def typeExpression[u: P]: P[TypeExpression] = { P( cardinality( - simplePredefinedTypes | patternType | uniqueIdType | enumeration | - sequenceType | setType | mappingType | rangeType | + predefinedTypes | patternType | uniqueIdType | enumeration | sequenceType | + aggregateUseCaseTypeExpression | mappingFromTo | rangeType | decimalType | alternation | entityReferenceType | aggregation | aggregateUseCaseTypeExpression | aliasedTypeExpression ) @@ -540,33 +503,35 @@ private[parsing] trait TypeParser extends CommonParser { } def replicaTypeExpression[u: P]: P[TypeExpression] = { - P(integerPredefTypes | mappingType | setType) + P(integerPredefTypes | mappingFromTo | set) } private def defOfTypeKindType[u: P]: P[Type] = { P( location ~ aggregateUseCase ~/ identifier ~ is ~ (aliasedTypeExpression | aggregation) ~ briefly ~ - description - ).map { case (loc, useCase, id, ateOrAgg, b, d) => + description ~ comments + ).map { case (loc, useCase, id, ateOrAgg, brief, description, comments) => ateOrAgg match { case agg: Aggregation => val mt = AggregateUseCaseTypeExpression(agg.loc, useCase, agg.fields, agg.methods) - Type(loc, id, mt, b, d) + Type(loc, id, mt, brief, description, comments) case ate: AliasedTypeExpression => - Type(loc, id, ate, b, d) + Type(loc, id, ate, brief, description, comments) case _ => require(false, "Oops! Impossible case") // Type just to satisfy compiler because it doesn't know require(false...) will throw - Type(loc, id, Nothing(loc), b, d) + Type(loc, id, Nothing(loc), brief, description, comments) } } } private def defOfType[u: P]: P[Type] = { P( - location ~ Keywords.`type` ~/ identifier ~ is ~ typeExpression ~ briefly ~ - description - ).map { case (loc, id, typEx, b, d) => Type(loc, id, typEx, b, d) } + location ~ Keywords.type_ ~/ identifier ~ is ~ typeExpression ~ briefly ~ + description ~ comments + ).map { case (loc, id, typ, brief, description, comments) => + Type(loc, id, typ, brief, description, comments) + } } def typeDef[u: P]: P[Type] = { defOfType | defOfTypeKindType } @@ -575,8 +540,8 @@ private[parsing] trait TypeParser extends CommonParser { def constant[u: P]: P[Constant] = { P( - location ~ Keywords.const ~ identifier ~ is ~ typeExpression ~ - Punctuation.equalsSign ~ literalString ~ briefly ~ description + location ~ Keywords.constant ~ identifier ~ is ~ typeExpression ~ + Punctuation.equalsSign ~ literalString ~ briefly ~ description ~ comments ).map { tpl => (Constant.apply _).tupled(tpl) } } diff --git a/language/src/test/input/domains/rbbq.riddl b/language/src/test/input/domains/rbbq.riddl index 9d75c2439..f1dbff586 100644 --- a/language/src/test/input/domains/rbbq.riddl +++ b/language/src/test/input/domains/rbbq.riddl @@ -1,9 +1,5 @@ -// #everything -// #domains domain ReactiveBBQ is { -// #domains - // Create some types with better names than just "Id" type CustomerId is Id(ReactiveBBQ.Customer.Customer) explained as { "Unique identifier for a customer" } @@ -24,24 +20,18 @@ domain ReactiveBBQ is { } } - - // #Kitchen - // The Kitchen context pertains to context Kitchen is { type IP4Address is { a: Number, b: Number, c: Number, d: Number} type OrderViewType is { address: type Kitchen.IP4Address - } + } // Should we support IP4Address? entity OrderViewer is { option is kind("device") record fields is { field: type Kitchen.OrderViewType } state OrderState of OrderViewer.fields is { handler input is { ??? } } - } explained as { - |# brief - |This is an OrderViewer - |# details + } briefly "This is an OrderViewer" explained as { |The OrderViewer is the device in the kitchen, probably a touch screen, |that the cooks use to view the sequence of orders to cook |# see @@ -63,10 +53,8 @@ domain ReactiveBBQ is { |1. Order sends order status changes to Kitchen |1. Kitchen ignores drink items on order |1. - } - // #Kitchen + } // test end of definition comments - // #Loyalty context Loyalty is { type AccrualEvent is { when is TimeStamp, @@ -96,9 +84,7 @@ domain ReactiveBBQ is { ??? } } - // #Loyalty - // #Order context Order is { entity Order is { option is aggregate @@ -111,9 +97,7 @@ domain ReactiveBBQ is { } } } - // #Order - // #Payment context Payment is { entity Payment is { option is aggregate @@ -127,9 +111,7 @@ domain ReactiveBBQ is { } } } - // #Payment - // #Menu context Menu is { entity MenuItem is { record fields is { something: String } @@ -146,9 +128,7 @@ domain ReactiveBBQ is { } } } - // #Menu - // #Reservation context Reservation is { type ReservationValue is { partyName is String, @@ -171,8 +151,7 @@ domain ReactiveBBQ is { handler ofInputs is {} } } - } - // #Reservation + } // end of context } explained as { |# brief | Reactive BBQ Domain Definition @@ -182,5 +161,4 @@ domain ReactiveBBQ is { |the basis for defining the domain. The course uses a set of interviews with |Reactive BBQ employees to define the requirements. This domain specification |is a possible result of analyzing that domain: the Reactive BBQ restaurant. -} -// #everything +} // end of domain diff --git a/language/src/test/input/everything.riddl b/language/src/test/input/everything.riddl index a862b3fc7..6fc0baca9 100644 --- a/language/src/test/input/everything.riddl +++ b/language/src/test/input/everything.riddl @@ -1,5 +1,3 @@ -// #everything -// RIDDL is about defining domains so ... domain Everything by author Reid is { author Reid is { name: "Reid" email: "reid@ossum.biz" } @@ -7,9 +5,6 @@ domain Everything by author Reid is { type SomeType is String command DoAThing is { thingField: Integer } - // Channels are defined to flow messages between entities. They are defined - // in domain scope because they can cross contexts too. - context APlant is { source Source is { outlet Commands is type DoAThing } described by "Data Source" sink Sink is { inlet Commands is type DoAThing } explained as "Data Sink" @@ -27,11 +22,7 @@ domain Everything by author Reid is { case primary is { ??? } } described as "A simple authoring epic" - // Domains are composed of bounded contexts so ... context full is { - // Contexts can contain many kinds of definitions - - // 8 pre-defined types, shown as re-names type str is String // Define str as a String type num is Number // Define num as a Number type boo is Boolean // Define boo as a Boolean @@ -39,34 +30,28 @@ domain Everything by author Reid is { type dat is Date // Define dat as a Date type tim is Time // Define tim as a Time type stamp is TimeStamp // Define stamp as a TimeStamp - type url is URL + type url is URL // Define url as a Uniform Resource Locator - // Enumerations have a value chosen from a list of identifiers type PeachType is { a: Integer } type enum is any of { Apple Pear Peach(23) Persimmon(24) } - // Alternations select one type from a list of types - type alt is one of { enum or stamp or url } + type alt is one of { enum or stamp or url } described as { + | Alternations select one type from a list of types + } + - // Aggregations combine several types and give each a field name identifier type agg is { key: num, id: ident, time is TimeStamp } - // Types can have cardinality requirements similar to regular expressions type oneOrMore is many agg type zeroOrMore is agg* type optional is agg? - // Commands, Events, Queries and Results are defined in terms of some - // type, previously defined. Commands yield events. Queries yield results. - command ACommand is { ??? } - // Entities are the main objects that contexts define. They can be - // transient (memory only) or aggregate (they consume commands and queries) entity Something is { options (aggregate, transient) function misc is { @@ -90,8 +75,8 @@ domain Everything by author Reid is { } function whenUnderTheInfluence is { - requires { n:Nothing } - returns { b: Boolean } + requires { n: Nothing } + returns { b: Boolean } body ??? } } briefly "Something is nothing interesting" @@ -110,5 +95,3 @@ domain Everything by author Reid is { } } } - -// #everything diff --git a/language/src/test/scala/com/reactific/riddl/language/ASTTest.scala b/language/src/test/scala/com/reactific/riddl/language/ASTTest.scala index 1f2a1af5f..94b2eaef7 100644 --- a/language/src/test/scala/com/reactific/riddl/language/ASTTest.scala +++ b/language/src/test/scala/com/reactific/riddl/language/ASTTest.scala @@ -6,7 +6,7 @@ package com.reactific.riddl.language -import com.reactific.riddl.language.parsing.Terminals.Keywords +import com.reactific.riddl.language.parsing.Keyword import com.reactific.riddl.language.AST.* import org.scalatest.matchers.must.Matchers import org.scalatest.wordspec.AnyWordSpec @@ -20,35 +20,27 @@ class ASTTest extends AnyWordSpec with Matchers { Domain((0, 0), Identifier((1, 1), "foo")) } "support all type constructs" in { - AliasedTypeExpression(0 -> 0, PathIdentifier(0 -> 0, Seq("Foo"))) mustBe - AliasedTypeExpression(0 -> 0, PathIdentifier(0 -> 0, Seq("Foo"))) - - Enumeration((0, 0), Seq.empty[Enumerator]) mustBe - Enumeration((0, 0), Seq.empty[Enumerator]) - Alternation((0, 0), Seq.empty[AliasedTypeExpression]) mustBe - Alternation((0, 0), Seq.empty[AliasedTypeExpression]) - Aggregation((0, 0), Seq.empty[Field]) mustBe - Aggregation((0, 0), Seq.empty[Field]) + AliasedTypeExpression(0 -> 0, "record", PathIdentifier(0 -> 0, Seq("Foo"))).format mustBe "record Foo" + Enumeration((0, 0), Seq.empty[Enumerator]).format mustBe "{ }" + Alternation((0, 0), Seq.empty[AliasedTypeExpression]).format mustBe "one of { }" + Aggregation((0, 0), Seq.empty[Field]).format mustBe "{ }" Optional( (0, 0), - AliasedTypeExpression((0, 0), PathIdentifier((0, 0), Seq("String"))) - ) mustBe Optional( - (0, 0), - AliasedTypeExpression((0, 0), PathIdentifier((0, 0), Seq("String"))) - ) + AliasedTypeExpression((0, 0), "record", PathIdentifier((0, 0), Seq("String"))) + ).format mustBe "record String?" ZeroOrMore( (0, 0), - AliasedTypeExpression((0, 0), PathIdentifier((0, 0), Seq("Time"))) + AliasedTypeExpression((0, 0), "record", PathIdentifier((0, 0), Seq("Time"))) ) mustBe ZeroOrMore( (0, 0), - AliasedTypeExpression((0, 0), PathIdentifier((0, 0), Seq("Time"))) + AliasedTypeExpression((0, 0), "record", PathIdentifier((0, 0), Seq("Time"))) ) OneOrMore( (0, 0), - AliasedTypeExpression((0, 0), PathIdentifier((0, 0), Seq("URL"))) + AliasedTypeExpression((0, 0), "record", PathIdentifier((0, 0), Seq("URL"))) ) mustBe OneOrMore( (0, 0), - AliasedTypeExpression((0, 0), PathIdentifier((0, 0), Seq("URL"))) + AliasedTypeExpression((0, 0), "record", PathIdentifier((0, 0), Seq("URL"))) ) } } @@ -70,7 +62,7 @@ class ASTTest extends AnyWordSpec with Matchers { "have kind 'Boolean'" in { Bool(At()).kind mustBe "Boolean" } } - "Entity Options" should { + "Entity RiddlOptions" should { "have correct names" in { EntityIsAggregate(At()).name mustBe "aggregate" EntityTransient(At()).name mustBe "transient" @@ -284,7 +276,7 @@ class ASTTest extends AnyWordSpec with Matchers { "Term" should { "format correctly" in { - term.format mustBe s"${Keywords.term} ${term.id.format}" + term.format mustBe s"${Keyword.term} ${term.id.format}" } } } diff --git a/language/src/test/scala/com/reactific/riddl/language/HandlerTest.scala b/language/src/test/scala/com/reactific/riddl/language/HandlerTest.scala index e96106c96..0e03a9e56 100644 --- a/language/src/test/scala/com/reactific/riddl/language/HandlerTest.scala +++ b/language/src/test/scala/com/reactific/riddl/language/HandlerTest.scala @@ -180,7 +180,6 @@ class HandlerTest extends ParsingTest { | "execute Unnest" | "end" | } - | // TODO: what commands bring item out of a hold? | on command MarkItemOutForDelivery { | set field journey to "field OutForDelivery" | } @@ -189,13 +188,13 @@ class HandlerTest extends ParsingTest { | "execute Unnest" | } | on command MachineMissort { - | set field journey to "unknown() // TODO: how do we respond to this?" + | set field journey to "unknown()" | } | on command HumanMissort { - | set field journey to "unknown() // TODO: how do we respond to this?" + | set field journey to "unknown()" | } | on command CustomerAddressingError { - | set field journey to "onHold() // TODO: how do we respond to this?" + | set field journey to "onHold()" | } | } |} diff --git a/language/src/test/scala/com/reactific/riddl/language/ParsingTest.scala b/language/src/test/scala/com/reactific/riddl/language/ParsingTest.scala index feea2d530..d05a046e1 100644 --- a/language/src/test/scala/com/reactific/riddl/language/ParsingTest.scala +++ b/language/src/test/scala/com/reactific/riddl/language/ParsingTest.scala @@ -56,20 +56,15 @@ case class TestParser(input: RiddlParserInput, throwOnError: Boolean = false) parser.asInstanceOf[P[?] => P[T]] } - def parseTopLevelDomains: Either[Messages, RootContainer] = { - expectMultiple("test case", root(_)).map { - case (defs: Seq[RootDefinition], _) => - RootContainer(defs, Seq(input)) - } + def parseRootContainer: Either[Messages, RootContainer] = { + parseRootContainer(withVerboseFailures = true) } def parseTopLevelDomain[TO <: RiddlNode]( extract: RootContainer => TO ): Either[Messages, (TO, RiddlParserInput)] = { - expectMultiple[RootDefinition]("test case", root(_)).map { - case (roots: Seq[RootDefinition], _) => - val rc = RootContainer(roots, Seq(input)) - extract(rc) -> input + parseRootContainer(withVerboseFailures = true).map { case root: RootContainer => + extract(root) -> current } } @@ -117,7 +112,7 @@ class ParsingTest extends ParsingTestBase { input: RiddlParserInput ): Either[Messages, RootContainer] = { val tp = TestParser(input) - tp.parseTopLevelDomains + tp.parseRootContainer } def parseTopLevelDomain[TO <: RiddlNode]( diff --git a/language/src/test/scala/com/reactific/riddl/language/TypeExpressionTest.scala b/language/src/test/scala/com/reactific/riddl/language/TypeExpressionTest.scala index 4677a703e..792ac1bee 100644 --- a/language/src/test/scala/com/reactific/riddl/language/TypeExpressionTest.scala +++ b/language/src/test/scala/com/reactific/riddl/language/TypeExpressionTest.scala @@ -8,16 +8,17 @@ package com.reactific.riddl.language import org.scalatest.matchers.must.Matchers import org.scalatest.wordspec.AnyWordSpec -import com.reactific.riddl.language.parsing.Terminals.* import com.reactific.riddl.language.AST.* +import com.reactific.riddl.language.parsing.PredefType + /** Unit Tests For TypeExpressions */ class TypeExpressionTest extends AnyWordSpec with Matchers { val abstract_ = Abstract(At.empty) - val bool = Bool(At.empty) - val current = Current(At.empty) - val currency = Currency(At.empty, "CA") - val date = Date(At.empty) + val bool: Bool = Bool(At.empty) + val current: Current = Current(At.empty) + val currency: Currency = Currency(At.empty, "CA") + val date: Date = Date(At.empty) val dateTime = DateTime(At.empty) val decimal = Decimal(At.empty, 8, 3) val duration = Duration(At.empty) @@ -43,150 +44,150 @@ class TypeExpressionTest extends AnyWordSpec with Matchers { "Simple Predefined Types" must { "support Abstract" in { - abstract_.kind mustBe Predefined.Abstract - AST.errorDescription(abstract_) mustBe Predefined.Abstract + abstract_.kind mustBe PredefType.Abstract + AST.errorDescription(abstract_) mustBe PredefType.Abstract } "support Boolean" in { - bool.kind mustBe Predefined.Boolean - AST.errorDescription(bool) mustBe Predefined.Boolean + bool.kind mustBe PredefType.Boolean + AST.errorDescription(bool) mustBe PredefType.Boolean bool.isEmpty mustBe true bool.isContainer mustBe false } "support Current" in { - current.kind mustBe Predefined.Current - AST.errorDescription(current) mustBe Predefined.Current + current.kind mustBe PredefType.Current + AST.errorDescription(current) mustBe PredefType.Current current.isEmpty mustBe true current.isContainer mustBe false } "support Currency" in { - currency.kind mustBe Predefined.Currency - AST.errorDescription(currency) mustBe Predefined.Currency + currency.kind mustBe PredefType.Currency + AST.errorDescription(currency) mustBe PredefType.Currency currency.isEmpty mustBe true currency.isContainer mustBe false } "support Date" in { - date.kind mustBe Predefined.Date - AST.errorDescription(date) mustBe Predefined.Date + date.kind mustBe PredefType.Date + AST.errorDescription(date) mustBe PredefType.Date date.isEmpty mustBe true date.isContainer mustBe false } "support DateTime" in { - dateTime.kind mustBe Predefined.DateTime - AST.errorDescription(dateTime) mustBe Predefined.DateTime + dateTime.kind mustBe PredefType.DateTime + AST.errorDescription(dateTime) mustBe PredefType.DateTime dateTime.isEmpty mustBe true dateTime.isContainer mustBe false } "support Decimal" in { - decimal.kind mustBe Predefined.Decimal + decimal.kind mustBe PredefType.Decimal AST.errorDescription(decimal) mustBe s"Decimal(8,3)" decimal.isEmpty mustBe true decimal.isContainer mustBe false decimal.format mustBe s"Decimal(8,3)" } "support Duration" in { - duration.kind mustBe Predefined.Duration - AST.errorDescription(duration) mustBe Predefined.Duration + duration.kind mustBe PredefType.Duration + AST.errorDescription(duration) mustBe PredefType.Duration duration.isEmpty mustBe true duration.isContainer mustBe false - duration.format mustBe Predefined.Duration + duration.format mustBe PredefType.Duration } "support Integer" in { - integer.kind mustBe Predefined.Integer - AST.errorDescription(integer) mustBe Predefined.Integer + integer.kind mustBe PredefType.Integer + AST.errorDescription(integer) mustBe PredefType.Integer integer.isEmpty mustBe true integer.isContainer mustBe false - integer.format mustBe Predefined.Integer + integer.format mustBe PredefType.Integer } "support Length" in { - length_.kind mustBe Predefined.Length - AST.errorDescription(length_) mustBe Predefined.Length + length_.kind mustBe PredefType.Length + AST.errorDescription(length_) mustBe PredefType.Length length_.isEmpty mustBe true length_.isContainer mustBe false - length_.format mustBe Predefined.Length + length_.format mustBe PredefType.Length } "support Location" in { - location.kind mustBe Predefined.Location - AST.errorDescription(location) mustBe Predefined.Location + location.kind mustBe PredefType.Location + AST.errorDescription(location) mustBe PredefType.Location location.isEmpty mustBe true location.isContainer mustBe false - location.format mustBe Predefined.Location + location.format mustBe PredefType.Location } "support Luminosity" in { - luminosity.kind mustBe Predefined.Luminosity - AST.errorDescription(luminosity) mustBe Predefined.Luminosity + luminosity.kind mustBe PredefType.Luminosity + AST.errorDescription(luminosity) mustBe PredefType.Luminosity luminosity.isEmpty mustBe true luminosity.isContainer mustBe false - luminosity.format mustBe Predefined.Luminosity + luminosity.format mustBe PredefType.Luminosity } "support Mass" in { - mass.kind mustBe Predefined.Mass - AST.errorDescription(mass) mustBe Predefined.Mass + mass.kind mustBe PredefType.Mass + AST.errorDescription(mass) mustBe PredefType.Mass mass.isEmpty mustBe true mass.isContainer mustBe false - mass.format mustBe Predefined.Mass + mass.format mustBe PredefType.Mass } "support Mole" in { - mole.kind mustBe Predefined.Mole - AST.errorDescription(mole) mustBe Predefined.Mole + mole.kind mustBe PredefType.Mole + AST.errorDescription(mole) mustBe PredefType.Mole mole.isEmpty mustBe true mole.isContainer mustBe false - mole.format mustBe Predefined.Mole + mole.format mustBe PredefType.Mole } "support Nothing" in { - nothing.kind mustBe Predefined.Nothing - AST.errorDescription(nothing) mustBe Predefined.Nothing + nothing.kind mustBe PredefType.Nothing + AST.errorDescription(nothing) mustBe PredefType.Nothing nothing.isEmpty mustBe true nothing.isContainer mustBe false - nothing.format mustBe Predefined.Nothing + nothing.format mustBe PredefType.Nothing } "support Number" in { - number.kind mustBe Predefined.Number - AST.errorDescription(number) mustBe Predefined.Number + number.kind mustBe PredefType.Number + AST.errorDescription(number) mustBe PredefType.Number number.isEmpty mustBe true number.isContainer mustBe false - number.format mustBe Predefined.Number + number.format mustBe PredefType.Number } "support Real" in { - real.kind mustBe Predefined.Real - AST.errorDescription(real) mustBe Predefined.Real + real.kind mustBe PredefType.Real + AST.errorDescription(real) mustBe PredefType.Real real.isEmpty mustBe true real.isContainer mustBe false - real.format mustBe Predefined.Real + real.format mustBe PredefType.Real } "support Temperature" in { - temperature.kind mustBe Predefined.Temperature - AST.errorDescription(temperature) mustBe Predefined.Temperature + temperature.kind mustBe PredefType.Temperature + AST.errorDescription(temperature) mustBe PredefType.Temperature temperature.isEmpty mustBe true temperature.isContainer mustBe false - temperature.format mustBe Predefined.Temperature + temperature.format mustBe PredefType.Temperature } "support Time" in { - time.kind mustBe Predefined.Time - AST.errorDescription(time) mustBe Predefined.Time + time.kind mustBe PredefType.Time + AST.errorDescription(time) mustBe PredefType.Time time.isEmpty mustBe true time.isContainer mustBe false - time.format mustBe Predefined.Time + time.format mustBe PredefType.Time } "support TimeStamp" in { - timestamp.kind mustBe Predefined.TimeStamp - AST.errorDescription(timestamp) mustBe Predefined.TimeStamp + timestamp.kind mustBe PredefType.TimeStamp + AST.errorDescription(timestamp) mustBe PredefType.TimeStamp timestamp.isEmpty mustBe true timestamp.isContainer mustBe false - timestamp.format mustBe Predefined.TimeStamp + timestamp.format mustBe PredefType.TimeStamp } "support URL" in { - url.kind mustBe Predefined.URL - AST.errorDescription(url) mustBe Predefined.URL + url.kind mustBe PredefType.URL + AST.errorDescription(url) mustBe PredefType.URL url.isEmpty mustBe true url.isContainer mustBe false - url.format mustBe Predefined.URL + url.format mustBe PredefType.URL } "support UUID" in { - uuid.kind mustBe Predefined.UUID - AST.errorDescription(uuid) mustBe Predefined.UUID + uuid.kind mustBe PredefType.UUID + AST.errorDescription(uuid) mustBe PredefType.UUID uuid.isEmpty mustBe true uuid.isContainer mustBe false - uuid.format mustBe Predefined.UUID + uuid.format mustBe PredefType.UUID } } @@ -194,20 +195,20 @@ class TypeExpressionTest extends AnyWordSpec with Matchers { "support Pattern" in { pattern.isEmpty mustBe true pattern.isContainer mustBe false - pattern.kind mustBe Predefined.Pattern - AST.errorDescription(pattern) mustBe Predefined.Pattern + pattern.kind mustBe PredefType.Pattern + AST.errorDescription(pattern) mustBe "Pattern(\"^$\")" pattern.format mustBe "Pattern(\"^$\")" } "support Range" in { range.isEmpty mustBe true range.isContainer mustBe false - range.kind mustBe Predefined.Range + range.kind mustBe PredefType.Range AST.errorDescription(range) mustBe "Range(2,4)" range.format mustBe "Range(2,4)" } "support String" in { - string.kind mustBe Predefined.String - AST.errorDescription(string) mustBe Predefined.String + string.kind mustBe PredefType.String + AST.errorDescription(string) mustBe PredefType.String string.format mustBe "String(42,)" string.isEmpty mustBe true string.isContainer mustBe false @@ -215,13 +216,13 @@ class TypeExpressionTest extends AnyWordSpec with Matchers { "support UniqueId" in { id.format mustBe "Id(a.b)" - id.kind mustBe Predefined.Id + id.kind mustBe PredefType.Id id.isEmpty mustBe true id.isContainer mustBe false } } - val enumeration = Enumeration( + val enumeration: Enumeration = Enumeration( At.empty, Seq( Enumerator(At.empty, Identifier(At.empty, "one")), @@ -230,7 +231,7 @@ class TypeExpressionTest extends AnyWordSpec with Matchers { ) ) - val aggregation = Aggregation( + val aggregation: Aggregation = Aggregation( At.empty, Seq( Field(At.empty, Identifier(At.empty, "integer"), integer), @@ -263,28 +264,26 @@ class TypeExpressionTest extends AnyWordSpec with Matchers { ) ) - val alternation = Alternation( + val alternation: Alternation = Alternation( At.empty, Seq( - AliasedTypeExpression(At.empty, PathIdentifier(At.empty, Seq("a", "b"))), - AliasedTypeExpression(At.empty, PathIdentifier(At.empty, Seq("z", "y"))) + AliasedTypeExpression(At.empty, "message", PathIdentifier(At.empty, Seq("a", "b"))), + AliasedTypeExpression(At.empty, "message", PathIdentifier(At.empty, Seq("z", "y"))) ) ) - val mapping = Mapping(At.empty, string, integer) + val mapping: Mapping = Mapping(At.empty, string, integer) - val reference = EntityReferenceTypeExpression( + val reference: EntityReferenceTypeExpression = EntityReferenceTypeExpression( At.empty, PathIdentifier(At.empty, Seq("a", "b", "c", "d", "entity")) ) - val message = + val message: AggregateUseCaseTypeExpression = AggregateUseCaseTypeExpression(At.empty, RecordCase, aggregation.fields) - val alias = AliasedTypeExpression( - At.empty, - PathIdentifier(At.empty, Seq("a", "b", "foo")) - ) + val alias: AliasedTypeExpression = AliasedTypeExpression(At.empty, "message", PathIdentifier(At.empty, Seq("a", + "b", "foo"))) "Complex Expression Types" must { "Support Aggregation" in { @@ -310,7 +309,7 @@ class TypeExpressionTest extends AnyWordSpec with Matchers { } "Support Alternation" in { AST.errorDescription(alternation) mustBe "Alternation of 2 types" - alternation.format mustBe "one of { a.b, z.y }" + alternation.format mustBe "one of { message a.b, message z.y }" alternation.isEmpty mustBe true alternation.isContainer mustBe false } @@ -343,8 +342,8 @@ class TypeExpressionTest extends AnyWordSpec with Matchers { message.isContainer mustBe true } "Support type aliases" in { - AST.errorDescription(alias) mustBe "a.b.foo" - alias.format mustBe "a.b.foo" + AST.errorDescription(alias) mustBe "message a.b.foo" + alias.format mustBe "message a.b.foo" alias.isEmpty mustBe true alias.isContainer mustBe false } diff --git a/language/src/test/scala/com/reactific/riddl/language/parsing/ParserTest.scala b/language/src/test/scala/com/reactific/riddl/language/parsing/ParserTest.scala index 03f748c0a..a731355cb 100644 --- a/language/src/test/scala/com/reactific/riddl/language/parsing/ParserTest.scala +++ b/language/src/test/scala/com/reactific/riddl/language/parsing/ParserTest.scala @@ -29,16 +29,26 @@ class ParserTest extends ParsingTest { case Left(errors) => errors must not be empty errors.head.message mustBe - "Expected one of (\"author\" | \"domain\")" + "Expected one of (\"author\" | \"domain\" | \"//\")" case Right(_) => fail("Invalid syntax should make an error") } } + "ensure keywords are distinct" in { + val input = "domainfoois { author nobody is { ??? } } \n" + parseTopLevelDomain(input, _.contents.head) match { + case Left(errors) => + errors must not be empty + errors.head.message must include("whitespace after keyword") + case Right(_) => fail("'domainfoois' should be flagged as needing whitespace after a keyword") + } + } "handle missing }" in { - val input = "domain foo is { author nobody is { ??? }\n" + val input = "domain foo is { author nobody is { ??? } \n" parseTopLevelDomain(input, _.contents.head) match { case Left(errors) => errors must not be empty - errors.head.message.contains("Expected one of (") must be(true) + if errors.head.message.startsWith("Expected one of (") && errors.head.message.contains("context") then succeed + else fail(errors.format) case Right(_) => fail("Missing closing brace should make an error") } } @@ -220,8 +230,9 @@ class ParserTest extends ParsingTest { content mustBe Invariant( (1, 11, rpi), Identifier((1, 11, rpi), "large"), - Option(LiteralString((1,20,rpi), "x is greater or equal to 10")), - None, None + Option(LiteralString((1, 20, rpi), "x is greater or equal to 10")), + None, + None ) } } @@ -245,7 +256,7 @@ class ParserTest extends ParsingTest { State( (3, 3, rpi), Identifier((3, 9, rpi), "BurgerState"), - TypeRef((3, 24, rpi), PathIdentifier((3, 29, rpi), Seq("BurgerStruct"))), + TypeRef((3, 24, rpi), "type", PathIdentifier((3, 29, rpi), Seq("BurgerStruct"))), Seq(Handler((4, 11, rpi), Identifier((4, 11, rpi), "BurgerHandler"))) ) ), @@ -260,13 +271,9 @@ class ParserTest extends ParsingTest { (2, 50, rpi), Identifier((2, 50, rpi), "x"), Strng((2, 53, rpi), None, None), - None, - None ) ) - ), - None, - None + ) ) ) ) @@ -297,8 +304,8 @@ class ParserTest extends ParsingTest { "allow functions" in { val input = """ |function foo is { - | requires {b:Boolean} - | returns {i:Integer} + | requires { b : Boolean} + | returns { i : Integer} | body { ??? } |} |""".stripMargin @@ -315,13 +322,15 @@ class ParserTest extends ParsingTest { Some( Aggregation( _, - Seq(Field(_, Identifier(_, "b"), Bool(_), _, _)), _ + Seq(Field(_, Identifier(_, "b"), Bool(_), _, _, _)), + _ ) ), Some( Aggregation( _, - Seq(Field(_, Identifier(_, "i"), Integer(_), _, _)), _ + Seq(Field(_, Identifier(_, "i"), Integer(_), _, _, _)), + _ ) ), _, @@ -332,7 +341,8 @@ class ParserTest extends ParsingTest { _, _, None, - None + None, + List() ) => } } diff --git a/language/src/test/scala/com/reactific/riddl/language/parsing/StreamingParserTest.scala b/language/src/test/scala/com/reactific/riddl/language/parsing/StreamingParserTest.scala index df9be8797..615784377 100644 --- a/language/src/test/scala/com/reactific/riddl/language/parsing/StreamingParserTest.scala +++ b/language/src/test/scala/com/reactific/riddl/language/parsing/StreamingParserTest.scala @@ -32,7 +32,7 @@ class StreamingParserTest extends ParsingTest { (row + 2, 3, rpi), Identifier((row + 2, 10, rpi), "Weather"), TypeRef( - (row + 2, 21, rpi), + (row + 2, 21, rpi), "command", PathIdentifier((row + 2, 29, rpi), List("Forecast")) ) ) @@ -133,7 +133,7 @@ class StreamingParserTest extends ParsingTest { (7, 5, rpi), Identifier((7, 12, rpi), "Weather"), TypeRef( - (7, 23, rpi), + (7, 23, rpi), "command", PathIdentifier((7, 31, rpi), List("Forecast")) ) ) @@ -169,7 +169,7 @@ class StreamingParserTest extends ParsingTest { (11, 5, rpi), Identifier((11, 11, rpi), "Weather"), TypeRef( - (11, 22, rpi), + (11, 22, rpi), "command", PathIdentifier((11, 30, rpi), List("Forecast")) ) ) @@ -179,7 +179,7 @@ class StreamingParserTest extends ParsingTest { (12, 5, rpi), Identifier((12, 12, rpi), "CurrentTemp"), TypeRef( - (12, 27, rpi), + (12, 27, rpi), "command", PathIdentifier((12, 35, rpi), List("Temperature")) ) ) @@ -215,7 +215,7 @@ class StreamingParserTest extends ParsingTest { (16, 5, rpi), Identifier((16, 11, rpi), "CurrentTemp"), TypeRef( - (16, 26, rpi), + (16, 26, rpi), "command", PathIdentifier((16, 34, rpi), List("Temperature")) ) ) diff --git a/language/src/test/scala/com/reactific/riddl/language/parsing/TopLevelParserTest.scala b/language/src/test/scala/com/reactific/riddl/language/parsing/TopLevelParserTest.scala index 6bb44e0e5..607cddf84 100644 --- a/language/src/test/scala/com/reactific/riddl/language/parsing/TopLevelParserTest.scala +++ b/language/src/test/scala/com/reactific/riddl/language/parsing/TopLevelParserTest.scala @@ -27,7 +27,9 @@ class TopLevelParserTest extends ParsingTestBase { Seq.empty[Author] ) val simpleDomainResults: AST.RootContainer = RootContainer( + Seq.empty, List(simpleDomain), + Seq.empty, List( RiddlParserInput( new File("language/src/test/input/domains/simpleDomain.riddl") @@ -53,7 +55,9 @@ class TopLevelParserTest extends ParsingTestBase { val stringContents = source.mkString val result = TopLevelParser.parse(stringContents, origin) val expected = RootContainer( + Seq.empty, List(simpleDomain), + Seq.empty, List( StringParserInput( """domain foo is { @@ -69,7 +73,7 @@ class TopLevelParserTest extends ParsingTestBase { } "parse empty String" in { val expected = - RootContainer(List(), List(StringParserInput("", "string"))) + RootContainer(List(), List(), List(), List(StringParserInput("", "string"))) TopLevelParser.parse("") match { case Right(expected) => fail("Should have failed excpecting an author or domain") @@ -82,14 +86,16 @@ class TopLevelParserTest extends ParsingTestBase { "handle garbage" in { val expected = - RootContainer(List(), List(StringParserInput("", "string"))) - TopLevelParser.parse(" pweio afhj") match { + RootContainer(List(),List(),List(), List(StringParserInput("", "string"))) + TopLevelParser.parse(" pweio afhj", "handle garbage") match { case Right(expected) => fail("Should have failed excpecting an author or domain") case Left(messages) => messages.length mustBe(1) val msg = messages.head - msg.message must include("Expected one of (\"author\" | \"domain\"") + msg.message must include("Expected one of") + msg.message must include("\"author\"") + msg.message must include("\"domain\"") } } } diff --git a/language/src/test/scala/com/reactific/riddl/language/parsing/TypeParserTest.scala b/language/src/test/scala/com/reactific/riddl/language/parsing/TypeParserTest.scala index f1ac6bc37..cf171bb92 100644 --- a/language/src/test/scala/com/reactific/riddl/language/parsing/TypeParserTest.scala +++ b/language/src/test/scala/com/reactific/riddl/language/parsing/TypeParserTest.scala @@ -118,7 +118,7 @@ class TypeParserTest extends ParsingTest { checkDefinition[Type, Type](input, expected, identity) } "allow alternation" in { - val input = "type alt = one of { enum or stamp or url }" + val input = "type alt = one of { type enum or type stamp or type url }" val expected = Type( 1 -> 1, Identifier(1 -> 6, "alt"), @@ -127,13 +127,15 @@ class TypeParserTest extends ParsingTest { List( AliasedTypeExpression( 1 -> 21, - PathIdentifier(1 -> 21, Seq("enum")) + "type", + PathIdentifier(1 -> 26, Seq("enum")) ), AliasedTypeExpression( - 1 -> 29, - PathIdentifier(1 -> 29, Seq("stamp")) + 1 -> 34, + "type", + PathIdentifier(1 -> 39, Seq("stamp")) ), - AliasedTypeExpression(1 -> 38, PathIdentifier(1 -> 38, Seq("url"))) + AliasedTypeExpression(1 -> 48, "type", PathIdentifier(1 -> 53, Seq("url"))) ) ) ) @@ -150,6 +152,7 @@ class TypeParserTest extends ParsingTest { List( AliasedTypeExpression( (3, 21, rpi), + "type", PathIdentifier((3, 26, rpi), Seq("Foo")) ) ) @@ -158,7 +161,7 @@ class TypeParserTest extends ParsingTest { case Left(errors) => val msg = errors.map(_.format).mkString fail(msg) - case Right((Type(_, _, typeExp, _, _), _)) => typeExp mustBe expected + case Right((Type(_, _, typeExp, _, _, _), _)) => typeExp mustBe expected } } "allow aggregation" in { @@ -290,22 +293,6 @@ class TypeParserTest extends ParsingTest { checkDefinition[Type, Type](rip, expected, identity) } - "allow one or more in word style" in { - val rip = RiddlParserInput("type oneOrMoreA = many agg") - val expected = Type( - (1, 1, rip), - Identifier((1, 6, rip), "oneOrMoreA"), - OneOrMore( - (1, 24, rip), - AliasedTypeExpression( - (1, 24, rip), - PathIdentifier((1, 24, rip), Seq("agg")) - ) - ) - ) - checkDefinition[Type, Type](rip, expected, identity) - } - "allow one or more in regex style" in { val rip = RiddlParserInput("type oneOrMoreB = agg+") val expected = Type( @@ -314,7 +301,7 @@ class TypeParserTest extends ParsingTest { OneOrMore( (1, 19, rip), AliasedTypeExpression( - (1, 19, rip), + (1, 19, rip), "type", PathIdentifier((1, 19, rip), Seq("agg")) ) ) @@ -331,6 +318,7 @@ class TypeParserTest extends ParsingTest { (1, 33, rip), AliasedTypeExpression( (1, 33, rip), + "type", PathIdentifier((1, 33, rip), Seq("agg")) ) ) @@ -347,6 +335,7 @@ class TypeParserTest extends ParsingTest { (1, 26, rip), AliasedTypeExpression( (1, 26, rip), + "type", PathIdentifier((1, 26, rip), Seq("agg")) ) ) @@ -378,7 +367,7 @@ class TypeParserTest extends ParsingTest { val rip = RiddlParserInput(""" |domain foo is { | type Simple = String - | type Compound is { + | record Compound is { | s: Simple, | ns: many Number | } @@ -386,7 +375,7 @@ class TypeParserTest extends ParsingTest { | type Complex is { | a: Simple, | b: TimeStamp, - | c: many optional Compound, + | c: many optional record Compound, | d: optional Choices | } |} @@ -406,6 +395,7 @@ class TypeParserTest extends ParsingTest { Identifier((10, 5, rpi), "a"), AliasedTypeExpression( (10, 8, rpi), + "type", PathIdentifier((10, 8, rpi), Seq("Simple")) ) ), @@ -421,7 +411,8 @@ class TypeParserTest extends ParsingTest { (12, 22, rpi), AliasedTypeExpression( (12, 22, rpi), - PathIdentifier((12, 22, rpi), Seq("Compound")) + "record", + PathIdentifier((12, 29, rpi), Seq("Compound")) ) ) ), @@ -432,6 +423,7 @@ class TypeParserTest extends ParsingTest { (13, 17, rpi), AliasedTypeExpression( (13, 17, rpi), + "type", PathIdentifier((13, 17, rpi), Seq("Choices")) ) ) diff --git a/passes/src/main/scala/com/reactific/riddl/passes/Folding.scala b/passes/src/main/scala/com/reactific/riddl/passes/Folding.scala index b57c54c5c..b2852268d 100644 --- a/passes/src/main/scala/com/reactific/riddl/passes/Folding.scala +++ b/passes/src/main/scala/com/reactific/riddl/passes/Folding.scala @@ -212,7 +212,7 @@ object Folding { case a: AggregateUseCaseTypeExpression => // Message types have fields too, those fields are what we seek a.contents - case AliasedTypeExpression(_, pid) => + case AliasedTypeExpression(_, _, pid) => // if we're at a field that references another type then we // need to push that types path on the name stack adjustStacksForPid(searchFor, pid, parentStack, nameStack) @@ -225,7 +225,7 @@ object Folding { case a: Aggregation => a.contents case a: AggregateUseCaseTypeExpression => a.contents case Enumeration(_, enumerators) => enumerators - case AliasedTypeExpression(_, pid) => + case AliasedTypeExpression(_, _, pid) => // if we're at a type definition that references another type then // we need to push that type's path on the name stack adjustStacksForPid(searchFor, pid, parentStack, nameStack) @@ -241,7 +241,7 @@ object Folding { input.contents ++ output.contents case d: Definition => d.contents.flatMap { - case Include(_, contents, _) => contents + case Include(_, contents, _, _) => contents case d: Definition => Seq(d) } } diff --git a/passes/src/main/scala/com/reactific/riddl/passes/resolve/ResolutionPass.scala b/passes/src/main/scala/com/reactific/riddl/passes/resolve/ResolutionPass.scala index f754de1d1..5a437f998 100644 --- a/passes/src/main/scala/com/reactific/riddl/passes/resolve/ResolutionPass.scala +++ b/passes/src/main/scala/com/reactific/riddl/passes/resolve/ResolutionPass.scala @@ -111,7 +111,7 @@ case class ResolutionPass(input: PassInput, outputs: PassesOutput) extends Pass( resolveARef[Group](cg.group, parentsAsSeq) case gi: GenericInteraction => gi match { - case ArbitraryInteraction(_, _, from, _, to, _, _) => + case ArbitraryInteraction(_, _, from, _, to, _, _, _) => resolveARef[Definition](from, parentsAsSeq) resolveARef[Definition](to, parentsAsSeq) case ti: ShowOutputInteraction => @@ -162,7 +162,7 @@ case class ResolutionPass(input: PassInput, outputs: PassesOutput) extends Pass( typ match { case UniqueId(_, entityPath) => resolveAPathId[Entity](entityPath, parents) - case AliasedTypeExpression(_, pathId) => + case AliasedTypeExpression(_, _, pathId) => resolveAPathId[Type](pathId, parents) case agg: AggregateTypeExpression => agg.fields.foreach { (fld: Field) => @@ -680,7 +680,7 @@ case class ResolutionPass(input: PassInput, outputs: PassesOutput) extends Pass( case a: AggregateUseCaseTypeExpression => // Any kind of Aggregate's fields are candidates for resolution a.contents - case AliasedTypeExpression(_, pid) => + case AliasedTypeExpression(_, _, pid) => // if we're at a field that references another type then the candidates // are that type's fields. To solve this we need to push // that type's path on the name stack to be resolved @@ -728,7 +728,7 @@ case class ResolutionPass(input: PassInput, outputs: PassesOutput) extends Pass( inputs.contents ++ outputs.contents case d: Definition => d.contents.flatMap { - case Include(_, contents, _) => + case Include(_, contents, _, _) => contents case d: Definition => Seq(d) diff --git a/passes/src/main/scala/com/reactific/riddl/passes/validate/BasicValidation.scala b/passes/src/main/scala/com/reactific/riddl/passes/validate/BasicValidation.scala index 3691eb5ca..afbef4795 100644 --- a/passes/src/main/scala/com/reactific/riddl/passes/validate/BasicValidation.scala +++ b/passes/src/main/scala/com/reactific/riddl/passes/validate/BasicValidation.scala @@ -127,7 +127,7 @@ trait BasicValidation { } else { checkRefAndExamine[Type](ref, topDef, parents) { (definition: Definition) => definition match { - case Type(_, _, typ, _, _) => + case Type(_, _, typ, _, _, _) => typ match { case AggregateUseCaseTypeExpression(_, mk, _, _) => check( @@ -155,7 +155,7 @@ trait BasicValidation { } } - @tailrec final def getPathIdType( + @tailrec private final def getPathIdType( pid: PathIdentifier, parents: Seq[Definition] ): Option[TypeExpression] = { @@ -170,14 +170,14 @@ trait BasicValidation { case Some(f: Field) => Some(f.typeEx) case Some(c: Constant) => Some(c.typeEx) case Some(s: State) => - Some(AliasedTypeExpression(s.typ.loc, s.typ.pathId)) - case Some(Inlet(_, _, typ, _, _)) => - Some(AliasedTypeExpression(typ.loc, typ.pathId)) - case Some(Outlet(_, _, typ, _, _)) => - Some(AliasedTypeExpression(typ.loc, typ.pathId)) + Some(AliasedTypeExpression(s.typ.loc, "state", s.typ.pathId)) + case Some(Inlet(_, _, typ, _, _, _)) => + Some(AliasedTypeExpression(typ.loc, "inlet", typ.pathId)) + case Some(Outlet(_, _, typ, _, _, _)) => + Some(AliasedTypeExpression(typ.loc, "outlet", typ.pathId)) case Some(connector: Connector) => connector.flows - .map(typeRef => AliasedTypeExpression(typeRef.loc, typeRef.pathId)) + .map(typeRef => AliasedTypeExpression(typeRef.loc, "connector", typeRef.pathId)) .orElse(Option.empty[TypeExpression]) case Some(streamlet: Streamlet) => streamlet.outlets.headOption match @@ -186,7 +186,7 @@ trait BasicValidation { case Some(_) => Option.empty[TypeExpression] } candidate match { - case Some(AliasedTypeExpression(_, pid)) => + case Some(AliasedTypeExpression(_, _, pid)) => getPathIdType(pid, maybeDef.toSeq) case Some(other: TypeExpression) => Some(other) case None => None diff --git a/passes/src/main/scala/com/reactific/riddl/passes/validate/DefinitionValidation.scala b/passes/src/main/scala/com/reactific/riddl/passes/validate/DefinitionValidation.scala index a29d1ccc4..5a958dadd 100644 --- a/passes/src/main/scala/com/reactific/riddl/passes/validate/DefinitionValidation.scala +++ b/passes/src/main/scala/com/reactific/riddl/passes/validate/DefinitionValidation.scala @@ -21,7 +21,7 @@ trait DefinitionValidation extends BasicValidation { def checkOptions[T <: OptionValue](options: Seq[T], loc: At): this.type = { check( options.sizeIs == options.distinct.size, - "Options should not be repeated", + "RiddlOptions should not be repeated", Error, loc ) diff --git a/passes/src/main/scala/com/reactific/riddl/passes/validate/TypeValidation.scala b/passes/src/main/scala/com/reactific/riddl/passes/validate/TypeValidation.scala index 2742f05a2..04c671dec 100644 --- a/passes/src/main/scala/com/reactific/riddl/passes/validate/TypeValidation.scala +++ b/passes/src/main/scala/com/reactific/riddl/passes/validate/TypeValidation.scala @@ -161,7 +161,7 @@ trait TypeValidation extends DefinitionValidation { parents: Seq[Definition] ): this.type = { typ match { - case AliasedTypeExpression(_, id: PathIdentifier) => + case AliasedTypeExpression(_, _, id: PathIdentifier) => checkPathRef[Type](id, defn, parents) case mt: AggregateUseCaseTypeExpression => checkAggregateUseCase(mt, defn, parents) diff --git a/passes/src/main/scala/com/reactific/riddl/passes/validate/ValidationPass.scala b/passes/src/main/scala/com/reactific/riddl/passes/validate/ValidationPass.scala index 39e28f7f3..389377db5 100644 --- a/passes/src/main/scala/com/reactific/riddl/passes/validate/ValidationPass.scala +++ b/passes/src/main/scala/com/reactific/riddl/passes/validate/ValidationPass.scala @@ -749,9 +749,9 @@ case class ValidationPass( destination match { case Some(d) if d.isAppRelated => d match { - case output@Output(loc, _, id, _, putOut, _, _, _) => + case output@Output(loc, _, id, _, putOut, _, _, _, _) => checkTypeRef(putOut, parents.head, parents.tail) match { - case Some(Type(_, _, typEx, _, _)) if typEx.isContainer => + case Some(Type(_, _, typEx, _, _, _)) if typEx.isContainer => typEx match { case ate: AggregateUseCaseTypeExpression if ate.usecase == EventCase || ate.usecase == ResultCase => @@ -775,9 +775,9 @@ case class ValidationPass( destination match { case Some(d) if d.isVital => o match { - case input@Input(loc, _, id, _, putIn, _, _, _) => + case input@Input(loc, _, id, _, putIn, _, _, _, _) => checkTypeRef(putIn, parents.head, parents.tail) match { - case Some(Type(_, _, typEx, _, _)) if typEx.isContainer => + case Some(Type(_, _, typEx, _, _, _)) if typEx.isContainer => typEx match { case ate: AggregateUseCaseTypeExpression if ate.usecase == CommandCase || ate.usecase == QueryCase => @@ -810,18 +810,18 @@ case class ValidationPass( checkDefinition(parents, interaction) checkDescription(interaction) interaction match { - case SelfInteraction(_, _, from, _, _, _) => + case SelfInteraction(_, _, from, _, _, _, _) => checkRef[Definition](from, interaction, parents) - case TakeInputInteraction(_, _, user: UserRef, _, inputRef: InputRef, _, _) => + case TakeInputInteraction(_, _, user: UserRef, _, inputRef: InputRef, _, _, _) => checkRef[User](user, interaction, parents) checkRef[Input](inputRef, interaction, parents) - case ArbitraryInteraction(_, _, from, _, to, _, _) => + case ArbitraryInteraction(_, _, from, _, to, _, _, _) => checkRef[Definition](from, interaction, parents) checkRef[Definition](to, interaction, parents) val origin = resolution.refMap.definitionOf[Definition](from.pathId, parents.head) val destination = resolution.refMap.definitionOf[Definition](to.pathId, parents.head) validateArbitraryInteraction(origin, destination, parents) - case ShowOutputInteraction(_, _, from: OutputRef, _, to: UserRef, _, _) => + case ShowOutputInteraction(_, _, from: OutputRef, _, to: UserRef, _, _, _) => checkRef[Output](from, interaction, parents) checkRef[User](to, interaction, parents) case _: VagueInteraction => diff --git a/passes/src/test/scala/com/reactific/riddl/passes/resolve/PathResolutionPassTest.scala b/passes/src/test/scala/com/reactific/riddl/passes/resolve/PathResolutionPassTest.scala index c693c18ba..147db5d8a 100644 --- a/passes/src/test/scala/com/reactific/riddl/passes/resolve/PathResolutionPassTest.scala +++ b/passes/src/test/scala/com/reactific/riddl/passes/resolve/PathResolutionPassTest.scala @@ -297,6 +297,7 @@ class PathResolutionPassTest extends ResolvingTest { Identifier(eL, "C2_T"), AliasedTypeExpression( eL, + "type", PathIdentifier(eL, Seq("D", "C1", "C1_T")) ) ) diff --git a/passes/src/test/scala/com/reactific/riddl/passes/validate/EntityValidatorTest.scala b/passes/src/test/scala/com/reactific/riddl/passes/validate/EntityValidatorTest.scala index 02e22e8d2..8e2ea674d 100644 --- a/passes/src/test/scala/com/reactific/riddl/passes/validate/EntityValidatorTest.scala +++ b/passes/src/test/scala/com/reactific/riddl/passes/validate/EntityValidatorTest.scala @@ -15,7 +15,7 @@ class EntityValidatorTest extends ValidatingTest { "EntityValidator" should { "handle a variety of options" in { val input = """entity WithOptions is { - | options(fsm, mq, aggregate, transient, available) + | options(finite-state-machine, message-queue, aggregate, transient, available) | ??? |} |""".stripMargin @@ -28,17 +28,17 @@ class EntityValidatorTest extends ValidatingTest { msgs.count(_.kind.isMissing) numMissing mustBe 3 entity.options must contain(EntityIsFiniteStateMachine((3, 10, rpi))) - entity.options must contain(EntityMessageQueue((3, 15, rpi))) - entity.options must contain(EntityIsAggregate((3, 19, rpi))) - entity.options must contain(EntityTransient((3, 30, rpi))) - entity.options must contain(EntityIsAvailable((3, 41, rpi))) + entity.options must contain(EntityMessageQueue((3, 32, rpi))) + entity.options must contain(EntityIsAggregate((3, 47, rpi))) + entity.options must contain(EntityTransient((3, 58, rpi))) + entity.options must contain(EntityIsAvailable((3, 69, rpi))) } } "handle entity with multiple states" in { val input = """entity MultiState is { - | options(fsm) + | options(finite-state-machine) | record fields is { field: String } | state foo of MultiState.fields is { handler x is {???} } | state bar of MultiState.fields is { handler x is {???} } @@ -53,7 +53,7 @@ class EntityValidatorTest extends ValidatingTest { "error for finite-state-machine entities without at least two states" in { val input = """entity MultiState is { - | options(fsm) + | options(finite-state-machine) | record fields is { field: String } | state foo of MultiState.fields is { handler x is {???} } |}""".stripMargin diff --git a/passes/src/test/scala/com/reactific/riddl/passes/validate/FunctionValidatorTest.scala b/passes/src/test/scala/com/reactific/riddl/passes/validate/FunctionValidatorTest.scala index 9c71605e5..798058f1a 100644 --- a/passes/src/test/scala/com/reactific/riddl/passes/validate/FunctionValidatorTest.scala +++ b/passes/src/test/scala/com/reactific/riddl/passes/validate/FunctionValidatorTest.scala @@ -27,8 +27,8 @@ class FunctionValidatorTest extends ValidatingTest { AST.Function( _, Identifier(_, "foo"), - Some(Aggregation(_, Seq(Field(_, _, AST.Bool(_), _, _)),_)), - Some(Aggregation(_, Seq(Field(_, _, AST.Integer(_), _, _)),_)), + Some(Aggregation(_, Seq(Field(_, _, AST.Bool(_), _, _, _)), _)), + Some(Aggregation(_, Seq(Field(_, _, AST.Integer(_), _, _, _)), _)), _, _, _, @@ -36,8 +36,9 @@ class FunctionValidatorTest extends ValidatingTest { _, _, _, - None, - None + _, + _, + _ ) ) => } diff --git a/passes/src/test/scala/com/reactific/riddl/passes/validate/RegressionTests.scala b/passes/src/test/scala/com/reactific/riddl/passes/validate/RegressionTests.scala index ffd2f3564..285bd9f97 100644 --- a/passes/src/test/scala/com/reactific/riddl/passes/validate/RegressionTests.scala +++ b/passes/src/test/scala/com/reactific/riddl/passes/validate/RegressionTests.scala @@ -70,25 +70,28 @@ class RegressionTests extends ValidatingTest { } "catch types with predefined expression with a suffix" in { val input = """domain foo { - | type Bug is IntegerRange + | type Bug: IntegerRange |}""".stripMargin def extract(root: RootContainer): Type = { root.domains.head.types.head } parseTopLevelDomain[Type](input, extract) match { - case Left(messages) => fail(messages.format) + case Left(messages) => + val errors = messages.justErrors + errors.size mustBe 1 + errors.head.message contains ("whitespace after keyword") case Right((typ, rpi)) => val expected: Type = Type( (2, 3, rpi), Identifier((2, 8, rpi), "Bug"), AliasedTypeExpression( (2, 15, rpi), - PathIdentifier((2, 15, rpi), List("IntegerRange")) + "type", + PathIdentifier((2, 20, rpi), List("IntegerRange")) ) ) typ mustBe expected - } } @@ -97,8 +100,8 @@ class RegressionTests extends ValidatingTest { | type DateRange = Duration | type SomePlace = Location | type Thing is { - | locationId: SomePlace, - | schedule: DateRange+ + | locationId: type SomePlace, + | schedule: type DateRange+ | } |} |""".stripMargin @@ -119,7 +122,8 @@ class RegressionTests extends ValidatingTest { Identifier((5, 5, rpi), "locationId"), AliasedTypeExpression( (5, 17, rpi), - PathIdentifier((5, 17, rpi), List("SomePlace")) + "type", + PathIdentifier((5, 22, rpi), List("SomePlace")) ), None, None @@ -131,7 +135,8 @@ class RegressionTests extends ValidatingTest { (6, 15, rpi), AliasedTypeExpression( (6, 15, rpi), - PathIdentifier((6, 15, rpi), List("DateRange")) + "type", + PathIdentifier((6, 20, rpi), List("DateRange")) ) ), None, diff --git a/passes/src/test/scala/com/reactific/riddl/passes/validate/ValidatingTest.scala b/passes/src/test/scala/com/reactific/riddl/passes/validate/ValidatingTest.scala index 2373862c1..2a97af2e9 100644 --- a/passes/src/test/scala/com/reactific/riddl/passes/validate/ValidatingTest.scala +++ b/passes/src/test/scala/com/reactific/riddl/passes/validate/ValidatingTest.scala @@ -111,7 +111,7 @@ abstract class ValidatingTest extends ParsingTest { validator(Domain(loc, Identifier(loc, "stand-in")), input, errors) } case Right((model: Domain, rpi)) => - val root = RootContainer(Seq(model), Seq(rpi)) + val root = RootContainer(List(),Seq(model), List(), Seq(rpi)) runStandardPasses(root, options, shouldFailOnErrors) match { case Left(errors) => fail(errors.format) diff --git a/passes/src/test/scala/com/reactific/riddl/passes/validate/ValidationTest.scala b/passes/src/test/scala/com/reactific/riddl/passes/validate/ValidationTest.scala index ba82dced2..6d79d8afd 100644 --- a/passes/src/test/scala/com/reactific/riddl/passes/validate/ValidationTest.scala +++ b/passes/src/test/scala/com/reactific/riddl/passes/validate/ValidationTest.scala @@ -37,7 +37,7 @@ class ValidationTest extends ParsingTest { "find the parent of an existent child" in { val aType = Type(At(), Identifier(At(), "bar"), Strng(At())) val domain = Domain(At(), Identifier(At(), "foo"), types = aType :: Nil) - val root = RootContainer(Seq(domain), Seq.empty) + val root = RootContainer(List(), Seq(domain), List(), Seq.empty) val outputs = PassesOutput() val output = Pass.runSymbols(PassInput(root), outputs) output.parentOf(aType) mustBe Some(domain) @@ -45,7 +45,7 @@ class ValidationTest extends ParsingTest { "not find the parent of a non-existent child" in { val aType = Type(At(), Identifier(At(), "bar"), Strng(At())) val domain = Domain(At(), Identifier(At(), "foo"), types = Nil) - val root = RootContainer(Seq(domain), Seq.empty) + val root = RootContainer(List(),Seq(domain), List(), Seq.empty) val outputs = PassesOutput() val output = Pass.runSymbols(PassInput(root), outputs) output.parentOf(aType) mustBe None diff --git a/prettify/src/main/scala/com/reactific/riddl/prettify/PrettifyPass.scala b/prettify/src/main/scala/com/reactific/riddl/prettify/PrettifyPass.scala index e1748935d..87b2c6124 100644 --- a/prettify/src/main/scala/com/reactific/riddl/prettify/PrettifyPass.scala +++ b/prettify/src/main/scala/com/reactific/riddl/prettify/PrettifyPass.scala @@ -8,8 +8,8 @@ package com.reactific.riddl.prettify import com.reactific.riddl.language.AST.* import com.reactific.riddl.language.Messages.Messages +import com.reactific.riddl.language.parsing.{Keyword,Readability} import com.reactific.riddl.language.{AST, Messages} -import com.reactific.riddl.language.parsing.Terminals.* import com.reactific.riddl.passes.{HierarchyPass, PassInfo, PassInput, PassOutput, PassesOutput} import com.reactific.riddl.passes.resolve.ResolutionPass import com.reactific.riddl.passes.symbols.SymbolsPass @@ -30,26 +30,26 @@ object PrettifyPass extends PassInfo { */ def keyword(definition: Definition): String = { definition match { - case _: Adaptor => Keywords.adaptor - case _: Context => Keywords.context - case _: Connector => Keywords.connector - case _: Domain => Keywords.domain - case _: Entity => Keywords.entity + case _: Adaptor => Keyword.adaptor + case _: Context => Keyword.context + case _: Connector => Keyword.connector + case _: Domain => Keyword.domain + case _: Entity => Keyword.entity case _: Enumerator => "" case _: Field => "" - case _: Function => Keywords.function - case _: Handler => Keywords.handler - case _: Inlet => Keywords.inlet - case _: Invariant => Keywords.invariant - case _: Outlet => Keywords.outlet + case _: Function => Keyword.function + case _: Handler => Keyword.handler + case _: Inlet => Keyword.inlet + case _: Invariant => Keyword.invariant + case _: Outlet => Keyword.outlet case s: Streamlet => s.shape.keyword case _: RootContainer => "root" - case _: Saga => Keywords.saga - case _: SagaStep => Keywords.step - case _: State => Keywords.state - case _: Epic => Keywords.epic - case _: Term => Keywords.term - case _: Type => Keywords.`type` + case _: Saga => Keyword.saga + case _: SagaStep => Keyword.step + case _: State => Keyword.state + case _: Epic => Keyword.epic + case _: Term => Keyword.term + case _: Type => Keyword.type_ case _ => "unknown" } } @@ -197,12 +197,9 @@ case class PrettifyPass(input: PassInput, outputs: PassesOutput, state: Prettify val us = epic.userStory.getOrElse(UserStory()) val user = us.user.pathId st.openDef(epic) - .addIndent("user") + .addIndent("user ") .add(user.format) - .add(" ") - .add(Readability.wants) - .add(" ") - .add(Readability.to) + .add(" wants to ") .add(s"\"${us.capability.s}\" so that \"${us.benefit.s}\"") .nl } diff --git a/prettify/src/main/scala/com/reactific/riddl/prettify/RiddlFileEmitter.scala b/prettify/src/main/scala/com/reactific/riddl/prettify/RiddlFileEmitter.scala index e853e8773..bb3339cfa 100644 --- a/prettify/src/main/scala/com/reactific/riddl/prettify/RiddlFileEmitter.scala +++ b/prettify/src/main/scala/com/reactific/riddl/prettify/RiddlFileEmitter.scala @@ -9,12 +9,11 @@ package com.reactific.riddl.prettify import java.nio.file.Files import java.nio.file.Path import com.reactific.riddl.language.AST.* +import com.reactific.riddl.language.parsing.Keyword import com.reactific.riddl.prettify.PrettifyPass.keyword import com.reactific.riddl.utils.TextFileWriter import java.nio.charset.StandardCharsets -import com.reactific.riddl.language.parsing.Terminals.* - /** Unit Tests For RiddlFileEmitter */ @SuppressWarnings(Array("org.wartremover.warts.Var")) case class RiddlFileEmitter(filePath: Path) extends TextFileWriter { @@ -220,7 +219,7 @@ case class RiddlFileEmitter(filePath: Path) extends TextFileWriter { def emitTypeExpression(typEx: TypeExpression): this.type = { typEx match { case string: Strng => emitString(string) - case AliasedTypeExpression(_, id) => this.add(id.format) + case AliasedTypeExpression(_, _, id) => this.add(id.format) case URL(_, scheme) => this .add(s"URL${scheme.fold("")(s => "\"" + s.s + "\"")}") @@ -233,7 +232,7 @@ case class RiddlFileEmitter(filePath: Path) extends TextFileWriter { case Decimal(_, whl, frac) => this.add(s"Decimal($whl,$frac)") case EntityReferenceTypeExpression(_, er) => this - .add(s"${Keywords.reference} to ${er.format}") + .add(s"${Keyword.reference} to ${er.format}") case pattern: Pattern => emitPattern(pattern) case UniqueId(_, id) => this.add(s"Id(${id.format}) ") case Optional(_, typex) => this.emitTypeExpression(typex).add("?") diff --git a/prettify/src/test/scala/com/reactific/riddl/prettify/PrettifyTest.scala b/prettify/src/test/scala/com/reactific/riddl/prettify/PrettifyTest.scala index 077852bb8..0363e2ddf 100644 --- a/prettify/src/test/scala/com/reactific/riddl/prettify/PrettifyTest.scala +++ b/prettify/src/test/scala/com/reactific/riddl/prettify/PrettifyTest.scala @@ -19,13 +19,8 @@ import java.nio.file.Path /** Test The PrettifyPass's ability to generate consistent output */ class PrettifyTest extends RiddlFilesTestBase { - def checkAFile(rootDir: Path, file: File): Assertion = {checkAFile(file)} - - def outputWithLineNos(output: String): String = { - output.split('\n').zipWithIndex.map { case (str, n) => f"$n%3d $str" } - .mkString("\n") - } - + def checkAFile(rootDir: Path, file: File): Assertion = { checkAFile(file) } + def runPrettify(source: RiddlParserInput, run: String): String = { val passes = standardPasses ++ Seq( { (input: PassInput, outputs: PassesOutput) => @@ -48,7 +43,7 @@ class PrettifyTest extends RiddlFilesTestBase { /** Parse and prettify a file twice and compare the original with the third version. */ def checkAFile( - file: File, + file: File ): Assertion = { val input1 = RiddlParserInput(file.toPath) val output1 = runPrettify(input1, "first") @@ -60,14 +55,19 @@ class PrettifyTest extends RiddlFilesTestBase { } "PrettifyTranslator" should { - "check domains" in { processADirectory("testkit/src/test/input/domains") } + "check domains" in { + processADirectory("testkit/src/test/input/domains") + } "check enumerations" in { processADirectory("testkit/src/test/input/enumerations") } - - "check mappings" in { processADirectory("testkit/src/test/input/mappings") } - "check ranges" in { processADirectory("testkit/src/test/input/ranges") } - "check everything.riddl" in { + "check mappings" in { + processADirectory("testkit/src/test/input/mappings") + } + "check ranges" in { + processADirectory("testkit/src/test/input/ranges") + } + "check everything.riddl" in { processAFile("testkit/src/test/input/everything.riddl") } "check petstore.riddl" in { diff --git a/project/plugins.sbt b/project/plugins.sbt index ac71bd9dc..2638a5635 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,4 +1,3 @@ -addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.11.1") addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.6.4") addSbtPlugin("org.jetbrains" % "sbt-ide-settings" % "1.0.0") addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.2") diff --git a/riddlc/src/test/scala/com/reactific/riddl/OptionsReadingTest.scala b/riddlc/src/test/scala/com/reactific/riddl/RiddlOptionsReadingTest.scala similarity index 95% rename from riddlc/src/test/scala/com/reactific/riddl/OptionsReadingTest.scala rename to riddlc/src/test/scala/com/reactific/riddl/RiddlOptionsReadingTest.scala index 15406fc62..46df76f5d 100644 --- a/riddlc/src/test/scala/com/reactific/riddl/OptionsReadingTest.scala +++ b/riddlc/src/test/scala/com/reactific/riddl/RiddlOptionsReadingTest.scala @@ -4,7 +4,7 @@ import org.scalatest.wordspec.AnyWordSpec import java.nio.file.Path -class OptionsReadingTest extends AnyWordSpec with Matchers { +class RiddlOptionsReadingTest extends AnyWordSpec with Matchers { val confFile = "riddlc/src/test/input/cmdoptions.conf" diff --git a/riddlc/src/test/scala/com/reactific/riddl/RunRiddlcOnArbitraryTest.scala b/riddlc/src/test/scala/com/reactific/riddl/RunRiddlcOnArbitraryTest.scala index 6aa0d1763..f4db24f99 100644 --- a/riddlc/src/test/scala/com/reactific/riddl/RunRiddlcOnArbitraryTest.scala +++ b/riddlc/src/test/scala/com/reactific/riddl/RunRiddlcOnArbitraryTest.scala @@ -43,11 +43,13 @@ class RunRiddlcOnArbitraryTest extends RunCommandSpecBase { validateLocalProject(cwd, config) } "validate OffTheTop" in { + pending val cwd = "/Users/reid/Code/Improving/OffTheTop" val config = "design/src/main/riddl/OffTheTop.conf" validateLocalProject(cwd, config) } "validate kalix template" in { + pending val cwd = "/Users/reid/Code/Improving/kalix-improving-template" val config = "design/src/main/riddl/ksoapp.conf" validateLocalProject(cwd, config) diff --git a/stats/src/main/scala/com/reactific/riddl/stats/StatsPass.scala b/stats/src/main/scala/com/reactific/riddl/stats/StatsPass.scala index b096988ef..d3b164e73 100644 --- a/stats/src/main/scala/com/reactific/riddl/stats/StatsPass.scala +++ b/stats/src/main/scala/com/reactific/riddl/stats/StatsPass.scala @@ -221,7 +221,7 @@ case class StatsPass(input: PassInput, outputs: PassesOutput) extends Collecting val specsForDefinition: Int = 0 + 1 // Brief Description + 1 // Description - + 1 // Options + + 1 // RiddlOptions + 1 // Authors + 1 // Terms diff --git a/testkit/src/main/scala/com/reactific/riddl/testkit/ParsingTest.scala b/testkit/src/main/scala/com/reactific/riddl/testkit/ParsingTest.scala index ff7ccdeeb..2003bbb15 100644 --- a/testkit/src/main/scala/com/reactific/riddl/testkit/ParsingTest.scala +++ b/testkit/src/main/scala/com/reactific/riddl/testkit/ParsingTest.scala @@ -54,18 +54,18 @@ case class TestParser(input: RiddlParserInput, throwOnError: Boolean = false) } def parseTopLevelDomains: Either[Messages, RootContainer] = { - expectMultiple("test case", root(_)).map { case (defs: Seq[RootDefinition], rpi: RiddlParserInput) => - RootContainer(defs, Seq(rpi)) - } + parseRootContainer(withVerboseFailures = true) } def parseTopLevelDomain[T <: RiddlNode]( extract: RootContainer => T ): Either[Messages, (T, RiddlParserInput)] = { - expectMultiple[RootDefinition]("test case", root(_)).map { - case (defs: Seq[RootDefinition], rpi: RiddlParserInput) => - val rc = RootContainer(defs, Seq(rpi)) - extract(rc) -> rpi + parseRootContainer(withVerboseFailures = true).map { (root: RootContainer) => + val rpi = root.inputs.headOption match { + case Some(input) => input + case None => current + } + extract(root) -> rpi } } diff --git a/testkit/src/main/scala/com/reactific/riddl/testkit/RunCommandOnExamplesTest.scala b/testkit/src/main/scala/com/reactific/riddl/testkit/RunCommandOnExamplesTest.scala index ed397a0bd..c15f217cb 100644 --- a/testkit/src/main/scala/com/reactific/riddl/testkit/RunCommandOnExamplesTest.scala +++ b/testkit/src/main/scala/com/reactific/riddl/testkit/RunCommandOnExamplesTest.scala @@ -38,7 +38,7 @@ import scala.jdk.CollectionConverters.IteratorHasAsScala * temporary directory, and the output is compared to the expected output in the example. * * @tparam OPT - * The class for the Options of the command + * The class for the RiddlOptions of the command * @tparam CMD * The class for the Command * @param commandName diff --git a/testkit/src/test/input/check/everything/everything.check b/testkit/src/test/input/check/everything/everything.check index 78d21df3f..e20d991be 100644 --- a/testkit/src/test/input/check/everything/everything.check +++ b/testkit/src/test/input/check/everything/everything.check @@ -1,300 +1,300 @@ -Missing: testkit/src/test/input/check/everything/everything.riddl(30:5): - Type 'ident' should have a description: - type ident is UUID // Define ident as an Id +Missing: testkit/src/test/input/check/everything/everything.riddl(1:1): + Domain 'Everything' should have a description: + domain Everything is { + ^ +Style: testkit/src/test/input/check/everything/everything.riddl(30:5): + Type 'alt' should start with a capital letter: + type alt is one of { enum or stamp or url } ^ -Missing: testkit/src/test/input/check/everything/everything.riddl(71:7): - Vital definitions should have an author reference: +Usage: testkit/src/test/input/check/everything/everything.riddl(51:7): + Function 'whenUnderTheInfluence' is unused: function whenUnderTheInfluence is { ^ -Missing: testkit/src/test/input/check/everything/everything.riddl(33:5): +Missing: testkit/src/test/input/check/everything/everything.riddl(40:5): + Type 'optional' should have a description: + type optional is agg? + ^ +Style: testkit/src/test/input/check/everything/everything.riddl(8:20): + Inlet 'Commands' overloads Outlet 'Commands' at everything.riddl(7:24): + sink Sink is { inlet Commands is DoAThing } explained as "Data Sink" + ^ +Missing: testkit/src/test/input/check/everything/everything.riddl(6:3): + Context 'APlant' should have a description: + context APlant is { + ^ +Missing: testkit/src/test/input/check/everything/everything.riddl(45:7): + Type 'somethingDate' should have a description: + type somethingDate is Date + ^ +Missing: testkit/src/test/input/check/everything/everything.riddl(19:5): + Type 'num' should have a description: + type num is Number // Define num as a Number + ^ +Style: testkit/src/test/input/check/everything/everything.riddl(19:5): + Type 'num' should start with a capital letter: + type num is Number // Define num as a Number + ^ +Missing: testkit/src/test/input/check/everything/everything.riddl(24:5): Type 'stamp' should have a description: type stamp is TimeStamp // Define stamp as a TimeStamp ^ -Missing: testkit/src/test/input/check/everything/everything.riddl(38:5): - Type 'enum' should have a description: - type enum is any of { Apple Pear Peach(21) Persimmon(42) } +Missing: testkit/src/test/input/check/everything/everything.riddl(32:5): + Type 'agg' should have a description: + type agg is { + ^ +Style: testkit/src/test/input/check/everything/everything.riddl(22:5): + Type 'dat' should start with a capital letter: + type dat is Date // Define dat as a Date ^ -Usage: testkit/src/test/input/check/everything/everything.riddl(51:5): +Usage: testkit/src/test/input/check/everything/everything.riddl(38:5): Type 'oneOrMore' is unused: type oneOrMore is many agg ^ -Missing: testkit/src/test/input/check/everything/everything.riddl(41:5): - Type 'alt' should have a description: - type alt is one of { enum or stamp or url } - ^ -Style: testkit/src/test/input/check/everything/everything.riddl(72:20): - Field identifier 'n' is too short. The minimum length is 3: - requires { n: Nothing } - ^ -Missing: testkit/src/test/input/check/everything/everything.riddl(62:5): - Vital definitions should have an author reference: +Missing: testkit/src/test/input/check/everything/everything.riddl(3:3): + Type 'SomeType' should have a description: + type SomeType is String + ^ +Missing: testkit/src/test/input/check/everything/everything.riddl(49:7): + State 'someState' in Entity 'Something' should have content: + state someState of type Everything.StateType is { ??? } + ^ +Missing: testkit/src/test/input/check/everything/everything.riddl(42:5): + Entity 'Something' should have a description: entity Something is { ^ -Missing: testkit/src/test/input/check/everything/everything.riddl(13:5): - Vital definitions should have an author reference: - sink Sink is { inlet Commands is DoAThing } explained as "Data Sink" - ^ Usage: testkit/src/test/input/check/everything/everything.riddl(27:5): - Type 'str' is unused: - type str is String // Define str as a String + Type 'PeachType' is unused: + type PeachType is { a: Integer } ^ -Missing: testkit/src/test/input/check/everything/everything.riddl(19:3): - Type 'StateType' should have a description: - type StateType is { someField1: SomeType } - ^ -Usage: testkit/src/test/input/check/everything/everything.riddl(62:5): - Entity 'Something' is unused: - entity Something is { +Missing: testkit/src/test/input/check/everything/everything.riddl(22:5): + Type 'dat' should have a description: + type dat is Date // Define dat as a Date ^ -Style: testkit/src/test/input/check/everything/everything.riddl(28:5): - Type 'num' should start with a capital letter: - type num is Number // Define num as a Number +Missing: testkit/src/test/input/check/everything/everything.riddl(21:5): + Type 'ident' should have a description: + type ident is UUID // Define ident as an Id ^ -Style: testkit/src/test/input/check/everything/everything.riddl(51:5): +Missing: testkit/src/test/input/check/everything/everything.riddl(71:15): + Handler 'fee' should have a description: + handler fee is { + ^ +Style: testkit/src/test/input/check/everything/everything.riddl(38:5): Type 'oneOrMore' should start with a capital letter: type oneOrMore is many agg ^ -Missing: testkit/src/test/input/check/everything/everything.riddl(31:5): - Type 'dat' should have a description: - type dat is Date // Define dat as a Date +Missing: testkit/src/test/input/check/everything/everything.riddl(4:3): + Command 'DoAThing' should have a description: + type DoAThing is command { thingField: Integer } + ^ +Style: testkit/src/test/input/check/everything/everything.riddl(23:5): + Type 'tim' should start with a capital letter: + type tim is Time // Define tim as a Time ^ -Missing: testkit/src/test/input/check/everything/everything.riddl(34:5): - Type 'url' should have a description: - type url is URL +Missing: testkit/src/test/input/check/everything/everything.riddl(72:9): + OnMessageClause 'On event ItHappened' should have a description: + on event ItHappened { + ^ +Missing: testkit/src/test/input/check/everything/everything.riddl(18:5): + Type 'str' should have a description: + type str is String // Define str as a String ^ -Usage: testkit/src/test/input/check/everything/everything.riddl(41:5): +Usage: testkit/src/test/input/check/everything/everything.riddl(30:5): Type 'alt' is unused: type alt is one of { enum or stamp or url } ^ -Usage: testkit/src/test/input/check/everything/everything.riddl(65:7): - Type 'somethingDate' is unused: - type somethingDate is Date - ^ -Style: testkit/src/test/input/check/everything/everything.riddl(34:5): - Type 'url' should start with a capital letter: - type url is URL - ^ -Style: testkit/src/test/input/check/everything/everything.riddl(53:5): - Type 'optional' should start with a capital letter: - type optional is agg? - ^ -Style: testkit/src/test/input/check/everything/everything.riddl(33:5): - Type 'stamp' should start with a capital letter: - type stamp is TimeStamp // Define stamp as a TimeStamp +Style: testkit/src/test/input/check/everything/everything.riddl(52:20): + Field identifier 'n' is too short. The minimum length is 3: + requires { n: Nothing } + ^ +Style: testkit/src/test/input/check/everything/everything.riddl(28:5): + Type 'enum' should start with a capital letter: + type enum is any of { Apple Pear Peach(21) Persimmon(42) } ^ -Usage: testkit/src/test/input/check/everything/everything.riddl(29:5): - Type 'boo' is unused: - type boo is Boolean // Define boo as a Boolean +Usage: testkit/src/test/input/check/everything/everything.riddl(22:5): + Type 'dat' is unused: + type dat is Date // Define dat as a Date ^ -Missing: testkit/src/test/input/check/everything/everything.riddl(12:5): +Missing: testkit/src/test/input/check/everything/everything.riddl(42:5): Vital definitions should have an author reference: - source Source is { outlet Commands is DoAThing } described by "Data Source" - ^ -Missing: testkit/src/test/input/check/everything/everything.riddl(28:5): - Type 'num' should have a description: - type num is Number // Define num as a Number - ^ -Missing: testkit/src/test/input/check/everything/everything.riddl(51:5): - Type 'oneOrMore' should have a description: - type oneOrMore is many agg + entity Something is { ^ -Usage: testkit/src/test/input/check/everything/everything.riddl(31:5): - Type 'dat' is unused: - type dat is Date // Define dat as a Date +Missing: testkit/src/test/input/check/everything/everything.riddl(47:7): + Command 'ACommand' should have a description: + command ACommand is { ??? } + ^ +Missing: testkit/src/test/input/check/everything/everything.riddl(14:3): + Type 'StateType' should have a description: + type StateType is { someField1: SomeType } + ^ +Warning: testkit/src/test/input/check/everything/everything.riddl(68:7): + Inlet 'SOT_In' is not connected: + inlet SOT_In is SomeOtherThing.ItHappened + ^ +Missing: testkit/src/test/input/check/everything/everything.riddl(51:7): + Vital definitions should have an author reference: + function whenUnderTheInfluence is { + ^ +Style: testkit/src/test/input/check/everything/everything.riddl(39:5): + Type 'zeroOrMore' should start with a capital letter: + type zeroOrMore is agg* ^ -Usage: testkit/src/test/input/check/everything/everything.riddl(37:5): - Type 'PeachType' is unused: - type PeachType is { a: Integer } +Missing: testkit/src/test/input/check/everything/everything.riddl(59:15): + Handler 'foo' should have a description: + handler foo is { + ^ +Missing: testkit/src/test/input/check/everything/everything.riddl(16:3): + Vital definitions should have an author reference: + context full is { + ^ +Missing: testkit/src/test/input/check/everything/everything.riddl(30:5): + Type 'alt' should have a description: + type alt is one of { enum or stamp or url } ^ -Missing: testkit/src/test/input/check/everything/everything.riddl(29:5): +Missing: testkit/src/test/input/check/everything/everything.riddl(20:5): Type 'boo' should have a description: type boo is Boolean // Define boo as a Boolean ^ -Style: testkit/src/test/input/check/everything/everything.riddl(32:5): - Type 'tim' should start with a capital letter: +Missing: testkit/src/test/input/check/everything/everything.riddl(69:7): + Event 'ItHappened' should have a description: + type ItHappened is event { when: String } + ^ +Missing: testkit/src/test/input/check/everything/everything.riddl(7:5): + Vital definitions should have an author reference: + source Source is { outlet Commands is DoAThing } described by "Data Source" + ^ +Missing: testkit/src/test/input/check/everything/everything.riddl(6:3): + Vital definitions should have an author reference: + context APlant is { + ^ +Usage: testkit/src/test/input/check/everything/everything.riddl(23:5): + Type 'tim' is unused: type tim is Time // Define tim as a Time ^ -Style: testkit/src/test/input/check/everything/everything.riddl(29:5): - Type 'boo' should start with a capital letter: - type boo is Boolean // Define boo as a Boolean +Usage: testkit/src/test/input/check/everything/everything.riddl(18:5): + Type 'str' is unused: + type str is String // Define str as a String ^ -Style: testkit/src/test/input/check/everything/everything.riddl(65:7): - Type 'somethingDate' should start with a capital letter: - type somethingDate is Date - ^ -Missing: testkit/src/test/input/check/everything/everything.riddl(67:7): - Command 'ACommand' should have a description: - command ACommand is { ??? } - ^ -Missing: testkit/src/test/input/check/everything/everything.riddl(3:1): +Usage: testkit/src/test/input/check/everything/everything.riddl(40:5): + Type 'optional' is unused: + type optional is agg? + ^ +Missing: testkit/src/test/input/check/everything/everything.riddl(1:1): Vital definitions should have an author reference: domain Everything is { ^ -Usage: testkit/src/test/input/check/everything/everything.riddl(53:5): - Type 'optional' is unused: +Style: testkit/src/test/input/check/everything/everything.riddl(40:5): + Type 'optional' should start with a capital letter: type optional is agg? ^ -Missing: testkit/src/test/input/check/everything/everything.riddl(69:7): - State 'someState' in Entity 'Something' should have content: - state someState of type Everything.StateType is { ??? } - ^ -Usage: testkit/src/test/input/check/everything/everything.riddl(32:5): - Type 'tim' is unused: - type tim is Time // Define tim as a Time +Style: testkit/src/test/input/check/everything/everything.riddl(25:5): + Type 'url' should start with a capital letter: + type url is URL ^ -Style: testkit/src/test/input/check/everything/everything.riddl(38:5): - Type 'enum' should start with a capital letter: +Missing: testkit/src/test/input/check/everything/everything.riddl(28:5): + Type 'enum' should have a description: type enum is any of { Apple Pear Peach(21) Persimmon(42) } ^ -Style: testkit/src/test/input/check/everything/everything.riddl(37:25): - Field identifier 'a' is too short. The minimum length is 3: - type PeachType is { a: Integer } - ^ -Style: testkit/src/test/input/check/everything/everything.riddl(44:5): - Type 'agg' should start with a capital letter: - type agg is { - ^ -Missing: testkit/src/test/input/check/everything/everything.riddl(44:5): - Type 'agg' should have a description: - type agg is { - ^ -Missing: testkit/src/test/input/check/everything/everything.riddl(32:5): - Type 'tim' should have a description: - type tim is Time // Define tim as a Time - ^ -Missing: testkit/src/test/input/check/everything/everything.riddl(62:5): - Entity 'Something' should have a description: - entity Something is { +Usage: testkit/src/test/input/check/everything/everything.riddl(39:5): + Type 'zeroOrMore' is unused: + type zeroOrMore is agg* ^ -Missing: testkit/src/test/input/check/everything/everything.riddl(71:7): +Missing: testkit/src/test/input/check/everything/everything.riddl(51:7): Function 'whenUnderTheInfluence' should have a description: function whenUnderTheInfluence is { ^ -Style: testkit/src/test/input/check/everything/everything.riddl(13:20): - Inlet 'Commands' overloads Outlet 'Commands' at everything.riddl(12:24): - sink Sink is { inlet Commands is DoAThing } explained as "Data Sink" - ^ -Missing: testkit/src/test/input/check/everything/everything.riddl(27:5): - Type 'str' should have a description: - type str is String // Define str as a String - ^ -Missing: testkit/src/test/input/check/everything/everything.riddl(23:3): - Vital definitions should have an author reference: - context full is { - ^ -Missing: testkit/src/test/input/check/everything/everything.riddl(11:3): - Context 'APlant' should have a description: - context APlant is { - ^ -Style: testkit/src/test/input/check/everything/everything.riddl(41:5): - Type 'alt' should start with a capital letter: - type alt is one of { enum or stamp or url } - ^ -Missing: testkit/src/test/input/check/everything/everything.riddl(6:3): - Command 'DoAThing' should have a description: - type DoAThing is command { thingField: Integer } - ^ -Style: testkit/src/test/input/check/everything/everything.riddl(27:5): - Type 'str' should start with a capital letter: - type str is String // Define str as a String - ^ -Missing: testkit/src/test/input/check/everything/everything.riddl(37:5): - Type 'PeachType' should have a description: - type PeachType is { a: Integer } +Usage: testkit/src/test/input/check/everything/everything.riddl(67:5): + Entity 'SomeOtherThing' is unused: + entity SomeOtherThing is { ^ -Missing: testkit/src/test/input/check/everything/everything.riddl(11:3): +Missing: testkit/src/test/input/check/everything/everything.riddl(67:5): Vital definitions should have an author reference: - context APlant is { - ^ -Style: testkit/src/test/input/check/everything/everything.riddl(30:5): - Type 'ident' should start with a capital letter: - type ident is UUID // Define ident as an Id + entity SomeOtherThing is { ^ -Style: testkit/src/test/input/check/everything/everything.riddl(46:7): +Style: testkit/src/test/input/check/everything/everything.riddl(53:19): + Field identifier 'b' is too short. The minimum length is 3: + returns { b: Boolean } + ^ +Style: testkit/src/test/input/check/everything/everything.riddl(34:7): Field identifier 'id' is too short. The minimum length is 3: id is ident, ^ -Style: testkit/src/test/input/check/everything/everything.riddl(52:5): - Type 'zeroOrMore' should start with a capital letter: - type zeroOrMore is agg* +Missing: testkit/src/test/input/check/everything/everything.riddl(67:5): + Entity 'SomeOtherThing' should have a description: + entity SomeOtherThing is { ^ -Missing: testkit/src/test/input/check/everything/everything.riddl(5:3): - Type 'SomeType' should have a description: - type SomeType is String - ^ -Missing: testkit/src/test/input/check/everything/everything.riddl(53:5): - Type 'optional' should have a description: - type optional is agg? +Missing: testkit/src/test/input/check/everything/everything.riddl(23:5): + Type 'tim' should have a description: + type tim is Time // Define tim as a Time + ^ +Missing: testkit/src/test/input/check/everything/everything.riddl(25:5): + Type 'url' should have a description: + type url is URL ^ -Missing: testkit/src/test/input/check/everything/everything.riddl(23:3): +Style: testkit/src/test/input/check/everything/everything.riddl(45:7): + Type 'somethingDate' should start with a capital letter: + type somethingDate is Date + ^ +Missing: testkit/src/test/input/check/everything/everything.riddl(16:3): Context 'full' should have a description: context full is { ^ -Missing: testkit/src/test/input/check/everything/everything.riddl(3:1): - Domain 'Everything' should have a description: - domain Everything is { - ^ -Usage: testkit/src/test/input/check/everything/everything.riddl(52:5): - Type 'zeroOrMore' is unused: - type zeroOrMore is agg* +Missing: testkit/src/test/input/check/everything/everything.riddl(27:5): + Type 'PeachType' should have a description: + type PeachType is { a: Integer } ^ -Style: testkit/src/test/input/check/everything/everything.riddl(31:5): - Type 'dat' should start with a capital letter: - type dat is Date // Define dat as a Date +Style: testkit/src/test/input/check/everything/everything.riddl(32:5): + Type 'agg' should start with a capital letter: + type agg is { ^ -Missing: testkit/src/test/input/check/everything/everything.riddl(65:7): - Type 'somethingDate' should have a description: - type somethingDate is Date - ^ -Missing: testkit/src/test/input/check/everything/everything.riddl(52:5): - Type 'zeroOrMore' should have a description: - type zeroOrMore is agg* +Style: testkit/src/test/input/check/everything/everything.riddl(20:5): + Type 'boo' should start with a capital letter: + type boo is Boolean // Define boo as a Boolean ^ -Style: testkit/src/test/input/check/everything/everything.riddl(73:19): - Field identifier 'b' is too short. The minimum length is 3: - returns { b: Boolean } - ^ -Missing: testkit/src/test/input/check/everything/everything.riddl(79:15): - Handler 'foo' should have a description: - handler foo is { - ^ -Missing: testkit/src/test/input/check/everything/everything.riddl(80:9): +Missing: testkit/src/test/input/check/everything/everything.riddl(60:9): OnMessageClause 'On command ACommand' should have a description: on command ACommand { ^ -Missing: testkit/src/test/input/check/everything/everything.riddl(87:5): - Entity 'SomeOtherThing' should have a description: - entity SomeOtherThing is { +Usage: testkit/src/test/input/check/everything/everything.riddl(42:5): + Entity 'Something' is unused: + entity Something is { ^ -Missing: testkit/src/test/input/check/everything/everything.riddl(87:5): - Vital definitions should have an author reference: - entity SomeOtherThing is { +Style: testkit/src/test/input/check/everything/everything.riddl(21:5): + Type 'ident' should start with a capital letter: + type ident is UUID // Define ident as an Id ^ -Missing: testkit/src/test/input/check/everything/everything.riddl(89:7): - Event 'ItHappened' should have a description: - type ItHappened is event { when: String } - ^ -Missing: testkit/src/test/input/check/everything/everything.riddl(90:7): +Missing: testkit/src/test/input/check/everything/everything.riddl(38:5): + Type 'oneOrMore' should have a description: + type oneOrMore is many agg + ^ +Missing: testkit/src/test/input/check/everything/everything.riddl(70:7): State 'otherThingState' in Entity 'SomeOtherThing' should have content: state otherThingState of type Everything.StateType is { ??? } ^ -Missing: testkit/src/test/input/check/everything/everything.riddl(91:15): - Handler 'fee' should have a description: - handler fee is { - ^ -Missing: testkit/src/test/input/check/everything/everything.riddl(92:9): - OnMessageClause 'On event ItHappened' should have a description: - on event ItHappened { - ^ -Usage: testkit/src/test/input/check/everything/everything.riddl(71:7): - Function 'whenUnderTheInfluence' is unused: - function whenUnderTheInfluence is { - ^ -Usage: testkit/src/test/input/check/everything/everything.riddl(87:5): - Entity 'SomeOtherThing' is unused: - entity SomeOtherThing is { +Usage: testkit/src/test/input/check/everything/everything.riddl(20:5): + Type 'boo' is unused: + type boo is Boolean // Define boo as a Boolean ^ -Warning: testkit/src/test/input/check/everything/everything.riddl(88:7): - Inlet 'SOT_In' is not connected: - inlet SOT_In is SomeOtherThing.ItHappened +Style: testkit/src/test/input/check/everything/everything.riddl(27:25): + Field identifier 'a' is too short. The minimum length is 3: + type PeachType is { a: Integer } + ^ +Usage: testkit/src/test/input/check/everything/everything.riddl(45:7): + Type 'somethingDate' is unused: + type somethingDate is Date ^ +Missing: testkit/src/test/input/check/everything/everything.riddl(39:5): + Type 'zeroOrMore' should have a description: + type zeroOrMore is agg* + ^ +Style: testkit/src/test/input/check/everything/everything.riddl(18:5): + Type 'str' should start with a capital letter: + type str is String // Define str as a String + ^ +Missing: testkit/src/test/input/check/everything/everything.riddl(8:5): + Vital definitions should have an author reference: + sink Sink is { inlet Commands is DoAThing } explained as "Data Sink" + ^ +Style: testkit/src/test/input/check/everything/everything.riddl(24:5): + Type 'stamp' should start with a capital letter: + type stamp is TimeStamp // Define stamp as a TimeStamp + ^ diff --git a/testkit/src/test/input/check/everything/everything.riddl b/testkit/src/test/input/check/everything/everything.riddl index 0ad0cf5fe..c4c75dbd5 100644 --- a/testkit/src/test/input/check/everything/everything.riddl +++ b/testkit/src/test/input/check/everything/everything.riddl @@ -1,13 +1,8 @@ -// #everything -// RIDDL is about defining domains so ... domain Everything is { type SomeType is String type DoAThing is command { thingField: Integer } - // Channels are defined to flow messages between entities. They are defined - // in domain scope because they can cross contexts too. - context APlant is { source Source is { outlet Commands is DoAThing } described by "Data Source" sink Sink is { inlet Commands is DoAThing } explained as "Data Sink" @@ -18,12 +13,8 @@ domain Everything is { type StateType is { someField1: SomeType } - - // Domains are composed of bounded contexts so ... context full is { - // Contexts can contain many kinds of definitions - // 8 pre-defined types, shown as re-names type str is String // Define str as a String type num is Number // Define num as a Number type boo is Boolean // Define boo as a Boolean @@ -33,32 +24,21 @@ domain Everything is { type stamp is TimeStamp // Define stamp as a TimeStamp type url is URL - // Enumerations have a value chosen from a list of identifiers type PeachType is { a: Integer } type enum is any of { Apple Pear Peach(21) Persimmon(42) } - // Alternations select one type from a list of types type alt is one of { enum or stamp or url } - // Aggregations combine several types and give each a field name identifier type agg is { key is num, id is ident, time is TimeStamp } - // Types can have cardinality requirements similar to regular expressions type oneOrMore is many agg type zeroOrMore is agg* type optional is agg? - // Commands, Events, Queries and Results are defined in terms of some - // type, previously defined. Commands yield events. Queries yield results. - - - - // Entities are the main objects that contexts define. They can be - // persistent (long lived) or aggregate (they consume commands and queries) entity Something is { options (aggregate, transient) @@ -96,5 +76,3 @@ domain Everything is { } } } - -// #everything diff --git a/testkit/src/test/input/dokn.riddl b/testkit/src/test/input/dokn.riddl index 74f13d9c7..f4b2b055f 100644 --- a/testkit/src/test/input/dokn.riddl +++ b/testkit/src/test/input/dokn.riddl @@ -10,7 +10,7 @@ domain dokn is { postalCode: String } type MobileNumber = Pattern("\\(([0-9]{3})\\)([0-9]{3})-([0-9]{4})") - //https:emailregex.com RFC 5322 Official Standard + type EmailAddress = Pattern( "^[a-zA-Z0-9_+&*-] + (?:\\.[a-zA-Z0-9_+&*-] + )*@(?:[a-zA-Z0-9-]+\\.) + [a-zA-Z]{2,7}" ) @@ -98,8 +98,8 @@ domain dokn is { options(event-sourced, available) record fields is { noteId: NoteId, - locationId: LocationId, - companyId: CompanyId, + locationId: type LocationId, + companyId: type CompanyId, text: String, upvoteCount: Integer, downvoteCount: Integer, @@ -152,13 +152,13 @@ domain dokn is { type LocationNotes is result { notes: NoteList } record fields is { - locationId: LocationId, - geohash: Location, + locationId: type LocationId, + geohash: Location, label: String , - address: Address, - notes: NoteList, + address: type Address, + notes: type NoteList, ticketCount: Integer, - loadingMethod: LoadingMethod + loadingMethod: type LoadingMethod } state LocationBase of Location.fields is { handler Base is { diff --git a/testkit/src/test/input/domains/badstyle.riddl b/testkit/src/test/input/domains/badstyle.riddl index dd8a28345..91af1a50c 100644 --- a/testkit/src/test/input/domains/badstyle.riddl +++ b/testkit/src/test/input/domains/badstyle.riddl @@ -1,6 +1,4 @@ -// domain name should be capitalized -// domain is lacking description domain test is { type a is any of { B, C, D } -} \ No newline at end of file +} // domain name should be capitalized and lacking a description diff --git a/testkit/src/test/input/everything.riddl b/testkit/src/test/input/everything.riddl index 275774e28..c4bcb3332 100644 --- a/testkit/src/test/input/everything.riddl +++ b/testkit/src/test/input/everything.riddl @@ -1,13 +1,8 @@ -// #everything -// RIDDL is about defining domains so ... domain Everything is { type SomeType is String command DoAThing is { thingField: Integer } - // Channels are defined to flow messages between entities. They are defined - // in domain scope because they can cross contexts too. - context APlant is { source Source is { outlet Commands is DoAThing } described by "Data Source" sink Sink is { inlet Commands is DoAThing } explained as "Data Sink" @@ -23,16 +18,13 @@ domain Everything is { case primary is { ??? } } described as "A simple authoring story" - // Domains are composed of bounded contexts so ... context full is { - // Contexts can contain many kinds of definitions inlet input is type DoAThing connector foo is { from outlet Everything.APlant.Source.Commands to inlet full.input } - // 8 pre-defined types, shown as re-names type str is String // Define str as a String type num is Number // Define num as a Number type boo is Boolean // Define boo as a Boolean @@ -42,32 +34,23 @@ domain Everything is { type stamp is TimeStamp // Define stamp as a TimeStamp type url is URL - // Enumerations have a value chosen from a list of identifiers type PeachType is { a: Integer } type enum is any of { Apple Pear Peach(23) Persimmon(24) } - // Alternations select one type from a list of types type alt is one of { enum or stamp or url } - // Aggregations combine several types and give each a field name identifier type agg is { key: num, id: ident, time is TimeStamp } - // Types can have cardinality requirements similar to regular expressions type oneOrMore is many agg type zeroOrMore is agg* type optional is agg? - // Commands, Events, Queries and Results are defined in terms of some - // type, previously defined. Commands yield events. Queries yield results. - command ACommand is { ??? } - // Entities are the main objects that contexts define. They can be - // transient (memory only) or aggregate (they consume commands and queries) entity Something is { options (aggregate, transient) @@ -87,7 +70,7 @@ domain Everything is { } function whenUnderTheInfluence is { - requires {n:Nothing} + requires {n: Nothing} returns {b: Boolean} body ??? } @@ -111,5 +94,3 @@ domain Everything is { } } } - -// #everything diff --git a/testkit/src/test/input/issues/486.riddl b/testkit/src/test/input/issues/486.riddl new file mode 100644 index 000000000..f38d28d98 --- /dev/null +++ b/testkit/src/test/input/issues/486.riddl @@ -0,0 +1,12 @@ +domain test { + application ksoTemplateApp { + type ProductOfferingText is String(3,80) + page PricingPage { output PricingPageText showsksoTemplateApp.ProductOfferingText } described as { + | The line above should not compile. First, there should be a space between shows and ksoTemplateApp. + | Second, there should be a record keyword between shows and ksoTemplateApp. + | Third, there should not be a space between ksoTemplateApp. and ProductOfferingText. + | The correct syntax is expected to be: + | page PricingPage{ output PricingPageText shows record ksoTempateApp.ProductOfferingText } + } + } +} diff --git a/testkit/src/test/input/rbbq.riddl b/testkit/src/test/input/rbbq.riddl index 34405af33..311983940 100644 --- a/testkit/src/test/input/rbbq.riddl +++ b/testkit/src/test/input/rbbq.riddl @@ -1,9 +1,5 @@ -// #everything -// #domains domain ReactiveBBQ is { -// #domains - // Create some types with better names than just "Id" type CustomerId is Id(ReactiveBBQ.Customer.Customer) explained as { "Unique identifier for a customer" } @@ -25,8 +21,6 @@ domain ReactiveBBQ is { } - // #Kitchen - // The Kitchen context pertains to context Kitchen is { type IP4Address is { a: Number, b: Number, c: Number, d: Number} type OrderViewType is { @@ -64,9 +58,7 @@ domain ReactiveBBQ is { |1. Kitchen ignores drink items on order |1. } - // #Kitchen - // #Loyalty context Loyalty is { type AccrualEvent is { when is TimeStamp, @@ -96,9 +88,7 @@ domain ReactiveBBQ is { ??? } } - // #Loyalty - // #Order context Order is { entity Order is { option is aggregate @@ -111,9 +101,7 @@ domain ReactiveBBQ is { } } } - // #Order - // #Payment context Payment is { entity Payment is { option is aggregate @@ -127,9 +115,7 @@ domain ReactiveBBQ is { } } } - // #Payment - // #Menu context Menu is { entity MenuItem is { record fields is { something: String } @@ -146,9 +132,7 @@ domain ReactiveBBQ is { } } } - // #Menu - // #Reservation context Reservation is { type ReservationValue is { partyName is String, @@ -172,7 +156,6 @@ domain ReactiveBBQ is { } } } - // #Reservation } explained as { |# brief | Reactive BBQ Domain Definition @@ -183,4 +166,4 @@ domain ReactiveBBQ is { |Reactive BBQ employees to define the requirements. This domain specification |is a possible result of analyzing that domain: the Reactive BBQ restaurant. } -// #everything +// #end-of-domain diff --git a/testkit/src/test/scala/com/reactific/riddl/testkit/ReportedIssuesTest.scala b/testkit/src/test/scala/com/reactific/riddl/testkit/ReportedIssuesTest.scala index 2ad2c159f..5035de302 100644 --- a/testkit/src/test/scala/com/reactific/riddl/testkit/ReportedIssuesTest.scala +++ b/testkit/src/test/scala/com/reactific/riddl/testkit/ReportedIssuesTest.scala @@ -95,5 +95,15 @@ class ReportedIssuesTest extends ValidatingTest { "480" in { checkOne("480.riddl") } + "486" in { + checkOne("486.riddl") { + case Left(messages) => + val errors = messages.justErrors + errors.size mustBe 1 + errors.head.message must include("whitespace after keyword") + case Right(result) => + fail("Should not have parsed correctly") + } + } } } From cbff56957e80c77fce50d9c2518f72c7ad069103 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Fri, 24 Nov 2023 16:48:58 +0100 Subject: [PATCH 2/3] Update commons-lang3 to 3.14.0 (#489) --- project/Helpers.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/project/Helpers.scala b/project/Helpers.scala index 02dc89f12..1953c94ef 100644 --- a/project/Helpers.scala +++ b/project/Helpers.scala @@ -29,7 +29,7 @@ object V { val config = "1.4.2" val fastparse = "3.0.2" val jgit = "6.5.0" - val lang3 = "3.13.0" + val lang3 = "3.14.0" val pureconfig = "0.17.4" val scalacheck = "1.17.0" val scalatest = "3.2.17" @@ -93,7 +93,8 @@ object C { // "-explain", // "-explain-types", "-Werror", - "-pagewidth", "120" + "-pagewidth", + "120" ) def scala_3_doc_options(version: String): Seq[String] = { From 57acb10cd97ca6160f81f98d1f1ac30c1e05c2db Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Fri, 24 Nov 2023 16:49:57 +0100 Subject: [PATCH 3/3] Update sbt-bloop to 1.5.12 (#488) --- project/metals.sbt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/project/metals.sbt b/project/metals.sbt index cbb25c6ad..c11a206af 100644 --- a/project/metals.sbt +++ b/project/metals.sbt @@ -2,5 +2,4 @@ // This file enables sbt-bloop to create bloop config files. -addSbtPlugin("ch.epfl.scala" % "sbt-bloop" % "1.5.11") - +addSbtPlugin("ch.epfl.scala" % "sbt-bloop" % "1.5.12")