Compare commits

...

3 Commits

5 changed files with 79 additions and 29 deletions

53
examples/examples_test.go Normal file
View File

@ -0,0 +1,53 @@
package examples_test
import (
"embed"
"fmt"
"testing"
"code.betamike.com/mediocregopher/ginger/gg"
"code.betamike.com/mediocregopher/ginger/vm"
"github.com/stretchr/testify/assert"
)
//go:embed *.gg
var examplesFS embed.FS
func TestAllExamples(t *testing.T) {
tests := []struct {
path string
in vm.Value
exp vm.Value
}{
{
path: "fib.gg",
in: vm.Value{Value: gg.Number(5)},
exp: vm.Value{Value: gg.Number(5)},
},
{
path: "fib.gg",
in: vm.Value{Value: gg.Number(10)},
exp: vm.Value{Value: gg.Number(55)},
},
{
path: "fib.gg",
in: vm.Value{Value: gg.Number(69)},
exp: vm.Value{Value: gg.Number(117669030460994)},
},
}
for _, test := range tests {
t.Run(fmt.Sprintf("%s_%v", test.path, test.in), func(t *testing.T) {
f, err := examplesFS.Open(test.path)
if err != nil {
t.Fatal(err)
}
defer f.Close()
got, err := vm.EvaluateSource(f, test.in, vm.GlobalScope)
assert.NoError(t, err)
assert.True(t, test.exp.Equal(got), "%v != %v", test.exp, got)
})
}
}

View File

@ -1,6 +1,7 @@
package vm package vm
import ( import (
"errors"
"fmt" "fmt"
"code.betamike.com/mediocregopher/ginger/gg" "code.betamike.com/mediocregopher/ginger/gg"
@ -79,8 +80,6 @@ func FunctionFromGraph(g *gg.Graph, scope Scope) (Function, error) {
return edgeFn(Identity(val)), nil return edgeFn(Identity(val)), nil
} }
name := *val.Name
if val.Equal(valNameIn) { if val.Equal(valNameIn) {
return edgeFn(FunctionFunc(func(inArg Value) Value { return edgeFn(FunctionFunc(func(inArg Value) Value {
return inArg return inArg
@ -89,15 +88,17 @@ func FunctionFromGraph(g *gg.Graph, scope Scope) (Function, error) {
edgesIn := g.ValueIns(val.Value) edgesIn := g.ValueIns(val.Value)
name := *val.Name
if l := len(edgesIn); l == 0 { if l := len(edgesIn); l == 0 {
resolvedVal, err := scope.Resolve(name)
val, err := scope.Resolve(name) if errors.Is(err, ErrNameNotDefined) {
return edgeFn(Identity(val)), nil
if err != nil { } else if err != nil {
return nil, fmt.Errorf("resolving name %q from the outer scope: %w", name, err) return nil, fmt.Errorf("resolving name %q from the outer scope: %w", name, err)
} }
return edgeFn(Identity(val)), nil return edgeFn(Identity(resolvedVal)), nil
} else if l != 1 { } else if l != 1 {
return nil, fmt.Errorf("resolved name %q to %d input edges, rather than one", name, l) return nil, fmt.Errorf("resolved name %q to %d input edges, rather than one", name, l)
@ -123,7 +124,6 @@ func FunctionFromGraph(g *gg.Graph, scope Scope) (Function, error) {
return valToEdgeFn(Value{Value: ggVal}) return valToEdgeFn(Value{Value: ggVal})
}, },
func(ggEdgeVal gg.OptionalValue, inEdgeFns []edgeFn) (edgeFn, error) { func(ggEdgeVal gg.OptionalValue, inEdgeFns []edgeFn) (edgeFn, error) {
if ggEdgeVal.Equal(valNameIf.Value) { if ggEdgeVal.Equal(valNameIf.Value) {
if len(inEdgeFns) != 3 { if len(inEdgeFns) != 3 {
@ -177,11 +177,7 @@ func FunctionFromGraph(g *gg.Graph, scope Scope) (Function, error) {
if edgeVal.Graph != nil { if edgeVal.Graph != nil {
opFromGraph, err := FunctionFromGraph( opFromGraph, err := FunctionFromGraph(edgeVal.Graph, scope)
edgeVal.Graph,
scope.NewScope(),
)
if err != nil { if err != nil {
return nil, fmt.Errorf("compiling graph to operation: %w", err) return nil, fmt.Errorf("compiling graph to operation: %w", err)
} }
@ -198,7 +194,7 @@ func FunctionFromGraph(g *gg.Graph, scope Scope) (Function, error) {
})), nil })), nil
} }
// the edgeVal is not an Function at compile time, and so // the edgeVal is not a Function at compile time, and so
// it must become one at runtime. We must resolve edgeVal to an // it must become one at runtime. We must resolve edgeVal to an
// edgeFn as well (edgeEdgeFn), and then at runtime that is // edgeFn as well (edgeEdgeFn), and then at runtime that is
// given the inArg and (hopefully) the resultant Function is // given the inArg and (hopefully) the resultant Function is
@ -217,8 +213,7 @@ func FunctionFromGraph(g *gg.Graph, scope Scope) (Function, error) {
if runtimeEdgeVal.Graph != nil { if runtimeEdgeVal.Graph != nil {
runtimeFn, err := FunctionFromGraph( runtimeFn, err := FunctionFromGraph(
runtimeEdgeVal.Graph, runtimeEdgeVal.Graph, scope,
scope.NewScope(),
) )
if err != nil { if err != nil {

View File

@ -1,18 +1,19 @@
package vm package vm
import ( import (
"fmt" "errors"
) )
// ErrNameNotDefined is returned from Scope.Resolve when a name could not be
// resolved within a Scope.
var ErrNameNotDefined = errors.New("not defined")
// Scope encapsulates a set of name->Value mappings. // Scope encapsulates a set of name->Value mappings.
type Scope interface { type Scope interface {
// Resolve accepts a name and returns an Value. // Resolve accepts a name and returns an Value, or returns
// ErrNameNotDefined.
Resolve(string) (Value, error) 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. // ScopeMap implements the Scope interface.
@ -27,17 +28,12 @@ func (m ScopeMap) Resolve(name string) (Value, error) {
v, ok := m[name] v, ok := m[name]
if !ok { if !ok {
return Value{}, fmt.Errorf("%q not defined", name) return Value{}, ErrNameNotDefined
} }
return v, nil return v, nil
} }
// NewScope returns the ScopeMap as-is.
func (m ScopeMap) NewScope() Scope {
return m
}
type scopeWith struct { type scopeWith struct {
Scope // parent Scope // parent
name string name string

View File

@ -114,7 +114,7 @@ func EvaluateSource(opSrc io.Reader, input Value, scope Scope) (Value, error) {
return Value{}, errors.New("value must be a graph") return Value{}, errors.New("value must be a graph")
} }
fn, err := FunctionFromGraph(v.Value.Graph, scope.NewScope()) fn, err := FunctionFromGraph(v.Value.Graph, scope)
if err != nil { if err != nil {
return Value{}, err return Value{}, err
} }

View File

@ -32,10 +32,16 @@ func TestVM(t *testing.T) {
in: Value{Value: gg.Number(1)}, in: Value{Value: gg.Number(1)},
expErr: "name !foo cannot start with a '!'", expErr: "name !foo cannot start with a '!'",
}, },
{
src: `{foo = bar; !out = foo;}`,
in: Value{},
exp: Value{Value: gg.Name("bar")},
},
} }
for i, test := range tests { for i, test := range tests {
t.Run(strconv.Itoa(i), func(t *testing.T) { t.Run(strconv.Itoa(i), func(t *testing.T) {
t.Log(test.src)
val, err := EvaluateSource( val, err := EvaluateSource(
bytes.NewBufferString(test.src), test.in, GlobalScope, bytes.NewBufferString(test.src), test.in, GlobalScope,
) )