On that one, found a solution too, posting to have it recorded in the annals of PureScript tutoriality. Comes down to tail recursion:
foldParse :: ∀ a. (a -> TextParser a) -> a -> TextParser a
foldParse f init = do
acc :: Maybe a <- optionMaybe $ f init
case acc of
Nothing -> pure init
Just acc' -> foldParse f acc'
It’s your typical foldl, except we’re folding the underlying monad. Internally it uses optionMaybe to check/ignore if parser couldn’t consume any input. So no “Unexpected EOF” error, for example. A failure with having input consumed is propagated as usual.
A small usage example in terms of the thread-starter case (i.e. “grep implementation” via Parsing), where I use foldParse to concatenate each subsequent match to a string. So running over foo ffoo foo returns Found: foo, foo, foo, (didn’t handle the trailing comma for simplicity):
Code (click to unroll)
module Main where
import Prelude
import Data.Maybe (Maybe(..))
import Data.String.CodeUnits as Str
import Effect (Effect)
import Effect.Console (logShow)
import Parsing (Parser, runParser)
import Parsing.Combinators (optionMaybe, try, (<|>))
import Parsing.String (anyChar, string)
type TextParser = Parser String
foldParse :: ∀ a. (a -> TextParser a) -> a -> TextParser a
foldParse f init = do
acc :: Maybe a <- optionMaybe $ f init
case acc of
Nothing -> pure init
Just acc' -> foldParse f acc'
anyCharS :: TextParser String
anyCharS = anyChar >>= pure <<< Str.singleton
skipChar :: ∀ a. a -> TextParser a
skipChar marker = anyCharS *> pure marker
myParser :: TextParser String
myParser = string "foo"
main :: Effect Unit
main = logShow $ runParser "foo ffoo foo" $ foldParse concatMyParseToString "Found: "
where
concatMyParseToString acc = do
ret <- try myParser <|> skipChar "skipped"
pure if ret == "skipped" then acc
else acc <> ret <> ", "