Compare commits
3 Commits
ec443899c3
...
22e14bbb3f
Author | SHA1 | Date | |
---|---|---|---|
22e14bbb3f | |||
a7a5018f38 | |||
4cde5179f1 |
53
examples/examples_test.go
Normal file
53
examples/examples_test.go
Normal 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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -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 {
|
||||||
|
20
vm/scope.go
20
vm/scope.go
@ -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
|
||||||
|
2
vm/vm.go
2
vm/vm.go
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user