diff --git a/combinator.go b/combinator.go index 4e7e4a5..38145ee 100644 --- a/combinator.go +++ b/combinator.go @@ -145,36 +145,6 @@ func Maybe(parser Parserish) Parser { }) } -// Until will consume all input until one of the given parsers match. This is running every parser over every byte, -// so its probably going to be slow. -func Until(parsers ...Parserish) Parser { - parserfied := ParsifyAll(parsers...) - return NewParser("Until()", func(ps *State, node *Result) { - ws := ps.WS - ps.WS = NoWhitespace - defer func() { - ps.WS = ws - }() - startPos := ps.Pos - for ps.Pos < len(ps.Input) { - endPos := ps.Pos - for _, p := range parserfied { - ps.Pos = endPos - - p(ps, node) - - if !ps.Errored() { - node.Token = ps.Input[startPos:endPos] - return - } - ps.Recover() - } - ps.Pos++ - } - node.Token = ps.Input[startPos:ps.Pos] - }) -} - // Bind will set the node .Result when the given parser matches // This is useful for giving a value to keywords and constant literals // like true and false. See the json parser for an example. diff --git a/combinator_test.go b/combinator_test.go index e792c47..c1eedb3 100644 --- a/combinator_test.go +++ b/combinator_test.go @@ -181,20 +181,6 @@ func TestMap(t *testing.T) { }) } -func TestUntil(t *testing.T) { - parser := Until("world", ".") - - t.Run("success", func(t *testing.T) { - result, _ := runParser("this is the end of the world", parser) - require.Equal(t, "this is the end of the ", result.Token) - }) - - t.Run("eof", func(t *testing.T) { - result, _ := runParser("this is the end of it all", parser) - require.Equal(t, "this is the end of it all", result.Token) - }) -} - func TestBind(t *testing.T) { parser := Bind("true", true) diff --git a/parser.go b/parser.go index fecf635..a612857 100644 --- a/parser.go +++ b/parser.go @@ -200,7 +200,7 @@ func Chars(matcher string, repetition ...int) Parser { } // NotChars accepts the full range of input from Chars, but it will stop when any -// character matches. +// character matches. If you need to match until you see a sequence use Until instead func NotChars(matcher string, repetition ...int) Parser { return NewParser("!["+matcher+"]", charsImpl(matcher, true, repetition...)) } @@ -244,3 +244,26 @@ func charsImpl(matcher string, stopOn bool, repetition ...int) Parser { ps.Advance(matched) } } + +// Until will consume all input until one of the given terminator sequences is found. If you want to stop when seeing +// single characters see NotChars instead +func Until(terminators ...string) Parser { + + return NewParser("Until", func(ps *State, node *Result) { + startPos := ps.Pos + loop: + for ps.Pos < len(ps.Input) { + for _, terminator := range terminators { + if ps.Pos+len(terminator) <= len(ps.Input) && ps.Input[ps.Pos:ps.Pos+len(terminator)] == terminator { + break loop + } + } + ps.Pos++ + } + + if ps.Pos == startPos { + ps.ErrorHere("something") + } + node.Token = ps.Input[startPos:ps.Pos] + }) +} diff --git a/parser_test.go b/parser_test.go index ce5f3e7..aaed25f 100644 --- a/parser_test.go +++ b/parser_test.go @@ -214,6 +214,22 @@ func TestAutoWS(t *testing.T) { }) } +func TestUntil(t *testing.T) { + parser := Until("world", ".") + + t.Run("success", func(t *testing.T) { + result, ps := runParser("this is the end of the world", parser) + require.Equal(t, "this is the end of the ", result.Token) + require.Equal(t, "world", ps.Get()) + }) + + t.Run("eof", func(t *testing.T) { + result, ps := runParser("this is the end of it all", parser) + require.Equal(t, "this is the end of it all", result.Token) + require.Equal(t, "", ps.Get()) + }) +} + func runParser(input string, parser Parser) (Result, *State) { ps := NewState(input) result := Result{}