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"
)
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 {

View File

@ -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{

View File

@ -21,9 +21,9 @@ package:
<number> ::= <negative-number> | <positive-number>
<list-el-tail> ::= <element> <list-tail>
<list-tail> ::= ")" | "," <list-el-tail>
<list-head> ::= ")" | <list-el-tail>
<list> ::= "(" <list-head>
<list-tail> ::= ")" | "," <list-el-tail>
<list-head> ::= ")" | <list-el-tail>
<list> ::= "(" <list-head>
<element> ::= <number> | <list>
```
@ -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)

View File

@ -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{}{} })
}