mirror of
https://github.com/enso-org/enso.git
synced 2024-12-22 17:41:53 +03:00
Add a low-level graph library (#334)
This commit is contained in:
parent
0ec41b5bbd
commit
22aa4efda8
1
.gitignore
vendored
1
.gitignore
vendored
@ -92,3 +92,4 @@ bench-report.xml
|
||||
#########
|
||||
|
||||
.editorconfig
|
||||
.bloop
|
||||
|
@ -63,10 +63,10 @@ verticalMultiline.excludeDanglingParens = [
|
||||
|
||||
// Rewrite Rules
|
||||
rewrite.rules = [
|
||||
ExpandImportSelectors,
|
||||
PreferCurlyFors,
|
||||
RedundantParens,
|
||||
SortModifiers,
|
||||
SortImports,
|
||||
]
|
||||
rewrite.sortModifiers.order = [
|
||||
"implicit", "final", "sealed", "abstract",
|
||||
|
130
build.sbt
130
build.sbt
@ -35,6 +35,7 @@ scalacOptions in ThisBuild ++= Seq(
|
||||
"-language:higherKinds", // Allow higher-kinded types
|
||||
"-language:implicitConversions", // Allow definition of implicit functions called views
|
||||
"-unchecked", // Enable additional warnings where generated code depends on assumptions.
|
||||
"-Xcheckinit", // Wrap field accessors to throw an exception on uninitialized access.
|
||||
"-Xlint:adapted-args", // Warn if an argument list is modified to match the receiver.
|
||||
"-Xlint:by-name-right-associative", // By-name parameter of right associative operator.
|
||||
"-Xlint:constant", // Evaluation of a constant arithmetic expression results in an error.
|
||||
@ -50,9 +51,10 @@ scalacOptions in ThisBuild ++= Seq(
|
||||
"-Xlint:poly-implicit-overload", // Parameterized overloaded implicit methods are not visible as view bounds.
|
||||
"-Xlint:private-shadow", // A private field (or class parameter) shadows a superclass field.
|
||||
"-Xlint:stars-align", // Pattern sequence wildcard must align with sequence component.
|
||||
"-Xlint:type-parameter-shadow", // A local type parameter shadows a type already in scope.
|
||||
"-Xlint:unsound-match", // Pattern match may not be typesafe.
|
||||
"-Xmacro-settings:-logging@org.enso", // Disable the debug logging globally.
|
||||
"-Yno-adapted-args", // Do not adapt an argument list (either by inserting () or creating a tuple) to match the receiver.
|
||||
"-Ypartial-unification", // Enable partial unification (which is enabled by default in Scala 2.13).
|
||||
"-Ypartial-unification", // Enable partial unification in type constructor inference
|
||||
"-Ywarn-dead-code", // Warn when dead code is identified.
|
||||
"-Ywarn-extra-implicit", // Warn when more than one implicit parameter section is defined.
|
||||
@ -61,18 +63,18 @@ scalacOptions in ThisBuild ++= Seq(
|
||||
"-Ywarn-nullary-override", // Warn when non-nullary `def f()' overrides nullary `def f'.
|
||||
"-Ywarn-nullary-unit", // Warn when nullary methods return Unit.
|
||||
"-Ywarn-numeric-widen", // Warn when numerics are widened.
|
||||
"-Ywarn-unused:implicits", // Warn if an implicit parameter is unused.
|
||||
"-Ywarn-unused:imports", // Warn if an import selector is not referenced.
|
||||
"-Ywarn-unused:locals", // Warn if a local definition is unused.
|
||||
"-Ywarn-unused:params", // Warn if a value parameter is unused.
|
||||
"-Ywarn-unused:patvars", // Warn if a variable bound in a pattern is unused.
|
||||
"-Ywarn-unused:privates", // Warn if a private member is unused.
|
||||
"-Ywarn-value-discard", // Warn when non-Unit expression results are unused.
|
||||
"-Ypartial-unification", // Enable partial unification (which is enabled by default in Scala 2.13).
|
||||
"-Xmacro-settings:-logging@org.enso", // Disable the debug logging globally.
|
||||
"-Xcheckinit" // Wrap field accessors to throw an exception on uninitialized access.
|
||||
"-Ywarn-value-discard" // Warn when non-Unit expression results are unused.
|
||||
)
|
||||
|
||||
// ENABLE THIS IN Scala 2.13.1 (where import annotation.unused is available).
|
||||
// "-Xlint:type-parameter-shadow", // A local type parameter shadows a type already in scope.
|
||||
// "-Ywarn-unused:implicits", // Warn if an implicit parameter is unused.
|
||||
// "-Ywarn-unused:params", // Warn if a value parameter is unused.
|
||||
|
||||
/////////////////////////////////
|
||||
//// Benchmark Configuration ////
|
||||
/////////////////////////////////
|
||||
@ -90,14 +92,15 @@ lazy val buildNativeImage =
|
||||
lazy val enso = (project in file("."))
|
||||
.settings(version := "0.1")
|
||||
.aggregate(
|
||||
syntax,
|
||||
pkg,
|
||||
runtime,
|
||||
flexer,
|
||||
unused,
|
||||
syntax_definition,
|
||||
file_manager,
|
||||
project_manager
|
||||
flexer,
|
||||
graph,
|
||||
pkg,
|
||||
project_manager,
|
||||
runtime,
|
||||
syntax,
|
||||
syntax_definition,
|
||||
unused
|
||||
)
|
||||
.settings(Global / concurrentRestrictions += Tags.exclusive(Exclusive))
|
||||
|
||||
@ -115,8 +118,11 @@ val monocle = {
|
||||
}
|
||||
val cats = {
|
||||
Seq(
|
||||
"org.typelevel" %% "cats-core" % "2.0.0-RC1",
|
||||
"org.typelevel" %% "kittens" % "2.0.0"
|
||||
"org.typelevel" %% "cats-core" % "2.0.0",
|
||||
"org.typelevel" %% "cats-effect" % "2.0.0",
|
||||
"org.typelevel" %% "cats-free" % "2.0.0",
|
||||
"org.typelevel" %% "cats-macros" % "2.0.0",
|
||||
"org.typelevel" %% "kittens" % "2.0.0"
|
||||
)
|
||||
}
|
||||
|
||||
@ -149,9 +155,11 @@ val jmh = Seq(
|
||||
"org.openjdk.jmh" % "jmh-generator-annprocess" % "1.21" % Benchmark
|
||||
)
|
||||
|
||||
//////////////////////
|
||||
//// Sub-Projects ////
|
||||
//////////////////////
|
||||
val silencerVersion = "1.4.4"
|
||||
|
||||
////////////////////////////
|
||||
//// Internal Libraries ////
|
||||
////////////////////////////
|
||||
|
||||
lazy val logger = (project in file("common/scala/logger"))
|
||||
.dependsOn(unused)
|
||||
@ -182,6 +190,34 @@ lazy val syntax_definition = (project in file("common/scala/syntax/definition"))
|
||||
)
|
||||
)
|
||||
|
||||
lazy val graph = (project in file("common/scala/graph/"))
|
||||
.dependsOn(logger)
|
||||
.configs(Test)
|
||||
.settings(
|
||||
version := "0.1",
|
||||
scalacOptions -= "-deprecation", // FIXME
|
||||
resolvers ++= Seq(
|
||||
Resolver.sonatypeRepo("releases"),
|
||||
Resolver.sonatypeRepo("snapshots")
|
||||
),
|
||||
libraryDependencies ++= scala_compiler ++ Seq(
|
||||
"com.chuusai" %% "shapeless" % "2.3.3",
|
||||
"io.estatico" %% "newtype" % "0.4.3",
|
||||
"org.scalatest" %% "scalatest" % "3.2.0-SNAP10" % Test,
|
||||
"org.scalacheck" %% "scalacheck" % "1.14.0" % Test,
|
||||
"com.github.julien-truffaut" %% "monocle-core" % "2.0.0"
|
||||
),
|
||||
libraryDependencies ++= Seq(
|
||||
compilerPlugin(
|
||||
"com.github.ghik" % "silencer-plugin" % silencerVersion cross CrossVersion.full
|
||||
),
|
||||
"com.github.ghik" % "silencer-lib" % silencerVersion % Provided cross CrossVersion.full
|
||||
),
|
||||
addCompilerPlugin(
|
||||
"org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.full
|
||||
)
|
||||
)
|
||||
|
||||
lazy val syntax = (project in file("common/scala/syntax/specialization"))
|
||||
.dependsOn(logger, flexer, syntax_definition)
|
||||
.configs(Test)
|
||||
@ -220,6 +256,36 @@ lazy val pkg = (project in file("common/scala/pkg"))
|
||||
libraryDependencies ++= circe ++ Seq("commons-io" % "commons-io" % "2.6")
|
||||
)
|
||||
|
||||
lazy val file_manager = (project in file("common/scala/file-manager"))
|
||||
.settings(
|
||||
(Compile / mainClass) := Some("org.enso.filemanager.FileManager")
|
||||
)
|
||||
.settings(
|
||||
libraryDependencies ++= akka,
|
||||
libraryDependencies += akkaSLF4J,
|
||||
libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.2.3",
|
||||
libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.0-SNAP10" % Test,
|
||||
libraryDependencies += "org.scalacheck" %% "scalacheck" % "1.14.0" % Test,
|
||||
libraryDependencies += akkaTestkitTyped,
|
||||
libraryDependencies += "commons-io" % "commons-io" % "2.6",
|
||||
libraryDependencies += "io.methvin" % "directory-watcher" % "0.9.6"
|
||||
)
|
||||
|
||||
lazy val project_manager = (project in file("common/scala/project-manager"))
|
||||
.settings(
|
||||
(Compile / mainClass) := Some("org.enso.projectmanager.Server")
|
||||
)
|
||||
.settings(
|
||||
libraryDependencies ++= akka,
|
||||
libraryDependencies ++= circe,
|
||||
libraryDependencies += "io.spray" %% "spray-json" % "1.3.5"
|
||||
)
|
||||
.dependsOn(pkg)
|
||||
|
||||
//////////////////////
|
||||
//// Sub Projects ////
|
||||
//////////////////////
|
||||
|
||||
val truffleRunOptions = Seq(
|
||||
"-Dgraal.TruffleIterativePartialEscape=true",
|
||||
"-XX:-UseJVMCIClassLoader",
|
||||
@ -316,29 +382,3 @@ lazy val runtime = (project in file("engine/runtime"))
|
||||
)
|
||||
.dependsOn(pkg)
|
||||
.dependsOn(syntax)
|
||||
|
||||
lazy val file_manager = (project in file("common/scala/file-manager"))
|
||||
.settings(
|
||||
(Compile / mainClass) := Some("org.enso.filemanager.FileManager")
|
||||
)
|
||||
.settings(
|
||||
libraryDependencies ++= akka,
|
||||
libraryDependencies += akkaSLF4J,
|
||||
libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.2.3",
|
||||
libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.0-SNAP10" % Test,
|
||||
libraryDependencies += "org.scalacheck" %% "scalacheck" % "1.14.0" % Test,
|
||||
libraryDependencies += akkaTestkitTyped,
|
||||
libraryDependencies += "commons-io" % "commons-io" % "2.6",
|
||||
libraryDependencies += "io.methvin" % "directory-watcher" % "0.9.6"
|
||||
)
|
||||
|
||||
lazy val project_manager = (project in file("common/scala/project-manager"))
|
||||
.settings(
|
||||
(Compile / mainClass) := Some("org.enso.projectmanager.Server")
|
||||
)
|
||||
.settings(
|
||||
libraryDependencies ++= akka,
|
||||
libraryDependencies ++= circe,
|
||||
libraryDependencies += "io.spray" %% "spray-json" % "1.3.5"
|
||||
)
|
||||
.dependsOn(pkg)
|
||||
|
590
common/scala/graph/src/main/scala/org/enso/graph/Graph.scala
Normal file
590
common/scala/graph/src/main/scala/org/enso/graph/Graph.scala
Normal file
@ -0,0 +1,590 @@
|
||||
package org.enso.graph
|
||||
|
||||
import shapeless.ops.{hlist, nat}
|
||||
import shapeless.{::, HList, HNil, Nat}
|
||||
|
||||
// Don't use AnyType here, as it gets boxed sometimes.
|
||||
import io.estatico.newtype.macros.newtype
|
||||
|
||||
// Type-level literals (_0, _1, ...)
|
||||
import shapeless.nat._
|
||||
|
||||
/* TODO [AA, WD] The following are features that we want for this graph:
|
||||
* - Graphviz output for visualisations.
|
||||
* - Utilities for copying (sub-)graphs.
|
||||
* - Storage should keep a free-list and re-use space in the underlying buffers
|
||||
* as much as possible.
|
||||
* - Basic equality testing (that should be overridden as needed).
|
||||
* - An ability to define fields that store complex data such as `String`.
|
||||
*/
|
||||
|
||||
/** This file contains the implementation of an incredibly generic graph.
|
||||
*
|
||||
* There are a few key notes that should be kept in mind when analysing this
|
||||
* solution:
|
||||
* - This is a _highly_ generic graph structure, which provides the plumbing
|
||||
* for building your own graph structures.
|
||||
* - It only provides an _unsafe_ API. As a result, this graph should never be
|
||||
* used directly by client code. Instead, it should be used to implement a
|
||||
* custom graph structure that provides a safe API.
|
||||
*/
|
||||
// ============================================================================
|
||||
// === HList generic utilities ================================================
|
||||
// ============================================================================
|
||||
|
||||
// ================
|
||||
// === HListSum ===
|
||||
// ================
|
||||
|
||||
/** Sums a [[HList]] that contains only [[Nat]].
|
||||
*
|
||||
* @tparam L the [[HList]] to sum
|
||||
*/
|
||||
trait HListSum[L <: HList] {
|
||||
type Out <: Nat
|
||||
}
|
||||
object HListSum {
|
||||
type Aux[L <: HList, X] = HListSum[L] { type Out = X }
|
||||
|
||||
def apply[L <: HList](implicit ev: HListSum[L]): Aux[L, ev.Out] = ev
|
||||
|
||||
implicit val onNil: HListSum.Aux[HNil, _0] =
|
||||
new HListSum[HNil] { type Out = _0 }
|
||||
|
||||
implicit def onCons[H <: Nat, T <: HList, TS <: Nat](
|
||||
implicit
|
||||
rest: HListSum.Aux[T, TS],
|
||||
all: nat.Sum[H, TS]
|
||||
): HListSum.Aux[H :: T, all.Out] =
|
||||
new HListSum[H :: T] { type Out = all.Out }
|
||||
}
|
||||
|
||||
// =======================
|
||||
// === HListOfNatToVec ===
|
||||
// =======================
|
||||
|
||||
/** Converts an [[HList]] of [[Nat]] to a vactor containing those same numbers
|
||||
* as integers.
|
||||
*
|
||||
* @tparam L the [[HList]] to convert
|
||||
*/
|
||||
trait HListOfNatToVec[L <: HList] {
|
||||
val out: Vector[Int]
|
||||
}
|
||||
object HListOfNatToVec {
|
||||
implicit def onNil: HListOfNatToVec[HNil] =
|
||||
new HListOfNatToVec[HNil] { val out = Vector[Int]() }
|
||||
|
||||
implicit def onCons[Head <: Nat, Tail <: HList](
|
||||
implicit
|
||||
tail: HListOfNatToVec[Tail],
|
||||
head: nat.ToInt[Head]
|
||||
): HListOfNatToVec[Head :: Tail] = new HListOfNatToVec[Head :: Tail] {
|
||||
val out = head() +: tail.out
|
||||
}
|
||||
}
|
||||
|
||||
// ======================
|
||||
// === HListTakeUntil ===
|
||||
// ======================
|
||||
|
||||
/** Takes members from an [[HList]] until it reaches a specified member [[T]].
|
||||
*
|
||||
* The sentinel member [[T]] is not included in the result list. For example,
|
||||
* the following code will result in the list `A :: HNil`:
|
||||
*
|
||||
* {{{
|
||||
* type MyList = A :: B :: C :: HNil
|
||||
* type Sentinel = B
|
||||
*
|
||||
* type HListTakeUntil[Sentinel, MyList]
|
||||
* }}}
|
||||
*
|
||||
* If the sentinel [[T]] is not found in [[List]], the entire list is returned.
|
||||
*
|
||||
* @tparam T the sentinel member
|
||||
* @tparam List the list to take members from
|
||||
*/
|
||||
trait HListTakeUntil[T, List <: HList] {
|
||||
type Out <: HList
|
||||
}
|
||||
object HListTakeUntil extends HListTakeUntilDefaults {
|
||||
type Aux[T, List <: HList, X] = HListTakeUntil[T, List] { type Out = X }
|
||||
|
||||
def apply[T, List <: HList](
|
||||
implicit ev: HListTakeUntil[T, List]
|
||||
): Aux[T, List, ev.Out] = ev
|
||||
|
||||
implicit def onNil[T]: HListTakeUntil.Aux[T, HNil, HNil] =
|
||||
new HListTakeUntil[T, HNil] { type Out = HNil }
|
||||
|
||||
implicit def onConsFound[Head, Tail <: HList]
|
||||
: HListTakeUntil.Aux[Head, Head :: Tail, HNil] =
|
||||
new HListTakeUntil[Head, Head :: Tail] { type Out = HNil }
|
||||
}
|
||||
|
||||
trait HListTakeUntilDefaults {
|
||||
implicit def onConsNotFound[T, Head, Tail <: HList, Tail2 <: HList](
|
||||
implicit
|
||||
ev1: HListTakeUntil.Aux[T, Tail, Tail2]
|
||||
): HListTakeUntil.Aux[T, Head :: Tail, Head :: Tail2] =
|
||||
new HListTakeUntil[T, Head :: Tail] { type Out = Head :: Tail2 }
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// === Graph-specific utilities ===============================================
|
||||
// ============================================================================
|
||||
|
||||
// =============
|
||||
// === Sized ===
|
||||
// =============
|
||||
|
||||
/** An abstraction for sized objects.
|
||||
*
|
||||
* Every sized object is aware of the size it occupies (specified as a number
|
||||
* of [[Int]]) at compile time.
|
||||
*
|
||||
* @tparam T the [[Sized]] type
|
||||
*/
|
||||
trait Sized[T] {
|
||||
type Out <: Nat
|
||||
}
|
||||
object Sized {
|
||||
type Aux[T, X] = Sized[T] { type Out = X }
|
||||
def apply[T](implicit ev: Sized[T]): Aux[T, ev.Out] = ev
|
||||
|
||||
implicit def instance[
|
||||
List <: HList,
|
||||
ListOfSizes <: HList,
|
||||
TotalSize <: Nat
|
||||
](
|
||||
implicit
|
||||
ev1: MapSized.Aux[List, ListOfSizes],
|
||||
ev2: HListSum.Aux[ListOfSizes, TotalSize]
|
||||
): Sized.Aux[List, TotalSize] =
|
||||
new Sized[List] { type Out = TotalSize }
|
||||
}
|
||||
|
||||
/** A utility for accessing a the Size of a [[Sized]] object as an [[Int]]. */
|
||||
trait KnownSize[T] extends Sized[T] {
|
||||
val asInt: Int
|
||||
}
|
||||
object KnownSize {
|
||||
implicit def instance[T, Size <: Nat](
|
||||
implicit
|
||||
ev: Sized.Aux[T, Size],
|
||||
sizeEv: nat.ToInt[Size]
|
||||
): KnownSize[T] = new KnownSize[T] { val asInt: Int = sizeEv() }
|
||||
}
|
||||
|
||||
// ================
|
||||
// === MapSized ===
|
||||
// ================
|
||||
|
||||
/** Extracts the sizes of all types in a list of types. All of these types must
|
||||
* be `Sized`.
|
||||
*
|
||||
* @tparam L the list of elements to extract sizes from
|
||||
*/
|
||||
trait MapSized[L <: HList] {
|
||||
type Out <: HList
|
||||
}
|
||||
object MapSized {
|
||||
type Aux[L <: HList, X] = MapSized[L] { type Out = X }
|
||||
|
||||
def apply[L <: HList](implicit ev: MapSized[L]): Aux[L, ev.Out] = ev
|
||||
|
||||
implicit val onNil: MapSized.Aux[HNil, HNil] =
|
||||
new MapSized[HNil] { type Out = HNil }
|
||||
|
||||
implicit def onCons[H, T <: HList, TS <: HList, HSize <: Nat](
|
||||
implicit
|
||||
rest: MapSized.Aux[T, TS],
|
||||
headSize: Sized.Aux[H, HSize]
|
||||
): MapSized.Aux[H :: T, HSize :: TS] =
|
||||
new MapSized[H :: T] { type Out = HSize :: TS }
|
||||
}
|
||||
|
||||
// =================
|
||||
// === SizeUntil ===
|
||||
// =================
|
||||
|
||||
/** Computes the size of the types in a HList up to some sentinel [[Elem]].
|
||||
*
|
||||
* When summing the sizes in the list, the sentinel element [[Elem]] is not
|
||||
* included in the sum.
|
||||
*
|
||||
* @tparam Elem the type of the element to stop computing at
|
||||
* @tparam List the list of types to compute over
|
||||
*/
|
||||
trait SizeUntil[Elem, List <: HList] {
|
||||
type Out <: Nat
|
||||
val asInt: Int
|
||||
}
|
||||
object SizeUntil {
|
||||
type Aux[Elem, List <: HList, X] = SizeUntil[Elem, List] { type Out = X }
|
||||
|
||||
def apply[Elem, List <: HList](
|
||||
implicit ev: SizeUntil[Elem, List]
|
||||
): Aux[Elem, List, ev.Out] = ev
|
||||
|
||||
implicit def instance[
|
||||
Elem,
|
||||
List <: HList,
|
||||
PriorElems <: HList,
|
||||
PriorFieldSizes <: HList,
|
||||
PriorFieldsSize <: Nat
|
||||
](
|
||||
implicit
|
||||
ev1: HListTakeUntil.Aux[Elem, List, PriorElems],
|
||||
ev2: MapSized.Aux[PriorElems, PriorFieldSizes],
|
||||
ev3: HListSum.Aux[PriorFieldSizes, PriorFieldsSize],
|
||||
sizeAsInt: nat.ToInt[PriorFieldsSize]
|
||||
): SizeUntil.Aux[Elem, List, PriorFieldsSize] =
|
||||
new SizeUntil[Elem, List] {
|
||||
type Out = PriorFieldsSize
|
||||
val asInt = sizeAsInt()
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// === Graph ==================================================================
|
||||
// ============================================================================
|
||||
|
||||
/** A generic graph implementation.
|
||||
*
|
||||
* The type [[Graph]] should not be used directly by programs that use this
|
||||
* library. Instead, it should be used to implement custom graph instances by
|
||||
* extending the [[Graph]] trait.
|
||||
*/
|
||||
trait Graph
|
||||
object Graph {
|
||||
|
||||
// ==========================
|
||||
// === Smart Constructors ===
|
||||
// ==========================
|
||||
|
||||
def apply[G <: Graph: GraphInfo](): GraphData[G] = new GraphData[G]()
|
||||
|
||||
// =================
|
||||
// === Component ===
|
||||
// =================
|
||||
|
||||
/** A graph component is a type of thing that can be stored in the graph.
|
||||
*
|
||||
* Components can be arbitrary, such as nodes, edges, groups, and so on. They
|
||||
* are defined entirely by the users, and this is done by extending the
|
||||
* [[Component]] trait. Please see the tests for examples of how this can be
|
||||
* done.
|
||||
*/
|
||||
trait Component
|
||||
object Component {
|
||||
|
||||
// === Ref ===
|
||||
|
||||
/** A generic reference to a graph component.
|
||||
*
|
||||
* For example, the `Node` type could be defined as follows, where `Nodes`
|
||||
* is the appropriate component in the graph.
|
||||
*
|
||||
* {{{
|
||||
* type Node = Ref[MyGraph, Nodes]
|
||||
* }}}
|
||||
*/
|
||||
@newtype
|
||||
final case class Ref[G <: Graph, C <: Component](ix: Int)
|
||||
|
||||
// === Refined ===
|
||||
|
||||
/** Type refinement for component references.
|
||||
*
|
||||
* Type refinement is used to add additional information to a [[Component]]
|
||||
* to encode properties into the graph structure. This information can be
|
||||
* type information, but can also be used to tag graph components with
|
||||
* arbitrary properties.
|
||||
*
|
||||
* For example, a node with information that its shape is `App` can be
|
||||
* encoded having the following type `Refined[Shape, App, Node]`.
|
||||
*/
|
||||
@newtype
|
||||
final case class Refined[C <: Component.Field, Spec, T](wrapped: T)
|
||||
object Refined {
|
||||
implicit def unwrap[C <: Component.Field, S, T](
|
||||
t: Refined[C, S, T]
|
||||
): T = { t.wrapped }
|
||||
}
|
||||
|
||||
// === List ===
|
||||
|
||||
/** Defines the set of components in a graph by assigning a type to the
|
||||
* `Out` parameter when implementing the trait.
|
||||
*
|
||||
* This is used to encode the graph structure at the type level. An example
|
||||
* is as follows:
|
||||
*
|
||||
* {{{
|
||||
* implicit def components = new Component.List[MyGraph] {
|
||||
* type Out = Nodes :: Edges :: HNil
|
||||
* }
|
||||
* }}}
|
||||
*
|
||||
* @tparam G the graph for which the components are defined.
|
||||
*/
|
||||
trait List[G <: Graph] {
|
||||
type Out <: HList
|
||||
}
|
||||
object List {
|
||||
type Aux[G <: Graph, X] = List[G] { type Out = X }
|
||||
}
|
||||
|
||||
// === Field ===
|
||||
|
||||
/** A field is a portion of a [[Component]].
|
||||
*
|
||||
* They are used to attribute data to components, and store data within a
|
||||
* component.
|
||||
*
|
||||
* An example would be a component `Node`, that consists of fields such as
|
||||
* `ParentLink` and `Shape`.
|
||||
*/
|
||||
trait Field
|
||||
object Field {
|
||||
|
||||
/** Defines the set of fields for a given kind of component.
|
||||
*
|
||||
* An example is as follows:
|
||||
*
|
||||
* {{{
|
||||
* implicit def nodeFields = new Component.Field.List[MyGraph, Nodes] {
|
||||
* type Out = Node.Shape :: Node.ParentLink :: HNil
|
||||
* }
|
||||
* }}}
|
||||
*
|
||||
* @tparam G the graph to which the component type [[C]] belongs
|
||||
* @tparam C the component type to which the fields in the list belong
|
||||
*/
|
||||
trait List[G <: Graph, C <: Component] { type Out <: HList }
|
||||
object List {
|
||||
type Aux[G <: Graph, C <: Component, X] = List[G, C] { type Out = X }
|
||||
}
|
||||
}
|
||||
|
||||
// === Storage ===
|
||||
|
||||
/** Specialized storage for component data.
|
||||
*
|
||||
* We intentionally do not use [[scala.collection.mutable.ArrayBuffer]] as
|
||||
* it cannot be specialised for primitive types. [[Array]], on the other
|
||||
* hand, can be.
|
||||
*/
|
||||
final class Storage(elemSize: Int) {
|
||||
var length: Int = 0
|
||||
var array: Array[Int] = new Array[Int](length)
|
||||
|
||||
// TODO: Assert that elem size = elemSize
|
||||
def push(elem: Array[Int]): Unit = {
|
||||
this.array = this.array ++ elem
|
||||
this.length += 1
|
||||
}
|
||||
}
|
||||
|
||||
// === VariantMatcher ===
|
||||
|
||||
/** A utility for generating unapply and match methods for a [[Component]]
|
||||
* that contains a sum type.
|
||||
*
|
||||
* It is used internally by the [[org.enso.graph.definition.Macro.field]]
|
||||
* macro to autogenerate matchers for variant fields.
|
||||
*/
|
||||
case class VariantMatcher[T <: Component.Field, V](ix: Int) {
|
||||
def unapply[G <: Graph, C <: Component](
|
||||
arg: Component.Ref[G, C]
|
||||
)(
|
||||
implicit graph: GraphData[G],
|
||||
ev: HasComponentField[G, C, T]
|
||||
): Option[Component.Refined[T, V, Component.Ref[G, C]]] = {
|
||||
val variantIndexByteOffset = 0
|
||||
if (graph.unsafeReadField[C, T](arg.ix, variantIndexByteOffset) == ix)
|
||||
Some(Component.Refined[T, V, Component.Ref[G, C]](arg))
|
||||
else None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =================
|
||||
// === GraphData ===
|
||||
// =================
|
||||
|
||||
/** [[GraphData]] is the underlying storage representation used by the
|
||||
* [[Graph]].
|
||||
*
|
||||
* It contains the raw data for all the graph components and their fields.
|
||||
*
|
||||
* @param info information about the graph's underlying structure
|
||||
* @tparam G the graph type that the data is for
|
||||
*/
|
||||
final class GraphData[G <: Graph]()(implicit val info: GraphInfo[G]) {
|
||||
var components: Array[Component.Storage] =
|
||||
this.componentSizes.map(size => new Component.Storage(size)).to[Array]
|
||||
|
||||
def unsafeGetFieldData[C <: Component, F <: Component.Field](
|
||||
componentIx: Int,
|
||||
fieldIx: Int
|
||||
)(implicit info: HasComponentField[G, C, F]): (Array[Int], Int) = {
|
||||
val arr = components(info.componentIndex).array
|
||||
val idx = info.componentSize * componentIx + info.fieldOffset + fieldIx
|
||||
(arr, idx)
|
||||
}
|
||||
|
||||
def unsafeReadField[C <: Component, F <: Component.Field](
|
||||
componentIx: Int,
|
||||
fieldIx: Int
|
||||
)(implicit ev: HasComponentField[G, C, F]): Int = {
|
||||
val (arr, idx) = unsafeGetFieldData(componentIx, fieldIx)
|
||||
arr(idx)
|
||||
}
|
||||
|
||||
def unsafeWriteField[C <: Component, F <: Component.Field](
|
||||
componentIx: Int,
|
||||
fieldIx: Int,
|
||||
value: Int
|
||||
)(implicit ev: HasComponentField[G, C, F]): Unit = {
|
||||
val (arr, idx) = unsafeGetFieldData(componentIx, fieldIx)
|
||||
arr(idx) = value
|
||||
}
|
||||
|
||||
def addComponent[C <: Component]()(
|
||||
implicit info: HasComponent[G, C]
|
||||
): Component.Ref[G, C] = {
|
||||
val compClsIx = info.componentIndex
|
||||
val compIx = components(compClsIx).length
|
||||
val data = new Array[Int](info.componentSize)
|
||||
components(compClsIx).push(data)
|
||||
Component.Ref(compIx)
|
||||
}
|
||||
}
|
||||
object GraphData {
|
||||
implicit def getInfo[G <: Graph](g: GraphData[G]): GraphInfo[G] = g.info
|
||||
}
|
||||
|
||||
// ====================
|
||||
// === TypeFamilies ===
|
||||
// ====================
|
||||
|
||||
// === GraphInfo ===
|
||||
|
||||
/** Information about the number and sizes of components stored in the graph.
|
||||
*
|
||||
* @tparam G the graph for which this metadata exists
|
||||
*/
|
||||
trait GraphInfo[G <: Graph] {
|
||||
val componentCount: Int
|
||||
val componentSizes: Vector[Int]
|
||||
}
|
||||
object GraphInfo {
|
||||
implicit def instance[
|
||||
G <: Graph,
|
||||
ComponentList <: HList,
|
||||
ComponentSizeList >: HList,
|
||||
ComponentListLength <: Nat
|
||||
](
|
||||
implicit
|
||||
ev1: Component.List.Aux[G, ComponentList],
|
||||
ev2: hlist.Length.Aux[ComponentList, ComponentListLength],
|
||||
componentSizesEv: ComponentListToSizes[G, ComponentList],
|
||||
len: nat.ToInt[ComponentListLength]
|
||||
): GraphInfo[G] = new GraphInfo[G] {
|
||||
val componentCount = len()
|
||||
val componentSizes = componentSizesEv.sizes
|
||||
}
|
||||
}
|
||||
|
||||
// === HasComponent ===
|
||||
|
||||
/** Encodes that a given graph [[G]] has a component with given type [[C]].
|
||||
*
|
||||
* @tparam G the graph type
|
||||
* @tparam C the component type
|
||||
*/
|
||||
trait HasComponent[G <: Graph, C <: Component] {
|
||||
val componentIndex: Int
|
||||
val componentSize: Int
|
||||
}
|
||||
object HasComponent {
|
||||
implicit def instance[
|
||||
G <: Graph,
|
||||
C <: Component,
|
||||
ComponentList <: HList,
|
||||
PrevComponentList <: HList,
|
||||
ComponentIndex <: Nat,
|
||||
FieldList <: HList
|
||||
](
|
||||
implicit
|
||||
ev1: Component.List.Aux[G, ComponentList],
|
||||
ev2: Component.Field.List.Aux[G, C, FieldList],
|
||||
ev3: HListTakeUntil.Aux[C, ComponentList, PrevComponentList],
|
||||
ev4: hlist.Length.Aux[PrevComponentList, ComponentIndex],
|
||||
componentIndexEv: nat.ToInt[ComponentIndex],
|
||||
componentSizeEv: KnownSize[FieldList]
|
||||
): HasComponent[G, C] = new HasComponent[G, C] {
|
||||
val componentIndex = componentIndexEv()
|
||||
val componentSize = componentSizeEv.asInt
|
||||
}
|
||||
}
|
||||
|
||||
// === HasComponentField ===
|
||||
|
||||
/** Encodes that a graph [[G]] has field [[F]] in component [[C]].
|
||||
*
|
||||
* @tparam G the graph type
|
||||
* @tparam C the component type in [[G]]
|
||||
* @tparam F the field type in [[C]]
|
||||
*/
|
||||
trait HasComponentField[G <: Graph, C <: Component, F <: Component.Field] {
|
||||
val componentIndex: Int
|
||||
val componentSize: Int
|
||||
val fieldOffset: Int
|
||||
}
|
||||
object HasComponentField {
|
||||
implicit def instance[
|
||||
G <: Graph,
|
||||
C <: Component,
|
||||
F <: Component.Field,
|
||||
FieldList <: HList
|
||||
](
|
||||
implicit
|
||||
ev1: Component.Field.List.Aux[G, C, FieldList],
|
||||
evx: HasComponent[G, C],
|
||||
fieldOffsetEv: SizeUntil[F, FieldList]
|
||||
): HasComponentField[G, C, F] = new HasComponentField[G, C, F] {
|
||||
val componentIndex = evx.componentIndex
|
||||
val componentSize = evx.componentSize
|
||||
val fieldOffset = fieldOffsetEv.asInt
|
||||
}
|
||||
}
|
||||
|
||||
// === ComponentListToSizes ===
|
||||
|
||||
/** Obtains the sizes of all the components from the graph's list of
|
||||
* components.
|
||||
*
|
||||
* @tparam G the graph
|
||||
* @tparam ComponentList the list of components
|
||||
*/
|
||||
trait ComponentListToSizes[G <: Graph, ComponentList <: HList] {
|
||||
val sizes: Vector[Int]
|
||||
}
|
||||
object ComponentListToSizes {
|
||||
implicit def onNil[G <: Graph]: ComponentListToSizes[G, HNil] =
|
||||
new ComponentListToSizes[G, HNil] { val sizes = Vector[Int]() }
|
||||
|
||||
implicit def onCons[G <: Graph, C <: Component, Tail <: HList](
|
||||
implicit
|
||||
tail: ComponentListToSizes[G, Tail],
|
||||
info: HasComponent[G, C]
|
||||
): ComponentListToSizes[G, C :: Tail] =
|
||||
new ComponentListToSizes[G, C :: Tail] {
|
||||
val sizes = info.componentSize +: tail.sizes
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,933 @@
|
||||
package org.enso.graph.definition
|
||||
|
||||
import scala.annotation.{compileTimeOnly, StaticAnnotation}
|
||||
import scala.reflect.macros.whitebox
|
||||
|
||||
object Macro {
|
||||
|
||||
/** This macro generates a field definition for the graph, and can generate
|
||||
* these definitions for both single and variant fields for a
|
||||
* [[org.enso.graph.Graph.Component]].
|
||||
*
|
||||
* For a single field, you provide it a definition as follows:
|
||||
*
|
||||
* {{{
|
||||
* @field case class MyName[TParams..](args...)
|
||||
* }}}
|
||||
*
|
||||
* It will then generate the required boilerplate for this field definition,
|
||||
* including field setters and getters for each of the constructor arguments
|
||||
* in the template definition.
|
||||
*
|
||||
* As an example, consider the following:
|
||||
*
|
||||
* {{{@field case class ParentLink[G <: Graph](parent: Edge[G])}}}
|
||||
*
|
||||
* This application of the macro will generate the following code:
|
||||
*
|
||||
* {{{
|
||||
* sealed case class ParentLink() extends Graph.Component.Field
|
||||
* object ParentLink {
|
||||
* implicit def sized = new Sized[ParentLink] { type Out = _1 }
|
||||
*
|
||||
* implicit class ParentLinkInstance[G <: Graph, C <: Component](
|
||||
* node: Component.Ref[G, C]
|
||||
* ) {
|
||||
* def parent(
|
||||
* implicit graph: GraphData[G],
|
||||
* ev: HasComponentField[G, C, ParentLink]
|
||||
* ): Edge[G] = {
|
||||
* Component.Ref(graph.unsafeReadField[C, ParentLink](node.ix, 0))
|
||||
* }
|
||||
*
|
||||
* def parent_=(value: Edge[G])(
|
||||
* implicit graph: GraphData[G],
|
||||
* ev: HasComponentField[G, C, ParentLink]
|
||||
* ): Unit = {
|
||||
* graph.unsafeWriteField[C, ParentLink](node.ix, 0, value.ix)
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* implicit def ParentLink_transInstance[
|
||||
* F <: Component.Field,
|
||||
* R,
|
||||
* G <: Graph,
|
||||
* C <: Component
|
||||
* ](
|
||||
* t: Component.Refined[F, R, Component.Ref[G, C]]
|
||||
* ): ParentLinkInstance[G, C] =
|
||||
* t.wrapped
|
||||
* }
|
||||
* }}}
|
||||
*
|
||||
* You will need to ensure that `T._` is imported into scope as we currently
|
||||
* have no way of making that work better.
|
||||
*
|
||||
* For a variant field (tagged union), the same macro can be applied to an
|
||||
* object definition. This object definition must be provided as follows:
|
||||
*
|
||||
* {{{
|
||||
* @field object VariantType {
|
||||
* case class V1()
|
||||
* case class V2[T](field1: T)
|
||||
* case class V3[T, Q](field1: T, field2: Q)
|
||||
* // ...
|
||||
* }
|
||||
* }}}
|
||||
*
|
||||
* For this type of definition, the name of the object (here `VariantType`)
|
||||
* defines the type of the variant, and the case classes in the body define
|
||||
* the variant cases. Each case is supplied with subfield accessors, as well
|
||||
* ]as custom unapply methods, where necessary. The fields are nested in the
|
||||
* scope of the variant, so above you would have `VariantType.V1`, for
|
||||
* access.
|
||||
*
|
||||
* The following is a simple definition of a variant component:
|
||||
*
|
||||
* {{{
|
||||
* @field object Shape {
|
||||
* case class Null()
|
||||
* case class App[G <: Graph](fn: Edge[G], argTest: Edge[G])
|
||||
* }
|
||||
* }}}
|
||||
*
|
||||
* This generates the following field definition:
|
||||
*
|
||||
* {{{
|
||||
* sealed trait Shape extends Graph.Component.Field
|
||||
* object Shape {
|
||||
* implicit def sized = new Sized[Shape] { type Out = _3 }
|
||||
*
|
||||
* sealed case class Null() extends Shape
|
||||
* object Null {
|
||||
* val any = Component.VariantMatcher[Shape, Null](0)
|
||||
* implicit def sized = new Sized[Null] { type Out = _0 }
|
||||
* }
|
||||
*
|
||||
* sealed case class App() extends Shape
|
||||
* object App {
|
||||
* implicit def sized = new Sized[App] { type Out = _1 }
|
||||
*
|
||||
* val any = Component.VariantMatcher[Shape, App](1)
|
||||
*
|
||||
* def unapply[G <: Graph, C <: Component](arg: Component.Ref[G, C])(
|
||||
* implicit
|
||||
* graph: GraphData[G],
|
||||
* ev: HasComponentField[G, C, Shape]
|
||||
* ): Option[(Edge[G], Edge[G])] =
|
||||
* any.unapply(arg).map(t => (t.fn, t.arg))
|
||||
*
|
||||
* implicit class AppInstance[G <: Graph, C <: Component](
|
||||
* node: Component.Refined[Shape, App, Component.Ref[G, C]]
|
||||
* ) {
|
||||
*
|
||||
* def fn(
|
||||
* implicit graph: GraphData[G],
|
||||
* ev: HasComponentField[G, C, Shape]
|
||||
* ): Edge[G] = {
|
||||
* Component.Ref(
|
||||
* graph
|
||||
* .unsafeReadField[C, Shape](
|
||||
* Component.Refined.unwrap(node).ix,
|
||||
* 1
|
||||
* )
|
||||
* )
|
||||
* }
|
||||
*
|
||||
* def fn_=(value: Edge[G])(
|
||||
* implicit graph: GraphData[G],
|
||||
* ev: HasComponentField[G, C, Shape]
|
||||
* ): Unit = {
|
||||
* graph.unsafeWriteField[C, Shape](
|
||||
* Component.Refined.unwrap(node).ix,
|
||||
* 1,
|
||||
* value.ix
|
||||
* )
|
||||
* }
|
||||
*
|
||||
* def arg(
|
||||
* implicit graph: GraphData[G],
|
||||
* ev: HasComponentField[G, C, Shape]
|
||||
* ): Edge[G] = {
|
||||
* Component.Ref(
|
||||
* graph
|
||||
* .unsafeReadField[C, Shape](
|
||||
* Component.Refined.unwrap(node).ix,
|
||||
* 2
|
||||
* )
|
||||
* )
|
||||
* }
|
||||
*
|
||||
* def arg_=(value: Edge[G])(
|
||||
* implicit graph: GraphData[G],
|
||||
* ev: HasComponentField[G, C, Shape]
|
||||
* ): Unit = {
|
||||
* graph.unsafeWriteField[C, Shape](
|
||||
* Component.Refined.unwrap(node).ix,
|
||||
* 2,
|
||||
* value.ix
|
||||
* )
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }}}
|
||||
*/
|
||||
@compileTimeOnly("please enable macro paradise to expand macro annotations")
|
||||
class field extends StaticAnnotation {
|
||||
def macroTransform(annottees: Any*): Any = macro FieldMacro.impl
|
||||
}
|
||||
object FieldMacro {
|
||||
def impl(c: whitebox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
|
||||
import c.universe._
|
||||
val members = annottees.map(_.tree).toList
|
||||
|
||||
if (members.size != 1) {
|
||||
c.error(
|
||||
c.enclosingPosition,
|
||||
"You must apply the @field annotation to a single entity"
|
||||
)
|
||||
}
|
||||
|
||||
val imports: Block = Block(
|
||||
List(
|
||||
q"""import shapeless.nat._""",
|
||||
q"""import org.enso.graph.Graph.Component""",
|
||||
q"""import org.enso.graph.Graph.GraphData""",
|
||||
q"""import org.enso.graph.Graph.HasComponentField"""
|
||||
),
|
||||
EmptyTree
|
||||
)
|
||||
|
||||
/** Extracts the template from a type definition.
|
||||
*
|
||||
* The template contains the body of the type definition, as well as the
|
||||
* definition of its constructor.
|
||||
*
|
||||
* @param classDef the class definition
|
||||
* @return the class template, if it exists
|
||||
*/
|
||||
def extractDefTemplate(classDef: ClassDef): Option[Template] = {
|
||||
for (elem <- classDef.children) {
|
||||
elem match {
|
||||
case template: Template => return Some(template)
|
||||
case _ =>
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/** Extracts the constructor arguments from the class definition.
|
||||
*
|
||||
* @param classDef the class definition
|
||||
* @return a list containing the constructor arguments from `classDef`
|
||||
*/
|
||||
def extractConstructorArguments(classDef: ClassDef): List[ValDef] = {
|
||||
val mTemplate = extractDefTemplate(classDef)
|
||||
|
||||
mTemplate match {
|
||||
case Some(template) => {
|
||||
val allVals = template.body.collect {
|
||||
case valDef: ValDef => valDef
|
||||
}
|
||||
|
||||
allVals.filter(
|
||||
t =>
|
||||
t.mods.hasFlag(
|
||||
c.universe.Flag.CASEACCESSOR | c.universe.Flag.PARAMACCESSOR
|
||||
)
|
||||
)
|
||||
}
|
||||
case _ => List()
|
||||
}
|
||||
}
|
||||
|
||||
/** Appends a statement to a block with no return value.
|
||||
*
|
||||
* @param block the block to append to
|
||||
* @param statement the statement to append to `block`
|
||||
* @return `block` with `statement` appended to it
|
||||
*/
|
||||
def appendToBlock(block: Block, statement: Tree*): Block = Block(
|
||||
block.stats ++ statement,
|
||||
EmptyTree
|
||||
)
|
||||
|
||||
/** Creates a constant type-level identifier for Shapeless'
|
||||
* [[shapeless.Nat]] type.
|
||||
*
|
||||
* @param num the natural number you want to represent as a
|
||||
* [[shapeless.Nat]]
|
||||
* @return a [[TypeName]] representing that [[shapeless.Nat]]
|
||||
*/
|
||||
def mkNatConstantTypeName(num: Int): TypeName =
|
||||
TypeName("_" + num.toString)
|
||||
|
||||
/** Generates a getter for an element of a non-variant field.
|
||||
*
|
||||
* @param paramDef the definition of the subfield
|
||||
* @param enclosingTypeName the name of the field type
|
||||
* @param index the index of this subfield in the field
|
||||
* @return a definition for this subfield getter
|
||||
*/
|
||||
def genSimpleSubfieldGetter(
|
||||
paramDef: ValDef,
|
||||
enclosingTypeName: TypeName,
|
||||
index: Int
|
||||
): Tree = {
|
||||
val paramName: TermName = paramDef.name
|
||||
val paramType: Tree = paramDef.tpt
|
||||
|
||||
q"""
|
||||
def $paramName(
|
||||
implicit graph: GraphData[G],
|
||||
ev: HasComponentField[G, C, $enclosingTypeName]
|
||||
): $paramType = {
|
||||
Component.Ref(
|
||||
graph.unsafeReadField[C, $enclosingTypeName](node.ix, $index)
|
||||
)
|
||||
}
|
||||
"""
|
||||
}
|
||||
|
||||
/** Generates a setter for an element of a non-variant field.
|
||||
*
|
||||
* @param paramDef the definition of the subfield
|
||||
* @param enclosingTypeName the name of the field type
|
||||
* @param index the index of this subfield in the field
|
||||
* @return a definition for this subfield setter
|
||||
*/
|
||||
def genSimpleSubfieldSetter(
|
||||
paramDef: ValDef,
|
||||
enclosingTypeName: TypeName,
|
||||
index: Int
|
||||
): Tree = {
|
||||
val accessorName: TermName = TermName(paramDef.name.toString + "_$eq")
|
||||
val paramType: Tree = paramDef.tpt
|
||||
|
||||
q"""
|
||||
def $accessorName(value: $paramType)(
|
||||
implicit graph: GraphData[G],
|
||||
ev: HasComponentField[G, C, $enclosingTypeName]
|
||||
): Unit = {
|
||||
graph.unsafeWriteField[C, $enclosingTypeName](
|
||||
node.ix, $index, value.ix
|
||||
)
|
||||
}
|
||||
"""
|
||||
}
|
||||
|
||||
/** Generates setters and getters for all the subfields for a given
|
||||
* non-variant field.
|
||||
*
|
||||
* @param subfields a list containing the subfield definitions
|
||||
* @param enclosingName the name of the field type
|
||||
* @return the definitions of getters and setters for all the subfields
|
||||
* in `subfields`
|
||||
*/
|
||||
def genSimpleSubfieldAccessors(
|
||||
subfields: List[ValDef],
|
||||
enclosingName: TypeName
|
||||
): List[Tree] = {
|
||||
var accessorDefs: List[Tree] = List()
|
||||
|
||||
for ((subfield, ix) <- subfields.view.zipWithIndex) {
|
||||
accessorDefs = accessorDefs :+ genSimpleSubfieldGetter(
|
||||
subfield,
|
||||
enclosingName,
|
||||
ix
|
||||
)
|
||||
accessorDefs = accessorDefs :+ genSimpleSubfieldSetter(
|
||||
subfield,
|
||||
enclosingName,
|
||||
ix
|
||||
)
|
||||
}
|
||||
|
||||
accessorDefs
|
||||
}
|
||||
|
||||
/** Generates an instance that is used for assisting inference with
|
||||
* refined subfields.
|
||||
*
|
||||
* @param enclosingName the name of the field
|
||||
* @param implicitClassName the name of the instance
|
||||
* @return an instance definition that assists with inference
|
||||
*/
|
||||
def genTransInstance(
|
||||
enclosingName: TermName,
|
||||
implicitClassName: TypeName
|
||||
): Tree = {
|
||||
val defName = TermName(enclosingName.toString + "_transInstance")
|
||||
|
||||
q"""
|
||||
implicit def $defName[
|
||||
F <: Component.Field,
|
||||
R,
|
||||
G <: Graph,
|
||||
C <: Component
|
||||
](
|
||||
t: Component.Refined[F, R, Component.Ref[G, C]]
|
||||
): $implicitClassName[G, C] = t.wrapped
|
||||
"""
|
||||
}
|
||||
|
||||
/** Appends trees to a template definition.
|
||||
*
|
||||
* @param template the template to append to
|
||||
* @param trees the trees to append
|
||||
* @return `template` with `trees` appended to the end of its body
|
||||
*/
|
||||
def appendToTemplate(template: Template, trees: List[Tree]): Template = {
|
||||
Template(
|
||||
template.parents,
|
||||
template.self,
|
||||
template.body ++ trees
|
||||
)
|
||||
}
|
||||
|
||||
/** Appends trees to the body of a class definition.
|
||||
*
|
||||
* @param classDef the definition to append to
|
||||
* @param trees the trees to append
|
||||
* @return `classDef` with `trees` appended to the end of its body
|
||||
*/
|
||||
def appendToClass(classDef: ClassDef, trees: List[Tree]): ClassDef = {
|
||||
ClassDef(
|
||||
classDef.mods,
|
||||
classDef.name,
|
||||
classDef.tparams,
|
||||
appendToTemplate(classDef.impl, trees)
|
||||
)
|
||||
}
|
||||
|
||||
/** Appends trees to the body of a module (object) definition.
|
||||
*
|
||||
* @param module the definition to append to
|
||||
* @param trees the trees to append
|
||||
* @return `module` with `trees` appended to the end of its body
|
||||
*/
|
||||
def appendToModule(module: ModuleDef, trees: List[Tree]): ModuleDef = {
|
||||
ModuleDef(
|
||||
Modifiers(),
|
||||
module.name,
|
||||
appendToTemplate(module.impl, trees)
|
||||
)
|
||||
}
|
||||
|
||||
/** Generates the name for the implicit class containing the accessors.
|
||||
*
|
||||
* @param typeName the name of the field
|
||||
* @return the name of the field's implicit accessor class
|
||||
*/
|
||||
def mkImplicitClassName(typeName: TypeName): TypeName =
|
||||
TypeName(typeName.toString + "Instance")
|
||||
|
||||
/** Generates a set of definitions that correspond to defining a
|
||||
* non-variant field for a graph component.
|
||||
*
|
||||
* @param classDef the class definition to expand
|
||||
* @return a full definition for the field
|
||||
*/
|
||||
def processSingleField(classDef: ClassDef): c.Expr[Any] = {
|
||||
val fieldTypeName: TypeName = classDef.name
|
||||
val fieldTermName: TermName = fieldTypeName.toTermName
|
||||
val subfields: List[ValDef] = extractConstructorArguments(classDef)
|
||||
val natSubfields: TypeName = mkNatConstantTypeName(subfields.length)
|
||||
val implicitClassName: TypeName = mkImplicitClassName(fieldTypeName)
|
||||
|
||||
val baseClass: Tree =
|
||||
q"sealed case class $fieldTypeName() extends Graph.Component.Field"
|
||||
|
||||
val accessorClassStub: ClassDef = q"""
|
||||
implicit class $implicitClassName[G <: Graph, C <: Component](
|
||||
node: Component.Ref[G, C]
|
||||
)
|
||||
""".asInstanceOf[ClassDef]
|
||||
val accessorClass: ClassDef = appendToClass(
|
||||
accessorClassStub,
|
||||
genSimpleSubfieldAccessors(subfields, fieldTypeName)
|
||||
)
|
||||
|
||||
val companionModuleStub: ModuleDef =
|
||||
q"""
|
||||
object $fieldTermName {
|
||||
implicit def sized = new Sized[$fieldTypeName] {
|
||||
type Out = $natSubfields
|
||||
}
|
||||
}
|
||||
""".asInstanceOf[ModuleDef]
|
||||
|
||||
val companionModule =
|
||||
appendToModule(
|
||||
companionModuleStub,
|
||||
List(
|
||||
accessorClass,
|
||||
genTransInstance(fieldTermName, implicitClassName)
|
||||
)
|
||||
)
|
||||
|
||||
val resultBlock =
|
||||
appendToBlock(imports, baseClass, companionModule).stats
|
||||
|
||||
val result = q"..$resultBlock"
|
||||
|
||||
c.Expr(result)
|
||||
}
|
||||
|
||||
/** Generates a getter for an element of a variant
|
||||
*
|
||||
* @param paramDef the definition of the subfield
|
||||
* @param enclosingTypeName the name of the field type
|
||||
* @param index the index of this subfield in the field
|
||||
* @return a definition for this subfield getter
|
||||
*/
|
||||
def genVariantSubfieldGetter(
|
||||
paramDef: ValDef,
|
||||
enclosingTypeName: TypeName,
|
||||
index: Int
|
||||
): Tree = {
|
||||
val paramName: TermName = paramDef.name
|
||||
val paramType: Tree = paramDef.tpt
|
||||
|
||||
q"""
|
||||
def $paramName(
|
||||
implicit graph: GraphData[G],
|
||||
ev: HasComponentField[G, C, $enclosingTypeName]
|
||||
): $paramType = {
|
||||
Component.Ref(
|
||||
graph.unsafeReadField[C, $enclosingTypeName](
|
||||
Component.Refined.unwrap(node).ix,
|
||||
$index
|
||||
)
|
||||
)
|
||||
}
|
||||
"""
|
||||
}
|
||||
|
||||
/** Generates a setter for an element of a variant field.
|
||||
*
|
||||
* @param paramDef the definition of the subfield
|
||||
* @param enclosingTypeName the name of the field type
|
||||
* @param index the index of this subfield in the field
|
||||
* @return a definition for this subfield setter
|
||||
*/
|
||||
def genVariantSubfieldSetter(
|
||||
paramDef: ValDef,
|
||||
enclosingTypeName: TypeName,
|
||||
index: Int
|
||||
): Tree = {
|
||||
val accessorName: TermName = TermName(paramDef.name.toString + "_$eq")
|
||||
val paramType: Tree = paramDef.tpt
|
||||
|
||||
q"""
|
||||
def $accessorName(value: $paramType)(
|
||||
implicit graph: GraphData[G],
|
||||
ev: HasComponentField[G, C, $enclosingTypeName]
|
||||
): Unit = {
|
||||
graph.unsafeWriteField[C, $enclosingTypeName](
|
||||
Component.Refined.unwrap(node).ix,
|
||||
$index,
|
||||
value.ix
|
||||
)
|
||||
}
|
||||
"""
|
||||
}
|
||||
|
||||
/** Generates setters and getters for all the subfields for a given
|
||||
* variant field.
|
||||
*
|
||||
* @param subfields a list containing the subfield definitions
|
||||
* @param enclosingName the name of the field type
|
||||
* @return the definitions of getters and setters for all the subfields
|
||||
* in `subfields`
|
||||
*/
|
||||
def genVariantSubfieldAccessors(
|
||||
subfields: List[ValDef],
|
||||
enclosingName: TypeName
|
||||
): List[Tree] = {
|
||||
var accessorDefs: List[Tree] = List()
|
||||
|
||||
for ((subfield, ix) <- subfields.view.zipWithIndex) {
|
||||
accessorDefs = accessorDefs :+ genVariantSubfieldGetter(
|
||||
subfield,
|
||||
enclosingName,
|
||||
ix
|
||||
)
|
||||
accessorDefs = accessorDefs :+ genVariantSubfieldSetter(
|
||||
subfield,
|
||||
enclosingName,
|
||||
ix
|
||||
)
|
||||
}
|
||||
|
||||
accessorDefs
|
||||
}
|
||||
|
||||
/** Extracts the variant case definitions from the module body.
|
||||
*
|
||||
* @param template the template of the variant defining module
|
||||
* @return a list of the variant cases
|
||||
*/
|
||||
def extractVariantDefs(template: Template): List[ClassDef] = {
|
||||
template.body.collect { case classDef: ClassDef => classDef }
|
||||
}
|
||||
|
||||
/** Generates the definition body for a variant case.
|
||||
*
|
||||
* @param classDef the definition of the variant case
|
||||
* @param parentName the name of the parent variant
|
||||
* @param index the case's index in the variant
|
||||
* @return the expanded defintion for the variant case described by
|
||||
* `classDef`
|
||||
*/
|
||||
def generateVariantCaseDef(
|
||||
classDef: ClassDef,
|
||||
parentName: TypeName,
|
||||
index: Int
|
||||
): (Block, Int) = {
|
||||
val typeName = classDef.name
|
||||
val termName = typeName.toTermName
|
||||
val subfields = extractConstructorArguments(classDef)
|
||||
val natSubfields = mkNatConstantTypeName(subfields.length)
|
||||
val implicitClassName = mkImplicitClassName(typeName)
|
||||
val subfieldTypes = subfields.map(f => f.tpt)
|
||||
|
||||
val variantClass: ClassDef =
|
||||
q"""
|
||||
sealed case class $typeName() extends $parentName
|
||||
""".asInstanceOf[ClassDef]
|
||||
|
||||
val variantModuleStub: ModuleDef =
|
||||
q"""
|
||||
object $termName {
|
||||
val any = Component.VariantMatcher[$parentName, $typeName]($index)
|
||||
implicit def sized = new Sized[$typeName] {
|
||||
type Out = $natSubfields
|
||||
}
|
||||
}
|
||||
""".asInstanceOf[ModuleDef]
|
||||
|
||||
val unapplyDef: DefDef =
|
||||
q"""
|
||||
def unapply[G <: Graph, C <: Component](arg: Component.Ref[G, C])(
|
||||
implicit
|
||||
graph: GraphData[G],
|
||||
ev: HasComponentField[G, C, $parentName]
|
||||
): Option[(..$subfieldTypes)] = {
|
||||
any.unapply(arg).map(
|
||||
t => (..${subfields.map(f => q"t.${f.name}")})
|
||||
)
|
||||
}
|
||||
""".asInstanceOf[DefDef]
|
||||
|
||||
val accessorClassStub: ClassDef = q"""
|
||||
implicit class $implicitClassName[G <: Graph, C <: Component](
|
||||
node: Component.Refined[
|
||||
$parentName,
|
||||
$typeName,
|
||||
Component.Ref[G, C]
|
||||
]
|
||||
)
|
||||
""".asInstanceOf[ClassDef]
|
||||
val accessorClass: ClassDef = appendToClass(
|
||||
accessorClassStub,
|
||||
genVariantSubfieldAccessors(subfields, parentName)
|
||||
)
|
||||
|
||||
val result = if (subfields.nonEmpty) {
|
||||
appendToModule(variantModuleStub, List(unapplyDef, accessorClass))
|
||||
} else {
|
||||
variantModuleStub
|
||||
}
|
||||
|
||||
(
|
||||
q"..${List(variantClass, result)}".asInstanceOf[Block],
|
||||
subfields.length
|
||||
)
|
||||
}
|
||||
|
||||
/** Generates the whole set of definitions necessary for a variant
|
||||
* component field from the provided description.
|
||||
*
|
||||
* @param moduleDef the description of the variant field
|
||||
* @return the expanded defintion of the variant described by `moduleDef`
|
||||
*/
|
||||
def processVariantField(moduleDef: ModuleDef): c.Expr[Any] = {
|
||||
val variantTermName: TermName = moduleDef.name
|
||||
val variantTypeName: TypeName = variantTermName.toTypeName
|
||||
|
||||
val baseTrait: ClassDef =
|
||||
q"""
|
||||
sealed trait $variantTypeName extends Graph.Component.Field
|
||||
""".asInstanceOf[ClassDef]
|
||||
|
||||
val variantDefs = extractVariantDefs(moduleDef.impl)
|
||||
|
||||
if (variantDefs.length < 1) {
|
||||
c.error(
|
||||
c.enclosingPosition,
|
||||
"A variant must contain at least one case"
|
||||
)
|
||||
}
|
||||
|
||||
val variantResults =
|
||||
for ((cls, ix) <- variantDefs.view.zipWithIndex)
|
||||
yield generateVariantCaseDef(cls, variantTypeName, ix)
|
||||
|
||||
// We want to flatten the block structure for correct codegen
|
||||
val variantDefinitions =
|
||||
variantResults.map(_._1).map(t => t.stats).flatten
|
||||
|
||||
// Note [Encoding Variant Size]
|
||||
val numTotalSize = variantResults
|
||||
.map(_._2)
|
||||
.foldLeft(0)((x: Int, y: Int) => Math.max(x, y)) + 1
|
||||
|
||||
val baseModuleStub: ModuleDef =
|
||||
q"""
|
||||
object $variantTermName {
|
||||
implicit def sized = new Sized[$variantTypeName] {
|
||||
type Out = ${mkNatConstantTypeName(numTotalSize)}
|
||||
}
|
||||
}
|
||||
""".asInstanceOf[ModuleDef]
|
||||
|
||||
val traitCompanionModule =
|
||||
appendToModule(baseModuleStub, variantDefinitions.toList)
|
||||
|
||||
val result =
|
||||
q"..${appendToBlock(imports, baseTrait, traitCompanionModule).stats}"
|
||||
|
||||
c.Expr(result)
|
||||
}
|
||||
|
||||
/* Note [Encoding Variant Size]
|
||||
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
* Variant fields are encoded on the graph much like a union is in C. The
|
||||
* space allocation for storing the type is always the maximum size of any
|
||||
* of the variant types.
|
||||
*
|
||||
* However, the encoding used by the graphs operates more like a _tagged_
|
||||
* union, and hence we need to encode the constructor of the variant that
|
||||
* is stored in its allocation.
|
||||
*
|
||||
* To this end, the size of the variant type is _actually_ the maximum
|
||||
* size of all the variant constructors + 1, where that additional
|
||||
* field is used to encode the constructor tag.
|
||||
*/
|
||||
|
||||
members.head match {
|
||||
case classDef: ClassDef => {
|
||||
val modifiers: Modifiers = classDef.mods
|
||||
|
||||
if (!modifiers.hasFlag(c.universe.Flag.CASE)) {
|
||||
c.error(
|
||||
c.enclosingPosition,
|
||||
"@field must be applied to a case class or object"
|
||||
)
|
||||
annottees.head
|
||||
} else {
|
||||
processSingleField(classDef)
|
||||
}
|
||||
}
|
||||
case moduleDef: ModuleDef => {
|
||||
processVariantField(moduleDef)
|
||||
}
|
||||
case _ => {
|
||||
c.error(
|
||||
c.enclosingPosition,
|
||||
"The @field macro only operates on case classes"
|
||||
)
|
||||
annottees.head
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** This macro generates a component definition for the graph to help avoid
|
||||
* the boilerplate necessary to defined a [[org.enso.graph.Graph.Component]].
|
||||
*
|
||||
* You must apply the macro to a definition as follows:
|
||||
*
|
||||
* {{{
|
||||
* @component case class Types() { type Type[G <: Graph] }
|
||||
* }}}
|
||||
*
|
||||
* The macro will generate the required boilerplate for the component
|
||||
* definition. It will generate a parent class named `Types`, and the a
|
||||
* contained type with name taken from the first type definition in the body.
|
||||
* That type must take one type parameter that is a subtype of Graph.
|
||||
*
|
||||
* By way of example, consider the following macro invocation:
|
||||
*
|
||||
* {{{
|
||||
* @component case class Nodes() { type Node[G <: Graph] }
|
||||
* }}}
|
||||
*
|
||||
* This will generate the following code (along with required imports):
|
||||
*
|
||||
* {{{
|
||||
* sealed case class Nodes() extends Component
|
||||
* type Node[G <: Graph] = Component.Ref[G, Nodes]
|
||||
* implicit class GraphWithNodes[G <: Graph](graph: GraphData[G]) {
|
||||
* def addNode()(implicit ev: HasComponent[G, Nodes]): Node[G] = {
|
||||
* graph.addComponent[Nodes]()
|
||||
* }
|
||||
* }
|
||||
* }}}
|
||||
*
|
||||
*/
|
||||
@compileTimeOnly("please enable macro paradise to expand macro annotations")
|
||||
class component extends StaticAnnotation {
|
||||
def macroTransform(annottees: Any*): Any = macro ComponentMacro.impl
|
||||
}
|
||||
object ComponentMacro {
|
||||
def impl(c: whitebox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
|
||||
import c.universe._
|
||||
val members = annottees.map(_.tree).toList
|
||||
|
||||
if (members.size != 1) {
|
||||
c.error(
|
||||
c.enclosingPosition,
|
||||
"You must apply the @component annotation to a single entity"
|
||||
)
|
||||
}
|
||||
|
||||
val imports: Block = Block(
|
||||
List(
|
||||
q"""import org.enso.graph.Graph""",
|
||||
q"""import org.enso.graph.Graph.Component""",
|
||||
q"""import org.enso.graph.Graph.GraphData""",
|
||||
q"""import org.enso.graph.Graph.HasComponent"""
|
||||
),
|
||||
EmptyTree
|
||||
)
|
||||
|
||||
/**
|
||||
* Appends a statement to a block with no return value.
|
||||
*
|
||||
* @param block the block to append to
|
||||
* @param statement the statement to append to `block`
|
||||
* @return `block` with `statement` appended to it
|
||||
*/
|
||||
def appendToBlock(block: Block, statement: Tree*): Block = Block(
|
||||
block.stats ++ statement,
|
||||
EmptyTree
|
||||
)
|
||||
|
||||
/**
|
||||
* Extracts the name of the child type in the definition. This name is
|
||||
* used to define the type contained in a given component.
|
||||
*
|
||||
* If multiple `type T` definitions are found, the first will be used.
|
||||
*
|
||||
* @param template the body of the class the macro is applied to
|
||||
* @return the name of the type declared in the body of `template`
|
||||
*/
|
||||
def extractItemName(template: Template): TypeName = {
|
||||
val typeDefs = template.body.collect { case tDef: TypeDef => tDef }
|
||||
val boundName = TypeName("Graph")
|
||||
|
||||
if (typeDefs.isEmpty) {
|
||||
c.error(
|
||||
c.enclosingPosition,
|
||||
"You must provide a name for the contained type, none found"
|
||||
)
|
||||
TypeName("ERROR")
|
||||
} else {
|
||||
val tDef = typeDefs.head
|
||||
val typeParams = tDef.children.collect {
|
||||
case typeDef: TypeDef => typeDef
|
||||
}
|
||||
|
||||
if (typeParams.length == 1) {
|
||||
val boundNames = typeParams.head.children
|
||||
.collect {
|
||||
case tree: TypeBoundsTree => tree
|
||||
}
|
||||
.map(_.hi)
|
||||
.collect {
|
||||
case Ident(name) => name.toTypeName
|
||||
}
|
||||
|
||||
if (boundNames.contains(boundName)) {
|
||||
tDef.name
|
||||
} else {
|
||||
c.error(
|
||||
c.enclosingPosition,
|
||||
"The contained type's parameter must be a subtype of Graph"
|
||||
)
|
||||
tDef.name
|
||||
}
|
||||
} else {
|
||||
c.error(
|
||||
c.enclosingPosition,
|
||||
"Your contained type must only have one type parameter"
|
||||
)
|
||||
tDef.name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Generates a graph component from the provided description.
|
||||
*
|
||||
* @param classDef a description of the variant component
|
||||
* @return the expanded form of the variant described by `classDef`
|
||||
*/
|
||||
def genComponentFromClassDef(classDef: ClassDef): c.Expr[Any] = {
|
||||
if (!classDef.mods.hasFlag(Flag.CASE)) {
|
||||
c.error(
|
||||
c.enclosingPosition,
|
||||
"@component must be applied to a case class"
|
||||
)
|
||||
}
|
||||
|
||||
val componentTypeName = classDef.name
|
||||
val componentItemName = extractItemName(classDef.impl)
|
||||
|
||||
val caseClass: ClassDef =
|
||||
q"""
|
||||
sealed case class $componentTypeName() extends Component
|
||||
""".asInstanceOf[ClassDef]
|
||||
|
||||
val typeDef: TypeDef =
|
||||
q"""
|
||||
type $componentItemName[G <: Graph] =
|
||||
Component.Ref[G, $componentTypeName]
|
||||
""".asInstanceOf[TypeDef]
|
||||
|
||||
val implClassName = TypeName("GraphWith" + componentTypeName.toString)
|
||||
val addName = TermName("add" + componentItemName.toString)
|
||||
val implicitClass: ClassDef =
|
||||
q"""
|
||||
implicit class $implClassName[G <: Graph](graph: GraphData[G]) {
|
||||
def $addName()(
|
||||
implicit ev: HasComponent[G, $componentTypeName]
|
||||
): $componentItemName[G] = {
|
||||
graph.addComponent[$componentTypeName]()
|
||||
}
|
||||
}
|
||||
""".asInstanceOf[ClassDef]
|
||||
|
||||
val block = appendToBlock(imports, caseClass, typeDef, implicitClass)
|
||||
val result = q"..${block.stats}"
|
||||
|
||||
c.Expr(result)
|
||||
}
|
||||
|
||||
val annotatedItem = members.head
|
||||
|
||||
annotatedItem match {
|
||||
case classDef: ClassDef => genComponentFromClassDef(classDef)
|
||||
case _ => {
|
||||
c.error(
|
||||
c.enclosingPosition,
|
||||
"You must provide a class definition to the @component macro"
|
||||
)
|
||||
c.Expr(annotatedItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package org.enso.graph
|
||||
|
||||
import org.enso.graph.definition.Macro.component
|
||||
import org.scalatest.{FlatSpec, Matchers}
|
||||
import shapeless.test.illTyped
|
||||
|
||||
class ComponentMacroTest extends FlatSpec with Matchers {
|
||||
|
||||
"The `@component` macro" should "define correct components" in {
|
||||
"@component case class Nodes() { type Node[G <: Graph] }" should compile
|
||||
"@component case class Edges() { type Edge[G <: Graph] }" should compile
|
||||
}
|
||||
|
||||
"The `@component` macro child type" should "be accessible" in {
|
||||
"@component case class Nodes() { type Node[G <: Graph] }\n type Test = Node[Graph]" should compile
|
||||
}
|
||||
|
||||
"The `@component` macro child type" must "error when its type parameter does not subclass graph" in {
|
||||
illTyped(
|
||||
"@component case class Nodes() { type Node[G] }",
|
||||
"The contained type's parameter must be a subtype of Graph"
|
||||
)
|
||||
}
|
||||
|
||||
"The `@component` child type" must "not allow more than one type parameter" in {
|
||||
illTyped(
|
||||
"@component case class Nodes() { type Node[G <: Graph, C] }",
|
||||
"Your contained type must only have one type parameter"
|
||||
)
|
||||
}
|
||||
|
||||
"The `@component` macro" should "only allow application to case classes" in {
|
||||
illTyped (
|
||||
"@component object Test",
|
||||
"You must provide a class definition to the @component macro"
|
||||
)
|
||||
illTyped (
|
||||
"@component class Foo()",
|
||||
"@component must be applied to a case class"
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
package org.enso.graph
|
||||
|
||||
import org.enso.graph.definition.Macro.{component, field}
|
||||
import org.scalatest.{FlatSpec, Matchers}
|
||||
import shapeless.test.illTyped
|
||||
|
||||
class FieldMacroTest extends FlatSpec with Matchers {
|
||||
|
||||
// == Components for Tests ==================================================
|
||||
@component case class Nodes() { type Node[G <: Graph] }
|
||||
@component case class Edges() { type Edge[G <: Graph] }
|
||||
|
||||
"The `@field` macro" should "work for single fields" in {
|
||||
"@field case class Shape[G <: Graph](source: Node[G], target: Node[G])" should compile
|
||||
}
|
||||
|
||||
"The `@field` macro" should "work for variant fields" in {
|
||||
"@field object Shape {\n case class Null()\n case class App[G <: Graph](fn: Edge[G], argTest: Edge[G])}" should compile
|
||||
}
|
||||
|
||||
"Access to companion objects for fields" should "work as expected" in {
|
||||
"@field object Shape {\n case class Null()\n case class App[G <: Graph](fn: Edge[G], argTest: Edge[G])}\nval test = Shape.Null.any" should compile
|
||||
}
|
||||
|
||||
"The `@field` macro" should "not allow application to non case classes" in {
|
||||
illTyped(
|
||||
"@field class Shape",
|
||||
"@field must be applied to a case class or object"
|
||||
)
|
||||
}
|
||||
|
||||
"The `@field` macro" should "not allow application to invalid constructs" in {
|
||||
illTyped(
|
||||
"@field type foo",
|
||||
"The @field macro only operates on case classes"
|
||||
)
|
||||
}
|
||||
|
||||
"The `@field` macro" should "error on variants without branches" in {
|
||||
illTyped(
|
||||
"@field object Shape{}",
|
||||
"A variant must contain at least one case"
|
||||
)
|
||||
}
|
||||
}
|
126
common/scala/graph/src/test/scala/org/enso/graph/GraphTest.scala
Normal file
126
common/scala/graph/src/test/scala/org/enso/graph/GraphTest.scala
Normal file
@ -0,0 +1,126 @@
|
||||
package org.enso.graph
|
||||
|
||||
import org.enso.graph.Graph.Component
|
||||
import org.enso.graph.definition.Macro.{component, field}
|
||||
import org.scalatest.{FlatSpec, Matchers}
|
||||
import shapeless.{::, HNil}
|
||||
|
||||
class GraphTest extends FlatSpec with Matchers {
|
||||
object GraphImpl {
|
||||
|
||||
// ========================================================================
|
||||
// === Component Definitions ==============================================
|
||||
// ========================================================================
|
||||
|
||||
// === Node ===
|
||||
@component case class Nodes() { type Node[G <: Graph] }
|
||||
|
||||
// === Edge ===
|
||||
@component case class Edges() { type Edge[G <: Graph] }
|
||||
|
||||
// ========================================================================
|
||||
// === Component Field Definitions ========================================
|
||||
// ========================================================================
|
||||
|
||||
object Node {
|
||||
|
||||
// === Node Shape ===
|
||||
@field object Shape {
|
||||
case class Null()
|
||||
case class App[G <: Graph](fn: Edge[G], argTest: Edge[G])
|
||||
}
|
||||
|
||||
// === ParentLink ===
|
||||
@field case class ParentLink[G <: Graph](parent: Edge[G])
|
||||
}
|
||||
|
||||
object Edge {
|
||||
|
||||
// === Edge Shape ===
|
||||
@field case class Shape[G <: Graph](source: Node[G], target: Node[G])
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// === Example Graph Implementation =======================================
|
||||
// ========================================================================
|
||||
|
||||
case class MyGraph() extends Graph
|
||||
|
||||
implicit def components = new Graph.Component.List[MyGraph] {
|
||||
type Out = Nodes :: Edges :: HNil
|
||||
}
|
||||
|
||||
implicit def nodeFields = new Graph.Component.Field.List[MyGraph, Nodes] {
|
||||
type Out = Node.Shape :: Node.ParentLink :: HNil
|
||||
}
|
||||
|
||||
implicit def edgeFields = new Graph.Component.Field.List[MyGraph, Edges] {
|
||||
type Out = Edge.Shape :: HNil
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// === Example Graph Usage ==================================================
|
||||
// ==========================================================================
|
||||
|
||||
import GraphImpl.Edge.Shape._
|
||||
import GraphImpl.Node.ParentLink._
|
||||
import GraphImpl.Node.Shape.App._
|
||||
import GraphImpl._
|
||||
|
||||
implicit val graph = Graph[GraphImpl.MyGraph]();
|
||||
|
||||
val n1: Node[MyGraph] = graph.addNode()
|
||||
val n2: Node[MyGraph] = graph.addNode()
|
||||
val n3: Node[MyGraph] = graph.addNode()
|
||||
|
||||
val e1: Edge[MyGraph] = graph.addEdge()
|
||||
e1.source = n1
|
||||
e1.target = n2
|
||||
|
||||
n1.parent = Component.Ref(1)
|
||||
n2.parent = Component.Ref(2)
|
||||
n3.parent = Component.Ref(3)
|
||||
|
||||
// This is just dirty and very unsafe way of changing `n1` to be App!
|
||||
graph.unsafeWriteField[Nodes, GraphImpl.Node.Shape](n1.ix, 0, 1)
|
||||
|
||||
// ==========================================================================
|
||||
// === Tests ================================================================
|
||||
// ==========================================================================
|
||||
|
||||
"Matching on variants" should "work properly" in {
|
||||
val typeResult = n1 match {
|
||||
case GraphImpl.Node.Shape.Null.any(n @ _) => "Null"
|
||||
case GraphImpl.Node.Shape.App.any(n1 @ _) => "App1"
|
||||
case GraphImpl.Node.Shape.App(_, _) => "App2"
|
||||
}
|
||||
|
||||
typeResult shouldEqual "App1"
|
||||
}
|
||||
|
||||
"Matching on variants" should "refine the variant type" in {
|
||||
val refinedResult = n1 match {
|
||||
case GraphImpl.Node.Shape.App.any(n1) => n1.fn
|
||||
}
|
||||
|
||||
refinedResult shouldEqual 1
|
||||
}
|
||||
|
||||
"Component fields" can "be accessed properly" in {
|
||||
e1.source shouldEqual n1
|
||||
e1.target shouldEqual n2
|
||||
}
|
||||
|
||||
"The graph" should "be mutable" in {
|
||||
val e2: Edge[MyGraph] = graph.addEdge()
|
||||
e2.source = n1
|
||||
e2.target = n2
|
||||
|
||||
e2.source shouldEqual n1
|
||||
|
||||
e2.source = n3
|
||||
|
||||
e2.source shouldEqual n3
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package org.enso.graph
|
||||
|
||||
import shapeless.{::, HNil}
|
||||
import shapeless.Nat._
|
||||
|
||||
object TypeFunctionTest {
|
||||
|
||||
object HListSumTest {
|
||||
implicitly[HListSum.Aux[HNil, _0]]
|
||||
implicitly[HListSum.Aux[_1 :: HNil, _1]]
|
||||
implicitly[HListSum.Aux[_1 :: _2 :: HNil, _3]]
|
||||
implicitly[HListSum.Aux[_1 :: _2 :: _3 :: HNil, _6]]
|
||||
}
|
||||
|
||||
object HListTakeUntilTest {
|
||||
case class A()
|
||||
case class B()
|
||||
case class C()
|
||||
implicitly[HListTakeUntil.Aux[A, HNil, HNil]]
|
||||
implicitly[HListTakeUntil.Aux[A, A :: B :: C :: HNil, HNil]]
|
||||
implicitly[HListTakeUntil.Aux[B, A :: B :: C :: HNil, A :: HNil]]
|
||||
implicitly[HListTakeUntil.Aux[C, A :: B :: C :: HNil, A :: B :: HNil]]
|
||||
}
|
||||
|
||||
object MapSizedTest {
|
||||
case class A()
|
||||
case class B()
|
||||
case class C()
|
||||
implicit def sizedA = new Sized[A] { type Out = _1 }
|
||||
implicit def sizedB = new Sized[B] { type Out = _3 }
|
||||
implicit def sizedC = new Sized[C] { type Out = _5 }
|
||||
implicitly[MapSized.Aux[HNil, HNil]]
|
||||
implicitly[MapSized.Aux[A :: B :: C :: HNil, _1 :: _3 :: _5 :: HNil]]
|
||||
}
|
||||
|
||||
object SizeUntilTest {
|
||||
case class A()
|
||||
case class B()
|
||||
case class C()
|
||||
implicit def sizedA = new Sized[A] { type Out = _1 }
|
||||
implicit def sizedB = new Sized[B] { type Out = _3 }
|
||||
implicit def sizedC = new Sized[C] { type Out = _5 }
|
||||
implicitly[SizeUntil.Aux[A, HNil, _0]]
|
||||
implicitly[SizeUntil.Aux[A, A :: B :: C :: HNil, _0]]
|
||||
implicitly[SizeUntil.Aux[B, A :: B :: C :: HNil, _1]]
|
||||
implicitly[SizeUntil.Aux[C, A :: B :: C :: HNil, _4]]
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@ package org.enso.compiler
|
||||
import com.oracle.truffle.api.TruffleFile
|
||||
import com.oracle.truffle.api.source.Source
|
||||
import org.enso.compiler.generate.AstToIr
|
||||
import org.enso.compiler.ir.IR
|
||||
import org.enso.compiler.core.IR
|
||||
import org.enso.flexer.Reader
|
||||
import org.enso.interpreter.AstExpression
|
||||
import org.enso.interpreter.Constants
|
||||
@ -17,8 +17,7 @@ import org.enso.interpreter.runtime.Module
|
||||
import org.enso.interpreter.runtime.error.ModuleDoesNotExistException
|
||||
import org.enso.interpreter.runtime.scope.LocalScope
|
||||
import org.enso.interpreter.runtime.scope.ModuleScope
|
||||
import org.enso.syntax.text.AST
|
||||
import org.enso.syntax.text.Parser
|
||||
import org.enso.syntax.text.{AST, Parser}
|
||||
|
||||
import scala.collection.JavaConverters._
|
||||
import scala.collection.mutable
|
||||
@ -46,6 +45,11 @@ class Compiler(
|
||||
* executable functionality in the module corresponding to `source`.
|
||||
*/
|
||||
def run(source: Source, scope: ModuleScope): ExpressionNode = {
|
||||
/* TODO [AA] Introduce this next task
|
||||
* val parsedAST: AST = parse(source)
|
||||
* val convertedIR: ExpressionNode = translate(parsedAST)
|
||||
*/
|
||||
|
||||
val parsed =
|
||||
new EnsoParser().parseEnso(source.getCharacters.toString)
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
package org.enso.compiler.ir
|
||||
package org.enso.compiler.core
|
||||
|
||||
import org.enso.compiler.ir.IR.Literal.Text
|
||||
import org.enso.compiler.core.IR.Literal.Text
|
||||
import org.enso.syntax.text.AST
|
||||
import org.enso.syntax.text.ast.text.Escape
|
||||
|
@ -1,7 +1,7 @@
|
||||
package org.enso.compiler.generate
|
||||
|
||||
import org.enso.compiler.exception.UnhandledEntity
|
||||
import org.enso.compiler.ir.IR
|
||||
import org.enso.compiler.core.IR
|
||||
import org.enso.syntax.text.AST
|
||||
|
||||
/**
|
||||
|
@ -1,5 +0,0 @@
|
||||
package org.enso.compiler.generate
|
||||
|
||||
class IrToTruffle {
|
||||
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
package org.enso.interpreter.analysis
|
||||
|
||||
class Pass {
|
||||
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
package org.enso.interpreter.analysis
|
||||
|
||||
class StaticAnalyser {
|
||||
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
package org.enso.interpreter.analysis.desugar
|
||||
|
||||
class DataTypeDefinition {
|
||||
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
package org.enso.interpreter.analysis.desugar
|
||||
|
||||
class DesugarPhase {
|
||||
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
package org.enso.interpreter.analysis.desugar
|
||||
|
||||
class NestedPatternMatch {
|
||||
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
package org.enso.interpreter.analysis.desugar
|
||||
|
||||
class UnderscoreToLambda {
|
||||
|
||||
}
|
@ -1 +1,2 @@
|
||||
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.10")
|
||||
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.10")
|
||||
addSbtPlugin("ch.epfl.scala" % "sbt-bloop" % "1.3.4")
|
Loading…
Reference in New Issue
Block a user