diff --git a/CustomizeTheBuild.md b/CustomizeTheBuild.md new file mode 100644 index 000000000..beaae8276 --- /dev/null +++ b/CustomizeTheBuild.md @@ -0,0 +1,51 @@ +# Customizing the Build + +## Using build.user.conf + +* Add a build.user.conf to the root directory. +* This file can be used to set BuildSettings -> ScalaSettings. +* The path to each setting is based on the path in the BuildSettings case class + +For example, to disable the JavaScript and Native builds, as well as +to only build with Scala 3.3.0 do the following: + +``` +# build.user.conf +js.enable=false +native.enable=false +scala.defaultCrossScalaVersions=3.3.0 +``` + +It is very useful to generate the IntelliJ settings file in this mode +as it will radically increase IntelliJ's build performance. + +```bash +./mill mill.scalalib.GenIdea/idea +``` + +## Command Environment Variables + +Another way to specify certain properties on the command line is +through environment variables. +``` +MORPHIR_BUILD_JVM_ENABLE=false ./mill -i showBuildSettings +``` +Make sure to use `./mill -i` with this feature in order to +for these settings to take effect. Otherwise mill will use the background +mill server which is on a separate JVM and these settings will not take +effect (however the build.user.conf approach above will still work). + +## Modifying JVM Properties + +Use `.mill-jvm-opts` to set Java properties for the build. + +## Dev Mode + +In order to easily disable Native/JS builds and set the Scala +version to 3.3.0 you can also use a global environment variable. +Add the following to your `.zprofile` (on OSX) or `.bashrc` (on Linux) +etc... + +``` +export MORPHIR_SCALA_DEV_MODE='true' +``` diff --git a/README.md b/README.md index fc568bc84..12d64aca3 100644 --- a/README.md +++ b/README.md @@ -49,9 +49,9 @@ Morphir-jvm use [mill](https://com-lihaoyi.github.io/mill) as its build tool. If you are using IntelliJ IDEA to edit morphir-jvm's Scala code, you can create the IntelliJ project files via: -```bash +````bash ./mill mill.scalalib.GenIdea/idea -``` +```` ### BSP Setup diff --git a/examples/morphir-elm-projects/evaluator-tests/src/Morphir/Examples/App/EnumTest.elm b/examples/morphir-elm-projects/evaluator-tests/src/Morphir/Examples/App/EnumTest.elm new file mode 100644 index 000000000..7e9186688 --- /dev/null +++ b/examples/morphir-elm-projects/evaluator-tests/src/Morphir/Examples/App/EnumTest.elm @@ -0,0 +1,6 @@ +module Morphir.Examples.App.EnumTest exposing (..) + +type Amount = Amount Int + +amount: Amount +amount = Amount 123 diff --git a/morphir/datamodel/src-2/datamodel/Deriver.scala b/morphir/datamodel/src-2/datamodel/Deriver.scala new file mode 100644 index 000000000..24f65d3f6 --- /dev/null +++ b/morphir/datamodel/src-2/datamodel/Deriver.scala @@ -0,0 +1,7 @@ +package org.finos.morphir.datamodel + +// Stub so Scala 2 can compile org.finos.morphir.datamodel package since it requires the Deriver trait +trait Deriver[T] { + def derive(value: T): Data + def concept: Concept +} diff --git a/morphir/datamodel/src-3/org/finos/morphir/datamodel/Deriver.scala b/morphir/datamodel/src-3/org/finos/morphir/datamodel/Deriver.scala index 23df5ae5f..67eec7039 100644 --- a/morphir/datamodel/src-3/org/finos/morphir/datamodel/Deriver.scala +++ b/morphir/datamodel/src-3/org/finos/morphir/datamodel/Deriver.scala @@ -17,17 +17,12 @@ trait Deriver[T] { def concept: Concept } -trait SpecificDeriver[T] extends Deriver[T] { - def derive(value: T): Data - def concept: Concept -} - object Deriver { import DeriverTypes._ import DeriverMacros._ inline def toData[T](value: T): Data = { - import org.finos.morphir.datamodel.Derivers.{given, _} + import org.finos.morphir.datamodel.{given, _} val deriver = Deriver.gen[T] deriver.derive(value) } diff --git a/morphir/datamodel/src-3/org/finos/morphir/datamodel/Derivers.scala b/morphir/datamodel/src-3/org/finos/morphir/datamodel/Derivers.scala index b5c9841fd..6180b111a 100644 --- a/morphir/datamodel/src-3/org/finos/morphir/datamodel/Derivers.scala +++ b/morphir/datamodel/src-3/org/finos/morphir/datamodel/Derivers.scala @@ -14,119 +14,117 @@ import org.finos.morphir.datamodel.Concept import scala.collection.immutable.ListMap import scala.collection.mutable.LinkedHashMap -object Derivers { - given SpecificDeriver[Boolean] with - def derive(value: Boolean) = Data.Boolean(value) - def concept = Concept.Boolean - - given SpecificDeriver[Byte] with - def derive(value: Byte) = Data.Byte(value) - def concept = Concept.Byte - - given SpecificDeriver[BigDecimal] with - def derive(value: BigDecimal) = Data.Decimal(value) - def concept = Concept.Decimal - - given SpecificDeriver[BigInt] with - def derive(value: BigInt) = Data.Integer(value) - def concept = Concept.Integer - - given SpecificDeriver[Short] with - def derive(value: Short) = Data.Int16(value) - def concept = Concept.Int16 - - given SpecificDeriver[Int] with - def derive(value: Int) = Data.Int32(value) - def concept = Concept.Int32 - - given SpecificDeriver[String] with - def derive(value: String) = Data.String(value) - def concept = Concept.String - - given SpecificDeriver[LocalDate] with - def derive(value: LocalDate) = Data.LocalDate(value) - def concept = Concept.LocalDate - - given SpecificDeriver[Month] with - def derive(value: Month) = Data.Month(value) - def concept = Concept.Month - - given SpecificDeriver[LocalTime] with - def derive(value: LocalTime) = Data.LocalTime(value) - def concept = Concept.LocalTime - - given SpecificDeriver[Char] with - def derive(value: Char) = Data.Char(value) - def concept = Concept.Char - - given SpecificDeriver[Unit] with - def derive(value: Unit) = Data.Unit - def concept = Concept.Unit - - given optionDeriver[T](using elementDeriver: Deriver[T]): SpecificDeriver[Option[T]] with - def derive(value: Option[T]) = - value match - case Some(value) => Data.Optional.Some(elementDeriver.derive(value), elementDeriver.concept) - case None => Data.Optional.None(elementDeriver.concept) - def concept = Concept.Optional(elementDeriver.concept) - - given optionSomeDeriver[T](using elementDeriver: Deriver[T]): SpecificDeriver[Some[T]] with - def derive(value: Some[T]) = Data.Optional.Some(elementDeriver.derive(value.value), elementDeriver.concept) - def concept = Concept.Optional(elementDeriver.concept) - - given optionNoneDeriver: SpecificDeriver[scala.None.type] with - def derive(value: scala.None.type) = Data.Optional.None(Concept.Nothing) - def concept = Concept.Optional(Concept.Nothing) - - given listDeriver[T](using elementDeriver: Deriver[T]): SpecificDeriver[List[T]] with { - def derive(value: scala.List[T]) = - def toData(value: T) = elementDeriver.derive(value) - // Take the schema from the elementDeriver instead of the list elements - // because even if the elements themeselves have more specific schemas than the derver schema, - // the deriver schema has a generalization of the elements whose type is the only valid - // type for the whole list. - Data.List(value.map(toData(_)), elementDeriver.concept) - - def concept: Concept.List = Concept.List(elementDeriver.concept) - } - - /* - * Since we want to have the option to use either ordered or unordered maps in the DDL, - * derivers for ordered and non-ordered map variants have been provided. In particular - * since the Scala ListMap is problematic in many ways (e.g. lookup time is O(n)) the - * baseline implementation for the deriver uses scala's LinkedHashMap. Since - * this datastructure is mutable, we make a copy of it during the derivation process - * so that changes to it will not cause changes to the underlying Data object. - */ - - given linkedMapDeriver[K, V](using - keyDeriver: Deriver[K], - valueDeriver: Deriver[V] - ): SpecificDeriver[LinkedHashMap[K, V]] with { - def derive(value: LinkedHashMap[K, V]) = - def toData(value: (K, V)) = (keyDeriver.derive(value._1), valueDeriver.derive(value._2)) - Data.Map.copyFrom(value.map(toData(_)), Concept.Map(keyDeriver.concept, valueDeriver.concept)) - - def concept: Concept.Map = Concept.Map(keyDeriver.concept, valueDeriver.concept) - } - - given listMapDeriver[K, V](using - keyDeriver: Deriver[K], - valueDeriver: Deriver[V] - ): SpecificDeriver[ListMap[K, V]] with - def derive(value: ListMap[K, V]): Data = linkedMapDeriver[K, V].derive(LinkedHashMap.from(value)) - def concept: Concept = linkedMapDeriver[K, V].concept - - given mapDeriver[K, V](using - keyDeriver: Deriver[K], - valueDeriver: Deriver[V] - ): SpecificDeriver[Map[K, V]] with - def derive(value: Map[K, V]): Data = linkedMapDeriver[K, V].derive(LinkedHashMap.from(value)) - def concept: Concept = linkedMapDeriver[K, V].concept - - implicit inline def autoProductDeriver[T <: Product]: GenericProductDeriver[T] = - GenericProductDeriver.gen[T] - - implicit inline def autoSumDeriver[T]: GenericSumDeriver[T] = - GenericSumDeriver.gen[T] +given booleanDeriver: SpecificDeriver[Boolean] with + def derive(value: Boolean) = Data.Boolean(value) + def concept = Concept.Boolean + +given byteDeriver: SpecificDeriver[Byte] with + def derive(value: Byte) = Data.Byte(value) + def concept = Concept.Byte + +given bigDecimalDeriver: SpecificDeriver[BigDecimal] with + def derive(value: BigDecimal) = Data.Decimal(value) + def concept = Concept.Decimal + +given bigIntDeriver: SpecificDeriver[BigInt] with + def derive(value: BigInt) = Data.Integer(value) + def concept = Concept.Integer + +given shortDeriver: SpecificDeriver[Short] with + def derive(value: Short) = Data.Int16(value) + def concept = Concept.Int16 + +given intDeriver: SpecificDeriver[Int] with + def derive(value: Int) = Data.Int32(value) + def concept = Concept.Int32 + +given stringDeriver: SpecificDeriver[String] with + def derive(value: String) = Data.String(value) + def concept = Concept.String + +given localDateDeriver: SpecificDeriver[LocalDate] with + def derive(value: LocalDate) = Data.LocalDate(value) + def concept = Concept.LocalDate + +given monthDeriver: SpecificDeriver[Month] with + def derive(value: Month) = Data.Month(value) + def concept = Concept.Month + +given localTimeDeriver: SpecificDeriver[LocalTime] with + def derive(value: LocalTime) = Data.LocalTime(value) + def concept = Concept.LocalTime + +given charDeriver: SpecificDeriver[Char] with + def derive(value: Char) = Data.Char(value) + def concept = Concept.Char + +given unitDeriver: SpecificDeriver[Unit] with + def derive(value: Unit) = Data.Unit + def concept = Concept.Unit + +given optionDeriver[T](using elementDeriver: Deriver[T]): SpecificDeriver[Option[T]] with + def derive(value: Option[T]) = + value match + case Some(value) => Data.Optional.Some(elementDeriver.derive(value), elementDeriver.concept) + case None => Data.Optional.None(elementDeriver.concept) + def concept = Concept.Optional(elementDeriver.concept) + +given optionSomeDeriver[T](using elementDeriver: Deriver[T]): SpecificDeriver[Some[T]] with + def derive(value: Some[T]) = Data.Optional.Some(elementDeriver.derive(value.value), elementDeriver.concept) + def concept = Concept.Optional(elementDeriver.concept) + +given optionNoneDeriver: SpecificDeriver[scala.None.type] with + def derive(value: scala.None.type) = Data.Optional.None(Concept.Nothing) + def concept = Concept.Optional(Concept.Nothing) + +given listDeriver[T](using elementDeriver: Deriver[T]): SpecificDeriver[List[T]] with { + def derive(value: scala.List[T]) = + def toData(value: T) = elementDeriver.derive(value) + // Take the schema from the elementDeriver instead of the list elements + // because even if the elements themeselves have more specific schemas than the derver schema, + // the deriver schema has a generalization of the elements whose type is the only valid + // type for the whole list. + Data.List(value.map(toData(_)), elementDeriver.concept) + + def concept: Concept.List = Concept.List(elementDeriver.concept) } + +/* + * Since we want to have the option to use either ordered or unordered maps in the DDL, + * derivers for ordered and non-ordered map variants have been provided. In particular + * since the Scala ListMap is problematic in many ways (e.g. lookup time is O(n)) the + * baseline implementation for the deriver uses scala's LinkedHashMap. Since + * this datastructure is mutable, we make a copy of it during the derivation process + * so that changes to it will not cause changes to the underlying Data object. + */ + +given linkedMapDeriver[K, V](using + keyDeriver: Deriver[K], + valueDeriver: Deriver[V] +): SpecificDeriver[LinkedHashMap[K, V]] with { + def derive(value: LinkedHashMap[K, V]) = + def toData(value: (K, V)) = (keyDeriver.derive(value._1), valueDeriver.derive(value._2)) + Data.Map.copyFrom(value.map(toData(_)), Concept.Map(keyDeriver.concept, valueDeriver.concept)) + + def concept: Concept.Map = Concept.Map(keyDeriver.concept, valueDeriver.concept) +} + +given listMapDeriver[K, V](using + keyDeriver: Deriver[K], + valueDeriver: Deriver[V] +): SpecificDeriver[ListMap[K, V]] with + def derive(value: ListMap[K, V]): Data = linkedMapDeriver[K, V].derive(LinkedHashMap.from(value)) + def concept: Concept = linkedMapDeriver[K, V].concept + +given mapDeriver[K, V](using + keyDeriver: Deriver[K], + valueDeriver: Deriver[V] +): SpecificDeriver[Map[K, V]] with + def derive(value: Map[K, V]): Data = linkedMapDeriver[K, V].derive(LinkedHashMap.from(value)) + def concept: Concept = linkedMapDeriver[K, V].concept + +implicit inline def autoProductDeriver[T <: Product]: GenericProductDeriver[T] = + GenericProductDeriver.gen[T] + +implicit inline def autoSumDeriver[T]: GenericSumDeriver[T] = + GenericSumDeriver.gen[T] diff --git a/morphir/datamodel/src-3/org/finos/morphir/datamodel/EnumWrapper.scala b/morphir/datamodel/src-3/org/finos/morphir/datamodel/EnumWrapper.scala new file mode 100644 index 000000000..8befdc377 --- /dev/null +++ b/morphir/datamodel/src-3/org/finos/morphir/datamodel/EnumWrapper.scala @@ -0,0 +1,254 @@ +package org.finos.morphir.datamodel + +import org.finos.morphir.datamodel.namespacing.PartialName + +extension (v: Data.Boolean.type) { + def deriveEnumWrapperNamespaced[T]( + partialName: PartialName, + label: String, + fromScalaType: T => Boolean + ): SpecificDeriver[T] = + new SpecificDeriver[T] { + val wrapper = SingleEnumWrapper(label, Concept.Boolean, partialName) + override def derive(value: T): Data.Case = wrapper.construct(Data.Boolean(fromScalaType(value))) + override def concept: Concept = wrapper.concept + } + inline def deriveEnumWrapper[T](label: String, fromScalaType: T => Boolean): SpecificDeriver[T] = { + val (partialName, _) = DeriverMacros.summonNamespaceOrFail[T] + deriveEnumWrapperNamespaced(partialName, label, fromScalaType) + } + inline def deriveEnumWrapper[T](fromScalaType: T => Boolean): SpecificDeriver[T] = { + val name = DeriverMacros.typeName[T] + deriveEnumWrapper(name, fromScalaType) + } +} + +extension (v: Data.Byte.type) { + def deriveEnumWrapperNamespaced[T]( + partialName: PartialName, + label: String, + fromScalaType: T => Byte + ): SpecificDeriver[T] = + new SpecificDeriver[T] { + val wrapper = SingleEnumWrapper(label, Concept.Byte, partialName) + override def derive(value: T): Data.Case = wrapper.construct(Data.Byte(fromScalaType(value))) + override def concept: Concept = wrapper.concept + } + inline def deriveEnumWrapper[T](label: String, fromScalaType: T => Byte): SpecificDeriver[T] = { + val (partialName, _) = DeriverMacros.summonNamespaceOrFail[T] + deriveEnumWrapperNamespaced(partialName, label, fromScalaType) + } + inline def deriveEnumWrapper[T](fromScalaType: T => Byte): SpecificDeriver[T] = { + val name = DeriverMacros.typeName[T] + deriveEnumWrapper(name, fromScalaType) + } +} + +extension (v: Data.Decimal.type) { + def deriveEnumWrapperNamespaced[T]( + partialName: PartialName, + label: String, + fromScalaType: T => scala.BigDecimal + ): SpecificDeriver[T] = + new SpecificDeriver[T] { + val wrapper = SingleEnumWrapper(label, Concept.Decimal, partialName) + override def derive(value: T): Data.Case = wrapper.construct(Data.Decimal(fromScalaType(value))) + override def concept: Concept = wrapper.concept + } + inline def deriveEnumWrapper[T](label: String, fromScalaType: T => scala.BigDecimal): SpecificDeriver[T] = { + val (partialName, _) = DeriverMacros.summonNamespaceOrFail[T] + deriveEnumWrapperNamespaced(partialName, label, fromScalaType) + } + inline def deriveEnumWrapper[T](fromScalaType: T => scala.BigDecimal): SpecificDeriver[T] = { + val name = DeriverMacros.typeName[T] + deriveEnumWrapper(name, fromScalaType) + } +} + +extension (v: Data.Integer.type) { + def deriveEnumWrapperNamespaced[T]( + partialName: PartialName, + label: String, + fromScalaType: T => scala.BigInt + ): SpecificDeriver[T] = + new SpecificDeriver[T] { + val wrapper = SingleEnumWrapper(label, Concept.Integer, partialName) + override def derive(value: T): Data.Case = wrapper.construct(Data.Integer(fromScalaType(value))) + override def concept: Concept = wrapper.concept + } + inline def deriveEnumWrapper[T](label: String, fromScalaType: T => scala.BigInt): SpecificDeriver[T] = { + val (partialName, _) = DeriverMacros.summonNamespaceOrFail[T] + deriveEnumWrapperNamespaced(partialName, label, fromScalaType) + } + inline def deriveEnumWrapper[T](fromScalaType: T => scala.BigInt): SpecificDeriver[T] = { + val name = DeriverMacros.typeName[T] + deriveEnumWrapper(name, fromScalaType) + } +} + +extension (v: Data.Int16.type) { + def deriveEnumWrapperNamespaced[T]( + partialName: PartialName, + label: String, + fromScalaType: T => Short + ): SpecificDeriver[T] = + new SpecificDeriver[T] { + val wrapper = SingleEnumWrapper(label, Concept.Int16, partialName) + override def derive(value: T): Data.Case = wrapper.construct(Data.Int16(fromScalaType(value))) + override def concept: Concept = wrapper.concept + } + inline def deriveEnumWrapper[T](label: String, fromScalaType: T => Short): SpecificDeriver[T] = { + val (partialName, _) = DeriverMacros.summonNamespaceOrFail[T] + deriveEnumWrapperNamespaced(partialName, label, fromScalaType) + } + inline def deriveEnumWrapper[T](fromScalaType: T => Short): SpecificDeriver[T] = { + val name = DeriverMacros.typeName[T] + deriveEnumWrapper(name, fromScalaType) + } +} + +extension (v: Data.Int32.type) { + def deriveEnumWrapperNamespaced[T]( + partialName: PartialName, + label: String, + fromScalaType: T => Int + ): SpecificDeriver[T] = + new SpecificDeriver[T] { + val wrapper = SingleEnumWrapper(label, Concept.Int32, partialName) + override def derive(value: T): Data.Case = wrapper.construct(Data.Int32(fromScalaType(value))) + override def concept: Concept = wrapper.concept + } + inline def deriveEnumWrapper[T](label: String, fromScalaType: T => Int): SpecificDeriver[T] = { + val (partialName, _) = DeriverMacros.summonNamespaceOrFail[T] + deriveEnumWrapperNamespaced(partialName, label, fromScalaType) + } + inline def deriveEnumWrapper[T](fromScalaType: T => Int): SpecificDeriver[T] = { + val name = DeriverMacros.typeName[T] + deriveEnumWrapper(name, fromScalaType) + } +} + +extension (v: Data.String.type) { + def deriveEnumWrapperNamespaced[T]( + partialName: PartialName, + label: String, + fromScalaType: T => String + ): SpecificDeriver[T] = + new SpecificDeriver[T] { + val wrapper = SingleEnumWrapper(label, Concept.String, partialName) + override def derive(value: T): Data.Case = wrapper.construct(Data.String(fromScalaType(value))) + override def concept: Concept = wrapper.concept + } + inline def deriveEnumWrapper[T](label: String, fromScalaType: T => String): SpecificDeriver[T] = { + val (partialName, _) = DeriverMacros.summonNamespaceOrFail[T] + deriveEnumWrapperNamespaced(partialName, label, fromScalaType) + } + inline def deriveEnumWrapper[T](fromScalaType: T => String): SpecificDeriver[T] = { + val name = DeriverMacros.typeName[T] + deriveEnumWrapper(name, fromScalaType) + } +} + +extension (v: Data.LocalDate.type) { + def deriveEnumWrapperNamespaced[T]( + partialName: PartialName, + label: String, + fromScalaType: T => java.time.LocalDate + ): SpecificDeriver[T] = + new SpecificDeriver[T] { + val wrapper = SingleEnumWrapper(label, Concept.LocalDate, partialName) + override def derive(value: T): Data.Case = wrapper.construct(Data.LocalDate(fromScalaType(value))) + override def concept: Concept = wrapper.concept + } + inline def deriveEnumWrapper[T](label: String, fromScalaType: T => java.time.LocalDate): SpecificDeriver[T] = { + val (partialName, _) = DeriverMacros.summonNamespaceOrFail[T] + deriveEnumWrapperNamespaced(partialName, label, fromScalaType) + } + inline def deriveEnumWrapper[T](fromScalaType: T => java.time.LocalDate): SpecificDeriver[T] = { + val name = DeriverMacros.typeName[T] + deriveEnumWrapper(name, fromScalaType) + } +} + +extension (v: Data.Month.type) { + def deriveEnumWrapperNamespaced[T]( + partialName: PartialName, + label: String, + fromScalaType: T => java.time.Month + ): SpecificDeriver[T] = + new SpecificDeriver[T] { + val wrapper = SingleEnumWrapper(label, Concept.Month, partialName) + override def derive(value: T): Data.Case = wrapper.construct(Data.Month(fromScalaType(value))) + override def concept: Concept = wrapper.concept + } + inline def deriveEnumWrapper[T](label: String, fromScalaType: T => java.time.Month): SpecificDeriver[T] = { + val (partialName, _) = DeriverMacros.summonNamespaceOrFail[T] + deriveEnumWrapperNamespaced(partialName, label, fromScalaType) + } + inline def deriveEnumWrapper[T](fromScalaType: T => java.time.Month): SpecificDeriver[T] = { + val name = DeriverMacros.typeName[T] + deriveEnumWrapper(name, fromScalaType) + } +} + +extension (v: Data.LocalTime.type) { + def deriveEnumWrapperNamespaced[T]( + partialName: PartialName, + label: String, + fromScalaType: T => java.time.LocalTime + ): SpecificDeriver[T] = + new SpecificDeriver[T] { + val wrapper = SingleEnumWrapper(label, Concept.LocalTime, partialName) + override def derive(value: T): Data.Case = wrapper.construct(Data.LocalTime(fromScalaType(value))) + override def concept: Concept = wrapper.concept + } + inline def deriveEnumWrapper[T](label: String, fromScalaType: T => java.time.LocalTime): SpecificDeriver[T] = { + val (partialName, _) = DeriverMacros.summonNamespaceOrFail[T] + deriveEnumWrapperNamespaced(partialName, label, fromScalaType) + } + inline def deriveEnumWrapper[T](fromScalaType: T => java.time.LocalTime): SpecificDeriver[T] = { + val name = DeriverMacros.typeName[T] + deriveEnumWrapper(name, fromScalaType) + } +} + +extension (v: Data.Char.type) { + def deriveEnumWrapperNamespaced[T]( + partialName: PartialName, + label: String, + fromScalaType: T => Char + ): SpecificDeriver[T] = + new SpecificDeriver[T] { + val wrapper = SingleEnumWrapper(label, Concept.Char, partialName) + override def derive(value: T): Data.Case = wrapper.construct(Data.Char(fromScalaType(value))) + override def concept: Concept = wrapper.concept + } + inline def deriveEnumWrapper[T](label: String, fromScalaType: T => Char): SpecificDeriver[T] = { + val (partialName, _) = DeriverMacros.summonNamespaceOrFail[T] + deriveEnumWrapperNamespaced(partialName, label, fromScalaType) + } + inline def deriveEnumWrapper[T](fromScalaType: T => Char): SpecificDeriver[T] = { + val name = DeriverMacros.typeName[T] + deriveEnumWrapper(name, fromScalaType) + } +} + +extension (v: Data.Unit.type) { + def deriveEnumWrapperNamespaced[T]( + partialName: PartialName, + label: String + ): SpecificDeriver[T] = + new SpecificDeriver[T] { + val wrapper = UnitEnumWrapper(label, partialName) + override def derive(value: T): Data.Case = wrapper.construct + override def concept: Concept = wrapper.concept + } + inline def deriveEnumWrapper[T](label: String): SpecificDeriver[T] = { + val (partialName, _) = DeriverMacros.summonNamespaceOrFail[T] + deriveEnumWrapperNamespaced(partialName, label) + } + inline def deriveEnumWrapper[T]: SpecificDeriver[T] = { + val name = DeriverMacros.typeName[T] + deriveEnumWrapper(name) + } +} diff --git a/morphir/datamodel/src/org/finos/morphir/datamodel/Data.scala b/morphir/datamodel/src/org/finos/morphir/datamodel/Data.scala index e414b5abf..99d47f0eb 100644 --- a/morphir/datamodel/src/org/finos/morphir/datamodel/Data.scala +++ b/morphir/datamodel/src/org/finos/morphir/datamodel/Data.scala @@ -19,8 +19,7 @@ object Data { def Int(value: Int) = Int32(value) - sealed trait Basic[+A] extends Data - + sealed trait Basic[+A] extends Data case class Boolean(value: scala.Boolean) extends Basic[scala.Boolean] { val shape = Concept.Boolean } case class Byte(value: scala.Byte) extends Basic[Byte] { val shape = Concept.Byte } case class Decimal(value: scala.BigDecimal) extends Basic[scala.BigDecimal] { val shape = Concept.Decimal } @@ -34,6 +33,19 @@ object Data { case class Char(value: scala.Char) extends Basic[scala.Char] { val shape = Concept.Char } case object Unit extends Basic[scala.Unit] { val shape = Concept.Unit } + // Needed for Scala 3 extension methods to work + object Boolean {} + object Byte {} + object Decimal {} + object Integer {} + object Int16 {} + object Int32 {} + object String {} + object LocalDate {} + object Month {} + object LocalTime {} + object Char {} + /** * See notes on Concept.Enum for information on how this type is modelled */ diff --git a/morphir/datamodel/src/org/finos/morphir/datamodel/SingleEnumWrapper.scala b/morphir/datamodel/src/org/finos/morphir/datamodel/SingleEnumWrapper.scala new file mode 100644 index 000000000..feef2f31f --- /dev/null +++ b/morphir/datamodel/src/org/finos/morphir/datamodel/SingleEnumWrapper.scala @@ -0,0 +1,27 @@ +package org.finos.morphir.datamodel + +import org.finos.morphir.datamodel.namespacing.PartialName + +case class SingleEnumWrapper(label: String, innerShape: Concept, rootPath: PartialName) { + def construct(value: Data) = + Data.Case( + EnumLabel.Empty -> value + )(label, this.concept) + + def concept = + Concept.Enum( + rootPath % label, + Concept.Enum.Case(Label(label), EnumLabel.Empty -> innerShape) + ) +} + +case class UnitEnumWrapper(label: String, rootPath: PartialName) { + def construct = + Data.Case()(label, this.concept) + + def concept = + Concept.Enum( + rootPath % label, + Concept.Enum.Case(Label(label)) + ) +} diff --git a/morphir/datamodel/src/org/finos/morphir/datamodel/SpecificDeriver.scala b/morphir/datamodel/src/org/finos/morphir/datamodel/SpecificDeriver.scala new file mode 100644 index 000000000..3f8dad4c2 --- /dev/null +++ b/morphir/datamodel/src/org/finos/morphir/datamodel/SpecificDeriver.scala @@ -0,0 +1,6 @@ +package org.finos.morphir.datamodel + +trait SpecificDeriver[T] extends Deriver[T] { + def derive(value: T): Data + def concept: Concept +} diff --git a/morphir/datamodel/src/org/finos/morphir/datamodel/namespacing.scala b/morphir/datamodel/src/org/finos/morphir/datamodel/namespacing.scala index 989d0d19b..f68dcb7f5 100644 --- a/morphir/datamodel/src/org/finos/morphir/datamodel/namespacing.scala +++ b/morphir/datamodel/src/org/finos/morphir/datamodel/namespacing.scala @@ -35,6 +35,7 @@ object namespacing { def /(namespace: Namespace): Namespace = Namespace(unwrap(self) ++ unwrap(namespace)) } } + val ns = Namespace.ns type LocalName = LocalName.Type object LocalName extends Subtype[String] { @@ -74,6 +75,7 @@ object namespacing { def /(namespace: PackageName): PackageName = PackageName(unwrap(self) ++ unwrap(namespace)) } } + val root = PackageName.root final case class QualifiedName(pack: PackageName, namespace: Namespace, localName: LocalName) { self => override def toString: String = s"${pack}::${namespace.show}::${localName.value}" diff --git a/morphir/datamodel/test/src-3/org/finos/morphir/datamodel/EnumWrapperSpec.scala b/morphir/datamodel/test/src-3/org/finos/morphir/datamodel/EnumWrapperSpec.scala new file mode 100644 index 000000000..7b244a78d --- /dev/null +++ b/morphir/datamodel/test/src-3/org/finos/morphir/datamodel/EnumWrapperSpec.scala @@ -0,0 +1,124 @@ +package org.finos.morphir.datamodel + +import org.finos.morphir.datamodel.namespacing.* + +class EnumWrapperSpec extends munit.FunSuite { + case class MyBool(value: Boolean) + case class MyByte(value: Byte) + case class MyDecimal(value: BigDecimal) + case class MyInteger(value: scala.BigInt) + case class MyInt16(value: scala.Short) + case class MyInt32(value: scala.Int) + case class MyString(value: java.lang.String) + case class MyLocalDate(value: java.time.LocalDate) + case class MyMonth(value: java.time.Month) + case class MyLocalTime(value: java.time.LocalTime) + case class MyChar(value: scala.Char) + object MyUnit + + given rootName: GlobalDatamodelContext with { + override def value = root / "test" % ns / "enumwrapper" + } + + def enumMaker(label: String, data: Data, concept: Concept) = + SingleEnumWrapper(label, concept, rootName.value) + + test("Bool Deriver") { + given SpecificDeriver[MyBool] = Data.Boolean.deriveEnumWrapper("MyBoolLabel", _.value) + val myBoolData = Data.Boolean(true) + val maker = enumMaker("MyBoolLabel", myBoolData, Concept.Boolean) + val myBool = MyBool(true) + val myBoolDeriver = Deriver.gen[MyBool] + assertEquals(myBoolDeriver.concept, maker.concept) + assertEquals(myBoolDeriver.derive(myBool), maker.construct(myBoolData)) + } + + test("Byte Deriver") { + given SpecificDeriver[MyByte] = Data.Byte.deriveEnumWrapper("MyByteLabel", _.value) + val myByteData = Data.Byte(1) + val maker = enumMaker("MyByteLabel", myByteData, Concept.Byte) + val myByte = MyByte(1.toByte) + val myByteDeriver = Deriver.gen[MyByte] + assertEquals(myByteDeriver.concept, maker.concept) + assertEquals(myByteDeriver.derive(myByte), maker.construct(myByteData)) + } + + test("Decimal Deriver") { + given SpecificDeriver[MyDecimal] = Data.Decimal.deriveEnumWrapper("MyDecimalLabel", _.value) + val myDecimalData = Data.Decimal(BigDecimal(123)) + val maker = enumMaker("MyDecimalLabel", myDecimalData, Concept.Decimal) + val myDecimal = MyDecimal(BigDecimal(123)) + val myDecimalDeriver = Deriver.gen[MyDecimal] + assertEquals(myDecimalDeriver.concept, maker.concept) + assertEquals(myDecimalDeriver.derive(myDecimal), maker.construct(myDecimalData)) + } + + test("Integer Deriver") { + given SpecificDeriver[MyInteger] = Data.Integer.deriveEnumWrapper("MyIntegerLabel", _.value) + val myIntegerData = Data.Integer(123) + val maker = enumMaker("MyIntegerLabel", myIntegerData, Concept.Integer) + val myInteger = MyInteger(123) + val myIntegerDeriver = Deriver.gen[MyInteger] + assertEquals(myIntegerDeriver.concept, maker.concept) + assertEquals(myIntegerDeriver.derive(myInteger), maker.construct(myIntegerData)) + } + + test("Int16 Deriver") { + given SpecificDeriver[MyInt16] = Data.Int16.deriveEnumWrapper("MyInt16Label", _.value) + val myInt16Data = Data.Int16(123) + val maker = enumMaker("MyInt16Label", myInt16Data, Concept.Int16) + val myInt16 = MyInt16(123) + val myInt16Deriver = Deriver.gen[MyInt16] + assertEquals(myInt16Deriver.concept, maker.concept) + assertEquals(myInt16Deriver.derive(myInt16), maker.construct(myInt16Data)) + } + + test("Int32 Deriver") { + given SpecificDeriver[MyInt32] = Data.Int32.deriveEnumWrapper("MyInt32Label", _.value) + val myInt32Data = Data.Int32(123) + val maker = enumMaker("MyInt32Label", myInt32Data, Concept.Int32) + val myInt32 = MyInt32(123) + val myInt32Deriver = Deriver.gen[MyInt32] + assertEquals(myInt32Deriver.concept, maker.concept) + assertEquals(myInt32Deriver.derive(myInt32), maker.construct(myInt32Data)) + } + + test("String Deriver") { + given SpecificDeriver[MyString] = Data.String.deriveEnumWrapper("MyStringLabel", _.value) + val myStringData = Data.String("123") + val maker = enumMaker("MyStringLabel", myStringData, Concept.String) + val myString = MyString("123") + val myStringDeriver = Deriver.gen[MyString] + assertEquals(myStringDeriver.concept, maker.concept) + assertEquals(myStringDeriver.derive(myString), maker.construct(myStringData)) + } + + test("LocalDate Deriver") { + given SpecificDeriver[MyLocalDate] = Data.LocalDate.deriveEnumWrapper("MyLocalDateLabel", _.value) + val myLocalDateData = Data.LocalDate(java.time.LocalDate.now) + val maker = enumMaker("MyLocalDateLabel", myLocalDateData, Concept.LocalDate) + val myLocalDate = MyLocalDate(java.time.LocalDate.now) + val myLocalDateDeriver = Deriver.gen[MyLocalDate] + assertEquals(myLocalDateDeriver.concept, maker.concept) + assertEquals(myLocalDateDeriver.derive(myLocalDate), maker.construct(myLocalDateData)) + } + + test("Char Deriver") { + given SpecificDeriver[MyChar] = Data.Char.deriveEnumWrapper("MyCharLabel", _.value) + val myCharData = Data.Char('a') + val maker = enumMaker("MyCharLabel", myCharData, Concept.Char) + val myChar = MyChar('a') + val myCharDeriver = Deriver.gen[MyChar] + assertEquals(myCharDeriver.concept, maker.concept) + assertEquals(myCharDeriver.derive(myChar), maker.construct(myCharData)) + } + + test("Unit Deriver") { + given SpecificDeriver[MyUnit.type] = Data.Unit.deriveEnumWrapper("MyUnitLabel") + val maker = UnitEnumWrapper("MyUnitLabel", rootName.value) + val myUnit = MyUnit + val myUnitDeriver = Deriver.gen[MyUnit.type] + assertEquals(myUnitDeriver.concept, maker.concept) + assertEquals(myUnitDeriver.derive(myUnit), maker.construct) + } +} diff --git a/morphir/datamodel/test/src-3/org/finos/morphir/datamodel/PrintSpecExample.scala b/morphir/datamodel/test/src-3/org/finos/morphir/datamodel/PrintSpecExample.scala index 4317a2de7..8934c2da8 100644 --- a/morphir/datamodel/test/src-3/org/finos/morphir/datamodel/PrintSpecExample.scala +++ b/morphir/datamodel/test/src-3/org/finos/morphir/datamodel/PrintSpecExample.scala @@ -1,6 +1,6 @@ package org.finos.morphir.datamodel -import org.finos.morphir.datamodel.Derivers.{*, given} +import org.finos.morphir.datamodel.{*, given} object PrintSpecExample { import EnumGns._ diff --git a/morphir/datamodel/test/src-3/org/finos/morphir/datamodel/ToDataCollections.scala b/morphir/datamodel/test/src-3/org/finos/morphir/datamodel/ToDataCollections.scala index 80ed98004..6629d126d 100644 --- a/morphir/datamodel/test/src-3/org/finos/morphir/datamodel/ToDataCollections.scala +++ b/morphir/datamodel/test/src-3/org/finos/morphir/datamodel/ToDataCollections.scala @@ -4,6 +4,7 @@ import org.finos.morphir.datamodel.Deriver import scala.collection.immutable.ListMap import scala.collection.mutable.LinkedHashMap +import org.finos.morphir.datamodel.{*, given} class ToDataCollections extends munit.FunSuite { test("Primitive List") { diff --git a/morphir/datamodel/test/src-3/org/finos/morphir/datamodel/ToDataEnums.scala b/morphir/datamodel/test/src-3/org/finos/morphir/datamodel/ToDataEnums.scala index fa2df1d70..37d490f4f 100644 --- a/morphir/datamodel/test/src-3/org/finos/morphir/datamodel/ToDataEnums.scala +++ b/morphir/datamodel/test/src-3/org/finos/morphir/datamodel/ToDataEnums.scala @@ -1,6 +1,6 @@ package org.finos.morphir.datamodel -import org.finos.morphir.datamodel.Derivers.{*, given} +import org.finos.morphir.datamodel.{*, given} import org.finos.morphir.datamodel.Concept.Enum import org.finos.morphir.datamodel.Data import org.finos.morphir.datamodel.Data.Case diff --git a/morphir/datamodel/test/src-3/org/finos/morphir/datamodel/ToDataOptional.scala b/morphir/datamodel/test/src-3/org/finos/morphir/datamodel/ToDataOptional.scala index 8958cbee8..c9a43c3b6 100644 --- a/morphir/datamodel/test/src-3/org/finos/morphir/datamodel/ToDataOptional.scala +++ b/morphir/datamodel/test/src-3/org/finos/morphir/datamodel/ToDataOptional.scala @@ -4,6 +4,7 @@ import org.finos.morphir.datamodel.Deriver import scala.collection.immutable.ListMap import scala.collection.mutable.LinkedHashMap +import org.finos.morphir.datamodel.{*, given} case class Person(fName: String, lName: Int) object Joe { diff --git a/morphir/datamodel/test/src-3/org/finos/morphir/datamodel/ToDataPrimitives.scala b/morphir/datamodel/test/src-3/org/finos/morphir/datamodel/ToDataPrimitives.scala index a734ff208..21d3614a1 100644 --- a/morphir/datamodel/test/src-3/org/finos/morphir/datamodel/ToDataPrimitives.scala +++ b/morphir/datamodel/test/src-3/org/finos/morphir/datamodel/ToDataPrimitives.scala @@ -1,6 +1,7 @@ package org.finos.morphir.datamodel import org.finos.morphir.datamodel.Deriver +import org.finos.morphir.datamodel.{*, given} class ToDataPrimitives extends munit.FunSuite { test("Boolean-true") { assertEquals(Deriver.toData(true), Data.Boolean(true)) } diff --git a/morphir/datamodel/test/src-3/org/finos/morphir/datamodel/ToDataRecords.scala b/morphir/datamodel/test/src-3/org/finos/morphir/datamodel/ToDataRecords.scala index 0595b7a81..5b894d162 100644 --- a/morphir/datamodel/test/src-3/org/finos/morphir/datamodel/ToDataRecords.scala +++ b/morphir/datamodel/test/src-3/org/finos/morphir/datamodel/ToDataRecords.scala @@ -5,6 +5,7 @@ import org.finos.morphir.datamodel.Util.* import org.finos.morphir.datamodel.namespacing.{LocalName, Namespace, PartialName} import org.finos.morphir.datamodel.namespacing.PackageName.root import org.finos.morphir.datamodel.namespacing.Namespace.ns +import org.finos.morphir.datamodel.{*, given} class ToDataRecords extends munit.FunSuite { val gns: PartialName = root / "morphir" :: ns / "datamodel"