diff --git a/examples/fib.gg b/examples/fib.gg index 9c875ff..b99249d 100644 --- a/examples/fib.gg +++ b/examples/fib.gg @@ -1,24 +1,24 @@ * A function which accepts a number N and returns the Nth fibonacci number { * We are passing a tuple of inputs into a graph here, such that the graph is - * evaluated as an anonymous function. That anonymous function uses recur + * evaluated as an anonymous function. That anonymous function uses !recur * internally to compute the result. - out = { + !out = { * A little helper function. - decr = { out = add < (in, -1) }; + decr = { !out = !add < (!in, -1) }; * Deconstruct the input tuple into its individual elements, for clarity. * There will be a more ergonomic way of doing this one day. - n = tupEl < (in, 0); - a = tupEl < (in, 1); - b = tupEl < (in, 2); + n = !tupEl < (!in, 0); + a = !tupEl < (!in, 1); + b = !tupEl < (!in, 2); - out = if < ( - isZero < n, + !out = !if < ( + !isZero < n, a, - recur < ( decr ::= "-" ::= | - ::= ( | ) ( | | )* + ::= | | "!" + ::= | + ::= * ::= "(" ::= ")" | diff --git a/graph/graph.go b/graph/graph.go index ea4a61e..c6dda18 100644 --- a/graph/graph.go +++ b/graph/graph.go @@ -225,6 +225,8 @@ func (g *Graph[E, V]) dedupeEdge(edge *OpenEdge[E, V]) *OpenEdge[E, V] { // Graph (ie, all those added via AddValueIn). // // The returned slice should not be modified. +// +// TODO better name might be OpenEdgesInto. func (g *Graph[E, V]) ValueIns(val Value) []*OpenEdge[E, V] { var edges []*OpenEdge[E, V] @@ -261,6 +263,16 @@ func (g *Graph[E, V]) AddValueIn(val V, oe *OpenEdge[E, V]) *Graph[E, V] { return g } +// AllValueIns returns all values which have had incoming edges added to them +// using AddValueIn. +func (g *Graph[E, V]) AllValueIns() []V { + vals := make([]V, len(g.valIns)) + for i := range g.valIns { + vals[i] = g.valIns[i].val + } + return vals +} + // Equal returns whether or not the two Graphs are equivalent in value. func (g *Graph[E, V]) Equal(g2 *Graph[E, V]) bool { diff --git a/vm/function.go b/vm/function.go index 9fb9d5d..5c6a4ad 100644 --- a/vm/function.go +++ b/vm/function.go @@ -32,18 +32,38 @@ func Identity(val Value) Function { } var ( - valNameIn = Value{Value: gg.Name("in")} - valNameOut = Value{Value: gg.Name("out")} - valNameIf = Value{Value: gg.Name("if")} - valNameRecur = Value{Value: gg.Name("recur")} + valNameIn = Value{Value: gg.Name("!in")} + valNameOut = Value{Value: gg.Name("!out")} + valNameIf = Value{Value: gg.Name("!if")} + valNameRecur = Value{Value: gg.Name("!recur")} valNumberZero = Value{Value: gg.Number(0)} ) +func checkGraphForFunction(g *gg.Graph) error { + for _, val := range g.AllValueIns() { + if val.Name == nil { + return fmt.Errorf("non-name %v cannot have incoming edges", val) + } + + if !(Value{Value: val}).Equal(valNameOut) && (*val.Name)[0] == '!' { + return fmt.Errorf("name %v cannot start with a '!'", val) + } + } + + // TODO check for acyclic-ness + + return nil +} + // FunctionFromGraph wraps the given Graph such that it can be used as an // Function. The given Scope determines what values outside of the Graph are // available for use within the Function. func FunctionFromGraph(g *gg.Graph, scope Scope) (Function, error) { + if err := checkGraphForFunction(g); err != nil { + return nil, err + } + // edgeFn is distinct from a generic Function in that the Value passed into // Perform will _always_ be the value of "in" for the overall Function. // @@ -67,8 +87,6 @@ func FunctionFromGraph(g *gg.Graph, scope Scope) (Function, error) { })), nil } - // TODO intercept if and recur? - edgesIn := g.ValueIns(val.Value) if l := len(edgesIn); l == 0 { @@ -109,7 +127,7 @@ func FunctionFromGraph(g *gg.Graph, scope Scope) (Function, error) { if ggEdgeVal.Equal(valNameIf.Value) { if len(inEdgeFns) != 3 { - return nil, fmt.Errorf("'if' requires a 3-tuple argument") + return nil, fmt.Errorf("'!if' requires a 3-tuple argument") } return edgeFn(FunctionFunc(func(inArg Value) Value { @@ -123,7 +141,7 @@ func FunctionFromGraph(g *gg.Graph, scope Scope) (Function, error) { })), nil } - // "if" statements (above) are the only case where we want the + // "!if" statements (above) are the only case where we want the // input edges to remain separated, otherwise they should always // be combined into a single edge whose value is a tuple. Do // that here. diff --git a/vm/scope_global.go b/vm/scope_global.go index e36889e..96780b2 100644 --- a/vm/scope_global.go +++ b/vm/scope_global.go @@ -22,7 +22,7 @@ func globalFn(fn func(Value) (Value, error)) Value { // any operation in a ginger program. var GlobalScope = ScopeMap{ - "add": globalFn(func(val Value) (Value, error) { + "!add": globalFn(func(val Value) (Value, error) { var sum int64 @@ -39,7 +39,7 @@ var GlobalScope = ScopeMap{ }), - "tupEl": globalFn(func(val Value) (Value, error) { + "!tupEl": globalFn(func(val Value) (Value, error) { tup, i := val.Tuple[0], val.Tuple[1] @@ -47,7 +47,7 @@ var GlobalScope = ScopeMap{ }), - "isZero": globalFn(func(val Value) (Value, error) { + "!isZero": globalFn(func(val Value) (Value, error) { if *val.Number == 0 { one := int64(1) diff --git a/vm/vm_test.go b/vm/vm_test.go index 18c093b..318d269 100644 --- a/vm/vm_test.go +++ b/vm/vm_test.go @@ -2,6 +2,7 @@ package vm import ( "bytes" + "strconv" "testing" "code.betamike.com/mediocregopher/ginger/gg" @@ -9,21 +10,43 @@ import ( ) func TestVM(t *testing.T) { - - src := `{ - incr = { out = add < (1, in); }; - - out = incr < incr < in; - }` - - var in int64 = 5 - - val, err := EvaluateSource( - bytes.NewBufferString(src), - Value{Value: gg.Number(in)}, - GlobalScope, - ) - - assert.NoError(t, err) - assert.Equal(t, in+2, *val.Number) + tests := []struct { + src string + in Value + exp Value + expErr string + }{ + { + src: `{ + incr = { !out = !add < (1, !in); }; + !out = incr < incr < !in; + }`, + in: Value{Value: gg.Number(5)}, + exp: Value{Value: gg.Number(7)}, + }, + { + src: `{ + !foo = in; + !out = !foo; + }`, + in: Value{Value: gg.Number(1)}, + expErr: "name !foo cannot start with a '!'", + }, + } + + for i, test := range tests { + t.Run(strconv.Itoa(i), func(t *testing.T) { + val, err := EvaluateSource( + bytes.NewBufferString(test.src), test.in, GlobalScope, + ) + + if test.expErr != "" { + assert.Error(t, err) + assert.Equal(t, test.expErr, err.Error()) + } else { + assert.NoError(t, err) + assert.True(t, val.Equal(test.exp), "%v != %v", test.exp, val) + } + }) + } }