The vm does what it needs to do (evaluate the result of passing an input to an operatio, where the input and the operation themselves may have sub-inputs/operations to evaluate), with many caveats/misgivings.rust
parent
ec3218e2d0
commit
6040abc836
@ -0,0 +1,108 @@ |
||||
package vm |
||||
|
||||
import "github.com/mediocregopher/ginger/gg" |
||||
|
||||
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(gg.OpenEdge, Scope) (Value, error) |
||||
} |
||||
|
||||
func preEvalValOp(fn func(Value) (Value, error)) Operation { |
||||
|
||||
return OperationFunc(func(edge gg.OpenEdge, 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 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] = gg.ValueOut(val.Tuple[i].Value, gg.ZeroValue) |
||||
} |
||||
|
||||
edge = gg.TupleOut(tupEdges, gg.ZeroValue) |
||||
|
||||
} else { |
||||
|
||||
edge = gg.ValueOut(val.Value, gg.ZeroValue) |
||||
|
||||
} |
||||
|
||||
return fn(edge) |
||||
}) |
||||
|
||||
} |
||||
|
||||
type graphOp struct { |
||||
*gg.Graph |
||||
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 *gg.Graph, scope Scope) Operation { |
||||
return &graphOp{ |
||||
Graph: g, |
||||
scope: scope, |
||||
} |
||||
} |
||||
|
||||
func (g *graphOp) Perform(edge gg.OpenEdge, scope Scope) (Value, error) { |
||||
|
||||
return preEvalEdgeOp(func(edge gg.OpenEdge) (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(gg.OpenEdge, Scope) (Value, error) |
||||
|
||||
// Perform calls the underlying OperationFunc directly.
|
||||
func (f OperationFunc) Perform(edge gg.OpenEdge, scope Scope) (Value, error) { |
||||
return f(edge, scope) |
||||
} |
@ -0,0 +1,178 @@ |
||||
package vm |
||||
|
||||
import ( |
||||
"fmt" |
||||
|
||||
"github.com/mediocregopher/ginger/gg" |
||||
) |
||||
|
||||
// Scope encapsulates a set of names and the values they indicate, or the means
|
||||
// by which to obtain those values, and allows for the evaluation of a name to
|
||||
// its value.
|
||||
type Scope interface { |
||||
|
||||
// Evaluate accepts a name Value and returns the real Value which that name
|
||||
// points to.
|
||||
Evaluate(Value) (Value, error) |
||||
|
||||
// NewScope returns a new Scope which sub-operations within this Scope
|
||||
// should use for themselves.
|
||||
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,
|
||||
// after passing all leaf vertices up the tree through all Operations found on
|
||||
// edge values.
|
||||
func EvaluateEdge(edge gg.OpenEdge, scope Scope) (Value, error) { |
||||
|
||||
edgeVal := Value{Value: edge.EdgeValue()} |
||||
|
||||
if edgeVal.IsZero() { |
||||
return edgeToValue(edge, scope) |
||||
} |
||||
|
||||
edge = edge.WithEdgeValue(gg.ZeroValue) |
||||
|
||||
if edgeVal.Name != nil { |
||||
|
||||
var err error |
||||
|
||||
if edgeVal, err = scope.Evaluate(edgeVal); err != nil { |
||||
return Value{}, err |
||||
} |
||||
} |
||||
|
||||
if edgeVal.Graph != nil { |
||||
|
||||
edgeVal = Value{ |
||||
Operation: OperationFromGraph(edgeVal.Graph, scope.NewScope()), |
||||
} |
||||
} |
||||
|
||||
if edgeVal.Operation == nil { |
||||
return Value{}, fmt.Errorf("edge value must be an operation") |
||||
} |
||||
|
||||
return edgeVal.Operation.Perform(edge, scope) |
||||
} |
||||
|
||||
// ScopeMap implements the Scope interface.
|
||||
type ScopeMap map[string]Value |
||||
|
||||
var _ Scope = ScopeMap{} |
||||
|
||||
// Evaluate uses the given name Value as a key into the ScopeMap map, and
|
||||
// returns the Value held there for the key, if any.
|
||||
func (m ScopeMap) Evaluate(nameVal Value) (Value, error) { |
||||
|
||||
if nameVal.Name == nil { |
||||
return Value{}, fmt.Errorf("value %v is not a name value", nameVal) |
||||
} |
||||
|
||||
val, ok := m[*nameVal.Name] |
||||
|
||||
if !ok { |
||||
return Value{}, fmt.Errorf("%q not defined", *nameVal.Name) |
||||
} |
||||
|
||||
return val, nil |
||||
} |
||||
|
||||
// NewScope returns the ScopeMap as-is.
|
||||
func (m ScopeMap) NewScope() Scope { |
||||
return m |
||||
} |
||||
|
||||
type graphScope struct { |
||||
*gg.Graph |
||||
parent Scope |
||||
} |
||||
|
||||
// ScopeFromGraph returns a Scope which will use the given Graph for evaluation.
|
||||
//
|
||||
// When a name is evaluated, that name will be looked up in the Graph. The
|
||||
// name's vertex must have only a single OpenEdge leading to it. That edge will
|
||||
// be followed, with edge values being evaluated to Operations, until a Value
|
||||
// can be obtained.
|
||||
//
|
||||
// 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
|
||||
// returned.
|
||||
//
|
||||
// NewScope will return the parent scope, if one is given, or an empty ScopeMap
|
||||
// if not.
|
||||
func ScopeFromGraph(g *gg.Graph, parent Scope) Scope { |
||||
return &graphScope{ |
||||
Graph: g, |
||||
parent: parent, |
||||
} |
||||
} |
||||
|
||||
func (g *graphScope) Evaluate(nameVal Value) (Value, error) { |
||||
|
||||
if nameVal.Name == nil { |
||||
return Value{}, fmt.Errorf("value %v is not a name value", nameVal) |
||||
} |
||||
|
||||
edgesIn := g.ValueIns(nameVal.Value) |
||||
|
||||
if l := len(edgesIn); l == 0 && g.parent != nil { |
||||
|
||||
return g.parent.Evaluate(nameVal) |
||||
|
||||
} else if l != 1 { |
||||
|
||||
return Value{}, fmt.Errorf( |
||||
"%q must have exactly one input edge, found %d input edges", |
||||
*nameVal.Name, l, |
||||
) |
||||
} |
||||
|
||||
return EvaluateEdge(edgesIn[0], g) |
||||
} |
||||
|
||||
func (g *graphScope) NewScope() Scope { |
||||
|
||||
if g.parent == nil { |
||||
return ScopeMap{} |
||||
} |
||||
|
||||
return g.parent |
||||
} |
@ -0,0 +1,33 @@ |
||||
package vm |
||||
|
||||
import ( |
||||
"fmt" |
||||
|
||||
"github.com/mediocregopher/ginger/gg" |
||||
) |
||||
|
||||
// GlobalScope contains operations and values which are available from within
|
||||
// any operation in a ginger program.
|
||||
var GlobalScope = ScopeMap{ |
||||
|
||||
"add": Value{Operation: preEvalValOp(func(val Value) (Value, error) { |
||||
|
||||
if len(val.Tuple) == 0 { |
||||
return Value{}, fmt.Errorf("add requires a non-zero tuple of numbers as an argument") |
||||
} |
||||
|
||||
var sum int64 |
||||
|
||||
for _, tupVal := range val.Tuple { |
||||
|
||||
if tupVal.Number == nil { |
||||
return Value{}, fmt.Errorf("add requires a non-zero tuple of numbers as an argument") |
||||
} |
||||
|
||||
sum += *tupVal.Number |
||||
} |
||||
|
||||
return Value{Value: gg.Value{Number: &sum}}, nil |
||||
|
||||
})}, |
||||
} |
@ -0,0 +1,41 @@ |
||||
// Package vm implements the execution of gg.Graphs as programs.
|
||||
package vm |
||||
|
||||
import ( |
||||
"io" |
||||
|
||||
"github.com/mediocregopher/ginger/gg" |
||||
) |
||||
|
||||
// Value extends a gg.Value to include Operations and Tuples as a possible
|
||||
// types.
|
||||
type Value struct { |
||||
gg.Value |
||||
|
||||
Operation |
||||
Tuple []Value |
||||
} |
||||
|
||||
func nameVal(n string) Value { |
||||
var val Value |
||||
val.Name = &n |
||||
return val |
||||
} |
||||
|
||||
// EvaluateSource reads and parses the io.Reader as an operation, input is used
|
||||
// as the argument to the operation, and the resultant value is returned.
|
||||
//
|
||||
// scope contains pre-defined operations and values which are available during
|
||||
// the evaluation.
|
||||
func EvaluateSource(opSrc io.Reader, input gg.Value, scope Scope) (Value, error) { |
||||
lexer := gg.NewLexer(opSrc) |
||||
|
||||
g, err := gg.DecodeLexer(lexer) |
||||
if err != nil { |
||||
return Value{}, err |
||||
} |
||||
|
||||
op := OperationFromGraph(g, scope.NewScope()) |
||||
|
||||
return op.Perform(gg.ValueOut(input, gg.ZeroValue), scope) |
||||
} |
@ -0,0 +1,29 @@ |
||||
package vm |
||||
|
||||
import ( |
||||
"bytes" |
||||
"testing" |
||||
|
||||
"github.com/mediocregopher/ginger/gg" |
||||
"github.com/stretchr/testify/assert" |
||||
) |
||||
|
||||
func TestVM(t *testing.T) { |
||||
|
||||
src := ` |
||||
incr = { out = add < (1; in;); }; |
||||
|
||||
out = incr < incr < in; |
||||
` |
||||
|
||||
var in int64 = 5 |
||||
|
||||
val, err := EvaluateSource( |
||||
bytes.NewBufferString(src), |
||||
gg.Value{Number: &in}, |
||||
GlobalScope, |
||||
) |
||||
|
||||
assert.NoError(t, err) |
||||
assert.Equal(t, in+2, *val.Number) |
||||
} |
Loading…
Reference in new issue