module Text.Syntax.Combinators
  (  -- * Lexemes
     text
  ,  comma
  ,  dot
     -- * Repetition
  ,  many
  ,  many1
  ,  sepBy
  ,  chainl1
     -- * Sequencing
  ,  (*>)
  ,  (<*)
  ,  between
     -- * Alternation
  ,  (<+>)
  ,  optional
     -- * Whitespace
  ,  skipSpace
  ,  sepSpace
  ,  optSpace) where

import Prelude (String)

import Control.Category ((.))
import Control.Isomorphism.Partial.Constructors (nothing, just, nil, cons, left, right)
import Control.Isomorphism.Partial.Derived (foldl)
import Control.Isomorphism.Partial.Prim (Iso, (<$>), inverse, element, unit, commute, ignore) 

import Data.Maybe (Maybe)
import Data.Either (Either)

import Text.Syntax.Classes

-- derived combinators
many :: Syntax delta => delta alpha -> delta [alpha]
many p 
  =    nil   <$>  pure ()
  <|>  cons  <$>  p 
             <*>  many p 

many1 :: Syntax delta => delta alpha -> delta [alpha]
many1 p = cons <$> p <*> many p
             
infixl 4 <+>

(<+>) :: Syntax delta => delta alpha -> delta beta -> delta (Either alpha beta)
p <+> q = (left <$> p) <|> (right <$> q) 

-- | `text` parses\/prints a fixed text and consumes\/produces a unit value.
text :: Syntax delta => String -> delta ()
text []      =    pure ()
text (c:cs)  =    inverse (element ((), ())) 
             <$>  (inverse (element c) <$> token) 
             <*>  text 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. 

(*>) :: Syntax 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. 

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

-- | The `between` function combines `*>` and `<*` in the obvious way.
between :: Syntax 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 :: Syntax delta => delta alpha -> delta beta -> Iso (alpha, (beta, alpha)) alpha -> delta alpha
chainl1 arg op f 
  = foldl f <$> arg <*> many (op <*> arg)

optional :: Syntax delta => delta alpha -> delta (Maybe alpha)
optional x  = just <$> x <|> nothing <$> text ""

sepBy :: Syntax delta => delta alpha -> delta () -> delta [alpha]
sepBy x sep 
  =    nil <$> text "" 
  <|>  cons <$> x <*> many (sep *> x) 

comma :: Syntax delta => delta ()
comma = text ","

dot :: Syntax delta => delta ()
dot = text "."


-- Expressing whitespace
-- ---------------------
-- 
-- Parsers and pretty printers treat whitespace 
-- differently. Parsers
-- specify where whitespace is allowed or required to occur, while
-- pretty printers specify how much whitespace is to be inserted at
-- these locations. To account for these different roles of
-- whitespace, the following three syntax descriptions provide
-- fine-grained control over where whitespace is allowed, desired or
-- required to occur.

-- | `skipSpace` marks a position where whitespace is allowed to
-- occur. It accepts arbitrary space while parsing, and produces
-- no space while printing. 

skipSpace  ::  Syntax delta => delta ()
skipSpace  =   ignore []    <$>  many (text " ")
 
-- | `optSpace` marks a position where whitespace is desired to occur.
-- It accepts arbitrary space while parsing, and produces a 
-- single space character while printing.

optSpace  ::  Syntax delta => delta ()
optSpace  =   ignore [()]  <$>  many (text " ")

-- | `sepSpace` marks a position where whitespace is required to
-- occur. It requires one or more space characters while parsing, 
-- and produces a single space character while printing.
   
sepSpace  ::  Syntax delta => delta ()
sepSpace  =   text " " <* skipSpace