diff --git a/combinator.go b/combinator.go index d7be3f9..4e7e4a5 100644 --- a/combinator.go +++ b/combinator.go @@ -26,7 +26,7 @@ func NoAutoWS(parser Parserish) Parser { parserfied := Parsify(parser) return func(ps *State, node *Result) { oldWS := ps.WS - ps.WS = func(ps *State) {} + ps.WS = NoWhitespace parserfied(ps, node) ps.WS = oldWS } @@ -145,6 +145,36 @@ 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 c1eedb3..e792c47 100644 --- a/combinator_test.go +++ b/combinator_test.go @@ -181,6 +181,20 @@ 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/state.go b/state.go index 5d35ac8..9d081cf 100644 --- a/state.go +++ b/state.go @@ -44,6 +44,10 @@ func UnicodeWhitespace(s *State) { } s.Pos += w } +} + +// NoWhitespace disables automatic whitespace matching +func NoWhitespace(s *State) { }