{-# LANGUAGE FlexibleContexts #-}
module Control.Effects.Parser (
   parse,
   oneOf,
   item,
   itemIf,
   lookahead,
   noMatch,
   noBacktrack,
   parseFail,
   parseEnd,
   parseOpt,
   parseMany,
   parseMany1,
   parseOpt',
   parseMany',
   parseMany1'
) where

import Control.Applicative
import Control.Effects
import Control.Monad

-- |A parser is a function from a list of items to a parsed result.
-- The result culminates with "return (Just a)" on success. On
-- failure the result is the second argument, which is defaults 
-- to "return Nothing", but can be modified to include effects
-- such as backtracking.
newtype Parser c m a = Parser ([c] -> m (Maybe a) -> m (Maybe a))

-- |Generates a Handler to parse input from the given list of items.
parse :: Monad m => [c] -> Handler (Parser c m a) (Maybe a) m a
parse input = Handler
   { ret = \a -> return . Parser $ \cs f -> return (Just a)
   , fin = \(Parser r) -> r input (return Nothing)
   }

-- |Internal helper primitive to change the input for the remainder of the parser.
putInput :: AutoLift (Parser c m a) m n => Effect (Parser c m a) m -> [c] -> n ()
putInput p cs' = operation p $ \k -> return . Parser $ \cs f -> do
  Parser r <- k ()
  r cs' f

-- |Internal helper primitive to change the action on failure for the remainder of the parser.
putFailAction :: AutoLift (Parser c m a) m n => Effect (Parser c m a) m -> m (Maybe a) -> n ()
putFailAction p f' = operation p $ \k -> return . Parser $ \cs f -> do
  Parser r <- k ()
  r cs f'

-- |Tries each action from the given list in turn, stopping with the first one
-- which successfully parses the input. A parse failure in the action or the
-- continuation results in backtracking to the original location in the input,
-- and reverts the state of any more deeply nested effects.
oneOf :: AutoLift (Parser c m a) m n => Effect (Parser c m a) m -> [n b] -> n b
oneOf p []     = parseFail p
oneOf p (a:as) = join $ operation p $ \k -> return . Parser $ \cs f -> do
   Parser r <- k a
   r cs $ do
      Parser r' <- k (oneOf p as)
      r' cs f

-- |Returns and consumes the next item from the input.
-- Fails if there is no more input to be consumed.
item :: AutoLift (Parser c m a) m n => Effect (Parser c m a) m -> n c
item p = operation p $ \k -> return . Parser $ \cs f ->
   case cs of
      []    -> f
      (h:t) -> do { Parser r <- k h; r t f }

-- |Returns and consumes the next item from the input, provided it satisfies a predicate.
-- Fails if there is no more input, or if the predicate function evaluates to False.
itemIf :: AutoLift (Parser c m a) m n => Effect (Parser c m a) m -> (c -> Bool) -> n c
itemIf p f = do { x <- item p; if f x then return x else parseFail p }

-- |Zero-width positive lookahead assertion. Runs the given action against the
-- current input without consuming it. A parse failure in the action causes the
-- lookahead to fail; otherwise, the result of the action is returned.
lookahead :: AutoLift (Parser c m a) m n => Effect (Parser c m a) m -> n b -> n b
lookahead p a = join $ operation p $ \k -> return . Parser $ \cs f -> do
   Parser r <- k (a <* putInput p cs)
   r cs f

-- |Zero-width negative lookahead assertion. Runs the given action against the
-- current input without consuming it. A parse failure in the action causes the
-- lookahead to succeed; otherwise, the lookahead fails.
noMatch :: AutoLift (Parser c m a) m n => Effect (Parser c m a) m -> n b -> n ()
noMatch p a = join $ operation p $ \k -> return . Parser $ \cs f -> do
   Parser r <- k (a >> putFailAction p f >> parseFail p)
   r cs $ do
      Parser r' <- k (return ())
      r' cs f

-- |Prevents backtracking into the middle of the given action. This can be used to
-- create actions which always consume as much or as little of the input as possible,
-- even if that would cause a later match to fail. Note that backtracking is still
-- possible within the action, or across the entire noBacktrack action.
--
-- Examples:
--
-- >  noBacktrack p $ oneOf [return (), item p >> return ()]  -- never consumes any input
-- >  noBacktrack p $ parseMany p $ itemIf p (=='a')          -- always consumes all the 'a's
noBacktrack :: AutoLift (Parser c m a) m n => Effect (Parser c m a) m -> n b -> n b
noBacktrack p a = join $ operation p $ \k -> return . Parser $ \cs f -> do
   Parser r <- k (a <* putFailAction p f)
   r cs f

-- |A parser which always fails.
parseFail :: AutoLift (Parser c m a) m n => Effect (Parser c m a) m -> n b
parseFail p = operation p $ \k -> return . Parser $ \cs f -> f

-- |A parser which succeeds only when there is no more input.
parseEnd :: AutoLift (Parser c m a) m n => Effect (Parser c m a) m -> n ()
parseEnd p = noMatch p (item p)

-- |Try the given parser; returns Just the result on success, or Nothing otherwise.
parseOpt :: AutoLift (Parser c m a) m n => Effect (Parser c m a) m -> n b -> n (Maybe b)
parseOpt p f = oneOf p [Just <$> f, return Nothing]

-- |Apply the given parser zero or more times, and return a list of the results.
parseMany :: AutoLift (Parser c m a) m n => Effect (Parser c m a) m -> n b -> n [b]
parseMany p f = oneOf p [parseMany1 p f, return []]

-- |Like parseMany, but fails if there isn't at least one match.
parseMany1 :: AutoLift (Parser c m a) m n => Effect (Parser c m a) m -> n b -> n [b]
parseMany1 p f = (:) <$> f <*> parseMany p f

-- |A non-greedy version of parseOpt which prefers to match Nothing.
parseOpt' :: AutoLift (Parser c m a) m n => Effect (Parser c m a) m -> n b -> n (Maybe b)
parseOpt' p f = oneOf p [return Nothing, Just <$> f]

-- |A non-greedy version of parseMany which matches as few times as possible. 
parseMany' :: AutoLift (Parser c m a) m n => Effect (Parser c m a) m -> n b -> n [b]
parseMany' p f = oneOf p [return [], parseMany1' p f]

-- |A non-greedy version of parseMany1 which matches as few times as possible (but at least once).
parseMany1' :: AutoLift (Parser c m a) m n => Effect (Parser c m a) m -> n b -> n [b]
parseMany1' p f = (:) <$> f <*> parseMany' p f