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 }()