Skip to content

Commit

Permalink
Merge pull request #301 from michelchan/morefiles
Browse files Browse the repository at this point in the history
Adding More IR Files
  • Loading branch information
michelchan authored Oct 3, 2024
2 parents 3f185d4 + ee65b72 commit 89ec528
Show file tree
Hide file tree
Showing 11 changed files with 888 additions and 607 deletions.
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

0 comments on commit 89ec528

Please sign in to comment.