Builtins start with exclamation point

This commit is contained in:
Brian Picciano 2023-10-29 21:42:41 +01:00
parent 3ef69920c7
commit ec443899c3
8 changed files with 99 additions and 47 deletions

View File

@ -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);
} }

View File

@ -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,

View File

@ -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{

View File

@ -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>

View File

@ -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 {

View File

@ -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.

View File

@ -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)

View File

@ -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)
} }