2021-12-28 20:18:01 +00:00
|
|
|
package vm
|
|
|
|
|
2021-12-29 19:32:53 +00:00
|
|
|
import (
|
2022-03-31 15:18:02 +00:00
|
|
|
"fmt"
|
|
|
|
|
2021-12-29 19:32:53 +00:00
|
|
|
"github.com/mediocregopher/ginger/gg"
|
2022-03-31 15:18:02 +00:00
|
|
|
"github.com/mediocregopher/ginger/graph"
|
2021-12-29 19:32:53 +00:00
|
|
|
)
|
2021-12-28 20:18:01 +00:00
|
|
|
|
2022-03-31 15:18:02 +00:00
|
|
|
// Operation is an entity which accepts an argument Value and performs some
|
|
|
|
// internal processing on that argument to return a resultant Value.
|
|
|
|
type Operation interface {
|
|
|
|
Perform(Value) Value
|
|
|
|
}
|
|
|
|
|
|
|
|
// OperationFunc is a function which implements the Operation interface.
|
|
|
|
type OperationFunc func(Value) Value
|
|
|
|
|
|
|
|
// Perform calls the underlying OperationFunc directly.
|
|
|
|
func (f OperationFunc) Perform(arg Value) Value {
|
|
|
|
return f(arg)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Identity returns an Operation which always returns the given Value,
|
|
|
|
// regardless of the input argument.
|
|
|
|
//
|
|
|
|
// TODO this might not be the right name
|
|
|
|
func Identity(val Value) Operation {
|
|
|
|
return OperationFunc(func(Value) Value {
|
|
|
|
return val
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
type graphOp struct {
|
|
|
|
*gg.Graph
|
|
|
|
scope Scope
|
|
|
|
}
|
|
|
|
|
2021-12-28 20:18:01 +00:00
|
|
|
var (
|
2022-03-31 15:18:02 +00:00
|
|
|
valNameIn = Value{Value: gg.Name("in")}
|
|
|
|
valNameOut = Value{Value: gg.Name("out")}
|
|
|
|
valNameIf = Value{Value: gg.Name("if")}
|
|
|
|
valNameRecur = Value{Value: gg.Name("recur")}
|
|
|
|
valNumberZero = Value{Value: gg.Number(0)}
|
2021-12-28 20:18:01 +00:00
|
|
|
)
|
|
|
|
|
2022-03-31 15:18:02 +00:00
|
|
|
// OperationFromGraph wraps the given Graph such that it can be used as an
|
|
|
|
// Operation. The given Scope determines what values outside of the Graph are
|
|
|
|
// available for use within the Operation.
|
|
|
|
func OperationFromGraph(g *gg.Graph, scope Scope) (Operation, error) {
|
2021-12-30 21:00:04 +00:00
|
|
|
|
2022-03-31 15:18:02 +00:00
|
|
|
// edgeOp is distinct from a generic Operation in that the Value passed into
|
|
|
|
// Perform will _always_ be the value of "in" for the overall Operation.
|
|
|
|
//
|
|
|
|
// edgeOps will wrap each other, passing "in" downwards to the leaf edgeOps.
|
|
|
|
type edgeOp Operation
|
2021-12-28 20:18:01 +00:00
|
|
|
|
2022-03-31 15:18:02 +00:00
|
|
|
var compileEdge func(*gg.OpenEdge) (edgeOp, error)
|
2021-12-28 20:18:01 +00:00
|
|
|
|
2022-03-31 15:18:02 +00:00
|
|
|
// TODO memoize?
|
|
|
|
valToEdgeOp := func(val Value) (edgeOp, error) {
|
2021-12-30 21:00:04 +00:00
|
|
|
|
2022-03-31 15:18:02 +00:00
|
|
|
if val.Name == nil {
|
|
|
|
return edgeOp(Identity(val)), nil
|
|
|
|
}
|
2021-12-28 20:18:01 +00:00
|
|
|
|
2022-03-31 15:18:02 +00:00
|
|
|
name := *val.Name
|
|
|
|
|
|
|
|
if val.Equal(valNameIn) {
|
|
|
|
return edgeOp(OperationFunc(func(inArg Value) Value {
|
|
|
|
return inArg
|
|
|
|
})), nil
|
|
|
|
}
|
2021-12-28 20:18:01 +00:00
|
|
|
|
2022-03-31 15:18:02 +00:00
|
|
|
// TODO intercept if and recur?
|
|
|
|
|
|
|
|
edgesIn := g.ValueIns(val.Value)
|
|
|
|
|
|
|
|
if l := len(edgesIn); l == 0 {
|
|
|
|
|
|
|
|
val, err := scope.Resolve(name)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("resolving name %q from the outer scope: %w", name, err)
|
2021-12-30 21:00:04 +00:00
|
|
|
}
|
2022-03-31 15:18:02 +00:00
|
|
|
|
|
|
|
return edgeOp(Identity(val)), nil
|
|
|
|
|
|
|
|
} else if l != 1 {
|
|
|
|
return nil, fmt.Errorf("resolved name %q to %d input edges, rather than one", name, l)
|
2021-12-28 20:18:01 +00:00
|
|
|
}
|
|
|
|
|
2022-03-31 15:18:02 +00:00
|
|
|
edge := edgesIn[0]
|
|
|
|
|
|
|
|
return compileEdge(edge)
|
2021-12-30 21:00:04 +00:00
|
|
|
}
|
2021-12-28 20:18:01 +00:00
|
|
|
|
2022-03-31 15:18:02 +00:00
|
|
|
// "out" resolves to more than a static value, treat the graph as a full
|
|
|
|
// operation.
|
2021-12-28 20:18:01 +00:00
|
|
|
|
2022-03-31 15:18:02 +00:00
|
|
|
// thisOp is used to support recur. It will get filled in with the Operation
|
|
|
|
// which is returned by this function, once that Operation is created.
|
|
|
|
thisOp := new(Operation)
|
2021-12-28 20:18:01 +00:00
|
|
|
|
2022-03-31 15:18:02 +00:00
|
|
|
compileEdge = func(edge *gg.OpenEdge) (edgeOp, error) {
|
2021-12-28 20:18:01 +00:00
|
|
|
|
2022-03-31 15:18:02 +00:00
|
|
|
return graph.MapReduce[gg.Value, gg.Value, edgeOp](
|
|
|
|
edge,
|
|
|
|
func(ggVal gg.Value) (edgeOp, error) {
|
|
|
|
return valToEdgeOp(Value{Value: ggVal})
|
|
|
|
},
|
|
|
|
func(ggEdgeVal gg.Value, inEdgeOps []edgeOp) (edgeOp, error) {
|
2021-12-28 20:18:01 +00:00
|
|
|
|
2022-03-31 15:18:02 +00:00
|
|
|
if ggEdgeVal.Equal(valNameIf.Value) {
|
2021-12-28 20:18:01 +00:00
|
|
|
|
2022-03-31 15:18:02 +00:00
|
|
|
if len(inEdgeOps) != 3 {
|
|
|
|
return nil, fmt.Errorf("'if' requires a 3-tuple argument")
|
|
|
|
}
|
2021-12-28 20:18:01 +00:00
|
|
|
|
2022-03-31 15:18:02 +00:00
|
|
|
return edgeOp(OperationFunc(func(inArg Value) Value {
|
2021-12-28 20:18:01 +00:00
|
|
|
|
2022-03-31 15:18:02 +00:00
|
|
|
if pred := inEdgeOps[0].Perform(inArg); pred.Equal(valNumberZero) {
|
|
|
|
return inEdgeOps[2].Perform(inArg)
|
|
|
|
}
|
2021-12-28 20:18:01 +00:00
|
|
|
|
2022-03-31 15:18:02 +00:00
|
|
|
return inEdgeOps[1].Perform(inArg)
|
2021-12-28 20:18:01 +00:00
|
|
|
|
2022-03-31 15:18:02 +00:00
|
|
|
})), nil
|
|
|
|
}
|
2021-12-28 20:18:01 +00:00
|
|
|
|
2022-03-31 15:18:02 +00:00
|
|
|
// "if" statements (above) are the only case where we want the
|
|
|
|
// input edges to remain separated, otherwise they should always
|
|
|
|
// be combined into a single edge whose value is a tuple. Do
|
|
|
|
// that here.
|
|
|
|
|
|
|
|
inEdgeOp := inEdgeOps[0]
|
|
|
|
|
|
|
|
if len(inEdgeOps) > 1 {
|
|
|
|
inEdgeOp = edgeOp(OperationFunc(func(inArg Value) Value {
|
|
|
|
tupVals := make([]Value, len(inEdgeOps))
|
|
|
|
|
|
|
|
for i := range inEdgeOps {
|
|
|
|
tupVals[i] = inEdgeOps[i].Perform(inArg)
|
|
|
|
}
|
|
|
|
|
|
|
|
return Tuple(tupVals...)
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
|
|
|
edgeVal := Value{Value: ggEdgeVal}
|
|
|
|
|
|
|
|
if edgeVal.IsZero() {
|
|
|
|
return inEdgeOp, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if edgeVal.Equal(valNameRecur) {
|
|
|
|
return edgeOp(OperationFunc(func(inArg Value) Value {
|
|
|
|
return (*thisOp).Perform(inEdgeOp.Perform(inArg))
|
|
|
|
})), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if edgeVal.Graph != nil {
|
|
|
|
|
|
|
|
opFromGraph, err := OperationFromGraph(
|
|
|
|
edgeVal.Graph,
|
|
|
|
scope.NewScope(),
|
|
|
|
)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("compiling graph to operation: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
edgeVal = Value{Operation: opFromGraph}
|
|
|
|
}
|
|
|
|
|
|
|
|
// The Operation is known at compile-time, so we can wrap it
|
|
|
|
// directly into an edgeVal using the existing inEdgeOp as the
|
|
|
|
// input.
|
|
|
|
if edgeVal.Operation != nil {
|
|
|
|
return edgeOp(OperationFunc(func(inArg Value) Value {
|
|
|
|
return edgeVal.Operation.Perform(inEdgeOp.Perform(inArg))
|
|
|
|
})), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// the edgeVal is not an Operation at compile time, and so
|
|
|
|
// it must become one at runtime. We must resolve edgeVal to an
|
|
|
|
// edgeOp as well (edgeEdgeOp), and then at runtime that is
|
|
|
|
// given the inArg and (hopefully) the resultant Operation is
|
|
|
|
// called.
|
|
|
|
|
|
|
|
edgeEdgeOp, err := valToEdgeOp(edgeVal)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return edgeOp(OperationFunc(func(inArg Value) Value {
|
|
|
|
|
|
|
|
runtimeEdgeVal := edgeEdgeOp.Perform(inArg)
|
|
|
|
|
|
|
|
if runtimeEdgeVal.Graph != nil {
|
|
|
|
|
|
|
|
runtimeOp, err := OperationFromGraph(
|
|
|
|
runtimeEdgeVal.Graph,
|
|
|
|
scope.NewScope(),
|
|
|
|
)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
panic(fmt.Sprintf("compiling graph to operation: %v", err))
|
|
|
|
}
|
|
|
|
|
|
|
|
runtimeEdgeVal = Value{Operation: runtimeOp}
|
|
|
|
}
|
|
|
|
|
|
|
|
if runtimeEdgeVal.Operation == nil {
|
|
|
|
panic("edge value must be an operation")
|
|
|
|
}
|
|
|
|
|
|
|
|
return runtimeEdgeVal.Operation.Perform(inEdgeOp.Perform(inArg))
|
|
|
|
|
|
|
|
})), nil
|
|
|
|
},
|
|
|
|
)
|
2021-12-28 20:18:01 +00:00
|
|
|
}
|
|
|
|
|
2022-03-31 15:18:02 +00:00
|
|
|
graphOp, err := valToEdgeOp(valNameOut)
|
2021-12-28 20:18:01 +00:00
|
|
|
|
2022-03-31 15:18:02 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2021-12-28 20:18:01 +00:00
|
|
|
|
2022-03-31 15:18:02 +00:00
|
|
|
*thisOp = Operation(graphOp)
|
|
|
|
|
|
|
|
return Operation(graphOp), nil
|
2021-12-28 20:18:01 +00:00
|
|
|
}
|