Implemented P.ExpectEndOfFile() and shortened some parser/combinator functions (since Go-people seem to like that better than a somewhat longer descriptive name)

This commit is contained in:
Maurice Makaay 2019-05-24 13:59:01 +00:00
parent 6ad4499971
commit c164f320cb
7 changed files with 226 additions and 161 deletions

View File

@ -7,7 +7,7 @@ import (
)
// C provides convenient access to a range of parser/combinators
// that can be used to build Matcher functions.
// that can be used to construct Matcher functions.
//
// When using C in your own parser, then it is advised to create
// a variable in your own package to reference it:
@ -16,53 +16,52 @@ import (
//
// Doing so saves you a lot of typing, and it makes your code a lot cleaner.
var C = struct {
Rune func(rune) Matcher
Runes func(...rune) Matcher
RuneRange func(rune, rune) Matcher
String func(string) Matcher
StringNoCase func(string) Matcher
AnyOf func(...Matcher) Matcher
Not func(Matcher) Matcher
Optional func(Matcher) Matcher
Sequence func(...Matcher) Matcher
Repeat func(int, Matcher) Matcher
Min func(int, Matcher) Matcher
Max func(int, Matcher) Matcher
ZeroOrMore func(Matcher) Matcher
OneOrMore func(Matcher) Matcher
MinMax func(int, int, Matcher) Matcher
Separated func(Matcher, Matcher) Matcher
Drop func(Matcher) Matcher
Trim func(Matcher, string) Matcher
TrimLeft func(Matcher, string) Matcher
TrimRight func(Matcher, string) Matcher
Rune func(rune) Matcher
Runes func(...rune) Matcher
RuneRange func(rune, rune) Matcher
Str func(string) Matcher
StrNoCase func(string) Matcher
Any func(...Matcher) Matcher
Not func(Matcher) Matcher
Opt func(Matcher) Matcher
Seq func(...Matcher) Matcher
Rep func(int, Matcher) Matcher
Min func(int, Matcher) Matcher
Max func(int, Matcher) Matcher
ZeroOrMore func(Matcher) Matcher
OneOrMore func(Matcher) Matcher
MinMax func(int, int, Matcher) Matcher
Separated func(Matcher, Matcher) Matcher
}{
Rune: MatchRune,
Runes: MatchRunes,
RuneRange: MatchRuneRange,
String: MatchString,
StringNoCase: MatchStringNoCase,
Optional: MatchOptional,
AnyOf: MatchAnyOf,
Not: MatchNot,
Sequence: MatchSequence,
Repeat: MatchRepeat,
Min: MatchMin,
Max: MatchMax,
ZeroOrMore: MatchZeroOrMore,
OneOrMore: MatchOneOrMore,
MinMax: MatchMinMax,
Separated: MatchSeparated,
Drop: MatchDrop,
Trim: MatchTrim,
TrimLeft: MatchTrimLeft,
TrimRight: MatchTrimRight,
Rune: MatchRune,
Runes: MatchRunes,
RuneRange: MatchRuneRange,
Str: MatchStr,
StrNoCase: MatchStrNoCase,
Opt: MatchOpt,
Any: MatchAny,
Not: MatchNot,
Seq: MatchSeq,
Rep: MatchRep,
Min: MatchMin,
Max: MatchMax,
ZeroOrMore: MatchZeroOrMore,
OneOrMore: MatchOneOrMore,
MinMax: MatchMinMax,
Separated: MatchSeparated,
}
// A provides convenient access to a range of atoms that can be used to
// build combinators or parsing rules.
//
// In parsekit, an atom is defined as a ready to go Matcher function.
//
// When using A in your own parser, then it is advised to create
// a variable in your own package to reference it:
//
// var a = parsekit.A
//
// Doing so saves you a lot of typing, and it makes your code a lot cleaner.
var A = struct {
EndOfFile Matcher
AnyRune Matcher
@ -119,7 +118,7 @@ var A = struct {
Tab: C.Rune('\t'),
CR: C.Rune('\r'),
LF: C.Rune('\n'),
CRLF: C.String("\r\n"),
CRLF: C.Str("\r\n"),
Excl: C.Rune('!'),
DoubleQuote: C.Rune('"'),
Hash: C.Rune('#'),
@ -152,14 +151,43 @@ var A = struct {
Pipe: C.Rune('|'),
CurlyClose: C.Rune('}'),
Tilde: C.Rune('~'),
Whitespace: C.OneOrMore(C.AnyOf(C.Rune(' '), C.Rune('\t'))),
WhitespaceAndNewlines: C.OneOrMore(C.AnyOf(C.Rune(' '), C.Rune('\t'), C.String("\r\n"), C.Rune('\n'))),
EndOfLine: C.AnyOf(C.String("\r\n"), C.Rune('\n'), MatchEndOfFile()),
Whitespace: C.OneOrMore(C.Any(C.Rune(' '), C.Rune('\t'))),
WhitespaceAndNewlines: C.OneOrMore(C.Any(C.Rune(' '), C.Rune('\t'), C.Str("\r\n"), C.Rune('\n'))),
EndOfLine: C.Any(C.Str("\r\n"), C.Rune('\n'), MatchEndOfFile()),
Digit: C.RuneRange('0', '9'),
ASCII: C.RuneRange('\x00', '\x7F'),
ASCIILower: C.RuneRange('a', 'z'),
ASCIIUpper: C.RuneRange('A', 'Z'),
HexDigit: C.AnyOf(C.RuneRange('0', '9'), C.RuneRange('a', 'f'), C.RuneRange('A', 'F')),
HexDigit: C.Any(C.RuneRange('0', '9'), C.RuneRange('a', 'f'), C.RuneRange('A', 'F')),
}
// M provides convenient access to a range of modifiers that can be
// used when creating Matcher functions.
//
// In parsekit, a modifier is defined as a Matcher function that modifies the
// resulting output of another Matcher in some way. It does not do any matching
// against input of its own.
//
// When using M in your own parser, then it is advised to create
// a variable in your own package to reference it:
//
// var m = parsekit.M
//
// Doing so saves you a lot of typing, and it makes your code a lot cleaner.
var M = struct {
Drop func(Matcher) Matcher
Trim func(Matcher, string) Matcher
TrimLeft func(Matcher, string) Matcher
TrimRight func(Matcher, string) Matcher
ToLower func(Matcher) Matcher
ToUpper func(Matcher) Matcher
}{
Drop: ModifyDrop,
Trim: ModifyTrim,
TrimLeft: ModifyTrimLeft,
TrimRight: ModifyTrimRight,
ToLower: ModifyToLower,
ToUpper: ModifyToUpper,
}
// MatchRune creates a Matcher function that checks if the next rune from
@ -213,34 +241,34 @@ func MatchRuneRange(start rune, end rune) Matcher {
}
}
// MatchString creater a Matcher that will check if the upcoming runes on the
// MatchStr creates a Matcher that will check if the upcoming runes on the
// input match the provided string.
// TODO make this a more efficient string-level match?
func MatchString(expected string) Matcher {
func MatchStr(expected string) Matcher {
var matchers = []Matcher{}
for _, r := range expected {
matchers = append(matchers, MatchRune(r))
}
return MatchSequence(matchers...)
return MatchSeq(matchers...)
}
// MatchStringNoCase creater a Matcher that will check if the upcoming runes
// MatchStrNoCase creates a Matcher that will check if the upcoming runes
// on the input match the provided string in a case-insensitive manner.
// TODO make this a more efficient string-level match?
func MatchStringNoCase(expected string) Matcher {
func MatchStrNoCase(expected string) Matcher {
var matchers = []Matcher{}
for _, r := range expected {
u := unicode.ToUpper(r)
l := unicode.ToLower(r)
matchers = append(matchers, MatchRunes(u, l))
}
return MatchSequence(matchers...)
return MatchSeq(matchers...)
}
// MatchOptional creates a Matcher that makes the provided Matcher optional.
// MatchOpt creates a Matcher that makes the provided Matcher optional.
// When the provided Matcher applies, then its output is used, otherwise
// no output is generated but still a successful match is reported.
func MatchOptional(matcher Matcher) Matcher {
func MatchOpt(matcher Matcher) Matcher {
return func(m *MatchDialog) bool {
child := m.Fork()
if matcher(child) {
@ -250,10 +278,10 @@ func MatchOptional(matcher Matcher) Matcher {
}
}
// MatchSequence creates a Matcher that checks if the provided Matchers can be
// MatchSeq creates a Matcher that checks if the provided Matchers can be
// applied in their exact order. Only if all matcher apply, the sequence
// reports successful match.
func MatchSequence(matchers ...Matcher) Matcher {
func MatchSeq(matchers ...Matcher) Matcher {
return func(m *MatchDialog) bool {
child := m.Fork()
for _, matcher := range matchers {
@ -266,10 +294,10 @@ func MatchSequence(matchers ...Matcher) Matcher {
}
}
// MatchAnyOf creates a Matcher that checks if any of the provided Matchers
// MatchAny creates a Matcher that checks if any of the provided Matchers
// can be applied. They are applied in their provided order. The first Matcher
// that applies is used for reporting back a match.
func MatchAnyOf(matchers ...Matcher) Matcher {
func MatchAny(matchers ...Matcher) Matcher {
return func(m *MatchDialog) bool {
for _, matcher := range matchers {
child := m.Fork()
@ -299,16 +327,16 @@ func MatchNot(matcher Matcher) Matcher {
}
}
// MatchRepeat creates a Matcher that checks if the provided Matcher can be
// MatchRep creates a Matcher that checks if the provided Matcher can be
// applied exactly the provided amount of times.
//
// Note that the input can contain more Matches for the provided matcher, e.g.:
//
// MatchRepeat(4, MatchRune('X'))
// MatchRep(4, MatchRune('X'))
//
// will not match input "XXX", it will match input "XXXX", but also "XXXXXX".
// In that last case, there will be a remainder "XX" of the input.
func MatchRepeat(times int, matcher Matcher) Matcher {
func MatchRep(times int, matcher Matcher) Matcher {
return matchMinMax(times, times, matcher)
}
@ -357,7 +385,7 @@ func matchMinMax(min int, max int, matcher Matcher) Matcher {
return func(m *MatchDialog) bool {
child := m.Fork()
if max >= 0 && min > max {
panic(fmt.Sprintf("internal parser error: MatchRepeat definition error: max %d must not be < min %d", max, min))
panic(fmt.Sprintf("internal parser error: MatchRep definition error: max %d must not be < min %d", max, min))
}
total := 0
// Check for the minimum required amount of matches.
@ -386,69 +414,7 @@ func matchMinMax(min int, max int, matcher Matcher) Matcher {
// (the separator). All matches (separated + separator) are included in the
// output.
func MatchSeparated(separated Matcher, separator Matcher) Matcher {
return MatchSequence(separated, MatchZeroOrMore(MatchSequence(separator, separated)))
}
// MatchDrop creates a Matcher that checks if the provided Matcher applies.
// If it does, then a successful match is reported, but its output is not used.
// If the Matcher does not apply, a successful match is reported as well.
func MatchDrop(matcher Matcher) Matcher {
return func(m *MatchDialog) bool {
child := m.Fork()
if matcher(child) {
child.ClearOutput()
child.Merge()
return true
}
return true
}
}
// MatchTrim creates a Matcher that checks if the provided Matcher applies.
// If it does, then its output is taken and characters from the provided
// cutset are trimmed from both the left and the right of the output.
// The trimmed output is reported back as the match output.
func MatchTrim(matcher Matcher, cutset string) Matcher {
return func(m *MatchDialog) bool {
return matchTrim(m, cutset, matcher, true, true)
}
}
// MatchTrimLeft creates a Matcher that checks if the provided Matcher applies.
// If it does, then its output is taken and characters from the provided
// cutset are trimmed from the left of the output.
// The trimmed output is reported back as the match output.
func MatchTrimLeft(matcher Matcher, cutset string) Matcher {
return func(m *MatchDialog) bool {
return matchTrim(m, cutset, matcher, true, false)
}
}
// MatchTrimRight creates a Matcher that checks if the provided Matcher applies.
// If it does, then its output is taken and characters from the provided
// cutset are trimmed from the right of the output.
// The trimmed output is reported back as the match output.
func MatchTrimRight(matcher Matcher, cutset string) Matcher {
return func(m *MatchDialog) bool {
return matchTrim(m, cutset, matcher, false, true)
}
}
func matchTrim(m *MatchDialog, cutset string, matcher Matcher, trimLeft bool, trimRight bool) bool {
child := m.Fork()
if matcher(child) {
child.Merge()
s := string(m.output)
if trimLeft {
s = strings.TrimLeft(s, cutset)
}
if trimRight {
s = strings.TrimRight(s, cutset)
}
m.output = []rune(s)
return true
}
return false
return MatchSeq(separated, MatchZeroOrMore(MatchSeq(separator, separated)))
}
// MatchEndOfFile creates a Matcher that checks if the end of the input data
@ -475,3 +441,86 @@ func MatchAnyRune() Matcher {
return false
}
}
// ModifyDrop creates a Matcher that checks if the provided Matcher applies.
// If it does, then its output is discarded completely.
//
// Note that if the Matcher does not apply, a mismatch will be reported back,
// even though we would have dropped the output anyway. So if you would like
// to drop optional whitespace, then use something like:
//
// M.Drop(C.Opt(A.Whitespace))
//
// instead of:
//
// M.Drop(A.Whitespace)
//
// Since whitespace is defined as "1 or more spaces and/or tabs", the input
// string "bork" would not match against the second form, but " bork" would.
// In both cases, it would match the first form.
func ModifyDrop(matcher Matcher) Matcher {
return modifyStrCallback(matcher, func(s string) string {
return ""
})
}
// ModifyTrim creates a Matcher that checks if the provided Matcher applies.
// If it does, then its output is taken and characters from the provided
// cutset are trimmed from both the left and the right of the output.
func ModifyTrim(matcher Matcher, cutset string) Matcher {
return modifyTrim(matcher, cutset, true, true)
}
// ModifyTrimLeft creates a Matcher that checks if the provided Matcher applies.
// If it does, then its output is taken and characters from the provided
// cutset are trimmed from the left of the output.
func ModifyTrimLeft(matcher Matcher, cutset string) Matcher {
return modifyTrim(matcher, cutset, true, false)
}
// ModifyTrimRight creates a Matcher that checks if the provided Matcher applies.
// If it does, then its output is taken and characters from the provided
// cutset are trimmed from the right of the output.
func ModifyTrimRight(matcher Matcher, cutset string) Matcher {
return modifyTrim(matcher, cutset, false, true)
}
func modifyTrim(matcher Matcher, cutset string, trimLeft bool, trimRight bool) Matcher {
modfunc := func(s string) string {
if trimLeft {
s = strings.TrimLeft(s, cutset)
}
if trimRight {
s = strings.TrimRight(s, cutset)
}
return s
}
return modifyStrCallback(matcher, modfunc)
}
// ModifyToUpper creates a Matcher that checks if the provided Matcher applies.
// If it does, then its output is taken and characters from the provided
// cutset are converted into upper case.
func ModifyToUpper(matcher Matcher) Matcher {
return modifyStrCallback(matcher, strings.ToUpper)
}
// ModifyToLower creates a Matcher that checks if the provided Matcher applies.
// If it does, then its output is taken and characters from the provided
// cutset are converted into lower case.
func ModifyToLower(matcher Matcher) Matcher {
return modifyStrCallback(matcher, strings.ToLower)
}
func modifyStrCallback(matcher Matcher, modfunc func(string) string) Matcher {
return func(m *MatchDialog) bool {
child := m.Fork()
if matcher(child) {
s := modfunc(string(child.output))
child.output = []rune(s)
child.Merge()
return true
}
return false
}
}

View File

@ -32,8 +32,8 @@ func TestCombinators(t *testing.T) {
{"dd", c.RuneRange('b', 'e'), true, "d"},
{"ee", c.RuneRange('b', 'e'), true, "e"},
{"ff", c.RuneRange('b', 'e'), false, ""},
{"Hello, world!", c.String("Hello"), true, "Hello"},
{"HellÖ, world!", c.StringNoCase("hellö"), true, "HellÖ"},
{"Hello, world!", c.Str("Hello"), true, "Hello"},
{"HellÖ, world!", c.StrNoCase("hellö"), true, "HellÖ"},
{"+X", c.Runes('+', '-', '*', '/'), true, "+"},
{"-X", c.Runes('+', '-', '*', '/'), true, "-"},
{"*X", c.Runes('+', '-', '*', '/'), true, "*"},
@ -42,13 +42,13 @@ func TestCombinators(t *testing.T) {
{"abc", c.Not(c.Rune('b')), true, "a"},
{"bcd", c.Not(c.Rune('b')), false, ""},
{"bcd", c.Not(c.Rune('b')), false, ""},
{"1010", c.Not(c.Sequence(c.Rune('2'), c.Rune('0'))), true, "1"},
{"2020", c.Not(c.Sequence(c.Rune('2'), c.Rune('0'))), false, ""},
{"abc", c.AnyOf(c.Rune('a'), c.Rune('b')), true, "a"},
{"bcd", c.AnyOf(c.Rune('a'), c.Rune('b')), true, "b"},
{"cde", c.AnyOf(c.Rune('a'), c.Rune('b')), false, ""},
{"ababc", c.Repeat(4, c.Runes('a', 'b')), true, "abab"},
{"ababc", c.Repeat(5, c.Runes('a', 'b')), false, ""},
{"1010", c.Not(c.Seq(c.Rune('2'), c.Rune('0'))), true, "1"},
{"2020", c.Not(c.Seq(c.Rune('2'), c.Rune('0'))), false, ""},
{"abc", c.Any(c.Rune('a'), c.Rune('b')), true, "a"},
{"bcd", c.Any(c.Rune('a'), c.Rune('b')), true, "b"},
{"cde", c.Any(c.Rune('a'), c.Rune('b')), false, ""},
{"ababc", c.Rep(4, c.Runes('a', 'b')), true, "abab"},
{"ababc", c.Rep(5, c.Runes('a', 'b')), false, ""},
{"", c.Min(0, c.Rune('a')), true, ""},
{"a", c.Min(0, c.Rune('a')), true, "a"},
{"aaaaa", c.Min(4, c.Rune('a')), true, "aaaaa"},
@ -79,23 +79,28 @@ func TestCombinators(t *testing.T) {
{"X", c.ZeroOrMore(c.Rune('e')), true, ""},
{"eX", c.ZeroOrMore(c.Rune('e')), true, "e"},
{"eeeeeX", c.ZeroOrMore(c.Rune('e')), true, "eeeee"},
{"Hello, world!X", c.Sequence(c.String("Hello"), a.Comma, a.Space, c.String("world"), a.Excl), true, "Hello, world!"},
{"101010123", c.OneOrMore(c.Sequence(c.Rune('1'), c.Rune('0'))), true, "101010"},
{"", c.Optional(c.OneOrMore(c.Rune('f'))), true, ""},
{"ghijkl", c.Optional(c.Rune('h')), true, ""},
{"ghijkl", c.Optional(c.Rune('g')), true, "g"},
{"fffffX", c.Optional(c.OneOrMore(c.Rune('f'))), true, "fffff"},
{"Hello, world!X", c.Seq(c.Str("Hello"), a.Comma, a.Space, c.Str("world"), a.Excl), true, "Hello, world!"},
{"101010123", c.OneOrMore(c.Seq(c.Rune('1'), c.Rune('0'))), true, "101010"},
{"", c.Opt(c.OneOrMore(c.Rune('f'))), true, ""},
{"ghijkl", c.Opt(c.Rune('h')), true, ""},
{"ghijkl", c.Opt(c.Rune('g')), true, "g"},
{"fffffX", c.Opt(c.OneOrMore(c.Rune('f'))), true, "fffff"},
{"1,2,3,b,c", c.Separated(a.Digit, a.Comma), true, "1,2,3"},
{"--cool", c.Sequence(c.Drop(c.OneOrMore(a.Minus)), c.String("cool")), true, "cool"},
{`\x9a\x01\xF0\xfCAndSomeMoreStuff`, c.OneOrMore(c.Sequence(a.Backslash, c.Rune('x'), c.Repeat(2, a.HexDigit))), true, `\x9a\x01\xF0\xfC`},
{" ", c.Trim(c.OneOrMore(a.AnyRune), " "), true, ""},
{" ", c.TrimLeft(c.OneOrMore(a.AnyRune), " "), true, ""},
{" ", c.TrimRight(c.OneOrMore(a.AnyRune), " "), true, ""},
{" trim ", c.Trim(c.OneOrMore(a.AnyRune), " "), true, "trim"},
{" \t trim \t ", c.Trim(c.OneOrMore(a.AnyRune), " \t"), true, "trim"},
{" trim ", c.TrimLeft(c.OneOrMore(a.AnyRune), " "), true, "trim "},
{" trim ", c.TrimRight(c.OneOrMore(a.AnyRune), " "), true, " trim"},
{" \t trim \t ", c.TrimRight(c.OneOrMore(a.AnyRune), " \t"), true, " \t trim"},
{`\x9a\x01\xF0\xfCAndSomeMoreStuff`, c.OneOrMore(c.Seq(a.Backslash, c.Rune('x'), c.Rep(2, a.HexDigit))), true, `\x9a\x01\xF0\xfC`},
{" ", m.Trim(c.OneOrMore(a.AnyRune), " "), true, ""},
{" ", m.TrimLeft(c.OneOrMore(a.AnyRune), " "), true, ""},
{" ", m.TrimRight(c.OneOrMore(a.AnyRune), " "), true, ""},
})
}
func TestModifiers(t *testing.T) {
RunMatcherTests(t, []MatcherTest{
{" trim ", m.Trim(c.OneOrMore(a.AnyRune), " "), true, "trim"},
{" \t trim \t ", m.Trim(c.OneOrMore(a.AnyRune), " \t"), true, "trim"},
{" trim ", m.TrimLeft(c.OneOrMore(a.AnyRune), " "), true, "trim "},
{" trim ", m.TrimRight(c.OneOrMore(a.AnyRune), " "), true, " trim"},
{" \t trim \t ", m.TrimRight(c.OneOrMore(a.AnyRune), " \t"), true, " \t trim"},
{"--cool", c.Seq(m.Drop(c.OneOrMore(a.Minus)), c.Str("cool")), true, "cool"},
})
}
@ -179,7 +184,7 @@ func TestAtoms(t *testing.T) {
}
func TestSequenceOfRunes(t *testing.T) {
sequence := c.Sequence(
sequence := c.Seq(
a.Hash, a.Dollar, a.Percent, a.Amp, a.SingleQuote, a.RoundOpen,
a.RoundClose, a.Asterisk, a.Plus, a.Comma, a.Minus, a.Dot, a.Slash,
a.Colon, a.Semicolon, a.AngleOpen, a.Equal, a.AngleClose, a.Question,

View File

@ -57,7 +57,7 @@ func makeParserForStateHandler(handler StateHandler) *Parser {
func makeParserForMatcher(matcher Matcher) *Parser {
return New(StateHandler(func(p *P) {
p.Expects("match")
if p.On(matcher).Accept().RouteRepeat().End() {
if p.On(matcher).Accept().RouteRep().End() {
p.EmitLiteral(MatchedItem)
}
}))

View File

@ -11,7 +11,7 @@ import (
const TestItem parsekit.ItemType = 1
var c, a = parsekit.C, parsekit.A
var c, a, m = parsekit.C, parsekit.A, parsekit.M
type MatcherTest struct {
input string

View File

@ -93,9 +93,9 @@ func (p *P) RouteTo(state StateHandler) *routeFollowupAction {
return &routeFollowupAction{chainAction: chainAction{p, true}}
}
// RouteRepeat indicates that on the next parsing cycle, the current
// RouteRep indicates that on the next parsing cycle, the current
// StateHandler must be reinvoked.
func (p *P) RouteRepeat() *chainAction {
func (p *P) RouteRep() *chainAction {
p.RouteTo(p.state)
return &chainAction{nil, true}
}
@ -126,3 +126,15 @@ func (p *P) popRoute() StateHandler {
p.routeStack = head
return tail
}
// ExpectEndOfFile can be used from a StateHandler function to indicate that
// your parser expects to be at the end of the file. This will schedule
// a parsekit-provided StateHandler which will do the actual check for this.
func (p *P) ExpectEndOfFile() {
p.RouteTo(func(p *P) {
p.Expects("end of file")
if p.On(A.EndOfFile).Stay().End() {
p.Emit(ItemEOF, "EOF")
}
})
}

View File

@ -84,9 +84,8 @@ func (p *P) EmitError(format string, args ...interface{}) {
p.Emit(ItemError, message)
}
// UnexpectedInput is used by a parser implementation to emit an
// error item that tells the client that an unexpected rune was
// encountered in the input.
// UnexpectedInput is used by a StateHandler function to emit an error item
// that tells the client that an unexpected rune was encountered in the input.
func (p *P) UnexpectedInput() {
r, _, ok := p.peek(0)
switch {

View File

@ -134,11 +134,11 @@ type routeAction struct {
chainAction
}
// RouteRepeat indicates that on the next parsing cycle,
// RouteRep indicates that on the next parsing cycle,
// the current StateHandler must be reinvoked.
func (a *routeAction) RouteRepeat() *chainAction {
func (a *routeAction) RouteRep() *chainAction {
if a.ok {
return a.p.RouteRepeat()
return a.p.RouteRep()
}
return &chainAction{nil, false}
}