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:
parent
6ad4499971
commit
c164f320cb
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue