Make graph generic
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.
This commit is contained in:
parent
6040abc836
commit
e7991adfaa
44
README.md
44
README.md
@ -3,26 +3,42 @@
|
|||||||
Fibonacci function in ginger:
|
Fibonacci function in ginger:
|
||||||
|
|
||||||
```
|
```
|
||||||
fib {
|
fib = {
|
||||||
decr { out add(in, -1) }
|
decr = { out = add < (in; -1;); };
|
||||||
|
|
||||||
out {
|
out = {
|
||||||
n 0(in),
|
n = 0 < in;
|
||||||
a 1(in),
|
a = 1 < in;
|
||||||
b 2(in),
|
b = 2 < in;
|
||||||
|
|
||||||
out if(
|
out < if < (
|
||||||
zero?(n),
|
zero? < n;
|
||||||
a,
|
a;
|
||||||
recur(decr(n), b, add(a,b))
|
recur < (decr < n; b; add < (a;b;); );
|
||||||
)
|
);
|
||||||
|
|
||||||
}(in, 0, 1)
|
} < (in; 0; 1;);
|
||||||
}
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
Usage of the function to generate the 6th fibonnaci number:
|
Usage of the function to generate the 6th fibonnaci number:
|
||||||
|
|
||||||
```
|
```
|
||||||
fib(5)
|
fib < 5;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
Current efforts on ginger are focused on a golang-based virtual machine, which
|
||||||
|
will then be used to bootstrap the language. go >=1.18 is required for this vm.
|
||||||
|
|
||||||
|
If you are on a linux-amd64 machine with nix installed, you can run:
|
||||||
|
|
||||||
|
```
|
||||||
|
nix-shell -A shell
|
||||||
|
```
|
||||||
|
|
||||||
|
from the repo root and you will be dropped into a shell with all dependencies
|
||||||
|
(including the correct go version) in your PATH, ready to use. This could
|
||||||
|
probably be expanded to other OSs/architectures easily, if you care to do so
|
||||||
|
please check out the `default.nix` file and submit a PR!
|
||||||
|
25
default.nix
Normal file
25
default.nix
Normal file
@ -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
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
@ -5,6 +5,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/mediocregopher/ginger/graph"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Punctuations which are used in the gg file format.
|
// Punctuations which are used in the gg file format.
|
||||||
@ -88,7 +90,7 @@ func (d *decoder) parseSingleValue(
|
|||||||
func (d *decoder) parseOpenEdge(
|
func (d *decoder) parseOpenEdge(
|
||||||
toks []LexerToken,
|
toks []LexerToken,
|
||||||
) (
|
) (
|
||||||
OpenEdge, []LexerToken, error,
|
graph.OpenEdge[Value], []LexerToken, error,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
if isPunct(toks[0], punctOpenTuple) {
|
if isPunct(toks[0], punctOpenTuple) {
|
||||||
@ -111,31 +113,31 @@ func (d *decoder) parseOpenEdge(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return OpenEdge{}, nil, err
|
return graph.OpenEdge[Value]{}, nil, err
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if termed {
|
if termed {
|
||||||
return ValueOut(val, ZeroValue), toks, nil
|
return graph.ValueOut[Value](val, ZeroValue), toks, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
opTok, toks := toks[0], toks[1:]
|
opTok, toks := toks[0], toks[1:]
|
||||||
|
|
||||||
if !isPunct(opTok, punctOp) {
|
if !isPunct(opTok, punctOp) {
|
||||||
return OpenEdge{}, nil, decoderErrf(opTok, "must be %q or %q", punctOp, punctTerm)
|
return graph.OpenEdge[Value]{}, nil, decoderErrf(opTok, "must be %q or %q", punctOp, punctTerm)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(toks) == 0 {
|
if len(toks) == 0 {
|
||||||
return OpenEdge{}, nil, decoderErrf(opTok, "%q cannot terminate an edge declaration", punctOp)
|
return graph.OpenEdge[Value]{}, nil, decoderErrf(opTok, "%q cannot terminate an edge declaration", punctOp)
|
||||||
}
|
}
|
||||||
|
|
||||||
oe, toks, err := d.parseOpenEdge(toks)
|
oe, toks, err := d.parseOpenEdge(toks)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return OpenEdge{}, nil, err
|
return graph.OpenEdge[Value]{}, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
oe = TupleOut([]OpenEdge{oe}, val)
|
oe = graph.TupleOut[Value]([]graph.OpenEdge[Value]{oe}, val)
|
||||||
|
|
||||||
return oe, toks, nil
|
return oe, toks, nil
|
||||||
}
|
}
|
||||||
@ -143,17 +145,17 @@ func (d *decoder) parseOpenEdge(
|
|||||||
func (d *decoder) parseTuple(
|
func (d *decoder) parseTuple(
|
||||||
toks []LexerToken,
|
toks []LexerToken,
|
||||||
) (
|
) (
|
||||||
OpenEdge, []LexerToken, error,
|
graph.OpenEdge[Value], []LexerToken, error,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
openTok, toks := toks[0], toks[1:]
|
openTok, toks := toks[0], toks[1:]
|
||||||
|
|
||||||
var edges []OpenEdge
|
var edges []graph.OpenEdge[Value]
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
|
||||||
if len(toks) == 0 {
|
if len(toks) == 0 {
|
||||||
return OpenEdge{}, nil, decoderErrf(openTok, "no matching %q", punctCloseTuple)
|
return graph.OpenEdge[Value]{}, nil, decoderErrf(openTok, "no matching %q", punctCloseTuple)
|
||||||
|
|
||||||
} else if isPunct(toks[0], punctCloseTuple) {
|
} else if isPunct(toks[0], punctCloseTuple) {
|
||||||
toks = toks[1:]
|
toks = toks[1:]
|
||||||
@ -161,14 +163,14 @@ func (d *decoder) parseTuple(
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
oe OpenEdge
|
oe graph.OpenEdge[Value]
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
|
|
||||||
oe, toks, err = d.parseOpenEdge(toks)
|
oe, toks, err = d.parseOpenEdge(toks)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return OpenEdge{}, nil, err
|
return graph.OpenEdge[Value]{}, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
edges = append(edges, oe)
|
edges = append(edges, oe)
|
||||||
@ -181,7 +183,7 @@ func (d *decoder) parseTuple(
|
|||||||
toks = toks[1:]
|
toks = toks[1:]
|
||||||
}
|
}
|
||||||
|
|
||||||
return TupleOut(edges, ZeroValue), toks, nil
|
return graph.TupleOut[Value](edges, ZeroValue), toks, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// returned boolean value indicates if the token following the graph is a term.
|
// returned boolean value indicates if the token following the graph is a term.
|
||||||
@ -201,7 +203,7 @@ func (d *decoder) parseGraphValue(
|
|||||||
openTok, toks = toks[0], toks[1:]
|
openTok, toks = toks[0], toks[1:]
|
||||||
}
|
}
|
||||||
|
|
||||||
g := ZeroGraph
|
g := new(graph.Graph[Value])
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
|
||||||
@ -252,7 +254,7 @@ func (d *decoder) parseGraphValue(
|
|||||||
return val, toks, termed, nil
|
return val, toks, termed, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *decoder) parseValIn(into *Graph, toks []LexerToken) (*Graph, []LexerToken, error) {
|
func (d *decoder) parseValIn(into *graph.Graph[Value], toks []LexerToken) (*graph.Graph[Value], []LexerToken, error) {
|
||||||
|
|
||||||
if len(toks) == 0 {
|
if len(toks) == 0 {
|
||||||
return into, nil, nil
|
return into, nil, nil
|
||||||
@ -283,7 +285,7 @@ func (d *decoder) parseValIn(into *Graph, toks []LexerToken) (*Graph, []LexerTok
|
|||||||
return into.AddValueIn(oe, dstVal), toks, nil
|
return into.AddValueIn(oe, dstVal), toks, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *decoder) decode(lexer Lexer) (*Graph, error) {
|
func (d *decoder) decode(lexer Lexer) (*graph.Graph[Value], error) {
|
||||||
|
|
||||||
var toks []LexerToken
|
var toks []LexerToken
|
||||||
|
|
||||||
@ -314,7 +316,7 @@ func (d *decoder) decode(lexer Lexer) (*Graph, error) {
|
|||||||
// construct a Graph according to the rules of the gg file format. DecodeLexer
|
// construct a Graph according to the rules of the gg file format. DecodeLexer
|
||||||
// will only return an error if there is a non-EOF file returned from the Lexer,
|
// will only return an error if there is a non-EOF file returned from the Lexer,
|
||||||
// or the tokens read cannot be used to construct a valid Graph.
|
// or the tokens read cannot be used to construct a valid Graph.
|
||||||
func DecodeLexer(lexer Lexer) (*Graph, error) {
|
func DecodeLexer(lexer Lexer) (*graph.Graph[Value], error) {
|
||||||
decoder := &decoder{}
|
decoder := &decoder{}
|
||||||
return decoder.decode(lexer)
|
return decoder.decode(lexer)
|
||||||
}
|
}
|
||||||
|
@ -5,10 +5,14 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/mediocregopher/ginger/graph"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDecoder(t *testing.T) {
|
func TestDecoder(t *testing.T) {
|
||||||
|
|
||||||
|
zeroGraph := new(graph.Graph[Value])
|
||||||
|
|
||||||
i := func(i int64) Value {
|
i := func(i int64) Value {
|
||||||
return Value{Number: &i}
|
return Value{Number: &i}
|
||||||
}
|
}
|
||||||
@ -17,27 +21,37 @@ func TestDecoder(t *testing.T) {
|
|||||||
return Value{Name: &n}
|
return Value{Name: &n}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vOut := func(val, edgeVal Value) graph.OpenEdge[Value] {
|
||||||
|
return graph.ValueOut(val, edgeVal)
|
||||||
|
}
|
||||||
|
|
||||||
|
tOut := func(ins []graph.OpenEdge[Value], edgeVal Value) graph.OpenEdge[Value] {
|
||||||
|
return graph.TupleOut(ins, edgeVal)
|
||||||
|
}
|
||||||
|
|
||||||
|
type openEdge = graph.OpenEdge[Value]
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
in string
|
in string
|
||||||
exp *Graph
|
exp *graph.Graph[Value]
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
in: "",
|
in: "",
|
||||||
exp: ZeroGraph,
|
exp: zeroGraph,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
in: "out = 1;",
|
in: "out = 1;",
|
||||||
exp: ZeroGraph.AddValueIn(ValueOut(i(1), ZeroValue), n("out")),
|
exp: zeroGraph.AddValueIn(vOut(i(1), ZeroValue), n("out")),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
in: "out = incr < 1;",
|
in: "out = incr < 1;",
|
||||||
exp: ZeroGraph.AddValueIn(ValueOut(i(1), n("incr")), n("out")),
|
exp: zeroGraph.AddValueIn(vOut(i(1), n("incr")), n("out")),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
in: "out = a < b < 1;",
|
in: "out = a < b < 1;",
|
||||||
exp: ZeroGraph.AddValueIn(
|
exp: zeroGraph.AddValueIn(
|
||||||
TupleOut(
|
tOut(
|
||||||
[]OpenEdge{ValueOut(i(1), n("b"))},
|
[]openEdge{vOut(i(1), n("b"))},
|
||||||
n("a"),
|
n("a"),
|
||||||
),
|
),
|
||||||
n("out"),
|
n("out"),
|
||||||
@ -45,14 +59,14 @@ func TestDecoder(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
in: "out = a < b < (1; c < 2; d < e < 3;);",
|
in: "out = a < b < (1; c < 2; d < e < 3;);",
|
||||||
exp: ZeroGraph.AddValueIn(
|
exp: zeroGraph.AddValueIn(
|
||||||
TupleOut(
|
tOut(
|
||||||
[]OpenEdge{TupleOut(
|
[]openEdge{tOut(
|
||||||
[]OpenEdge{
|
[]openEdge{
|
||||||
ValueOut(i(1), ZeroValue),
|
vOut(i(1), ZeroValue),
|
||||||
ValueOut(i(2), n("c")),
|
vOut(i(2), n("c")),
|
||||||
TupleOut(
|
tOut(
|
||||||
[]OpenEdge{ValueOut(i(3), n("e"))},
|
[]openEdge{vOut(i(3), n("e"))},
|
||||||
n("d"),
|
n("d"),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@ -65,15 +79,15 @@ func TestDecoder(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
in: "out = a < b < (1; c < (d < 2; 3;); );",
|
in: "out = a < b < (1; c < (d < 2; 3;); );",
|
||||||
exp: ZeroGraph.AddValueIn(
|
exp: zeroGraph.AddValueIn(
|
||||||
TupleOut(
|
tOut(
|
||||||
[]OpenEdge{TupleOut(
|
[]openEdge{tOut(
|
||||||
[]OpenEdge{
|
[]openEdge{
|
||||||
ValueOut(i(1), ZeroValue),
|
vOut(i(1), ZeroValue),
|
||||||
TupleOut(
|
tOut(
|
||||||
[]OpenEdge{
|
[]openEdge{
|
||||||
ValueOut(i(2), n("d")),
|
vOut(i(2), n("d")),
|
||||||
ValueOut(i(3), ZeroValue),
|
vOut(i(3), ZeroValue),
|
||||||
},
|
},
|
||||||
n("c"),
|
n("c"),
|
||||||
),
|
),
|
||||||
@ -87,14 +101,14 @@ func TestDecoder(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
in: "out = { a = 1; b = c < d < 2; };",
|
in: "out = { a = 1; b = c < d < 2; };",
|
||||||
exp: ZeroGraph.AddValueIn(
|
exp: zeroGraph.AddValueIn(
|
||||||
ValueOut(
|
vOut(
|
||||||
Value{Graph: ZeroGraph.
|
Value{Graph: zeroGraph.
|
||||||
AddValueIn(ValueOut(i(1), ZeroValue), n("a")).
|
AddValueIn(vOut(i(1), ZeroValue), n("a")).
|
||||||
AddValueIn(
|
AddValueIn(
|
||||||
TupleOut(
|
tOut(
|
||||||
[]OpenEdge{
|
[]openEdge{
|
||||||
ValueOut(i(2), n("d")),
|
vOut(i(2), n("d")),
|
||||||
},
|
},
|
||||||
n("c"),
|
n("c"),
|
||||||
),
|
),
|
||||||
@ -108,13 +122,13 @@ func TestDecoder(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
in: "out = a < { b = 1; } < 2;",
|
in: "out = a < { b = 1; } < 2;",
|
||||||
exp: ZeroGraph.AddValueIn(
|
exp: zeroGraph.AddValueIn(
|
||||||
TupleOut(
|
tOut(
|
||||||
[]OpenEdge{
|
[]openEdge{
|
||||||
ValueOut(
|
vOut(
|
||||||
i(2),
|
i(2),
|
||||||
Value{Graph: ZeroGraph.
|
Value{Graph: zeroGraph.
|
||||||
AddValueIn(ValueOut(i(1), ZeroValue), n("b")),
|
AddValueIn(vOut(i(1), ZeroValue), n("b")),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@ -125,9 +139,9 @@ func TestDecoder(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
in: "a = 1; b = 2;",
|
in: "a = 1; b = 2;",
|
||||||
exp: ZeroGraph.
|
exp: zeroGraph.
|
||||||
AddValueIn(ValueOut(i(1), ZeroValue), n("a")).
|
AddValueIn(vOut(i(1), ZeroValue), n("a")).
|
||||||
AddValueIn(ValueOut(i(2), ZeroValue), n("b")),
|
AddValueIn(vOut(i(2), ZeroValue), n("b")),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,7 +153,7 @@ func TestDecoder(t *testing.T) {
|
|||||||
|
|
||||||
got, err := DecodeLexer(lexer)
|
got, err := DecodeLexer(lexer)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.True(t, Equal(got, test.exp), "\nexp:%v\ngot:%v", test.exp, got)
|
assert.True(t, got.Equal(test.exp), "\nexp:%v\ngot:%v", test.exp, got)
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
304
gg/gg.go
304
gg/gg.go
@ -3,7 +3,8 @@ package gg
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
|
"github.com/mediocregopher/ginger/graph"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ZeroValue is a Value with no fields set.
|
// ZeroValue is a Value with no fields set.
|
||||||
@ -15,7 +16,7 @@ type Value struct {
|
|||||||
// Only one of these fields may be set
|
// Only one of these fields may be set
|
||||||
Name *string
|
Name *string
|
||||||
Number *int64
|
Number *int64
|
||||||
Graph *Graph
|
Graph *graph.Graph[Value]
|
||||||
|
|
||||||
// TODO coming soon!
|
// TODO coming soon!
|
||||||
// String *string
|
// String *string
|
||||||
@ -28,15 +29,22 @@ type Value struct {
|
|||||||
// IsZero returns true if the Value is the zero value (none of the sub-value
|
// IsZero returns true if the Value is the zero value (none of the sub-value
|
||||||
// fields are set). LexerToken is ignored for this check.
|
// fields are set). LexerToken is ignored for this check.
|
||||||
func (v Value) IsZero() bool {
|
func (v Value) IsZero() bool {
|
||||||
v.LexerToken = nil
|
return v.Equal(ZeroValue)
|
||||||
return v == Value{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Equal returns true if the passed in Value is equivalent.
|
// Equal returns true if the passed in Value is equivalent, ignoring the
|
||||||
func (v Value) Equal(v2 Value) bool {
|
// LexerToken on either Value.
|
||||||
|
//
|
||||||
|
// Will panic if the passed in v2 is not a Value from this package.
|
||||||
|
func (v Value) Equal(v2g graph.Value) bool {
|
||||||
|
|
||||||
|
v2 := v2g.(Value)
|
||||||
|
|
||||||
|
v.LexerToken, v2.LexerToken = nil, nil
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
|
|
||||||
case v.IsZero() && v2.IsZero():
|
case v == ZeroValue && v2 == ZeroValue:
|
||||||
return true
|
return true
|
||||||
|
|
||||||
case v.Name != nil && v2.Name != nil && *v.Name == *v2.Name:
|
case v.Name != nil && v2.Name != nil && *v.Name == *v2.Name:
|
||||||
@ -45,7 +53,7 @@ func (v Value) Equal(v2 Value) bool {
|
|||||||
case v.Number != nil && v2.Number != nil && *v.Number == *v2.Number:
|
case v.Number != nil && v2.Number != nil && *v.Number == *v2.Number:
|
||||||
return true
|
return true
|
||||||
|
|
||||||
case v.Graph != nil && v2.Graph != nil && Equal(v.Graph, v2.Graph):
|
case v.Graph != nil && v2.Graph != nil && v.Graph.Equal(v2.Graph):
|
||||||
return true
|
return true
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -57,9 +65,6 @@ func (v Value) String() string {
|
|||||||
|
|
||||||
switch {
|
switch {
|
||||||
|
|
||||||
case v.IsZero():
|
|
||||||
return "<zero>"
|
|
||||||
|
|
||||||
case v.Name != nil:
|
case v.Name != nil:
|
||||||
return *v.Name
|
return *v.Name
|
||||||
|
|
||||||
@ -70,281 +75,6 @@ func (v Value) String() string {
|
|||||||
return v.Graph.String()
|
return v.Graph.String()
|
||||||
|
|
||||||
default:
|
default:
|
||||||
panic("unknown value kind")
|
return "<zero>"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
// 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 struct {
|
|
||||||
fromV vertex
|
|
||||||
edgeVal Value
|
|
||||||
}
|
|
||||||
|
|
||||||
func (oe OpenEdge) 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) WithEdgeValue(val Value) OpenEdge {
|
|
||||||
oe.edgeVal = val
|
|
||||||
return oe
|
|
||||||
}
|
|
||||||
|
|
||||||
// EdgeValue returns the Value which lies on the edge itself.
|
|
||||||
func (oe OpenEdge) EdgeValue() Value {
|
|
||||||
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) FromValue() (Value, bool) {
|
|
||||||
if oe.fromV.val == nil {
|
|
||||||
return ZeroValue, 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) FromTuple() ([]OpenEdge, 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(val, edgeVal Value) OpenEdge {
|
|
||||||
return OpenEdge{fromV: vertex{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(ins []OpenEdge, edgeVal Value) OpenEdge {
|
|
||||||
|
|
||||||
if len(ins) == 1 {
|
|
||||||
|
|
||||||
in := ins[0]
|
|
||||||
|
|
||||||
if edgeVal.IsZero() {
|
|
||||||
return in
|
|
||||||
}
|
|
||||||
|
|
||||||
if in.edgeVal.IsZero() {
|
|
||||||
in.edgeVal = edgeVal
|
|
||||||
return in
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return OpenEdge{
|
|
||||||
fromV: vertex{tup: ins},
|
|
||||||
edgeVal: edgeVal,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (oe OpenEdge) equal(oe2 OpenEdge) bool {
|
|
||||||
return oe.edgeVal.Equal(oe2.edgeVal) && oe.fromV.equal(oe2.fromV)
|
|
||||||
}
|
|
||||||
|
|
||||||
type vertex struct {
|
|
||||||
val *Value
|
|
||||||
tup []OpenEdge
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v vertex) equal(v2 vertex) 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) 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 struct {
|
|
||||||
val Value
|
|
||||||
edges []OpenEdge
|
|
||||||
}
|
|
||||||
|
|
||||||
func (valIn graphValueIn) cp() graphValueIn {
|
|
||||||
cp := valIn
|
|
||||||
cp.edges = make([]OpenEdge, len(valIn.edges))
|
|
||||||
copy(cp.edges, valIn.edges)
|
|
||||||
return valIn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (valIn graphValueIn) equal(valIn2 graphValueIn) 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 struct {
|
|
||||||
valIns []graphValueIn
|
|
||||||
}
|
|
||||||
|
|
||||||
// ZeroGraph is the root empty graph, and is the base off which all graphs are
|
|
||||||
// built.
|
|
||||||
var ZeroGraph = &Graph{}
|
|
||||||
|
|
||||||
func (g *Graph) cp() *Graph {
|
|
||||||
cp := &Graph{
|
|
||||||
valIns: make([]graphValueIn, len(g.valIns)),
|
|
||||||
}
|
|
||||||
copy(cp.valIns, g.valIns)
|
|
||||||
return cp
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *Graph) 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) ValueIns(val Value) []OpenEdge {
|
|
||||||
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. Any Vertices
|
|
||||||
// referenced within toe OpenEdge which do not yet exist in the Graph will also
|
|
||||||
// be created in this step.
|
|
||||||
func (g *Graph) AddValueIn(oe OpenEdge, val Value) *Graph {
|
|
||||||
|
|
||||||
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{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 Equal(g1, g2 *Graph) bool {
|
|
||||||
|
|
||||||
if len(g1.valIns) != len(g2.valIns) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
outer:
|
|
||||||
for _, valIn1 := range g1.valIns {
|
|
||||||
|
|
||||||
for _, valIn2 := range g2.valIns {
|
|
||||||
|
|
||||||
if valIn1.equal(valIn2) {
|
|
||||||
continue outer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
112
gg/gg_test.go
112
gg/gg_test.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))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
8
go.mod
8
go.mod
@ -1,5 +1,11 @@
|
|||||||
module github.com/mediocregopher/ginger
|
module github.com/mediocregopher/ginger
|
||||||
|
|
||||||
go 1.16
|
go 1.18
|
||||||
|
|
||||||
require github.com/stretchr/testify v1.7.0
|
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
|
||||||
|
)
|
||||||
|
284
graph/graph.go
Normal file
284
graph/graph.go
Normal file
@ -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
|
||||||
|
}
|
115
graph/graph_test.go
Normal file
115
graph/graph_test.go
Normal file
@ -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))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
35
vm/op.go
35
vm/op.go
@ -1,6 +1,9 @@
|
|||||||
package vm
|
package vm
|
||||||
|
|
||||||
import "github.com/mediocregopher/ginger/gg"
|
import (
|
||||||
|
"github.com/mediocregopher/ginger/gg"
|
||||||
|
"github.com/mediocregopher/ginger/graph"
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
inVal = nameVal("in")
|
inVal = nameVal("in")
|
||||||
@ -14,12 +17,12 @@ var (
|
|||||||
// The Scope passed into Perform can be used to Evaluate the OpenEdge, as
|
// The Scope passed into Perform can be used to Evaluate the OpenEdge, as
|
||||||
// needed.
|
// needed.
|
||||||
type Operation interface {
|
type Operation interface {
|
||||||
Perform(gg.OpenEdge, Scope) (Value, error)
|
Perform(graph.OpenEdge[gg.Value], Scope) (Value, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func preEvalValOp(fn func(Value) (Value, error)) Operation {
|
func preEvalValOp(fn func(Value) (Value, error)) Operation {
|
||||||
|
|
||||||
return OperationFunc(func(edge gg.OpenEdge, scope Scope) (Value, error) {
|
return OperationFunc(func(edge graph.OpenEdge[gg.Value], scope Scope) (Value, error) {
|
||||||
|
|
||||||
edgeVal, err := EvaluateEdge(edge, scope)
|
edgeVal, err := EvaluateEdge(edge, scope)
|
||||||
|
|
||||||
@ -33,30 +36,30 @@ func preEvalValOp(fn func(Value) (Value, error)) Operation {
|
|||||||
|
|
||||||
// NOTE this is a giant hack to get around the fact that we're not yet
|
// NOTE this is a giant hack to get around the fact that we're not yet
|
||||||
// using a genericized Graph implementation, so when we do AddValueIn
|
// using a genericized Graph implementation, so when we do AddValueIn
|
||||||
// on a gg.Graph we can't use a Tuple value (because gg has no Tuple
|
// on a graph.Graph[gg.Value] we can't use a Tuple value (because gg has no Tuple
|
||||||
// value), we have to use a Tuple vertex instead.
|
// value), we have to use a Tuple vertex instead.
|
||||||
//
|
//
|
||||||
// This also doesn't yet support passing an operation as a value to another
|
// This also doesn't yet support passing an operation as a value to another
|
||||||
// operation.
|
// operation.
|
||||||
func preEvalEdgeOp(fn func(gg.OpenEdge) (Value, error)) Operation {
|
func preEvalEdgeOp(fn func(graph.OpenEdge[gg.Value]) (Value, error)) Operation {
|
||||||
|
|
||||||
return preEvalValOp(func(val Value) (Value, error) {
|
return preEvalValOp(func(val Value) (Value, error) {
|
||||||
|
|
||||||
var edge gg.OpenEdge
|
var edge graph.OpenEdge[gg.Value]
|
||||||
|
|
||||||
if len(val.Tuple) > 0 {
|
if len(val.Tuple) > 0 {
|
||||||
|
|
||||||
tupEdges := make([]gg.OpenEdge, len(val.Tuple))
|
tupEdges := make([]graph.OpenEdge[gg.Value], len(val.Tuple))
|
||||||
|
|
||||||
for i := range val.Tuple {
|
for i := range val.Tuple {
|
||||||
tupEdges[i] = gg.ValueOut(val.Tuple[i].Value, gg.ZeroValue)
|
tupEdges[i] = graph.ValueOut[gg.Value](val.Tuple[i].Value, gg.ZeroValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
edge = gg.TupleOut(tupEdges, gg.ZeroValue)
|
edge = graph.TupleOut[gg.Value](tupEdges, gg.ZeroValue)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
edge = gg.ValueOut(val.Value, gg.ZeroValue)
|
edge = graph.ValueOut[gg.Value](val.Value, gg.ZeroValue)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,7 +69,7 @@ func preEvalEdgeOp(fn func(gg.OpenEdge) (Value, error)) Operation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type graphOp struct {
|
type graphOp struct {
|
||||||
*gg.Graph
|
*graph.Graph[gg.Value]
|
||||||
scope Scope
|
scope Scope
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,16 +80,16 @@ type graphOp struct {
|
|||||||
// of the given Graph, then that resultant graph and the given parent Scope are
|
// of the given Graph, then that resultant graph and the given parent Scope are
|
||||||
// used to construct a new Scope. The "out" name value is Evaluated on that
|
// used to construct a new Scope. The "out" name value is Evaluated on that
|
||||||
// Scope to obtain a resultant Value.
|
// Scope to obtain a resultant Value.
|
||||||
func OperationFromGraph(g *gg.Graph, scope Scope) Operation {
|
func OperationFromGraph(g *graph.Graph[gg.Value], scope Scope) Operation {
|
||||||
return &graphOp{
|
return &graphOp{
|
||||||
Graph: g,
|
Graph: g,
|
||||||
scope: scope,
|
scope: scope,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *graphOp) Perform(edge gg.OpenEdge, scope Scope) (Value, error) {
|
func (g *graphOp) Perform(edge graph.OpenEdge[gg.Value], scope Scope) (Value, error) {
|
||||||
|
|
||||||
return preEvalEdgeOp(func(edge gg.OpenEdge) (Value, error) {
|
return preEvalEdgeOp(func(edge graph.OpenEdge[gg.Value]) (Value, error) {
|
||||||
|
|
||||||
scope = ScopeFromGraph(
|
scope = ScopeFromGraph(
|
||||||
g.Graph.AddValueIn(edge, inVal.Value),
|
g.Graph.AddValueIn(edge, inVal.Value),
|
||||||
@ -100,9 +103,9 @@ func (g *graphOp) Perform(edge gg.OpenEdge, scope Scope) (Value, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// OperationFunc is a function which implements the Operation interface.
|
// OperationFunc is a function which implements the Operation interface.
|
||||||
type OperationFunc func(gg.OpenEdge, Scope) (Value, error)
|
type OperationFunc func(graph.OpenEdge[gg.Value], Scope) (Value, error)
|
||||||
|
|
||||||
// Perform calls the underlying OperationFunc directly.
|
// Perform calls the underlying OperationFunc directly.
|
||||||
func (f OperationFunc) Perform(edge gg.OpenEdge, scope Scope) (Value, error) {
|
func (f OperationFunc) Perform(edge graph.OpenEdge[gg.Value], scope Scope) (Value, error) {
|
||||||
return f(edge, scope)
|
return f(edge, scope)
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/mediocregopher/ginger/gg"
|
"github.com/mediocregopher/ginger/gg"
|
||||||
|
"github.com/mediocregopher/ginger/graph"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Scope encapsulates a set of names and the values they indicate, or the means
|
// Scope encapsulates a set of names and the values they indicate, or the means
|
||||||
@ -22,7 +23,7 @@ type Scope interface {
|
|||||||
|
|
||||||
// edgeToValue ignores the edgeValue, it only evaluates the edge's vertex as a
|
// edgeToValue ignores the edgeValue, it only evaluates the edge's vertex as a
|
||||||
// Value.
|
// Value.
|
||||||
func edgeToValue(edge gg.OpenEdge, scope Scope) (Value, error) {
|
func edgeToValue(edge graph.OpenEdge[gg.Value], scope Scope) (Value, error) {
|
||||||
|
|
||||||
if ggVal, ok := edge.FromValue(); ok {
|
if ggVal, ok := edge.FromValue(); ok {
|
||||||
|
|
||||||
@ -60,7 +61,7 @@ func edgeToValue(edge gg.OpenEdge, scope Scope) (Value, error) {
|
|||||||
// EvaluateEdge will use the given Scope to evaluate the edge's ultimate Value,
|
// EvaluateEdge will use the given Scope to evaluate the edge's ultimate Value,
|
||||||
// after passing all leaf vertices up the tree through all Operations found on
|
// after passing all leaf vertices up the tree through all Operations found on
|
||||||
// edge values.
|
// edge values.
|
||||||
func EvaluateEdge(edge gg.OpenEdge, scope Scope) (Value, error) {
|
func EvaluateEdge(edge graph.OpenEdge[gg.Value], scope Scope) (Value, error) {
|
||||||
|
|
||||||
edgeVal := Value{Value: edge.EdgeValue()}
|
edgeVal := Value{Value: edge.EdgeValue()}
|
||||||
|
|
||||||
@ -121,7 +122,7 @@ func (m ScopeMap) NewScope() Scope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type graphScope struct {
|
type graphScope struct {
|
||||||
*gg.Graph
|
*graph.Graph[gg.Value]
|
||||||
parent Scope
|
parent Scope
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,7 +139,7 @@ type graphScope struct {
|
|||||||
//
|
//
|
||||||
// NewScope will return the parent scope, if one is given, or an empty ScopeMap
|
// NewScope will return the parent scope, if one is given, or an empty ScopeMap
|
||||||
// if not.
|
// if not.
|
||||||
func ScopeFromGraph(g *gg.Graph, parent Scope) Scope {
|
func ScopeFromGraph(g *graph.Graph[gg.Value], parent Scope) Scope {
|
||||||
return &graphScope{
|
return &graphScope{
|
||||||
Graph: g,
|
Graph: g,
|
||||||
parent: parent,
|
parent: parent,
|
||||||
|
80
vm/vm.go
80
vm/vm.go
@ -3,10 +3,16 @@ package vm
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/mediocregopher/ginger/gg"
|
"github.com/mediocregopher/ginger/gg"
|
||||||
|
"github.com/mediocregopher/ginger/graph"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ZeroValue is a Value with no fields set. It is equivalent to the 0-tuple.
|
||||||
|
var ZeroValue Value
|
||||||
|
|
||||||
// Value extends a gg.Value to include Operations and Tuples as a possible
|
// Value extends a gg.Value to include Operations and Tuples as a possible
|
||||||
// types.
|
// types.
|
||||||
type Value struct {
|
type Value struct {
|
||||||
@ -16,6 +22,78 @@ type Value struct {
|
|||||||
Tuple []Value
|
Tuple []Value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsZero returns true if the Value is the zero value (aka the 0-tuple).
|
||||||
|
// LexerToken (within the gg.Value) is ignored for this check.
|
||||||
|
func (v Value) IsZero() bool {
|
||||||
|
return v.Equal(ZeroValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal returns true if the passed in Value is equivalent, ignoring the
|
||||||
|
// LexerToken on either Value.
|
||||||
|
//
|
||||||
|
// Will panic if the passed in v2 is not a Value from this package.
|
||||||
|
func (v Value) Equal(v2g graph.Value) bool {
|
||||||
|
|
||||||
|
v2 := v2g.(Value)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
|
||||||
|
case !v.Value.IsZero() || !v2.Value.IsZero():
|
||||||
|
return v.Value.Equal(v2.Value)
|
||||||
|
|
||||||
|
case v.Operation != nil || v2.Operation != nil:
|
||||||
|
// for now we say that Operations can't be compared. This will probably
|
||||||
|
// get revisted later.
|
||||||
|
return false
|
||||||
|
|
||||||
|
case len(v.Tuple) == len(v2.Tuple):
|
||||||
|
|
||||||
|
for i := range v.Tuple {
|
||||||
|
if !v.Tuple[i].Equal(v2.Tuple[i]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
|
||||||
|
default:
|
||||||
|
|
||||||
|
// if both were the zero value then both tuples would have the same
|
||||||
|
// length (0), which is covered by the previous check. So anything left
|
||||||
|
// over must be tuples with differing lengths.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Value) String() string {
|
||||||
|
|
||||||
|
switch {
|
||||||
|
|
||||||
|
case v.Operation != nil:
|
||||||
|
|
||||||
|
// We can try to get better strings for ops later
|
||||||
|
return "<op>"
|
||||||
|
|
||||||
|
case !v.Value.IsZero():
|
||||||
|
return v.Value.String()
|
||||||
|
|
||||||
|
default:
|
||||||
|
|
||||||
|
// we consider zero value to be the 0-tuple
|
||||||
|
|
||||||
|
strs := make([]string, len(v.Tuple))
|
||||||
|
|
||||||
|
for i := range v.Tuple {
|
||||||
|
strs[i] = v.Tuple[i].String()
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("(%s)", strings.Join(strs, ", "))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func nameVal(n string) Value {
|
func nameVal(n string) Value {
|
||||||
var val Value
|
var val Value
|
||||||
val.Name = &n
|
val.Name = &n
|
||||||
@ -37,5 +115,5 @@ func EvaluateSource(opSrc io.Reader, input gg.Value, scope Scope) (Value, error)
|
|||||||
|
|
||||||
op := OperationFromGraph(g, scope.NewScope())
|
op := OperationFromGraph(g, scope.NewScope())
|
||||||
|
|
||||||
return op.Perform(gg.ValueOut(input, gg.ZeroValue), scope)
|
return op.Perform(graph.ValueOut[gg.Value](input, gg.ZeroValue), scope)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user