Complete refactor vm to not be as stupid
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.
This commit is contained in:
parent
ebf57591a8
commit
2be865181d
@ -28,6 +28,4 @@ An example program which computes the Nth fibonacci number can be found at
|
||||
go run ./cmd/eval/main.go "$(cat examples/fib.gg)" 5
|
||||
```
|
||||
|
||||
Where you can replace `5` with any number. The vm has only been given enough
|
||||
capability to run this program as a demo, and is extremely poorly optimized (as
|
||||
will be evident if you input any large number). Further work is obviously TODO.
|
||||
Where you can replace `5` with any number.
|
||||
|
@ -29,7 +29,7 @@ func main() {
|
||||
|
||||
res, err := vm.EvaluateSource(
|
||||
bytes.NewBufferString(opSrc),
|
||||
inVal,
|
||||
vm.Value{Value: inVal},
|
||||
vm.GlobalScope,
|
||||
)
|
||||
|
||||
|
10
gg/gg.go
10
gg/gg.go
@ -26,6 +26,16 @@ type Value struct {
|
||||
LexerToken *LexerToken
|
||||
}
|
||||
|
||||
// Name returns a name Value.
|
||||
func Name(name string) Value {
|
||||
return Value{Name: &name}
|
||||
}
|
||||
|
||||
// Number returns a number Value.
|
||||
func Number(n int64) Value {
|
||||
return Value{Number: &n}
|
||||
}
|
||||
|
||||
// IsZero returns true if the Value is the zero value (none of the sub-value
|
||||
// fields are set). LexerToken is ignored for this check.
|
||||
func (v Value) IsZero() bool {
|
||||
|
294
vm/op.go
294
vm/op.go
@ -1,75 +1,33 @@
|
||||
package vm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/mediocregopher/ginger/gg"
|
||||
"github.com/mediocregopher/ginger/graph"
|
||||
)
|
||||
|
||||
var (
|
||||
outVal = nameVal("out")
|
||||
)
|
||||
|
||||
// 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)
|
||||
|
||||
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.
|
||||
//
|
||||
// The Operation passed into Perform is the Operation which is calling the
|
||||
// Perform. It may be nil.
|
||||
// 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([]Thunk, Operation) (Thunk, error)
|
||||
Perform(Value) Value
|
||||
}
|
||||
|
||||
func preEvalValOp(fn func(Value) (Value, error)) Operation {
|
||||
// OperationFunc is a function which implements the Operation interface.
|
||||
type OperationFunc func(Value) Value
|
||||
|
||||
return OperationFunc(func(args []Thunk, _ Operation) (Thunk, error) {
|
||||
|
||||
return func() (Value, error) {
|
||||
|
||||
val, err := evalThunks(args)()
|
||||
|
||||
if err != nil {
|
||||
return ZeroValue, err
|
||||
}
|
||||
|
||||
return fn(val)
|
||||
|
||||
}, nil
|
||||
// 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
|
||||
})
|
||||
}
|
||||
|
||||
@ -78,32 +36,200 @@ type graphOp struct {
|
||||
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 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,
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
func (g *graphOp) Perform(args []Thunk, _ Operation) (Thunk, error) {
|
||||
return ScopeFromGraph(
|
||||
g.Graph,
|
||||
evalThunks(args),
|
||||
g.scope,
|
||||
g,
|
||||
).Evaluate(outVal)
|
||||
}
|
||||
name := *val.Name
|
||||
|
||||
// OperationFunc is a function which implements the Operation interface.
|
||||
type OperationFunc func([]Thunk, Operation) (Thunk, error)
|
||||
if val.Equal(valNameIn) {
|
||||
return edgeOp(OperationFunc(func(inArg Value) Value {
|
||||
return inArg
|
||||
})), nil
|
||||
}
|
||||
|
||||
// Perform calls the underlying OperationFunc directly.
|
||||
func (f OperationFunc) Perform(args []Thunk, op Operation) (Thunk, error) {
|
||||
return f(args, op)
|
||||
// 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
|
||||
}
|
||||
|
169
vm/scope.go
169
vm/scope.go
@ -4,108 +4,35 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/mediocregopher/ginger/gg"
|
||||
"github.com/mediocregopher/ginger/graph"
|
||||
)
|
||||
|
||||
// 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.
|
||||
// Scope encapsulates a set of name->Value mappings.
|
||||
type Scope interface {
|
||||
|
||||
// Evaluate accepts a name Value and returns a Thunk which will return the
|
||||
// real Value which that name points to.
|
||||
Evaluate(Value) (Thunk, error)
|
||||
// Resolve accepts a name and returns an Value.
|
||||
Resolve(string) (Value, error)
|
||||
|
||||
// NewScope returns a new Scope which sub-operations within this Scope
|
||||
// should use for themselves.
|
||||
NewScope() Scope
|
||||
}
|
||||
|
||||
// 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.
|
||||
//
|
||||
// The Operation is the Operation which is evaluating the edge, if any.
|
||||
func EvaluateEdge(edge *gg.OpenEdge, scope Scope, op Operation) (Value, error) {
|
||||
|
||||
thunk, err := graph.MapReduce[gg.Value, gg.Value, Thunk](
|
||||
edge,
|
||||
func(ggVal gg.Value) (Thunk, error) {
|
||||
|
||||
val := Value{Value: ggVal}
|
||||
|
||||
if val.Name != nil {
|
||||
return scope.Evaluate(val)
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
nameThunk, err := scope.Evaluate(edgeVal)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
} else if edgeVal, err = nameThunk(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if edgeVal.Graph != nil {
|
||||
|
||||
edgeVal = Value{
|
||||
Operation: OperationFromGraph(
|
||||
edgeVal.Graph,
|
||||
scope.NewScope(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
if edgeVal.Operation == nil {
|
||||
return nil, fmt.Errorf("edge value must be an operation")
|
||||
}
|
||||
|
||||
return edgeVal.Operation.Perform(args, op)
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return ZeroValue, err
|
||||
}
|
||||
|
||||
return thunk()
|
||||
}
|
||||
|
||||
// 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) (Thunk, error) {
|
||||
// Resolve uses the given name as a key into the ScopeMap map, and
|
||||
// returns the Operation held there for the key, if any.
|
||||
func (m ScopeMap) Resolve(name string) (Value, error) {
|
||||
|
||||
if nameVal.Name == nil {
|
||||
return nil, fmt.Errorf("value %v is not a name value", nameVal)
|
||||
}
|
||||
|
||||
val, ok := m[*nameVal.Name]
|
||||
v, ok := m[name]
|
||||
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%q not defined", *nameVal.Name)
|
||||
return Value{}, fmt.Errorf("%q not defined", name)
|
||||
}
|
||||
|
||||
return valThunk(val), nil
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// NewScope returns the ScopeMap as-is.
|
||||
@ -113,65 +40,79 @@ func (m ScopeMap) NewScope() Scope {
|
||||
return m
|
||||
}
|
||||
|
||||
type graphScope struct {
|
||||
*gg.Graph
|
||||
in Thunk
|
||||
parent Scope
|
||||
op Operation
|
||||
type scopeWith struct {
|
||||
Scope // parent
|
||||
name string
|
||||
val Value
|
||||
}
|
||||
|
||||
// ScopeFromGraph returns a Scope which will use the given Graph for evaluation.
|
||||
// ScopeWith returns a copy of the given Scope, except that evaluating the given
|
||||
// name will always return the given Value.
|
||||
func ScopeWith(scope Scope, name string, val Value) Scope {
|
||||
return &scopeWith{
|
||||
Scope: scope,
|
||||
name: name,
|
||||
val: val,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *scopeWith) Resolve(name string) (Value, error) {
|
||||
if name == s.name {
|
||||
return s.val, nil
|
||||
}
|
||||
return s.Scope.Resolve(name)
|
||||
}
|
||||
|
||||
type graphScope struct {
|
||||
*gg.Graph
|
||||
parent Scope
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
TODO I don't think this is actually necessary
|
||||
|
||||
// ScopeFromGraph returns a Scope which will use the given Graph for name
|
||||
// resolution.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// 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.
|
||||
// When a name is resolved, 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
|
||||
// compiled into an Operation and returned.
|
||||
//
|
||||
// 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 resolve 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, in Thunk, parent Scope, op Operation) Scope {
|
||||
func ScopeFromGraph(g *gg.Graph, parent Scope) Scope {
|
||||
return &graphScope{
|
||||
Graph: g,
|
||||
in: in,
|
||||
parent: parent,
|
||||
op: op,
|
||||
}
|
||||
}
|
||||
|
||||
func (g *graphScope) Evaluate(nameVal Value) (Thunk, error) {
|
||||
func (g *graphScope) Resolve(name string) (Value, error) {
|
||||
|
||||
if nameVal.Name == nil {
|
||||
return nil, fmt.Errorf("value %v is not a name value", nameVal)
|
||||
}
|
||||
var ggNameVal gg.Value
|
||||
ggNameVal.Name = &name
|
||||
|
||||
if *nameVal.Name == "in" {
|
||||
return g.in, nil
|
||||
}
|
||||
|
||||
edgesIn := g.ValueIns(nameVal.Value)
|
||||
log.Printf("resolving %q", name)
|
||||
edgesIn := g.ValueIns(ggNameVal)
|
||||
|
||||
if l := len(edgesIn); l == 0 && g.parent != nil {
|
||||
|
||||
return g.parent.Evaluate(nameVal)
|
||||
return g.parent.Resolve(name)
|
||||
|
||||
} else if l != 1 {
|
||||
|
||||
return nil, fmt.Errorf(
|
||||
"%q must have exactly one input edge, found %d input edges",
|
||||
*nameVal.Name, l,
|
||||
name, l,
|
||||
)
|
||||
}
|
||||
|
||||
return func() (Value, error) {
|
||||
return EvaluateEdge(edgesIn[0], g, g.op)
|
||||
}, nil
|
||||
return CompileEdge(edgesIn[0], g)
|
||||
}
|
||||
|
||||
func (g *graphScope) NewScope() Scope {
|
||||
@ -182,3 +123,5 @@ func (g *graphScope) NewScope() Scope {
|
||||
|
||||
return g.parent
|
||||
}
|
||||
|
||||
*/
|
||||
|
@ -6,11 +6,23 @@ import (
|
||||
"github.com/mediocregopher/ginger/gg"
|
||||
)
|
||||
|
||||
func globalOp(fn func(Value) (Value, error)) Value {
|
||||
return Value{
|
||||
Operation: OperationFunc(func(in Value) Value {
|
||||
res, err := fn(in)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return res
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
"add": globalOp(func(val Value) (Value, error) {
|
||||
|
||||
var sum int64
|
||||
|
||||
@ -25,17 +37,17 @@ var GlobalScope = ScopeMap{
|
||||
|
||||
return Value{Value: gg.Value{Number: &sum}}, nil
|
||||
|
||||
})},
|
||||
}),
|
||||
|
||||
"tupEl": Value{Operation: preEvalValOp(func(val Value) (Value, error) {
|
||||
"tupEl": globalOp(func(val Value) (Value, error) {
|
||||
|
||||
tup, i := val.Tuple[0], val.Tuple[1]
|
||||
|
||||
return tup.Tuple[int(*i.Number)], nil
|
||||
|
||||
})},
|
||||
}),
|
||||
|
||||
"isZero": Value{Operation: preEvalValOp(func(val Value) (Value, error) {
|
||||
"isZero": globalOp(func(val Value) (Value, error) {
|
||||
|
||||
if *val.Number == 0 {
|
||||
one := int64(1)
|
||||
@ -45,33 +57,5 @@ var GlobalScope = ScopeMap{
|
||||
zero := int64(0)
|
||||
return Value{Value: gg.Value{Number: &zero}}, nil
|
||||
|
||||
})},
|
||||
|
||||
"if": Value{Operation: OperationFunc(func(args []Thunk, _ Operation) (Thunk, error) {
|
||||
|
||||
b := args[0]
|
||||
onTrue := args[1]
|
||||
onFalse := args[2]
|
||||
|
||||
return func() (Value, error) {
|
||||
|
||||
bVal, err := b()
|
||||
|
||||
if err != nil {
|
||||
return ZeroValue, err
|
||||
}
|
||||
|
||||
if *bVal.Number == 0 {
|
||||
return onFalse()
|
||||
}
|
||||
|
||||
return onTrue()
|
||||
|
||||
}, nil
|
||||
|
||||
})},
|
||||
|
||||
"recur": Value{Operation: OperationFunc(func(args []Thunk, op Operation) (Thunk, error) {
|
||||
return op.Perform(args, op)
|
||||
})},
|
||||
}),
|
||||
}
|
||||
|
19
vm/vm.go
19
vm/vm.go
@ -22,6 +22,12 @@ type Value struct {
|
||||
Tuple []Value
|
||||
}
|
||||
|
||||
// Tuple returns a tuple Value comprising the given Values. Calling Tuple with
|
||||
// no arguments returns ZeroValue.
|
||||
func Tuple(vals ...Value) Value {
|
||||
return Value{Tuple: vals}
|
||||
}
|
||||
|
||||
// IsZero returns true if the Value is the zero value (aka the 0-tuple).
|
||||
// LexerToken (within the gg.Value) is ignored for this check.
|
||||
func (v Value) IsZero() bool {
|
||||
@ -105,7 +111,7 @@ func nameVal(n string) Value {
|
||||
//
|
||||
// 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) {
|
||||
func EvaluateSource(opSrc io.Reader, input Value, scope Scope) (Value, error) {
|
||||
lexer := gg.NewLexer(opSrc)
|
||||
|
||||
g, err := gg.DecodeLexer(lexer)
|
||||
@ -113,16 +119,11 @@ func EvaluateSource(opSrc io.Reader, input gg.Value, scope Scope) (Value, error)
|
||||
return Value{}, err
|
||||
}
|
||||
|
||||
op := OperationFromGraph(g, scope.NewScope())
|
||||
|
||||
thunk, err := op.Perform(
|
||||
[]Thunk{valThunk(Value{Value: input})},
|
||||
nil,
|
||||
)
|
||||
op, err := OperationFromGraph(g, scope.NewScope())
|
||||
|
||||
if err != nil {
|
||||
return ZeroValue, err
|
||||
return Value{}, err
|
||||
}
|
||||
|
||||
return thunk()
|
||||
return op.Perform(input), nil
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ func TestVM(t *testing.T) {
|
||||
|
||||
val, err := EvaluateSource(
|
||||
bytes.NewBufferString(src),
|
||||
gg.Value{Number: &in},
|
||||
Value{Value: gg.Number(in)},
|
||||
GlobalScope,
|
||||
)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user