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(
|
res, err := vm.EvaluateSource(
|
||||||
bytes.NewBufferString(opSrc),
|
bytes.NewBufferString(opSrc),
|
||||||
vm.Value{Value: inVal},
|
vm.Value{Value: inVal.Value},
|
||||||
vm.GlobalScope,
|
vm.GlobalScope,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
341
gg/decoder.go
341
gg/decoder.go
@ -1,73 +1,310 @@
|
|||||||
package gg
|
package gg
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"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.
|
func s(str string) fmt.Stringer {
|
||||||
type Decoder struct {
|
return Stringer{S: str}
|
||||||
br *bufio.Reader
|
|
||||||
brNextLoc Location
|
|
||||||
|
|
||||||
unread []locatableRune
|
|
||||||
lastRead locatableRune
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDecoder returns a Decoder which will decode the given stream as a gg
|
var (
|
||||||
// formatted stream of a Values.
|
notNewline = RuneFunc(
|
||||||
func NewDecoder(r io.Reader) *Decoder {
|
s("not-newline"), func(r rune) bool { return r != '\n' },
|
||||||
return &Decoder{
|
)
|
||||||
br: bufio.NewReader(r),
|
|
||||||
brNextLoc: Location{Row: 1, Col: 1},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Decoder) readRune() (locatableRune, error) {
|
comment = Prefixed(
|
||||||
if len(d.unread) > 0 {
|
Prefixed(Rune('*'), ZeroOrMore(notNewline)), Rune('\n'),
|
||||||
d.lastRead = d.unread[len(d.unread)-1]
|
)
|
||||||
d.unread = d.unread[:len(d.unread)-1]
|
|
||||||
return d.lastRead, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
loc := d.brNextLoc
|
whitespace = ZeroOrMore(FirstOf(
|
||||||
|
Discard(RuneFunc(s("whitespace"), unicode.IsSpace)),
|
||||||
r, _, err := d.br.ReadRune()
|
Discard(comment),
|
||||||
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,
|
|
||||||
))
|
))
|
||||||
|
)
|
||||||
|
|
||||||
|
func trimmed[T any](sym Symbol[T]) Symbol[T] {
|
||||||
|
sym = PrefixDiscarded(whitespace, sym)
|
||||||
|
sym = Suffixed(sym, whitespace)
|
||||||
|
return sym
|
||||||
}
|
}
|
||||||
|
|
||||||
d.unread = append(d.unread, lr)
|
func trimmedRune(r rune) Symbol[Located[rune]] {
|
||||||
|
return trimmed(Rune(r))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Decoder) nextLoc() Location {
|
var (
|
||||||
if len(d.unread) > 0 {
|
digit = RuneFunc(
|
||||||
return d.unread[len(d.unread)-1].Location
|
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 d.brNextLoc
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next returns the next top-level value in the stream, or io.EOF.
|
var graphSym, value = func() (
|
||||||
func (d *Decoder) Next() (Value, error) {
|
Symbol[Located[Value]], Symbol[Located[Value]],
|
||||||
return topLevelTerm.decodeFn(d)
|
) {
|
||||||
|
|
||||||
|
type tupleState struct {
|
||||||
|
ins []*OpenEdge
|
||||||
|
oe *OpenEdge
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
|
@ -2,86 +2,72 @@ package gg
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"io"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
. "github.com/mediocregopher/ginger/gg/grammar"
|
||||||
"github.com/mediocregopher/ginger/graph"
|
"github.com/mediocregopher/ginger/graph"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func decoderLeftover(d *Decoder) string {
|
func TestDecoder(t *testing.T) {
|
||||||
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 {
|
type test struct {
|
||||||
in string
|
in string
|
||||||
exp Value
|
exp Located[Value]
|
||||||
expErr string
|
expErr string
|
||||||
leftover string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
runTests := func(
|
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) {
|
t.Run(name, func(t *testing.T) {
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
||||||
dec := NewDecoder(bytes.NewBufferString(test.in))
|
r := NewReader(bytes.NewBufferString(test.in))
|
||||||
got, err := term.decodeFn(dec)
|
got, err := sym.Decode(r)
|
||||||
if test.expErr != "" {
|
if test.expErr != "" {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Equal(t, test.expErr, err.Error())
|
assert.Equal(t, test.expErr, err.Error())
|
||||||
} else if assert.NoError(t, err) {
|
} else if assert.NoError(t, err) {
|
||||||
assert.True(t,
|
assert.True(t,
|
||||||
test.exp.Equal(got),
|
test.exp.Value.Equal(got.Value),
|
||||||
"\nexp:%v\ngot:%v", test.exp, got,
|
"\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 {
|
expNum := func(row, col int, n int64) Located[Value] {
|
||||||
return Value{Number: &n, Location: Location{row, col}}
|
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: `0`, exp: expNum(1, 1, 0)},
|
||||||
{in: `100`, exp: expNum(1, 1, 100)},
|
{in: `100`, exp: expNum(1, 1, 100)},
|
||||||
{in: `-100`, exp: expNum(1, 1, -100)},
|
{in: `-100`, exp: expNum(1, 1, -100)},
|
||||||
{in: `0foo`, exp: expNum(1, 1, 0), leftover: "foo"},
|
{in: `0foo`, exp: expNum(1, 1, 0)},
|
||||||
{in: `100foo`, exp: expNum(1, 1, 100), leftover: "foo"},
|
{in: `100foo`, exp: expNum(1, 1, 100)},
|
||||||
})
|
})
|
||||||
|
|
||||||
expName := func(row, col int, name string) Value {
|
expName := func(row, col int, name string) Located[Value] {
|
||||||
return Value{Name: &name, Location: Location{row, col}}
|
return Located[Value]{Location{row, col}, Name(name)}
|
||||||
}
|
}
|
||||||
|
|
||||||
expGraph := func(row, col int, g *Graph) Value {
|
expGraph := func(row, col int, g *Graph) Located[Value] {
|
||||||
return Value{Graph: g, Location: Location{row, col}}
|
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: `a`, exp: expName(1, 1, "a")},
|
||||||
{in: `ab`, exp: expName(1, 1, "ab")},
|
{in: `ab`, exp: expName(1, 1, "ab")},
|
||||||
{in: `ab2c`, exp: expName(1, 1, "ab2c")},
|
{in: `ab2c`, exp: expName(1, 1, "ab2c")},
|
||||||
{in: `ab2c,`, exp: expName(1, 1, "ab2c"), 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: `{}`, exp: expGraph(1, 1, new(Graph))},
|
||||||
{in: `{`, expErr: `1:2: expected '}' or name`},
|
{in: `{`, expErr: `1:2: expected '}' or name`},
|
||||||
{in: `{a}`, expErr: `1:3: expected '='`},
|
{in: `{a}`, expErr: `1:3: expected '='`},
|
||||||
@ -90,20 +76,14 @@ func TestTermDecoding(t *testing.T) {
|
|||||||
in: `{foo=a}`,
|
in: `{foo=a}`,
|
||||||
exp: expGraph(
|
exp: expGraph(
|
||||||
1, 1, new(Graph).
|
1, 1, new(Graph).
|
||||||
AddValueIn(
|
AddValueIn(Name("foo"), graph.ValueOut(None, Name("a"))),
|
||||||
expName(2, 1, "foo"),
|
|
||||||
graph.ValueOut(None, expName(6, 1, "a")),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
in: `{ foo = a }`,
|
in: `{ foo = a }`,
|
||||||
exp: expGraph(
|
exp: expGraph(
|
||||||
1, 1, new(Graph).
|
1, 1, new(Graph).
|
||||||
AddValueIn(
|
AddValueIn(Name("foo"), graph.ValueOut(None, Name("a"))),
|
||||||
expName(2, 1, "foo"),
|
|
||||||
graph.ValueOut(None, expName(6, 1, "a")),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{in: `{1=a}`, expErr: `1:2: expected '}' or name`},
|
{in: `{1=a}`, expErr: `1:2: expected '}' or name`},
|
||||||
@ -114,11 +94,8 @@ func TestTermDecoding(t *testing.T) {
|
|||||||
exp: expGraph(
|
exp: expGraph(
|
||||||
1, 1, new(Graph).
|
1, 1, new(Graph).
|
||||||
AddValueIn(
|
AddValueIn(
|
||||||
expName(2, 1, "foo"),
|
Name("foo"),
|
||||||
graph.ValueOut(
|
graph.ValueOut(Some(Name("a")), Name("b")),
|
||||||
Some(expName(6, 1, "a")),
|
|
||||||
expName(8, 1, "b"),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@ -127,12 +104,12 @@ func TestTermDecoding(t *testing.T) {
|
|||||||
exp: expGraph(
|
exp: expGraph(
|
||||||
1, 1, new(Graph).
|
1, 1, new(Graph).
|
||||||
AddValueIn(
|
AddValueIn(
|
||||||
expName(2, 1, "foo"),
|
Name("foo"),
|
||||||
graph.TupleOut(
|
graph.TupleOut(
|
||||||
Some(expName(6, 1, "a")),
|
Some(Name("a")),
|
||||||
graph.ValueOut(
|
graph.ValueOut(
|
||||||
Some(expName(8, 1, "b")),
|
Some(Name("b")),
|
||||||
expName(10, 1, "c"),
|
Name("c"),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -143,14 +120,14 @@ func TestTermDecoding(t *testing.T) {
|
|||||||
exp: expGraph(
|
exp: expGraph(
|
||||||
1, 1, new(Graph).
|
1, 1, new(Graph).
|
||||||
AddValueIn(
|
AddValueIn(
|
||||||
expName(2, 1, "foo"),
|
Name("foo"),
|
||||||
graph.TupleOut(
|
graph.TupleOut(
|
||||||
Some(expName(6, 1, "a")),
|
Some(Name("a")),
|
||||||
graph.TupleOut(
|
graph.TupleOut(
|
||||||
Some(expName(8, 1, "b")),
|
Some(Name("b")),
|
||||||
graph.ValueOut(
|
graph.ValueOut(
|
||||||
Some(expName(10, 1, "c")),
|
Some(Name("c")),
|
||||||
expNum(12, 1, 1),
|
Number(1),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -162,10 +139,10 @@ func TestTermDecoding(t *testing.T) {
|
|||||||
exp: expGraph(
|
exp: expGraph(
|
||||||
1, 1, new(Graph).
|
1, 1, new(Graph).
|
||||||
AddValueIn(
|
AddValueIn(
|
||||||
expName(2, 1, "foo"),
|
Name("foo"),
|
||||||
graph.ValueOut(
|
graph.ValueOut(
|
||||||
Some(expName(6, 1, "a")),
|
Some(Name("a")),
|
||||||
expName(8, 1, "b"),
|
Name("b"),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -175,15 +152,15 @@ func TestTermDecoding(t *testing.T) {
|
|||||||
exp: expGraph(
|
exp: expGraph(
|
||||||
1, 1, new(Graph).
|
1, 1, new(Graph).
|
||||||
AddValueIn(
|
AddValueIn(
|
||||||
expName(2, 1, "foo"),
|
Name("foo"),
|
||||||
graph.ValueOut(
|
graph.ValueOut(
|
||||||
Some(expName(6, 1, "a")),
|
Some(Name("a")),
|
||||||
expName(8, 1, "b"),
|
Name("b"),
|
||||||
),
|
),
|
||||||
).
|
).
|
||||||
AddValueIn(
|
AddValueIn(
|
||||||
expName(10, 1, "bar"),
|
Name("bar"),
|
||||||
graph.ValueOut(None, expName(15, 1, "c")),
|
graph.ValueOut(None, Name("c")),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@ -192,18 +169,18 @@ func TestTermDecoding(t *testing.T) {
|
|||||||
exp: expGraph(
|
exp: expGraph(
|
||||||
1, 1, new(Graph).
|
1, 1, new(Graph).
|
||||||
AddValueIn(
|
AddValueIn(
|
||||||
expName(2, 1, "foo"),
|
Name("foo"),
|
||||||
graph.ValueOut(
|
graph.ValueOut(
|
||||||
Some(expName(6, 1, "a")),
|
Some(Name("a")),
|
||||||
expGraph(8, 1, new(Graph).AddValueIn(
|
Value{Graph: new(Graph).AddValueIn(
|
||||||
expName(9, 1, "baz"),
|
Name("baz"),
|
||||||
graph.ValueOut(None, expNum(13, 1, 1)),
|
graph.ValueOut(None, Number(1)),
|
||||||
)),
|
)},
|
||||||
),
|
),
|
||||||
).
|
).
|
||||||
AddValueIn(
|
AddValueIn(
|
||||||
expName(16, 1, "bar"),
|
Name("bar"),
|
||||||
graph.ValueOut(None, expName(20, 1, "c")),
|
graph.ValueOut(None, Name("c")),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@ -212,31 +189,31 @@ func TestTermDecoding(t *testing.T) {
|
|||||||
exp: expGraph(
|
exp: expGraph(
|
||||||
1, 1, new(Graph).
|
1, 1, new(Graph).
|
||||||
AddValueIn(
|
AddValueIn(
|
||||||
expName(2, 1, "foo"),
|
Name("foo"),
|
||||||
graph.ValueOut(
|
graph.ValueOut(
|
||||||
Some(expGraph(8, 1, new(Graph).AddValueIn(
|
Some(Value{Graph: new(Graph).AddValueIn(
|
||||||
expName(9, 1, "baz"),
|
Name("baz"),
|
||||||
graph.ValueOut(None, expNum(13, 1, 1)),
|
graph.ValueOut(None, Number(1)),
|
||||||
))),
|
)}),
|
||||||
expName(6, 1, "a"),
|
Name("a"),
|
||||||
),
|
),
|
||||||
).
|
).
|
||||||
AddValueIn(
|
AddValueIn(
|
||||||
expName(16, 1, "bar"),
|
Name("bar"),
|
||||||
graph.ValueOut(None, expName(20, 1, "c")),
|
graph.ValueOut(None, Name("c")),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
runTests(t, "tuple", graphTerm, []test{
|
runTests(t, "tuple", graphSym, []test{
|
||||||
{
|
{
|
||||||
in: `{foo=(a)}`,
|
in: `{foo=(a)}`,
|
||||||
exp: expGraph(
|
exp: expGraph(
|
||||||
1, 1, new(Graph).
|
1, 1, new(Graph).
|
||||||
AddValueIn(
|
AddValueIn(
|
||||||
expName(2, 1, "foo"),
|
Name("foo"),
|
||||||
graph.ValueOut(None, expName(6, 1, "a")),
|
graph.ValueOut(None, Name("a")),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@ -245,10 +222,10 @@ func TestTermDecoding(t *testing.T) {
|
|||||||
exp: expGraph(
|
exp: expGraph(
|
||||||
1, 1, new(Graph).
|
1, 1, new(Graph).
|
||||||
AddValueIn(
|
AddValueIn(
|
||||||
expName(2, 1, "foo"),
|
Name("foo"),
|
||||||
graph.ValueOut(
|
graph.ValueOut(
|
||||||
Some(expName(6, 1, "a")),
|
Some(Name("a")),
|
||||||
expName(8, 1, "b"),
|
Name("b"),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -258,10 +235,10 @@ func TestTermDecoding(t *testing.T) {
|
|||||||
exp: expGraph(
|
exp: expGraph(
|
||||||
1, 1, new(Graph).
|
1, 1, new(Graph).
|
||||||
AddValueIn(
|
AddValueIn(
|
||||||
expName(2, 1, "foo"),
|
Name("foo"),
|
||||||
graph.ValueOut(
|
graph.ValueOut(
|
||||||
Some(expName(6, 1, "a")),
|
Some(Name("a")),
|
||||||
expName(8, 1, "b"),
|
Name("b"),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -271,11 +248,11 @@ func TestTermDecoding(t *testing.T) {
|
|||||||
exp: expGraph(
|
exp: expGraph(
|
||||||
1, 1, new(Graph).
|
1, 1, new(Graph).
|
||||||
AddValueIn(
|
AddValueIn(
|
||||||
expName(2, 1, "foo"),
|
Name("foo"),
|
||||||
graph.TupleOut(
|
graph.TupleOut(
|
||||||
Some(expName(6, 1, "a")),
|
Some(Name("a")),
|
||||||
graph.ValueOut(None, expName(8, 1, "b")),
|
graph.ValueOut(None, Name("b")),
|
||||||
graph.ValueOut(None, expName(10, 1, "c")),
|
graph.ValueOut(None, Name("c")),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -285,12 +262,12 @@ func TestTermDecoding(t *testing.T) {
|
|||||||
exp: expGraph(
|
exp: expGraph(
|
||||||
1, 1, new(Graph).
|
1, 1, new(Graph).
|
||||||
AddValueIn(
|
AddValueIn(
|
||||||
expName(2, 1, "foo"),
|
Name("foo"),
|
||||||
graph.TupleOut(
|
graph.TupleOut(
|
||||||
Some(expName(6, 1, "a")),
|
Some(Name("a")),
|
||||||
graph.TupleOut(
|
graph.TupleOut(
|
||||||
Some(expName(6, 1, "b")),
|
Some(Name("b")),
|
||||||
graph.ValueOut(None, expName(8, 1, "c")),
|
graph.ValueOut(None, Name("c")),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -301,12 +278,12 @@ func TestTermDecoding(t *testing.T) {
|
|||||||
exp: expGraph(
|
exp: expGraph(
|
||||||
1, 1, new(Graph).
|
1, 1, new(Graph).
|
||||||
AddValueIn(
|
AddValueIn(
|
||||||
expName(2, 1, "foo"),
|
Name("foo"),
|
||||||
graph.TupleOut(
|
graph.TupleOut(
|
||||||
Some(expName(6, 1, "a")),
|
Some(Name("a")),
|
||||||
graph.TupleOut(
|
graph.TupleOut(
|
||||||
Some(expName(6, 1, "b")),
|
Some(Name("b")),
|
||||||
graph.ValueOut(None, expName(8, 1, "c")),
|
graph.ValueOut(None, Name("c")),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -317,15 +294,15 @@ func TestTermDecoding(t *testing.T) {
|
|||||||
exp: expGraph(
|
exp: expGraph(
|
||||||
1, 1, new(Graph).
|
1, 1, new(Graph).
|
||||||
AddValueIn(
|
AddValueIn(
|
||||||
expName(2, 1, "foo"),
|
Name("foo"),
|
||||||
graph.TupleOut(
|
graph.TupleOut(
|
||||||
Some(expName(6, 1, "a")),
|
Some(Name("a")),
|
||||||
graph.TupleOut(
|
graph.TupleOut(
|
||||||
Some(expName(6, 1, "b")),
|
Some(Name("b")),
|
||||||
graph.ValueOut(None, expName(8, 1, "c")),
|
graph.ValueOut(None, Name("c")),
|
||||||
graph.ValueOut(
|
graph.ValueOut(
|
||||||
Some(expName(12, 1, "d")),
|
Some(Name("d")),
|
||||||
expNum(10, 1, 1),
|
Number(1),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -337,12 +314,12 @@ func TestTermDecoding(t *testing.T) {
|
|||||||
exp: expGraph(
|
exp: expGraph(
|
||||||
1, 1, new(Graph).
|
1, 1, new(Graph).
|
||||||
AddValueIn(
|
AddValueIn(
|
||||||
expName(2, 1, "foo"),
|
Name("foo"),
|
||||||
graph.TupleOut(
|
graph.TupleOut(
|
||||||
Some(expName(6, 1, "a")),
|
Some(Name("a")),
|
||||||
graph.TupleOut(
|
graph.TupleOut(
|
||||||
Some(expName(6, 1, "b")),
|
Some(Name("b")),
|
||||||
graph.ValueOut(None, expName(8, 1, "c")),
|
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{}",
|
in: "*\n{}",
|
||||||
exp: expGraph(1, 1, new(Graph)),
|
exp: expGraph(2, 1, new(Graph)),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
in: "* ignore me!\n{}",
|
in: "* ignore me!\n{}",
|
||||||
exp: expGraph(1, 1, new(Graph)),
|
exp: expGraph(2, 1, new(Graph)),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
in: "{* ignore me!\n}",
|
in: "{* ignore me!\n}",
|
||||||
@ -368,8 +345,8 @@ func TestTermDecoding(t *testing.T) {
|
|||||||
exp: expGraph(
|
exp: expGraph(
|
||||||
1, 1, new(Graph).
|
1, 1, new(Graph).
|
||||||
AddValueIn(
|
AddValueIn(
|
||||||
expName(2, 1, "foo"),
|
Name("foo"),
|
||||||
graph.ValueOut(None, expName(6, 1, "a")),
|
graph.ValueOut(None, Name("a")),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@ -378,8 +355,8 @@ func TestTermDecoding(t *testing.T) {
|
|||||||
exp: expGraph(
|
exp: expGraph(
|
||||||
1, 1, new(Graph).
|
1, 1, new(Graph).
|
||||||
AddValueIn(
|
AddValueIn(
|
||||||
expName(2, 1, "foo"),
|
Name("foo"),
|
||||||
graph.ValueOut(None, expName(6, 1, "a")),
|
graph.ValueOut(None, Name("a")),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
},
|
},
|
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.
|
// Value represents a value which can be serialized by the gg text format.
|
||||||
type Value struct {
|
type Value struct {
|
||||||
Location
|
|
||||||
|
|
||||||
// Only one of these fields may be set
|
// Only one of these fields may be set
|
||||||
Name *string
|
Name *string
|
||||||
Number *int64
|
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()
|
v, err := gg.NewDecoder(opSrc).Next()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Value{}, err
|
return Value{}, err
|
||||||
} else if v.Graph == nil {
|
} else if v.Value.Graph == nil {
|
||||||
return Value{}, errors.New("value must be a graph")
|
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 {
|
if err != nil {
|
||||||
return Value{}, err
|
return Value{}, err
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user