Builtins start with exclamation point
This commit is contained in:
parent
3ef69920c7
commit
ec443899c3
@ -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<n, b, add<(a,b) ),
|
||||
!recur < ( decr<n, b, !add<(a,b) ),
|
||||
);
|
||||
|
||||
} < (in, 0, 1);
|
||||
} < (!in, 0, 1);
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ package gg
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"strconv"
|
||||
"unicode"
|
||||
|
||||
@ -69,19 +68,18 @@ var (
|
||||
)
|
||||
|
||||
var (
|
||||
letter = RuneFunc(
|
||||
"letter",
|
||||
func(r rune) bool {
|
||||
return unicode.In(r, unicode.Letter, unicode.Mark)
|
||||
},
|
||||
nameHead = FirstOf(
|
||||
RuneFunc("letter", unicode.IsLetter),
|
||||
RuneFunc("mark", unicode.IsMark),
|
||||
Rune('!'),
|
||||
)
|
||||
|
||||
nameTail = ZeroOrMore(FirstOf(letter, digit))
|
||||
nameTail = ZeroOrMore(FirstOf(nameHead, digit))
|
||||
|
||||
name = Named(
|
||||
"name",
|
||||
Reduction(
|
||||
letter,
|
||||
nameHead,
|
||||
nameTail,
|
||||
func(head Located[rune], tail []Located[rune]) Located[Value] {
|
||||
name := make([]rune, 0, len(tail)+1)
|
||||
@ -244,7 +242,6 @@ var graphSym, value = func() (
|
||||
},
|
||||
),
|
||||
)
|
||||
log.Printf("populated graphTail with %v", graphTail)
|
||||
|
||||
graphOpenEdge.Symbol = FirstOf(
|
||||
Reduction[Located[Value], graphState, graphState](
|
||||
@ -269,7 +266,6 @@ var graphSym, value = func() (
|
||||
graphEnd,
|
||||
Prefixed[Located[rune], graphState](trimmedRune(';'), graphTail),
|
||||
)
|
||||
log.Printf("populated graphOpenEdgeTail with %v", graphOpenEdgeTail)
|
||||
|
||||
graphOpenEdgeValueTail.Symbol = FirstOf[graphState](
|
||||
graphOpenEdgeTail,
|
||||
|
@ -65,6 +65,7 @@ func TestDecoder(t *testing.T) {
|
||||
{in: `ab`, exp: expName(1, 1, "ab")},
|
||||
{in: `ab2c`, exp: expName(1, 1, "ab2c")},
|
||||
{in: `ab2c,`, exp: expName(1, 1, "ab2c")},
|
||||
{in: `!ab2c,`, exp: expName(1, 1, "!ab2c")},
|
||||
})
|
||||
|
||||
runTests(t, "graph", graphSym, []test{
|
||||
|
@ -3,7 +3,9 @@
|
||||
<negative-number> ::= "-" <positive-number>
|
||||
<number> ::= <negative-number> | <positive-number>
|
||||
|
||||
<name> ::= (<letter> | <mark>) (<letter> | <mark> | <digit>)*
|
||||
<name-head> ::= <letter> | <mark> | "!"
|
||||
<name-tail> ::= <name-head> | <digit>
|
||||
<name> ::= <name-head> <name-tail>*
|
||||
|
||||
<tuple> ::= "(" <tuple-tail>
|
||||
<tuple-tail> ::= ")" | <tuple-open-edge>
|
||||
|
@ -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 {
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
|
@ -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) {
|
||||
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 '!'",
|
||||
},
|
||||
}
|
||||
|
||||
src := `{
|
||||
incr = { out = add < (1, in); };
|
||||
for i, test := range tests {
|
||||
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
||||
val, err := EvaluateSource(
|
||||
bytes.NewBufferString(test.src), test.in, GlobalScope,
|
||||
)
|
||||
|
||||
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)
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user