ginger/vm/op.go
Brian Picciano e7991adfaa 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.
2021-12-29 12:32:53 -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)
}