diff --git a/gg/decoder.go b/gg/decoder.go index e7d4e6c..bbea867 100644 --- a/gg/decoder.go +++ b/gg/decoder.go @@ -12,13 +12,9 @@ import ( "golang.org/x/exp/slices" ) -func s(str string) fmt.Stringer { - return Stringer{S: str} -} - var ( notNewline = RuneFunc( - s("not-newline"), func(r rune) bool { return r != '\n' }, + "not-newline", func(r rune) bool { return r != '\n' }, ) comment = Prefixed( @@ -26,7 +22,7 @@ var ( ) whitespace = ZeroOrMore(FirstOf( - Discard(RuneFunc(s("whitespace"), unicode.IsSpace)), + Discard(RuneFunc("whitespace", unicode.IsSpace)), Discard(comment), )) ) @@ -43,13 +39,12 @@ func trimmedRune(r rune) Symbol[Located[rune]] { var ( digit = RuneFunc( - s("digit"), func(r rune) bool { return '0' <= r && r <= '9' }, + "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] { @@ -57,23 +52,25 @@ var ( }, ) - 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)) - } + number = Named( + "number", + Mapping( + 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 Locate(str.Location, Number(i)) - }, + return Locate(str.Location, Number(i)) + }, + ), ) ) var ( letter = RuneFunc( - s("letter"), + "letter", func(r rune) bool { return unicode.In(r, unicode.Letter, unicode.Mark) }, @@ -81,18 +78,20 @@ var ( 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 Locate(head.Location, Name(string(name))) - }, + name = Named( + "name", + Reduction( + 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 Locate(head.Location, Name(string(name))) + }, + ), ) ) @@ -122,10 +121,8 @@ var graphSym, value = func() ( } var ( - rightParen = trimmedRune(')') - tupleEnd = Mapping( - rightParen, - rightParen, + tupleEnd = Mapping( + trimmedRune(')'), func(Located[rune]) tupleState { // if ')', then map that to an empty state. This acts as a // sentinel value to indicate "end of tuple". @@ -133,10 +130,8 @@ var graphSym, value = func() ( }, ) - rightCurlyBrace = trimmedRune('}') - graphEnd = Mapping( - rightCurlyBrace, - rightCurlyBrace, + graphEnd = Mapping( + trimmedRune('}'), func(Located[rune]) graphState { // if '}', then map that to an empty state. This acts as a // sentinel value to indicate "end of graph". @@ -163,20 +158,21 @@ var graphSym, value = func() ( 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...) - }, + tuple.Symbol = Named( + "tuple", + Reduction[Located[rune], tupleState, *OpenEdge]( + 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) @@ -188,7 +184,6 @@ var graphSym, value = func() ( tupleOpenEdge.Symbol = FirstOf( Reduction[Located[Value], tupleState, tupleState]( - value, value, tupleOpenEdgeValueTail, func(val Located[Value], ts tupleState) tupleState { @@ -197,7 +192,6 @@ var graphSym, value = func() ( }, ), Reduction[*OpenEdge, tupleState, tupleState]( - tuple, tuple, tupleOpenEdgeTail, func(oe *OpenEdge, ts tupleState) tupleState { @@ -217,23 +211,24 @@ var graphSym, value = func() ( 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) - } + graphSym.Symbol = Named( + "graph", + Reduction[Located[rune], graphState, Located[Value]]( + trimmedRune('{'), + graphTail, + func(r Located[rune], gs graphState) Located[Value] { + if gs.g == nil { + gs.g = new(Graph) + } - return Locate(r.Location, Value{Graph: gs.g}) - }, + return Locate(r.Location, Value{Graph: gs.g}) + }, + ), ) graphTail.Symbol = FirstOf( graphEnd, Reduction( - name, name, Prefixed[Located[rune], graphState]( trimmedRune('='), graphOpenEdge, @@ -253,7 +248,6 @@ var graphSym, value = func() ( graphOpenEdge.Symbol = FirstOf( Reduction[Located[Value], graphState, graphState]( - value, value, graphOpenEdgeValueTail, func(val Located[Value], gs graphState) graphState { @@ -262,7 +256,6 @@ var graphSym, value = func() ( }, ), Reduction[*OpenEdge, graphState, graphState]( - tuple, tuple, graphOpenEdgeTail, func(oe *OpenEdge, gs graphState) graphState { diff --git a/gg/decoder_test.go b/gg/decoder_test.go index 68d54c5..8e9b814 100644 --- a/gg/decoder_test.go +++ b/gg/decoder_test.go @@ -41,7 +41,7 @@ func TestDecoder(t *testing.T) { } expNum := func(row, col int, n int64) Located[Value] { - return Located[Value]{Location{row, col}, Number(n)} + return Locate(Location{Row: row, Col: col}, Number(n)) } runTests(t, "number", number, []test{ @@ -53,11 +53,11 @@ func TestDecoder(t *testing.T) { }) expName := func(row, col int, name string) Located[Value] { - return Located[Value]{Location{row, col}, Name(name)} + return Locate(Location{Row: row, Col: col}, Name(name)) } expGraph := func(row, col int, g *Graph) Located[Value] { - return Located[Value]{Location{row, col}, Value{Graph: g}} + return Locate(Location{Row: row, Col: col}, Value{Graph: g}) } runTests(t, "name", name, []test{ diff --git a/gg/grammar/example_test.go b/gg/grammar/example_test.go index d97d079..86ac00a 100644 --- a/gg/grammar/example_test.go +++ b/gg/grammar/example_test.go @@ -21,9 +21,9 @@ package: ::= | ::= - ::= ")" | "," - ::= ")" | - ::= "(" + ::= ")" | "," + ::= ")" | + ::= "(" ::= | ``` @@ -48,19 +48,14 @@ func (e Element) String() string { return fmt.Sprint(e.Number) } -func s(str string) fmt.Stringer { - return grammar.Stringer{S: str} -} - var ( digit = grammar.RuneFunc( - s("digit"), func(r rune) bool { return '0' <= r && r <= '9' }, + "digit", func(r rune) bool { return '0' <= r && r <= '9' }, ) positiveNumber = grammar.StringFromRunes(grammar.OneOrMore(digit)) negativeNumber = grammar.Reduction( - s("negative-number"), grammar.Rune('-'), positiveNumber, func( @@ -70,16 +65,18 @@ var ( }, ) - number = grammar.Mapping( - s("number"), - grammar.FirstOf(negativeNumber, positiveNumber), - func(str grammar.Located[string]) Element { - i, err := strconv.ParseInt(str.Value, 10, 64) - if err != nil { - panic(fmt.Errorf("parsing %q as int: %w", str, err)) - } - return Element{Number: i} - }, + number = grammar.Named( + "number", + grammar.Mapping( + grammar.FirstOf(negativeNumber, positiveNumber), + func(str grammar.Located[string]) Element { + i, err := strconv.ParseInt(str.Value, 10, 64) + if err != nil { + panic(fmt.Errorf("parsing %q as int: %w", str, err)) + } + return Element{Number: i} + }, + ), ) // Because the list/element definitions are recursive it requires using @@ -95,19 +92,13 @@ var ( element = new(grammar.SymbolPtr[Element]) // Right parenthesis indicates the end of a list, at which point we - // can initialize the state which gets returned down the stack. We - // pass rightParen as the Stringer because we want that to still be - // the string identifier for this branch of the symbol, for error - // purposes. - rightParen = grammar.Rune(')') - listTerm = grammar.Mapping( - rightParen, - rightParen, + // can initialize the state which gets returned down the stack. + listTerm = grammar.Mapping( + grammar.Rune(')'), func(grammar.Located[rune]) listState { return listState{} }, ) listElTail = grammar.Reduction[Element, listState, listState]( - element, // Stringer element, listTail, func(el Element, ls listState) listState { @@ -124,16 +115,16 @@ var ( grammar.Prefixed(grammar.Rune(','), listElTail), ) - list.Symbol = grammar.Reduction[ - grammar.Located[rune], listState, Element, - ]( - s("list"), - grammar.Rune('('), - listHead, - func(_ grammar.Located[rune], ls listState) Element { - slices.Reverse(ls) - return Element{List: []Element(ls)} - }, + list.Symbol = grammar.Named( + "list", + grammar.Reduction[grammar.Located[rune], listState, Element]( + grammar.Rune('('), + listHead, + func(_ grammar.Located[rune], ls listState) Element { + slices.Reverse(ls) + return Element{List: []Element(ls)} + }, + ), ) element.Symbol = grammar.FirstOf[Element](number, list) diff --git a/gg/grammar/symbol.go b/gg/grammar/symbol.go index daace9c..37ebd97 100644 --- a/gg/grammar/symbol.go +++ b/gg/grammar/symbol.go @@ -47,11 +47,24 @@ type SymbolPtr[T any] struct { Symbol[T] } +func named[T any](stringer fmt.Stringer, sym Symbol[T]) Symbol[T] { + return &symbol[T]{ + stringer, + sym.Decode, + } +} + +// Named wraps the given Symbol such that its String method returns the given +// name. +func Named[T any](name string, sym Symbol[T]) Symbol[T] { + return named(Stringer{S: name}, sym) +} + // 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]] { +func RuneFunc(name string, fn func(rune) bool) Symbol[Located[rune]] { return &symbol[Located[rune]]{ - stringer, + Stringer{S: name}, func(rr Reader) (Located[rune], error) { var zero Located[rune] @@ -75,7 +88,7 @@ func RuneFunc(stringer fmt.Stringer, fn func(rune) bool) Symbol[Located[rune]] { // Rune matches and produces the given rune. func Rune(r rune) Symbol[Located[rune]] { return RuneFunc( - Stringer{S: fmt.Sprintf("'%c'", r)}, + fmt.Sprintf("'%c'", r), func(r2 rune) bool { return r == r2 }, ) } @@ -84,7 +97,7 @@ func Rune(r rune) Symbol[Located[rune]] { // 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] { + return Mapping(sym, func(runes []Located[rune]) Located[string] { if len(runes) == 0 { panic("StringFromRunes used on empty set of runes") } @@ -101,10 +114,10 @@ func StringFromRunes(sym Symbol[[]Located[rune]]) Symbol[Located[string]] { // 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, + sym Symbol[Ta], fn func(Ta) Tb, ) Symbol[Tb] { return &symbol[Tb]{ - stringer, + sym, func(rr Reader) (Tb, error) { var zero Tb va, err := sym.Decode(rr) @@ -214,13 +227,12 @@ func FirstOf[T any](syms ...Symbol[T]) Symbol[T] { // 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, + symA, func(rr Reader) (Tc, error) { var zero Tc @@ -248,9 +260,9 @@ func Reduction[Ta, Tb, Tc any]( // 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 named(prefixSym, Reduction(prefixSym, sym, func(_ Ta, b Tb) Tb { return b - }) + })) } // PrefixDiscarded is similar to Prefixed, except that if sym does not match @@ -282,13 +294,13 @@ func PrefixDiscarded[Ta, Tb any](prefixSym Symbol[Ta], sym Symbol[Tb]) Symbol[Tb // 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 named(sym, Reduction(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{}{} }) + return Mapping(sym, func(T) struct{} { return struct{}{} }) }