ginger/gg/term_test.go
Brian Picciano 4870455430 Completely refactor gg with new BNF file and decoder
The new gg format is based on a BNF file which can be found in the `gg`
directory. The code for decoding `.gg` files has been refactored to
mirror that file. The result is more resilient parsing, better errors,
and a greater ability to extend the format in the future.

The new decoder is notable in that it does not use a lexer. Both lexing
and parsing are done in a single step.

The format syntax itself has also been modified. Rather than using
semi-colons everywhere, commas are used as separators in tuples.
Additionally the final comma/semi-colon is no longer required.
2023-10-25 11:31:33 +02:00

388 lines
7.9 KiB
Go

package gg
import (
"bytes"
"io"
"strconv"
"testing"
"github.com/mediocregopher/ginger/graph"
"github.com/stretchr/testify/assert"
)
func decoderLeftover(d *Decoder) string {
unread := make([]rune, len(d.unread))
for i := range unread {
unread[i] = d.unread[i].r
}
rest, err := io.ReadAll(d.br)
if err != nil {
panic(err)
}
return string(unread) + string(rest)
}
func TestTermDecoding(t *testing.T) {
type test struct {
in string
exp Value
expErr string
leftover string
}
runTests := func(
t *testing.T, name string, term *term[Value], tests []test,
) {
t.Run(name, func(t *testing.T) {
for i, test := range tests {
t.Run(strconv.Itoa(i), func(t *testing.T) {
dec := NewDecoder(bytes.NewBufferString(test.in))
got, err := term.decodeFn(dec)
if test.expErr != "" {
assert.Error(t, err)
assert.Equal(t, test.expErr, err.Error())
} else if assert.NoError(t, err) {
assert.True(t,
test.exp.Equal(got),
"\nexp:%v\ngot:%v", test.exp, got,
)
assert.Equal(t, test.leftover, decoderLeftover(dec))
}
})
}
})
}
expNum := func(row, col int, n int64) Value {
return Value{Number: &n, Location: Location{row, col}}
}
runTests(t, "number", numberTerm, []test{
{in: `0`, exp: expNum(1, 1, 0)},
{in: `100`, exp: expNum(1, 1, 100)},
{in: `-100`, exp: expNum(1, 1, -100)},
{in: `0foo`, exp: expNum(1, 1, 0), leftover: "foo"},
{in: `100foo`, exp: expNum(1, 1, 100), leftover: "foo"},
})
expName := func(row, col int, name string) Value {
return Value{Name: &name, Location: Location{row, col}}
}
expGraph := func(row, col int, g *Graph) Value {
return Value{Graph: g, Location: Location{row, col}}
}
runTests(t, "name", nameTerm, []test{
{in: `a`, exp: expName(1, 1, "a")},
{in: `ab`, exp: expName(1, 1, "ab")},
{in: `ab2c`, exp: expName(1, 1, "ab2c")},
{in: `ab2c,`, exp: expName(1, 1, "ab2c"), leftover: ","},
})
runTests(t, "graph", graphTerm, []test{
{in: `{}`, exp: expGraph(1, 1, new(Graph))},
{in: `{`, expErr: `1:2: expected '}' or name`},
{in: `{a}`, expErr: `1:3: expected '='`},
{in: `{a=}`, expErr: `1:4: expected name or number or graph or tuple`},
{
in: `{foo=a}`,
exp: expGraph(
1, 1, new(Graph).
AddValueIn(
expName(2, 1, "foo"),
graph.ValueOut(None, expName(6, 1, "a")),
),
),
},
{
in: `{ foo = a }`,
exp: expGraph(
1, 1, new(Graph).
AddValueIn(
expName(2, 1, "foo"),
graph.ValueOut(None, expName(6, 1, "a")),
),
),
},
{in: `{1=a}`, expErr: `1:2: expected '}' or name`},
{in: `{foo=a ,}`, expErr: `1:8: expected '}' or ';' or '<'`},
{in: `{foo=a`, expErr: `1:7: expected '}' or ';' or '<'`},
{
in: `{foo=a<b}`,
exp: expGraph(
1, 1, new(Graph).
AddValueIn(
expName(2, 1, "foo"),
graph.ValueOut(
Some(expName(6, 1, "a")),
expName(8, 1, "b"),
),
),
),
},
{
in: `{foo=a< b <c}`,
exp: expGraph(
1, 1, new(Graph).
AddValueIn(
expName(2, 1, "foo"),
graph.TupleOut(
Some(expName(6, 1, "a")),
graph.ValueOut(
Some(expName(8, 1, "b")),
expName(10, 1, "c"),
),
),
),
),
},
{
in: `{foo =a<b<c<1 }`,
exp: expGraph(
1, 1, new(Graph).
AddValueIn(
expName(2, 1, "foo"),
graph.TupleOut(
Some(expName(6, 1, "a")),
graph.TupleOut(
Some(expName(8, 1, "b")),
graph.ValueOut(
Some(expName(10, 1, "c")),
expNum(12, 1, 1),
),
),
),
),
),
},
{
in: `{foo=a<b ; }`,
exp: expGraph(
1, 1, new(Graph).
AddValueIn(
expName(2, 1, "foo"),
graph.ValueOut(
Some(expName(6, 1, "a")),
expName(8, 1, "b"),
),
),
),
},
{
in: `{foo=a<b;bar=c}`,
exp: expGraph(
1, 1, new(Graph).
AddValueIn(
expName(2, 1, "foo"),
graph.ValueOut(
Some(expName(6, 1, "a")),
expName(8, 1, "b"),
),
).
AddValueIn(
expName(10, 1, "bar"),
graph.ValueOut(None, expName(15, 1, "c")),
),
),
},
{
in: `{foo= a<{ baz=1 } ; bar=c}`,
exp: expGraph(
1, 1, new(Graph).
AddValueIn(
expName(2, 1, "foo"),
graph.ValueOut(
Some(expName(6, 1, "a")),
expGraph(8, 1, new(Graph).AddValueIn(
expName(9, 1, "baz"),
graph.ValueOut(None, expNum(13, 1, 1)),
)),
),
).
AddValueIn(
expName(16, 1, "bar"),
graph.ValueOut(None, expName(20, 1, "c")),
),
),
},
{
in: `{foo= {baz=1} <a; bar=c}`,
exp: expGraph(
1, 1, new(Graph).
AddValueIn(
expName(2, 1, "foo"),
graph.ValueOut(
Some(expGraph(8, 1, new(Graph).AddValueIn(
expName(9, 1, "baz"),
graph.ValueOut(None, expNum(13, 1, 1)),
))),
expName(6, 1, "a"),
),
).
AddValueIn(
expName(16, 1, "bar"),
graph.ValueOut(None, expName(20, 1, "c")),
),
),
},
})
runTests(t, "tuple", graphTerm, []test{
{
in: `{foo=(a)}`,
exp: expGraph(
1, 1, new(Graph).
AddValueIn(
expName(2, 1, "foo"),
graph.ValueOut(None, expName(6, 1, "a")),
),
),
},
{
in: `{foo=(a<b)}`,
exp: expGraph(
1, 1, new(Graph).
AddValueIn(
expName(2, 1, "foo"),
graph.ValueOut(
Some(expName(6, 1, "a")),
expName(8, 1, "b"),
),
),
),
},
{
in: `{foo=a<(b)}`,
exp: expGraph(
1, 1, new(Graph).
AddValueIn(
expName(2, 1, "foo"),
graph.ValueOut(
Some(expName(6, 1, "a")),
expName(8, 1, "b"),
),
),
),
},
{
in: `{foo=a<(b,c)}`,
exp: expGraph(
1, 1, new(Graph).
AddValueIn(
expName(2, 1, "foo"),
graph.TupleOut(
Some(expName(6, 1, "a")),
graph.ValueOut(None, expName(8, 1, "b")),
graph.ValueOut(None, expName(10, 1, "c")),
),
),
),
},
{
in: `{foo=a<(b<c)}`,
exp: expGraph(
1, 1, new(Graph).
AddValueIn(
expName(2, 1, "foo"),
graph.TupleOut(
Some(expName(6, 1, "a")),
graph.TupleOut(
Some(expName(6, 1, "b")),
graph.ValueOut(None, expName(8, 1, "c")),
),
),
),
),
},
{
in: `{foo=a<(b<(c))}`,
exp: expGraph(
1, 1, new(Graph).
AddValueIn(
expName(2, 1, "foo"),
graph.TupleOut(
Some(expName(6, 1, "a")),
graph.TupleOut(
Some(expName(6, 1, "b")),
graph.ValueOut(None, expName(8, 1, "c")),
),
),
),
),
},
{
in: `{foo=a<(b<(c,d<1))}`,
exp: expGraph(
1, 1, new(Graph).
AddValueIn(
expName(2, 1, "foo"),
graph.TupleOut(
Some(expName(6, 1, "a")),
graph.TupleOut(
Some(expName(6, 1, "b")),
graph.ValueOut(None, expName(8, 1, "c")),
graph.ValueOut(
Some(expName(12, 1, "d")),
expNum(10, 1, 1),
),
),
),
),
),
},
{
in: `{foo=a<(b<( ( (c) ) ))}`,
exp: expGraph(
1, 1, new(Graph).
AddValueIn(
expName(2, 1, "foo"),
graph.TupleOut(
Some(expName(6, 1, "a")),
graph.TupleOut(
Some(expName(6, 1, "b")),
graph.ValueOut(None, expName(8, 1, "c")),
),
),
),
),
},
})
runTests(t, "comment", graphTerm, []test{
{
in: "*\n{}",
exp: expGraph(1, 1, new(Graph)),
},
{
in: "* ignore me!\n{}",
exp: expGraph(1, 1, new(Graph)),
},
{
in: "{* ignore me!\n}",
exp: expGraph(1, 1, new(Graph)),
},
{
in: "{foo* ignore me!\n = a}",
exp: expGraph(
1, 1, new(Graph).
AddValueIn(
expName(2, 1, "foo"),
graph.ValueOut(None, expName(6, 1, "a")),
),
),
},
{
in: "{foo = a* ignore me!\n}",
exp: expGraph(
1, 1, new(Graph).
AddValueIn(
expName(2, 1, "foo"),
graph.ValueOut(None, expName(6, 1, "a")),
),
),
},
})
}