From c164f320cb35ad05d4942a9ef17c6f5c4d049571 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Fri, 24 May 2019 13:59:01 +0000 Subject: [PATCH] Implemented P.ExpectEndOfFile() and shortened some parser/combinator functions (since Go-people seem to like that better than a somewhat longer descriptive name) --- matcher_builtin.go | 299 +++++++++++++++++++++++----------------- matcher_builtin_test.go | 57 ++++---- parsekit.go | 2 +- parsekit_test.go | 2 +- statehandler.go | 16 ++- statehandler_emit.go | 5 +- statehandler_on.go | 6 +- 7 files changed, 226 insertions(+), 161 deletions(-) diff --git a/matcher_builtin.go b/matcher_builtin.go index 6df87c3..f2e61dc 100644 --- a/matcher_builtin.go +++ b/matcher_builtin.go @@ -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 + } +} diff --git a/matcher_builtin_test.go b/matcher_builtin_test.go index 963cf4f..4412a27 100644 --- a/matcher_builtin_test.go +++ b/matcher_builtin_test.go @@ -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, diff --git a/parsekit.go b/parsekit.go index 8990542..65f52ec 100644 --- a/parsekit.go +++ b/parsekit.go @@ -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) } })) diff --git a/parsekit_test.go b/parsekit_test.go index 0ac4586..19887e2 100644 --- a/parsekit_test.go +++ b/parsekit_test.go @@ -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 diff --git a/statehandler.go b/statehandler.go index 77fec0f..0452429 100644 --- a/statehandler.go +++ b/statehandler.go @@ -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") + } + }) +} diff --git a/statehandler_emit.go b/statehandler_emit.go index 3cd5d54..8d5ff28 100644 --- a/statehandler_emit.go +++ b/statehandler_emit.go @@ -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 { diff --git a/statehandler_on.go b/statehandler_on.go index 37841ee..ee19322 100644 --- a/statehandler_on.go +++ b/statehandler_on.go @@ -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} }