Skip to content

Commit 8cd77f5

Browse files
committed
advance, manyIndex combinators
1 parent 3f1bbbb commit 8cd77f5

File tree

4 files changed

+78
-2
lines changed

4 files changed

+78
-2
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ New features:
4848
- Add the `anyTill` primitive `String` combinator. (#186 by @jamesdbrock)
4949
- Add the `Parsing.String.Replace` module, copied from
5050
https://github.com/jamesdbrock/purescript-parsing-replace (#188 by @jamesdbrock)
51+
- Add the `advance` and `manyIndex` combinators. (#193 by @jamesdbrock)
52+
5153

5254
Bugfixes:
5355

packages.dhall

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
let upstream =
22
https://raw.githubusercontent.com/purescript/package-sets/prepare-0.15/src/packages.dhall
3+
sha256:bc7c22be785b6873048f7328745c1f518c9f3f9d8dc1768967a7401c786e091c
34

45
in upstream

src/Parsing/Combinators.purs

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ module Parsing.Combinators
5555
, manyTill_
5656
, many1Till
5757
, many1Till_
58+
, manyIndex
5859
, skipMany
5960
, skipMany1
6061
, sepBy
@@ -67,6 +68,7 @@ module Parsing.Combinators
6768
, chainl1
6869
, chainr
6970
, chainr1
71+
, advance
7072
, withErrorMessage
7173
, (<?>)
7274
, withLazyErrorMessage
@@ -96,7 +98,7 @@ import Data.Tuple (Tuple(..))
9698
import Data.Tuple.Nested (type (/\), (/\))
9799
import Data.Unfoldable (replicateA)
98100
import Data.Unfoldable1 (replicate1A)
99-
import Parsing (ParseError(..), ParseState(..), ParserT(..), fail)
101+
import Parsing (ParseError(..), ParseState(..), ParserT(..), Position(..), fail, position)
100102

101103
-- | Provide an error message in the case of failure.
102104
withErrorMessage :: forall m s a. ParserT s m a -> String -> ParserT s m a
@@ -440,3 +442,51 @@ manyTill_ p end = tailRecM go Nil
440442
do
441443
x <- p
442444
pure (Loop (x : xs))
445+
446+
-- | Parse the phrase as many times as possible, at least *N* times, but no
447+
-- | more than *M* times.
448+
-- | If the phrase can’t parse as least *N* times then the whole
449+
-- | parser fails. If the phrase parses successfully *M* times then stop.
450+
-- | The current phrase index, starting at *0*, is passed to the phrase.
451+
-- |
452+
-- | Returns the list of parse results and the number of results.
453+
-- |
454+
-- | `manyIndex n n (\_ -> p)` is equivalent to `replicateA n p`.
455+
manyIndex :: forall s m a. Int -> Int -> (Int -> ParserT s m a) -> ParserT s m (Tuple Int (List a))
456+
manyIndex from to p =
457+
if from > to || from < 0 then
458+
pure (Tuple 0 Nil)
459+
else
460+
tailRecM go (Tuple 0 Nil)
461+
where
462+
go (Tuple i xs) =
463+
if i >= to then
464+
pure (Done (Tuple i (reverse xs)))
465+
else
466+
( do
467+
x <- p i
468+
pure (Loop (Tuple (i + 1) (x : xs)))
469+
)
470+
<|>
471+
( if i >= from then
472+
pure (Done (Tuple i (reverse xs)))
473+
else
474+
fail "Expected more phrases"
475+
)
476+
477+
-- | If the parser succeeds without advancing the input stream position,
478+
-- | then force the parser to fail.
479+
-- |
480+
-- | This combinator can be used to prevent infinite parser repetition.
481+
-- |
482+
-- | Does not depend on or effect the `consumed` flag which indicates whether
483+
-- | we are committed to this parsing branch.
484+
advance :: forall s m a. ParserT s m a -> ParserT s m a
485+
advance p = do
486+
Position { index: index1 } <- position
487+
x <- p
488+
Position { index: index2 } <- position
489+
if index2 > index1 then
490+
pure x
491+
else
492+
fail "Expected progress"

test/Main.purs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import Effect.Console (log, logShow)
3434
import Effect.Unsafe (unsafePerformEffect)
3535
import Node.Process (lookupEnv)
3636
import Parsing (ParseError(..), Parser, ParserT, Position(..), consume, fail, initialPos, parseErrorMessage, parseErrorPosition, position, region, runParser)
37-
import Parsing.Combinators (between, chainl, chainl1, chainr, chainr1, choice, endBy, endBy1, lookAhead, many, many1, many1Till, many1Till_, manyTill, manyTill_, notFollowedBy, optionMaybe, sepBy, sepBy1, sepEndBy, sepEndBy1, skipMany, skipMany1, try, (<?>), (<??>), (<~?>))
37+
import Parsing.Combinators (between, chainl, chainl1, chainr, chainr1, choice, endBy, endBy1, lookAhead, many, many1, many1Till, many1Till_, manyIndex, manyIndex, manyTill, manyTill_, notFollowedBy, optionMaybe, sepBy, sepBy1, sepEndBy, sepEndBy1, skipMany, skipMany1, try, (<?>), (<??>), (<~?>))
3838
import Parsing.Expr (Assoc(..), Operator(..), buildExprParser)
3939
import Parsing.Language (haskellDef, haskellStyle, javaStyle)
4040
import Parsing.String (anyChar, anyCodePoint, anyTill, char, eof, match, regex, rest, satisfy, string, takeN)
@@ -1006,3 +1006,26 @@ main = do
10061006
rmap fst <$> splitCap "((🌼)) (()())" (match balancedParens)
10071007
, expected: NonEmptyList $ Right "((🌼))" :| Left " " : Right "(()())" : Nil
10081008
}
1009+
1010+
log "\nTESTS manyIndex\n"
1011+
1012+
assertEqual' "manyIndex 1"
1013+
{ actual: runParser "aaab" $ manyIndex 0 3 (\_ -> char 'a')
1014+
, expected: Right (Tuple 3 ('a' : 'a' : 'a' : Nil))
1015+
}
1016+
assertEqual' "manyIndex 2"
1017+
{ actual: runParser "aaaa" $ manyIndex 0 3 (\_ -> char 'a')
1018+
, expected: Right (Tuple 3 ('a' : 'a' : 'a' : Nil))
1019+
}
1020+
assertEqual' "manyIndex 3"
1021+
{ actual: runParser "b" $ manyIndex 0 3 (\_ -> char 'a')
1022+
, expected: Right (Tuple 0 (Nil))
1023+
}
1024+
assertEqual' "manyIndex 4"
1025+
{ actual: lmap parseErrorPosition $ runParser "ab" $ manyIndex 3 3 (\_ -> char 'a')
1026+
, expected: Left (Position { index: 1, line: 1, column: 2 })
1027+
}
1028+
assertEqual' "manyIndex 5"
1029+
{ actual: runParser "aaa" $ manyIndex (-2) (1) (\_ -> char 'a')
1030+
, expected: Right (Tuple 0 (Nil))
1031+
}

0 commit comments

Comments
 (0)