{-# OPTIONS -fglasgow-exts #-}

{- | 

    Representation of Data Type Grammars.

-}
module Text.GRead.Grammar (
                       -- * Class Gram
                       Gram(..),
 
                       -- * Typed Grammar Representations for Data Types

                       -- ** Types
                       DGrammar(..), DGram(..), DRef(..), DLNontDefs(..),
                       DProductions(..), DProd(..), DSymbol(..), 
 
                       -- ** Smart Constructors
                       ( .#. ), consG, consD, dNont, dTerm, dEnd, parenT,

                       -- * Typed Grammar Representations

                       -- ** Types
                       Grammar(..), Productions(..), Prod(..), 
                       Symbol(..), Token(..),

                       -- ** Smart Constructors
                       ext, ( .*. ),

                       -- ** Utils
                       matchSym, append
                     ) where

import Language.AbstractSyntax.TTTAS (Env(..), Ref(..), Equal(..), match)

-- |
--
-- Class of data types with typed grammar representation. It has to be
-- instantiated in order to use the function 'Text.GRead.gread'.  
--
-- Instances can be derived automatically using the functions defined
-- in the module "Text.GRead.Derive".
-- 
-- For example, given the declarations
--
-- > infixl 5 :<:
-- > infixr 6 :>:, :*:
-- > 
-- > data T1  =  T1 :<: T1
-- >          |  T1 :>: T1
-- >          |  C1
-- > 
-- > data T2 a  =  a :*: T2 a
-- >            |  C2
--
-- the instances of 'Gram' can be
--
-- > _0 = Zero
-- > _1 = Suc _0
-- > 
-- > instance Gram T1 where
-- >  grammar = DGrammar   _0 envT1
-- > 
-- > envT1 :: Env DGram ((),T1) ((),T1) 
-- > envT1 =  consD (nonts _0) Empty 
-- >      where
-- >       nonts _T1 = DLNontDefs
-- >        [  (  DRef (_T1, 5) 
-- >           ,  DPS  [  dNont (_T1, 5) .#. dTerm ":<:" .#.
-- >                      dNont (_T1, 6) .#. dEnd infixL ]
-- >           )
-- >        ,  (  DRef (_T1, 6) 
-- >           ,  DPS  [  dNont (_T1, 7) .#. dTerm ":>:" .#.
-- >                      dNont (_T1, 6) .#. dEnd infixR ] 
-- >           ) 
-- >        ,  (  DRef (_T1,10) 
-- >           ,  DPS  [  dTerm "C1"   .#. dEnd (const C1)
-- >                   ,  dTerm "(" .#. dNont (_T1,0) .#. 
-- >                      dTerm ")" .#. dEnd parenT ] 
-- >           )
-- >        ]
-- >       infixL e1 _ e2   = e2 :<: e1
-- >       infixR e1 _ e2   = e2 :>: e1 
-- > 
-- > instance Gram a => Gram (T2 a) where
-- >  grammar = DGrammar   _0  envT2 
-- > 
-- > envT2 :: (Gram a) => Env DGram  (((),a),T2 a)
-- >                                 (((),a),T2 a)
-- > envT2 =  consD (nonts  _0 _1) $ 
-- >          consG grammar Empty
-- >      where
-- >       nonts _T2 _A = DLNontDefs
-- >        [  (  DRef (_T2, 6)
-- >           ,  DPS  [  dNont (_A,   7)  .#. dTerm ":*:" .#. 
-- >                      dNont (_T2,  7)  .#. dEnd infixT ] 
-- >           )
-- >        ,  (  DRef (_T2,10) 
-- >           ,  DPS  [  dTerm "C2"   .#. dEnd (const C2)
-- >                   ,  dTerm "("    .#. dNont (_T2,0) .#. 
-- >                      dTerm ")"    .#. dEnd parenT ] 
-- >           )
-- >        ]
-- >       infixP   e1 _ e2  = e2 :+: e1
-- >       infixT   e1 _ e2  = e2 :*: e1 
--
-- In case of mutually recursive datatypes, their definitions have
-- to be tupled together into a single environment.

class Gram a where
 -- | The function 'grammar' returns the grammar representation of
 --   the data type.
 grammar :: DGrammar a

-- | Data type describing grammatical structures of data types,
--   including information about precedences. The type @DGrammar a@
--   describes the grammar of the data type @a@.
data DGrammar a 
        -- | A grammar consists of an environment ('Env') with the
        --   defined non-terminals and a reference ('Ref') to the
        --   /main non-terminal/ in the environment.
        = forall env. DGrammar  (Ref a env) 
                                (Env DGram env env)
 
data DGram a env  =  DGD (DLNontDefs a env) 
                  |  DGG (DGrammar a)
newtype DRef a env = DRef (Ref a env, Int)
newtype DLNontDefs a env 
            = DLNontDefs [(DRef a env, DProductions a env)]
newtype DProductions a env 
            = DPS { unDPS :: [DProd a env] }
 
data DProd a env where
  DSeq    :: DSymbol b env  -> DProd (b->a) env 
                            -> DProd a env
  DEnd    :: a              -> DProd a env
 
data DSymbol a env where
  DNont  :: DRef a env  -> DSymbol a env
  DTerm  :: Token       -> DSymbol Token env

infixr 5 .#.

( .#. ) ::  DSymbol b env -> DProd (b -> a) env -> DProd a env
( .#. )                  = DSeq

consG :: DGrammar a -> Env DGram use def' -> Env DGram use (def', a)
consG   g es             = Ext    es (DGG g)

consD :: DLNontDefs a env -> Env DGram env def' -> Env DGram env (def', a)
consD   g es             = Ext    es (DGD g)

dNont ::  (Ref a env, Int) -> DSymbol a env
dNont   nt               = DNont  (DRef nt)

dTerm ::  [Char] -> DSymbol Token env
dTerm   t  | t == "("    = DTerm  Open
           | t == ")"    = DTerm  Close
           | otherwise   = DTerm  (Keyw t)

dEnd ::  a -> DProd a env
dEnd    f                = DEnd   f

parenT ::  t -> t1 -> t2 -> t1
parenT  _ e _            = e



data Grammar a  
  = forall env . Grammar  (Ref a env)  
                          (Env Productions env env)

newtype Productions a env 
  = PS {unPS :: [Prod a env]}

data Prod a env where
      Seq   ::  Symbol    b        env  ->  Prod   (  b -> a)  env  
                                        ->  Prod      a        env
      End   ::  a                       ->  Prod      a        env

data Symbol a env where
  Nont :: Ref a env ->  Symbol  a       env
  Term :: Token     ->  Symbol  Token   env

data Token = Keyw String
           | Open
           | Close
         deriving (Ord, Eq)


infixr 5 `ext` , .*.

ext :: Env Productions env def' -> [Prod a env]
    -> Env Productions env (def', a)
ext g prods  = Ext g (PS prods)

( .*. ) ::  Symbol b env -> Prod (b -> a) env -> Prod a env
( .*. )      = Seq

matchSym  ::  Symbol a env -> Symbol b env 
          ->  Maybe (Equal a b)
matchSym (Nont x)  (Nont y)             = match x y
matchSym (Term x)  (Term y) | x == y    = Just Eq
matchSym _         _                    = Nothing


append  :: (a -> b -> c) -> Prod a env -> Symbol b env 
        -> Prod c env
append g (End f     )  s  =  Seq s  (End (g f))
append g (Seq t ts  )  s  
  =  Seq t (append  (\b c d -> g (b d) c) ts s)