Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding More IR Files #301

Merged
merged 3 commits into from
Oct 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions morphir/src/org/finos/morphir/ir/gen1/Names.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.finos.morphir.ir.gen1

trait Names
extends FQNameModule
with ModuleNameModule
with NameModule
with NamespaceModule
with NodeIDModule
with PathModule
with PackageNameModule
with QualifiedModuleNameModule
with QNameModule
with NamingOptionsModule
11 changes: 11 additions & 0 deletions morphir/src/org/finos/morphir/ir/gen1/NamingOptionsModule.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.finos.morphir.ir.gen1

trait NamingOptionsModule { self: PackageNameModule with ModuleNameModule =>

sealed case class FQNamingOptions(defaultPackage: PackageName, defaultModule: ModuleName, defaultSeparator: String)

object FQNamingOptions {
implicit val default: FQNamingOptions =
FQNamingOptions(PackageName.empty, ModuleName.empty, ":")
}
}
106 changes: 106 additions & 0 deletions morphir/src/org/finos/morphir/ir/gen1/fqname.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package org.finos.morphir.ir.gen1

trait FQNameModule {
self: NameModule with ModuleNameModule with NamespaceModule with PackageNameModule with PathModule
with QualifiedModuleNameModule
with QNameModule
with NamingOptionsModule =>

sealed case class FQName(packagePath: PackageName, modulePath: ModuleName, localName: Name) { self =>
def getPackagePath: Path = packagePath.toPath

def getModulePath: Path = modulePath.toPath
def getModuleName: ModuleName = modulePath

/// An alias for `packagePath`
def pack: PackageName = packagePath

def toReferenceName: String = Seq(
Path.toString(Name.toTitleCase, ".", packagePath.toPath),
Path.toString(Name.toTitleCase, ".", modulePath.toPath),
localName.toTitleCase
).mkString(".")

override def toString: String = Array(
Path.toString(Name.toTitleCase, ".", packagePath.toPath),
Path.toString(Name.toTitleCase, ".", modulePath.toPath),
Name.toCamelCase(localName)
).mkString(":")

def toStringTitleCase: String = Array(
Path.toString(Name.toTitleCase, ".", packagePath.toPath),
Path.toString(Name.toTitleCase, ".", modulePath.toPath),
Name.toTitleCase(this.localName)
).mkString(":")
}

object FQName {
val empty: FQName = FQName(PackageName.empty, ModuleName.empty, Name.empty)
// 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.fromPath(packagePath), ModuleName(modulePath), localName)

def fromQName(packagePath: Path, qName: QName): FQName =
FQName(PackageName.fromPath(packagePath), ModuleName(qName.modulePath), qName.localName)

def fromQName(qName: QName)(implicit packageName: PackageName): FQName =
FQName(packageName, ModuleName(qName.modulePath), qName.localName)

def fromQName(qName: QName)(implicit options: FQNamingOptions): FQName =
FQName(options.defaultPackage, ModuleName(QName.getModulePath(qName)), QName.getLocalName(qName))

/** Get the package path part of a fully-qualified name. */
def getPackagePath(fqName: FQName): Path = fqName.getPackagePath

/** Get the module path part of a fully-qualified name */
def getModulePath(fqName: FQName): Path = fqName.getModulePath

/** Get the local name part of a fully-qualified name */
def getLocalName(fqName: FQName): Name = fqName.localName

/** Convenience function to create a fully-qualified name from 3 strings */
def fqn(packageName: String, moduleName: String, localName: String): FQName =
FQName(PackageName.fromString(packageName), ModuleName.fromString(moduleName), Name.fromString(localName))

/** Convenience function to create a fully-qualified name from 2 strings with default package name */
def fqn(moduleName: String, localName: String)(implicit options: FQNamingOptions): FQName =
FQName(options.defaultPackage, ModuleName(Path.fromString(moduleName)), Name.fromString(localName))

/** Convenience function to create a fully-qualified name from 1 string with defaults for package and module */
def fqn(localName: String)(implicit options: FQNamingOptions): FQName =
FQName(options.defaultPackage, options.defaultModule, Name.fromString(localName))

/// Convenience function to create a fully-qualified name from a local name and an implicitly provided `QualifiedModuleName`.
def fromLocalName(localName: String)(implicit qualifiedModuleName: QualifiedModuleName): FQName =
FQName(qualifiedModuleName.packageName, qualifiedModuleName.modulePath, Name.fromString(localName))

def fromLocalName(localName: Name)(implicit qualifiedModuleName: QualifiedModuleName): FQName =
FQName(qualifiedModuleName.packageName, qualifiedModuleName.modulePath, localName)

def toString(fqName: FQName): String = fqName.toString

/** Parse a string into a FQName using splitter as the separator between package, module, and local names */
def fromString(fqNameString: String, splitter: String)(implicit options: FQNamingOptions): FQName =
fqNameString.split(splitter) match {
case Array(packageNameString, moduleNameString, localNameString) =>
fqn(packageNameString, moduleNameString, localNameString)
case Array(moduleNameString, localNameString) =>
fqn(moduleNameString, localNameString)
case Array(localNameString) =>
fqn(localNameString)
case _ => throw FQNameParsingError(fqNameString)
}

def fromString(fqNameString: String)(implicit options: FQNamingOptions): FQName =
fromString(fqNameString, options.defaultSeparator)

object ReferenceName {
def unapply(fqName: FQName): Some[String] = Some(fqName.toReferenceName)
}
}

sealed case class FQNameParsingError(invalidName: String)
extends Exception(s"Unable to parse: [$invalidName] into a valid FQName")
}
55 changes: 55 additions & 0 deletions morphir/src/org/finos/morphir/ir/gen1/naming.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package org.finos.morphir.ir.gen1

object naming extends Names {

final implicit class PackageNameSyntax(val self: PackageName) extends AnyVal {
def /(moduleName: ModuleName): QualifiedModuleName = QualifiedModuleName(self, moduleName)
}

final implicit class QualifiedModuleNameSyntax(val self: QualifiedModuleName) extends AnyVal {
def %(localName: String): FQName = FQName(self.packageName, self.modulePath, Name.fromString(localName))
def %(name: Name): FQName = FQName(self.packageName, self.modulePath, name)
}

final implicit class NamingHelper(val sc: StringContext) extends AnyVal {

def fqn(args: Any*): FQName = {
val interlaced = interlace(sc.parts, args.map(_.toString))
FQName.fromString(interlaced.mkString)
}

def mod(args: Any*): ModuleName = {
val interlaced = interlace(sc.parts, args.map(_.toString))
ModuleName.fromString(interlaced.mkString)
}

def n(args: Any*): Name = {
val interlaced = interlace(sc.parts, args.map(_.toString))
Name.fromString(interlaced.mkString)
}

def qmn(args: Any*): QualifiedModuleName = {
val interlaced = interlace(sc.parts, args.map(_.toString))
QualifiedModuleName.fromString(interlaced.mkString)
}

def name(args: Any*): Name = {
val interlaced = interlace(sc.parts, args.map(_.toString))
Name.fromString(interlaced.mkString)
}

def pkg(args: Any*): PackageName = {
val interlaced = interlace(sc.parts, args.map(_.toString))
PackageName.fromString(interlaced.mkString)
}

def path(args: Any*): Path = {
val interlaced = interlace(sc.parts, args.map(_.toString))
Path.fromString(interlaced.mkString)
}
}

private[morphir] def interlace[T](a: Iterable[T], b: Iterable[T]): List[T] =
if (a.isEmpty) b.toList
else a.head +: interlace(b, a.tail)
}
158 changes: 158 additions & 0 deletions morphir/src/org/finos/morphir/ir/gen1/nodeID.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package org.finos.morphir.ir.gen1

trait NodeIDModule {
self: FQNameModule with NamingOptionsModule with NameModule with PathModule with PackageNameModule
with ModuleNameModule
with QualifiedModuleNameModule =>
import NodePath.*

/** Represents a path in the IR.
* ==Overview==
* A NodeID can have two slightly different structures depending on if we are refering to modules or definitions
* (types/values).
*
* - When refefering to modules: `"module:<Package>:<Module>"`
* - When refering to definitions: `"type\value:<Package>:<Module><localName>#<nodePath>"`, where nodePath is
* optional
*
* Examples of valid NodeIDs:
* - "module:Morphir.Reference.Model:BooksAndRecords"
* - "type:Morphir.Reference.Model:BooksAndRecords:deal"
* - "value:Morphir.Reference.Model:BooksAndRecords:deal#1"
*
* ==Referring to modules==
* We can refer to modules by their Qualified Name, with the module: prefix
*
* For example: `"module:Morphir.Reference.Model:BooksAndRecords"` refers to the `Books and Records` module inside
* the `Morphir.Reference.Model` package.
*/
sealed trait NodeID extends Product with Serializable { self =>
import NodeID.*
override def toString(): String = {
implicit val renderer: PathRenderer = PathRenderer.TitleCase
def mapToTypeOrValue(
packageName: Path,
moduleName: Path,
localName: Name,
suffix: String,
nodePath: NodePath
): String = {
val nodeIdString = s"${packageName.render}:${moduleName.render}:${localName.toCamelCase}$suffix"
nodePath match {
case NodePath(Vector()) => nodeIdString
case _ => s"$nodeIdString$nodePath"
}
}

self match {
case ModuleID(packagePath, modulePath) =>
s"${packagePath.path.render}:${modulePath.path.render}"
case TypeID(FQName(packageName, moduleName, localName), path) =>
mapToTypeOrValue(packageName.path, moduleName.path, localName, ".type", path)
case ValueID(FQName(packageName, moduleName, localName), path) =>
mapToTypeOrValue(packageName.path, moduleName.path, localName, ".value", path)
}
}
}
type NodeIDCompanion = NodeID.type
object NodeID {

def fromQualifiedName(qualifiedModuleName: QualifiedModuleName): NodeID =
ModuleID.fromQualifiedName(qualifiedModuleName)

def fromString(input: String): Either[Error, NodeID] = {
def mapToTypeOrValue(packageName: String, moduleName: String, defNameWithSuffix: String, nodePath: String) = {
def defName(suffix: String) = defNameWithSuffix.dropRight(suffix.length())
if (defNameWithSuffix.endsWith(".value"))
Right(ValueID(FQName.fqn(packageName, moduleName, defName(".value")), NodePath.fromString(nodePath)))
else
Right(TypeID(FQName.fqn(packageName, moduleName, defName(".type")), NodePath.fromString(nodePath)))
}

input.split(":") match {
case Array(packageName, moduleName) =>
Right(ModuleID(Path(packageName), Path(moduleName)))
case Array(packageName, moduleName, localName) =>
if (localName.contains("#"))
localName.split("#") match {
case Array(defName, path) => mapToTypeOrValue(packageName, moduleName, defName, path)
case _ => Left(Error.InvalidNodeId(input))
}
else
mapToTypeOrValue(packageName, moduleName, localName, "")
case _ =>
Left(Error.InvalidNodeId(input))
}
}

sealed case class TypeID(name: FQName, memberPath: NodePath) extends NodeID
sealed case class ValueID(name: FQName, memberPath: NodePath) extends NodeID
sealed case class ModuleID(packageName: PackageName, moduleName: ModuleName) extends NodeID
object ModuleID {
def apply(packagePath: Path, modulePath: Path): ModuleID =
ModuleID(PackageName(packagePath), ModuleName(modulePath))

def fromQualifiedName(qualifiedModuleName: QualifiedModuleName): ModuleID =
ModuleID(qualifiedModuleName.packageName, qualifiedModuleName.modulePath)
}

sealed abstract class Error(errorMessage: String) extends Exception(errorMessage)
object Error {
sealed case class InvalidPath(input: String, errorMessage: String) extends Error(errorMessage)
sealed case class InvalidNodeId(input: String, errorMessage: String) extends Error(errorMessage) {
def this(input: String) = this(input, s"Invalid NodeId: $input")
}

object InvalidNodeId {
def apply(input: String): InvalidNodeId = new InvalidNodeId(input)
}
}
}

sealed case class NodePath(steps: Vector[NodePathStep]) { self =>
import NodePathStep.*

def /(step: NodePathStep): NodePath = NodePath(steps :+ step)
def /(name: String): NodePath = self / ChildByName(Name.fromString(name))

@inline def isEmpty: Boolean = steps.isEmpty

override def toString(): String =
if (self.isEmpty) ""
else
steps.map {
case ChildByName(name) => name.toCamelCase
case ChildByIndex(index) => index.toString()
}.mkString("#", ":", "")
}

object NodePath {
import NodePathStep.*
val empty: NodePath = NodePath(Vector.empty)

@inline def fromIterable(iterable: Iterable[NodePathStep]): NodePath = NodePath(iterable.toVector)

def fromString(input: String): NodePath =
if (input.isEmpty()) empty
else
fromIterable(input.split(":").map { stepString =>
stepString.toIntOption match {
case Some(index) => NodePathStep.childByIndex(index)
case None => NodePathStep.childByName(stepString)
}
})
}

sealed trait NodePathStep
object NodePathStep {
def childByName(input: String): NodePathStep = ChildByName(Name.fromString(input))
def childByIndex(index: Int): NodePathStep = ChildByIndex(index)

sealed case class ChildByName(name: Name) extends NodePathStep
sealed case class ChildByIndex(index: Int) extends NodePathStep
}

trait HasId {
def id: NodeID
}
}
44 changes: 44 additions & 0 deletions morphir/src/org/finos/morphir/ir/gen1/packageName.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package org.finos.morphir.ir.gen1

trait PackageNameModule { self: Names =>

/** A package name is a globally unique identifier for a package. It is represented by a `Path` which is a list of
* names.
*/
sealed case class PackageName(path: Path) { self =>
def ++(that: PackageName): PackageName = PackageName(path ++ that.path)
def ++(that: Path): PackageName = PackageName(path ++ that)
def /(pathString: String): PackageName = PackageName(path ++ Path.fromString(pathString))

@deprecated("Use `%(moduleName: ModuleName)` instead", "0.4.0-M3")
def /(moduleName: ModuleName): QualifiedModuleName = QualifiedModuleName(self, moduleName)
def %(modulePath: String): QualifiedModuleName = QualifiedModuleName(self, ModuleName.fromString(modulePath))
def %(moduleName: ModuleName): QualifiedModuleName = QualifiedModuleName(self, moduleName)

@inline def isEmpty: Boolean = path.isEmpty
@inline def toPath: Path = path

def render(implicit renderer: PathRenderer): String = renderer(path)
/// An alias for `render`
def show(implicit renderer: PathRenderer): String = render
override def toString(): String = render
}

val root = PackageName.root

object PackageName {
val empty: PackageName = PackageName(Path.empty)
val root: PackageName = PackageName(Path.empty)

// val morphirSdk:PackageName = PackageName.fromString("Morphir.SDK")

def fromPath(path: Path): PackageName = PackageName(path)

def fromString(str: String): PackageName = PackageName(Path.fromString(str))
def fromIterable(segments: Iterable[Name]): PackageName =
PackageName(Path.fromIterable(segments))
def fromList(segments: List[Name]): PackageName =
PackageName(Path.fromList(segments))

}
}
Loading
Loading