Skip to content

Commit

Permalink
Organized source files and imports
Browse files Browse the repository at this point in the history
  • Loading branch information
propensive committed Jun 8, 2024
1 parent cd03219 commit 116b0da
Show file tree
Hide file tree
Showing 7 changed files with 158 additions and 76 deletions.
30 changes: 13 additions & 17 deletions src/core/dag.scala → src/core/acyclicity.Dag.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,11 @@

package acyclicity

import rudiments.*
import anticipation.*
import language.experimental.captureChecking

import scala.collection.mutable.HashMap

import language.experimental.captureChecking
import rudiments.*

object Dag:
@targetName("apply2")
Expand All @@ -35,20 +34,20 @@ object Dag:
val key = todo.head
dependencies(key).pipe: children =>
recur(map.updated(key, children), (todo ++ children.filter(!done(_))) - key, done + key)

recur(Map(), Set(start), Set())

@targetName("fromEdges")
def apply[NodeType](edges: (NodeType, NodeType)*): Dag[NodeType] = Dag:
edges.foldLeft(Map[NodeType, Set[NodeType]]()):
case (acc, (key, value)) => acc.updated(key, acc.get(key).fold(Set(value))(_ + value))

@targetName("fromNodes")
def apply[NodeType](nodes: (NodeType, Set[NodeType])*): Dag[NodeType] = Dag(Map(nodes*))

case class Dag[NodeType] private(edgeMap: Map[NodeType, Set[NodeType]] = Map()):
private val reachableCache: HashMap[NodeType, Set[NodeType]] = HashMap()

def keys: Set[NodeType] = edgeMap.keySet
def map[NodeType2](lambda: NodeType => NodeType2): Dag[NodeType2] = Dag(edgeMap.map { case (k, v) => (lambda(k), v.map(lambda)) })
def subgraph(keep: Set[NodeType]): Dag[NodeType] = (keys &~ keep).foldLeft(this)(_.remove(_))
Expand All @@ -57,13 +56,13 @@ case class Dag[NodeType] private(edgeMap: Map[NodeType, Set[NodeType]] = Map()):

@targetName("removeKey")
infix def -(key: NodeType): Dag[NodeType] = Dag(edgeMap - key)

def sources: Set[NodeType] = edgeMap.collect { case (k, v) if v.isEmpty => k }.to(Set)
def edges: Set[(NodeType, NodeType)] = edgeMap.to(Set).flatMap { (k, vs) => vs.map(k -> _) }
def closure: Dag[NodeType] = Dag(keys.map { k => k -> (reachable(k) - k) }.to(Map))
def sorted: List[NodeType] = sort(edgeMap, Nil).reverse
def hasCycle(start: NodeType): Boolean = findCycle(start).isDefined

def remove(key: NodeType, value: NodeType): Dag[NodeType] =
Dag(edgeMap.updated(key, edgeMap.get(key).fold(Set())(_ - value)))

Expand All @@ -77,7 +76,7 @@ case class Dag[NodeType] private(edgeMap: Map[NodeType, Set[NodeType]] = Map()):
infix def ++(dag: Dag[NodeType]): Dag[NodeType] =
val joined = edgeMap.to(List) ++ dag.edgeMap.to(List)
Dag(joined.groupBy(_._1).view.mapValues(_.flatMap(_._2).to(Set)).to(Map))

def add(key: NodeType, value: NodeType): Dag[NodeType] = this ++ Dag(key -> value)

def flatMap[NodeType2](lambda: NodeType => Dag[NodeType2]): Dag[NodeType2] = Dag:
Expand All @@ -89,7 +88,7 @@ case class Dag[NodeType] private(edgeMap: Map[NodeType, Set[NodeType]] = Map()):
def reduction: Dag[NodeType] =
val allEdges = closure.edgeMap
val removals = for i <- keys; j <- edgeMap(i); k <- edgeMap(j) if allEdges(i)(k) yield (i, k)

Dag:
removals.foldLeft(edgeMap):
case (m, (k, v)) => m.updated(k, m(k) - v)
Expand All @@ -108,7 +107,7 @@ case class Dag[NodeType] private(edgeMap: Map[NodeType, Set[NodeType]] = Map()):
(edgeMap - elem).view.mapValues:
map => if map(elem) then map ++ edgeMap(elem) - elem else map
.to(Map)

private def sort(todo: Map[NodeType, Set[NodeType]], done: List[NodeType]): List[NodeType] =
if todo.isEmpty then done
else
Expand All @@ -118,7 +117,7 @@ case class Dag[NodeType] private(edgeMap: Map[NodeType, Set[NodeType]] = Map()):
def filter(pred: NodeType => Boolean): Dag[NodeType] =
val deletions = keys.filter(!pred(_))
val inverted = invert

Dag:
deletions.foldLeft(edgeMap) { (acc, next) =>
inverted(next).foldLeft(acc):
Expand All @@ -134,12 +133,9 @@ case class Dag[NodeType] private(edgeMap: Map[NodeType, Set[NodeType]] = Map()):
trace.to(Set).intersect(apply(vertex)).headOption match
case Some(element) =>
Some(trace ++ List(vertex, element))

case None =>
val queue = tail ++ apply(vertex).diff(finished).toList.map((_, trace :+ vertex))
recur(queue, finished + vertex)

recur(List((start, List())), Set())

extension (dag: Dag[Text])
def dot: Dot = Digraph(dag.edges.to(List).map(Dot.Ref(_) --> Dot.Ref(_))*)
24 changes: 24 additions & 0 deletions src/core/acyclicity.Digraph.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
Acyclicity, version [unreleased]. Copyright 2024 Jon Pretty, Propensive OÜ.
The primary distribution site is: https://propensive.com/
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the
License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
either express or implied. See the License for the specific language governing permissions
and limitations under the License.
*/

package acyclicity

import language.experimental.captureChecking

object Digraph:
def apply(id: Dot.Id, statements: Dot.Statement*): Dot = Dot.Digraph(Some(id), false, statements*)
def apply(statements: Dot.Statement*): Dot = Dot.Digraph(None, false, statements*)
def strict(id: Dot.Id, statements: Dot.Statement*): Dot = Dot.Digraph(Some(id), true, statements*)
75 changes: 17 additions & 58 deletions src/core/dot.scala → src/core/acyclicity.Dot.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,14 @@

package acyclicity

import language.dynamics
import language.experimental.captureChecking

import anticipation.*
import contextual.*
import gossamer.*
import rudiments.*
import fulminate.*
import anticipation.*
import spectacular.*
import gossamer.*

import language.dynamics
import language.experimental.captureChecking

enum Dot:
case Graph(id: Option[Dot.Id], strict: Boolean, statements: Dot.Statement*)
Expand All @@ -35,7 +34,7 @@ enum Dot:
def add(additions: Dot.Statement*): Dot = this match
case Dot.Graph(id, strict, statements*) =>
Dot.Graph(id, strict, (statements ++ additions)*)

case Dot.Digraph(id, strict, statements*) =>
Dot.Digraph(id, strict, (statements ++ additions)*)

Expand All @@ -46,14 +45,14 @@ object Dot:
object Attachment:
// FIXME: This needs to include the port
given Show[Attachment] = _.id.key

case class Attachment(id: Id, compass: Option[CompassPoint] = None)

case class Ref(id: Id, port: Option[Attachment] = None):
@targetName("joinTo")
infix def --(dest: Ref | Statement.Subgraph): Dot.Statement.Edge =
Dot.Statement.Edge(this, Target(false, dest, None))

@targetName("mapTo")
infix def -->(dest: Ref | Statement.Subgraph): Dot.Statement.Edge =
Dot.Statement.Edge(this, Target(true, dest, None))
Expand All @@ -64,13 +63,13 @@ object Dot:
case class Id(key: Text) extends Dynamic:
def applyDynamicNamed(method: "apply")(attrs: (String, Text)*) =
Statement.Node(this, attrs.map { (k, v) => Property(k.show, v) }*)

@targetName("assign")
infix def :=(id: Id): Statement.Assignment = Statement.Assignment(this, id)

enum CompassPoint:
case North, South, East, West, NorthEast, NorthWest, SouthEast, SouthWest

enum Statement:
case Node(id: Id, attrs: Property*)
case Edge(id: Ref, rhs: Target, attrs: Property*)
Expand Down Expand Up @@ -105,10 +104,10 @@ object Dot:
private def tokenize(graph: Ref | Dot | Target | Statement | Property): LazyList[Text] = graph match
case Ref(id, port) =>
LazyList(port.fold(t"\"${id.key}\"") { p => t"\"${id.key}:$p\"" })

case Property(key, value) =>
LazyList(t"$key=\"$value\"")

case Target(directed, dest, link) =>
val op = if directed then t"->" else t"--"
op #:: tokenize(dest) #::: link.to(LazyList).flatMap(tokenize(_)) #::: LazyList(t";")
Expand All @@ -117,25 +116,25 @@ object Dot:
t"\"${id.key}\"" #:: (if attrs.isEmpty then LazyList() else (LazyList(t"[") #:::
attrs.to(LazyList).flatMap(tokenize(_) :+ t",").init #::: LazyList(t"]"))) #:::
LazyList(t";")

case Statement.Edge(id, rhs, attrs*) =>
tokenize(id) #::: tokenize(rhs)

case Statement.Assignment(id, id2) =>
LazyList(t"\"${id.key}\"", t"=", t"\"${id2.key}\"", t";")

case Statement.Subgraph(id, statements*) =>
t"subgraph" #:: id.to(LazyList).map(_.key) #::: t"{" #::
statements.to(LazyList).flatMap(tokenize(_)) #::: LazyList(t"}")

case Dot.Graph(id, strict, statements*) =>
LazyList(
if strict then LazyList(t"strict") else LazyList(),
LazyList(t"graph"),
id.to(LazyList).map(_.key), LazyList(t"{"),
statements.flatMap(tokenize(_)), LazyList(t"}")
).flatten

case Dot.Digraph(id, strict, statements*) =>
LazyList(
if strict then LazyList(t"strict") else LazyList(),
Expand All @@ -145,43 +144,3 @@ object Dot:
statements.flatMap(tokenize(_)),
LazyList(t"}")
).flatten

object Digraph:
def apply(id: Dot.Id, statements: Dot.Statement*): Dot = Dot.Digraph(Some(id), false, statements*)
def apply(statements: Dot.Statement*): Dot = Dot.Digraph(None, false, statements*)
def strict(id: Dot.Id, statements: Dot.Statement*): Dot = Dot.Digraph(Some(id), true, statements*)

object Graph:
def apply(id: Dot.Id, statements: Dot.Statement*): Dot = Dot.Graph(Some(id), false, statements*)
def strict(id: Dot.Id, statements: Dot.Statement*): Dot = Dot.Graph(Some(id), true, statements*)

object Subgraph:
def apply(id: Dot.Id, statements: Dot.Statement*): Dot.Statement.Subgraph =
Dot.Statement.Subgraph(Some(id), statements*)

def apply(statements: Dot.Statement*): Dot.Statement.Subgraph =
Dot.Statement.Subgraph(None, statements*)

object NodeParser extends Interpolator[Unit, Option[Dot.Ref], Dot.Ref]:

private val compassPoints: Set[Text] = Set(t"n", t"e", t"s", t"w", t"ne", t"nw", t"se", t"sw")
def parse(state: Option[Dot.Ref], next: Text): Some[Dot.Ref] =
Some { next.show.cut(t":").to(List) match
case List(id) =>
Dot.Ref(Dot.Id(id))

case List(id, port) =>
Dot.Ref(Dot.Id(id), Some(Dot.Attachment(Dot.Id(port.show))))

case List(id, port, point) if compassPoints.contains(point) =>
Dot.Ref(Dot.Id(id), Some(Dot.Attachment(Dot.Id(port),
Some(Dot.CompassPoint.valueOf(point.capitalize.s)))))

case _ =>
throw InterpolationError(msg"not a valid node ID")
}

def initial: Option[Dot.Ref] = None
def complete(value: Option[Dot.Ref]): Dot.Ref = value.get
def skip(state: Option[Dot.Ref]): Option[Dot.Ref] = state
def insert(state: Option[Dot.Ref], value: Unit): Option[Dot.Ref] = state
23 changes: 23 additions & 0 deletions src/core/acyclicity.Graph.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
Acyclicity, version [unreleased]. Copyright 2024 Jon Pretty, Propensive OÜ.
The primary distribution site is: https://propensive.com/
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the
License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
either express or implied. See the License for the specific language governing permissions
and limitations under the License.
*/

package acyclicity

import language.experimental.captureChecking

object Graph:
def apply(id: Dot.Id, statements: Dot.Statement*): Dot = Dot.Graph(Some(id), false, statements*)
def strict(id: Dot.Id, statements: Dot.Statement*): Dot = Dot.Graph(Some(id), true, statements*)
49 changes: 49 additions & 0 deletions src/core/acyclicity.NodeParser.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
Acyclicity, version [unreleased]. Copyright 2024 Jon Pretty, Propensive OÜ.
The primary distribution site is: https://propensive.com/
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the
License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
either express or implied. See the License for the specific language governing permissions
and limitations under the License.
*/

package acyclicity

import language.experimental.captureChecking

import anticipation.*
import contextual.*
import fulminate.*
import gossamer.*
import rudiments.*
import spectacular.*

object NodeParser extends Interpolator[Unit, Option[Dot.Ref], Dot.Ref]:
private val compassPoints: Set[Text] = Set(t"n", t"e", t"s", t"w", t"ne", t"nw", t"se", t"sw")
def parse(state: Option[Dot.Ref], next: Text): Some[Dot.Ref] =
Some { next.show.cut(t":").to(List) match
case List(id) =>
Dot.Ref(Dot.Id(id))

case List(id, port) =>
Dot.Ref(Dot.Id(id), Some(Dot.Attachment(Dot.Id(port.show))))

case List(id, port, point) if compassPoints.contains(point) =>
Dot.Ref(Dot.Id(id), Some(Dot.Attachment(Dot.Id(port),
Some(Dot.CompassPoint.valueOf(point.capitalize.s)))))

case _ =>
throw InterpolationError(msg"not a valid node ID")
}

def initial: Option[Dot.Ref] = None
def complete(value: Option[Dot.Ref]): Dot.Ref = value.get
def skip(state: Option[Dot.Ref]): Option[Dot.Ref] = state
def insert(state: Option[Dot.Ref], value: Unit): Option[Dot.Ref] = state
26 changes: 26 additions & 0 deletions src/core/acyclicity.Subgraph.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
Acyclicity, version [unreleased]. Copyright 2024 Jon Pretty, Propensive OÜ.
The primary distribution site is: https://propensive.com/
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the
License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
either express or implied. See the License for the specific language governing permissions
and limitations under the License.
*/

package acyclicity

import language.experimental.captureChecking

object Subgraph:
def apply(id: Dot.Id, statements: Dot.Statement*): Dot.Statement.Subgraph =
Dot.Statement.Subgraph(Some(id), statements*)

def apply(statements: Dot.Statement*): Dot.Statement.Subgraph =
Dot.Statement.Subgraph(None, statements*)
7 changes: 6 additions & 1 deletion src/core/idextensions.scala → src/core/acyclicity.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,14 @@

package acyclicity

import language.experimental.captureChecking

import anticipation.*
import rudiments.*
import spectacular.*

import language.experimental.captureChecking
extension (dag: Dag[Text])
def dot: Dot = Digraph(dag.edges.to(List).map(Dot.Ref(_) --> Dot.Ref(_))*)

extension (inline stringContext: StringContext)
transparent inline def ref(inline parts: Any*): Dot.Ref =
Expand Down

0 comments on commit 116b0da

Please sign in to comment.