From dd2d4c799206bb751aedae98f5700089d756efc8 Mon Sep 17 00:00:00 2001 From: Damian Reeves <957246+DamianReeves@users.noreply.github.com> Date: Fri, 16 Jul 2021 13:34:33 -0400 Subject: [PATCH] Model Morphir IR in Scala 3 (#97) * Adding IR - #75 * Adding Package related IR types * issue-94: add tuple to sdk (#98) * issue-94: add tuple to sdk * issue-94: run formatter * Finishing up modeling of morphir IR in Scala 3 * Move IR into morphir-ir Co-authored-by: willms2021 <82905512+willms2021@users.noreply.github.com> --- .gitignore | 10 ++- .mill-version | 2 +- .scalafmt.conf | 86 ++++++++++++++++++- build.sbt | 52 +++++++++++ .../src/elm/GlobalCo/Comms/EmailAddress.elm | 26 +++--- .../finos/morphir/ir/AccessControlled.scala | 24 ++++++ .../org/finos/morphir/ir/Constructors.scala | 14 +++ .../org/finos/morphir/ir/Distribution.scala | 4 + .../org/finos/morphir/ir/Documented.scala | 7 ++ .../scala/org/finos/morphir/ir/FQName.scala | 7 ++ .../scala/org/finos/morphir/ir/Field.scala | 10 +++ .../main/scala/org/finos/morphir/ir/Lit.scala | 8 ++ .../finos/morphir/ir/ModuleDefinition.scala | 22 +++++ .../org/finos/morphir/ir/ModuleName.scala | 3 + .../morphir/ir/ModuleSpecification.scala | 7 ++ .../scala/org/finos/morphir/ir/Name.scala | 3 + .../finos/morphir/ir/PackageDefinition.scala | 9 ++ .../org/finos/morphir/ir/PackageName.scala | 3 + .../morphir/ir/PackageSpecification.scala | 8 ++ .../org/finos/morphir/ir/ParameterInfo.scala | 7 ++ .../scala/org/finos/morphir/ir/Path.scala | 3 + .../scala/org/finos/morphir/ir/Pattern.scala | 13 +++ .../scala/org/finos/morphir/ir/Type.scala | 52 +++++++++++ .../org/finos/morphir/ir/TypeDefinition.scala | 7 ++ .../finos/morphir/ir/TypeSpecification.scala | 14 +++ .../scala/org/finos/morphir/ir/Value.scala | 39 +++++++++ .../finos/morphir/ir/ValueDefinition.scala | 12 +++ .../finos/morphir/ir/ValueSpecification.scala | 12 +++ .../morphir/ir/PackageDefinitionSpec.scala | 15 ++++ .../morphir/ir/PackageSpecificationSpec.scala | 9 ++ .../scala/org/finos/morphir/ir/TypeSpec.scala | 21 +++++ .../experiments/MetaProgrammingSandbox.scala | 11 +++ morphir/sdk/core/src/morphir/sdk/Tuple.scala | 31 +++++++ project/Dependencies.scala | 19 ++++ project/build.properties | 1 + project/plugins.sbt | 2 + 36 files changed, 556 insertions(+), 17 deletions(-) create mode 100644 build.sbt create mode 100644 morphir-ir/src/main/scala/org/finos/morphir/ir/AccessControlled.scala create mode 100644 morphir-ir/src/main/scala/org/finos/morphir/ir/Constructors.scala create mode 100644 morphir-ir/src/main/scala/org/finos/morphir/ir/Distribution.scala create mode 100644 morphir-ir/src/main/scala/org/finos/morphir/ir/Documented.scala create mode 100644 morphir-ir/src/main/scala/org/finos/morphir/ir/FQName.scala create mode 100644 morphir-ir/src/main/scala/org/finos/morphir/ir/Field.scala create mode 100644 morphir-ir/src/main/scala/org/finos/morphir/ir/Lit.scala create mode 100644 morphir-ir/src/main/scala/org/finos/morphir/ir/ModuleDefinition.scala create mode 100644 morphir-ir/src/main/scala/org/finos/morphir/ir/ModuleName.scala create mode 100644 morphir-ir/src/main/scala/org/finos/morphir/ir/ModuleSpecification.scala create mode 100644 morphir-ir/src/main/scala/org/finos/morphir/ir/Name.scala create mode 100644 morphir-ir/src/main/scala/org/finos/morphir/ir/PackageDefinition.scala create mode 100644 morphir-ir/src/main/scala/org/finos/morphir/ir/PackageName.scala create mode 100644 morphir-ir/src/main/scala/org/finos/morphir/ir/PackageSpecification.scala create mode 100644 morphir-ir/src/main/scala/org/finos/morphir/ir/ParameterInfo.scala create mode 100644 morphir-ir/src/main/scala/org/finos/morphir/ir/Path.scala create mode 100644 morphir-ir/src/main/scala/org/finos/morphir/ir/Pattern.scala create mode 100644 morphir-ir/src/main/scala/org/finos/morphir/ir/Type.scala create mode 100644 morphir-ir/src/main/scala/org/finos/morphir/ir/TypeDefinition.scala create mode 100644 morphir-ir/src/main/scala/org/finos/morphir/ir/TypeSpecification.scala create mode 100644 morphir-ir/src/main/scala/org/finos/morphir/ir/Value.scala create mode 100644 morphir-ir/src/main/scala/org/finos/morphir/ir/ValueDefinition.scala create mode 100644 morphir-ir/src/main/scala/org/finos/morphir/ir/ValueSpecification.scala create mode 100644 morphir-ir/src/test/scala/org/finos/morphir/ir/PackageDefinitionSpec.scala create mode 100644 morphir-ir/src/test/scala/org/finos/morphir/ir/PackageSpecificationSpec.scala create mode 100644 morphir-ir/src/test/scala/org/finos/morphir/ir/TypeSpec.scala create mode 100644 morphir-ir/src/test/scala/org/finos/morphir/ir/experiments/MetaProgrammingSandbox.scala create mode 100644 morphir/sdk/core/src/morphir/sdk/Tuple.scala create mode 100644 project/Dependencies.scala create mode 100644 project/build.properties create mode 100644 project/plugins.sbt diff --git a/.gitignore b/.gitignore index 3145638c..780bd214 100644 --- a/.gitignore +++ b/.gitignore @@ -7,8 +7,8 @@ output/ .ionide .vscode out/ -/.bloop/ -/.metals/ +.bloop/ +.metals/ contrib/bsp/mill-external-bs contrib/bsp/mill-out-bs mill.iml @@ -21,4 +21,8 @@ models/shared-domain/elm-stuff/ models/shared-domain/elm.json elm-stuff/ .tmp/ -null/ \ No newline at end of file +null/ + +metals.sbt + +metals.sbt \ No newline at end of file diff --git a/.mill-version b/.mill-version index 03834411..b5d0ec55 100644 --- a/.mill-version +++ b/.mill-version @@ -1 +1 @@ -0.9.5 \ No newline at end of file +0.9.8 \ No newline at end of file diff --git a/.scalafmt.conf b/.scalafmt.conf index b6c62ffb..6b1705a7 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -4,10 +4,81 @@ align.preset = most align.multiline = false continuationIndent.defnSite = 2 assumeStandardLibraryStripMargin = true -docstrings = JavaDoc +docstrings.style = Asterisk lineEndings = preserve includeCurlyBraceInSelectChains = false danglingParentheses.preset = true +version = "3.0.0-RC5" + +align { + stripMargin = true +} + +assumeStandardLibraryStripMargin = false + +binPack { + literalArgumentLists = false +} + +continuationIndent { + withSiteRelativeToExtends = 3 +} + +includeNoParensInSelectChains = true + +indent { + caseSite = 5 +} + +indentOperator { + topLevelOnly = false +} + +maxColumn = 100 + +newlines { + alwaysBeforeElseAfterCurlyIf = true + avoidInResultType = true + beforeCurlyLambdaParams = multilineWithCaseOnly +} + +project { + excludeFilters = [ + ".metals" + ] +} + +rewrite { + rules = [ + PreferCurlyFors + RedundantBraces + RedundantParens + SortModifiers + ] + sortModifiers { + order = [ + final + sealed + abstract + override + implicit + private + protected + lazy + ] + } +} + +rewriteTokens { + "⇒" = "=>" + "→" = "->" + "←" = "<-" +} + +runner { + dialect = scala3 +} + spaces { inImportCurlyBraces = true } @@ -17,7 +88,7 @@ newlines.alwaysBeforeMultilineDef = false rewrite.rules = [RedundantBraces] project.excludeFilters = [ - + trailingCommas, multiple ] rewrite.redundantBraces.generalExpressions = false @@ -25,4 +96,13 @@ rewriteTokens = { "⇒": "=>" "→": "->" "←": "<-" -} \ No newline at end of file +} +verticalMultiline = { + arityThreshold = 3 + atDefnSite = true + excludeDanglingParens = [] + newlineAfterImplicitKW = true + newlineAfterOpenParen = true + newlineBeforeImplicitKW = false +} + diff --git a/build.sbt b/build.sbt new file mode 100644 index 00000000..1e4031e5 --- /dev/null +++ b/build.sbt @@ -0,0 +1,52 @@ +import sbt.Keys._ +import sbt._ + +resolvers ++= Seq( + Resolver.mavenLocal, + Resolver.sonatypeRepo("releases"), + Resolver.sonatypeRepo("snapshots"), + Resolver.jcenterRepo +) + +lazy val root = (project in file(".")) + .settings( + commonSettings, + libraryDependencies ++= Dependencies.zioCommonDeps, + testFrameworks := Seq(new TestFramework("zio.test.sbt.ZTestFramework")) + ) + .aggregate( + morphirIR + ) + +lazy val commonSettings = Seq( + name := "morphir-ir", + version := "0.1.0", + scalacOptions ++= Seq( + "-deprecation", + "-language:postfixOps", + "-Ykind-projector", + "-Yexplicit-nulls", + "-source", + "future", + "-Xfatal-warnings" + ) ++ Seq("-rewrite", "-indent"), + scalaVersion := "3.0.0" +) + +lazy val morphirIR = project + .in(file("./morphir-ir")) + .settings( + name := "morphir-ir", + version := "0.1.0", + scalacOptions ++= Seq( + "-language:postfixOps", + "-Ykind-projector", + "-Yexplicit-nulls", + "-source", + "future", + "-Xfatal-warnings" + ), + libraryDependencies ++= Dependencies.zioCommonDeps, + testFrameworks := Seq(new TestFramework("zio.test.sbt.ZTestFramework")), + scalaVersion := "3.0.0" + ) diff --git a/examples/workspaces/multi-project-workspace/models/shared-domain/src/elm/GlobalCo/Comms/EmailAddress.elm b/examples/workspaces/multi-project-workspace/models/shared-domain/src/elm/GlobalCo/Comms/EmailAddress.elm index 370e8fa9..80dae3a4 100644 --- a/examples/workspaces/multi-project-workspace/models/shared-domain/src/elm/GlobalCo/Comms/EmailAddress.elm +++ b/examples/workspaces/multi-project-workspace/models/shared-domain/src/elm/GlobalCo/Comms/EmailAddress.elm @@ -1,17 +1,17 @@ {- -Copyright 2020 Morgan Stanley + Copyright 2020 Morgan Stanley -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. -} @@ -45,3 +45,9 @@ create text = else "Invalid email address" |> Result.Err + + _ -> + "Invalid email address" |> Result.Err + + _ -> + "Invalid email address" |> Result.Err diff --git a/morphir-ir/src/main/scala/org/finos/morphir/ir/AccessControlled.scala b/morphir-ir/src/main/scala/org/finos/morphir/ir/AccessControlled.scala new file mode 100644 index 00000000..54fbffb5 --- /dev/null +++ b/morphir-ir/src/main/scala/org/finos/morphir/ir/AccessControlled.scala @@ -0,0 +1,24 @@ +package org.finos.morphir.ir + +final case class AccessControlled[+A](access: Access, value: A) { + + def isPrivate: Boolean = access == Access.Private + def isPublic: Boolean = access == Access.Public + + def map[B](f: A => B): AccessControlled[B] = + copy(value = f(value)) + + def withPrivateAccess: A = access match { + case Access.Public => value + case Access.Private => value + } + + def withPublicAccess: Option[A] = access match { + case Access.Public => Some(value) + case Access.Private => None + } +} + +enum Access: + case Public + case Private diff --git a/morphir-ir/src/main/scala/org/finos/morphir/ir/Constructors.scala b/morphir-ir/src/main/scala/org/finos/morphir/ir/Constructors.scala new file mode 100644 index 00000000..41dd277c --- /dev/null +++ b/morphir-ir/src/main/scala/org/finos/morphir/ir/Constructors.scala @@ -0,0 +1,14 @@ +package org.finos.morphir.ir +import scala.collection.immutable.ListMap + +/** + * Constructors in a dictionary keyed by their name. The values are the argument types for each + * constructor + */ +final case class TypeConstructors[+A](lookup: ListMap[Name, List[(Name, Type[A])]]): + def transform[B](f: A => B): TypeConstructors[B] = + TypeConstructors( + lookup.map((name, args) => + (name, args.map((argName, argType) => (argName, argType.transform(f)))) + ) + ) diff --git a/morphir-ir/src/main/scala/org/finos/morphir/ir/Distribution.scala b/morphir-ir/src/main/scala/org/finos/morphir/ir/Distribution.scala new file mode 100644 index 00000000..20dc794b --- /dev/null +++ b/morphir-ir/src/main/scala/org/finos/morphir/ir/Distribution.scala @@ -0,0 +1,4 @@ +package org.finos.morphir.ir + +enum Distribution: + case Library(packageName: PackageName) diff --git a/morphir-ir/src/main/scala/org/finos/morphir/ir/Documented.scala b/morphir-ir/src/main/scala/org/finos/morphir/ir/Documented.scala new file mode 100644 index 00000000..619bcb33 --- /dev/null +++ b/morphir-ir/src/main/scala/org/finos/morphir/ir/Documented.scala @@ -0,0 +1,7 @@ +package org.finos.morphir.ir + +/** + * Type that represents a documented value. + */ +final case class Documented[+A](doc: String, value: A): + def map[B](f: A => B): Documented[B] = copy(value = f(value)) diff --git a/morphir-ir/src/main/scala/org/finos/morphir/ir/FQName.scala b/morphir-ir/src/main/scala/org/finos/morphir/ir/FQName.scala new file mode 100644 index 00000000..f9f2d598 --- /dev/null +++ b/morphir-ir/src/main/scala/org/finos/morphir/ir/FQName.scala @@ -0,0 +1,7 @@ +package org.finos.morphir.ir + +case class FQName( + packagePath: Path, + modulePath: Path, + localName: Name + ) diff --git a/morphir-ir/src/main/scala/org/finos/morphir/ir/Field.scala b/morphir-ir/src/main/scala/org/finos/morphir/ir/Field.scala new file mode 100644 index 00000000..d06a8e54 --- /dev/null +++ b/morphir-ir/src/main/scala/org/finos/morphir/ir/Field.scala @@ -0,0 +1,10 @@ +package org.finos.morphir.ir + +final case class Field[+A](name: String, fieldType: Type[A]): + def mapAttributes[B](f: A => B): Field[B] = + Field(name = name, fieldType = fieldType.transform(f)) + +object FieldList: + extension [A](self: List[Field[A]]) + def mapAttributes[B](f: A => B): List[Field[B]] = + self.map(field => field.mapAttributes(f)) diff --git a/morphir-ir/src/main/scala/org/finos/morphir/ir/Lit.scala b/morphir-ir/src/main/scala/org/finos/morphir/ir/Lit.scala new file mode 100644 index 00000000..4953f412 --- /dev/null +++ b/morphir-ir/src/main/scala/org/finos/morphir/ir/Lit.scala @@ -0,0 +1,8 @@ +package org.finos.morphir.ir + +enum Lit: + case Bool(value: Boolean) + case Char(value: scala.Char) + case Str(value: String) + case Int(value: scala.Int) //TODO: Maybe BigInt + case Float(value: scala.Double) diff --git a/morphir-ir/src/main/scala/org/finos/morphir/ir/ModuleDefinition.scala b/morphir-ir/src/main/scala/org/finos/morphir/ir/ModuleDefinition.scala new file mode 100644 index 00000000..de9d187a --- /dev/null +++ b/morphir-ir/src/main/scala/org/finos/morphir/ir/ModuleDefinition.scala @@ -0,0 +1,22 @@ +package org.finos.morphir.ir +import scala.collection.immutable.ListMap + +/** + * Type that represents a module definition. A module definition contains all the details including + * implementation and private types and values. + * + * A module contains types and values which is represented by two field in this type: + * - types: a map of local name to access controlled, documented type specification. + * - values: a map of local name to access controlled value specification. + */ +final case class ModuleDefinition[+TA, +VA]( + types: ListMap[Name, AccessControlled[Documented[TypeDefinition[TA]]]], + values: ListMap[Name, AccessControlled[ValueDefinition[TA, VA]]] + ): + + def lookupValueDefinition(name: Name): Option[ValueDefinition[TA, VA]] = + values get name map (_.withPrivateAccess) + +object ModuleDefinition: + def empty[TA, VA]: ModuleDefinition[TA, VA] = + ModuleDefinition[TA, VA](ListMap.empty, ListMap.empty) diff --git a/morphir-ir/src/main/scala/org/finos/morphir/ir/ModuleName.scala b/morphir-ir/src/main/scala/org/finos/morphir/ir/ModuleName.scala new file mode 100644 index 00000000..d905db55 --- /dev/null +++ b/morphir-ir/src/main/scala/org/finos/morphir/ir/ModuleName.scala @@ -0,0 +1,3 @@ +package org.finos.morphir.ir + +final case class ModuleName(path: List[Name]) diff --git a/morphir-ir/src/main/scala/org/finos/morphir/ir/ModuleSpecification.scala b/morphir-ir/src/main/scala/org/finos/morphir/ir/ModuleSpecification.scala new file mode 100644 index 00000000..67b51949 --- /dev/null +++ b/morphir-ir/src/main/scala/org/finos/morphir/ir/ModuleSpecification.scala @@ -0,0 +1,7 @@ +package org.finos.morphir.ir +import scala.collection.immutable.ListMap + +final case class ModuleSpecification[+A]( + types: ListMap[Name, Documented[TypeSpecification[A]]], + values: ListMap[Name, ValueSpecification[A]] + ) diff --git a/morphir-ir/src/main/scala/org/finos/morphir/ir/Name.scala b/morphir-ir/src/main/scala/org/finos/morphir/ir/Name.scala new file mode 100644 index 00000000..56799112 --- /dev/null +++ b/morphir-ir/src/main/scala/org/finos/morphir/ir/Name.scala @@ -0,0 +1,3 @@ +package org.finos.morphir.ir + +final case class Name(name: List[String]) diff --git a/morphir-ir/src/main/scala/org/finos/morphir/ir/PackageDefinition.scala b/morphir-ir/src/main/scala/org/finos/morphir/ir/PackageDefinition.scala new file mode 100644 index 00000000..17faa494 --- /dev/null +++ b/morphir-ir/src/main/scala/org/finos/morphir/ir/PackageDefinition.scala @@ -0,0 +1,9 @@ +package org.finos.morphir.ir + +import scala.collection.immutable.ListMap + +final case class PackageDefinition[TA, VA]( + modules: ListMap[ModuleName, AccessControlled[ModuleDefinition[TA, VA]]] + ) +object PackageDefinition: + val empty: PackageDefinition[Unit, Unit] = PackageDefinition[Unit, Unit](ListMap.empty) diff --git a/morphir-ir/src/main/scala/org/finos/morphir/ir/PackageName.scala b/morphir-ir/src/main/scala/org/finos/morphir/ir/PackageName.scala new file mode 100644 index 00000000..c32ab228 --- /dev/null +++ b/morphir-ir/src/main/scala/org/finos/morphir/ir/PackageName.scala @@ -0,0 +1,3 @@ +package org.finos.morphir.ir + +final case class PackageName(path: List[Name]) diff --git a/morphir-ir/src/main/scala/org/finos/morphir/ir/PackageSpecification.scala b/morphir-ir/src/main/scala/org/finos/morphir/ir/PackageSpecification.scala new file mode 100644 index 00000000..38709c46 --- /dev/null +++ b/morphir-ir/src/main/scala/org/finos/morphir/ir/PackageSpecification.scala @@ -0,0 +1,8 @@ +package org.finos.morphir.ir +import scala.collection.immutable.ListMap + +final case class PackageSpecification[+A](modules: ListMap[ModuleName, ModuleSpecification[A]]) + +object PackageSpecification: + def empty[A]: PackageSpecification[A] = + PackageSpecification[A](ListMap.empty) diff --git a/morphir-ir/src/main/scala/org/finos/morphir/ir/ParameterInfo.scala b/morphir-ir/src/main/scala/org/finos/morphir/ir/ParameterInfo.scala new file mode 100644 index 00000000..158b4013 --- /dev/null +++ b/morphir-ir/src/main/scala/org/finos/morphir/ir/ParameterInfo.scala @@ -0,0 +1,7 @@ +package org.finos.morphir.ir + +final case class ParameterInfo[+TA, +VA]( + name: Name, + annotations: VA, + parameterType: Type[TA] + ) diff --git a/morphir-ir/src/main/scala/org/finos/morphir/ir/Path.scala b/morphir-ir/src/main/scala/org/finos/morphir/ir/Path.scala new file mode 100644 index 00000000..6adf3fb7 --- /dev/null +++ b/morphir-ir/src/main/scala/org/finos/morphir/ir/Path.scala @@ -0,0 +1,3 @@ +package org.finos.morphir.ir + +final case class Path(path: List[Name]) diff --git a/morphir-ir/src/main/scala/org/finos/morphir/ir/Pattern.scala b/morphir-ir/src/main/scala/org/finos/morphir/ir/Pattern.scala new file mode 100644 index 00000000..01a48b68 --- /dev/null +++ b/morphir-ir/src/main/scala/org/finos/morphir/ir/Pattern.scala @@ -0,0 +1,13 @@ +package org.finos.morphir.ir + +case class Pattern[+A](ann: A, details: PatternDetails[A]) + +enum PatternDetails[+A]: + case Wildcard + case As(pattern: Pattern[A], name: Name) + case Tuple(components: List[Pattern[A]]) + case Constructor(fqName: FQName, parameters: List[Pattern[A]]) + case EmptyList + case HeadTail(head: Pattern[A], tail: Pattern[A]) + case Literal(literal: Lit) + case Unit diff --git a/morphir-ir/src/main/scala/org/finos/morphir/ir/Type.scala b/morphir-ir/src/main/scala/org/finos/morphir/ir/Type.scala new file mode 100644 index 00000000..8fe2cd46 --- /dev/null +++ b/morphir-ir/src/main/scala/org/finos/morphir/ir/Type.scala @@ -0,0 +1,52 @@ +package org.finos.morphir.ir +import org.finos.morphir.ir.* + +final case class Type[+A](annotations: A, typeDetails: TypeDetails[A]): + def transform[B](f: A => B): Type[B] = + Type(f(annotations), typeDetails.transform(f)) + +object Type: + def reference(fqName: FQName, typeParameters: List[Type[Unit]]): Type[Unit] = + reference[Unit](fqName, typeParameters)(()) + + def reference[A](fqName: FQName, typeParameters: List[Type[A]])(annotations: A): Type[A] = + Type(annotations, TypeDetails.Reference(fqName, typeParameters)) + + val unit: Type[scala.Unit] = unit(()) + def unit[A](annotations: A): Type[A] = Type(annotations, TypeDetails.Unit) + + /** + * Creates a type variable. + */ + def variable(name: Name): Type[Unit] = variable(name)(()) + def variable[A](name: Name)(annotations: A): Type[A] = + Type(annotations, TypeDetails.Variable(name)) + +enum TypeDetails[+A]: + self => + + case Variable(name: Name) + case Reference(typeName: FQName, typeParameters: List[Type[A]]) + case Tuple(elementTypes: List[Type[A]]) + case Record(fields: List[Field[A]]) + case ExtensibleRecord(name: Name, fields: List[Field[A]]) + case Function(parameters: Type[A], returnType: Type[A]) + case Unit + + def transform[B](f: A => B): TypeDetails[B] = self match + case Variable(name) => Variable(name) + case Reference(typeName, typeParameters) => + Reference(typeName, typeParameters.transform(f)) + case Tuple(elementTypes) => + Tuple(elementTypes.transform(f)) + case Record(fields) => + Record(FieldList.mapAttributes(fields)(f)) + case ExtensibleRecord(name: Name, fields) => + ExtensibleRecord(name, FieldList.mapAttributes(fields)(f)) + case Function(parameters, returnType) => + Function(parameters = parameters.transform(f), returnType = returnType.transform(f)) + case Unit => Unit + +extension [A](self: List[Type[A]]) + def transform[B](f: A => B): List[Type[B]] = + self.map(t => t.transform(f)) diff --git a/morphir-ir/src/main/scala/org/finos/morphir/ir/TypeDefinition.scala b/morphir-ir/src/main/scala/org/finos/morphir/ir/TypeDefinition.scala new file mode 100644 index 00000000..2fe3ed89 --- /dev/null +++ b/morphir-ir/src/main/scala/org/finos/morphir/ir/TypeDefinition.scala @@ -0,0 +1,7 @@ +package org.finos.morphir.ir + +final case class TypeDefinition[+A](typeParams: List[Name], details: TypeDefinitionDetails[A]) + +enum TypeDefinitionDetails[+A]: + case TypeAlias(typeParams: List[Name], ref: Type[A]) + case CustomType(typeParams: List[Name], ctors: AccessControlled[TypeConstructors[A]]) diff --git a/morphir-ir/src/main/scala/org/finos/morphir/ir/TypeSpecification.scala b/morphir-ir/src/main/scala/org/finos/morphir/ir/TypeSpecification.scala new file mode 100644 index 00000000..e2c618a2 --- /dev/null +++ b/morphir-ir/src/main/scala/org/finos/morphir/ir/TypeSpecification.scala @@ -0,0 +1,14 @@ +package org.finos.morphir.ir + +final case class TypeSpecification[+A](typeParams: List[Name], details: TypeSpecificationDetails[A]) + +enum TypeSpecificationDetails[+A]: + self => + case CustomType(ctors: TypeConstructors[A]) + case OpaqueType + case TypeAlias(typeExp: Type[A]) + + def transform[B](f: A => B): TypeSpecificationDetails[B] = self match + case CustomType(ctors) => CustomType(ctors.transform(f)) + case OpaqueType => OpaqueType + case TypeAlias(typeExp) => TypeAlias(typeExp.transform(f)) diff --git a/morphir-ir/src/main/scala/org/finos/morphir/ir/Value.scala b/morphir-ir/src/main/scala/org/finos/morphir/ir/Value.scala new file mode 100644 index 00000000..c4372199 --- /dev/null +++ b/morphir-ir/src/main/scala/org/finos/morphir/ir/Value.scala @@ -0,0 +1,39 @@ +package org.finos.morphir.ir +import scala.collection.immutable.ListMap + +type RawValue = Value[Unit, Unit] +val RawValue = Value + +final case class Value[+TA, +VA](ann: VA, valueDetails: ValueDetails[VA, TA]) + +enum ValueDetails[+TA, +VA]: + case Literal(literal: Lit) + case Constructor(fqName: FQName) + case Tuple(components: scala.List[Value[TA, VA]]) + case List(elements: scala.List[Value[TA, VA]]) + case Record(fields: scala.List[(Name, Value[TA, VA])]) + case Variable(name: Name) + case Reference(fqName: FQName) + case Field(target: Value[TA, VA], name: Name) + case FieldFunction(name: Name) + case Apply(function: Value[TA, VA], parameters: Value[TA, VA]) + case Lambda(parameters: Pattern[VA], body: Value[TA, VA]) + case LetDefinition( + binding: Name, + definition: ValueDefinition[TA, VA], + inValue: Value[TA, VA] + ) + case LetRecursion(definitions: ListMap[Name, ValueDefinition[TA, VA]], inValue: Value[TA, VA]) + case Destructure( + pattern: Pattern[VA], + valueToDestruct: Value[TA, VA], + inValue: Value[TA, VA] + ) + case IfThenElse( + condition: Value[TA, VA], + thenBranch: Value[TA, VA], + elseBranch: Value[TA, VA] + ) + case PatternMatch(matchOn: Value[TA, VA], cases: scala.List[(Pattern[VA], Value[TA, VA])]) + case UpdateRecord(valueToUpdate: Value[TA, VA], fieldsToUpdate: Value[TA, VA]) + case Unit diff --git a/morphir-ir/src/main/scala/org/finos/morphir/ir/ValueDefinition.scala b/morphir-ir/src/main/scala/org/finos/morphir/ir/ValueDefinition.scala new file mode 100644 index 00000000..6a66cbb1 --- /dev/null +++ b/morphir-ir/src/main/scala/org/finos/morphir/ir/ValueDefinition.scala @@ -0,0 +1,12 @@ +package org.finos.morphir.ir + +/** + * Type that represents a value or function definition. A definition is the actual data or logic as + * opposed to a specification which is just the specification of those. Value definitions can be + * typed or untyped. Exposed values have to be typed. + */ +final case class ValueDefinition[+TA, +VA]( + inputTypes: List[ParameterInfo[TA, VA]], + outputType: Type[TA], + body: Value[TA, VA] + ) diff --git a/morphir-ir/src/main/scala/org/finos/morphir/ir/ValueSpecification.scala b/morphir-ir/src/main/scala/org/finos/morphir/ir/ValueSpecification.scala new file mode 100644 index 00000000..2863746c --- /dev/null +++ b/morphir-ir/src/main/scala/org/finos/morphir/ir/ValueSpecification.scala @@ -0,0 +1,12 @@ +package org.finos.morphir.ir + +/** + * Type that represents a value or function specification. The specification of what the value or + * function is without the actual data or logic behind it. + */ +final case class ValueSpecification[+A](inputs: List[(Name, Type[A])], output: Type[A]): + def transform[B](f: A => B): ValueSpecification[B] = + ValueSpecification( + inputs = inputs.map { case (name, tpe) => (name, tpe.transform(f)) }, + output = output.transform(f) + ) diff --git a/morphir-ir/src/test/scala/org/finos/morphir/ir/PackageDefinitionSpec.scala b/morphir-ir/src/test/scala/org/finos/morphir/ir/PackageDefinitionSpec.scala new file mode 100644 index 00000000..298ea254 --- /dev/null +++ b/morphir-ir/src/test/scala/org/finos/morphir/ir/PackageDefinitionSpec.scala @@ -0,0 +1,15 @@ +package org.finos.morphir.ir + +import zio.test.{DefaultRunnableSpec, assert} +import zio.test.Assertion.* + +import scala.collection.immutable.ListMap + +object PackageDefinitionSpec extends DefaultRunnableSpec { + def spec = suite("PackageDefinition spec")( + test("It should be possible to create an empty PackageDefinition"){ + val actual = PackageDefinition.empty + assert(actual)(equalTo(PackageDefinition[Unit,Unit](ListMap.empty))) + } + ) +} \ No newline at end of file diff --git a/morphir-ir/src/test/scala/org/finos/morphir/ir/PackageSpecificationSpec.scala b/morphir-ir/src/test/scala/org/finos/morphir/ir/PackageSpecificationSpec.scala new file mode 100644 index 00000000..0f258479 --- /dev/null +++ b/morphir-ir/src/test/scala/org/finos/morphir/ir/PackageSpecificationSpec.scala @@ -0,0 +1,9 @@ +package org.finos.morphir.ir +import zio.test.{assert, DefaultRunnableSpec} +import zio.test.Assertion.* + +object PackageSpecificationSpec extends DefaultRunnableSpec { + def spec = suite("PackageSpecification spec")() +} + + diff --git a/morphir-ir/src/test/scala/org/finos/morphir/ir/TypeSpec.scala b/morphir-ir/src/test/scala/org/finos/morphir/ir/TypeSpec.scala new file mode 100644 index 00000000..36ae6c87 --- /dev/null +++ b/morphir-ir/src/test/scala/org/finos/morphir/ir/TypeSpec.scala @@ -0,0 +1,21 @@ +package org.finos.morphir.ir +import zio.test.{assert, DefaultRunnableSpec} +import zio.test.Assertion.* +object TypeSpec extends DefaultRunnableSpec: + def spec = suite("TypeSpec")( + test("Types with different annotations should not be equal"){ + val typeA = Type("A", TypeDetails.Unit) + val typeB = Type("B", TypeDetails.Unit) + assert(typeA)(not (equalTo(typeB))) + }, + test("unit constant should be a Type[Unit]"){ + val expected:Type[Unit] = Type.unit(()) + val actual = Type.unit + assert(actual)(equalTo(expected)) + }, + test("annotateWith should map the attributes directly on the Type"){ + val expected = Type.unit("Test!") + val actual = Type.unit.transform(_ => "Test!") + assert(actual)(equalTo(expected)) + } + ) \ No newline at end of file diff --git a/morphir-ir/src/test/scala/org/finos/morphir/ir/experiments/MetaProgrammingSandbox.scala b/morphir-ir/src/test/scala/org/finos/morphir/ir/experiments/MetaProgrammingSandbox.scala new file mode 100644 index 00000000..4adf4e7f --- /dev/null +++ b/morphir-ir/src/test/scala/org/finos/morphir/ir/experiments/MetaProgrammingSandbox.scala @@ -0,0 +1,11 @@ +package org.finos.morphir.ir.experiments +// import scala.quoted._ +// import zio._ +object MetaProgrammingSandbox { + // inline def info[T]:Unit = ${} + // def infoImpl[T:Type](using Quotes):Expr[Unit] = { + // Type.of[T] match { + // case '[]' + // } + // } +} diff --git a/morphir/sdk/core/src/morphir/sdk/Tuple.scala b/morphir/sdk/core/src/morphir/sdk/Tuple.scala new file mode 100644 index 00000000..55223ceb --- /dev/null +++ b/morphir/sdk/core/src/morphir/sdk/Tuple.scala @@ -0,0 +1,31 @@ +/* +Copyright 2020 Morgan Stanley + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + */ + +package morphir.sdk + +object Tuple { + type Tuple[K, V] = (K, V) + + def pair[K, V](k: K)(v: V): Tuple[K, V] = (k, v) + + def first[K, V](t: Tuple[K, V]): K = t._1 + def second[K, V](t: Tuple[K, V]): V = t._2 + + def mapFirst[K, V, O](f: K => O)(t: Tuple[K, V]): Tuple[O, V] = pair(f(t._1))(t._2) + def mapSecond[K, V, O](f: V => O)(t: Tuple[K, V]): Tuple[K, O] = pair(t._1)(f(t._2)) + def mapBoth[K, V, KO, VO](kf: K => KO)(vf: V => VO)(t: Tuple[K, V]): Tuple[KO, VO] = pair(kf(t._1))(vf(t._2)) + +} diff --git a/project/Dependencies.scala b/project/Dependencies.scala new file mode 100644 index 00000000..ad64e50f --- /dev/null +++ b/project/Dependencies.scala @@ -0,0 +1,19 @@ +import sbt._ + +object Dependencies { + case object dev { + case object zio { + private val zioVersion = "1.0.9" + val zio = "dev.zio" %% "zio" % zioVersion + val `zio-test` = "dev.zio" %% "zio-test" % zioVersion + val `zio-test-sbt` = "dev.zio" %% "zio-test-sbt" % zioVersion + + } + } + + val zioCommonDeps = Seq( + dev.zio.zio, + dev.zio.`zio-test` % "test", + dev.zio.`zio-test-sbt` % "test" + ) +} diff --git a/project/build.properties b/project/build.properties new file mode 100644 index 00000000..bb5389da --- /dev/null +++ b/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.5.5 \ No newline at end of file diff --git a/project/plugins.sbt b/project/plugins.sbt new file mode 100644 index 00000000..7639e342 --- /dev/null +++ b/project/plugins.sbt @@ -0,0 +1,2 @@ +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.3") +addSbtPlugin("com.typesafe.sbt" % "sbt-git" % "1.0.0")