c4dd673bf4
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.
112 lines
2.8 KiB
Go
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)
|
|
}
|