diff --git a/gg/v2/gg.bnf b/gg/v2/gg.bnf index a6d262b..2530000 100644 --- a/gg/v2/gg.bnf +++ b/gg/v2/gg.bnf @@ -5,20 +5,19 @@ ::= ( | ) ( | | )* - ::= | | | + ::= "(" + ::= ")" | + ::= + ::= ")" + | "," + | "<" - ::= "(" - ::= ")" | - ::= ")" - | "," - | "<" + ::= "{" + ::= "}" | "=" + ::= + | + ::= "}" | ";" + ::= | "<" - ::= "{" - ::= "}" | "=" - ::= - ::= "}" - | ";" - | "<" - - ::= | | - ::= | + ::= | | + ::= | diff --git a/gg/v2/term.go b/gg/v2/term.go index 675e024..5cb8c08 100644 --- a/gg/v2/term.go +++ b/gg/v2/term.go @@ -9,6 +9,7 @@ import ( "unicode" "github.com/mediocregopher/ginger/graph" + "golang.org/x/exp/slices" ) var ( @@ -27,6 +28,10 @@ func (str stringerStr) String() string { return string(str) } +// TODO in the end I don't think locatable is actually needed here... I think +// seq is the only place where locating anything is required generally, and +// that's done using Decoder.nextLoc. Otherwise the Location is used when +// constructing Value's, which only really requires runes to be locatable. type term[T locatable] struct { name fmt.Stringer decodeFn func(d *Decoder) (T, error) @@ -71,7 +76,7 @@ func seq[Ta, Tb, Tc locatable]( name fmt.Stringer, termA *term[Ta], termB *term[Tb], - fn func(Ta, Tb) (Tc, error), + fn func(Ta, Tb) (Tc, error), // TODO probably don't need error return ) *term[Tc] { return &term[Tc]{ name: name, @@ -276,7 +281,29 @@ var ( ) ) +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 locatableOpenEdge struct { + Location + oe *OpenEdge + } + + type tupleState struct { + Location // location of last place tupleState was updated + ins []*OpenEdge + oe *OpenEdge + } + type graphState struct { Location // location of last place graphState was updated g *Graph @@ -284,18 +311,22 @@ var graphTerm = func() *term[Value] { } var ( - // pre-define these, and then fill in the pointers after, in order to - // deal with recursive dependencies between them. - graphTerm = new(term[Value]) - graphTailTerm = new(term[graphState]) - graphOpenEdgeTerm = new(term[graphState]) - graphOpenEdgeTailTerm = new(term[graphState]) - valueTerm = new(term[Value]) + 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{Location: lr.locate()} + }, + ) rightCurlyBrace = runeTerm('}') graphEndTerm = mapTerm( rightCurlyBrace, - rightCurlyBrace, func(lr locatableRune) graphState { + 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{Location: lr.locate()} @@ -303,6 +334,67 @@ var graphTerm = func() *term[Value] { ) ) + 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[locatableOpenEdge]) + 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) (locatableOpenEdge, error) { + slices.Reverse(ts.ins) + oe := graph.TupleOut(None, ts.ins...) + return locatableOpenEdge{ + Location: lr.locate(), + oe: oe, + }, nil + }, + ) + + *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, error) { + ts.oe = openEdgeIntoValue(val, ts.oe) + ts.Location = val.locate() + return ts, nil + }, + ) + + *tupleOpenEdgeTailTerm = *firstOf( + tupleEndTerm, + matchAndSkip(runeTerm(','), tupleTailTerm), + matchAndSkip(runeTerm('<'), tupleOpenEdgeTerm), + ) + *graphTerm = *seq( stringerStr("graph"), runeTerm('{'), @@ -321,7 +413,7 @@ var graphTerm = func() *term[Value] { seq( nameTerm, nameTerm, - matchAndSkip(runeTerm('='), graphOpenEdgeTailTerm), + matchAndSkip(runeTerm('='), graphOpenEdgeTerm), func(name Value, gs graphState) (graphState, error) { if gs.g == nil { gs.g = new(Graph) @@ -336,27 +428,36 @@ var graphTerm = func() *term[Value] { ) *graphOpenEdgeTerm = *firstOf( - graphEndTerm, - matchAndSkip(runeTerm(';'), graphTailTerm), - matchAndSkip(runeTerm('<'), graphOpenEdgeTailTerm), + seq( + valueTerm, + valueTerm, + graphOpenEdgeValueTailTerm, + func(val Value, gs graphState) (graphState, error) { + gs.oe = openEdgeIntoValue(val, gs.oe) + gs.Location = val.locate() + return gs, nil + }, + ), + seq( + tupleTerm, + tupleTerm, + graphOpenEdgeTailTerm, + func(loe locatableOpenEdge, gs graphState) (graphState, error) { + gs.oe = loe.oe + gs.Location = loe.locate() + return gs, nil + }, + ), ) - *graphOpenEdgeTailTerm = *seq( - valueTerm, - valueTerm, - graphOpenEdgeTerm, - func(val Value, gs graphState) (graphState, error) { - if gs.oe == nil { - gs.oe = graph.ValueOut(None, val) - } else if !gs.oe.EdgeValue().Valid { - gs.oe = gs.oe.WithEdgeValue(Some(val)) - } else { - gs.oe = graph.TupleOut(Some(val), gs.oe) - } + *graphOpenEdgeTailTerm = *firstOf( + graphEndTerm, + matchAndSkip(runeTerm(';'), graphTailTerm), + ) - gs.Location = val.locate() - return gs, nil - }, + *graphOpenEdgeValueTailTerm = *firstOf( + graphOpenEdgeTailTerm, + matchAndSkip(runeTerm('<'), graphOpenEdgeTerm), ) *valueTerm = *firstOf(nameTerm, numberTerm, graphTerm) diff --git a/gg/v2/term_test.go b/gg/v2/term_test.go index 00e76c5..e6d8c85 100644 --- a/gg/v2/term_test.go +++ b/gg/v2/term_test.go @@ -84,7 +84,7 @@ func TestTermDecoding(t *testing.T) { {in: `{}`, exp: expGraph(1, 1, new(Graph))}, {in: `{`, expErr: `1:2: expected '}' or name`}, {in: `{a}`, expErr: `1:3: expected '='`}, - {in: `{a=}`, expErr: `1:4: expected name or number or graph`}, + {in: `{a=}`, expErr: `1:4: expected name or number or graph or tuple`}, { in: `{foo=a}`, exp: expGraph( @@ -217,4 +217,89 @@ func TestTermDecoding(t *testing.T) { ), }, }) + + runTests(t, "tuple", graphTerm, []test{ + { + in: `{foo=(a)}`, + exp: expGraph( + 1, 1, new(Graph). + AddValueIn( + expName(2, 1, "foo"), + graph.ValueOut(None, expName(6, 1, "a")), + ), + ), + }, + { + in: `{foo=(a