{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE CPP #-}

-- |
-- Module      : Text.Syntax.Poly.Combinators
-- Copyright   : 2010-11 University of Marburg, 2012 Kei Hibino
-- License     : BSD3
--
-- Maintainer  : ex8k.hibino@gmail.com
-- Stability   : experimental
-- Portability : unknown
--
-- This module contains combinators for classes defined in "Text.Syntax.Poly.Classes".
module Text.Syntax.Poly.Combinators (
  -- * Lexemes
  this,
  list,
  -- * Repetition
  none,
  many,
  some,
  replicate,
  sepBy, sepBy1,
  chainl1,
  count,
  -- * Skipping
  skipMany,
  skipSome,
  -- * Sequencing
  (*>),
  (<*),
  between,
  -- * Alternation
  (<+>), choice,
  optional, bool,
  (<$?>), (<?$>),
  -- * Printing
  format
  ) where

#if __GLASGOW_HASKELL__ < 710
import Prelude hiding (foldl, succ, replicate, (.))
#else
import Prelude hiding (foldl, succ, replicate, (.), (<$>), (<*>), (<*), (*>))
#endif

import Control.Isomorphism.Partial.Ext
  (nothing, just, nil, cons, left, right, foldl,
   (.), Iso, (<$>), inverse, element, unit, commute, ignore,
   mayAppend, mayPrepend, succ)

import Text.Syntax.Poly.Class
  ((<*>), (<|>), empty,
   AbstractSyntax(syntax), Syntax(token))

-- | 'none' parses\/prints empty tokens stream consume\/produces a empty list.
none :: AbstractSyntax delta => delta [alpha]
none =  nil <$> syntax ()

-- | The 'many' combinator is used to repeat syntax.
-- @many p@ repeats the passed syntax @p@
-- zero or more than zero times.
many :: AbstractSyntax delta => delta alpha -> delta [alpha]
many p = some p <|> none

-- | The 'some' combinator is used to repeat syntax.
-- @some p@ repeats the passed syntax @p@
-- more than zero times.
some :: AbstractSyntax delta => delta alpha -> delta [alpha]
some p = cons <$> p <*> many p

-- | The 'replicate' combinator is used to repeat syntax.
-- @replicate n p@ repeats the passwd syntax @p@
-- @n@ times.
replicate :: AbstractSyntax delta => Int -> delta alpha -> delta [alpha]
replicate n' p = rec n' where
  rec n | n <= 0    = none
        | otherwise = cons <$> p <*> rec (n - 1)

infixl 4 <+>

-- | The '<+>' combinator choose one of two syntax.
(<+>) :: AbstractSyntax delta => delta alpha -> delta beta -> delta (Either alpha beta)
p <+> q = (left <$> p) <|> (right <$> q) 

-- | The 'this' combinator parses\/prints a fixed token
this :: (Syntax tok delta, Eq tok) => tok -> delta ()
this t = inverse (element t) <$> token

-- | The 'list' combinator parses\/prints a fixed token list and consumes\/produces a unit value.
list :: (Syntax tok delta, Eq tok) => [tok] -> delta ()
list []      =    syntax ()
list (c:cs)  =    inverse (element ((), ()))
             <$>  this c
             <*>  list cs
-- list cs = foldr
--           (\ c -> (inverse (element ((), ())) <$>) . (this c <*>))
--           (syntax ())
--           cs

-- | This variant of '<*>' ignores its left result.
-- In contrast to its counterpart derived from the `Applicative` class, the ignored
-- parts have type `delta ()` rather than `delta beta` because otherwise information relevant
-- for pretty-printing would be lost. 

(*>) :: AbstractSyntax delta => delta () -> delta alpha -> delta alpha
p *> q = inverse unit . commute <$> p <*> q

-- | This variant of '<*>' ignores its right result.
-- In contrast to its counterpart derived from the `Applicative` class, the ignored
-- parts have type `delta ()` rather than `delta beta` because otherwise information relevant
-- for pretty-printing would be lost. 

(<*) :: AbstractSyntax delta => delta alpha -> delta () -> delta alpha
p <* q = inverse unit <$> p <*> q

infixl 7 *>, <*

-- | The 'between' function combines '*>' and '<*' in the obvious way.
between :: AbstractSyntax delta => delta () -> delta () -> delta alpha -> delta alpha
between p q r = p *> r <* q

-- | The 'chainl1' combinator is used to parse a
-- left-associative chain of infix operators. 
chainl1 :: AbstractSyntax delta =>
           delta alpha -> delta beta -> Iso (alpha, (beta, alpha)) alpha -> delta alpha
chainl1 arg op f 
  = foldl f <$> arg <*> many (op <*> arg)

-- | The 'count' combinator counts fixed syntax.
count :: (Eq beta, Enum beta, AbstractSyntax delta) => delta () -> delta beta
count p = succ <$> p *> count p <|> syntax (toEnum 0)

-- | The @skipMany p@ parse the passed syntax @p@
--   zero or more than zero times, and print nothing.
skipMany :: AbstractSyntax delta => delta alpha -> delta ()
skipMany p = ignore [] <$> many p

-- | The @skipSome v p@ parse the passed syntax @p@
--   more than zero times, and print @p@.
skipSome :: AbstractSyntax delta => delta alpha -> delta alpha
skipSome p = p <* skipMany p

-- | 'choice' a syntax from list.
choice :: AbstractSyntax delta => [delta alpha] -> delta alpha
choice (s:ss) = s <|> choice ss
choice []     = empty

-- | The 'optional' combinator may parse \/ print passed syntax.
optional :: AbstractSyntax delta => delta alpha -> delta (Maybe alpha)
optional x = just <$> x <|> nothing <$> syntax ()

-- | The 'bool' combinator parse \/ print passed syntax or not.
bool :: AbstractSyntax delta => delta () -> delta Bool
bool x = x *> syntax True <|> syntax False

-- | The 'sepBy' combinator separates syntax into delimited list.
-- @sepBy p d@ is @p@ list syntax delimited by @d@ syntax.
sepBy :: AbstractSyntax delta => delta alpha -> delta () -> delta [alpha]
sepBy x sep 
  =    x `sepBy1` sep
  <|>  none

-- | The 'sepBy1' combinator separates syntax into delimited non-empty list.
-- @sepBy p d@ is @p@ list syntax delimited by @d@ syntax.
sepBy1 :: AbstractSyntax delta => delta alpha -> delta () -> delta [alpha]
sepBy1 x sep = cons <$> x <*> many (sep *> x)

-- | May append not to repeat prefix syntax.
(<$?>) :: AbstractSyntax delta => Iso (a, b) a -> delta (a, Maybe b) -> delta a
cf <$?> pair = mayAppend  cf <$> pair

-- | May prepend not to repeat suffix syntax.
(<?$>) :: AbstractSyntax delta => Iso (a, b) b -> delta (Maybe a, b) -> delta b
cf <?$> pair = mayPrepend cf <$> pair

infix 5 <$?>, <?$>

-- | The 'format' combinator just print passed tokens
-- or may parse passed tokens.
-- This is useful in cases when just formatting with indents.
format :: (Syntax tok delta, Eq tok) => [tok] -> delta ()
format tks = ignore (Just ()) <$> optional (list tks)
