2be865181d
This commit is the result of many days of picking vm apart and putting it back together again. The result is an implementation which separates compile and runtime into separate steps, and which functions (more) correctly in the face of recursion. Pretty much all aspects of vm have been modified or deleted, so it's not even really worth it to describe specific changes. Just pretend this is the original implementaiton and the old one was never done.
236 lines
5.7 KiB
Go
236 lines
5.7 KiB
Go
package vm
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/mediocregopher/ginger/gg"
|
|
"github.com/mediocregopher/ginger/graph"
|
|
)
|
|
|
|
// 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
|
|
}
|
|
|
|
var (
|
|
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)}
|
|
)
|
|
|
|
// 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) {
|
|
|
|
// 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
|
|
|
|
var compileEdge func(*gg.OpenEdge) (edgeOp, error)
|
|
|
|
// TODO memoize?
|
|
valToEdgeOp := func(val Value) (edgeOp, error) {
|
|
|
|
if val.Name == nil {
|
|
return edgeOp(Identity(val)), nil
|
|
}
|
|
|
|
name := *val.Name
|
|
|
|
if val.Equal(valNameIn) {
|
|
return edgeOp(OperationFunc(func(inArg Value) Value {
|
|
return inArg
|
|
})), nil
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
edge := edgesIn[0]
|
|
|
|
return compileEdge(edge)
|
|
}
|
|
|
|
// "out" resolves to more than a static value, treat the graph as a full
|
|
// operation.
|
|
|
|
// 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)
|
|
|
|
compileEdge = func(edge *gg.OpenEdge) (edgeOp, error) {
|
|
|
|
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) {
|
|
|
|
if ggEdgeVal.Equal(valNameIf.Value) {
|
|
|
|
if len(inEdgeOps) != 3 {
|
|
return nil, fmt.Errorf("'if' requires a 3-tuple argument")
|
|
}
|
|
|
|
return edgeOp(OperationFunc(func(inArg Value) Value {
|
|
|
|
if pred := inEdgeOps[0].Perform(inArg); pred.Equal(valNumberZero) {
|
|
return inEdgeOps[2].Perform(inArg)
|
|
}
|
|
|
|
return inEdgeOps[1].Perform(inArg)
|
|
|
|
})), nil
|
|
}
|
|
|
|
// "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
|
|
},
|
|
)
|
|
}
|
|
|
|
graphOp, err := valToEdgeOp(valNameOut)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
*thisOp = Operation(graphOp)
|
|
|
|
return Operation(graphOp), nil
|
|
}
|