ginger/vm/op.go
Brian Picciano c4dd673bf4 Refactor Graph internals to deduplicate OpenEdges
This change required OpenEdges to be passed around as pointers, which in
turn required me to audit how value copying is being done everywhere and
simplify it in a few places. I think I covered all the bases.

The new internals of Graph allow the graph's actual structure to be
reflected within the graph itself. For example, if the OpenEdges of two
ValueIns are equivalent then the same OpenEdge pointer is shared for the
two internally. This applies recursively, so if two OpenEdge tuples
share an inner OpenEdge, they will share the same pointer.

This change is a preliminary requirement for creating a graph mapping
operation. Without OpenEdge deduplication the map operation would end up
operating on the same OpenEdge multiple times, which would be incorrect.
2021-12-29 13:57:14 -07:00

112 lines
2.8 KiB
Go

package vm
import (
"github.com/mediocregopher/ginger/gg"
"github.com/mediocregopher/ginger/graph"
)
var (
inVal = nameVal("in")
outVal = nameVal("out")
)
// Operation is an entity which can accept a single argument (the OpenEdge),
// perform some internal processing on that argument, and return a resultant
// Value.
//
// The Scope passed into Perform can be used to Evaluate the OpenEdge, as
// needed.
type Operation interface {
Perform(*graph.OpenEdge[gg.Value], Scope) (Value, error)
}
func preEvalValOp(fn func(Value) (Value, error)) Operation {
return OperationFunc(func(edge *graph.OpenEdge[gg.Value], scope Scope) (Value, error) {
edgeVal, err := EvaluateEdge(edge, scope)
if err != nil {
return Value{}, err
}
return fn(edgeVal)
})
}
// 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
// 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.
//
// This also doesn't yet support passing an operation as a value to another
// operation.
func preEvalEdgeOp(fn func(*graph.OpenEdge[gg.Value]) (Value, error)) Operation {
return preEvalValOp(func(val Value) (Value, error) {
var edge *graph.OpenEdge[gg.Value]
if len(val.Tuple) > 0 {
tupEdges := make([]*graph.OpenEdge[gg.Value], len(val.Tuple))
for i := range val.Tuple {
tupEdges[i] = graph.ValueOut[gg.Value](val.Tuple[i].Value, gg.ZeroValue)
}
edge = graph.TupleOut[gg.Value](tupEdges, gg.ZeroValue)
} else {
edge = graph.ValueOut[gg.Value](val.Value, gg.ZeroValue)
}
return fn(edge)
})
}
type graphOp struct {
*graph.Graph[gg.Value]
scope Scope
}
// OperationFromGraph wraps the given Graph such that it can be used as an
// operation.
//
// When Perform is called the passed in OpenEdge is set to the "in" name value
// 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
// Scope to obtain a resultant Value.
func OperationFromGraph(g *graph.Graph[gg.Value], scope Scope) Operation {
return &graphOp{
Graph: g,
scope: scope,
}
}
func (g *graphOp) Perform(edge *graph.OpenEdge[gg.Value], scope Scope) (Value, error) {
return preEvalEdgeOp(func(edge *graph.OpenEdge[gg.Value]) (Value, error) {
scope = ScopeFromGraph(
g.Graph.AddValueIn(edge, inVal.Value),
g.scope,
)
return scope.Evaluate(outVal)
}).Perform(edge, scope)
}
// OperationFunc is a function which implements the Operation interface.
type OperationFunc func(*graph.OpenEdge[gg.Value], Scope) (Value, error)
// Perform calls the underlying OperationFunc directly.
func (f OperationFunc) Perform(edge *graph.OpenEdge[gg.Value], scope Scope) (Value, error) {
return f(edge, scope)
}