gigaparsec-0.2.2.0: src/Text/Gigaparsec/Combinator.hs
{-# LANGUAGE Safe #-}
{-|
Module : Text.Gigaparsec.Combinator
Description : This module contains a huge number of pre-made combinators that are
very useful for a variety of purposes.
License : BSD-3-Clause
Maintainer : Jamie Willis, Gigaparsec Maintainers
Stability : stable
This module contains a huge number of pre-made combinators that are very useful for a variety of purposes.
In particular, it contains combinators for: performing a parser iteratively, collecting all the results;
querying whether or not any input is left; optionally performing parsers; parsing delimited constructions;
handling multiple possible alternatives or parsers to sequence; handling more complex conditional execution;
and more.
@since 0.1.0.0
-}
module Text.Gigaparsec.Combinator (
-- * Iterative Combinators
-- | These combinators all execute a given parser an unbounded number of times, until either it fails, or another
-- parser succeeds, depending on the combinator. Depending on the combinator, all of the results produced by the
-- repeated execution of the parser may be returned in a @[]@. These are almost essential for any practical parsing
-- task.
manyN, skipMany, skipSome, skipManyN, count, count1, manyTill, someTill, skipManyTill, skipSomeTill,
-- * Optional Parsing Combinators
-- | These combinators allow for the /possible/ parsing of some parser. If the parser succeeds, that is ok
-- so long as it __did not consume input__. Be aware that the result of the success may be replaced with
-- these combinators, with the exception of "option", which still preserves the result.
option, optional, optionalAs, decide, fromMaybeS,
-- * Separated Values Combinators
-- | These combinators are concerned with delimited parsing, where one parser is repeated but delimited by another one.
-- In each of these cases @p@ is the parser of interest and @sep@ is the delimeter. These combinators mainly differ
-- in either the number of @p@s they require, or exactly where the delimeters are allowed (only between, always
-- trailing, or either). In all cases, they return the list of results generated by the repeated parses of @p@.
sepBy, sepBy1, sepEndBy, sepEndBy1, endBy, endBy1,
-- * Multiple Branching/Sequencing Combinators
-- | These combinators allow for testing or sequencing a large number of parsers in one go.
choice, sequence, traverse, skip,
-- * Range Combinators
-- | These combinators allow for the parsing of a specific parser either a specific number of times, or between a certain
-- amount of times.
exactly, range, range_, countRange,
-- * Selective Combinators
-- | These combinators allow for the conditional extraction of a result, or the execution of a parser
-- based on another. They are derived from "branch".
ifS, whenS, guardS, whileS,
) where
import Text.Gigaparsec (Parsec, many, some, (<|>), ($>), (<:>), select,
branch, empty, unit, manyl, somel, notFollowedBy, liftA2, void)
import Data.Foldable (asum, sequenceA_)
{-|
This combinator tries to parse each of the parsers @ps@ in order, until one of them succeeds.
Finds the first parser in @ps@ which succeeds, returning its result. If Nothing of the parsers
succeed, then this combinator fails. If a parser fails having consumed input, this combinator
fails __immediately__.
==== __Examples__
>>> let p = choice [string "abc", string "ab", string "bc", string "d"]
>>> parse @String p "abc"
Success "abc"
>>> parse @String p "ab"
Failure ..
>>> parse @String p "bc"
Success "bc"
>>> parse @String p "x"
Failure ..
@since 0.1.0.0
-}
choice :: [Parsec a] -- ^ the parsers, @ps@ to try, in order.
-> Parsec a -- ^ a parser that tries to parse one of @ps@.
choice = asum
{-|
This combinator will parse each of @ps@ in order, discarding the results.
Given the parsers @ps@, consisting of @p1@ through @pn@, parses
each in order. If they all succeed, this combinator succeeds. If any of
the parsers fail, then the whole combinator fails.
==== __Examples__
>>> let p = skip [char'a', item, char 'c']
>>> parse @String p "abc"
Success ()
>>> parse @String p "ab"
Failure ..
@since 0.1.0.0
-}
skip :: [Parsec a] -- ^ parsers @ps@ to be sequenced.
-> Parsec () -- ^ a parser that parses each of @ps@, returning @()@.
skip = sequenceA_
{-|
This combinator tries to parse @p@, wrapping its result in a @Just@ if it succeeds, or returns @Nothing@ if it fails.
Tries to parse @p@. If @p@ succeeded, producing @x@, then @Just x@ is returned. Otherwise, if @p@ failed
__without consuming input__, then @Nothing@ is returned instead.
==== __Examples__
>>> let p = option (string "abc")
>>> parse @String p ""
Success Nothing
>>> parse @String p "abc"
Success (Just "abc")
>>> parse @String p "ab"
Failure ..
@since 0.1.0.0
-}
option :: Parsec a -- ^ the parser @p@ to try to parse
-> Parsec (Maybe a)
option p = Just <$> p <|> pure Nothing
{-|
This combinator will parse @p@ if possible, otherwise will do nothing.
Tries to parse @p@. If @p@ succeeds, or fails __without consuming input__ then this combinator is successful. Otherwise, if @p@ failed
having consumed input, this combinator fails.
==== __Examples__
>>> let p = optional (string "abc")
>>> parse @String p ""
Success ()
>>> parse @String p "abc"
Success ()
>>> parse @String p "ab"
Failure ..
@since 0.1.0.0
-}
optional :: Parsec a -- ^ the parser @p@ to try to parse.
-> Parsec ()
optional = optionalAs ()
{-|
This combinator will parse @p@ if possible, otherwise will do nothing.
Tries to parse @p@. If @p@ succeeds, or fails __without consuming input__ then this combinator is successful and returns @x@. Otherwise,
if @p@ failed having consumed input, this combinator fails.
==== __Examples__
>>> let p = optionalAs 7 (string "abc")
>>> parse @String p ""
Success 7
>>> parse @String p "abc"
Success 7
>>> parse @String p "ab"
Failure ..
@since 0.1.0.0
-}
optionalAs :: b -- ^ the value @x@ to return regardless of how @p@ performs.
-> Parsec a -- ^ the parser @p@ to try to parse.
-> Parsec b -- ^ a parser that tries to parse @p@, returning @x@ regardless of success or failure.
optionalAs x p = p $> x <|> pure x
-- TODO: collect
{-|
This combinator can eliminate an @Maybe@ from the result of the parser @p@.
First parse @p@, if it succeeds returning @Just x@, then return @x@. However,
if @p@ fails, or returned @Nothing@, then this combinator fails.
==== __Examples__
@decide (option p) = p@
-}
decide :: Parsec (Maybe a) -- ^ the parser @p@ to parse and extract the result from.
-> Parsec a -- ^ a parser that tries to extract the result from @p@.
decide p = p >>= maybe empty pure
-- this is decide overload
{-|
This combinator parses @q@ depending only if @p@ returns a @Nothing@.
First parses @p@. If @p@ returned @Just x@, then @x@ is returned.
Otherwise, if @p@ returned @Nothing@ then @q@ is parsed, producing @y@,
and @y@ is returned. If @p@ or @q@ fails, the combinator fails.
==== __Examples__
fromMaybe q (option p) = p <|> q
@since 0.1.0.0
-}
fromMaybeS :: Parsec a -- ^ a parser to execute when @p@ returns @Nothing@, to provide a value of type @a@.
-> Parsec (Maybe a) -- ^ the first parser @p@, which returns an @Maybe@ to eliminate.
-> Parsec a -- ^ a parser that either just parses @p@ or both @p@ and @q@ in order to return an @a@.
fromMaybeS q p = select (maybe (Left ()) Right <$> p) (const <$> q)
{-|
This combinator repeatedly parses a given parser __@n@__ or more times, collecting the results into a list.
Parses a given parser, @p@, repeatedly until it fails. If @p@ failed having consumed input,
this combinator fails. Otherwise when @p@ fails __without consuming input__, this combinator
will return all of the results, @x1@ through @xm@ (with @m >= n@), in a list: @[x1, .., xm]@.
If @p@ was not successful at least @n@ times, this combinator fails.
==== __Examples__
>>> let p = manyN 2 (string "ab")
>>> parse @String p ""
Failure ..
>>> parse @String p "ab"
Failure ..
>>> parse @String p "abababab"
Success ["ab", "ab", "ab", "ab"]
>>> parse @String p "aba"
Failure ..
==== Notes
* @many p == many 0 p@ and @some p == many 1 p@.
@since 0.1.0.0
-}
manyN :: Int -- ^ the minimum number of @p@s required, @n@.
-> Parsec a -- ^ the parser @p@ to execute multiple times.
-> Parsec [a] -- ^ a parser that parses @p@ until it fails, returning the list of all the successful results.
manyN 0 p = many p
manyN 1 p = some p
manyN n p = p <:> manyN (n - 1) p
{-|
This combinator repeatedly parses a given parser __zero__ or more times, ignoring the results.
Parses a given parser, @p@, repeatedly until it fails. If @p@ failed having consumed input,
this combinator fails. Otherwise when @p@ fails __without consuming input__, this combinator
will succeed.
==== __Examples__
>>> let p = skipMany (string "ab")
>>> parse @String p ""
Success ()
>>> parse @String p "ab"
Success ()
>>> parse @String p "abababab"
Success ()
>>> parse @String p "aba"
Failure ..
@since 0.1.0.0
-}
skipMany :: Parsec a -- ^ the parser @p@ to execute multiple times.
-> Parsec () -- ^ a parser that parses @p@ until it fails, returning unit.
skipMany p = let go = p *> go <|> unit in go
{-|
This combinator repeatedly parses a given parser __one__ or more times, ignoring the results.
Parses a given parser, @p@, repeatedly until it fails. If @p@ failed having consumed input,
this combinator fails. Otherwise when @p@ fails __without consuming input__, this combinator
will succeed. The parser @p@ must succeed at least once.
==== __Examples__
>>> let p = skipSome (string "ab")
>>> parse @String p ""
Failure ..
>>> parse @String p "ab"
Success ()
>>> parse @String p "abababab"
Success ()
>>> parse @String p "aba"
Failure ..
@since 0.1.0.0
-}
skipSome :: Parsec a -- ^ @p@, the parser to execute multiple times.
-> Parsec () -- ^ a parser that parses @p@ until it fails, returning unit.
skipSome p = p *> skipMany p
{-|
This combinator repeatedly parses a given parser __@n@__ or more times, ignoring the results.
Parses a given parser, @p@, repeatedly until it fails. If @p@ failed having consumed input,
this combinator fails. Otherwise when @p@ fails __without consuming input__, this combinator
will succeed. The parser @p@ must succeed at least @n@ times.
==== __Examples__
>>> let p = skipManyN 2 (string "ab")
>>> parse @String p ""
Failure ..
>>> parse @String p "ab"
Failure ..
>>> parse @String p "abababab"
Success ()
>>> parse @String p "aba"
Failure ..
@since 0.1.0.0
-}
skipManyN :: Int -- ^ @n@, the minimum number of times to execute.
-> Parsec a -- ^ @p@, the parser to execute multiple times.
-> Parsec () -- ^ a parser that parses @p@ until it fails, returning unit.
skipManyN 0 p = skipMany p
skipManyN 1 p = skipSome p
skipManyN n p = p *> skipManyN (n - 1) p
{-|
This combinator repeatedly parses a given parser __zero__ or more times, returning how many times it succeeded.
Parses a given parser, @p@, repeatedly until it fails. If @p@ failed having consumed input,
this combinator fails. Otherwise when @p@ fails __without consuming input__, this combinator
will succeed. The number of times @p@ succeeded is returned as the result.
==== __Examples__
>>> let p = count (string "ab")
>>> parse @String p ""
Success 0
>>> parse @String p "ab"
Success 1
>>> parse @String p "abababab"
Success 4
>>> parse @String p "aba"
Failure ..
@since 0.1.0.0
-}
count :: Parsec a -- ^ @p@, the parser to execute multiple times.
-> Parsec Int -- ^ the number of times @p@ successfully parses
count = manyl (flip (const (+ 1))) 0
{-|
This combinator repeatedly parses a given parser __one__ or more times, returning how many times it succeeded.
Parses a given parser, @p@, repeatedly until it fails. If @p@ failed having consumed input,
this combinator fails. Otherwise when @p@ fails __without consuming input__, this combinator
will succeed. The parser @p@ must succeed at least once. The number of times @p@ succeeded is returned as the result.
==== __Examples__
>>> let p = count1 (string "ab")
>>> parse @String p ""
Failure ..
>>> parse @String p "ab"
Success 1
>>> parse @String p "abababab"
Success 4
>>> parse @String p "aba"
Failure ..
@since 0.1.0.0
-}
count1 :: Parsec a -- ^ @p@, the parser to execute multiple times.
-> Parsec Int -- ^ the number of times @p@ successfully parses
count1 = somel (flip (const (+ 1))) 0
{-|
This combinator parses __zero__ or more occurrences of @p@, separated by @sep@.
Behaves just like @sepBy1@, except does not require an initial @p@, returning the empty list instead.
==== __Examples__
>>> ...
>>> let args = sepBy int (string ", ")
>>> parse @String args "7, 3, 2"
Success [7, 3, 2]
>>> parse @String args ""
Success []
>>> parse @String args "1"
Success [1]
>>> parse @String args "1, 2, "
Failure ..
@since 0.1.0.0
-}
sepBy :: Parsec a -- ^ @p@, the parser whose results are collected into a list.
-> Parsec sep -- ^ @sep@, the delimiter that must be parsed between every @p@.
-> Parsec [a] -- ^ a parser that parses @p@ delimited by @sep@, returning the list of @p@'s results.
sepBy p sep = sepBy1 p sep <|> pure []
{-|
This combinator parses __one__ or more occurrences of @p@, separated by @sep@.
First parses a @p@. Then parses @sep@ followed by @p@ until there are no more @sep@s.
The results of the @p@'s, @x1@ through @xn@, are returned as @[x1, .., xn]@.
If @p@ or @sep@ fails having consumed input, the whole parser fails. Requires at least
one @p@ to have been parsed.
==== __Examples__
>>> ...
>>> let args = sepBy1 int (string ", ")
>>> parse @String args "7, 3, 2"
Success [7, 3, 2]
>>> parse @String args ""
Failure ..
>>> parse @String args "1"
Success [1]
>>> parse @String args "1, 2, "
Failure ..
@since 0.1.0.0
-}
sepBy1 :: Parsec a -- ^ @p@, the parser whose results are collected into a list.
-> Parsec sep -- ^ @sep@, the delimiter that must be parsed between every @p@.
-> Parsec [a] -- ^ a parser that parses @p@ delimited by @sep@, returning the list of @p@'s results.
sepBy1 p sep = p <:> many (sep *> p)
{-|
This combinator parses __zero__ or more occurrences of @p@, separated and optionally ended by @sep@.
Behaves just like @sepEndBy1@, except does not require an initial @p@, returning the empty list instead.
==== __Examples__
>>> ...
>>> let args = sepEndBy int (string ";\n")
>>> parse @String args "7;\n3;\n2"
Success [7, 3, 2]
>>> parse @String args ""
Success Nil
>>> parse @String args "1"
Success [1]
>>> parse @String args "1;\n2;\n"
Success [1, 2]
@since 0.1.0.0
-}
sepEndBy :: Parsec a -- ^ @p@, the parser whose results are collected into a list.
-> Parsec sep -- ^ @sep@, the delimiter that must be parsed between every @p@.
-> Parsec [a] -- ^ a parser that parses @p@ delimited by @sep@, returning the list of @p@'s results.
sepEndBy p sep = sepEndBy1 p sep <|> pure []
{-|
This combinator parses __one__ or more occurrences of @p@, separated and optionally ended by @sep@.
First parses a @p@. Then parses @sep@ followed by @p@ until there are no more: if a final @sep@ exists, this is parsed.
The results of the @p@'s, @x1@ through @xn@, are returned as @[x1, .., xn]@.
If @p@ or @sep@ fails having consumed input, the whole parser fails. Requires at least
one @p@ to have been parsed.
==== __Examples__
>>> ...
>>> let args = sepEndBy1 int (string ";\n")
>>> parse @String args "7;\n3;\n2"
Success [7, 3, 2]
>>> parse @String args ""
Failure ..
>>> parse @String args "1"
Success [1]
>>> parse @String args "1;\n2;\n"
Success [1, 2]
@since 0.1.0.0
-}
sepEndBy1 :: Parsec a -- ^ @p@, the parser whose results are collected into a list.
-> Parsec sep -- ^ @sep@, the delimiter that must be parsed between every @p@.
-> Parsec [a] -- ^ a parser that parses @p@ delimited by @sep@, returning the list of @p@'s results.
sepEndBy1 p sep = let seb1 = p <:> (sep *> (seb1 <|> pure []) <|> pure []) in seb1
{-|
This combinator parses __zero__ or more occurrences of @p@, separated and ended by @sep@.
Behaves just like @endBy1@, except does not require an initial @p@ and @sep@, returning the empty list instead.
==== __Examples__
>>> ...
>>> let args = endBy int (string ";\n")
>>> parse @String args "7;\n3;\n2"
Failure ..
>>> parse @String args ""
Success Nil
>>> parse @String args "1;\n"
Success [1]
>>> parse @String args "1;\n2;\n"
Success [1, 2]
@since 0.1.0.0
-}
endBy :: Parsec a -- ^ @p@, the parser whose results are collected into a list.
-> Parsec sep -- ^ @sep@, the delimiter that must be parsed between every @p@.
-> Parsec [a] -- ^ a parser that parses @p@ delimited by @sep@, returning the list of @p@'s results.
endBy p sep = endBy1 p sep <|> pure []
{-|
This combinator parses __one__ or more occurrences of @p@, separated and ended by @sep@.
Parses @p@ followed by @sep@ one or more times.
The results of the @p@'s, @x1@ through @xn@, are returned as @[x1, .., xn]@.
If @p@ or @sep@ fails having consumed input, the whole parser fails.
==== __Examples__
>>> ...
>>> let args = endBy1 int (string ";\n")
>>> parse @String args "7;\n3;\n2"
Failure ..
>>> parse @String args ""
Failure ..
>>> parse @String args "1;\n"
Success [1]
>>> parse @String args "1;\n2;\n"
Success [1, 2]
@since 0.1.0.0
-}
endBy1 :: Parsec a -- ^ @p@, the parser whose results are collected into a list.
-> Parsec sep -- ^ @sep@, the delimiter that must be parsed between every @p@.
-> Parsec [a] -- ^ a parser that parses @p@ delimited by @sep@, returning the list of @p@'s results.
endBy1 p sep = some (p <* sep)
{-|
This combinator repeatedly parses a given parser __zero__ or more times, until the @end@ parser succeeds, collecting the results into a list.
First tries to parse @end@, if it fails __without consuming input__, then parses @p@, which must succeed. This repeats until @end@ succeeds.
When @end@ does succeed, this combinator will return all of the results generated by @p@, @x1@ through @xn@ (with @n >= 0@), in a
list: @[x1, .., xn]@. If @end@ could be parsed immediately, the empty list is returned.
==== __Examples__
This can be useful for scanning comments:
>>> let comment = string "--" *> manyUntil item endOfLine
>>> parse @String p "--hello world"
Failure ..
>>> parse @String p "--hello world\n"
Success ['h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd']
>>> parse @String p "--\n"
Success Nil
@since 0.1.0.0
-}
manyTill :: Parsec a -- ^ @p@, the parser to execute multiple times.
-> Parsec end -- ^ @end@, the parser that stops the parsing of @p@.
-> Parsec [a] -- ^ a parser that parses @p@ until @end@ succeeds, returning the list of all the successful results.
manyTill p end = let go = end $> [] <|> p <:> go in go
{-|
This combinator repeatedly parses a given parser __one__ or more times, until the @end@ parser succeeds, collecting the results into a list.
First ensures that trying to parse @end@ fails, then tries to parse @p@. If it succeed then it will repeatedly: try to parse @end@, if it fails
__without consuming input__, then parses @p@, which must succeed. When @end@ does succeed, this combinator will return all of the results
generated by @p@, @x1@ through @xn@ (with @n >= 1@), in a list: @[x1, .., xn]@. The parser @p@ must succeed at least once
before @end@ succeeds.
==== __Examples__
This can be useful for scanning comments:
>>> let comment = string "--" *> someUntil item endOfLine
>>> parse @String p "--hello world"
Failure ..
>>> parse @String p "--hello world\n"
Success ['h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd']
>>> parse @String p "--\n"
Failure ..
>>> parse @String p "--a\n"
Success ['a']
@since 0.1.0.0
-}
someTill :: Parsec a -- ^ @p@, the parser to execute multiple times.
-> Parsec end -- ^ @end@, the parser that stops the parsing of @p@.
-> Parsec [a] -- ^ a parser that parses @p@ until @end@ succeeds, returning the list of all the successful results.
someTill p end = notFollowedBy end *> (p <:> manyTill p end)
skipManyTill :: Parsec a -> Parsec end -> Parsec ()
skipManyTill p end = void (manyTill p end)
skipSomeTill :: Parsec a -> Parsec end -> Parsec ()
skipSomeTill p end = void (someTill p end)
-- this is ifP
{-|
This combinator parses one of @thenP@ or @elseP@ depending on the result of parsing @condP@.
This is a lifted @if@-statement. First, parse @condP@: if it is successful and returns
@True@, then parse @thenP@; else, if it returned @False@, parse @elseP@; or, if @condP@ failed
then fail. If either of @thenP@ or @elseP@ fail, then this combinator also fails.
Most useful in conjunction with /Registers/, as this allows for decisions to be made
based on state.
==== __Examples__
>>> ifP (pure True) p _ == p
>>> ifP (pure False) _ p == p
@since 0.1.0.0
-}
ifS :: Parsec Bool -- ^ @condP@, the parser that yields the condition value.
-> Parsec a -- ^ @thenP@, the parser to execute if the condition is @True@.
-> Parsec a -- ^ @elseP@, the parser to execute if the condition is @False@.
-> Parsec a -- ^ a parser that conditionally parses @thenP@ or @elseP@ after @condP@.
ifS cond t e = branch (bool <$> cond) (const <$> e) (const <$> t)
where bool True = Right ()
bool False = Left ()
-- this is when
{-|
This combinator conditionally parses @thenP@ depending on the result of parsing @condP@.
This is a lifted @if@-statement. First, parse @condP@: if it is successful and returns
@True@, then parse @thenP@; else, if it returned @False@ do nothing; or, if @condP@ failed
then fail. If @thenP@ fails, then this combinator also fails.
Most useful in conjunction with /Registers/, as this allows for decisions to be made
based on state.
==== __Examples__
>>> when (pure True) p == p
>>> when (pure False) _ == unit
@since 0.1.0.0
-}
whenS :: Parsec Bool -- ^ @condP@, the parser that yields the condition value.
-> Parsec () -- ^ @thenP@, the parser to execute if the condition is @True@.
-> Parsec () -- ^ a parser that conditionally parses @thenP@ after @condP@.
whenS cond p = ifS cond p unit
-- this is guard
{-|
This combinator verfies that the given parser returns @True@, or else fails.
First, parse @p@; if it succeeds then, so long at returns @True@, this @guard p@ succeeds. Otherwise,
if @p@ either fails, or returns @False@, @guard p@ will fail.
==== __Examples__
>>> guard (pure True) == unit
>>> guard (pure False) == empty
>>> when (not <$> p) empty == guard p
@since 0.1.0.0
-}
guardS :: Parsec Bool -- ^ @p@, the parser that yields the condition value.
-> Parsec ()
guardS cond = ifS cond unit empty
-- this is whileP
{-|
This combinator repeatedly parses @p@ so long as it returns @True@.
This is a lifted @while@-loop. First, parse @p@: if it is successful and
returns @True@, then repeat; else if it returned @False@ stop; or, if it
failed then this combinator fails.
Most useful in conjunction with /Registers/, as this allows for decisions to be made
based on state. In particular, this can be used to define the @forP@ combinator.
@since 0.1.0.0
-}
whileS :: Parsec Bool -- ^ @p@, the parser to repeatedly parse.
-> Parsec () -- ^ a parser that continues to parse @p@ until it returns @False@.
whileS c = let go = whenS c go in go
{-|
This combinator parses exactly @n@ occurrences of @p@, returning these @n@ results in a list.
Parses @p@ repeatedly up to @n@ times. If @p@ fails before @n@ is reached, then this combinator
fails. It is not required for @p@ to fail after the @n@th parse. The results produced by
@p@, @x1@ through @xn@, are returned as @[x1, .., xn]@.
==== __Examples__
>>> let p = exactly 3 item
>>> parse @String p "ab"
Failure ..
>>> parse @String p "abc"
Success ['a', 'b', 'c']
>>> parse @String p "abcd"
Success ['a', 'b', 'c']
@since 0.1.0.0
-}
exactly :: Int -- ^ @n@, the number of times to repeat @p@.
-> Parsec a -- ^ @p@, the parser to repeat.
-> Parsec [a] -- ^ a parser that parses @p@ exactly @n@ times, returning a list of the results.
exactly n = range n n
{-|
This combinator parses between @min@ and @max@ occurrences of @p@, returning these @n@ results in a list.
Parses @p@ repeatedly a minimum of @min@ times and up to @max@ times both inclusive. If @p@ fails before
@min@ is reached, then this combinator fails. It is not required for @p@ to fail after the @max@^th^ parse.
The results produced by @p@, @xmin@ through @xmax@, are returned as @[xmin, .., xmax]@.
==== __Examples__
>>> let p = range 3 5 item
>>> parse @String p "ab"
Failure ..
>>> parse @String p "abc"
Success ['a', 'b', 'c']
>>> parse @String p "abcd"
Success ['a', 'b', 'c', 'd']
>>> parse @String p "abcde"
Success ['a', 'b', 'c', 'd', 'e']
>>> parse @String p "abcdef"
Success ['a', 'b', 'c', 'd', 'e']
@since 0.1.0.0
-}
range :: Int -- ^ @min@, the minimum number of times to repeat @p@, inclusive.
-> Int -- ^ @max@, the maximum number of times to repeat @p@, inclusive.
-> Parsec a -- ^ @p@, the parser to repeat.
-> Parsec [a] -- ^ the results of the successful parses of @p@.
range mn mx p
| mn < 0 || mx < mn = pure []
| otherwise = go mn mx
where
go 0 0 = pure []
go 0 n = p <:> go 0 (n - 1) <|> pure []
go m n = p <:> go (m - 1) (n - 1)
{-|
This combinator parses between @min@ and @max@ occurrences of @p@ but ignoring the results.
Parses @p@ repeatedly a minimum of @min@ times and up to @max@ times both inclusive. If @p@ fails before
@min@ is reached, then this combinator fails. It is not required for @p@ to fail after the @max@th parse.
The results are discarded and @()@ is returned instead.
==== __Examples__
>>> let p = range_ 3 5 item
>>> parse @String p "ab"
Failure ..
>>> parse @String p "abc"
Success ()
>>> parse @String p "abcd"
Success ()
>>> parse @String p "abcde"
Success ()
>>> parse @String p "abcdef"
Success ()
@since 0.1.0.0
-}
range_ :: Int -- ^ @min@, the minimum number of times to repeat @p@, inclusive.
-> Int -- ^ @max@, the maximum number of times to repeat @p@, inclusive.
-> Parsec a -- ^ @p@, the parser to repeat.
-> Parsec ()
range_ mn mx p
| mn < 0 || mx < mn = unit
| otherwise = go mn mx
where
go 0 0 = unit
go 0 n = optional (p *> go 0 (n - 1))
go m n = p *> go (m - 1) (n - 1)
-- this is count overloading
{-|
This combinator parses between @min@ and @max@ occurrences of @p@, returning the number of successes.
Parses @p@ repeatedly a minimum of @min@ times and up to @max@ times both inclusive. If @p@ fails before
@min@ is reached, then this combinator fails. It is not required for @p@ to fail after the @max@th parse.
The results are discarded and the number of successful parses of @p@, @n@, is returned instead, such that
@min <= n <= max@.
==== __Examples__
>>> let p = count 3 5 item
>>> parse @String p "ab"
Failure ..
>>> parse @String p "abc"
Success 3
>>> parse @String p "abcd"
Success 4
>>> parse @String p "abcde"
Success 5
>>> parse @String p "abcdef"
Success 5
@since 0.1.0.0
-}
countRange :: Int -- ^ @min@, the minimum number of times to repeat @p@, inclusive.
-> Int -- ^ @max@, the maximum number of times to repeat @p@, inclusive.
-> Parsec a -- ^ @p@, the parser to repeat.
-> Parsec Int -- ^ the number of times @p@ parsed successfully.
countRange mn mx p
| mn < 0 || mx < mn = pure 0
| otherwise = go mn mx
where
go 0 0 = pure 0
go 0 n = liftA2 (const (+ 1)) p (go 0 (n - 1)) <|> pure 0
go m n = liftA2 (const (+ 1)) p (go (m - 1) (n - 1))