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.rust
parent
ebf57591a8
commit
2be865181d
@ -1,109 +1,235 @@ |
||||
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 ( |
||||
outVal = nameVal("out") |
||||
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)} |
||||
) |
||||
|
||||
// Thunk is returned from the performance of an Operation. When called it will
|
||||
// return the result of that Operation having been called with the particular
|
||||
// arguments which were passed in.
|
||||
type Thunk func() (Value, error) |
||||
// 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) { |
||||
|
||||
func valThunk(val Value) Thunk { |
||||
return func() (Value, error) { return val, nil } |
||||
} |
||||
// 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 |
||||
|
||||
// evalThunks is used to coalesce the results of multiple Thunks into a single
|
||||
// Thunk which will return a tuple Value. As a special case, if only one Thunk
|
||||
// is given then it is returned directly (1-tuple is equivalent to its only
|
||||
// element).
|
||||
func evalThunks(args []Thunk) Thunk { |
||||
var compileEdge func(*gg.OpenEdge) (edgeOp, error) |
||||
|
||||
if len(args) == 1 { |
||||
return args[0] |
||||
} |
||||
// TODO memoize?
|
||||
valToEdgeOp := func(val Value) (edgeOp, error) { |
||||
|
||||
return func() (Value, error) { |
||||
if val.Name == nil { |
||||
return edgeOp(Identity(val)), nil |
||||
} |
||||
|
||||
var ( |
||||
err error |
||||
tupVals = make([]Value, len(args)) |
||||
) |
||||
name := *val.Name |
||||
|
||||
if val.Equal(valNameIn) { |
||||
return edgeOp(OperationFunc(func(inArg Value) Value { |
||||
return inArg |
||||
})), nil |
||||
} |
||||
|
||||
for i := range args { |
||||
if tupVals[i], err = args[i](); err != nil { |
||||
return ZeroValue, err |
||||
// 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) |
||||
} |
||||
|
||||
return Value{Tuple: tupVals}, nil |
||||
edge := edgesIn[0] |
||||
|
||||
return compileEdge(edge) |
||||
} |
||||
} |
||||
|
||||
// Operation is an entity which can accept one or more arguments (each not
|
||||
// having been evaluated yet) and return a Thunk which will perform some
|
||||
// internal processing on those arguments and return a resultant Value.
|
||||
//
|
||||
// The Operation passed into Perform is the Operation which is calling the
|
||||
// Perform. It may be nil.
|
||||
type Operation interface { |
||||
Perform([]Thunk, Operation) (Thunk, error) |
||||
} |
||||
// "out" resolves to more than a static value, treat the graph as a full
|
||||
// operation.
|
||||
|
||||
func preEvalValOp(fn func(Value) (Value, error)) 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) |
||||
|
||||
return OperationFunc(func(args []Thunk, _ Operation) (Thunk, error) { |
||||
compileEdge = func(edge *gg.OpenEdge) (edgeOp, error) { |
||||
|
||||
return func() (Value, 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) { |
||||
|
||||
val, err := evalThunks(args)() |
||||
if ggEdgeVal.Equal(valNameIf.Value) { |
||||
|
||||
if err != nil { |
||||
return ZeroValue, err |
||||
} |
||||
if len(inEdgeOps) != 3 { |
||||
return nil, fmt.Errorf("'if' requires a 3-tuple argument") |
||||
} |
||||
|
||||
return fn(val) |
||||
return edgeOp(OperationFunc(func(inArg Value) Value { |
||||
|
||||
}, nil |
||||
if pred := inEdgeOps[0].Perform(inArg); pred.Equal(valNumberZero) { |
||||
return inEdgeOps[2].Perform(inArg) |
||||
} |
||||
|
||||
}) |
||||
} |
||||
return inEdgeOps[1].Perform(inArg) |
||||
|
||||
type graphOp struct { |
||||
*gg.Graph |
||||
scope Scope |
||||
} |
||||
})), nil |
||||
} |
||||
|
||||
// OperationFromGraph wraps the given Graph such that it can be used as an
|
||||
// operation.
|
||||
//
|
||||
// The Thunk returned by Perform will evaluate the passed in Thunks, and set
|
||||
// them to the "in" name value of the given Graph. The "out" name value is
|
||||
// Evaluated using the given Scope to obtain a resultant Value.
|
||||
func OperationFromGraph(g *gg.Graph, scope Scope) Operation { |
||||
return &graphOp{ |
||||
Graph: g, |
||||
scope: scope, |
||||
// "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 |
||||
}, |
||||
) |
||||
} |
||||
} |
||||
|
||||
func (g *graphOp) Perform(args []Thunk, _ Operation) (Thunk, error) { |
||||
return ScopeFromGraph( |
||||
g.Graph, |
||||
evalThunks(args), |
||||
g.scope, |
||||
g, |
||||
).Evaluate(outVal) |
||||
} |
||||
graphOp, err := valToEdgeOp(valNameOut) |
||||
|
||||
// OperationFunc is a function which implements the Operation interface.
|
||||
type OperationFunc func([]Thunk, Operation) (Thunk, error) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
// Perform calls the underlying OperationFunc directly.
|
||||
func (f OperationFunc) Perform(args []Thunk, op Operation) (Thunk, error) { |
||||
return f(args, op) |
||||
*thisOp = Operation(graphOp) |
||||
|
||||
return Operation(graphOp), nil |
||||
} |
||||
|
Loading…
Reference in new issue