Break grammar parsing utilities into their own package

This commit is contained in:
Brian Picciano 2023-10-27 16:14:37 +02:00
parent 360857d506
commit c2adfa3b46
12 changed files with 809 additions and 719 deletions

View File

@ -26,7 +26,7 @@ func main() {
res, err := vm.EvaluateSource(
bytes.NewBufferString(opSrc),
vm.Value{Value: inVal},
vm.Value{Value: inVal.Value},
vm.GlobalScope,
)
if err != nil {

View File

@ -1,73 +1,310 @@
package gg
import (
"bufio"
"fmt"
"io"
"log"
"strconv"
"unicode"
. "github.com/mediocregopher/ginger/gg/grammar"
"github.com/mediocregopher/ginger/graph"
"golang.org/x/exp/slices"
)
// Decoder reads Value's off of a byte stream.
type Decoder struct {
br *bufio.Reader
brNextLoc Location
unread []locatableRune
lastRead locatableRune
func s(str string) fmt.Stringer {
return Stringer{S: str}
}
// NewDecoder returns a Decoder which will decode the given stream as a gg
// formatted stream of a Values.
func NewDecoder(r io.Reader) *Decoder {
return &Decoder{
br: bufio.NewReader(r),
brNextLoc: Location{Row: 1, Col: 1},
}
}
var (
notNewline = RuneFunc(
s("not-newline"), func(r rune) bool { return r != '\n' },
)
func (d *Decoder) readRune() (locatableRune, error) {
if len(d.unread) > 0 {
d.lastRead = d.unread[len(d.unread)-1]
d.unread = d.unread[:len(d.unread)-1]
return d.lastRead, nil
}
comment = Prefixed(
Prefixed(Rune('*'), ZeroOrMore(notNewline)), Rune('\n'),
)
loc := d.brNextLoc
r, _, err := d.br.ReadRune()
if err != nil {
return d.lastRead, err
}
if r == '\n' {
d.brNextLoc.Row++
d.brNextLoc.Col = 1
} else {
d.brNextLoc.Col++
}
d.lastRead = locatableRune{loc, r}
return d.lastRead, nil
}
func (d *Decoder) unreadRune(lr locatableRune) {
if d.lastRead != lr {
panic(fmt.Sprintf(
"unreading rune %#v, but last read rune was %#v", lr, d.lastRead,
whitespace = ZeroOrMore(FirstOf(
Discard(RuneFunc(s("whitespace"), unicode.IsSpace)),
Discard(comment),
))
)
func trimmed[T any](sym Symbol[T]) Symbol[T] {
sym = PrefixDiscarded(whitespace, sym)
sym = Suffixed(sym, whitespace)
return sym
}
func trimmedRune(r rune) Symbol[Located[rune]] {
return trimmed(Rune(r))
}
var (
digit = RuneFunc(
s("digit"), func(r rune) bool { return '0' <= r && r <= '9' },
)
positiveNumber = StringFromRunes(OneOrMore(digit))
negativeNumber = Reduction(
s("negative-number"),
Rune('-'),
positiveNumber,
func(neg Located[rune], posNum Located[string]) Located[string] {
return Located[string]{
neg.Location, string(neg.Value) + posNum.Value,
}
},
)
number = Mapping(
s("number"),
FirstOf(negativeNumber, positiveNumber),
func(str Located[string]) Located[Value] {
i, err := strconv.ParseInt(str.Value, 10, 64)
if err != nil {
panic(fmt.Errorf("parsing %q as int: %w", str, err))
}
d.unread = append(d.unread, lr)
return Located[Value]{str.Location, Number(i)}
},
)
)
var (
letter = RuneFunc(
s("letter"),
func(r rune) bool {
return unicode.In(r, unicode.Letter, unicode.Mark)
},
)
nameTail = ZeroOrMore(FirstOf(letter, digit))
name = Reduction(
s("name"),
letter,
nameTail,
func(head Located[rune], tail []Located[rune]) Located[Value] {
name := make([]rune, 0, len(tail)+1)
name = append(name, head.Value)
for _, r := range tail {
name = append(name, r.Value)
}
return Located[Value]{head.Location, Name(string(name))}
},
)
)
func openEdgeIntoValue(val Value, oe *OpenEdge) *OpenEdge {
switch {
case oe == nil:
return graph.ValueOut(None, val)
case !oe.EdgeValue().Valid:
return oe.WithEdgeValue(Some(val))
default:
return graph.TupleOut(Some(val), oe)
}
}
func (d *Decoder) nextLoc() Location {
if len(d.unread) > 0 {
return d.unread[len(d.unread)-1].Location
var graphSym, value = func() (
Symbol[Located[Value]], Symbol[Located[Value]],
) {
type tupleState struct {
ins []*OpenEdge
oe *OpenEdge
}
return d.brNextLoc
type graphState struct {
g *Graph
oe *OpenEdge
}
var (
rightParen = trimmedRune(')')
tupleEnd = Mapping(
rightParen,
rightParen,
func(Located[rune]) tupleState {
// if ')', then map that to an empty state. This acts as a
// sentinel value to indicate "end of tuple".
return tupleState{}
},
)
rightCurlyBrace = trimmedRune('}')
graphEnd = Mapping(
rightCurlyBrace,
rightCurlyBrace,
func(Located[rune]) graphState {
// if '}', then map that to an empty state. This acts as a
// sentinel value to indicate "end of graph".
return graphState{}
},
)
)
var (
// pre-define these, and then fill in the pointers after, in order to
// deal with recursive dependencies between them.
value = new(SymbolPtr[Located[Value]])
tuple = new(SymbolPtr[*OpenEdge])
tupleTail = new(SymbolPtr[tupleState])
tupleOpenEdge = new(SymbolPtr[tupleState])
tupleOpenEdgeTail = new(SymbolPtr[tupleState])
tupleOpenEdgeValueTail = new(SymbolPtr[tupleState])
graphSym = new(SymbolPtr[Located[Value]])
graphTail = new(SymbolPtr[graphState])
graphOpenEdge = new(SymbolPtr[graphState])
graphOpenEdgeTail = new(SymbolPtr[graphState])
graphOpenEdgeValueTail = new(SymbolPtr[graphState])
)
tuple.Symbol = Reduction[Located[rune], tupleState, *OpenEdge](
s("tuple"),
trimmedRune('('),
tupleTail,
func(_ Located[rune], ts tupleState) *OpenEdge {
slices.Reverse(ts.ins)
return graph.TupleOut(None, ts.ins...)
},
)
tupleTail.Symbol = FirstOf(
tupleEnd,
Mapping[tupleState, tupleState](
tupleOpenEdge,
tupleOpenEdge,
func(ts tupleState) tupleState {
ts.ins = append(ts.ins, ts.oe)
ts.oe = nil
return ts
},
),
)
tupleOpenEdge.Symbol = FirstOf(
Reduction[Located[Value], tupleState, tupleState](
value,
value,
tupleOpenEdgeValueTail,
func(val Located[Value], ts tupleState) tupleState {
ts.oe = openEdgeIntoValue(val.Value, ts.oe)
return ts
},
),
Reduction[*OpenEdge, tupleState, tupleState](
tuple,
tuple,
tupleOpenEdgeTail,
func(oe *OpenEdge, ts tupleState) tupleState {
ts.oe = oe
return ts
},
),
)
tupleOpenEdgeTail.Symbol = FirstOf(
tupleEnd,
Prefixed[Located[rune], tupleState](trimmedRune(','), tupleTail),
)
tupleOpenEdgeValueTail.Symbol = FirstOf[tupleState](
tupleOpenEdgeTail,
Prefixed[Located[rune], tupleState](trimmedRune('<'), tupleOpenEdge),
)
graphSym.Symbol = Reduction[Located[rune], graphState, Located[Value]](
s("graph"),
trimmedRune('{'),
graphTail,
func(r Located[rune], gs graphState) Located[Value] {
if gs.g == nil {
gs.g = new(Graph)
}
return Located[Value]{r.Location, Value{Graph: gs.g}}
},
)
graphTail.Symbol = FirstOf(
graphEnd,
Reduction(
name,
name,
Prefixed[Located[rune], graphState](
trimmedRune('='), graphOpenEdge,
),
func(name Located[Value], gs graphState) graphState {
if gs.g == nil {
gs.g = new(Graph)
}
gs.g = gs.g.AddValueIn(name.Value, gs.oe)
gs.oe = nil
return gs
},
),
)
log.Printf("populated graphTail with %v", graphTail)
graphOpenEdge.Symbol = FirstOf(
Reduction[Located[Value], graphState, graphState](
value,
value,
graphOpenEdgeValueTail,
func(val Located[Value], gs graphState) graphState {
gs.oe = openEdgeIntoValue(val.Value, gs.oe)
return gs
},
),
Reduction[*OpenEdge, graphState, graphState](
tuple,
tuple,
graphOpenEdgeTail,
func(oe *OpenEdge, gs graphState) graphState {
gs.oe = oe
return gs
},
),
)
graphOpenEdgeTail.Symbol = FirstOf(
graphEnd,
Prefixed[Located[rune], graphState](trimmedRune(';'), graphTail),
)
log.Printf("populated graphOpenEdgeTail with %v", graphOpenEdgeTail)
graphOpenEdgeValueTail.Symbol = FirstOf[graphState](
graphOpenEdgeTail,
Prefixed[Located[rune], graphState](trimmedRune('<'), graphOpenEdge),
)
value.Symbol = trimmed(FirstOf[Located[Value]](name, number, graphSym))
return graphSym, value
}()
// Decoder reads Values off of an io.Reader, or return io.EOF.
type Decoder interface {
Next() (Located[Value], error)
}
// Next returns the next top-level value in the stream, or io.EOF.
func (d *Decoder) Next() (Value, error) {
return topLevelTerm.decodeFn(d)
type decoder struct {
r Reader
}
// NewDecoder returns a Decoder which reads off the given io.Reader. The
// io.Reader should not be read from after this call.
func NewDecoder(r io.Reader) Decoder {
return &decoder{r: NewReader(r)}
}
func (d *decoder) Next() (Located[Value], error) {
return value.Decode(d.r)
}

View File

@ -2,86 +2,72 @@ package gg
import (
"bytes"
"io"
"strconv"
"testing"
. "github.com/mediocregopher/ginger/gg/grammar"
"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) {
func TestDecoder(t *testing.T) {
type test struct {
in string
exp Value
exp Located[Value]
expErr string
leftover string
}
runTests := func(
t *testing.T, name string, term *term[Value], tests []test,
t *testing.T, name string, sym Symbol[Located[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)
r := NewReader(bytes.NewBufferString(test.in))
got, err := sym.Decode(r)
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),
test.exp.Value.Equal(got.Value),
"\nexp:%v\ngot:%v", test.exp, got,
)
assert.Equal(t, test.leftover, decoderLeftover(dec))
assert.Equal(t, test.exp.Location, got.Location)
}
})
}
})
}
expNum := func(row, col int, n int64) Value {
return Value{Number: &n, Location: Location{row, col}}
expNum := func(row, col int, n int64) Located[Value] {
return Located[Value]{Location{row, col}, Number(n)}
}
runTests(t, "number", numberTerm, []test{
runTests(t, "number", number, []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"},
{in: `0foo`, exp: expNum(1, 1, 0)},
{in: `100foo`, exp: expNum(1, 1, 100)},
})
expName := func(row, col int, name string) Value {
return Value{Name: &name, Location: Location{row, col}}
expName := func(row, col int, name string) Located[Value] {
return Located[Value]{Location{row, col}, Name(name)}
}
expGraph := func(row, col int, g *Graph) Value {
return Value{Graph: g, Location: Location{row, col}}
expGraph := func(row, col int, g *Graph) Located[Value] {
return Located[Value]{Location{row, col}, Value{Graph: g}}
}
runTests(t, "name", nameTerm, []test{
runTests(t, "name", name, []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: ","},
{in: `ab2c,`, exp: expName(1, 1, "ab2c")},
})
runTests(t, "graph", graphTerm, []test{
runTests(t, "graph", graphSym, []test{
{in: `{}`, exp: expGraph(1, 1, new(Graph))},
{in: `{`, expErr: `1:2: expected '}' or name`},
{in: `{a}`, expErr: `1:3: expected '='`},
@ -90,20 +76,14 @@ func TestTermDecoding(t *testing.T) {
in: `{foo=a}`,
exp: expGraph(
1, 1, new(Graph).
AddValueIn(
expName(2, 1, "foo"),
graph.ValueOut(None, expName(6, 1, "a")),
),
AddValueIn(Name("foo"), graph.ValueOut(None, Name("a"))),
),
},
{
in: `{ foo = a }`,
exp: expGraph(
1, 1, new(Graph).
AddValueIn(
expName(2, 1, "foo"),
graph.ValueOut(None, expName(6, 1, "a")),
),
AddValueIn(Name("foo"), graph.ValueOut(None, Name("a"))),
),
},
{in: `{1=a}`, expErr: `1:2: expected '}' or name`},
@ -114,11 +94,8 @@ func TestTermDecoding(t *testing.T) {
exp: expGraph(
1, 1, new(Graph).
AddValueIn(
expName(2, 1, "foo"),
graph.ValueOut(
Some(expName(6, 1, "a")),
expName(8, 1, "b"),
),
Name("foo"),
graph.ValueOut(Some(Name("a")), Name("b")),
),
),
},
@ -127,12 +104,12 @@ func TestTermDecoding(t *testing.T) {
exp: expGraph(
1, 1, new(Graph).
AddValueIn(
expName(2, 1, "foo"),
Name("foo"),
graph.TupleOut(
Some(expName(6, 1, "a")),
Some(Name("a")),
graph.ValueOut(
Some(expName(8, 1, "b")),
expName(10, 1, "c"),
Some(Name("b")),
Name("c"),
),
),
),
@ -143,14 +120,14 @@ func TestTermDecoding(t *testing.T) {
exp: expGraph(
1, 1, new(Graph).
AddValueIn(
expName(2, 1, "foo"),
Name("foo"),
graph.TupleOut(
Some(expName(6, 1, "a")),
Some(Name("a")),
graph.TupleOut(
Some(expName(8, 1, "b")),
Some(Name("b")),
graph.ValueOut(
Some(expName(10, 1, "c")),
expNum(12, 1, 1),
Some(Name("c")),
Number(1),
),
),
),
@ -162,10 +139,10 @@ func TestTermDecoding(t *testing.T) {
exp: expGraph(
1, 1, new(Graph).
AddValueIn(
expName(2, 1, "foo"),
Name("foo"),
graph.ValueOut(
Some(expName(6, 1, "a")),
expName(8, 1, "b"),
Some(Name("a")),
Name("b"),
),
),
),
@ -175,15 +152,15 @@ func TestTermDecoding(t *testing.T) {
exp: expGraph(
1, 1, new(Graph).
AddValueIn(
expName(2, 1, "foo"),
Name("foo"),
graph.ValueOut(
Some(expName(6, 1, "a")),
expName(8, 1, "b"),
Some(Name("a")),
Name("b"),
),
).
AddValueIn(
expName(10, 1, "bar"),
graph.ValueOut(None, expName(15, 1, "c")),
Name("bar"),
graph.ValueOut(None, Name("c")),
),
),
},
@ -192,18 +169,18 @@ func TestTermDecoding(t *testing.T) {
exp: expGraph(
1, 1, new(Graph).
AddValueIn(
expName(2, 1, "foo"),
Name("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)),
)),
Some(Name("a")),
Value{Graph: new(Graph).AddValueIn(
Name("baz"),
graph.ValueOut(None, Number(1)),
)},
),
).
AddValueIn(
expName(16, 1, "bar"),
graph.ValueOut(None, expName(20, 1, "c")),
Name("bar"),
graph.ValueOut(None, Name("c")),
),
),
},
@ -212,31 +189,31 @@ func TestTermDecoding(t *testing.T) {
exp: expGraph(
1, 1, new(Graph).
AddValueIn(
expName(2, 1, "foo"),
Name("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"),
Some(Value{Graph: new(Graph).AddValueIn(
Name("baz"),
graph.ValueOut(None, Number(1)),
)}),
Name("a"),
),
).
AddValueIn(
expName(16, 1, "bar"),
graph.ValueOut(None, expName(20, 1, "c")),
Name("bar"),
graph.ValueOut(None, Name("c")),
),
),
},
})
runTests(t, "tuple", graphTerm, []test{
runTests(t, "tuple", graphSym, []test{
{
in: `{foo=(a)}`,
exp: expGraph(
1, 1, new(Graph).
AddValueIn(
expName(2, 1, "foo"),
graph.ValueOut(None, expName(6, 1, "a")),
Name("foo"),
graph.ValueOut(None, Name("a")),
),
),
},
@ -245,10 +222,10 @@ func TestTermDecoding(t *testing.T) {
exp: expGraph(
1, 1, new(Graph).
AddValueIn(
expName(2, 1, "foo"),
Name("foo"),
graph.ValueOut(
Some(expName(6, 1, "a")),
expName(8, 1, "b"),
Some(Name("a")),
Name("b"),
),
),
),
@ -258,10 +235,10 @@ func TestTermDecoding(t *testing.T) {
exp: expGraph(
1, 1, new(Graph).
AddValueIn(
expName(2, 1, "foo"),
Name("foo"),
graph.ValueOut(
Some(expName(6, 1, "a")),
expName(8, 1, "b"),
Some(Name("a")),
Name("b"),
),
),
),
@ -271,11 +248,11 @@ func TestTermDecoding(t *testing.T) {
exp: expGraph(
1, 1, new(Graph).
AddValueIn(
expName(2, 1, "foo"),
Name("foo"),
graph.TupleOut(
Some(expName(6, 1, "a")),
graph.ValueOut(None, expName(8, 1, "b")),
graph.ValueOut(None, expName(10, 1, "c")),
Some(Name("a")),
graph.ValueOut(None, Name("b")),
graph.ValueOut(None, Name("c")),
),
),
),
@ -285,12 +262,12 @@ func TestTermDecoding(t *testing.T) {
exp: expGraph(
1, 1, new(Graph).
AddValueIn(
expName(2, 1, "foo"),
Name("foo"),
graph.TupleOut(
Some(expName(6, 1, "a")),
Some(Name("a")),
graph.TupleOut(
Some(expName(6, 1, "b")),
graph.ValueOut(None, expName(8, 1, "c")),
Some(Name("b")),
graph.ValueOut(None, Name("c")),
),
),
),
@ -301,12 +278,12 @@ func TestTermDecoding(t *testing.T) {
exp: expGraph(
1, 1, new(Graph).
AddValueIn(
expName(2, 1, "foo"),
Name("foo"),
graph.TupleOut(
Some(expName(6, 1, "a")),
Some(Name("a")),
graph.TupleOut(
Some(expName(6, 1, "b")),
graph.ValueOut(None, expName(8, 1, "c")),
Some(Name("b")),
graph.ValueOut(None, Name("c")),
),
),
),
@ -317,15 +294,15 @@ func TestTermDecoding(t *testing.T) {
exp: expGraph(
1, 1, new(Graph).
AddValueIn(
expName(2, 1, "foo"),
Name("foo"),
graph.TupleOut(
Some(expName(6, 1, "a")),
Some(Name("a")),
graph.TupleOut(
Some(expName(6, 1, "b")),
graph.ValueOut(None, expName(8, 1, "c")),
Some(Name("b")),
graph.ValueOut(None, Name("c")),
graph.ValueOut(
Some(expName(12, 1, "d")),
expNum(10, 1, 1),
Some(Name("d")),
Number(1),
),
),
),
@ -337,12 +314,12 @@ func TestTermDecoding(t *testing.T) {
exp: expGraph(
1, 1, new(Graph).
AddValueIn(
expName(2, 1, "foo"),
Name("foo"),
graph.TupleOut(
Some(expName(6, 1, "a")),
Some(Name("a")),
graph.TupleOut(
Some(expName(6, 1, "b")),
graph.ValueOut(None, expName(8, 1, "c")),
Some(Name("b")),
graph.ValueOut(None, Name("c")),
),
),
),
@ -350,14 +327,14 @@ func TestTermDecoding(t *testing.T) {
},
})
runTests(t, "comment", graphTerm, []test{
runTests(t, "comment", graphSym, []test{
{
in: "*\n{}",
exp: expGraph(1, 1, new(Graph)),
exp: expGraph(2, 1, new(Graph)),
},
{
in: "* ignore me!\n{}",
exp: expGraph(1, 1, new(Graph)),
exp: expGraph(2, 1, new(Graph)),
},
{
in: "{* ignore me!\n}",
@ -368,8 +345,8 @@ func TestTermDecoding(t *testing.T) {
exp: expGraph(
1, 1, new(Graph).
AddValueIn(
expName(2, 1, "foo"),
graph.ValueOut(None, expName(6, 1, "a")),
Name("foo"),
graph.ValueOut(None, Name("a")),
),
),
},
@ -378,8 +355,8 @@ func TestTermDecoding(t *testing.T) {
exp: expGraph(
1, 1, new(Graph).
AddValueIn(
expName(2, 1, "foo"),
graph.ValueOut(None, expName(6, 1, "a")),
Name("foo"),
graph.ValueOut(None, Name("a")),
),
),
},

View File

@ -15,8 +15,6 @@ type (
// Value represents a value which can be serialized by the gg text format.
type Value struct {
Location
// Only one of these fields may be set
Name *string
Number *int64

27
gg/grammar/grammar.go Normal file
View File

@ -0,0 +1,27 @@
// Package grammar is used for parsing a stream of runes according to a set of
// grammatical rules. This package only supports context-free grammars.
package grammar
import "fmt"
// Stringer is a convenience tool for working with fmt.Stringer. Exactly one of
// the fields must be set, and will be used to implement the fmt.Stringer
// interface.
type Stringer struct {
I fmt.Stringer
F func() string
S string
}
func (s Stringer) String() string {
switch {
case s.I != nil:
return s.I.String()
case s.F != nil:
return s.F()
case s.S != "":
return s.S
default:
panic("no fields set on Stringer")
}
}

27
gg/grammar/location.go Normal file
View File

@ -0,0 +1,27 @@
package grammar
import "fmt"
// Location indicates a position in a stream of runes identified by column
// within newline-separated rows.
type Location struct {
Row, Col int
}
func (l Location) errf(str string, args ...any) LocatedError {
return LocatedError{l, fmt.Errorf(str, args...)}
}
// Located wraps a value so that it has a Location attached to it.
type Located[T any] struct {
Location
Value T
}
// LocatedError is an error related to a specific point within a stream of
// runes.
type LocatedError Located[error]
func (e LocatedError) Error() string {
return fmt.Sprintf("%d:%d: %v", e.Row, e.Col, e.Value)
}

74
gg/grammar/reader.go Normal file
View File

@ -0,0 +1,74 @@
package grammar
import (
"bufio"
"io"
)
// Reader is used for reading Runes from a stream.
type Reader interface {
// ReadRune reads the next Rune off the stream, or returns io.EOF.
ReadRune() (Located[rune], error)
// UnreadRune can be used to place a Rune onto an internal buffer, such that
// the Rune will be the next to be read using ReadRune. If called multiple
// times then ReadRune will produce the given Runes in LIFO order.
UnreadRune(Located[rune])
// NextLocation returns the Location of the next Rune which will be returned
// with ReadRune.
NextLocation() Location
}
type reader struct {
br *bufio.Reader
brNextLoc Location
unread []Located[rune]
}
// NewReader wraps the io.Reader as a Reader. The given Reader should not be
// read from after this call.
func NewReader(r io.Reader) Reader {
return &reader{
br: bufio.NewReader(r),
brNextLoc: Location{Row: 1, Col: 1},
}
}
func (rr *reader) ReadRune() (Located[rune], error) {
if len(rr.unread) > 0 {
r := rr.unread[len(rr.unread)-1]
rr.unread = rr.unread[:len(rr.unread)-1]
return r, nil
}
loc := rr.brNextLoc
r, _, err := rr.br.ReadRune()
if err != nil {
return Located[rune]{}, err
}
if r == '\n' {
rr.brNextLoc.Row++
rr.brNextLoc.Col = 1
} else {
rr.brNextLoc.Col++
}
return Located[rune]{loc, r}, nil
}
func (rr *reader) UnreadRune(r Located[rune]) {
rr.unread = append(rr.unread, r)
}
func (rr *reader) NextLocation() Location {
if len(rr.unread) > 0 {
return rr.unread[len(rr.unread)-1].Location
}
return rr.brNextLoc
}

294
gg/grammar/symbol.go Normal file
View File

@ -0,0 +1,294 @@
package grammar
import (
"errors"
"fmt"
"io"
"strings"
)
// ErrNoMatch is used by Symbol's Decode method, see that method's docs for more
// details.
var ErrNoMatch = errors.New("no match")
// Symbol represents a symbol in the grammar. A Symbol is expected to be
// stateless, and is usually constructed from other Symbols using functions in
// this package.
type Symbol[T any] interface {
fmt.Stringer // Used when generating errors related to this Symbol, e.g. "number"
// Decode reads and parses a value represented by this Symbol off the
// Reader.
//
// This may return ErrNoMatch to indicate that the upcoming data on the
// Reader is rejected by this Symbol. In this case the Symbol should leave
// the Reader in the same state it was passed.
Decode(Reader) (T, error)
}
type symbol[T any] struct {
fmt.Stringer
decodeFn func(Reader) (T, error)
}
func (s *symbol[T]) Decode(r Reader) (T, error) { return s.decodeFn(r) }
// SymbolPtr wraps a Symbol in a such a way as to make lazily initializing a
// Symbol variable possible. This allows for recursion amongst different
// Symbols.
//
// Example:
//
// a := new(SymbolPtr)
// b := new(SymbolPtr)
// a.Symbol = FirstOf(Rune('a'), b)
// b.Symbol = FirstOf(Rune('b'), a)
type SymbolPtr[T any] struct {
Symbol[T]
}
// RuneFunc matches and produces any rune for which the given function returns
// true.
func RuneFunc(stringer fmt.Stringer, fn func(rune) bool) Symbol[Located[rune]] {
return &symbol[Located[rune]]{
stringer,
func(rr Reader) (Located[rune], error) {
var zero Located[rune]
r, err := rr.ReadRune()
if errors.Is(err, io.EOF) {
return zero, ErrNoMatch
} else if err != nil {
return zero, err
}
if !fn(r.Value) {
rr.UnreadRune(r)
return zero, ErrNoMatch
}
return r, nil
},
}
}
// Rune matches and produces the given rune.
func Rune(r rune) Symbol[Located[rune]] {
return RuneFunc(
Stringer{S: fmt.Sprintf("'%c'", r)},
func(r2 rune) bool { return r == r2 },
)
}
// StringFromRunes produces a string from the slice of runes produced by the
// given Symbol. The slice must not be empty. StringFromRunes does not match if
// the given Symbol does not match.
func StringFromRunes(sym Symbol[[]Located[rune]]) Symbol[Located[string]] {
return Mapping(sym, sym, func(runes []Located[rune]) Located[string] {
if len(runes) == 0 {
panic("StringFromRunes used on empty set of runes")
}
str := make([]rune, len(runes))
for i := range runes {
str[i] = runes[i].Value
}
return Located[string]{runes[0].Location, string(str)}
})
}
// Mapping produces a value of type Tb by decoding a value from the given
// Symbol and passing it through the given mapping function. If the given Symbol
// doesn't match then neither does Map.
func Mapping[Ta, Tb any](
stringer fmt.Stringer, sym Symbol[Ta], fn func(Ta) Tb,
) Symbol[Tb] {
return &symbol[Tb]{
stringer,
func(rr Reader) (Tb, error) {
var zero Tb
va, err := sym.Decode(rr)
if err != nil {
return zero, err
}
return fn(va), nil
},
}
}
// OneOrMore will produce as many of the given Symbol's value as can be found
// sequentially, up until a non-matching value is encountered. If no matches are
// found then OneOrMore does not match.
func OneOrMore[T any](sym Symbol[T]) Symbol[[]T] {
return &symbol[[]T]{
Stringer{F: func() string {
return fmt.Sprintf("one or more %v", sym)
}},
func(rr Reader) ([]T, error) {
var vv []T
for {
v, err := sym.Decode(rr)
if errors.Is(err, ErrNoMatch) {
break
} else if err != nil {
return nil, err
}
vv = append(vv, v)
}
if len(vv) == 0 {
return nil, ErrNoMatch
}
return vv, nil
},
}
}
// ZeroOrMore will produce as many of the given Symbol's value as can be found
// sequentially, up until a non-matching value is encountered. If no matches are
// found then an empty slice is produced.
func ZeroOrMore[T any](sym Symbol[T]) Symbol[[]T] {
return &symbol[[]T]{
Stringer{F: func() string {
return fmt.Sprintf("zero or more %v", sym)
}},
func(rr Reader) ([]T, error) {
var vv []T
for {
v, err := sym.Decode(rr)
if errors.Is(err, ErrNoMatch) {
break
} else if err != nil {
return nil, err
}
vv = append(vv, v)
}
return vv, nil
},
}
}
func firstOf[T any](stringer fmt.Stringer, syms ...Symbol[T]) Symbol[T] {
return &symbol[T]{
stringer,
func(rr Reader) (T, error) {
var zero T
for _, sym := range syms {
v, err := sym.Decode(rr)
if errors.Is(err, ErrNoMatch) {
continue
} else if err != nil {
return zero, err
}
return v, nil
}
return zero, ErrNoMatch
},
}
}
// FirstOf matches and produces the value for the first Symbol in the list which
// matches. FirstOf does not match if none of the given Symbols match.
func FirstOf[T any](syms ...Symbol[T]) Symbol[T] {
return firstOf(
Stringer{F: func() string {
descrs := make([]string, len(syms))
for i := range syms {
descrs[i] = syms[i].String()
}
return strings.Join(descrs, " or ")
}},
syms...,
)
}
// Reduction produces a value of type Tc by first reading a value from symA,
// then symB, and then running those through the given function.
//
// If symA does not match then Reduction does not match. If symA matches but
// symB does not then also match then Reduction produces a LocatedError.
func Reduction[Ta, Tb, Tc any](
stringer fmt.Stringer,
symA Symbol[Ta],
symB Symbol[Tb],
fn func(Ta, Tb) Tc,
) Symbol[Tc] {
return &symbol[Tc]{
stringer,
func(rr Reader) (Tc, error) {
var zero Tc
va, err := symA.Decode(rr)
if err != nil {
return zero, err
}
vb, err := symB.Decode(rr)
if errors.Is(err, ErrNoMatch) {
return zero, rr.NextLocation().errf("expected %v", symB)
} else if err != nil {
return zero, err
}
return fn(va, vb), nil
},
}
}
// Prefixed matches on prefixSym, discards its value, then produces the value
// produced by sym.
//
// If prefixSym does not match then Prefixed does not match. If prefixSym
// matches but sym does not also match then Prefixed produces a LocatedError.
func Prefixed[Ta, Tb any](prefixSym Symbol[Ta], sym Symbol[Tb]) Symbol[Tb] {
return Reduction(prefixSym, prefixSym, sym, func(_ Ta, b Tb) Tb {
return b
})
}
// PrefixDiscarded is similar to Prefixed, except that if sym does not match
// then PrefixDiscarded does not match, whereas Prefixed produces a LocatedError
// in that case.
//
// NOTE PrefixDiscarded does not fully honor the contract of Symbol. If
// prefixSym matches, but sym does not, then only sym will restore Reader to its
// prior state; prefixSym cannot return whatever data it read back onto the
// Reader. Therefore ErrNoMatch can be returned without Reader being fully back
// in its original state. In practice this isn't a big deal, given the common
// use-cases of PrefixDiscarded, but it may prove tricky.
func PrefixDiscarded[Ta, Tb any](prefixSym Symbol[Ta], sym Symbol[Tb]) Symbol[Tb] {
return &symbol[Tb]{
sym,
func(rr Reader) (Tb, error) {
var zero Tb
if _, err := prefixSym.Decode(rr); err != nil {
return zero, err
}
return sym.Decode(rr)
},
}
}
// Suffixed matchs on sym and then suffixSym, returning the value produced by
// sym and discarding the one produced by suffixSym.
//
// If sym does not match then Suffixed does not match. If sym matches but
// suffixSym does not also match then Suffixed produces a LocatedError.
func Suffixed[Ta, Tb any](sym Symbol[Ta], suffixSym Symbol[Tb]) Symbol[Ta] {
return Reduction(sym, sym, suffixSym, func(a Ta, _ Tb) Ta {
return a
})
}
// Discard matches if the given Symbol does, but discards the value it produces,
// producing an empty value instead.
func Discard[T any](sym Symbol[T]) Symbol[struct{}] {
return Mapping(sym, sym, func(T) struct{} { return struct{}{} })
}

View File

@ -1,36 +0,0 @@
package gg
import "fmt"
// Location indicates a position in a stream of bytes identified by column
// within newline-separated rows.
type Location struct {
Row, Col int
}
func (l Location) errf(str string, args ...any) LocatedError {
return LocatedError{l, fmt.Errorf(str, args...)}
}
func (l Location) locate() Location { return l }
// LocatedError is an error related to a specific point within a decode gg
// stream.
type LocatedError struct {
Location
Err error
}
func (e LocatedError) Error() string {
return fmt.Sprintf("%d:%d: %v", e.Row, e.Col, e.Err)
}
type locatableRune struct {
Location
r rune
}
type locatableString struct {
Location
str string
}

View File

@ -1,508 +0,0 @@
package gg
import (
"errors"
"fmt"
"io"
"strconv"
"strings"
"unicode"
"github.com/mediocregopher/ginger/graph"
"golang.org/x/exp/slices"
)
var (
errNoMatch = errors.New("not found")
)
type stringerFn func() string
func (fn stringerFn) String() string {
return fn()
}
type stringerStr string
func (str stringerStr) String() string {
return string(str)
}
type term[T any] struct {
name fmt.Stringer
decodeFn func(d *Decoder) (T, error)
}
func (t term[T]) String() string {
return t.name.String()
}
func firstOf[T any](terms ...*term[T]) *term[T] {
if len(terms) < 2 {
panic("firstOfTerms requires at least 2 terms")
}
return &term[T]{
name: stringerFn(func() string {
descrs := make([]string, len(terms))
for i := range terms {
descrs[i] = terms[i].String()
}
return strings.Join(descrs, " or ")
}),
decodeFn: func(d *Decoder) (T, error) {
var zero T
for _, t := range terms {
v, err := t.decodeFn(d)
if errors.Is(err, errNoMatch) {
continue
} else if err != nil {
return zero, err
}
return v, nil
}
return zero, errNoMatch
},
}
}
func seq[Ta, Tb, Tc any](
name fmt.Stringer,
termA *term[Ta],
termB *term[Tb],
fn func(Ta, Tb) Tc,
) *term[Tc] {
return &term[Tc]{
name: name,
decodeFn: func(d *Decoder) (Tc, error) {
var zero Tc
va, err := termA.decodeFn(d)
if err != nil {
return zero, err
}
vb, err := termB.decodeFn(d)
if errors.Is(err, errNoMatch) {
return zero, d.nextLoc().errf("expected %v", termB)
} else if err != nil {
return zero, err
}
return fn(va, vb), nil
},
}
}
func prefixed[Ta, Tb any](termA *term[Ta], termB *term[Tb]) *term[Tb] {
return seq(termA, termA, termB, func(_ Ta, b Tb) Tb {
return b
})
}
func prefixIgnored[Ta, Tb any](termA *term[Ta], termB *term[Tb]) *term[Tb] {
return &term[Tb]{
name: termB,
decodeFn: func(d *Decoder) (Tb, error) {
var zero Tb
if _, err := termA.decodeFn(d); err != nil {
return zero, err
}
return termB.decodeFn(d)
},
}
}
func suffixIgnored[Ta, Tb any](
termA *term[Ta], termB *term[Tb],
) *term[Ta] {
return seq(termA, termA, termB, func(a Ta, _ Tb) Ta {
return a
})
}
func oneOrMore[T any](t *term[T]) *term[[]T] {
return &term[[]T]{
name: stringerFn(func() string {
return fmt.Sprintf("one or more %v", t)
}),
decodeFn: func(d *Decoder) ([]T, error) {
var vv []T
for {
v, err := t.decodeFn(d)
if errors.Is(err, errNoMatch) {
break
} else if err != nil {
return nil, err
}
vv = append(vv, v)
}
if len(vv) == 0 {
return nil, errNoMatch
}
return vv, nil
},
}
}
func zeroOrMore[T any](t *term[T]) *term[[]T] {
return &term[[]T]{
name: stringerFn(func() string {
return fmt.Sprintf("zero or more %v", t)
}),
decodeFn: func(d *Decoder) ([]T, error) {
var vv []T
for {
v, err := t.decodeFn(d)
if errors.Is(err, errNoMatch) {
break
} else if err != nil {
return nil, err
}
vv = append(vv, v)
}
return vv, nil
},
}
}
func mapTerm[Ta, Tb any](
name fmt.Stringer, t *term[Ta], fn func(Ta) Tb,
) *term[Tb] {
return &term[Tb]{
name: name,
decodeFn: func(d *Decoder) (Tb, error) {
var zero Tb
va, err := t.decodeFn(d)
if err != nil {
return zero, err
}
return fn(va), nil
},
}
}
func runePredTerm(
name fmt.Stringer, pred func(rune) bool,
) *term[locatableRune] {
return &term[locatableRune]{
name: name,
decodeFn: func(d *Decoder) (locatableRune, error) {
lr, err := d.readRune()
if errors.Is(err, io.EOF) {
return locatableRune{}, errNoMatch
} else if err != nil {
return locatableRune{}, err
}
if !pred(lr.r) {
d.unreadRune(lr)
return locatableRune{}, errNoMatch
}
return lr, nil
},
}
}
func runeTerm(r rune) *term[locatableRune] {
return runePredTerm(
stringerStr(fmt.Sprintf("'%c'", r)),
func(r2 rune) bool { return r2 == r },
)
}
func locatableRunesToString(rr []locatableRune) string {
str := make([]rune, len(rr))
for i := range rr {
str[i] = rr[i].r
}
return string(str)
}
func runesToStringTerm(
t *term[[]locatableRune],
) *term[locatableString] {
return mapTerm(
t, t, func(rr []locatableRune) locatableString {
return locatableString{rr[0].locate(), locatableRunesToString(rr)}
},
)
}
func discard[T any](t *term[T]) *term[struct{}] {
return mapTerm(t, t, func(_ T) struct{} { return struct{}{} })
}
var (
notNewlineTerm = runePredTerm(
stringerStr("not-newline"),
func(r rune) bool { return r != '\n' },
)
commentTerm = prefixed(
prefixed(runeTerm('*'), zeroOrMore(notNewlineTerm)),
runeTerm('\n'),
)
whitespaceTerm = zeroOrMore(firstOf(
discard(runePredTerm(stringerStr("whitespace"), unicode.IsSpace)),
discard(commentTerm),
))
)
func trimmedTerm[T any](t *term[T]) *term[T] {
t = prefixIgnored(whitespaceTerm, t)
t = suffixIgnored(t, whitespaceTerm)
return t
}
func trimmedRuneTerm(r rune) *term[locatableRune] {
return trimmedTerm(runeTerm(r))
}
var (
digitTerm = runePredTerm(
stringerStr("digit"),
func(r rune) bool { return '0' <= r && r <= '9' },
)
positiveNumberTerm = runesToStringTerm(oneOrMore(digitTerm))
negativeNumberTerm = seq(
stringerStr("negative-number"),
runeTerm('-'),
positiveNumberTerm,
func(neg locatableRune, posNum locatableString) locatableString {
return locatableString{neg.locate(), string(neg.r) + posNum.str}
},
)
numberTerm = mapTerm(
stringerStr("number"),
firstOf(negativeNumberTerm, positiveNumberTerm),
func(str locatableString) Value {
i, err := strconv.ParseInt(str.str, 10, 64)
if err != nil {
panic(fmt.Errorf("parsing %q as int: %w", str, err))
}
return Value{Number: &i, Location: str.locate()}
},
)
)
var (
letterTerm = runePredTerm(
stringerStr("letter"),
func(r rune) bool {
return unicode.In(r, unicode.Letter, unicode.Mark)
},
)
letterTailTerm = zeroOrMore(firstOf(letterTerm, digitTerm))
nameTerm = seq(
stringerStr("name"),
letterTerm,
letterTailTerm,
func(head locatableRune, tail []locatableRune) Value {
name := string(head.r) + locatableRunesToString(tail)
return Value{Name: &name, Location: head.locate()}
},
)
)
func openEdgeIntoValue(val Value, oe *OpenEdge) *OpenEdge {
switch {
case oe == nil:
return graph.ValueOut(None, val)
case !oe.EdgeValue().Valid:
return oe.WithEdgeValue(Some(val))
default:
return graph.TupleOut(Some(val), oe)
}
}
var graphTerm, valueTerm = func() (*term[Value], *term[Value]) {
type tupleState struct {
ins []*OpenEdge
oe *OpenEdge
}
type graphState struct {
g *Graph
oe *OpenEdge
}
var (
rightParenthesis = trimmedRuneTerm(')')
tupleEndTerm = mapTerm(
rightParenthesis,
rightParenthesis,
func(lr locatableRune) tupleState {
// if ')', then map that to an empty state. This acts as a
// sentinel value to indicate "end of tuple".
return tupleState{}
},
)
rightCurlyBrace = trimmedRuneTerm('}')
graphEndTerm = mapTerm(
rightCurlyBrace,
rightCurlyBrace,
func(lr locatableRune) graphState {
// if '}', then map that to an empty state. This acts as a
// sentinel value to indicate "end of graph".
return graphState{}
},
)
)
var (
// pre-define these, and then fill in the pointers after, in order to
// deal with recursive dependencies between them.
valueTerm = new(term[Value])
tupleTerm = new(term[*OpenEdge])
tupleTailTerm = new(term[tupleState])
tupleOpenEdgeTerm = new(term[tupleState])
tupleOpenEdgeTailTerm = new(term[tupleState])
tupleOpenEdgeValueTailTerm = new(term[tupleState])
graphTerm = new(term[Value])
graphTailTerm = new(term[graphState])
graphOpenEdgeTerm = new(term[graphState])
graphOpenEdgeTailTerm = new(term[graphState])
graphOpenEdgeValueTailTerm = new(term[graphState])
)
*tupleTerm = *seq(
stringerStr("tuple"),
trimmedRuneTerm('('),
tupleTailTerm,
func(lr locatableRune, ts tupleState) *OpenEdge {
slices.Reverse(ts.ins)
return graph.TupleOut(None, ts.ins...)
},
)
*tupleTailTerm = *firstOf(
tupleEndTerm,
mapTerm(
tupleOpenEdgeTerm,
tupleOpenEdgeTerm,
func(ts tupleState) tupleState {
ts.ins = append(ts.ins, ts.oe)
ts.oe = nil
return ts
},
),
)
*tupleOpenEdgeTerm = *firstOf(
seq(
valueTerm,
valueTerm,
tupleOpenEdgeValueTailTerm,
func(val Value, ts tupleState) tupleState {
ts.oe = openEdgeIntoValue(val, ts.oe)
return ts
},
),
seq(
tupleTerm,
tupleTerm,
tupleOpenEdgeTailTerm,
func(oe *OpenEdge, ts tupleState) tupleState {
ts.oe = oe
return ts
},
),
)
*tupleOpenEdgeTailTerm = *firstOf(
tupleEndTerm,
prefixed(trimmedRuneTerm(','), tupleTailTerm),
)
*tupleOpenEdgeValueTailTerm = *firstOf(
tupleOpenEdgeTailTerm,
prefixed(trimmedRuneTerm('<'), tupleOpenEdgeTerm),
)
*graphTerm = *seq(
stringerStr("graph"),
trimmedRuneTerm('{'),
graphTailTerm,
func(lr locatableRune, gs graphState) Value {
if gs.g == nil {
gs.g = new(Graph)
}
return Value{Graph: gs.g, Location: lr.locate()}
},
)
*graphTailTerm = *firstOf(
graphEndTerm,
seq(
nameTerm,
nameTerm,
prefixed(trimmedRuneTerm('='), graphOpenEdgeTerm),
func(name Value, gs graphState) graphState {
if gs.g == nil {
gs.g = new(Graph)
}
gs.g = gs.g.AddValueIn(name, gs.oe)
gs.oe = nil
return gs
},
),
)
*graphOpenEdgeTerm = *firstOf(
seq(
valueTerm,
valueTerm,
graphOpenEdgeValueTailTerm,
func(val Value, gs graphState) graphState {
gs.oe = openEdgeIntoValue(val, gs.oe)
return gs
},
),
seq(
tupleTerm,
tupleTerm,
graphOpenEdgeTailTerm,
func(oe *OpenEdge, gs graphState) graphState {
gs.oe = oe
return gs
},
),
)
*graphOpenEdgeTailTerm = *firstOf(
graphEndTerm,
prefixed(trimmedRuneTerm(';'), graphTailTerm),
)
*graphOpenEdgeValueTailTerm = *firstOf(
graphOpenEdgeTailTerm,
prefixed(trimmedRuneTerm('<'), graphOpenEdgeTerm),
)
*valueTerm = *firstOf(nameTerm, numberTerm, graphTerm)
return graphTerm, valueTerm
}()
var topLevelTerm = trimmedTerm(valueTerm)

View File

@ -110,11 +110,11 @@ func EvaluateSource(opSrc io.Reader, input Value, scope Scope) (Value, error) {
v, err := gg.NewDecoder(opSrc).Next()
if err != nil {
return Value{}, err
} else if v.Graph == nil {
} else if v.Value.Graph == nil {
return Value{}, errors.New("value must be a graph")
}
fn, err := FunctionFromGraph(v.Graph, scope.NewScope())
fn, err := FunctionFromGraph(v.Value.Graph, scope.NewScope())
if err != nil {
return Value{}, err
}