-----------------------------------------------------------------------------
-- |
-- Module      :  Harp.Match
-- Copyright   :  (c) Niklas Broberg 2004,
-- License     :  BSD-style (see the file LICENSE.txt)
--
-- Maintainer  :  Niklas Broberg, d00nibro@dtek.chalmers.se
-- Stability   :  experimental
-- Portability :  portable
--
-- Functions that simulate the behavior of regular patterns
-- using a Match monad for parsing lists.
-----------------------------------------------------------------------------

{-# LANGUAGE CPP #-}

module Harp.Match (
        Match           -- Match e a
        , runMatch      -- Match e a -> [e] -> Maybe a
        , baseMatch     -- (a -> Maybe b) -> Match a (a, b)
        , manyMatch     -- Match e a -> Match e [a]
        , gManyMatch    -- Match e a -> Match e [a]
        , foldComp      -- [[a] -> [a]] -> ([a] -> [a])
        , unzip0, unzip1, unzip2, unzip3, unzip4, unzip5, unzip6, unzip7
        , (+++)
        ) where

#if MIN_VERSION_base(4,8,0)
import Control.Monad (ap, liftM)
#endif
import Data.List (unzip3, unzip4, unzip5, unzip6, unzip7)

--------------------------------------------------------------
-- | The Match monad

newtype Match e a = Match ([e] -> [(a, [e])])

(+++) :: Match e a -> Match e a -> Match e a
(Match f) +++ (Match g) = Match (\es -> let aes1 = f es
                                            aes2 = g es
                                         in aes1 ++ aes2)

#if MIN_VERSION_base(4,8,0)
instance Applicative (Match e) where
   (<*>) = ap
   pure = return

instance Functor (Match e) where
   fmap = liftM
#endif

instance Monad (Match e) where
  return x = Match (\es -> [(x, es)])

  (Match f) >>= k = Match (\es -> let aes = f es
                                   in concatMap help aes)
    where help (a, es) = let Match g = k a
                          in g es


mfail :: Match e a
mfail = Match $ \_ -> []

runM :: Match e a -> [e] -> [a]
runM (Match f) es = let aes = f es
                     in map fst $ filter (null . snd) aes

getElement :: Match e e
getElement = Match $ \es -> case es of
                             [] -> []
                             (x:xs) -> [(x,x:xs)]

discard :: Match e ()
discard = Match $ \es -> case es of
                           [] -> []
                           (_:xs) -> [((), xs)]


runMatch :: Match e a -> [e] -> Maybe a
runMatch m es = case runM m es of
                 []     -> Nothing
                 (a:_) -> Just a

baseMatch :: (a -> Maybe b) -> Match a (a, b)
baseMatch f = do e <- getElement
                 case f e of
                  Nothing -> mfail
                  Just b -> do discard
                               return (e, b)

gManyMatch :: Match e a -> Match e [a]
gManyMatch m = (do a <- m
                   as <- gManyMatch m
                   return (a:as))
              +++ (return [])

manyMatch :: Match e a -> Match e [a]
manyMatch m = (return []) +++
                (do a <- m
                    as <- manyMatch m
                    return (a:as))


foldComp :: [[a] -> [a]] -> ([a] -> [a])
foldComp = foldl (.) id


unzip0 :: [()] -> ()
unzip0 = const ()

unzip1 :: [a] -> [a]
unzip1 = id

unzip2 :: [(a,b)] -> ([a],[b])
unzip2 = unzip

{-
data M e a = Element (e -> M e a)
           | Fail
           | Return a (M e a)

instance Monad (M e) where
  return x = Return x Fail

  (Element f)  >>= k = Element (\e -> f e >>= k)
  Fail         >>= k = Fail
  (Return x m) >>= k = k x ++++ (m >>= k)


infix 5 ++++

(++++) :: M e a -> M e a -> M e a
Fail       ++++ n          = n
m          ++++ Fail       = m
Return x m ++++ n          = Return x (m ++++ n)
m          ++++ Return x n = Return x (m ++++ n)
Element f  ++++ Element g  = Element (\e -> f e ++++ g e)


runM :: M e a -> [e] -> [a]
runM (Element f)  (e:es) = runM (f e) es
runM (Element _)  []     = []
runM Fail         _      = []
runM (Return x m) []     = x : runM m []
runM (Return _ m) es     = runM m es

-- the continuation trick
newtype Match e a = Match ()

instance Monad (Match e) where
  return x = Match (\k -> k x)

  (Match f) >>= k = Match (\h -> f (\a -> let Match g = k a
                                           in g h))

runMatch :: Match e a -> [e] -> [a]
runMatch (Match f) = runM (f return)

mfail :: Match e a
mfail = Match $ \_ -> Fail
-}
