-
Notifications
You must be signed in to change notification settings - Fork 56
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #300 from michelchan/start-ir
Starting to add IR
- Loading branch information
Showing
11 changed files
with
1,193 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
package org.finos.morphir.ir.gen1 | ||
|
||
trait ModuleNameModule { self: NameModule with PathModule => | ||
|
||
/** A module name is a unique identifier for a module within a package. It is represented by a `Path`, which is a | ||
* "list" of names. | ||
*/ | ||
sealed case class ModuleName(path: Path) { self => | ||
|
||
/// Construct a new module name by concatting the given module name to this one. | ||
def ++(other: ModuleName): ModuleName = ModuleName(path ++ other.path) | ||
|
||
/// Construct a new module name by concatting the given module path to this one. | ||
def ++(other: Path): ModuleName = ModuleName(path ++ other) | ||
|
||
/// Construct a new module name by concatting the given local name to this module name. | ||
def /(name: Name): ModuleName = ModuleName(path / name) | ||
/// Construct a new module name by concatting the given local name to this module name. | ||
def /(name: String): ModuleName = ModuleName(path / Name(name)) | ||
|
||
// Need a non-symbolic version of this for QualifiedModuleName, strange compilation errors | ||
// happen in QualifiedModuleName./(str: String) implementation when using `moduleName / str` otherwise | ||
def addPart(name: String): ModuleName = ModuleName(path / Name(name)) | ||
|
||
/// Check if the module name is empty. | ||
@inline def isEmpty: Boolean = path.isEmpty | ||
|
||
/// Get the name of this module. | ||
/// For example if the module name is `Morphir.SDK.Basics` then the name is `Basics`. | ||
def name: Name = | ||
self match { | ||
case ModuleName(Path(Vector())) => Name.empty | ||
case ModuleName(Path(segments)) => segments.last | ||
} | ||
|
||
// Get the name of this module if a name is present. | ||
def nameOption: Option[Name] = | ||
self match { | ||
case ModuleName(Path(Vector())) => None | ||
case ModuleName(Path(segments)) => Some(segments.last) | ||
} | ||
|
||
/// Convert this module name to a `Path`. | ||
@inline def toPath: Path = path | ||
override def toString: String = path.toString | ||
} | ||
|
||
object ModuleName { | ||
/// Create an empty module name. | ||
val empty: ModuleName = ModuleName(Path.empty) | ||
|
||
def apply(input: String): ModuleName = fromString(input) | ||
|
||
def fromString(input: String): ModuleName = ModuleName(Path.fromString(input)) | ||
|
||
def apply(parts: Name*): ModuleName = ModuleName(Path.fromIterable(parts)) | ||
def fromIterable(segments: Iterable[Name]): ModuleName = ModuleName(Path.fromIterable(segments)) | ||
def fromStrings(parts: String*): ModuleName = ModuleName(Path.fromStrings(parts: _*)) | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
package org.finos.morphir.ir.gen1 | ||
|
||
import scala.annotation.tailrec | ||
|
||
trait NameModule { | ||
|
||
/** `Name` is an abstraction of human-readable identifiers made up of words. This abstraction allows us to use the | ||
* same identifiers across various naming conventions used by the different frontend and backend languages Morphir | ||
* integrates with. | ||
*/ | ||
sealed case class Name private (toList: List[String]) { | ||
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: String): Name = self ++ Name.fromString(that) | ||
|
||
// def /(that: Name): Path = Path(IndexedSeq(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 render(implicit renderer: NameRenderer): String = renderer.render(self) | ||
|
||
def toUpperCase: String = mkString(part => part.toUpperCase)("") | ||
|
||
// def toLocalName(implicit renderer: Name.Renderer): LocalName = { | ||
// val localNameStr = render | ||
// LocalName(localNameStr) | ||
// } | ||
|
||
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("") | ||
|
||
override def toString: String = toList.mkString("[", ",", "]") | ||
} | ||
|
||
object Name { | ||
|
||
val empty: Name = Name(Nil) | ||
|
||
private[morphir] def wrap(value: List[String]): Name = Name(value) | ||
|
||
private[morphir] def wrap(value: Array[String]): Name = Name(value.toList) | ||
|
||
def apply(first: String, rest: String*): Name = | ||
fromIterable(first +: rest) | ||
|
||
private val pattern = """([a-zA-Z][a-z]*|[0-9]+)""".r | ||
|
||
/** Converts a list of strings into a name. NOTE: When this function is used, the strings are used as is to | ||
* construct the name, and don't go through any processing. This behavior is desired here as it is consistent with | ||
* Morphir's semantics for this function as defined in the `morphir-elm` project. | ||
*/ | ||
def fromList(list: List[String]): Name = Name(list) | ||
|
||
/** Converts a list of strings into a name. NOTE: When this function is used, the strings are used as is to | ||
* construct the name, and don't go through any processing. This behavior is desired here as it is consistent with | ||
* Morphir's semantics for this function as defined in the `morphir-elm` project. | ||
*/ | ||
def fromList(list: String*): Name = fromList(list.toList) | ||
|
||
def fromIterable(iterable: Iterable[String]): Name = | ||
wrap(iterable.flatMap(str => pattern.findAllIn(str)).map(_.toLowerCase).toList) | ||
|
||
def fromString(str: String): Name = | ||
Name(pattern.findAllIn(str).toList.map(_.toLowerCase())) | ||
|
||
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 | ||
|
||
object VariableName { | ||
def unapply(name: Name): Option[String] = | ||
Some(name.toCamelCase) | ||
} | ||
|
||
} | ||
|
||
trait NameRenderer extends (Name => String) { | ||
final def render(name: Name): String = apply(name) | ||
} | ||
|
||
object NameRenderer { | ||
object CamelCase extends NameRenderer { | ||
def apply(name: Name): String = name.toCamelCase | ||
} | ||
|
||
object KebabCase extends NameRenderer { | ||
def apply(name: Name): String = name.toKebabCase | ||
} | ||
|
||
object SnakeCase extends NameRenderer { | ||
def apply(name: Name): String = name.toSnakeCase | ||
} | ||
|
||
object TitleCase extends NameRenderer { | ||
def apply(name: Name): String = name.toTitleCase | ||
} | ||
|
||
implicit val default: NameRenderer = TitleCase | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package org.finos.morphir.ir.gen1 | ||
|
||
trait NamespaceModule { self: NameModule with PathModule with ModuleNameModule => | ||
sealed case class Namespace(path: Path) { self => | ||
def ++(name: Namespace): Namespace = Namespace(path ++ path) | ||
def /(segment: String): Namespace = Namespace(path ++ Path.fromString(segment)) | ||
def /(names: String*): Namespace = Namespace(path ++ Path.fromIterable(names.map(Name.fromString(_)))) | ||
def /(names: Iterable[String]): Namespace = Namespace(path ++ Path.fromIterable(names.map(Name.fromString(_)))) | ||
|
||
@inline def toPath: Path = path | ||
def parts(implicit renderer: NamespaceRenderer): IndexedSeq[String] = path.parts(renderer) | ||
|
||
def render(implicit renderer: NamespaceRenderer): String = renderer(path) | ||
/// An alias for `render` | ||
def show(implicit renderer: NamespaceRenderer): String = render | ||
def toModuleName: ModuleName = ModuleName(path) | ||
|
||
override def toString(): String = render | ||
} | ||
|
||
object Namespace { | ||
val ns: Namespace = Namespace(Path.empty) | ||
|
||
def apply(parts: Name*): Namespace = Namespace(Path.fromIterable(parts)) | ||
def fromIterable(segments: Iterable[Name]): Namespace = Namespace(Path.fromIterable(segments)) | ||
def fromModuleName(moduleName: ModuleName): Namespace = Namespace(moduleName.path) | ||
def fromStrings(parts: String*): Namespace = Namespace(Path.fromStrings(parts: _*)) | ||
|
||
def fromPath(path: Path): Namespace = Namespace(path) | ||
|
||
} | ||
|
||
sealed case class NamespaceRenderer(separator: String, nameRenderer: NameRenderer) extends (Path => String) { | ||
def apply(path: Path): String = path.toString(nameRenderer, separator) | ||
final def render(path: Path): String = apply(path) | ||
} | ||
|
||
object NamespaceRenderer { | ||
val CamelCase: NamespaceRenderer = NamespaceRenderer(".", NameRenderer.CamelCase) | ||
val KebabCase: NamespaceRenderer = NamespaceRenderer(".", NameRenderer.KebabCase) | ||
val SnakeCase: NamespaceRenderer = NamespaceRenderer(".", NameRenderer.SnakeCase) | ||
val TitleCase: NamespaceRenderer = NamespaceRenderer(".", NameRenderer.TitleCase) | ||
|
||
implicit val default: NamespaceRenderer = TitleCase | ||
implicit def toPathRenderer(renderer: NamespaceRenderer): PathRenderer = | ||
PathRenderer(renderer.separator, renderer.nameRenderer) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
package org.finos.morphir.ir.gen1 | ||
|
||
import scala.annotation.tailrec | ||
|
||
trait PathModule { self: NameModule => | ||
|
||
sealed case class Path(segments: Vector[Name]) { | ||
self => | ||
|
||
def ++(that: Path): Path = Path(segments ++ that.segments) | ||
// def ::(name: Name): QName = QName(self.toPath, name) | ||
|
||
/** Indicates whether this path is empty. */ | ||
def isEmpty: Boolean = toList.isEmpty | ||
|
||
def toList: List[Name] = segments.toList | ||
|
||
/** Constructs a new path by combining this path with the given name. */ | ||
def /(name: Name): Path = Path(segments ++ List(name)) | ||
|
||
/** Constructs a new path by combining this path with the given path. */ | ||
def /(that: Path): Path = Path(segments ++ that.toList) | ||
// def %(other: Path): PackageAndModulePath = | ||
// PackageAndModulePath(PackageName(self), ModulePath(other)) | ||
|
||
def zip(other: Path): (Path, Path) = (self, other) | ||
|
||
def toString(f: Name => String, separator: String): String = | ||
toList.map(f).mkString(separator) | ||
|
||
/** Checks if this path is a prefix of provided path */ | ||
def isPrefixOf(path: Path): Boolean = Path.isPrefixOf(self, path) | ||
|
||
def parts(implicit renderer: PathRenderer): IndexedSeq[String] = segments.map(_.render(renderer.nameRenderer)) | ||
|
||
def render(implicit renderer: PathRenderer): String = renderer(self) | ||
def render(separator: String)(implicit nameRenderer: NameRenderer): String = | ||
render(PathRenderer(separator, nameRenderer)) | ||
|
||
// def toPackageName(implicit renderer: Name.Renderer = Name.Renderer.TitleCase): PackageName = { | ||
// val nsSegments = PackageName.segments(segments.map(_.render)) | ||
// PackageName.fromIterable(nsSegments) | ||
// } | ||
|
||
// def toNamespace(implicit renderer: Name.Renderer = Name.Renderer.TitleCase): Namespace = { | ||
// val nsSegments = Namespace.segments(segments.map(_.render)) | ||
// Namespace.fromIterable(nsSegments) | ||
// } | ||
|
||
override def toString(): String = render | ||
} | ||
|
||
object Path { | ||
val separatorRegex = """[^\w\s]+""".r | ||
val empty: Path = Path(Vector.empty) | ||
val root: Path = Path(Vector.empty) | ||
|
||
def apply(first: String, rest: String*): Path = | ||
if (rest.isEmpty) Path(Vector(Name.fromString(first))) | ||
else Path.fromIterable(Name.fromString(first) +: rest.map(Name.fromString(_))) | ||
|
||
def apply(first: Name, rest: Name*): Path = | ||
if (rest.isEmpty) Path(Vector(first)) | ||
else Path(first +: rest.toVector) | ||
|
||
/** Translates a string into a path by splitting it into names along special characters. The algorithm will treat | ||
* any non-word characters that are not spaces as a path separator. | ||
*/ | ||
def fromString(str: String): Path = | ||
fromArray(separatorRegex.split(str).map(Name.fromString)) | ||
|
||
def toString(f: Name => String, separator: String, path: Path): String = | ||
path.toString(f, separator) | ||
|
||
/// Converts an array of names into a path. | ||
@inline def fromArray(names: Array[Name]): Path = Path(names.toVector) | ||
/// Converts names into a path. | ||
@inline def fromIterable(names: Iterable[Name]): Path = Path(names.toVector) | ||
/// Converts a list of names into a path. | ||
@inline def fromList(names: List[Name]): Path = Path(names.toVector) | ||
/// Converts a list of names into a path. | ||
@inline def fromList(names: Name*): Path = Path(names.toVector) | ||
|
||
def fromStrings(names: String*): Path = Path(names.map(Name.fromString).toVector) | ||
/// Converts names into a path. | ||
@inline def fromVector(names: Vector[Name]): Path = Path(names) | ||
|
||
@inline def toList(path: Path): List[Name] = path.toList.toList | ||
|
||
/** Checks if the first provided path is a prefix of the second path */ | ||
@tailrec | ||
def isPrefixOf(prefix: Path, path: Path): Boolean = (prefix.toList, path.toList) match { | ||
case (Nil, _) => true | ||
case (_, Nil) => false | ||
case (prefixHead :: prefixTail, pathHead :: pathTail) => | ||
if (prefixHead == pathHead) | ||
isPrefixOf( | ||
Path.fromList(prefixTail), | ||
Path.fromList(pathTail) | ||
) | ||
else false | ||
} | ||
|
||
private[morphir] def unsafeMake(parts: Name*): Path = Path(parts.toVector) | ||
} | ||
|
||
sealed case class PathRenderer(separator: String, nameRenderer: NameRenderer) extends (Path => String) { | ||
def apply(path: Path): String = path.toString(nameRenderer, separator) | ||
final def render(path: Path): String = apply(path) | ||
} | ||
|
||
object PathRenderer { | ||
val CamelCase: PathRenderer = PathRenderer(".", NameRenderer.CamelCase) | ||
val KebabCase: PathRenderer = PathRenderer(".", NameRenderer.KebabCase) | ||
val SnakeCase: PathRenderer = PathRenderer(".", NameRenderer.SnakeCase) | ||
val TitleCase: PathRenderer = PathRenderer(".", NameRenderer.TitleCase) | ||
|
||
implicit val default: PathRenderer = TitleCase | ||
} | ||
} |
Oops, something went wrong.