Add a low-level graph library (#334)

This commit is contained in:
Ara Adkins 2019-11-18 11:18:16 +00:00 committed by GitHub
parent 0ec41b5bbd
commit 22aa4efda8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 1883 additions and 88 deletions

1
.gitignore vendored
View File

@ -92,3 +92,4 @@ bench-report.xml
#########
.editorconfig
.bloop

View File

@ -63,10 +63,10 @@ verticalMultiline.excludeDanglingParens = [
// Rewrite Rules
rewrite.rules = [
ExpandImportSelectors,
PreferCurlyFors,
RedundantParens,
SortModifiers,
SortImports,
]
rewrite.sortModifiers.order = [
"implicit", "final", "sealed", "abstract",

128
build.sbt
View File

@ -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,7 +118,10 @@ val monocle = {
}
val cats = {
Seq(
"org.typelevel" %% "cats-core" % "2.0.0-RC1",
"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)

View 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
}
}
}

View File

@ -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)
}
}
}
}
}

View File

@ -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"
)
}
}

View File

@ -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"
)
}
}

View 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
}
}

View File

@ -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]]
}
}

View File

@ -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)

View File

@ -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

View File

@ -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
/**

View File

@ -1,5 +0,0 @@
package org.enso.compiler.generate
class IrToTruffle {
}

View File

@ -1,5 +0,0 @@
package org.enso.interpreter.analysis
class Pass {
}

View File

@ -1,5 +0,0 @@
package org.enso.interpreter.analysis
class StaticAnalyser {
}

View File

@ -1,5 +0,0 @@
package org.enso.interpreter.analysis.desugar
class DataTypeDefinition {
}

View File

@ -1,5 +0,0 @@
package org.enso.interpreter.analysis.desugar
class DesugarPhase {
}

View File

@ -1,5 +0,0 @@
package org.enso.interpreter.analysis.desugar
class NestedPatternMatch {
}

View File

@ -1,5 +0,0 @@
package org.enso.interpreter.analysis.desugar
class UnderscoreToLambda {
}

View File

@ -1 +1,2 @@
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.10")
addSbtPlugin("ch.epfl.scala" % "sbt-bloop" % "1.3.4")