{-# LANGUAGE TypeFamilies, NoMonomorphismRestriction, FlexibleContexts #-}

module Text.ParserCombinators.Class where
import Data.Char
import Control.Monad hiding (forM)
import Control.Applicative
import Data.Traversable

-- | Parser class
class (Monad p, Alternative p) => IsParser p where
  type SymbolOf p -- Type of symbols processed
  satisfy :: (SymbolOf p -> Bool) -> p (SymbolOf p) -- ^ accept a symbol satisfying a given predicate
  look :: p [SymbolOf p] -- ^ access the stream of symbols from the current point
  label :: String -> p a -> p a -- ^ label the parser
  (<<|>) :: p a -> p a -> p a -- ^ Left-biased choice.

-------------------------------------------------------------------------
-- derived parsers

infix  2 <?>
infixr 3 <<|>

-- | Label a parser
(<?>) :: IsParser p => p a -> String -> p a
p <?> s = label s p

char c    = satisfy (==c) <?> show [c]
noneOf cs = satisfy (\c -> not (c `elem` cs)) <?> ("none of " ++ cs)
oneOf cs  = satisfy (\c -> c `elem` cs) <?> ("one of " ++ cs)

spaces    = skipMany space     <?> "white space"
space     = satisfy isSpace    <?> "space"
newline   = char '\n'          <?> "new-line"
tab       = char '\t'          <?> "tab"
upper     = satisfy isUpper    <?> "uppercase letter"
lower     = satisfy isLower    <?> "lowercase letter"
alphaNum  = satisfy isAlphaNum <?> "letter or digit"
letter    = satisfy isAlpha    <?> "letter"
digit     = satisfy isDigit    <?> "digit"
hexDigit  = satisfy isHexDigit <?> "hexadecimal digit"
octDigit  = satisfy isOctDigit <?> "octal digit"

anySymbol :: IsParser p => p (SymbolOf p)
anySymbol = satisfy (const True)

string :: (IsParser p, SymbolOf p ~ Char) => String -> p String
string s = forM s char <?> show s

choice :: Alternative f => [f a] -> f a
choice ps = foldr (<|>) empty ps

option :: Alternative f => a -> f a -> f a
option x p = p <|> pure x

between :: Applicative m => m x -> m y -> m a -> m a
between open close p = open *> p <* close

-- repetition
-- | Greedy repetition: match as many occurences as possible of the argument.
manyGreedy :: IsParser m => m a -> m [a]
manyGreedy p = do
  x <- (Just <$> p) <<|> pure Nothing
  case x of
    Nothing -> return []
    Just y -> (y:) <$> manyGreedy p

skipMany1 p = p *> skipMany p
skipMany  p = let scan = (p *> scan) <|> pure () in scan

sepBy  p sep = sepBy1 p sep <|> pure []
sepBy1 p sep = (:) <$> p <*> many (sep *> p)

count :: Applicative m => Int -> m a -> m [a]
count n p = sequenceA (replicate n p)

chainr p op x = chainr1 p op <|> return x
chainl p op x = chainl1 p op <|> return x

chainr1 p op = scan
 where
  scan   = do x <- p; rest x
  rest x = (do f <- op; y <- scan; return (f x y)) <|> return x

chainl1 p op = scan
 where
  scan   = do x <- p; rest x
  rest x = (do f <- op; y <- p; rest (f x y)) <|> return x

munch,munch1 :: IsParser m => (SymbolOf m -> Bool) -> m [SymbolOf m]
munch p = scan =<< look
 where
  scan (c:cs) | p c = (:) <$> anySymbol <*> scan cs
  scan _            = pure []

munch1 p = (:) <$> satisfy p <*> munch p

endOfFile = label "end of file" $ do
  s <- look
  guard (null s)