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
|
// 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
|
// When using C in your own parser, then it is advised to create
|
||||||
// a variable in your own package to reference it:
|
// 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.
|
// Doing so saves you a lot of typing, and it makes your code a lot cleaner.
|
||||||
var C = struct {
|
var C = struct {
|
||||||
Rune func(rune) Matcher
|
Rune func(rune) Matcher
|
||||||
Runes func(...rune) Matcher
|
Runes func(...rune) Matcher
|
||||||
RuneRange func(rune, rune) Matcher
|
RuneRange func(rune, rune) Matcher
|
||||||
String func(string) Matcher
|
Str func(string) Matcher
|
||||||
StringNoCase func(string) Matcher
|
StrNoCase func(string) Matcher
|
||||||
AnyOf func(...Matcher) Matcher
|
Any func(...Matcher) Matcher
|
||||||
Not func(Matcher) Matcher
|
Not func(Matcher) Matcher
|
||||||
Optional func(Matcher) Matcher
|
Opt func(Matcher) Matcher
|
||||||
Sequence func(...Matcher) Matcher
|
Seq func(...Matcher) Matcher
|
||||||
Repeat func(int, Matcher) Matcher
|
Rep func(int, Matcher) Matcher
|
||||||
Min func(int, Matcher) Matcher
|
Min func(int, Matcher) Matcher
|
||||||
Max func(int, Matcher) Matcher
|
Max func(int, Matcher) Matcher
|
||||||
ZeroOrMore func(Matcher) Matcher
|
ZeroOrMore func(Matcher) Matcher
|
||||||
OneOrMore func(Matcher) Matcher
|
OneOrMore func(Matcher) Matcher
|
||||||
MinMax func(int, int, Matcher) Matcher
|
MinMax func(int, int, Matcher) Matcher
|
||||||
Separated func(Matcher, 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: MatchRune,
|
Rune: MatchRune,
|
||||||
Runes: MatchRunes,
|
Runes: MatchRunes,
|
||||||
RuneRange: MatchRuneRange,
|
RuneRange: MatchRuneRange,
|
||||||
String: MatchString,
|
Str: MatchStr,
|
||||||
StringNoCase: MatchStringNoCase,
|
StrNoCase: MatchStrNoCase,
|
||||||
Optional: MatchOptional,
|
Opt: MatchOpt,
|
||||||
AnyOf: MatchAnyOf,
|
Any: MatchAny,
|
||||||
Not: MatchNot,
|
Not: MatchNot,
|
||||||
Sequence: MatchSequence,
|
Seq: MatchSeq,
|
||||||
Repeat: MatchRepeat,
|
Rep: MatchRep,
|
||||||
Min: MatchMin,
|
Min: MatchMin,
|
||||||
Max: MatchMax,
|
Max: MatchMax,
|
||||||
ZeroOrMore: MatchZeroOrMore,
|
ZeroOrMore: MatchZeroOrMore,
|
||||||
OneOrMore: MatchOneOrMore,
|
OneOrMore: MatchOneOrMore,
|
||||||
MinMax: MatchMinMax,
|
MinMax: MatchMinMax,
|
||||||
Separated: MatchSeparated,
|
Separated: MatchSeparated,
|
||||||
Drop: MatchDrop,
|
|
||||||
Trim: MatchTrim,
|
|
||||||
TrimLeft: MatchTrimLeft,
|
|
||||||
TrimRight: MatchTrimRight,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// A provides convenient access to a range of atoms that can be used to
|
// A provides convenient access to a range of atoms that can be used to
|
||||||
// build combinators or parsing rules.
|
// build combinators or parsing rules.
|
||||||
//
|
//
|
||||||
// In parsekit, an atom is defined as a ready to go Matcher function.
|
// 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 {
|
var A = struct {
|
||||||
EndOfFile Matcher
|
EndOfFile Matcher
|
||||||
AnyRune Matcher
|
AnyRune Matcher
|
||||||
|
@ -119,7 +118,7 @@ var A = struct {
|
||||||
Tab: C.Rune('\t'),
|
Tab: C.Rune('\t'),
|
||||||
CR: C.Rune('\r'),
|
CR: C.Rune('\r'),
|
||||||
LF: C.Rune('\n'),
|
LF: C.Rune('\n'),
|
||||||
CRLF: C.String("\r\n"),
|
CRLF: C.Str("\r\n"),
|
||||||
Excl: C.Rune('!'),
|
Excl: C.Rune('!'),
|
||||||
DoubleQuote: C.Rune('"'),
|
DoubleQuote: C.Rune('"'),
|
||||||
Hash: C.Rune('#'),
|
Hash: C.Rune('#'),
|
||||||
|
@ -152,14 +151,43 @@ var A = struct {
|
||||||
Pipe: C.Rune('|'),
|
Pipe: C.Rune('|'),
|
||||||
CurlyClose: C.Rune('}'),
|
CurlyClose: C.Rune('}'),
|
||||||
Tilde: C.Rune('~'),
|
Tilde: C.Rune('~'),
|
||||||
Whitespace: C.OneOrMore(C.AnyOf(C.Rune(' '), C.Rune('\t'))),
|
Whitespace: C.OneOrMore(C.Any(C.Rune(' '), C.Rune('\t'))),
|
||||||
WhitespaceAndNewlines: C.OneOrMore(C.AnyOf(C.Rune(' '), C.Rune('\t'), C.String("\r\n"), C.Rune('\n'))),
|
WhitespaceAndNewlines: C.OneOrMore(C.Any(C.Rune(' '), C.Rune('\t'), C.Str("\r\n"), C.Rune('\n'))),
|
||||||
EndOfLine: C.AnyOf(C.String("\r\n"), C.Rune('\n'), MatchEndOfFile()),
|
EndOfLine: C.Any(C.Str("\r\n"), C.Rune('\n'), MatchEndOfFile()),
|
||||||
Digit: C.RuneRange('0', '9'),
|
Digit: C.RuneRange('0', '9'),
|
||||||
ASCII: C.RuneRange('\x00', '\x7F'),
|
ASCII: C.RuneRange('\x00', '\x7F'),
|
||||||
ASCIILower: C.RuneRange('a', 'z'),
|
ASCIILower: C.RuneRange('a', 'z'),
|
||||||
ASCIIUpper: 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
|
// 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.
|
// input match the provided string.
|
||||||
// TODO make this a more efficient string-level match?
|
// TODO make this a more efficient string-level match?
|
||||||
func MatchString(expected string) Matcher {
|
func MatchStr(expected string) Matcher {
|
||||||
var matchers = []Matcher{}
|
var matchers = []Matcher{}
|
||||||
for _, r := range expected {
|
for _, r := range expected {
|
||||||
matchers = append(matchers, MatchRune(r))
|
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.
|
// on the input match the provided string in a case-insensitive manner.
|
||||||
// TODO make this a more efficient string-level match?
|
// TODO make this a more efficient string-level match?
|
||||||
func MatchStringNoCase(expected string) Matcher {
|
func MatchStrNoCase(expected string) Matcher {
|
||||||
var matchers = []Matcher{}
|
var matchers = []Matcher{}
|
||||||
for _, r := range expected {
|
for _, r := range expected {
|
||||||
u := unicode.ToUpper(r)
|
u := unicode.ToUpper(r)
|
||||||
l := unicode.ToLower(r)
|
l := unicode.ToLower(r)
|
||||||
matchers = append(matchers, MatchRunes(u, l))
|
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
|
// When the provided Matcher applies, then its output is used, otherwise
|
||||||
// no output is generated but still a successful match is reported.
|
// 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 {
|
return func(m *MatchDialog) bool {
|
||||||
child := m.Fork()
|
child := m.Fork()
|
||||||
if matcher(child) {
|
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
|
// applied in their exact order. Only if all matcher apply, the sequence
|
||||||
// reports successful match.
|
// reports successful match.
|
||||||
func MatchSequence(matchers ...Matcher) Matcher {
|
func MatchSeq(matchers ...Matcher) Matcher {
|
||||||
return func(m *MatchDialog) bool {
|
return func(m *MatchDialog) bool {
|
||||||
child := m.Fork()
|
child := m.Fork()
|
||||||
for _, matcher := range matchers {
|
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
|
// can be applied. They are applied in their provided order. The first Matcher
|
||||||
// that applies is used for reporting back a match.
|
// that applies is used for reporting back a match.
|
||||||
func MatchAnyOf(matchers ...Matcher) Matcher {
|
func MatchAny(matchers ...Matcher) Matcher {
|
||||||
return func(m *MatchDialog) bool {
|
return func(m *MatchDialog) bool {
|
||||||
for _, matcher := range matchers {
|
for _, matcher := range matchers {
|
||||||
child := m.Fork()
|
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.
|
// applied exactly the provided amount of times.
|
||||||
//
|
//
|
||||||
// Note that the input can contain more Matches for the provided matcher, e.g.:
|
// 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".
|
// 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.
|
// 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)
|
return matchMinMax(times, times, matcher)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -357,7 +385,7 @@ func matchMinMax(min int, max int, matcher Matcher) Matcher {
|
||||||
return func(m *MatchDialog) bool {
|
return func(m *MatchDialog) bool {
|
||||||
child := m.Fork()
|
child := m.Fork()
|
||||||
if max >= 0 && min > max {
|
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
|
total := 0
|
||||||
// Check for the minimum required amount of matches.
|
// 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
|
// (the separator). All matches (separated + separator) are included in the
|
||||||
// output.
|
// output.
|
||||||
func MatchSeparated(separated Matcher, separator Matcher) Matcher {
|
func MatchSeparated(separated Matcher, separator Matcher) Matcher {
|
||||||
return MatchSequence(separated, MatchZeroOrMore(MatchSequence(separator, separated)))
|
return MatchSeq(separated, MatchZeroOrMore(MatchSeq(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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MatchEndOfFile creates a Matcher that checks if the end of the input data
|
// MatchEndOfFile creates a Matcher that checks if the end of the input data
|
||||||
|
@ -475,3 +441,86 @@ func MatchAnyRune() Matcher {
|
||||||
return false
|
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"},
|
{"dd", c.RuneRange('b', 'e'), true, "d"},
|
||||||
{"ee", c.RuneRange('b', 'e'), true, "e"},
|
{"ee", c.RuneRange('b', 'e'), true, "e"},
|
||||||
{"ff", c.RuneRange('b', 'e'), false, ""},
|
{"ff", c.RuneRange('b', 'e'), false, ""},
|
||||||
{"Hello, world!", c.String("Hello"), true, "Hello"},
|
{"Hello, world!", c.Str("Hello"), true, "Hello"},
|
||||||
{"HellÖ, world!", c.StringNoCase("hellö"), true, "HellÖ"},
|
{"HellÖ, world!", c.StrNoCase("hellö"), true, "HellÖ"},
|
||||||
{"+X", c.Runes('+', '-', '*', '/'), true, "+"},
|
{"+X", c.Runes('+', '-', '*', '/'), true, "+"},
|
||||||
{"-X", c.Runes('+', '-', '*', '/'), true, "-"},
|
{"-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"},
|
{"abc", c.Not(c.Rune('b')), true, "a"},
|
||||||
{"bcd", c.Not(c.Rune('b')), false, ""},
|
{"bcd", c.Not(c.Rune('b')), false, ""},
|
||||||
{"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"},
|
{"1010", c.Not(c.Seq(c.Rune('2'), c.Rune('0'))), true, "1"},
|
||||||
{"2020", c.Not(c.Sequence(c.Rune('2'), c.Rune('0'))), false, ""},
|
{"2020", c.Not(c.Seq(c.Rune('2'), c.Rune('0'))), false, ""},
|
||||||
{"abc", c.AnyOf(c.Rune('a'), c.Rune('b')), true, "a"},
|
{"abc", c.Any(c.Rune('a'), c.Rune('b')), true, "a"},
|
||||||
{"bcd", c.AnyOf(c.Rune('a'), c.Rune('b')), true, "b"},
|
{"bcd", c.Any(c.Rune('a'), c.Rune('b')), true, "b"},
|
||||||
{"cde", c.AnyOf(c.Rune('a'), c.Rune('b')), false, ""},
|
{"cde", c.Any(c.Rune('a'), c.Rune('b')), false, ""},
|
||||||
{"ababc", c.Repeat(4, c.Runes('a', 'b')), true, "abab"},
|
{"ababc", c.Rep(4, c.Runes('a', 'b')), true, "abab"},
|
||||||
{"ababc", c.Repeat(5, c.Runes('a', 'b')), false, ""},
|
{"ababc", c.Rep(5, c.Runes('a', 'b')), false, ""},
|
||||||
{"", c.Min(0, c.Rune('a')), true, ""},
|
{"", c.Min(0, c.Rune('a')), true, ""},
|
||||||
{"a", c.Min(0, c.Rune('a')), true, "a"},
|
{"a", c.Min(0, c.Rune('a')), true, "a"},
|
||||||
{"aaaaa", c.Min(4, c.Rune('a')), true, "aaaaa"},
|
{"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, ""},
|
{"X", c.ZeroOrMore(c.Rune('e')), true, ""},
|
||||||
{"eX", c.ZeroOrMore(c.Rune('e')), true, "e"},
|
{"eX", c.ZeroOrMore(c.Rune('e')), true, "e"},
|
||||||
{"eeeeeX", c.ZeroOrMore(c.Rune('e')), true, "eeeee"},
|
{"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!"},
|
{"Hello, world!X", c.Seq(c.Str("Hello"), a.Comma, a.Space, c.Str("world"), a.Excl), true, "Hello, world!"},
|
||||||
{"101010123", c.OneOrMore(c.Sequence(c.Rune('1'), c.Rune('0'))), true, "101010"},
|
{"101010123", c.OneOrMore(c.Seq(c.Rune('1'), c.Rune('0'))), true, "101010"},
|
||||||
{"", c.Optional(c.OneOrMore(c.Rune('f'))), true, ""},
|
{"", c.Opt(c.OneOrMore(c.Rune('f'))), true, ""},
|
||||||
{"ghijkl", c.Optional(c.Rune('h')), true, ""},
|
{"ghijkl", c.Opt(c.Rune('h')), true, ""},
|
||||||
{"ghijkl", c.Optional(c.Rune('g')), true, "g"},
|
{"ghijkl", c.Opt(c.Rune('g')), true, "g"},
|
||||||
{"fffffX", c.Optional(c.OneOrMore(c.Rune('f'))), true, "fffff"},
|
{"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"},
|
{"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.Seq(a.Backslash, c.Rune('x'), c.Rep(2, a.HexDigit))), true, `\x9a\x01\xF0\xfC`},
|
||||||
{`\x9a\x01\xF0\xfCAndSomeMoreStuff`, c.OneOrMore(c.Sequence(a.Backslash, c.Rune('x'), c.Repeat(2, a.HexDigit))), true, `\x9a\x01\xF0\xfC`},
|
{" ", m.Trim(c.OneOrMore(a.AnyRune), " "), true, ""},
|
||||||
{" ", c.Trim(c.OneOrMore(a.AnyRune), " "), true, ""},
|
{" ", m.TrimLeft(c.OneOrMore(a.AnyRune), " "), true, ""},
|
||||||
{" ", c.TrimLeft(c.OneOrMore(a.AnyRune), " "), true, ""},
|
{" ", m.TrimRight(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 "},
|
func TestModifiers(t *testing.T) {
|
||||||
{" trim ", c.TrimRight(c.OneOrMore(a.AnyRune), " "), true, " trim"},
|
RunMatcherTests(t, []MatcherTest{
|
||||||
{" \t trim \t ", c.TrimRight(c.OneOrMore(a.AnyRune), " \t"), true, " \t trim"},
|
{" 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) {
|
func TestSequenceOfRunes(t *testing.T) {
|
||||||
sequence := c.Sequence(
|
sequence := c.Seq(
|
||||||
a.Hash, a.Dollar, a.Percent, a.Amp, a.SingleQuote, a.RoundOpen,
|
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.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,
|
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 {
|
func makeParserForMatcher(matcher Matcher) *Parser {
|
||||||
return New(StateHandler(func(p *P) {
|
return New(StateHandler(func(p *P) {
|
||||||
p.Expects("match")
|
p.Expects("match")
|
||||||
if p.On(matcher).Accept().RouteRepeat().End() {
|
if p.On(matcher).Accept().RouteRep().End() {
|
||||||
p.EmitLiteral(MatchedItem)
|
p.EmitLiteral(MatchedItem)
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
|
@ -11,7 +11,7 @@ import (
|
||||||
|
|
||||||
const TestItem parsekit.ItemType = 1
|
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 {
|
type MatcherTest struct {
|
||||||
input string
|
input string
|
||||||
|
|
|
@ -93,9 +93,9 @@ func (p *P) RouteTo(state StateHandler) *routeFollowupAction {
|
||||||
return &routeFollowupAction{chainAction: chainAction{p, true}}
|
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.
|
// StateHandler must be reinvoked.
|
||||||
func (p *P) RouteRepeat() *chainAction {
|
func (p *P) RouteRep() *chainAction {
|
||||||
p.RouteTo(p.state)
|
p.RouteTo(p.state)
|
||||||
return &chainAction{nil, true}
|
return &chainAction{nil, true}
|
||||||
}
|
}
|
||||||
|
@ -126,3 +126,15 @@ func (p *P) popRoute() StateHandler {
|
||||||
p.routeStack = head
|
p.routeStack = head
|
||||||
return tail
|
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)
|
p.Emit(ItemError, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnexpectedInput is used by a parser implementation to emit an
|
// UnexpectedInput is used by a StateHandler function to emit an error item
|
||||||
// error item that tells the client that an unexpected rune was
|
// that tells the client that an unexpected rune was encountered in the input.
|
||||||
// encountered in the input.
|
|
||||||
func (p *P) UnexpectedInput() {
|
func (p *P) UnexpectedInput() {
|
||||||
r, _, ok := p.peek(0)
|
r, _, ok := p.peek(0)
|
||||||
switch {
|
switch {
|
||||||
|
|
|
@ -134,11 +134,11 @@ type routeAction struct {
|
||||||
chainAction
|
chainAction
|
||||||
}
|
}
|
||||||
|
|
||||||
// RouteRepeat indicates that on the next parsing cycle,
|
// RouteRep indicates that on the next parsing cycle,
|
||||||
// the current StateHandler must be reinvoked.
|
// the current StateHandler must be reinvoked.
|
||||||
func (a *routeAction) RouteRepeat() *chainAction {
|
func (a *routeAction) RouteRep() *chainAction {
|
||||||
if a.ok {
|
if a.ok {
|
||||||
return a.p.RouteRepeat()
|
return a.p.RouteRep()
|
||||||
}
|
}
|
||||||
return &chainAction{nil, false}
|
return &chainAction{nil, false}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue