Further simplifications to grammar

This commit is contained in:
Brian Picciano 2023-10-29 10:20:37 +01:00
parent 9139d4830d
commit 3ef69920c7
4 changed files with 113 additions and 117 deletions

View File

@ -12,13 +12,9 @@ import (
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
) )
func s(str string) fmt.Stringer {
return Stringer{S: str}
}
var ( var (
notNewline = RuneFunc( notNewline = RuneFunc(
s("not-newline"), func(r rune) bool { return r != '\n' }, "not-newline", func(r rune) bool { return r != '\n' },
) )
comment = Prefixed( comment = Prefixed(
@ -26,7 +22,7 @@ var (
) )
whitespace = ZeroOrMore(FirstOf( whitespace = ZeroOrMore(FirstOf(
Discard(RuneFunc(s("whitespace"), unicode.IsSpace)), Discard(RuneFunc("whitespace", unicode.IsSpace)),
Discard(comment), Discard(comment),
)) ))
) )
@ -43,13 +39,12 @@ func trimmedRune(r rune) Symbol[Located[rune]] {
var ( var (
digit = RuneFunc( 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)) positiveNumber = StringFromRunes(OneOrMore(digit))
negativeNumber = Reduction( negativeNumber = Reduction(
s("negative-number"),
Rune('-'), Rune('-'),
positiveNumber, positiveNumber,
func(neg Located[rune], posNum Located[string]) Located[string] { func(neg Located[rune], posNum Located[string]) Located[string] {
@ -57,23 +52,25 @@ var (
}, },
) )
number = Mapping( number = Named(
s("number"), "number",
FirstOf(negativeNumber, positiveNumber), Mapping(
func(str Located[string]) Located[Value] { FirstOf(negativeNumber, positiveNumber),
i, err := strconv.ParseInt(str.Value, 10, 64) func(str Located[string]) Located[Value] {
if err != nil { i, err := strconv.ParseInt(str.Value, 10, 64)
panic(fmt.Errorf("parsing %q as int: %w", str, err)) 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 ( var (
letter = RuneFunc( letter = RuneFunc(
s("letter"), "letter",
func(r rune) bool { func(r rune) bool {
return unicode.In(r, unicode.Letter, unicode.Mark) return unicode.In(r, unicode.Letter, unicode.Mark)
}, },
@ -81,18 +78,20 @@ var (
nameTail = ZeroOrMore(FirstOf(letter, digit)) nameTail = ZeroOrMore(FirstOf(letter, digit))
name = Reduction( name = Named(
s("name"), "name",
letter, Reduction(
nameTail, letter,
func(head Located[rune], tail []Located[rune]) Located[Value] { nameTail,
name := make([]rune, 0, len(tail)+1) func(head Located[rune], tail []Located[rune]) Located[Value] {
name = append(name, head.Value) name := make([]rune, 0, len(tail)+1)
for _, r := range tail { name = append(name, head.Value)
name = append(name, r.Value) for _, r := range tail {
} name = append(name, r.Value)
return Locate(head.Location, Name(string(name))) }
}, return Locate(head.Location, Name(string(name)))
},
),
) )
) )
@ -122,10 +121,8 @@ var graphSym, value = func() (
} }
var ( var (
rightParen = trimmedRune(')') tupleEnd = Mapping(
tupleEnd = Mapping( trimmedRune(')'),
rightParen,
rightParen,
func(Located[rune]) tupleState { func(Located[rune]) tupleState {
// if ')', then map that to an empty state. This acts as a // if ')', then map that to an empty state. This acts as a
// sentinel value to indicate "end of tuple". // sentinel value to indicate "end of tuple".
@ -133,10 +130,8 @@ var graphSym, value = func() (
}, },
) )
rightCurlyBrace = trimmedRune('}') graphEnd = Mapping(
graphEnd = Mapping( trimmedRune('}'),
rightCurlyBrace,
rightCurlyBrace,
func(Located[rune]) graphState { func(Located[rune]) graphState {
// if '}', then map that to an empty state. This acts as a // if '}', then map that to an empty state. This acts as a
// sentinel value to indicate "end of graph". // sentinel value to indicate "end of graph".
@ -163,20 +158,21 @@ var graphSym, value = func() (
graphOpenEdgeValueTail = new(SymbolPtr[graphState]) graphOpenEdgeValueTail = new(SymbolPtr[graphState])
) )
tuple.Symbol = Reduction[Located[rune], tupleState, *OpenEdge]( tuple.Symbol = Named(
s("tuple"), "tuple",
trimmedRune('('), Reduction[Located[rune], tupleState, *OpenEdge](
tupleTail, trimmedRune('('),
func(_ Located[rune], ts tupleState) *OpenEdge { tupleTail,
slices.Reverse(ts.ins) func(_ Located[rune], ts tupleState) *OpenEdge {
return graph.TupleOut(None, ts.ins...) slices.Reverse(ts.ins)
}, return graph.TupleOut(None, ts.ins...)
},
),
) )
tupleTail.Symbol = FirstOf( tupleTail.Symbol = FirstOf(
tupleEnd, tupleEnd,
Mapping[tupleState, tupleState]( Mapping[tupleState, tupleState](
tupleOpenEdge,
tupleOpenEdge, tupleOpenEdge,
func(ts tupleState) tupleState { func(ts tupleState) tupleState {
ts.ins = append(ts.ins, ts.oe) ts.ins = append(ts.ins, ts.oe)
@ -188,7 +184,6 @@ var graphSym, value = func() (
tupleOpenEdge.Symbol = FirstOf( tupleOpenEdge.Symbol = FirstOf(
Reduction[Located[Value], tupleState, tupleState]( Reduction[Located[Value], tupleState, tupleState](
value,
value, value,
tupleOpenEdgeValueTail, tupleOpenEdgeValueTail,
func(val Located[Value], ts tupleState) tupleState { func(val Located[Value], ts tupleState) tupleState {
@ -197,7 +192,6 @@ var graphSym, value = func() (
}, },
), ),
Reduction[*OpenEdge, tupleState, tupleState]( Reduction[*OpenEdge, tupleState, tupleState](
tuple,
tuple, tuple,
tupleOpenEdgeTail, tupleOpenEdgeTail,
func(oe *OpenEdge, ts tupleState) tupleState { func(oe *OpenEdge, ts tupleState) tupleState {
@ -217,23 +211,24 @@ var graphSym, value = func() (
Prefixed[Located[rune], tupleState](trimmedRune('<'), tupleOpenEdge), Prefixed[Located[rune], tupleState](trimmedRune('<'), tupleOpenEdge),
) )
graphSym.Symbol = Reduction[Located[rune], graphState, Located[Value]]( graphSym.Symbol = Named(
s("graph"), "graph",
trimmedRune('{'), Reduction[Located[rune], graphState, Located[Value]](
graphTail, trimmedRune('{'),
func(r Located[rune], gs graphState) Located[Value] { graphTail,
if gs.g == nil { func(r Located[rune], gs graphState) Located[Value] {
gs.g = new(Graph) 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( graphTail.Symbol = FirstOf(
graphEnd, graphEnd,
Reduction( Reduction(
name,
name, name,
Prefixed[Located[rune], graphState]( Prefixed[Located[rune], graphState](
trimmedRune('='), graphOpenEdge, trimmedRune('='), graphOpenEdge,
@ -253,7 +248,6 @@ var graphSym, value = func() (
graphOpenEdge.Symbol = FirstOf( graphOpenEdge.Symbol = FirstOf(
Reduction[Located[Value], graphState, graphState]( Reduction[Located[Value], graphState, graphState](
value,
value, value,
graphOpenEdgeValueTail, graphOpenEdgeValueTail,
func(val Located[Value], gs graphState) graphState { func(val Located[Value], gs graphState) graphState {
@ -262,7 +256,6 @@ var graphSym, value = func() (
}, },
), ),
Reduction[*OpenEdge, graphState, graphState]( Reduction[*OpenEdge, graphState, graphState](
tuple,
tuple, tuple,
graphOpenEdgeTail, graphOpenEdgeTail,
func(oe *OpenEdge, gs graphState) graphState { func(oe *OpenEdge, gs graphState) graphState {

View File

@ -41,7 +41,7 @@ func TestDecoder(t *testing.T) {
} }
expNum := func(row, col int, n int64) Located[Value] { 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{ runTests(t, "number", number, []test{
@ -53,11 +53,11 @@ func TestDecoder(t *testing.T) {
}) })
expName := func(row, col int, name string) Located[Value] { 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] { 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{ runTests(t, "name", name, []test{

View File

@ -21,9 +21,9 @@ package:
<number> ::= <negative-number> | <positive-number> <number> ::= <negative-number> | <positive-number>
<list-el-tail> ::= <element> <list-tail> <list-el-tail> ::= <element> <list-tail>
<list-tail> ::= ")" | "," <list-el-tail> <list-tail> ::= ")" | "," <list-el-tail>
<list-head> ::= ")" | <list-el-tail> <list-head> ::= ")" | <list-el-tail>
<list> ::= "(" <list-head> <list> ::= "(" <list-head>
<element> ::= <number> | <list> <element> ::= <number> | <list>
``` ```
@ -48,19 +48,14 @@ func (e Element) String() string {
return fmt.Sprint(e.Number) return fmt.Sprint(e.Number)
} }
func s(str string) fmt.Stringer {
return grammar.Stringer{S: str}
}
var ( var (
digit = grammar.RuneFunc( 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)) positiveNumber = grammar.StringFromRunes(grammar.OneOrMore(digit))
negativeNumber = grammar.Reduction( negativeNumber = grammar.Reduction(
s("negative-number"),
grammar.Rune('-'), grammar.Rune('-'),
positiveNumber, positiveNumber,
func( func(
@ -70,16 +65,18 @@ var (
}, },
) )
number = grammar.Mapping( number = grammar.Named(
s("number"), "number",
grammar.FirstOf(negativeNumber, positiveNumber), grammar.Mapping(
func(str grammar.Located[string]) Element { grammar.FirstOf(negativeNumber, positiveNumber),
i, err := strconv.ParseInt(str.Value, 10, 64) func(str grammar.Located[string]) Element {
if err != nil { i, err := strconv.ParseInt(str.Value, 10, 64)
panic(fmt.Errorf("parsing %q as int: %w", str, err)) if err != nil {
} panic(fmt.Errorf("parsing %q as int: %w", str, err))
return Element{Number: i} }
}, return Element{Number: i}
},
),
) )
// Because the list/element definitions are recursive it requires using // Because the list/element definitions are recursive it requires using
@ -95,19 +92,13 @@ var (
element = new(grammar.SymbolPtr[Element]) element = new(grammar.SymbolPtr[Element])
// Right parenthesis indicates the end of a list, at which point we // Right parenthesis indicates the end of a list, at which point we
// can initialize the state which gets returned down the stack. We // can initialize the state which gets returned down the stack.
// pass rightParen as the Stringer because we want that to still be listTerm = grammar.Mapping(
// the string identifier for this branch of the symbol, for error grammar.Rune(')'),
// purposes.
rightParen = grammar.Rune(')')
listTerm = grammar.Mapping(
rightParen,
rightParen,
func(grammar.Located[rune]) listState { return listState{} }, func(grammar.Located[rune]) listState { return listState{} },
) )
listElTail = grammar.Reduction[Element, listState, listState]( listElTail = grammar.Reduction[Element, listState, listState](
element, // Stringer
element, element,
listTail, listTail,
func(el Element, ls listState) listState { func(el Element, ls listState) listState {
@ -124,16 +115,16 @@ var (
grammar.Prefixed(grammar.Rune(','), listElTail), grammar.Prefixed(grammar.Rune(','), listElTail),
) )
list.Symbol = grammar.Reduction[ list.Symbol = grammar.Named(
grammar.Located[rune], listState, Element, "list",
]( grammar.Reduction[grammar.Located[rune], listState, Element](
s("list"), grammar.Rune('('),
grammar.Rune('('), listHead,
listHead, func(_ grammar.Located[rune], ls listState) Element {
func(_ grammar.Located[rune], ls listState) Element { slices.Reverse(ls)
slices.Reverse(ls) return Element{List: []Element(ls)}
return Element{List: []Element(ls)} },
}, ),
) )
element.Symbol = grammar.FirstOf[Element](number, list) element.Symbol = grammar.FirstOf[Element](number, list)

View File

@ -47,11 +47,24 @@ type SymbolPtr[T any] struct {
Symbol[T] 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 // RuneFunc matches and produces any rune for which the given function returns
// true. // 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]]{ return &symbol[Located[rune]]{
stringer, Stringer{S: name},
func(rr Reader) (Located[rune], error) { func(rr Reader) (Located[rune], error) {
var zero Located[rune] 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. // Rune matches and produces the given rune.
func Rune(r rune) Symbol[Located[rune]] { func Rune(r rune) Symbol[Located[rune]] {
return RuneFunc( return RuneFunc(
Stringer{S: fmt.Sprintf("'%c'", r)}, fmt.Sprintf("'%c'", r),
func(r2 rune) bool { return r == r2 }, 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 // given Symbol. The slice must not be empty. StringFromRunes does not match if
// the given Symbol does not match. // the given Symbol does not match.
func StringFromRunes(sym Symbol[[]Located[rune]]) Symbol[Located[string]] { 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 { if len(runes) == 0 {
panic("StringFromRunes used on empty set of runes") 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 // Symbol and passing it through the given mapping function. If the given Symbol
// doesn't match then neither does Map. // doesn't match then neither does Map.
func Mapping[Ta, Tb any]( func Mapping[Ta, Tb any](
stringer fmt.Stringer, sym Symbol[Ta], fn func(Ta) Tb, sym Symbol[Ta], fn func(Ta) Tb,
) Symbol[Tb] { ) Symbol[Tb] {
return &symbol[Tb]{ return &symbol[Tb]{
stringer, sym,
func(rr Reader) (Tb, error) { func(rr Reader) (Tb, error) {
var zero Tb var zero Tb
va, err := sym.Decode(rr) 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 // 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. // symB does not then also match then Reduction produces a LocatedError.
func Reduction[Ta, Tb, Tc any]( func Reduction[Ta, Tb, Tc any](
stringer fmt.Stringer,
symA Symbol[Ta], symA Symbol[Ta],
symB Symbol[Tb], symB Symbol[Tb],
fn func(Ta, Tb) Tc, fn func(Ta, Tb) Tc,
) Symbol[Tc] { ) Symbol[Tc] {
return &symbol[Tc]{ return &symbol[Tc]{
stringer, symA,
func(rr Reader) (Tc, error) { func(rr Reader) (Tc, error) {
var zero Tc 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 // If prefixSym does not match then Prefixed does not match. If prefixSym
// matches but sym does not also match then Prefixed produces a LocatedError. // 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] { 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 return b
}) }))
} }
// PrefixDiscarded is similar to Prefixed, except that if sym does not match // 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 // If sym does not match then Suffixed does not match. If sym matches but
// suffixSym does not also match then Suffixed produces a LocatedError. // suffixSym does not also match then Suffixed produces a LocatedError.
func Suffixed[Ta, Tb any](sym Symbol[Ta], suffixSym Symbol[Tb]) Symbol[Ta] { 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 return a
}) }))
} }
// Discard matches if the given Symbol does, but discards the value it produces, // Discard matches if the given Symbol does, but discards the value it produces,
// producing an empty value instead. // producing an empty value instead.
func Discard[T any](sym Symbol[T]) Symbol[struct{}] { 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{}{} })
} }