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.rust
parent
6040abc836
commit
e7991adfaa
@ -0,0 +1,25 @@ |
||||
{ |
||||
|
||||
pkgs ? import (fetchTarball { |
||||
name = "nixpkgs-21-11"; |
||||
url = "https://github.com/NixOS/nixpkgs/archive/a7ecde854aee5c4c7cd6177f54a99d2c1ff28a31.tar.gz"; |
||||
sha256 = "162dywda2dvfj1248afxc45kcrg83appjd0nmdb541hl7rnncf02"; |
||||
}) { }, |
||||
|
||||
}: rec { |
||||
|
||||
# https://go.dev/dl/#go1.18beta1 |
||||
go = fetchTarball { |
||||
name = "go1.18beta1"; |
||||
url = "https://go.dev/dl/go1.18beta1.linux-amd64.tar.gz"; |
||||
sha256 = "09sb0viv1ybx6adgx4jym1sckdq3mpjkd6albj06hwnchj5rqn40"; |
||||
}; |
||||
|
||||
shell = pkgs.mkShell { |
||||
name = "ginger-dev"; |
||||
buildInputs = [ |
||||
go |
||||
]; |
||||
}; |
||||
|
||||
} |
@ -1,112 +0,0 @@ |
||||
package gg |
||||
|
||||
import ( |
||||
"strconv" |
||||
"testing" |
||||
|
||||
"github.com/stretchr/testify/assert" |
||||
) |
||||
|
||||
func TestEqual(t *testing.T) { |
||||
|
||||
i := func(i int64) Value { |
||||
return Value{Number: &i} |
||||
} |
||||
|
||||
n := func(n string) Value { |
||||
return Value{Name: &n} |
||||
} |
||||
|
||||
tests := []struct { |
||||
a, b *Graph |
||||
exp bool |
||||
}{ |
||||
{ |
||||
a: ZeroGraph, |
||||
b: ZeroGraph, |
||||
exp: true, |
||||
}, |
||||
{ |
||||
a: ZeroGraph, |
||||
b: ZeroGraph.AddValueIn(ValueOut(n("in"), n("incr")), n("out")), |
||||
exp: false, |
||||
}, |
||||
{ |
||||
a: ZeroGraph.AddValueIn(ValueOut(n("in"), n("incr")), n("out")), |
||||
b: ZeroGraph.AddValueIn(ValueOut(n("in"), n("incr")), n("out")), |
||||
exp: true, |
||||
}, |
||||
{ |
||||
a: ZeroGraph.AddValueIn(ValueOut(n("in"), n("incr")), n("out")), |
||||
b: ZeroGraph.AddValueIn(TupleOut([]OpenEdge{ |
||||
ValueOut(n("in"), n("ident")), |
||||
ValueOut(i(1), n("ident")), |
||||
}, n("add")), n("out")), |
||||
exp: false, |
||||
}, |
||||
{ |
||||
// tuples are different order
|
||||
a: ZeroGraph.AddValueIn(TupleOut([]OpenEdge{ |
||||
ValueOut(i(1), n("ident")), |
||||
ValueOut(n("in"), n("ident")), |
||||
}, n("add")), n("out")), |
||||
b: ZeroGraph.AddValueIn(TupleOut([]OpenEdge{ |
||||
ValueOut(n("in"), n("ident")), |
||||
ValueOut(i(1), n("ident")), |
||||
}, n("add")), n("out")), |
||||
exp: false, |
||||
}, |
||||
{ |
||||
// tuple with no edge value and just a single input edge should be
|
||||
// equivalent to just that edge.
|
||||
a: ZeroGraph.AddValueIn(TupleOut([]OpenEdge{ |
||||
ValueOut(i(1), n("ident")), |
||||
}, ZeroValue), n("out")), |
||||
b: ZeroGraph.AddValueIn(ValueOut(i(1), n("ident")), n("out")), |
||||
exp: true, |
||||
}, |
||||
{ |
||||
// tuple with an edge value and just a single input edge that has no
|
||||
// edgeVal should be equivalent to just that edge with the tuple's
|
||||
// edge value.
|
||||
a: ZeroGraph.AddValueIn(TupleOut([]OpenEdge{ |
||||
ValueOut(i(1), ZeroValue), |
||||
}, n("ident")), n("out")), |
||||
b: ZeroGraph.AddValueIn(ValueOut(i(1), n("ident")), n("out")), |
||||
exp: true, |
||||
}, |
||||
{ |
||||
a: ZeroGraph. |
||||
AddValueIn(ValueOut(n("in"), n("incr")), n("out")). |
||||
AddValueIn(ValueOut(n("in2"), n("incr2")), n("out2")), |
||||
b: ZeroGraph. |
||||
AddValueIn(ValueOut(n("in"), n("incr")), n("out")), |
||||
exp: false, |
||||
}, |
||||
{ |
||||
a: ZeroGraph. |
||||
AddValueIn(ValueOut(n("in"), n("incr")), n("out")). |
||||
AddValueIn(ValueOut(n("in2"), n("incr2")), n("out2")), |
||||
b: ZeroGraph. |
||||
AddValueIn(ValueOut(n("in"), n("incr")), n("out")). |
||||
AddValueIn(ValueOut(n("in2"), n("incr2")), n("out2")), |
||||
exp: true, |
||||
}, |
||||
{ |
||||
// order of value ins shouldn't matter
|
||||
a: ZeroGraph. |
||||
AddValueIn(ValueOut(n("in"), n("incr")), n("out")). |
||||
AddValueIn(ValueOut(n("in2"), n("incr2")), n("out2")), |
||||
b: ZeroGraph. |
||||
AddValueIn(ValueOut(n("in2"), n("incr2")), n("out2")). |
||||
AddValueIn(ValueOut(n("in"), n("incr")), n("out")), |
||||
exp: true, |
||||
}, |
||||
} |
||||
|
||||
for i, test := range tests { |
||||
t.Run(strconv.Itoa(i), func(t *testing.T) { |
||||
assert.Equal(t, test.exp, Equal(test.a, test.b)) |
||||
}) |
||||
} |
||||
} |
@ -1,5 +1,11 @@ |
||||
module github.com/mediocregopher/ginger |
||||
|
||||
go 1.16 |
||||
go 1.18 |
||||
|
||||
require github.com/stretchr/testify v1.7.0 |
||||
|
||||
require ( |
||||
github.com/davecgh/go-spew v1.1.0 // indirect |
||||
github.com/pmezard/go-difflib v1.0.0 // indirect |
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect |
||||
) |
||||
|
@ -0,0 +1,284 @@ |
||||
// 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 |
||||
} |
@ -0,0 +1,115 @@ |
||||
package graph |
||||
|
||||
import ( |
||||
"strconv" |
||||
"testing" |
||||
|
||||
"github.com/stretchr/testify/assert" |
||||
) |
||||
|
||||
type S string |
||||
|
||||
func (s S) Equal(s2 Value) bool { return s == s2.(S) } |
||||
|
||||
func (s S) String() string { return string(s) } |
||||
|
||||
func TestEqual(t *testing.T) { |
||||
|
||||
var ( |
||||
zeroValue S |
||||
zeroGraph = new(Graph[S]) |
||||
) |
||||
|
||||
tests := []struct { |
||||
a, b *Graph[S] |
||||
exp bool |
||||
}{ |
||||
{ |
||||
a: zeroGraph, |
||||
b: zeroGraph, |
||||
exp: true, |
||||
}, |
||||
{ |
||||
a: zeroGraph, |
||||
b: zeroGraph.AddValueIn(ValueOut[S]("in", "incr"), "out"), |
||||
exp: false, |
||||
}, |
||||
{ |
||||
a: zeroGraph.AddValueIn(ValueOut[S]("in", "incr"), "out"), |
||||
b: zeroGraph.AddValueIn(ValueOut[S]("in", "incr"), "out"), |
||||
exp: true, |
||||
}, |
||||
{ |
||||
a: zeroGraph.AddValueIn(ValueOut[S]("in", "incr"), "out"), |
||||
b: zeroGraph.AddValueIn(TupleOut[S]([]OpenEdge[S]{ |
||||
ValueOut[S]("in", "ident"), |
||||
ValueOut[S]("1", "ident"), |
||||
}, "add"), "out"), |
||||
exp: false, |
||||
}, |
||||
{ |
||||
// tuples are different order
|
||||
a: zeroGraph.AddValueIn(TupleOut[S]([]OpenEdge[S]{ |
||||
ValueOut[S]("1", "ident"), |
||||
ValueOut[S]("in", "ident"), |
||||
}, "add"), "out"), |
||||
b: zeroGraph.AddValueIn(TupleOut[S]([]OpenEdge[S]{ |
||||
ValueOut[S]("in", "ident"), |
||||
ValueOut[S]("1", "ident"), |
||||
}, "add"), "out"), |
||||
exp: false, |
||||
}, |
||||
{ |
||||
// tuple with no edge value and just a single input edge should be
|
||||
// equivalent to just that edge.
|
||||
a: zeroGraph.AddValueIn(TupleOut[S]([]OpenEdge[S]{ |
||||
ValueOut[S]("1", "ident"), |
||||
}, zeroValue), "out"), |
||||
b: zeroGraph.AddValueIn(ValueOut[S]("1", "ident"), "out"), |
||||
exp: true, |
||||
}, |
||||
{ |
||||
// tuple with an edge value and just a single input edge that has no
|
||||
// edgeVal should be equivalent to just that edge with the tuple's
|
||||
// edge value.
|
||||
a: zeroGraph.AddValueIn(TupleOut[S]([]OpenEdge[S]{ |
||||
ValueOut[S]("1", zeroValue), |
||||
}, "ident"), "out"), |
||||
b: zeroGraph.AddValueIn(ValueOut[S]("1", "ident"), "out"), |
||||
exp: true, |
||||
}, |
||||
{ |
||||
a: zeroGraph. |
||||
AddValueIn(ValueOut[S]("in", "incr"), "out"). |
||||
AddValueIn(ValueOut[S]("in2", "incr2"), "out2"), |
||||
b: zeroGraph. |
||||
AddValueIn(ValueOut[S]("in", "incr"), "out"), |
||||
exp: false, |
||||
}, |
||||
{ |
||||
a: zeroGraph. |
||||
AddValueIn(ValueOut[S]("in", "incr"), "out"). |
||||
AddValueIn(ValueOut[S]("in2", "incr2"), "out2"), |
||||
b: zeroGraph. |
||||
AddValueIn(ValueOut[S]("in", "incr"), "out"). |
||||
AddValueIn(ValueOut[S]("in2", "incr2"), "out2"), |
||||
exp: true, |
||||
}, |
||||
{ |
||||
// order of value ins shouldn't matter
|
||||
a: zeroGraph. |
||||
AddValueIn(ValueOut[S]("in", "incr"), "out"). |
||||
AddValueIn(ValueOut[S]("in2", "incr2"), "out2"), |
||||
b: zeroGraph. |
||||
AddValueIn(ValueOut[S]("in2", "incr2"), "out2"). |
||||
AddValueIn(ValueOut[S]("in", "incr"), "out"), |
||||
exp: true, |
||||
}, |
||||
} |
||||
|
||||
for i, test := range tests { |
||||
t.Run(strconv.Itoa(i), func(t *testing.T) { |
||||
assert.Equal(t, test.exp, test.a.Equal(test.b)) |
||||
}) |
||||
} |
||||
} |
Loading…
Reference in new issue