441 lines
8.5 KiB
Go
441 lines
8.5 KiB
Go
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 matchAndSkip[Ta, Tb any](
|
|
termA *term[Ta], termB *term[Tb],
|
|
) *term[Tb] {
|
|
return seq(termA, termA, termB, func(_ Ta, b Tb) Tb {
|
|
return b
|
|
})
|
|
}
|
|
|
|
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)}
|
|
},
|
|
)
|
|
}
|
|
|
|
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 = func() *term[Value] {
|
|
type tupleState struct {
|
|
ins []*OpenEdge
|
|
oe *OpenEdge
|
|
}
|
|
|
|
type graphState struct {
|
|
g *Graph
|
|
oe *OpenEdge
|
|
}
|
|
|
|
var (
|
|
rightParenthesis = runeTerm(')')
|
|
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 = runeTerm('}')
|
|
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])
|
|
|
|
graphTerm = new(term[Value])
|
|
graphTailTerm = new(term[graphState])
|
|
graphOpenEdgeTerm = new(term[graphState])
|
|
graphOpenEdgeTailTerm = new(term[graphState])
|
|
graphOpenEdgeValueTailTerm = new(term[graphState])
|
|
)
|
|
|
|
*tupleTerm = *seq(
|
|
stringerStr("tuple"),
|
|
runeTerm('('),
|
|
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 = *seq(
|
|
valueTerm,
|
|
valueTerm,
|
|
tupleOpenEdgeTailTerm,
|
|
func(val Value, ts tupleState) tupleState {
|
|
ts.oe = openEdgeIntoValue(val, ts.oe)
|
|
return ts
|
|
},
|
|
)
|
|
|
|
*tupleOpenEdgeTailTerm = *firstOf(
|
|
tupleEndTerm,
|
|
matchAndSkip(runeTerm(','), tupleTailTerm),
|
|
matchAndSkip(runeTerm('<'), tupleOpenEdgeTerm),
|
|
)
|
|
|
|
*graphTerm = *seq(
|
|
stringerStr("graph"),
|
|
runeTerm('{'),
|
|
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,
|
|
matchAndSkip(runeTerm('='), 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,
|
|
matchAndSkip(runeTerm(';'), graphTailTerm),
|
|
)
|
|
|
|
*graphOpenEdgeValueTailTerm = *firstOf(
|
|
graphOpenEdgeTailTerm,
|
|
matchAndSkip(runeTerm('<'), graphOpenEdgeTerm),
|
|
)
|
|
|
|
*valueTerm = *firstOf(nameTerm, numberTerm, graphTerm)
|
|
|
|
return graphTerm
|
|
}()
|