Refactor vm to use MapReduce and Thunks
The new code is much simpler, and is able to handle more cases than before, such as the `if` operation.
This commit is contained in:
parent
223b7f93a5
commit
3a2423a937
127
vm/op.go
127
vm/op.go
@ -2,72 +2,75 @@ package vm
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/mediocregopher/ginger/gg"
|
"github.com/mediocregopher/ginger/gg"
|
||||||
"github.com/mediocregopher/ginger/graph"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
inVal = nameVal("in")
|
|
||||||
outVal = nameVal("out")
|
outVal = nameVal("out")
|
||||||
)
|
)
|
||||||
|
|
||||||
// Operation is an entity which can accept a single argument (the OpenEdge),
|
// Thunk is returned from the performance of an Operation. When called it will
|
||||||
// perform some internal processing on that argument, and return a resultant
|
// return the result of that Operation having been called with the particular
|
||||||
// Value.
|
// arguments which were passed in.
|
||||||
//
|
type Thunk func() (Value, error)
|
||||||
// The Scope passed into Perform can be used to Evaluate the OpenEdge, as
|
|
||||||
// needed.
|
func valThunk(val Value) Thunk {
|
||||||
|
return func() (Value, error) { return val, nil }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
|
||||||
|
if len(args) == 1 {
|
||||||
|
return args[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
return func() (Value, error) {
|
||||||
|
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
tupVals = make([]Value, len(args))
|
||||||
|
)
|
||||||
|
|
||||||
|
for i := range args {
|
||||||
|
if tupVals[i], err = args[i](); err != nil {
|
||||||
|
return ZeroValue, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Value{Tuple: tupVals}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 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.
|
||||||
type Operation interface {
|
type Operation interface {
|
||||||
Perform(*gg.OpenEdge, Scope) (Value, error)
|
Perform([]Thunk) (Thunk, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func preEvalValOp(fn func(Value) (Value, error)) Operation {
|
func preEvalValOp(fn func(Value) (Value, error)) Operation {
|
||||||
|
|
||||||
return OperationFunc(func(edge *gg.OpenEdge, scope Scope) (Value, error) {
|
return OperationFunc(func(args []Thunk) (Thunk, error) {
|
||||||
|
|
||||||
edgeVal, err := EvaluateEdge(edge, scope)
|
return func() (Value, error) {
|
||||||
|
|
||||||
|
val, err := evalThunks(args)()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Value{}, err
|
return ZeroValue, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return fn(edgeVal)
|
return fn(val)
|
||||||
|
|
||||||
|
}, nil
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 gg.Graph 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(*gg.OpenEdge) (Value, error)) Operation {
|
|
||||||
|
|
||||||
return preEvalValOp(func(val Value) (Value, error) {
|
|
||||||
|
|
||||||
var edge *gg.OpenEdge
|
|
||||||
|
|
||||||
if len(val.Tuple) > 0 {
|
|
||||||
|
|
||||||
tupEdges := make([]*gg.OpenEdge, len(val.Tuple))
|
|
||||||
|
|
||||||
for i := range val.Tuple {
|
|
||||||
tupEdges[i] = graph.ValueOut[gg.Value](gg.ZeroValue, val.Tuple[i].Value)
|
|
||||||
}
|
|
||||||
|
|
||||||
edge = graph.TupleOut[gg.Value](gg.ZeroValue, tupEdges...)
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
edge = graph.ValueOut[gg.Value](gg.ZeroValue, val.Value)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return fn(edge)
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
type graphOp struct {
|
type graphOp struct {
|
||||||
*gg.Graph
|
*gg.Graph
|
||||||
scope Scope
|
scope Scope
|
||||||
@ -76,10 +79,9 @@ type graphOp struct {
|
|||||||
// OperationFromGraph wraps the given Graph such that it can be used as an
|
// OperationFromGraph wraps the given Graph such that it can be used as an
|
||||||
// operation.
|
// operation.
|
||||||
//
|
//
|
||||||
// When Perform is called the passed in OpenEdge is set to the "in" name value
|
// The Thunk returned by Perform will evaluate the passed in Thunks, and set
|
||||||
// of the given Graph, then that resultant graph and the given parent Scope are
|
// them to the "in" name value of the given Graph. The "out" name value is
|
||||||
// used to construct a new Scope. The "out" name value is Evaluated on that
|
// Evaluated using the given Scope to obtain a resultant Value.
|
||||||
// Scope to obtain a resultant Value.
|
|
||||||
func OperationFromGraph(g *gg.Graph, scope Scope) Operation {
|
func OperationFromGraph(g *gg.Graph, scope Scope) Operation {
|
||||||
return &graphOp{
|
return &graphOp{
|
||||||
Graph: g,
|
Graph: g,
|
||||||
@ -87,25 +89,18 @@ func OperationFromGraph(g *gg.Graph, scope Scope) Operation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *graphOp) Perform(edge *gg.OpenEdge, scope Scope) (Value, error) {
|
func (g *graphOp) Perform(args []Thunk) (Thunk, error) {
|
||||||
|
return ScopeFromGraph(
|
||||||
return preEvalEdgeOp(func(edge *gg.OpenEdge) (Value, error) {
|
g.Graph,
|
||||||
|
evalThunks(args),
|
||||||
scope = ScopeFromGraph(
|
|
||||||
g.Graph.AddValueIn(inVal.Value, edge),
|
|
||||||
g.scope,
|
g.scope,
|
||||||
)
|
).Evaluate(outVal)
|
||||||
|
|
||||||
return scope.Evaluate(outVal)
|
|
||||||
|
|
||||||
}).Perform(edge, scope)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// OperationFunc is a function which implements the Operation interface.
|
// OperationFunc is a function which implements the Operation interface.
|
||||||
type OperationFunc func(*gg.OpenEdge, Scope) (Value, error)
|
type OperationFunc func([]Thunk) (Thunk, error)
|
||||||
|
|
||||||
// Perform calls the underlying OperationFunc directly.
|
// Perform calls the underlying OperationFunc directly.
|
||||||
func (f OperationFunc) Perform(edge *gg.OpenEdge, scope Scope) (Value, error) {
|
func (f OperationFunc) Perform(args []Thunk) (Thunk, error) {
|
||||||
return f(edge, scope)
|
return f(args)
|
||||||
}
|
}
|
||||||
|
118
vm/scope.go
118
vm/scope.go
@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/mediocregopher/ginger/gg"
|
"github.com/mediocregopher/ginger/gg"
|
||||||
|
"github.com/mediocregopher/ginger/graph"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Scope encapsulates a set of names and the values they indicate, or the means
|
// Scope encapsulates a set of names and the values they indicate, or the means
|
||||||
@ -11,86 +12,76 @@ import (
|
|||||||
// its value.
|
// its value.
|
||||||
type Scope interface {
|
type Scope interface {
|
||||||
|
|
||||||
// Evaluate accepts a name Value and returns the real Value which that name
|
// Evaluate accepts a name Value and returns a Thunk which will return the
|
||||||
// points to.
|
// real Value which that name points to.
|
||||||
Evaluate(Value) (Value, error)
|
Evaluate(Value) (Thunk, error)
|
||||||
|
|
||||||
// NewScope returns a new Scope which sub-operations within this Scope
|
// NewScope returns a new Scope which sub-operations within this Scope
|
||||||
// should use for themselves.
|
// should use for themselves.
|
||||||
NewScope() Scope
|
NewScope() Scope
|
||||||
}
|
}
|
||||||
|
|
||||||
// edgeToValue ignores the edgeValue, it only evaluates the edge's vertex as a
|
|
||||||
// Value.
|
|
||||||
func edgeToValue(edge *gg.OpenEdge, scope Scope) (Value, error) {
|
|
||||||
|
|
||||||
if ggVal, ok := edge.FromValue(); ok {
|
|
||||||
|
|
||||||
val := Value{Value: ggVal}
|
|
||||||
|
|
||||||
if val.Name != nil {
|
|
||||||
return scope.Evaluate(val)
|
|
||||||
}
|
|
||||||
|
|
||||||
return val, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var tupVal Value
|
|
||||||
|
|
||||||
tup, _ := edge.FromTuple()
|
|
||||||
|
|
||||||
for _, tupEdge := range tup {
|
|
||||||
|
|
||||||
val, err := EvaluateEdge(tupEdge, scope)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return Value{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
tupVal.Tuple = append(tupVal.Tuple, val)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(tupVal.Tuple) == 1 {
|
|
||||||
return tupVal.Tuple[0], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return tupVal, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// EvaluateEdge will use the given Scope to evaluate the edge's ultimate Value,
|
// EvaluateEdge will use the given Scope to evaluate the edge's ultimate Value,
|
||||||
// after passing all leaf vertices up the tree through all Operations found on
|
// after passing all leaf vertices up the tree through all Operations found on
|
||||||
// edge values.
|
// edge values.
|
||||||
func EvaluateEdge(edge *gg.OpenEdge, scope Scope) (Value, error) {
|
func EvaluateEdge(edge *gg.OpenEdge, scope Scope) (Value, error) {
|
||||||
|
|
||||||
edgeVal := Value{Value: edge.EdgeValue()}
|
thunk, err := graph.MapReduce[gg.Value, gg.Value, Thunk](
|
||||||
|
edge,
|
||||||
|
func(ggVal gg.Value) (Thunk, error) {
|
||||||
|
|
||||||
if edgeVal.IsZero() {
|
val := Value{Value: ggVal}
|
||||||
return edgeToValue(edge, scope)
|
|
||||||
|
if val.Name != nil {
|
||||||
|
return scope.Evaluate(val)
|
||||||
}
|
}
|
||||||
|
|
||||||
edge = edge.WithEdgeValue(gg.ZeroValue)
|
return valThunk(val), nil
|
||||||
|
|
||||||
|
},
|
||||||
|
func(ggEdgeVal gg.Value, args []Thunk) (Thunk, error) {
|
||||||
|
|
||||||
|
if ggEdgeVal.IsZero() {
|
||||||
|
return evalThunks(args), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
edgeVal := Value{Value: ggEdgeVal}
|
||||||
|
|
||||||
if edgeVal.Name != nil {
|
if edgeVal.Name != nil {
|
||||||
|
|
||||||
var err error
|
nameThunk, err := scope.Evaluate(edgeVal)
|
||||||
|
|
||||||
if edgeVal, err = scope.Evaluate(edgeVal); err != nil {
|
if err != nil {
|
||||||
return Value{}, err
|
return nil, err
|
||||||
|
|
||||||
|
} else if edgeVal, err = nameThunk(); err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if edgeVal.Graph != nil {
|
if edgeVal.Graph != nil {
|
||||||
|
|
||||||
edgeVal = Value{
|
edgeVal = Value{
|
||||||
Operation: OperationFromGraph(edgeVal.Graph, scope.NewScope()),
|
Operation: OperationFromGraph(
|
||||||
|
edgeVal.Graph,
|
||||||
|
scope.NewScope(),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if edgeVal.Operation == nil {
|
if edgeVal.Operation == nil {
|
||||||
return Value{}, fmt.Errorf("edge value must be an operation")
|
return nil, fmt.Errorf("edge value must be an operation")
|
||||||
}
|
}
|
||||||
|
|
||||||
return edgeVal.Operation.Perform(edge, scope)
|
return edgeVal.Operation.Perform(args)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return ZeroValue, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return thunk()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ScopeMap implements the Scope interface.
|
// ScopeMap implements the Scope interface.
|
||||||
@ -100,19 +91,19 @@ var _ Scope = ScopeMap{}
|
|||||||
|
|
||||||
// Evaluate uses the given name Value as a key into the ScopeMap map, and
|
// Evaluate uses the given name Value as a key into the ScopeMap map, and
|
||||||
// returns the Value held there for the key, if any.
|
// returns the Value held there for the key, if any.
|
||||||
func (m ScopeMap) Evaluate(nameVal Value) (Value, error) {
|
func (m ScopeMap) Evaluate(nameVal Value) (Thunk, error) {
|
||||||
|
|
||||||
if nameVal.Name == nil {
|
if nameVal.Name == nil {
|
||||||
return Value{}, fmt.Errorf("value %v is not a name value", nameVal)
|
return nil, fmt.Errorf("value %v is not a name value", nameVal)
|
||||||
}
|
}
|
||||||
|
|
||||||
val, ok := m[*nameVal.Name]
|
val, ok := m[*nameVal.Name]
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return Value{}, fmt.Errorf("%q not defined", *nameVal.Name)
|
return nil, fmt.Errorf("%q not defined", *nameVal.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
return val, nil
|
return valThunk(val), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewScope returns the ScopeMap as-is.
|
// NewScope returns the ScopeMap as-is.
|
||||||
@ -122,6 +113,7 @@ func (m ScopeMap) NewScope() Scope {
|
|||||||
|
|
||||||
type graphScope struct {
|
type graphScope struct {
|
||||||
*gg.Graph
|
*gg.Graph
|
||||||
|
in Thunk
|
||||||
parent Scope
|
parent Scope
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,23 +124,31 @@ type graphScope struct {
|
|||||||
// be followed, with edge values being evaluated to Operations, until a Value
|
// be followed, with edge values being evaluated to Operations, until a Value
|
||||||
// can be obtained.
|
// can be obtained.
|
||||||
//
|
//
|
||||||
|
// As a special case, if the name "in" is evaluated, either directly or as part
|
||||||
|
// of an outer evaluation, then the given Thunk is used to evaluate the Value.
|
||||||
|
//
|
||||||
// If a name does not appear in the Graph, then the given parent Scope will be
|
// If a name does not appear in the Graph, then the given parent Scope will be
|
||||||
// used to evaluate that name. If the parent Scope is nil then an error is
|
// used to evaluate that name. If the parent Scope is nil then an error is
|
||||||
// returned.
|
// returned.
|
||||||
//
|
//
|
||||||
// NewScope will return the parent scope, if one is given, or an empty ScopeMap
|
// NewScope will return the parent scope, if one is given, or an empty ScopeMap
|
||||||
// if not.
|
// if not.
|
||||||
func ScopeFromGraph(g *gg.Graph, parent Scope) Scope {
|
func ScopeFromGraph(g *gg.Graph, in Thunk, parent Scope) Scope {
|
||||||
return &graphScope{
|
return &graphScope{
|
||||||
Graph: g,
|
Graph: g,
|
||||||
|
in: in,
|
||||||
parent: parent,
|
parent: parent,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *graphScope) Evaluate(nameVal Value) (Value, error) {
|
func (g *graphScope) Evaluate(nameVal Value) (Thunk, error) {
|
||||||
|
|
||||||
if nameVal.Name == nil {
|
if nameVal.Name == nil {
|
||||||
return Value{}, fmt.Errorf("value %v is not a name value", nameVal)
|
return nil, fmt.Errorf("value %v is not a name value", nameVal)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *nameVal.Name == "in" {
|
||||||
|
return g.in, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
edgesIn := g.ValueIns(nameVal.Value)
|
edgesIn := g.ValueIns(nameVal.Value)
|
||||||
@ -159,13 +159,13 @@ func (g *graphScope) Evaluate(nameVal Value) (Value, error) {
|
|||||||
|
|
||||||
} else if l != 1 {
|
} else if l != 1 {
|
||||||
|
|
||||||
return Value{}, fmt.Errorf(
|
return nil, fmt.Errorf(
|
||||||
"%q must have exactly one input edge, found %d input edges",
|
"%q must have exactly one input edge, found %d input edges",
|
||||||
*nameVal.Name, l,
|
*nameVal.Name, l,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return EvaluateEdge(edgesIn[0], g)
|
return func() (Value, error) { return EvaluateEdge(edgesIn[0], g) }, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *graphScope) NewScope() Scope {
|
func (g *graphScope) NewScope() Scope {
|
||||||
|
10
vm/vm.go
10
vm/vm.go
@ -115,5 +115,13 @@ func EvaluateSource(opSrc io.Reader, input gg.Value, scope Scope) (Value, error)
|
|||||||
|
|
||||||
op := OperationFromGraph(g, scope.NewScope())
|
op := OperationFromGraph(g, scope.NewScope())
|
||||||
|
|
||||||
return op.Perform(graph.ValueOut[gg.Value](gg.ZeroValue, input), scope)
|
thunk, err := op.Perform([]Thunk{
|
||||||
|
func() (Value, error) { return Value{Value: input}, nil },
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return ZeroValue, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return thunk()
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user