2021-12-28 20:18:01 +00:00
|
|
|
// Package vm implements the execution of gg.Graphs as programs.
|
|
|
|
package vm
|
|
|
|
|
|
|
|
import (
|
|
|
|
"io"
|
2021-12-29 19:32:53 +00:00
|
|
|
"fmt"
|
|
|
|
"strings"
|
2021-12-28 20:18:01 +00:00
|
|
|
|
|
|
|
"github.com/mediocregopher/ginger/gg"
|
2021-12-29 19:32:53 +00:00
|
|
|
"github.com/mediocregopher/ginger/graph"
|
2021-12-28 20:18:01 +00:00
|
|
|
)
|
|
|
|
|
2021-12-29 19:32:53 +00:00
|
|
|
// ZeroValue is a Value with no fields set. It is equivalent to the 0-tuple.
|
|
|
|
var ZeroValue Value
|
|
|
|
|
2021-12-28 20:18:01 +00:00
|
|
|
// Value extends a gg.Value to include Operations and Tuples as a possible
|
|
|
|
// types.
|
|
|
|
type Value struct {
|
|
|
|
gg.Value
|
|
|
|
|
|
|
|
Operation
|
|
|
|
Tuple []Value
|
|
|
|
}
|
|
|
|
|
2021-12-29 19:32:53 +00:00
|
|
|
// 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.IsZero() || !v2.Value.IsZero():
|
|
|
|
return v.Value.Equal(v2.Value)
|
|
|
|
|
|
|
|
case v.Operation != nil || v2.Operation != nil:
|
|
|
|
// for now we say that Operations 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.Operation != nil:
|
|
|
|
|
|
|
|
// We can try to get better strings for ops later
|
|
|
|
return "<op>"
|
|
|
|
|
|
|
|
case !v.Value.IsZero():
|
|
|
|
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, ", "))
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2021-12-28 20:18:01 +00:00
|
|
|
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())
|
|
|
|
|
2021-12-30 22:10:25 +00:00
|
|
|
thunk, err := op.Perform(
|
|
|
|
[]Thunk{valThunk(Value{Value: input})},
|
|
|
|
nil,
|
|
|
|
)
|
2021-12-30 21:00:04 +00:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return ZeroValue, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return thunk()
|
2021-12-28 20:18:01 +00:00
|
|
|
}
|