ginger/vm/vm.go

124 lines
2.7 KiB
Go

// Package vm implements the execution of gg.Graphs as programs.
package vm
import (
"errors"
"fmt"
"io"
"strings"
"code.betamike.com/mediocregopher/ginger/gg"
"code.betamike.com/mediocregopher/ginger/graph"
)
// ZeroValue is a Value with no fields set. It is equivalent to the 0-tuple.
var ZeroValue Value
// Value extends a gg.Value to include Functions and Tuples as a possible
// types.
type Value struct {
gg.Value
Function
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 {
return v.Equal(ZeroValue)
}
// Equal returns true if the passed in Value is equivalent, ignoring the
// LexerToken on either Value.
//
// Will panic if the passed in v2 is not a Value from this package.
func (v Value) Equal(v2g graph.Value) bool {
v2 := v2g.(Value)
switch {
case (v.Value != (gg.Value{}) || v2.Value != (gg.Value{})):
return v.Value.Equal(v2.Value)
case v.Function != nil || v2.Function != nil:
// for now we say that Functions can't be compared. This will probably
// get revisted later.
return false
case len(v.Tuple) == len(v2.Tuple):
for i := range v.Tuple {
if !v.Tuple[i].Equal(v2.Tuple[i]) {
return false
}
}
return true
default:
// if both were the zero value then both tuples would have the same
// length (0), which is covered by the previous check. So anything left
// over must be tuples with differing lengths.
return false
}
}
func (v Value) String() string {
switch {
case v.Function != nil:
// We can try to get better strings for ops later
return "<fn>"
case v.Value != (gg.Value{}):
return v.Value.String()
default:
// we consider zero value to be the 0-tuple
strs := make([]string, len(v.Tuple))
for i := range v.Tuple {
strs[i] = v.Tuple[i].String()
}
return fmt.Sprintf("(%s)", strings.Join(strs, ", "))
}
}
// 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 Value, scope Scope) (Value, error) {
v, err := gg.NewDecoder(opSrc).Next()
if err != nil {
return Value{}, err
} else if v.Value.Graph == nil {
return Value{}, errors.New("value must be a graph")
}
fn, err := FunctionFromGraph(v.Value.Graph, scope)
if err != nil {
return Value{}, err
}
return fn.Perform(input), nil
}