diff --git a/morphir-ir/shared/src/main/scala/zio/morphir/IR.scala b/morphir-ir/shared/src/main/scala/zio/morphir/IR.scala new file mode 100644 index 00000000..ace30e84 --- /dev/null +++ b/morphir-ir/shared/src/main/scala/zio/morphir/IR.scala @@ -0,0 +1,6 @@ +package zio.morphir + +import zio.morphir.ir._ + +final case class IR(valueSpecifications: Map[FQName, ???]) +object IR {} diff --git a/morphir-ir/shared/src/main/scala/zio/morphir/ir/AccessControlled.scala b/morphir-ir/shared/src/main/scala/zio/morphir/ir/AccessControlled.scala index 3a5c17fe..d1a11c83 100644 --- a/morphir-ir/shared/src/main/scala/zio/morphir/ir/AccessControlled.scala +++ b/morphir-ir/shared/src/main/scala/zio/morphir/ir/AccessControlled.scala @@ -10,6 +10,12 @@ final case class AccessControlled[+A](access: Access, value: A) { self => f(value) } + def fold[B](ifPublic: A => B, ifPrivate: A => B): B = + access match { + case Access.Public => ifPublic(self.value) + case Access.Private => ifPrivate(self.value) + } + def withPublicAccess: Option[A] = self match { case AccessControlled(Access.Public, a) => Some(a) case _ => None diff --git a/morphir-ir/shared/src/main/scala/zio/morphir/ir/FQName.scala b/morphir-ir/shared/src/main/scala/zio/morphir/ir/FQName.scala new file mode 100644 index 00000000..a67f0cca --- /dev/null +++ b/morphir-ir/shared/src/main/scala/zio/morphir/ir/FQName.scala @@ -0,0 +1,14 @@ +package zio.morphir.ir + +final case class FQName(packagePath: PackageName, modulePath: ModulePath, localName: Name) + +object FQName { + def apply(packagePath: Path, modulePath: Path, localName: Name): FQName = + FQName(PackageName(packagePath), ModulePath(modulePath), localName) + + val fqName: Path => Path => Name => FQName = packagePath => + modulePath => localName => FQName(PackageName(packagePath), ModulePath(modulePath), localName) + + /** Get the package path part of a fully-qualified name. */ + def getPackagePath(fqName: FQName): Path = fqName.packagePath.toPath +} diff --git a/morphir-ir/shared/src/main/scala/zio/morphir/ir/Literal.scala b/morphir-ir/shared/src/main/scala/zio/morphir/ir/Literal.scala new file mode 100644 index 00000000..fbec2f30 --- /dev/null +++ b/morphir-ir/shared/src/main/scala/zio/morphir/ir/Literal.scala @@ -0,0 +1,12 @@ +package zio.morphir.ir + +sealed trait Literal[+A] { + def value: A +} +object Literal { + final case class Bool(value: scala.Boolean) extends Literal[scala.Boolean] + final case class Char(value: scala.Char) extends Literal[scala.Char] + final case class String(value: java.lang.String) extends Literal[java.lang.String] + final case class WholeNumber(value: java.math.BigInteger) extends Literal[java.math.BigInteger] + final case class Float(value: java.math.BigDecimal) extends Literal[java.math.BigDecimal] +} diff --git a/morphir-ir/shared/src/main/scala/zio/morphir/ir/Module.scala b/morphir-ir/shared/src/main/scala/zio/morphir/ir/Module.scala new file mode 100644 index 00000000..6aa372cd --- /dev/null +++ b/morphir-ir/shared/src/main/scala/zio/morphir/ir/Module.scala @@ -0,0 +1,45 @@ +package zio.morphir.ir + +object Module { + + type Definition[+Annotations] = MorphirIR.ModuleDefinition[Annotations] + val Definition = MorphirIR.ModuleDefinition + + type Specification[+Annotations] = MorphirIR.ModuleSpecification[Annotations] + val Specification = MorphirIR.ModuleSpecification + + lazy val emptyDefinition: Definition[Any] = Definition.empty + + lazy val emptySpecification: Specification[Any] = Specification.empty + + final case class ModuleName(namespace: Path, localName: Name) { + lazy val toPath = namespace / localName + } + + final case class ModulePath(toPath: Path) + + final case class QualifiedModuleName(packageName: Path, module: Path) { + lazy val toPath = packageName / module + } + +} + +trait ModuleSpecFor[A] { + import Module.* + + def module: ModuleName + def spec: Specification[Any] +} + +object ModuleSpecFor { + import Module.* + + /** Summon the module specification for the given module/type. */ + def apply[A](implicit specFor: ModuleSpecFor[A]): ModuleSpecFor[A] = specFor + + def make[A](name: ModuleName)(moduleSpec: Specification[Any]): ModuleSpecFor[A] = + new ModuleSpecFor[A] { + val module = name + val spec = moduleSpec + } +} diff --git a/morphir-ir/shared/src/main/scala/zio/morphir/ir/MorphirIR.scala b/morphir-ir/shared/src/main/scala/zio/morphir/ir/MorphirIR.scala new file mode 100644 index 00000000..20b8a2dc --- /dev/null +++ b/morphir-ir/shared/src/main/scala/zio/morphir/ir/MorphirIR.scala @@ -0,0 +1,478 @@ +package zio.morphir.ir + +import zio.morphir.ir.recursive.* +import zio.prelude._ +import zio.prelude.fx._ +import scala.collection.immutable._ +import zio._ + +sealed trait MorphirIR[+Annotations] { self => + import MorphirIRCase.* + + def caseValue: MorphirIRCase[MorphirIR[Annotations]] + + def annotations: ZEnvironment[Annotations] + + def fold[Z](f: MorphirIRCase[Z] => Z): Z = + self.caseValue match { + case c @ DistributionCase.LibraryCase(_, _, _) => + f( + DistributionCase.LibraryCase( + c.packageName, + c.packageSpecs.map { case (name, spec) => (name, spec.fold(f)) }, + c.packageDef.fold(f) + ) + ) + case c @ ModuleDefinitionCase(_, _) => + f( + ModuleDefinitionCase( + c.types.map { case (name, value) => (name, value.map(d => d.map(_.fold(f)))) }, + c.values.map { case (name, value) => (name, value.map(_.fold(f))) } + ) + ) + case c @ ModuleSpecificationCase(_, _) => + f( + ModuleSpecificationCase( + c.types.map { case (name, value) => (name, value.map(_.fold(f))) }, + c.values.map { case (name, value) => (name, value.fold(f)) } + ) + ) + case c @ PackageDefinitionCase(_) => + f(PackageDefinitionCase(c.modules.map { case (name, value) => (name, value.map(_.fold(f))) })) + case c @ PackageSpecificationCase(_) => + f(PackageSpecificationCase(c.modules.map { case (name, spec) => (name, spec.fold(f)) })) + case c @ PatternCase.AsCase(_, _) => f(PatternCase.AsCase(c.pattern.fold(f), c.name)) + case c @ PatternCase.ConstructorCase(_, _) => + f(PatternCase.ConstructorCase(c.constructorName, c.argumentPatterns.map(_.fold(f)))) + case _ @PatternCase.EmptyListCase => f(PatternCase.EmptyListCase) + case c @ PatternCase.HeadTailCase(_, _) => f(PatternCase.HeadTailCase(c.head.fold(f), c.tail.fold(f))) + case c @ PatternCase.LiteralCase(_) => f(PatternCase.LiteralCase(c.value)) + case c @ PatternCase.TupleCase(_) => f(PatternCase.TupleCase(c.elements.map(_.fold(f)))) + case _ @PatternCase.UnitCase => f(PatternCase.UnitCase) + case _ @PatternCase.WildcardCase => f(PatternCase.WildcardCase) + case c @ ValueCase.ApplyCase(_, _) => f(ValueCase.ApplyCase(c.function.fold(f), c.arguments.map(_.fold(f)))) + case c @ ValueCase.ConstructorCase(_) => f(ValueCase.ConstructorCase(c.name)) + case c @ ValueCase.DestructureCase(_, _, _) => + f(ValueCase.DestructureCase(c.pattern.fold(f), c.valueToDestruct.fold(f), c.inValue.fold(f))) + case c @ ValueCase.FieldCase(_, _) => f(ValueCase.FieldCase(c.target.fold(f), c.name)) + case c @ ValueCase.FieldFunctionCase(_) => f(c) + case c @ ValueCase.IfThenElseCase(_, _, _) => + f(ValueCase.IfThenElseCase(c.condition.fold(f), c.thenBranch.fold(f), c.elseBranch.fold(f))) + case c @ ValueCase.LambdaCase(_, _) => f(ValueCase.LambdaCase(c.argumentPattern.fold(f), c.body.fold(f))) + case c @ ValueCase.LetDefinitionCase(_, _, _) => + f(ValueCase.LetDefinitionCase(c.valueName, c.valueDefinition.fold(f), c.inValue.fold(f))) + case c @ ValueCase.LetRecursionCase(_, _) => + f( + ValueCase.LetRecursionCase( + c.valueDefinitions.map { case (name, value) => (name, value.fold(f)) }, + c.inValue.fold(f) + ) + ) + case c @ ValueCase.ListCase(_) => f(ValueCase.ListCase(c.elements.map(_.fold(f)))) + case c @ ValueCase.LiteralCase(_) => f(c) + case c @ ValueCase.PatternMatchCase(_, _) => + f( + ValueCase.PatternMatchCase( + c.branchOutOn.fold(f), + c.cases.map { case (pattern, value) => + (pattern.fold(f), value.fold(f)) + } + ) + ) + case c @ ValueCase.RecordCase(_) => f(ValueCase.RecordCase(c.fields.map { case (k, v) => (k, v.fold(f)) })) + case c @ ValueCase.ReferenceCase(_) => f(c) + case c @ ValueCase.TupleCase(_) => f(ValueCase.TupleCase(c.elements.map(_.fold(f)))) + case _ @ValueCase.UnitCase => f(ValueCase.UnitCase) + case c @ ValueCase.UpdateRecordCase(_, _) => + f( + ValueCase.UpdateRecordCase( + c.valueToUpdate.fold(f), + c.fieldsToUpdate.map { case (name, value) => (name, value.fold(f)) } + ) + ) + case c @ ValueCase.VariableCase(_) => f(c) + case c @ ValueTreeCase.DefinitionCase(_, _, _) => + f( + ValueTreeCase.DefinitionCase( + c.inputTypes.map { case (name, self) => (name, self.fold(f)) }, + c.outputType.fold(f), + c.body.fold(f) + ) + ) + case c @ ValueTreeCase.SpecificationCase(_, _) => + f( + ValueTreeCase.SpecificationCase( + c.inputs.map { case (name, value) => (name, value.fold(f)) }, + c.output.fold(f) + ) + ) + case c @ TypeCase.ExtensibleRecordCase(_, _) => f(TypeCase.ExtensibleRecordCase(c.name, c.fields.map(_.fold(f)))) + case c @ TypeCase.FieldCase(_, _) => f(TypeCase.FieldCase(c.name, c.fieldType.fold(f))) + case c @ TypeCase.FunctionCase(_, _) => + f(TypeCase.FunctionCase(c.paramTypes.map(_.fold(f)), c.returnType.fold(f))) + case c @ TypeCase.RecordCase(_) => f(TypeCase.RecordCase(c.fields.map(_.fold(f)))) + case c @ TypeCase.ReferenceCase(_, _) => f(TypeCase.ReferenceCase(c.typeName, c.typeParams.map(_.fold(f)))) + case c @ TypeCase.TupleCase(_) => f(TypeCase.TupleCase(c.elementTypes.map(_.fold(f)))) + case _ @TypeCase.UnitCase => f(TypeCase.UnitCase) + case c @ TypeCase.VariableCase(_) => f(c) + case c @ TypeTreeCase.ConstructorsCase(_) => + f(TypeTreeCase.ConstructorsCase(c.args.map { case (name, tree) => (name, tree.fold(f)) })) + case c @ TypeTreeCase.DefinitionCase.CustomTypeDefinitionCase(_, _) => + f(TypeTreeCase.DefinitionCase.CustomTypeDefinitionCase(c.typeParams, c.ctors.map(_.fold(f)))) + case c @ TypeTreeCase.DefinitionCase.TypeAliasDefinitionCase(_, _) => + f(TypeTreeCase.DefinitionCase.TypeAliasDefinitionCase(c.typeParams, c.typeExpr.fold(f))) + case c @ TypeTreeCase.SpecificationCase.CustomTypeSpecificationCase(_, _) => + f(TypeTreeCase.SpecificationCase.CustomTypeSpecificationCase(c.typeParams, c.ctors.fold(f))) + case c @ TypeTreeCase.SpecificationCase.OpaqueTypeSpecificationCase(_) => f(c) + case c @ TypeTreeCase.SpecificationCase.TypeAliasSpecificationCase(_, _) => + f(TypeTreeCase.SpecificationCase.TypeAliasSpecificationCase(c.typeParams, c.typeExpr.fold(f))) + } + + def foldDown[Z](z: Z)(f: (Z, MorphirIR[Annotations]) => Z): Z = + caseValue.foldLeft(f(z, self))((z, recursive) => recursive.foldDown(z)(f)) + + def foldDownSome[Z](z: Z)(pf: PartialFunction[(Z, MorphirIR[Annotations]), Z]): Z = + foldDown(z)((z, recursive) => pf.lift(z -> recursive).getOrElse(z)) + + def foldM[F[+_]: AssociativeFlatten: Covariant: IdentityBoth, Z](f: MorphirIRCase[Z] => F[Z]): F[Z] = + fold[F[Z]](_.flip.flatMap(f)) + + def foldPure[W, S, R, E, Z](f: MorphirIRCase[Z] => ZPure[W, S, S, R, E, Z]): ZPure[W, S, S, R, E, Z] = + foldM(f) + + // TODO: Uncomment once appropriate instances are provided by ZIO Prelude + + // def foldManaged[R, E, Z](f: IRCase[Z] => ZManaged[R, E, Z]): ZManaged[R, E, Z] = + // foldM(f) + + // def foldSTM[R, E, Z](f: IRCase[Z] => ZSTM[R, E, Z]): ZSTM[R, E, Z] = + // foldM(f) + + // def foldValidation[W, E, Z](f: IRCase[Z] => ZValidation[W, E, Z]): ZValidation[W, E, Z] = + // foldM(f) + + def foldZIO[R, E, Z](f: MorphirIRCase[Z] => ZIO[R, E, Z]): ZIO[R, E, Z] = + foldM(f) + + def foldRecursive[Z](f: MorphirIRCase[(MorphirIR[Annotations], Z)] => Z): Z = + f(caseValue.map(recursive => recursive -> recursive.foldRecursive(f))) + + def foldUp[Z](z: Z)(f: (Z, MorphirIR[Annotations]) => Z): Z = + f(caseValue.foldLeft(z)((z, recursive) => recursive.foldUp(z)(f)), self) + + def foldUpSome[Z](z: Z)(pf: PartialFunction[(Z, MorphirIR[Annotations]), Z]): Z = + foldUp(z)((z, recursive) => pf.lift(z -> recursive).getOrElse(z)) + + def transformDown[Annotations0 >: Annotations]( + f: MorphirIR[Annotations0] => MorphirIR[Annotations0] + ): MorphirIR[Annotations0] = { + def loop(recursive: MorphirIR[Annotations0]): MorphirIR[Annotations] = + MorphirIR(f(recursive).caseValue.map(loop), annotations) + loop(self) + } + + def transformDownSome[Annotations0 >: Annotations]( + pf: PartialFunction[MorphirIR[Annotations0], MorphirIR[Annotations0]] + ): MorphirIR[Annotations0] = + transformDown[Annotations0]((recursive => pf.lift(recursive).getOrElse(recursive))) + + def transformUp[Annotations0 >: Annotations]( + f: MorphirIR[Annotations0] => MorphirIR[Annotations0] + ): MorphirIR[Annotations0] = { + def loop(recursive: MorphirIR[Annotations0]): MorphirIR[Annotations0] = + f(MorphirIR(recursive.caseValue.map(loop), annotations)) + loop(self) + } + + def transformUpSome[Annotations0 >: Annotations]( + pf: PartialFunction[MorphirIR[Annotations0], MorphirIR[Annotations0]] + ): MorphirIR[Annotations0] = + transformUp[Annotations0]((recursive => pf.lift(recursive).getOrElse(recursive))) + +} + +object MorphirIR { + + def apply[Annotations]( + caseValue0: MorphirIRCase[MorphirIR[Annotations]], + annotations0: ZEnvironment[Annotations] + ): MorphirIR[Annotations] = + new MorphirIR[Annotations] { + def caseValue = caseValue0 + def annotations = annotations0 + } + + def unapply[Annotations]( + morphir: MorphirIR[Annotations] + ): Option[(MorphirIRCase[MorphirIR[Annotations]], ZEnvironment[Annotations])] = + Some((morphir.caseValue, morphir.annotations)) + + sealed trait TypeTree[+Annotations] extends MorphirIR[Annotations] { self => + override def caseValue: TypeTreeCase[TypeTree[Annotations]] + } + + object TypeTree { + import TypeTreeCase.* + import DefinitionCase.* + import SpecificationCase.* + final case class Constructors[+Annotations]( + args: Map[Name, Type[Annotations]], + annotations: ZEnvironment[Annotations] + ) extends TypeTree[Annotations] { + override lazy val caseValue: TypeTreeCase[TypeTree[Annotations]] = ConstructorsCase(args) + } + + sealed trait Definition[+Annotations] extends TypeTree[Annotations] { self => + def typeParams: List[Name] + override def caseValue: DefinitionCase[TypeTree[Annotations]] + } + + object Definition { + object WithTypeParams { + def unapply[Annotations]( + ir: MorphirIR[Annotations] + ): Option[(List[Name], DefinitionCase[TypeTree[Annotations]])] = + ir match { + case ir: Definition[Annotations] => Some((ir.typeParams, ir.caseValue)) + case _ => None + } + } + final case class CustomTypeDefinition[+Annotations]( + typeParams: List[Name], + ctors: AccessControlled[Constructors[Annotations]], + annotations: ZEnvironment[Annotations] + ) extends Definition[Annotations] { + override lazy val caseValue: DefinitionCase[TypeTree[Annotations]] = CustomTypeDefinitionCase(typeParams, ctors) + } + + final case class TypeAliasDefinition[+Annotations]( + typeParams: List[Name], + typeExpr: Type[Annotations], + annotations: ZEnvironment[Annotations] + ) extends Definition[Annotations] { + override lazy val caseValue: DefinitionCase[TypeTree[Annotations]] = + TypeAliasDefinitionCase(typeParams, typeExpr) + } + } + + sealed trait Specification[+Annotations] extends TypeTree[Annotations] { self => + def typeParams: List[Name] + override def caseValue: SpecificationCase[TypeTree[Annotations]] + } + object Specification { + def unapply[Annotations](t: Specification[Annotations]): Option[(SpecificationCase[TypeTree[Annotations]])] = + Some(t.caseValue) + + object WithTypeParams { + def unapply[Annotations]( + ir: MorphirIR[Annotations] + ): Option[(List[Name], SpecificationCase[TypeTree[Annotations]])] = + ir match { + case s: Specification[Annotations] => Some((s.typeParams, s.caseValue)) + case _ => None + } + } + + final case class CustomTypeSpecification[+Annotations]( + typeParams: List[Name], + ctors: Constructors[Annotations], + annotations: ZEnvironment[Annotations] + ) extends Specification[Annotations] { + override lazy val caseValue: SpecificationCase[TypeTree[Annotations]] = + CustomTypeSpecificationCase(typeParams, ctors) + } + final case class OpaqueTypeSpecification[+Annotations]( + typeParams: List[Name], + annotations: ZEnvironment[Annotations] + ) extends Specification[Annotations] { + override lazy val caseValue: SpecificationCase[Type[Annotations]] = OpaqueTypeSpecificationCase(typeParams) + } + + final case class TypeAliasSpecification[+Annotations]( + typeParams: List[Name], + typeExpr: Type[Annotations], + annotations: ZEnvironment[Annotations] + ) extends Specification[Annotations] { + override lazy val caseValue: SpecificationCase[Type[Annotations]] = + TypeAliasSpecificationCase(typeParams, typeExpr) + } + } + } + + sealed trait Type[+Annotations] extends TypeTree[Annotations] { self => + // import TypeCase.* + + final def asType: Type[Annotations] = self + + override def caseValue: TypeCase[Type[Annotations]] + + def transformDown[Annotations0 >: Annotations]( + f: Type[Annotations0] => Type[Annotations0] + ): Type[Annotations0] = { + def loop(recursive: Type[Annotations0]): Type[Annotations] = + Type(f(recursive).caseValue.map(loop), annotations) + loop(self) + } + } + + object Type { + import TypeCase.* + + def apply[Annotations]( + caseValue0: TypeCase[Type[Annotations]], + annotations0: ZEnvironment[Annotations] + ): Type[Annotations] = + new Type[Annotations] { + override def caseValue: TypeCase[Type[Annotations]] = caseValue0 + override def annotations: ZEnvironment[Annotations] = annotations0 + } + + def ref(name: FQName): Reference[Any] = Reference(name, Chunk.empty, ZEnvironment.empty) + + /** + * Creates a type variable with the given `name`. + */ + def variable(name: Name): Variable[Any] = Variable(name, ZEnvironment.empty) + def variable(name: String): Variable[Any] = variable(Name(name)) + val unit: Type[Any] = Unit(ZEnvironment.empty) + + final case class Unit[+Annotations](annotations: ZEnvironment[Annotations]) extends Type[Annotations] { + override val caseValue: TypeCase[Type[Annotations]] = UnitCase + } + + final case class Field[+Annotations]( + name: Name, + fieldType: Type[Annotations], + annotations: ZEnvironment[Annotations] + ) extends Type[Annotations] { + override lazy val caseValue: FieldCase[Type[Annotations]] = FieldCase(name, fieldType) + } + object Field { + + object Case { + def unapply[Annotations](field: Field[Annotations]): Option[FieldCase[Type[Annotations]]] = + Some(field.caseValue) + } + } + + final case class Reference[+Annotations]( + name: FQName, + typeParams: Chunk[Type[Annotations]], + annotations: ZEnvironment[Annotations] + ) extends Type[Annotations] { + override lazy val caseValue: ReferenceCase[Type[Annotations]] = ReferenceCase(name, typeParams) + } + + object Reference { + object Case { + def unapply[Annotations](reference: Reference[Annotations]): Option[ReferenceCase[Type[Annotations]]] = + Some(reference.caseValue) + } + } + + final case class Variable[+Annotations](name: Name, annotations: ZEnvironment[Annotations]) + extends Type[Annotations] { + override lazy val caseValue: VariableCase = VariableCase(name) + } + object Variable { + object Case { + def unapply[Annotations](variable: Variable[Annotations]): Option[VariableCase] = + Some(variable.caseValue) + } + } + } + + sealed trait ValueTree[+Annotations] extends MorphirIR[Annotations] { self => + override def caseValue: ValueTreeCase[MorphirIR[Annotations]] + } + + object ValueTree { + import ValueTreeCase.* + + final case class Definition[+Annotations]( + inputTypes: Chunk[(Name, Type[Annotations])], + outputType: Type[Annotations], + body: Value[Annotations], + annotations: ZEnvironment[Annotations] + ) extends ValueTree[Annotations] { + override def caseValue: ValueTreeCase[MorphirIR[Annotations]] = DefinitionCase(inputTypes, outputType, body) + } + + final case class Specification[+Annotations]( + inputs: Chunk[(Name, Type[Annotations])], + output: Type[Annotations], + annotations: ZEnvironment[Annotations] + ) extends ValueTree[Annotations] { + override val caseValue: ValueTreeCase[MorphirIR[Annotations]] = SpecificationCase(inputs, output) + } + } + + sealed trait Value[+Annotations] extends ValueTree[Annotations] { self => + + def caseValue: ValueCase[Value[Annotations]] + } + + object Value { + import ValueCase.* + final case class Variable[+Annotations](name: Name, annotations: ZEnvironment[Annotations]) + extends Value[Annotations] { + override lazy val caseValue: VariableCase = VariableCase(name) + } + } + + sealed trait Distribution[+Annotations] extends MorphirIR[Annotations] { self => + def caseValue: DistributionCase[MorphirIR[Annotations]] + } + + object Distribution { + import DistributionCase.* + final case class Library[+Annotations]( + packageName: PackageName, + packageSpecs: Map[PackageName, PackageSpecification[Annotations]], + packageDef: PackageDefinition[Annotations], + annotations: ZEnvironment[Annotations] + ) extends Distribution[Annotations] { + override def caseValue: LibraryCase[MorphirIR[Annotations]] = LibraryCase(packageName, packageSpecs, packageDef) + } + } + + final case class ModuleDefinition[+Annotations]( + types: Map[Name, AccessControlled[Documented[TypeTree.Definition[Annotations]]]], + values: Map[Name, AccessControlled[ValueTree.Definition[Annotations]]], + annotations: ZEnvironment[Annotations] + ) extends MorphirIR[Annotations] { + override def caseValue: ModuleDefinitionCase[MorphirIR[Annotations]] = ModuleDefinitionCase(types, values) + } + + object ModuleDefinition { + val empty: ModuleDefinition[Any] = ModuleDefinition(Map.empty, Map.empty, ZEnvironment.empty) + } + + final case class ModuleSpecification[+Annotations]( + types: Map[Name, Documented[TypeTree.Specification[Annotations]]], + values: Map[Name, ValueTree.Specification[Annotations]], + annotations: ZEnvironment[Annotations] + ) extends MorphirIR[Annotations] { + override def caseValue: ModuleSpecificationCase[MorphirIR[Annotations]] = ModuleSpecificationCase(types, values) + } + + object ModuleSpecification { + val empty: ModuleSpecification[Any] = ModuleSpecification(Map.empty, Map.empty, ZEnvironment.empty) + } + + final case class PackageSpecification[+Annotations]( + modules: scala.collection.immutable.Map[ModuleName, ModuleSpecification[Annotations]], + annotations: ZEnvironment[Annotations] + ) extends MorphirIR[Annotations] { + override def caseValue: PackageSpecificationCase[ModuleSpecification[Annotations]] = PackageSpecificationCase( + modules + ) + } + + final case class PackageDefinition[+Annotations]( + modules: Map[ModuleName, AccessControlled[ModuleDefinition[Annotations]]], + annotations: ZEnvironment[Annotations] + ) extends MorphirIR[Annotations] { + override def caseValue: PackageDefinitionCase[ModuleDefinition[Annotations]] = PackageDefinitionCase(modules) + } +} diff --git a/morphir-ir/shared/src/main/scala/zio/morphir/ir/Name.scala b/morphir-ir/shared/src/main/scala/zio/morphir/ir/Name.scala new file mode 100644 index 00000000..e288153f --- /dev/null +++ b/morphir-ir/shared/src/main/scala/zio/morphir/ir/Name.scala @@ -0,0 +1,107 @@ +package zio.morphir.ir + +import scala.annotation.tailrec +import zio.Chunk + +final case class Name private (toList: List[String]) extends AnyVal { self => + def :+(that: String): Name = Name(self.toList :+ that) + def +:(that: String): Name = Name(that +: self.toList) + def ++(that: Name): Name = Name(self.toList ++ that.toList) + def /(that: Name): Path = Path(Chunk(self, that)) + + def humanize: List[String] = { + val words = toList + val join: List[String] => String = abbrev => abbrev.map(_.toUpperCase()).mkString("") + + @tailrec + def loop( + prefix: List[String], + abbrev: List[String], + suffix: List[String] + ): List[String] = + suffix match { + case Nil => + abbrev match { + case Nil => prefix + case _ => prefix ++ List(join(abbrev)) + } + case first :: rest => + if (first.length() == 1) + loop(prefix, abbrev ++ List(first), rest) + else + abbrev match { + case Nil => loop(prefix ++ List(first), List.empty, rest) + case _ => + loop(prefix ++ List(join(abbrev), first), List.empty, rest) + } + } + + loop(List.empty, List.empty, words.toList) + } + + /** + * Maps segments of the `Name`. + */ + def mapParts(f: String => String): Name = Name(self.toList.map(f)) + + def mkString(f: String => String)(sep: String): String = + toList.map(f).mkString(sep) + + def toLowerCase: String = + mkString(part => part.toLowerCase)("") + + def toCamelCase: String = + toList match { + case Nil => "" + case head :: tail => + (head :: tail.map(_.capitalize)).mkString("") + } + + def toKebabCase: String = + humanize.mkString("-") + + def toSnakeCase: String = + humanize.mkString("_") + + def toTitleCase: String = + toList + .map(_.capitalize) + .mkString("") + +} +object Name { + + val empty: Name = Name(Nil) + + private def wrap(value: List[String]): Name = Name(value) + + def apply(first: String, rest: String*): Name = + fromIterable(first +: rest) + + @inline def fromList(list: List[String]): Name = wrap(list) + def fromIterable(iterable: Iterable[String]): Name = + wrap(iterable.toList) + + def fromString(str: String): Name = { + val pattern = """[a-zA-Z][a-z]*|[0-9]+""".r + Name(pattern.findAllIn(str).toList.map(_.toLowerCase())) + } + + /** + * Creates a new name from a chunk of strings without checking. + */ + private[ir] def unsafeMake(value: List[String]): Name = Name(value) + + def toList(name: Name): List[String] = name.toList + + @inline def toTitleCase(name: Name): String = name.toTitleCase + + @inline def toCamelCase(name: Name): String = name.toCamelCase + + @inline def toSnakeCase(name: Name): String = name.toSnakeCase + + @inline def toKebabCase(name: Name): String = name.toKebabCase + + @inline def toHumanWords(name: Name): List[String] = name.humanize + +} diff --git a/morphir-ir/shared/src/main/scala/zio/morphir/ir/PackageModule.scala b/morphir-ir/shared/src/main/scala/zio/morphir/ir/PackageModule.scala new file mode 100644 index 00000000..6dbff905 --- /dev/null +++ b/morphir-ir/shared/src/main/scala/zio/morphir/ir/PackageModule.scala @@ -0,0 +1,30 @@ +package zio.morphir.ir +object PackageModule { + type Definition[+Annotations] = MorphirIR.PackageDefinition[Annotations] + val Definition = MorphirIR.PackageDefinition + + type Specification[+Annotations] = MorphirIR.PackageSpecification[Annotations] + val Specification = MorphirIR.PackageSpecification + + final case class PackageName(toPath: Path) { self => + def %(modulePath: ModulePath): PackageAndModulePath = PackageAndModulePath(self, modulePath) + def %(moduleName: ModuleName): FQName = + FQName(self, ModulePath(moduleName.namespace), moduleName.localName) + } + + final case class PackageAndModulePath(packageName: PackageName, modulePath: ModulePath) { self => + def %(name: Name): FQName = FQName(packageName, modulePath, name) + } +} + +trait PackageSpecFor[A] { + import PackageModule.* + + def packageName: PackageName + def spec: Specification[Any] + def nativeFunctions: Map[FQName, NativeFunction] +} + +object PackageSpecFor { + def apply[A](implicit packageSpecFor: PackageSpecFor[A]): PackageSpecFor[A] = packageSpecFor +} diff --git a/morphir-ir/shared/src/main/scala/zio/morphir/ir/Path.scala b/morphir-ir/shared/src/main/scala/zio/morphir/ir/Path.scala new file mode 100644 index 00000000..4a5b2a9a --- /dev/null +++ b/morphir-ir/shared/src/main/scala/zio/morphir/ir/Path.scala @@ -0,0 +1,25 @@ +package zio.morphir.ir +import zio.Chunk +import zio.morphir.ir.PackageModule.PackageAndModulePath + +final case class Path(segments: Chunk[Name]) { self => + + /** Constructs a new path by combining this path with the given name. */ + def /(name: Name): Path = Path(segments ++ Chunk(name)) + + /** Constructs a new path by combining this path with the given path. */ + def /(name: Path): Path = Path(segments ++ name.segments) + def %(other: Path): PackageAndModulePath = + PackageAndModulePath(PackageName(self), ModulePath(other)) + + def %(name: Name): ModuleName = ModuleName(self, name) + + /** Indicates whether this path is empty. */ + def isEmpty: Boolean = segments.isEmpty + def zip(other: Path): (Path, Path) = (self, other) + +} + +object Path { + val empty: Path = Path(Chunk.empty) +} diff --git a/morphir-ir/shared/src/main/scala/zio/morphir/ir/QName.scala b/morphir-ir/shared/src/main/scala/zio/morphir/ir/QName.scala new file mode 100644 index 00000000..2486e072 --- /dev/null +++ b/morphir-ir/shared/src/main/scala/zio/morphir/ir/QName.scala @@ -0,0 +1,16 @@ +package zio.morphir.ir + +final case class QName(modulePath: Path, localName: Name) { + @inline def toTuple: (Path, Name) = (modulePath, localName) + + override def toString: String = + if (modulePath.isEmpty) localName.toString + else modulePath.toString + "." + localName.toString +} + +object QName { + def fromTuple(tuple: (Path, Name)): QName = QName(tuple._1, tuple._2) + + def getLocalName(qname: QName): Name = qname.localName + def getModulePath(qname: QName): Path = qname.modulePath +} diff --git a/morphir-ir/shared/src/main/scala/zio/morphir/ir/Type.scala b/morphir-ir/shared/src/main/scala/zio/morphir/ir/Type.scala new file mode 100644 index 00000000..37d1e8b8 --- /dev/null +++ b/morphir-ir/shared/src/main/scala/zio/morphir/ir/Type.scala @@ -0,0 +1,64 @@ +package zio.morphir.ir +import zio.{Chunk, ZEnvironment} + +object Type { + type Type[+Annotations] = MorphirIR.Type[Annotations] + val Type = MorphirIR.Type + + type Field[+Annotations] = MorphirIR.Type.Field[Annotations] + val Field = MorphirIR.Type.Field + + type Specification[+Annotations] = MorphirIR.TypeTree.Specification[Annotations] + val Specification = MorphirIR.TypeTree.Specification + + type Definition[+Annotations] = MorphirIR.TypeTree.Definition[Annotations] + val Definition = MorphirIR.TypeTree.Definition + + def definitionToSpecification[Annotations](definition: Definition[Annotations]): Specification[Annotations] = + definition match { + case Definition.TypeAliasDefinition(typeParams, typeExpr, annotations) => + Specification.TypeAliasSpecification(typeParams, typeExpr, annotations) + case Definition.CustomTypeDefinition(params, accessControlledCtors, annotations) => + accessControlledCtors.fold( + ifPublic = ctors => Specification.CustomTypeSpecification(params, ctors, annotations), + ifPrivate = _ => Specification.OpaqueTypeSpecification(params, annotations) + ) + } + + val unit: Type.Unit[Any] = Type.Unit(ZEnvironment.empty) + def unit[Annotations](annotations: ZEnvironment[Annotations]): Type.Unit[Annotations] = + Type.Unit(annotations) + + /** + * Creates a type variable. + * {{{ + * toIR a == variable(Name.fromString("a")) + * toIR fooBar == variable(Name.fromString("fooBar")) + * }}} + */ + def variable[Annotations](name: Name, annotations: ZEnvironment[Annotations]): Type.Variable[Annotations] = + Type.Variable(name, annotations) + + /** + * Creates a type variable. + * {{{ + * toIR a == variable(Name.fromString("a")) + * toIR fooBar == variable(Name.fromString("fooBar")) + * }}} + */ + def variable(name: Name): Type.Variable[Any] = + Type.Variable(name, ZEnvironment.empty) + + def variable(name: String): Type.Variable[Any] = + Type.Variable(Name.fromString(name), ZEnvironment.empty) + + def reference[Annotations]( + name: FQName, + typeParams: Chunk[Type[Annotations]], + annotations: ZEnvironment[Annotations] + ): Type.Reference[Annotations] = + Type.Reference(name, typeParams, annotations) + + def reference(name: FQName, typeParams: Type[Any]*): Type.Reference[Any] = + Type.Reference(name, Chunk.fromIterable(typeParams), ZEnvironment.empty) +} diff --git a/morphir-ir/shared/src/main/scala/zio/morphir/ir/ir.recursive.scala b/morphir-ir/shared/src/main/scala/zio/morphir/ir/ir.recursive.scala index 2c2438e0..69949acd 100644 --- a/morphir-ir/shared/src/main/scala/zio/morphir/ir/ir.recursive.scala +++ b/morphir-ir/shared/src/main/scala/zio/morphir/ir/ir.recursive.scala @@ -3,9 +3,9 @@ import zio.Chunk import zio.prelude.* object recursive { - sealed trait IRCase[+Self] { self => + sealed trait MorphirIRCase[+Self] { self => - def map[B](f: Self => B): IRCase[B] = + def map[B](f: Self => B): MorphirIRCase[B] = self match { case c: DistributionCase[s] => c.map(f) case c: ModuleDefinitionCase[s] => c.map(f) @@ -17,7 +17,8 @@ object recursive { case c: ValueTreeCase[s] => c.map(f) } } - object IRCase { + + object MorphirIRCase { type TypeCase[+Self] = zio.morphir.ir.recursive.TypeCase[Self] val TypeCase = zio.morphir.ir.recursive.TypeCase @@ -33,9 +34,9 @@ object recursive { type ModuleDefinitionCase[+Self] = zio.morphir.ir.recursive.ModuleDefinitionCase[Self] val ModuleDefinitionCase = zio.morphir.ir.recursive.ModuleDefinitionCase - implicit def IRCaseForEach: ForEach[IRCase] = - new ForEach[IRCase] { - def forEach[G[+_]: IdentityBoth: Covariant, A, B](self: IRCase[A])(f: A => G[B]): G[IRCase[B]] = + implicit def IRCaseForEach: ForEach[MorphirIRCase] = + new ForEach[MorphirIRCase] { + def forEach[G[+_]: IdentityBoth: Covariant, A, B](self: MorphirIRCase[A])(f: A => G[B]): G[MorphirIRCase[B]] = self match { case c: DistributionCase[s] => c.forEach(f) case c @ ModuleDefinitionCase(_, _) => @@ -55,7 +56,7 @@ object recursive { } } - sealed trait DistributionCase[+Self] extends IRCase[Self] { self => + sealed trait DistributionCase[+Self] extends MorphirIRCase[Self] { self => import DistributionCase.* override def map[B](f: Self => B): DistributionCase[B] = @@ -83,16 +84,17 @@ object recursive { } } - final case class PackageSpecificationCase[+Self](modules: Map[ModuleName, Self]) extends IRCase[Self] - final case class PackageDefinitionCase[+Self](modules: Map[ModuleName, AccessControlled[Self]]) extends IRCase[Self] + final case class PackageSpecificationCase[+Self](modules: Map[ModuleName, Self]) extends MorphirIRCase[Self] + final case class PackageDefinitionCase[+Self](modules: Map[ModuleName, AccessControlled[Self]]) + extends MorphirIRCase[Self] final case class ModuleSpecificationCase[+Self](types: Map[Name, Documented[Self]], values: Map[Name, Self]) - extends IRCase[Self] + extends MorphirIRCase[Self] final case class ModuleDefinitionCase[+Self]( types: Map[Name, AccessControlled[Documented[Self]]], values: Map[Name, AccessControlled[Self]] - ) extends IRCase[Self] + ) extends MorphirIRCase[Self] - sealed trait TypeTreeCase[+Self] extends IRCase[Self] { self => + sealed trait TypeTreeCase[+Self] extends MorphirIRCase[Self] { self => import TypeTreeCase.* import DefinitionCase.* import SpecificationCase.* @@ -126,7 +128,7 @@ object recursive { object SpecificationCase { object TypeParams { - def unapply[Self](irCase: IRCase[Self]): Option[List[Name]] = irCase match { + def unapply[Self](irCase: MorphirIRCase[Self]): Option[List[Name]] = irCase match { case c: SpecificationCase[s] => Some(c.typeParams) case _ => None } @@ -210,7 +212,7 @@ object recursive { } } - sealed trait ValueTreeCase[+Self] extends IRCase[Self] { self => + sealed trait ValueTreeCase[+Self] extends MorphirIRCase[Self] { self => import ValueTreeCase.* override def map[B](f: Self => B): ValueTreeCase[B] = @@ -335,7 +337,7 @@ object recursive { } } - sealed trait PatternCase[+Self] extends IRCase[Self] { self => + sealed trait PatternCase[+Self] extends MorphirIRCase[Self] { self => import PatternCase.* override def map[B](f: Self => B): PatternCase[B] = self match { diff --git a/morphir-ir/shared/src/main/scala/zio/morphir/ir/ir.scala b/morphir-ir/shared/src/main/scala/zio/morphir/ir/ir.scala deleted file mode 100644 index 2593d940..00000000 --- a/morphir-ir/shared/src/main/scala/zio/morphir/ir/ir.scala +++ /dev/null @@ -1,412 +0,0 @@ -package zio.morphir.ir - -import zio.morphir.ir.recursive.* -import zio.prelude._ -import zio.prelude.fx._ -import scala.collection.immutable._ -import zio._ - -sealed trait TypeTree[+Annotations] extends IR[Annotations] { self => - override def caseValue: TypeTreeCase[TypeTree[Annotations]] -} - -object TypeTree { - import TypeTreeCase.* - import DefinitionCase.* - import SpecificationCase.* - final case class Constructors[+Annotations]( - args: Map[Name, Type[Annotations]], - annotations: ZEnvironment[Annotations] - ) extends TypeTree[Annotations] { - override lazy val caseValue: TypeTreeCase[TypeTree[Annotations]] = ConstructorsCase(args) - } - - sealed trait Definition[+Annotations] extends TypeTree[Annotations] { self => - def typeParams: List[Name] - override def caseValue: DefinitionCase[TypeTree[Annotations]] - } - - object Definition { - object WithTypeParams { - def unapply[Annotations](ir: IR[Annotations]): Option[(List[Name], DefinitionCase[TypeTree[Annotations]])] = - ir match { - case ir: Definition[Annotations] => Some((ir.typeParams, ir.caseValue)) - case _ => None - } - } - final case class CustomTypeDefinition[+Annotations]( - typeParams: List[Name], - ctors: AccessControlled[Constructors[Annotations]], - annotations: ZEnvironment[Annotations] - ) extends Definition[Annotations] { - override lazy val caseValue: DefinitionCase[TypeTree[Annotations]] = CustomTypeDefinitionCase(typeParams, ctors) - } - - final case class TypeAliasDefinition[+Annotations]( - typeParams: List[Name], - typeExpr: Type[Annotations], - annotations: ZEnvironment[Annotations] - ) extends Definition[Annotations] { - override lazy val caseValue: DefinitionCase[TypeTree[Annotations]] = TypeAliasDefinitionCase(typeParams, typeExpr) - } - } - - sealed trait Specification[+Annotations] extends TypeTree[Annotations] { self => - def typeParams: List[Name] - override def caseValue: SpecificationCase[TypeTree[Annotations]] - } - object Specification { - def unapply[Annotations](t: Specification[Annotations]): Option[(SpecificationCase[TypeTree[Annotations]])] = - Some(t.caseValue) - - object WithTypeParams { - def unapply[Annotations](ir: IR[Annotations]): Option[(List[Name], SpecificationCase[TypeTree[Annotations]])] = - ir match { - case s: Specification[Annotations] => Some((s.typeParams, s.caseValue)) - case _ => None - } - } - - final case class CustomTypeSpecification[+Annotations]( - typeParams: List[Name], - ctors: Constructors[Annotations], - annotations: ZEnvironment[Annotations] - ) extends Specification[Annotations] { - override lazy val caseValue: SpecificationCase[TypeTree[Annotations]] = - CustomTypeSpecificationCase(typeParams, ctors) - } - final case class OpaqueTypeSpecification[+Annotations]( - typeParams: List[Name], - annotations: ZEnvironment[Annotations] - ) extends Specification[Annotations] { - override lazy val caseValue: SpecificationCase[Type[Annotations]] = OpaqueTypeSpecificationCase(typeParams) - } - - final case class TypeAliasSpecification[+Annotations]( - typeParams: List[Name], - typeExpr: Type[Annotations], - annotations: ZEnvironment[Annotations] - ) extends Specification[Annotations] { - override lazy val caseValue: SpecificationCase[Type[Annotations]] = - TypeAliasSpecificationCase(typeParams, typeExpr) - } - } -} - -sealed trait Type[+Annotations] extends TypeTree[Annotations] { self => - // import TypeCase.* - - final def asType: Type[Annotations] = self - - override def caseValue: TypeCase[Type[Annotations]] -} - -object Type { - import TypeCase.* - - def ref(name: naming.FQName): Reference[Any] = Reference(name, Chunk.empty, ZEnvironment.empty) - - /** - * Creates a type variable with the given `name`. - */ - def variable(name: Name): Variable[Any] = Variable(name, ZEnvironment.empty) - def variable(name: String): Variable[Any] = variable(Name(name)) - val unit: Type[Any] = UnitType(ZEnvironment.empty) - - final case class UnitType[+Annotations](annotations: ZEnvironment[Annotations]) extends Type[Annotations] { - override val caseValue: TypeCase[Type[Annotations]] = UnitCase - } - - final case class Field[+Annotations](name: Name, fieldType: Type[Annotations], annotations: ZEnvironment[Annotations]) - extends Type[Annotations] { - override lazy val caseValue: FieldCase[Type[Annotations]] = FieldCase(name, fieldType) - } - object Field { - - object Case { - def unapply[Annotations](field: Field[Annotations]): Option[FieldCase[Type[Annotations]]] = - Some(field.caseValue) - } - } - - final case class Reference[+Annotations]( - name: FQName, - typeParams: Chunk[Type[Annotations]], - annotations: ZEnvironment[Annotations] - ) extends Type[Annotations] { - override lazy val caseValue: ReferenceCase[Type[Annotations]] = ReferenceCase(name, typeParams) - } - - object Reference { - object Case { - def unapply[Annotations](reference: Reference[Annotations]): Option[ReferenceCase[Type[Annotations]]] = - Some(reference.caseValue) - } - } - - final case class Variable[+Annotations](name: Name, annotations: ZEnvironment[Annotations]) - extends Type[Annotations] { - override lazy val caseValue: VariableCase = VariableCase(name) - } - object Variable { - object Case { - def unapply[Annotations](variable: Variable[Annotations]): Option[VariableCase] = - Some(variable.caseValue) - } - } -} - -sealed trait ValueTree[+Annotations] extends IR[Annotations] { self => - override def caseValue: ValueTreeCase[IR[Annotations]] -} - -object ValueTree { - import ValueTreeCase.* - - final case class Definition[+Annotations]( - inputTypes: Chunk[(Name, Type[Annotations])], - outputType: Type[Annotations], - body: Value[Annotations], - annotations: ZEnvironment[Annotations] - ) extends ValueTree[Annotations] { - override def caseValue: ValueTreeCase[IR[Annotations]] = DefinitionCase(inputTypes, outputType, body) - } - - final case class Specification[+Annotations]( - inputs: Chunk[(Name, Type[Annotations])], - output: Type[Annotations], - annotations: ZEnvironment[Annotations] - ) extends ValueTree[Annotations] { - override val caseValue: ValueTreeCase[IR[Annotations]] = SpecificationCase(inputs, output) - } -} - -sealed trait Value[+Annotations] extends ValueTree[Annotations] { self => - - def caseValue: ValueCase[Value[Annotations]] -} - -object Value { - import ValueCase.* - final case class Variable[+Annotations](name: Name, annotations: ZEnvironment[Annotations]) - extends Value[Annotations] { - override lazy val caseValue: VariableCase = VariableCase(name) - } -} - -sealed trait Distribution[+Annotations] extends IR[Annotations] { self => - def caseValue: DistributionCase[IR[Annotations]] -} - -object Distribution { - import DistributionCase.* - final case class Library[+Annotations]( - packageName: PackageName, - packageSpecs: Map[PackageName, PackageSpecification[Annotations]], - packageDef: PackageDefinition[Annotations], - annotations: ZEnvironment[Annotations] - ) extends Distribution[Annotations] { - override def caseValue: LibraryCase[IR[Annotations]] = LibraryCase(packageName, packageSpecs, packageDef) - } -} - -final case class PackageSpecification[+Annotations]( - modules: scala.collection.immutable.Map[ModuleName, ModuleSpecification[Annotations]], - annotations: ZEnvironment[Annotations] -) extends IR[Annotations] { - override def caseValue: PackageSpecificationCase[ModuleSpecification[Annotations]] = PackageSpecificationCase(modules) -} - -final case class PackageDefinition[+Annotations]( - modules: Map[ModuleName, AccessControlled[ModuleDefinition[Annotations]]], - annotations: ZEnvironment[Annotations] -) extends IR[Annotations] { - override def caseValue: PackageDefinitionCase[ModuleDefinition[Annotations]] = PackageDefinitionCase(modules) -} - -final case class ModuleDefinition[+Annotations]( - types: Map[Name, AccessControlled[Documented[TypeTree.Definition[Annotations]]]], - values: Map[Name, AccessControlled[ValueTree.Definition[Annotations]]], - annotations: ZEnvironment[Annotations] -) extends IR[Annotations] { - override def caseValue: ModuleDefinitionCase[IR[Annotations]] = ModuleDefinitionCase(types, values) -} - -final case class ModuleSpecification[+Annotations]( - types: Map[Name, Documented[TypeTree.Specification[Annotations]]], - values: Map[Name, ValueTree.Specification[Annotations]], - annotations: ZEnvironment[Annotations] -) extends IR[Annotations] { - override def caseValue: ModuleSpecificationCase[IR[Annotations]] = ModuleSpecificationCase(types, values) -} - -sealed trait Literal[+A] { - def value: A -} -object Literal { - final case class Bool(value: scala.Boolean) extends Literal[scala.Boolean] - final case class Char(value: scala.Char) extends Literal[scala.Char] - final case class String(value: java.lang.String) extends Literal[java.lang.String] - final case class WholeNumber(value: java.math.BigInteger) extends Literal[java.math.BigInteger] - // TODO: Consider using BigDecimal as the representation of Float in Literal - final case class Float(value: java.math.BigDecimal) extends Literal[java.math.BigDecimal] -} - -sealed trait IR[+Annotations] { self => - import IRCase.* - - def caseValue: IRCase[IR[Annotations]] - - def annotations: ZEnvironment[Annotations] - - def fold[Z](f: IRCase[Z] => Z): Z = - self.caseValue match { - case c @ DistributionCase.LibraryCase(_, _, _) => - f( - DistributionCase.LibraryCase( - c.packageName, - c.packageSpecs.map { case (name, spec) => (name, spec.fold(f)) }, - c.packageDef.fold(f) - ) - ) - case c @ ModuleDefinitionCase(_, _) => - f( - ModuleDefinitionCase( - c.types.map { case (name, value) => (name, value.map(d => d.map(_.fold(f)))) }, - c.values.map { case (name, value) => (name, value.map(_.fold(f))) } - ) - ) - case c @ ModuleSpecificationCase(_, _) => - f( - ModuleSpecificationCase( - c.types.map { case (name, value) => (name, value.map(_.fold(f))) }, - c.values.map { case (name, value) => (name, value.fold(f)) } - ) - ) - case c @ PackageDefinitionCase(_) => - f(PackageDefinitionCase(c.modules.map { case (name, value) => (name, value.map(_.fold(f))) })) - case c @ PackageSpecificationCase(_) => - f(PackageSpecificationCase(c.modules.map { case (name, spec) => (name, spec.fold(f)) })) - case c @ PatternCase.AsCase(_, _) => f(PatternCase.AsCase(c.pattern.fold(f), c.name)) - case c @ PatternCase.ConstructorCase(_, _) => - f(PatternCase.ConstructorCase(c.constructorName, c.argumentPatterns.map(_.fold(f)))) - case _ @PatternCase.EmptyListCase => f(PatternCase.EmptyListCase) - case c @ PatternCase.HeadTailCase(_, _) => f(PatternCase.HeadTailCase(c.head.fold(f), c.tail.fold(f))) - case c @ PatternCase.LiteralCase(_) => f(PatternCase.LiteralCase(c.value)) - case c @ PatternCase.TupleCase(_) => f(PatternCase.TupleCase(c.elements.map(_.fold(f)))) - case _ @PatternCase.UnitCase => f(PatternCase.UnitCase) - case _ @PatternCase.WildcardCase => f(PatternCase.WildcardCase) - case c @ ValueCase.ApplyCase(_, _) => f(ValueCase.ApplyCase(c.function.fold(f), c.arguments.map(_.fold(f)))) - case c @ ValueCase.ConstructorCase(_) => f(ValueCase.ConstructorCase(c.name)) - case c @ ValueCase.DestructureCase(_, _, _) => - f(ValueCase.DestructureCase(c.pattern.fold(f), c.valueToDestruct.fold(f), c.inValue.fold(f))) - case c @ ValueCase.FieldCase(_, _) => f(ValueCase.FieldCase(c.target.fold(f), c.name)) - case c @ ValueCase.FieldFunctionCase(_) => f(c) - case c @ ValueCase.IfThenElseCase(_, _, _) => - f(ValueCase.IfThenElseCase(c.condition.fold(f), c.thenBranch.fold(f), c.elseBranch.fold(f))) - case c @ ValueCase.LambdaCase(_, _) => f(ValueCase.LambdaCase(c.argumentPattern.fold(f), c.body.fold(f))) - case c @ ValueCase.LetDefinitionCase(_, _, _) => - f(ValueCase.LetDefinitionCase(c.valueName, c.valueDefinition.fold(f), c.inValue.fold(f))) - case c @ ValueCase.LetRecursionCase(_, _) => - f( - ValueCase.LetRecursionCase( - c.valueDefinitions.map { case (name, value) => (name, value.fold(f)) }, - c.inValue.fold(f) - ) - ) - case c @ ValueCase.ListCase(_) => f(ValueCase.ListCase(c.elements.map(_.fold(f)))) - case c @ ValueCase.LiteralCase(_) => f(c) - case c @ ValueCase.PatternMatchCase(_, _) => - f( - ValueCase.PatternMatchCase( - c.branchOutOn.fold(f), - c.cases.map { case (pattern, value) => - (pattern.fold(f), value.fold(f)) - } - ) - ) - case c @ ValueCase.RecordCase(_) => f(ValueCase.RecordCase(c.fields.map { case (k, v) => (k, v.fold(f)) })) - case c @ ValueCase.ReferenceCase(_) => f(c) - case c @ ValueCase.TupleCase(_) => f(ValueCase.TupleCase(c.elements.map(_.fold(f)))) - case _ @ValueCase.UnitCase => f(ValueCase.UnitCase) - case c @ ValueCase.UpdateRecordCase(_, _) => - f( - ValueCase.UpdateRecordCase( - c.valueToUpdate.fold(f), - c.fieldsToUpdate.map { case (name, value) => (name, value.fold(f)) } - ) - ) - case c @ ValueCase.VariableCase(_) => f(c) - case c @ ValueTreeCase.DefinitionCase(_, _, _) => - f( - ValueTreeCase.DefinitionCase( - c.inputTypes.map { case (name, self) => (name, self.fold(f)) }, - c.outputType.fold(f), - c.body.fold(f) - ) - ) - case c @ ValueTreeCase.SpecificationCase(_, _) => - f( - ValueTreeCase.SpecificationCase( - c.inputs.map { case (name, value) => (name, value.fold(f)) }, - c.output.fold(f) - ) - ) - case c @ TypeCase.ExtensibleRecordCase(_, _) => f(TypeCase.ExtensibleRecordCase(c.name, c.fields.map(_.fold(f)))) - case c @ TypeCase.FieldCase(_, _) => f(TypeCase.FieldCase(c.name, c.fieldType.fold(f))) - case c @ TypeCase.FunctionCase(_, _) => - f(TypeCase.FunctionCase(c.paramTypes.map(_.fold(f)), c.returnType.fold(f))) - case c @ TypeCase.RecordCase(_) => f(TypeCase.RecordCase(c.fields.map(_.fold(f)))) - case c @ TypeCase.ReferenceCase(_, _) => f(TypeCase.ReferenceCase(c.typeName, c.typeParams.map(_.fold(f)))) - case c @ TypeCase.TupleCase(_) => f(TypeCase.TupleCase(c.elementTypes.map(_.fold(f)))) - case _ @TypeCase.UnitCase => f(TypeCase.UnitCase) - case c @ TypeCase.VariableCase(_) => f(c) - case c @ TypeTreeCase.ConstructorsCase(_) => - f(TypeTreeCase.ConstructorsCase(c.args.map { case (name, tree) => (name, tree.fold(f)) })) - case c @ TypeTreeCase.DefinitionCase.CustomTypeDefinitionCase(_, _) => - f(TypeTreeCase.DefinitionCase.CustomTypeDefinitionCase(c.typeParams, c.ctors.map(_.fold(f)))) - case c @ TypeTreeCase.DefinitionCase.TypeAliasDefinitionCase(_, _) => - f(TypeTreeCase.DefinitionCase.TypeAliasDefinitionCase(c.typeParams, c.typeExpr.fold(f))) - case c @ TypeTreeCase.SpecificationCase.CustomTypeSpecificationCase(_, _) => - f(TypeTreeCase.SpecificationCase.CustomTypeSpecificationCase(c.typeParams, c.ctors.fold(f))) - case c @ TypeTreeCase.SpecificationCase.OpaqueTypeSpecificationCase(_) => f(c) - case c @ TypeTreeCase.SpecificationCase.TypeAliasSpecificationCase(_, _) => - f(TypeTreeCase.SpecificationCase.TypeAliasSpecificationCase(c.typeParams, c.typeExpr.fold(f))) - } - - def foldDown[Z](z: Z)(f: (Z, IR[Annotations]) => Z): Z = - caseValue.foldLeft(f(z, self))((z, recursive) => recursive.foldDown(z)(f)) - - def foldDownSome[Z](z: Z)(pf: PartialFunction[(Z, IR[Annotations]), Z]): Z = - foldDown(z)((z, recursive) => pf.lift(z -> recursive).getOrElse(z)) - - def foldM[F[+_]: AssociativeFlatten: Covariant: IdentityBoth, Z](f: IRCase[Z] => F[Z]): F[Z] = - fold[F[Z]](_.flip.flatMap(f)) - - def foldPure[W, S, R, E, Z](f: IRCase[Z] => ZPure[W, S, S, R, E, Z]): ZPure[W, S, S, R, E, Z] = - foldM(f) - - // TODO: Uncomment once appropriate instances are provided by ZIO Prelude - - // def foldManaged[R, E, Z](f: IRCase[Z] => ZManaged[R, E, Z]): ZManaged[R, E, Z] = - // foldM(f) - - // def foldSTM[R, E, Z](f: IRCase[Z] => ZSTM[R, E, Z]): ZSTM[R, E, Z] = - // foldM(f) - - // def foldValidation[W, E, Z](f: IRCase[Z] => ZValidation[W, E, Z]): ZValidation[W, E, Z] = - // foldM(f) - - def foldZIO[R, E, Z](f: IRCase[Z] => ZIO[R, E, Z]): ZIO[R, E, Z] = - foldM(f) - - def foldRecursive[Z](f: IRCase[(IR[Annotations], Z)] => Z): Z = - f(caseValue.map(recursive => recursive -> recursive.foldRecursive(f))) - - def foldUp[Z](z: Z)(f: (Z, IR[Annotations]) => Z): Z = - f(caseValue.foldLeft(z)((z, recursive) => recursive.foldUp(z)(f)), self) - - def foldUpSome[Z](z: Z)(pf: PartialFunction[(Z, IR[Annotations]), Z]): Z = - foldUp(z)((z, recursive) => pf.lift(z -> recursive).getOrElse(z)) -} -object IR {} diff --git a/morphir-ir/shared/src/main/scala/zio/morphir/ir/naming.scala b/morphir-ir/shared/src/main/scala/zio/morphir/ir/naming.scala deleted file mode 100644 index 6d7bba44..00000000 --- a/morphir-ir/shared/src/main/scala/zio/morphir/ir/naming.scala +++ /dev/null @@ -1,126 +0,0 @@ -package zio.morphir.ir - -import scala.annotation.tailrec -import zio.Chunk - -object naming { - final case class Name private (toList: List[String]) extends AnyVal { self => - def :+(that: String): Name = Name(self.toList :+ that) - def +:(that: String): Name = Name(that +: self.toList) - def ++(that: Name): Name = Name(self.toList ++ that.toList) - def /(that: Name): Path = Path(Chunk(self, that)) - - def humanize: List[String] = { - val words = toList - val join: List[String] => String = abbrev => abbrev.map(_.toUpperCase()).mkString("") - - @tailrec - def loop( - prefix: List[String], - abbrev: List[String], - suffix: List[String] - ): List[String] = - suffix match { - case Nil => - abbrev match { - case Nil => prefix - case _ => prefix ++ List(join(abbrev)) - } - case first :: rest => - if (first.length() == 1) - loop(prefix, abbrev ++ List(first), rest) - else - abbrev match { - case Nil => loop(prefix ++ List(first), List.empty, rest) - case _ => - loop(prefix ++ List(join(abbrev), first), List.empty, rest) - } - } - - loop(List.empty, List.empty, words.toList) - } - - /** - * Maps segments of the `Name`. - */ - def mapParts(f: String => String): Name = Name(self.toList.map(f)) - - def mkString(f: String => String)(sep: String): String = - toList.map(f).mkString(sep) - - def toLowerCase: String = - mkString(part => part.toLowerCase)("") - - def toCamelCase: String = - toList match { - case Nil => "" - case head :: tail => - (head :: tail.map(_.capitalize)).mkString("") - } - - def toKebabCase: String = - humanize.mkString("-") - - def toSnakeCase: String = - humanize.mkString("_") - - def toTitleCase: String = - toList - .map(_.capitalize) - .mkString("") - - } - object Name { - - private def wrap(value: List[String]): Name = Name(value) - - def apply(first: String, rest: String*): Name = - fromIterable(first +: rest) - - @inline def fromList(list: List[String]): Name = wrap(list) - def fromIterable(iterable: Iterable[String]): Name = - wrap(iterable.toList) - - /** - * Creates a new name from a chunk of strings without checking. - */ - private[ir] def unsafeMake(value: List[String]): Name = Name(value) - - def toList(name: Name): List[String] = name.toList - - @inline def toTitleCase(name: Name): String = name.toTitleCase - - @inline def toCamelCase(name: Name): String = name.toCamelCase - - @inline def toSnakeCase(name: Name): String = name.toSnakeCase - - @inline def toKebabCase(name: Name): String = name.toKebabCase - - @inline def toHumanWords(name: Name): List[String] = name.humanize - - } - - final case class Path(segments: Chunk[Name]) extends AnyVal { self => - def /(name: Name): Path = Path(segments ++ Chunk(name)) - def /(name: Path): Path = Path(segments ++ name.segments) - def %(other: Path): PackageAndModulePath = - PackageAndModulePath(PackageName(self), ModulePath(other)) - def %(name: Name): ModuleName = ModuleName(self, name) - def zip(other: Path): (Path, Path) = (self, other) - } - - final case class PackageName(toPath: Path) { self => - def %(modulePath: ModulePath): PackageAndModulePath = PackageAndModulePath(self, modulePath) - def %(moduleName: ModuleName): FQName = FQName(self, ModulePath(moduleName.namespace), moduleName.localName) - } - final case class ModulePath(toPath: Path) - - final case class ModuleName(namespace: Path, localName: Name) { - lazy val toPath = namespace / localName - } - - final case class PackageAndModulePath(packageName: PackageName, modulePath: ModulePath) { self => - def %(name: Name): FQName = FQName(packageName, modulePath, name) - } - final case class FQName(packagePath: PackageName, modulePath: ModulePath, localName: Name) -} diff --git a/morphir-ir/shared/src/main/scala/zio/morphir/ir/package.scala b/morphir-ir/shared/src/main/scala/zio/morphir/ir/package.scala index 92fa2f39..fc8e1f14 100644 --- a/morphir-ir/shared/src/main/scala/zio/morphir/ir/package.scala +++ b/morphir-ir/shared/src/main/scala/zio/morphir/ir/package.scala @@ -1,26 +1,20 @@ package zio.morphir package object ir { - type FQName = naming.FQName - val FQName = naming.FQName type LiteralValue = Literal[Nothing] val LiteralValue = Literal - type ModuleName = naming.ModuleName - val ModuleName = naming.ModuleName + type ModuleName = Module.ModuleName + val ModuleName = Module.ModuleName - type ModulePath = naming.ModulePath - val ModulePath = naming.ModulePath + type ModulePath = Module.ModulePath + val ModulePath = Module.ModulePath - type Name = naming.Name - val Name = naming.Name + type NativeFunction = value.Native.Function - type PackageName = naming.PackageName - val PackageName = naming.PackageName - - type Path = naming.Path - val Path = naming.Path + type PackageName = PackageModule.PackageName + val PackageName = PackageModule.PackageName type ??? = Nothing } diff --git a/morphir-ir/shared/src/main/scala/zio/morphir/ir/value/Native.scala b/morphir-ir/shared/src/main/scala/zio/morphir/ir/value/Native.scala new file mode 100644 index 00000000..567b9e8d --- /dev/null +++ b/morphir-ir/shared/src/main/scala/zio/morphir/ir/value/Native.scala @@ -0,0 +1,5 @@ +package zio.morphir.ir.value + +object Native { + type Function +} diff --git a/morphir-ir/shared/src/test/scala/zio/morphir/ir/AccessControlledSpec.scala b/morphir-ir/shared/src/test/scala/zio/morphir/ir/AccessControlledSpec.scala index 7fda856c..8b008388 100644 --- a/morphir-ir/shared/src/test/scala/zio/morphir/ir/AccessControlledSpec.scala +++ b/morphir-ir/shared/src/test/scala/zio/morphir/ir/AccessControlledSpec.scala @@ -1,7 +1,9 @@ package zio.morphir.ir + +import testing.MorphirBaseSpec import zio.test.* -object AccessControlledSpec extends DefaultRunnableSpec { +object AccessControlledSpec extends MorphirBaseSpec { def spec = suite("AccessControlled")( // test("Supports for comprehensions") { // val code = diff --git a/morphir-ir/shared/src/test/scala/zio/morphir/ir/Gens.scala b/morphir-ir/shared/src/test/scala/zio/morphir/ir/Gens.scala deleted file mode 100644 index 7cf3d056..00000000 --- a/morphir-ir/shared/src/test/scala/zio/morphir/ir/Gens.scala +++ /dev/null @@ -1,33 +0,0 @@ -package zio.morphir.ir -import zio.test.* - -object Gens { - val greeWords = Gen.oneOf( - Seq( - "alpha", - "beta", - "gamma", - "delta", - "epsilon", - "zeta", - "eta", - "theta", - "iota", - "kappa", - "lambda", - "mu", - "nu", - "xi", - "omicron", - "pi", - "rho", - "sigma", - "tau", - "upsilon", - "phi", - "chi", - "psi", - "omega" - ).map(Gen.const(_)): _* - ) -} diff --git a/morphir-ir/shared/src/test/scala/zio/morphir/ir/IRSpec.scala b/morphir-ir/shared/src/test/scala/zio/morphir/ir/IRSpec.scala deleted file mode 100644 index 59ac2f33..00000000 --- a/morphir-ir/shared/src/test/scala/zio/morphir/ir/IRSpec.scala +++ /dev/null @@ -1,18 +0,0 @@ -package zio.morphir.ir - -import zio.test.* - -object IRSpec extends DefaultRunnableSpec { - def spec = suite("IR")(typeSuite) - - val typeSuite = suite("Type")( - suite("Reference")() + - suite("Variable")( - test("It should work as expected") { - val actual = Type.variable("x") - assertTrue(actual.name == Name("x")) - } - ) - ) - -} diff --git a/morphir-ir/shared/src/test/scala/zio/morphir/ir/ModuleSpec.scala b/morphir-ir/shared/src/test/scala/zio/morphir/ir/ModuleSpec.scala new file mode 100644 index 00000000..8b9f82ac --- /dev/null +++ b/morphir-ir/shared/src/test/scala/zio/morphir/ir/ModuleSpec.scala @@ -0,0 +1,27 @@ +package zio.morphir.ir + +import zio.test.* +import zio.morphir.ir.testing.MorphirBaseSpec + +object ModuleSpec extends MorphirBaseSpec { + def spec = suite("Module Spec")( + suite("Definition")( + test("It can be empty") { + assertTrue( + Module.emptyDefinition == Module.Definition.empty, + Module.emptyDefinition.types.isEmpty, + Module.emptyDefinition.values.isEmpty + ) + } + ), + suite("Specification")( + test("It can be empty") { + assertTrue( + Module.emptySpecification == Module.Specification.empty, + Module.emptySpecification.types.isEmpty, + Module.emptySpecification.values.isEmpty + ) + } + ) + ) +} diff --git a/morphir-ir/shared/src/test/scala/zio/morphir/ir/NameSpec.scala b/morphir-ir/shared/src/test/scala/zio/morphir/ir/NameSpec.scala new file mode 100644 index 00000000..0897803f --- /dev/null +++ b/morphir-ir/shared/src/test/scala/zio/morphir/ir/NameSpec.scala @@ -0,0 +1,94 @@ +package zio.morphir.ir + +import zio.morphir.ir.testing.MorphirBaseSpec +import zio.test.* +object NameSpec extends MorphirBaseSpec { + def spec = suite("Name")( + suite("Create a Name from a string and check that:")( + suite("Name should be creatable from a single word that:")( + test("Starts with a capital letter") { + assertTrue(Name.fromString("Marco") == Name("marco")) + }, + test("Is all lowercase") { + assertTrue(Name.fromString("polo") == Name("polo")) + } + ), + suite("Name should be creatable from compound words that:")( + test("Are formed from a snake case word") { + assertTrue(Name.fromString("super_mario_world") == Name("super", "mario", "world")) + }, + test("Contain many kinds of word delimiters") { + assertTrue(Name.fromString("fooBar_baz 123") == Name("foo", "bar", "baz", "123")) + }, + test("Are formed from a camel-cased string") { + assertTrue(Name.fromString("valueInUSD") == Name("value", "in", "u", "s", "d")) + }, + test("Are formed from a title-cased string") { + assertTrue( + Name.fromString("ValueInUSD") == Name("value", "in", "u", "s", "d"), + Name.fromString("ValueInJPY") == Name("value", "in", "j", "p", "y") + ) + }, + test("Have a number in the middle") { + + assertTrue(Name.fromString("Nintendo64VideoGameSystem") == Name("nintendo", "64", "video", "game", "system")) + }, + test("Are complete and utter nonsense") { + assertTrue(Name.fromString("_-%") == Name.empty) + } + ) + ), + suite("Name should be convertible to a title-case string:")( + test("When the name was originally constructed from a snake-case string") { + val sut = Name.fromString("snake_case_input") + assertTrue(Name.toTitleCase(sut) == "SnakeCaseInput") + }, + test( + "When the name was originally constructed from a camel-case string" + ) { + val sut = Name.fromString("camelCaseInput") + assertTrue(Name.toTitleCase(sut) == "CamelCaseInput") + } + ), + suite("Name should be convertible to a camel-case string:")( + test( + "When the name was originally constructed from a snake-case string" + ) { + val sut = Name.fromString("snake_case_input") + assertTrue(Name.toCamelCase(sut) == "snakeCaseInput") + }, + test( + "When the name was originally constructed from a camel-case string" + ) { + val sut = Name.fromString("camelCaseInput") + assertTrue(Name.toCamelCase(sut) == "camelCaseInput") + } + ), + suite("Name should be convertible to snake-case")( + test("When given a name constructed from a list of words") { + val input = Name.fromList(List("foo", "bar", "baz", "123")) + assertTrue(Name.toSnakeCase(input) == "foo_bar_baz_123") + }, + test("When the name has parts of an abbreviation") { + val name = Name.fromList(List("value", "in", "u", "s", "d")) + assertTrue(Name.toSnakeCase(name) == "value_in_USD") + } + ), + suite("Name should be convertible to kebab-case")( + test("When given a name constructed from a list of words") { + val input = Name.fromList(List("foo", "bar", "baz", "123")) + assertTrue(Name.toKebabCase(input) == "foo-bar-baz-123") + }, + test("When the name has parts of an abbreviation") { + val name = Name.fromList(List("value", "in", "u", "s", "d")) + assertTrue(Name.toKebabCase(name) == "value-in-USD") + } + ), + suite("Name toHumanWords should provide a list of words from a Name")( + test("When the name is from a camelCase string") { + val sut = Name.fromString("ValueInUSD") + assertTrue(Name.toHumanWords(sut) == List("value", "in", "USD")) + } + ) + ) +} diff --git a/morphir-ir/shared/src/test/scala/zio/morphir/ir/NamingSpec.scala b/morphir-ir/shared/src/test/scala/zio/morphir/ir/PathSpec.scala similarity index 56% rename from morphir-ir/shared/src/test/scala/zio/morphir/ir/NamingSpec.scala rename to morphir-ir/shared/src/test/scala/zio/morphir/ir/PathSpec.scala index 98693876..d0701831 100644 --- a/morphir-ir/shared/src/test/scala/zio/morphir/ir/NamingSpec.scala +++ b/morphir-ir/shared/src/test/scala/zio/morphir/ir/PathSpec.scala @@ -2,19 +2,10 @@ package zio.morphir.ir import zio.Chunk import zio.test.* +import testing.MorphirBaseSpec -object NamingSpec extends MorphirBaseSpec { - def spec = suite("Naming Spec")( - fqNameSuite, - moduleNameSuite, - pathSuite - ) - - val fqNameSuite = suite("FQName")() - - val moduleNameSuite = suite("ModuleName")() - - val pathSuite = suite("Path")( +object PathSpec extends MorphirBaseSpec { + def spec = suite("Path")( test("It can be constructed from names")( assertTrue( Name("Org") / Name("Finos") == Path(Chunk(Name("Org"), Name("Finos"))), diff --git a/morphir-ir/shared/src/test/scala/zio/morphir/ir/TypeSpec.scala b/morphir-ir/shared/src/test/scala/zio/morphir/ir/TypeSpec.scala new file mode 100644 index 00000000..9086e90e --- /dev/null +++ b/morphir-ir/shared/src/test/scala/zio/morphir/ir/TypeSpec.scala @@ -0,0 +1,16 @@ +package zio.morphir.ir + +import testing.MorphirBaseSpec +import zio.test.* + +object TypeSpec extends MorphirBaseSpec { + def spec = suite("Type")( + suite("Reference")(), + suite("Variable")( + test("It should work as expected") { + val actual = Type.variable("x") + assertTrue(actual.name == Name("x")) + } + ) + ) +} diff --git a/morphir-ir/shared/src/test/scala/zio/morphir/ir/testing/Gens.scala b/morphir-ir/shared/src/test/scala/zio/morphir/ir/testing/Gens.scala new file mode 100644 index 00000000..91c8052f --- /dev/null +++ b/morphir-ir/shared/src/test/scala/zio/morphir/ir/testing/Gens.scala @@ -0,0 +1,105 @@ +package zio.morphir.ir.testing +import zio.test.* + +object Gens { + val greekLetterNames = Gen.weighted( + Seq( + "alpha", + "beta", + "gamma", + "delta", + "epsilon", + "zeta", + "eta", + "theta", + "iota", + "kappa", + "lambda", + "mu", + "nu", + "xi", + "omicron", + "pi", + "rho", + "sigma", + "tau", + "upsilon", + "phi", + "chi", + "psi", + "omega" + ).map(Gen.const(_) -> 12.0): _* + ) + + val monthNames = Gen.weighted( + Seq( + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December" + ).map(Gen.const(_) -> 12.0): _* + ) + + val words = { + val word = { + val choices = + Seq( + "area", + "benchmark", + "book", + "business", + "company", + "country", + "currency", + "day", + "description", + "entity", + "fact", + "family", + "from", + "government", + "group", + "home", + "id", + "job", + "left", + "lot", + "market", + "minute", + "money", + "month", + "name", + "number", + "owner", + "parent", + "part", + "problem", + "rate", + "right", + "state", + "source", + "system", + "time", + "title", + "to", + "valid", + "week", + "work", + "world", + "year" + ).map(Gen.const(_) -> 12.0) + Gen.weighted(choices: _*) + } + + word ++ greekLetterNames ++ monthNames + } + +} diff --git a/morphir-ir/shared/src/test/scala/zio/morphir/ir/MorphirSpec.scala b/morphir-ir/shared/src/test/scala/zio/morphir/ir/testing/MorphirBaseSpec.scala similarity index 86% rename from morphir-ir/shared/src/test/scala/zio/morphir/ir/MorphirSpec.scala rename to morphir-ir/shared/src/test/scala/zio/morphir/ir/testing/MorphirBaseSpec.scala index 04d81efe..52c50834 100644 --- a/morphir-ir/shared/src/test/scala/zio/morphir/ir/MorphirSpec.scala +++ b/morphir-ir/shared/src/test/scala/zio/morphir/ir/testing/MorphirBaseSpec.scala @@ -1,4 +1,4 @@ -package zio.morphir.ir +package zio.morphir.ir.testing import zio.test.DefaultRunnableSpec import zio.test.TestAspect