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
{
* 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);
}

View File

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

View File

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

View File

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

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).
//
// 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 {

View File

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

View File

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

View File

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