2be865181d
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.
128 lines
2.5 KiB
Go
128 lines
2.5 KiB
Go
package vm
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/mediocregopher/ginger/gg"
|
|
)
|
|
|
|
// Scope encapsulates a set of name->Value mappings.
|
|
type Scope interface {
|
|
|
|
// 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
|
|
}
|
|
|
|
// ScopeMap implements the Scope interface.
|
|
type ScopeMap map[string]Value
|
|
|
|
var _ Scope = ScopeMap{}
|
|
|
|
// 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) {
|
|
|
|
v, ok := m[name]
|
|
|
|
if !ok {
|
|
return Value{}, fmt.Errorf("%q not defined", name)
|
|
}
|
|
|
|
return v, nil
|
|
}
|
|
|
|
// NewScope returns the ScopeMap as-is.
|
|
func (m ScopeMap) NewScope() Scope {
|
|
return m
|
|
}
|
|
|
|
type scopeWith struct {
|
|
Scope // parent
|
|
name string
|
|
val Value
|
|
}
|
|
|
|
// 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 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 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, parent Scope) Scope {
|
|
return &graphScope{
|
|
Graph: g,
|
|
parent: parent,
|
|
}
|
|
}
|
|
|
|
func (g *graphScope) Resolve(name string) (Value, error) {
|
|
|
|
var ggNameVal gg.Value
|
|
ggNameVal.Name = &name
|
|
|
|
log.Printf("resolving %q", name)
|
|
edgesIn := g.ValueIns(ggNameVal)
|
|
|
|
if l := len(edgesIn); l == 0 && g.parent != nil {
|
|
|
|
return g.parent.Resolve(name)
|
|
|
|
} else if l != 1 {
|
|
|
|
return nil, fmt.Errorf(
|
|
"%q must have exactly one input edge, found %d input edges",
|
|
name, l,
|
|
)
|
|
}
|
|
|
|
return CompileEdge(edgesIn[0], g)
|
|
}
|
|
|
|
func (g *graphScope) NewScope() Scope {
|
|
|
|
if g.parent == nil {
|
|
return ScopeMap{}
|
|
}
|
|
|
|
return g.parent
|
|
}
|
|
|
|
*/
|