Break grammar parsing utilities into their own package
This commit is contained in:
parent
360857d506
commit
c2adfa3b46
@ -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 {
|
||||
|
335
gg/decoder.go
335
gg/decoder.go
@ -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' },
|
||||
)
|
||||
|
||||
comment = Prefixed(
|
||||
Prefixed(Rune('*'), ZeroOrMore(notNewline)), Rune('\n'),
|
||||
)
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
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) 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
|
||||
var graphSym, value = func() (
|
||||
Symbol[Located[Value]], Symbol[Located[Value]],
|
||||
) {
|
||||
|
||||
type tupleState struct {
|
||||
ins []*OpenEdge
|
||||
oe *OpenEdge
|
||||
}
|
||||
|
||||
loc := d.brNextLoc
|
||||
|
||||
r, _, err := d.br.ReadRune()
|
||||
if err != nil {
|
||||
return d.lastRead, err
|
||||
type graphState struct {
|
||||
g *Graph
|
||||
oe *OpenEdge
|
||||
}
|
||||
|
||||
if r == '\n' {
|
||||
d.brNextLoc.Row++
|
||||
d.brNextLoc.Col = 1
|
||||
} else {
|
||||
d.brNextLoc.Col++
|
||||
}
|
||||
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{}
|
||||
},
|
||||
)
|
||||
|
||||
d.lastRead = locatableRune{loc, r}
|
||||
return d.lastRead, nil
|
||||
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)
|
||||
}
|
||||
|
||||
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,
|
||||
))
|
||||
}
|
||||
|
||||
d.unread = append(d.unread, lr)
|
||||
type decoder struct {
|
||||
r Reader
|
||||
}
|
||||
|
||||
func (d *Decoder) nextLoc() Location {
|
||||
if len(d.unread) > 0 {
|
||||
return d.unread[len(d.unread)-1].Location
|
||||
}
|
||||
|
||||
return d.brNextLoc
|
||||
// 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)}
|
||||
}
|
||||
|
||||
// Next returns the next top-level value in the stream, or io.EOF.
|
||||
func (d *Decoder) Next() (Value, error) {
|
||||
return topLevelTerm.decodeFn(d)
|
||||
func (d *decoder) Next() (Located[Value], error) {
|
||||
return value.Decode(d.r)
|
||||
}
|
||||
|
@ -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
|
||||
expErr string
|
||||
leftover string
|
||||
in string
|
||||
exp Located[Value]
|
||||
expErr 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")),
|
||||
),
|
||||
),
|
||||
},
|
@ -17,7 +17,7 @@
|
||||
<graph-open-edge> ::= <value> <graph-open-edge-value-tail>
|
||||
| <tuple> <graph-open-edge-tail>
|
||||
<graph-open-edge-tail> ::= "}" | ";" <graph-tail>
|
||||
<graph-open-edge-value-tail> ::= <graph-open-edge-tail> | "<" <graph-open-edge>
|
||||
<graph-open-edge-value-tail> ::= <graph-open-edge-tail> | "<" <graph-open-edge>
|
||||
|
||||
<value> ::= <name> | <number> | <graph>
|
||||
<gg> ::= <eof> | <value> <gg>
|
||||
|
2
gg/gg.go
2
gg/gg.go
@ -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
27
gg/grammar/grammar.go
Normal 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
27
gg/grammar/location.go
Normal 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
74
gg/grammar/reader.go
Normal 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
294
gg/grammar/symbol.go
Normal 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{}{} })
|
||||
}
|
@ -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
|
||||
}
|
508
gg/term.go
508
gg/term.go
@ -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)
|
4
vm/vm.go
4
vm/vm.go
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user