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
|
* 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
|
* 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.
|
* internally to compute the result.
|
||||||
out = {
|
!out = {
|
||||||
|
|
||||||
* A little helper function.
|
* A little helper function.
|
||||||
decr = { out = add < (in, -1) };
|
decr = { !out = !add < (!in, -1) };
|
||||||
|
|
||||||
* Deconstruct the input tuple into its individual elements, for clarity.
|
* Deconstruct the input tuple into its individual elements, for clarity.
|
||||||
* There will be a more ergonomic way of doing this one day.
|
* There will be a more ergonomic way of doing this one day.
|
||||||
n = tupEl < (in, 0);
|
n = !tupEl < (!in, 0);
|
||||||
a = tupEl < (in, 1);
|
a = !tupEl < (!in, 1);
|
||||||
b = tupEl < (in, 2);
|
b = !tupEl < (!in, 2);
|
||||||
|
|
||||||
out = if < (
|
!out = !if < (
|
||||||
isZero < n,
|
!isZero < n,
|
||||||
a,
|
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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
@ -69,19 +68,18 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
letter = RuneFunc(
|
nameHead = FirstOf(
|
||||||
"letter",
|
RuneFunc("letter", unicode.IsLetter),
|
||||||
func(r rune) bool {
|
RuneFunc("mark", unicode.IsMark),
|
||||||
return unicode.In(r, unicode.Letter, unicode.Mark)
|
Rune('!'),
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
nameTail = ZeroOrMore(FirstOf(letter, digit))
|
nameTail = ZeroOrMore(FirstOf(nameHead, digit))
|
||||||
|
|
||||||
name = Named(
|
name = Named(
|
||||||
"name",
|
"name",
|
||||||
Reduction(
|
Reduction(
|
||||||
letter,
|
nameHead,
|
||||||
nameTail,
|
nameTail,
|
||||||
func(head Located[rune], tail []Located[rune]) Located[Value] {
|
func(head Located[rune], tail []Located[rune]) Located[Value] {
|
||||||
name := make([]rune, 0, len(tail)+1)
|
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(
|
graphOpenEdge.Symbol = FirstOf(
|
||||||
Reduction[Located[Value], graphState, graphState](
|
Reduction[Located[Value], graphState, graphState](
|
||||||
@ -269,7 +266,6 @@ var graphSym, value = func() (
|
|||||||
graphEnd,
|
graphEnd,
|
||||||
Prefixed[Located[rune], graphState](trimmedRune(';'), graphTail),
|
Prefixed[Located[rune], graphState](trimmedRune(';'), graphTail),
|
||||||
)
|
)
|
||||||
log.Printf("populated graphOpenEdgeTail with %v", graphOpenEdgeTail)
|
|
||||||
|
|
||||||
graphOpenEdgeValueTail.Symbol = FirstOf[graphState](
|
graphOpenEdgeValueTail.Symbol = FirstOf[graphState](
|
||||||
graphOpenEdgeTail,
|
graphOpenEdgeTail,
|
||||||
|
@ -65,6 +65,7 @@ func TestDecoder(t *testing.T) {
|
|||||||
{in: `ab`, exp: expName(1, 1, "ab")},
|
{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")},
|
{in: `ab2c,`, exp: expName(1, 1, "ab2c")},
|
||||||
|
{in: `!ab2c,`, exp: expName(1, 1, "!ab2c")},
|
||||||
})
|
})
|
||||||
|
|
||||||
runTests(t, "graph", graphSym, []test{
|
runTests(t, "graph", graphSym, []test{
|
||||||
|
@ -3,7 +3,9 @@
|
|||||||
<negative-number> ::= "-" <positive-number>
|
<negative-number> ::= "-" <positive-number>
|
||||||
<number> ::= <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> ::= "(" <tuple-tail>
|
||||||
<tuple-tail> ::= ")" | <tuple-open-edge>
|
<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).
|
// Graph (ie, all those added via AddValueIn).
|
||||||
//
|
//
|
||||||
// The returned slice should not be modified.
|
// The returned slice should not be modified.
|
||||||
|
//
|
||||||
|
// TODO better name might be OpenEdgesInto.
|
||||||
func (g *Graph[E, V]) ValueIns(val Value) []*OpenEdge[E, V] {
|
func (g *Graph[E, V]) ValueIns(val Value) []*OpenEdge[E, V] {
|
||||||
|
|
||||||
var edges []*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
|
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.
|
// Equal returns whether or not the two Graphs are equivalent in value.
|
||||||
func (g *Graph[E, V]) Equal(g2 *Graph[E, V]) bool {
|
func (g *Graph[E, V]) Equal(g2 *Graph[E, V]) bool {
|
||||||
|
|
||||||
|
@ -32,18 +32,38 @@ func Identity(val Value) Function {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
valNameIn = Value{Value: gg.Name("in")}
|
valNameIn = Value{Value: gg.Name("!in")}
|
||||||
valNameOut = Value{Value: gg.Name("out")}
|
valNameOut = Value{Value: gg.Name("!out")}
|
||||||
valNameIf = Value{Value: gg.Name("if")}
|
valNameIf = Value{Value: gg.Name("!if")}
|
||||||
valNameRecur = Value{Value: gg.Name("recur")}
|
valNameRecur = Value{Value: gg.Name("!recur")}
|
||||||
valNumberZero = Value{Value: gg.Number(0)}
|
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
|
// 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
|
// Function. The given Scope determines what values outside of the Graph are
|
||||||
// available for use within the Function.
|
// available for use within the Function.
|
||||||
func FunctionFromGraph(g *gg.Graph, scope Scope) (Function, error) {
|
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
|
// 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.
|
// 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
|
})), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO intercept if and recur?
|
|
||||||
|
|
||||||
edgesIn := g.ValueIns(val.Value)
|
edgesIn := g.ValueIns(val.Value)
|
||||||
|
|
||||||
if l := len(edgesIn); l == 0 {
|
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 ggEdgeVal.Equal(valNameIf.Value) {
|
||||||
|
|
||||||
if len(inEdgeFns) != 3 {
|
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 {
|
return edgeFn(FunctionFunc(func(inArg Value) Value {
|
||||||
@ -123,7 +141,7 @@ func FunctionFromGraph(g *gg.Graph, scope Scope) (Function, error) {
|
|||||||
})), nil
|
})), 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
|
// input edges to remain separated, otherwise they should always
|
||||||
// be combined into a single edge whose value is a tuple. Do
|
// be combined into a single edge whose value is a tuple. Do
|
||||||
// that here.
|
// that here.
|
||||||
|
@ -22,7 +22,7 @@ func globalFn(fn func(Value) (Value, error)) Value {
|
|||||||
// any operation in a ginger program.
|
// any operation in a ginger program.
|
||||||
var GlobalScope = ScopeMap{
|
var GlobalScope = ScopeMap{
|
||||||
|
|
||||||
"add": globalFn(func(val Value) (Value, error) {
|
"!add": globalFn(func(val Value) (Value, error) {
|
||||||
|
|
||||||
var sum int64
|
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]
|
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 {
|
if *val.Number == 0 {
|
||||||
one := int64(1)
|
one := int64(1)
|
||||||
|
@ -2,6 +2,7 @@ package vm
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.betamike.com/mediocregopher/ginger/gg"
|
"code.betamike.com/mediocregopher/ginger/gg"
|
||||||
@ -9,21 +10,43 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestVM(t *testing.T) {
|
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 := `{
|
for i, test := range tests {
|
||||||
incr = { out = add < (1, in); };
|
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
||||||
|
val, err := EvaluateSource(
|
||||||
|
bytes.NewBufferString(test.src), test.in, GlobalScope,
|
||||||
|
)
|
||||||
|
|
||||||
out = incr < incr < in;
|
if test.expErr != "" {
|
||||||
}`
|
assert.Error(t, err)
|
||||||
|
assert.Equal(t, test.expErr, err.Error())
|
||||||
var in int64 = 5
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
val, err := EvaluateSource(
|
assert.True(t, val.Equal(test.exp), "%v != %v", test.exp, val)
|
||||||
bytes.NewBufferString(src),
|
}
|
||||||
Value{Value: gg.Number(in)},
|
})
|
||||||
GlobalScope,
|
}
|
||||||
)
|
|
||||||
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, in+2, *val.Number)
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user