e7991adfaa
The base graph implementation has been moved into its own package, `graph`, and been made fully generic, ie the value on each vertex/edge is a parameterized type. This will allow us to use the graph for both syntax parsing (gg) and runtime evaluation (vm), with each use-case being able to use slightly different Value types.
285 lines
5.8 KiB
Go
285 lines
5.8 KiB
Go
// Package graph implements a generic directed graph type, with support for
|
|
// tuple vertices in addition to traditional "value" vertices.
|
|
package graph
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// Value is any value which can be stored within a Graph.
|
|
type Value interface {
|
|
Equal(Value) bool
|
|
String() string
|
|
}
|
|
|
|
// OpenEdge is an un-realized Edge which can't be used for anything except
|
|
// constructing graphs. It has no meaning on its own.
|
|
type OpenEdge[V Value] struct {
|
|
fromV vertex[V]
|
|
edgeVal V
|
|
}
|
|
|
|
func (oe OpenEdge[V]) equal(oe2 OpenEdge[V]) bool {
|
|
return oe.edgeVal.Equal(oe2.edgeVal) && oe.fromV.equal(oe2.fromV)
|
|
}
|
|
|
|
func (oe OpenEdge[V]) String() string {
|
|
|
|
vertexType := "tup"
|
|
|
|
if oe.fromV.val != nil {
|
|
vertexType = "val"
|
|
}
|
|
|
|
return fmt.Sprintf("%s(%s, %s)", vertexType, oe.fromV.String(), oe.edgeVal.String())
|
|
}
|
|
|
|
// WithEdgeValue returns a copy of the OpenEdge with the given Value replacing
|
|
// the previous edge value.
|
|
//
|
|
// NOTE I _think_ this can be factored out once Graph is genericized.
|
|
func (oe OpenEdge[V]) WithEdgeValue(val V) OpenEdge[V] {
|
|
oe.edgeVal = val
|
|
return oe
|
|
}
|
|
|
|
// EdgeValue returns the Value which lies on the edge itself.
|
|
func (oe OpenEdge[V]) EdgeValue() V {
|
|
return oe.edgeVal
|
|
}
|
|
|
|
// FromValue returns the Value from which the OpenEdge was created via ValueOut,
|
|
// or false if it wasn't created via ValueOut.
|
|
func (oe OpenEdge[V]) FromValue() (V, bool) {
|
|
if oe.fromV.val == nil {
|
|
var zero V
|
|
return zero, false
|
|
}
|
|
|
|
return *oe.fromV.val, true
|
|
}
|
|
|
|
// FromTuple returns the tuple of OpenEdges from which the OpenEdge was created
|
|
// via TupleOut, or false if it wasn't created via TupleOut.
|
|
func (oe OpenEdge[V]) FromTuple() ([]OpenEdge[V], bool) {
|
|
if oe.fromV.val != nil {
|
|
return nil, false
|
|
}
|
|
|
|
return oe.fromV.tup, true
|
|
}
|
|
|
|
// ValueOut creates a OpenEdge which, when used to construct a Graph, represents
|
|
// an edge (with edgeVal attached to it) coming from the ValueVertex containing
|
|
// val.
|
|
func ValueOut[V Value](val, edgeVal V) OpenEdge[V] {
|
|
return OpenEdge[V]{fromV: vertex[V]{val: &val}, edgeVal: edgeVal}
|
|
}
|
|
|
|
// TupleOut creates an OpenEdge which, when used to construct a Graph,
|
|
// represents an edge (with edgeVal attached to it) coming from the
|
|
// TupleVertex comprised of the given ordered-set of input edges.
|
|
//
|
|
// If len(ins) == 1 && edgeVal.IsZero(), then that single OpenEdge is
|
|
// returned as-is.
|
|
func TupleOut[V Value](ins []OpenEdge[V], edgeVal V) OpenEdge[V] {
|
|
|
|
if len(ins) == 1 {
|
|
|
|
in := ins[0]
|
|
var zero V
|
|
|
|
if edgeVal.Equal(zero) {
|
|
return in
|
|
}
|
|
|
|
if in.edgeVal.Equal(zero) {
|
|
in.edgeVal = edgeVal
|
|
return in
|
|
}
|
|
|
|
}
|
|
|
|
return OpenEdge[V]{
|
|
fromV: vertex[V]{tup: ins},
|
|
edgeVal: edgeVal,
|
|
}
|
|
}
|
|
|
|
|
|
type vertex[V Value] struct {
|
|
val *V
|
|
tup []OpenEdge[V]
|
|
}
|
|
|
|
func (v vertex[V]) equal(v2 vertex[V]) bool {
|
|
|
|
if v.val != nil {
|
|
return v2.val != nil && (*v.val).Equal(*v2.val)
|
|
}
|
|
|
|
if len(v.tup) != len(v2.tup) {
|
|
return false
|
|
}
|
|
|
|
for i := range v.tup {
|
|
if !v.tup[i].equal(v2.tup[i]) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func (v vertex[V]) String() string {
|
|
|
|
if v.val != nil {
|
|
return (*v.val).String()
|
|
}
|
|
|
|
strs := make([]string, len(v.tup))
|
|
|
|
for i := range v.tup {
|
|
strs[i] = v.tup[i].String()
|
|
}
|
|
|
|
return fmt.Sprintf("[%s]", strings.Join(strs, ", "))
|
|
}
|
|
|
|
type graphValueIn[V Value] struct {
|
|
val V
|
|
edges []OpenEdge[V]
|
|
}
|
|
|
|
func (valIn graphValueIn[V]) cp() graphValueIn[V] {
|
|
cp := valIn
|
|
cp.edges = make([]OpenEdge[V], len(valIn.edges))
|
|
copy(cp.edges, valIn.edges)
|
|
return valIn
|
|
}
|
|
|
|
func (valIn graphValueIn[V]) equal(valIn2 graphValueIn[V]) bool {
|
|
if !valIn.val.Equal(valIn2.val) {
|
|
return false
|
|
}
|
|
|
|
if len(valIn.edges) != len(valIn2.edges) {
|
|
return false
|
|
}
|
|
|
|
outer:
|
|
for _, edge := range valIn.edges {
|
|
|
|
for _, edge2 := range valIn2.edges {
|
|
|
|
if edge.equal(edge2) {
|
|
continue outer
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// Graph is an immutable container of a set of vertices. The Graph keeps track
|
|
// of all Values which terminate an OpenEdge (which may be a tree of Value/Tuple
|
|
// vertices).
|
|
//
|
|
// NOTE The current implementation of Graph is incredibly inefficient, there's
|
|
// lots of O(N) operations, unnecessary copying on changes, and duplicate data
|
|
// in memory.
|
|
type Graph[V Value] struct {
|
|
valIns []graphValueIn[V]
|
|
}
|
|
|
|
func (g *Graph[V]) cp() *Graph[V] {
|
|
cp := &Graph[V]{
|
|
valIns: make([]graphValueIn[V], len(g.valIns)),
|
|
}
|
|
copy(cp.valIns, g.valIns)
|
|
return cp
|
|
}
|
|
|
|
func (g *Graph[V]) String() string {
|
|
|
|
var strs []string
|
|
|
|
for _, valIn := range g.valIns {
|
|
for _, oe := range valIn.edges {
|
|
strs = append(
|
|
strs,
|
|
fmt.Sprintf("valIn(%s, %s)", oe.String(), valIn.val.String()),
|
|
)
|
|
}
|
|
}
|
|
|
|
return fmt.Sprintf("graph(%s)", strings.Join(strs, ", "))
|
|
}
|
|
|
|
// ValueIns returns, if any, all OpenEdges which lead to the given Value in the
|
|
// Graph (ie, all those added via AddValueIn).
|
|
func (g *Graph[V]) ValueIns(val Value) []OpenEdge[V] {
|
|
for _, valIn := range g.valIns {
|
|
if valIn.val.Equal(val) {
|
|
return valIn.cp().edges
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// AddValueIn takes a OpenEdge and connects it to the Value vertex containing
|
|
// val, returning the new Graph which reflects that connection.
|
|
func (g *Graph[V]) AddValueIn(oe OpenEdge[V], val V) *Graph[V] {
|
|
|
|
edges := g.ValueIns(val)
|
|
|
|
for _, existingOE := range edges {
|
|
if existingOE.equal(oe) {
|
|
return g
|
|
}
|
|
}
|
|
|
|
// ValueIns returns a copy of edges, so we're ok to modify it.
|
|
edges = append(edges, oe)
|
|
valIn := graphValueIn[V]{val: val, edges: edges}
|
|
|
|
g = g.cp()
|
|
|
|
for i, existingValIn := range g.valIns {
|
|
if existingValIn.val.Equal(val) {
|
|
g.valIns[i] = valIn
|
|
return g
|
|
}
|
|
}
|
|
|
|
g.valIns = append(g.valIns, valIn)
|
|
return g
|
|
}
|
|
|
|
// Equal returns whether or not the two Graphs are equivalent in value.
|
|
func (g *Graph[V]) Equal(g2 *Graph[V]) bool {
|
|
|
|
if len(g.valIns) != len(g2.valIns) {
|
|
return false
|
|
}
|
|
|
|
outer:
|
|
for _, valIn := range g.valIns {
|
|
|
|
for _, valIn2 := range g2.valIns {
|
|
|
|
if valIn.equal(valIn2) {
|
|
continue outer
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|