ginger/vm/scope.go
Brian Picciano c4dd673bf4 Refactor Graph internals to deduplicate OpenEdges
This change required OpenEdges to be passed around as pointers, which in
turn required me to audit how value copying is being done everywhere and
simplify it in a few places. I think I covered all the bases.

The new internals of Graph allow the graph's actual structure to be
reflected within the graph itself. For example, if the OpenEdges of two
ValueIns are equivalent then the same OpenEdge pointer is shared for the
two internally. This applies recursively, so if two OpenEdge tuples
share an inner OpenEdge, they will share the same pointer.

This change is a preliminary requirement for creating a graph mapping
operation. Without OpenEdge deduplication the map operation would end up
operating on the same OpenEdge multiple times, which would be incorrect.
2021-12-29 13:57:14 -07:00

180 lines
3.9 KiB
Go

package vm
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.
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 *graph.OpenEdge[gg.Value], 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 *graph.OpenEdge[gg.Value], 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 {
*graph.Graph[gg.Value]
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 *graph.Graph[gg.Value], 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
}