Skip to content

Commit e94bf23

Browse files
authored
Merge pull request #193 from purescript-contrib/takemany
advance, manyIndex combinators
2 parents e5cdcd3 + 0d1e63c commit e94bf23

File tree

4 files changed

+91
-5
lines changed

4 files changed

+91
-5
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ 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)
5152

5253
Bugfixes:
5354

src/Parsing.purs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -404,10 +404,11 @@ region context p = catchError p $ \err -> throwError $ context err
404404

405405
-- | `Position` represents the position of the parser in the input stream.
406406
-- |
407-
-- | - `index` is the position since the start of the input. Starts at 0.
408-
-- | - `line` is the current line in the input. Starts at 1.
407+
-- | - `index` is the position offset since the start of the input. Starts
408+
-- | at *0*.
409+
-- | - `line` is the current line in the input. Starts at *1*.
409410
-- | - `column` is the column of the next character in the current line that
410-
-- | will be parsed. Starts at 1.
411+
-- | will be parsed. Starts at *1*.
411412
newtype Position = Position
412413
{ index :: Int
413414
, line :: Int

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: 35 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 (advance, between, chainl, chainl1, chainr, chainr1, choice, endBy, endBy1, lookAhead, many, many1, many1Till, many1Till_, 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,37 @@ 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+
}
1032+
1033+
log "\nTESTS advance\n"
1034+
1035+
assertEqual' "advance 1"
1036+
{ actual: runParser "aa" $ advance $ char 'a'
1037+
, expected: Right 'a'
1038+
}
1039+
assertEqual' "advance 2"
1040+
{ actual: lmap parseErrorPosition $ runParser "aa" $ advance consume
1041+
, expected: Left (Position { index: 0, line: 1, column: 1 })
1042+
}

0 commit comments

Comments
 (0)