{-# LANGUAGE DeriveFoldable #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeFamilies #-}

-- we have lots of parsers which don't want signatures; and we have
-- uniplate patterns
{-# OPTIONS_GHC -fno-warn-missing-signatures
                -fno-warn-incomplete-patterns
                -fno-warn-name-shadowing #-}
{-# OPTIONS_HADDOCK show-extensions #-}

-- |
-- Module      :  Yi.Syntax.Haskell
-- License     :  GPL-2
-- Maintainer  :  yi-devel@googlegroups.com
-- Stability   :  experimental
-- Portability :  portable
--
-- NOTES:
-- Note if the layout of the first line (not comments)
-- is wrong the parser will only parse what is in the blocks given by Layout.hs

module Yi.Syntax.Haskell ( PModule
                         , PModuleDecl
                         , PImport
                         , Exp (..)
                         , Tree
                         , parse
                         , indentScanner
                         ) where

import Control.Applicative
import Data.Foldable hiding (elem, notElem)
import Data.Maybe
import Data.List ((\\))
import Yi.IncrementalParse
import Yi.Lexer.Alex
import Yi.Lexer.Haskell
import Yi.Syntax.Layout
import Yi.Syntax.Tree
import Yi.Syntax
import Control.Arrow ((&&&))

indentScanner :: Scanner (AlexState lexState) TT
              -> Scanner (Yi.Syntax.Layout.State Token lexState) TT
indentScanner = layoutHandler startsLayout [(Special '(', Special ')'),
                                            (Reserved Let, Reserved In),
                                            (Special '[', Special ']'),
                                            (Special '{', Special '}')]
                         ignoredToken
                         (Special '<', Special '>', Special '.')
                         isBrace

-- HACK: We insert the Special '<', '>', '.', which do not occur in
-- normal haskell parsing.

-- | Check if a token is a brace, this function is used to
-- fix the layout so that do { works correctly
isBrace :: TT -> Bool
isBrace (Tok br _ _) = Special '{' == br

-- | Theese are the tokens ignored by the layout handler.
ignoredToken :: TT -> Bool
ignoredToken (Tok t _ (Posn{})) = isComment t || t == CppDirective

type Tree = PModule
type PAtom = Exp
type Block = Exp
type PGuard = Exp
type PModule = Exp
type PModuleDecl = Exp
type PImport = Exp


-- | Exp can be expression or declaration
data Exp t
    = PModule { comments :: [t]
              , progMod  :: Maybe (PModule t)
              }
    | ProgMod { modDecl :: PModuleDecl t
              , body    :: PModule t  -- ^ The module declaration part
              }
    | Body { imports :: Exp t -- [PImport t]
           , content :: Block t
           , extraContent :: Block t -- ^ The body of the module
           }
    | PModuleDecl { moduleKeyword :: PAtom t
                                 , name          :: PAtom t
                                 , exports       :: Exp t
                                 , whereKeyword  :: Exp t
                                    }
    | PImport { importKeyword :: PAtom t
                         , qual          :: Exp t
                         , name'         :: PAtom t
                         , as            :: Exp t
                         , specification :: Exp t
                         }

    | TS t [Exp t] -- ^ Type signature
    | PType { typeKeyword :: PAtom t
            , typeCons    :: Exp t
            , equal       :: PAtom t
            , btype       :: Exp t
            } -- ^ Type declaration
    | PData { dataKeyword :: PAtom t
            , dtypeCons   :: Exp t
            , dEqual      :: Exp t
            , dataRhs     :: Exp t
            }  -- ^ Data declaration
    | PData' { dEqual     :: PAtom t
             , dataCons   :: Exp t -- ^ Data declaration RHS
             }
    | PClass { cKeyword   :: PAtom t -- Can be class or instance
             , cHead      :: Exp t
             , cwhere     :: Exp t -- ^ Class declaration
             }
      -- declaration
      -- declarations and parts of them follow
    | Paren (PAtom t) [Exp t] (PAtom t) -- ^ A parenthesized, bracked or braced
    | Block [Exp t] -- ^ A block of things separated by layout
    | PAtom t [t] -- ^ An atom is a token followed by many comments
    | Expr [Exp t] -- ^
    | PWhere (PAtom t) (Exp t) (Exp t) -- ^ Where clause
    | Bin (Exp t) (Exp t)
       -- an error with comments following so we never color comments in wrong
       -- color. The error has an extra token, the Special '!' token to
       -- indicate that it contains an error
    | PError { errorTok    :: t
             , marker      :: t
             , commentList :: [t] -- ^ An wrapper for errors
             }
      -- rhs that begins with Equal
    | RHS (PAtom t) (Exp t) -- ^ Righthandside of functions with =
    | Opt (Maybe (Exp t)) -- ^ An optional
    | Modid t [t] -- ^ Module identifier
    | Context (Exp t) (Exp t) (PAtom t) -- ^
    | PGuard [PGuard t] -- ^ Righthandside of functions with |
      -- the PAtom in PGuard' does not contain any comments
    | PGuard' (PAtom t) (Exp t) (PAtom t)
      -- type constructor is just a wrapper to indicate which highlightning to
      -- use.
    | TC (Exp t) -- ^ Type constructor
      -- data constructor same as with the TC constructor
    | DC (Exp t) -- ^ Data constructor
    | PLet (PAtom t) (Exp t) (Exp t) -- ^ let expression
    | PIn t [Exp t]
  deriving (Show, Foldable)

instance IsTree Exp where
   emptyNode = Expr []
   uniplate tree = case tree of
       (ProgMod a b)     -> ([a,b], \[a,b] -> ProgMod a b)
       (Body x exp exp') -> ([x, exp, exp'], \[x, exp, exp'] -> Body x exp exp')
       (PModule x (Just e)) -> ([e],\[e] -> PModule x (Just e))
       (Paren l g r)  -> -- TODO: improve
         (l:g ++ [r], \(l:gr) -> Paren l (init gr) (last gr))
       (RHS l g)      -> ([l,g],\[l,g] -> (RHS l g))
       (Block s)      -> (s,Block)
       (PLet l s i)   -> ([l,s,i],\[l,s,i] -> PLet l s i)
       (PIn x ts)     -> (ts,PIn x)
       (Expr a)       -> (a,Expr)
       (PClass a b c) -> ([a,b,c],\[a,b,c] -> PClass a b c)
       (PWhere a b c) -> ([a,b,c],\[a,b,c] -> PWhere a b c)
       (Opt (Just x)) -> ([x],\[x] -> (Opt (Just x)))
       (Bin a b) -> ([a,b],\[a,b] -> (Bin a b))
       (PType a b c d) -> ([a,b,c,d],\[a,b,c,d] -> PType a b c d)
       (PData a b c d) -> ([a,b,c,d],\[a,b,c,d] -> PData a b c d)
       (PData' a b) -> ([a,b] ,\[a,b] -> PData' a b)
       (Context a b c) -> ([a,b,c],\[a,b,c] -> Context a b c)
       (PGuard xs) -> (xs,PGuard)
       (PGuard' a b c) -> ([a,b,c],\[a,b,c] -> PGuard' a b c)
       (TC e) -> ([e],\[e] -> TC e)
       (DC e) -> ([e],\[e] -> DC e)
       PModuleDecl a b c d -> ([a,b,c,d],\[a,b,c,d] -> PModuleDecl a b c d)
       PImport a b c d e -> ([a,b,c,d,e],\[a,b,c,d,e] -> PImport a b c d e)
       t              -> ([],const t)

-- | The parser
parse :: P TT (Tree TT)
parse = pModule <* eof

-- | @pModule@ parse a module
pModule :: Parser TT (PModule TT)
pModule = PModule <$> pComments <*> optional
           (pBlockOf' (ProgMod <$> pModuleDecl
                       <*> pModBody <|> pBody))

-- | Parse a body that follows a module
pModBody :: Parser TT (PModule TT)
pModBody = (exact [startBlock] *>
            (Body <$> pImports
             <*> ((pTestTok elems *> pBod)
                  <|> pEmptyBL) <* exact [endBlock]
             <*> pBod
            <|> Body <$> noImports
             <*> ((pBod <|> pEmptyBL) <* exact [endBlock])
             <*> pBod))
       <|> (exact [nextLine] *> pBody)
       <|> Body <$> pure emptyNode <*> pEmptyBL <*> pEmptyBL
    where pBod  = Block <$> pBlocks pTopDecl
          elems = [Special ';', nextLine, startBlock]

-- | @pEmptyBL@ A parser returning an empty block
pEmptyBL :: Parser TT (Exp TT)
pEmptyBL = Block <$> pEmpty

-- | Parse a body of a program
pBody :: Parser TT (PModule TT)
pBody = Body <$> noImports <*> (Block <$> pBlocks pTopDecl) <*> pEmptyBL
    <|> Body <$> pImports <*> ((pTestTok elems *> (Block <$> pBlocks pTopDecl))
                               <|> pEmptyBL) <*> pEmptyBL
    where elems = [nextLine, startBlock]

noImports :: Parser TT (Exp TT)
noImports = notNext [Reserved Import] *> pure emptyNode
    where notNext f = testNext $ uncurry (||) . (&&&) isNothing
                      (flip notElem f . tokT . fromJust)

-- Helper functions for parsing follows
-- | Parse Variables
pVarId :: Parser TT (Exp TT)
pVarId = pAtom [VarIdent, Reserved Other, Reserved As]

-- | Parse VarIdent and ConsIdent
pQvarid :: Parser TT (Exp TT)
pQvarid = pAtom [VarIdent, ConsIdent, Reserved Other, Reserved As]

-- | Parse an operator using please
pQvarsym :: Parser TT (Exp TT)
pQvarsym = pParen ((:) <$> please (PAtom <$> sym isOperator <*> pComments)
                   <*> pEmpty)

-- | Parse any operator
isOperator :: Token -> Bool
isOperator (Operator _)     = True
isOperator (ReservedOp _)   = True
isOperator (ConsOperator _) = True
isOperator _                = False

-- | Parse a consident
pQtycon :: Parser TT (Exp TT)
pQtycon = pAtom [ConsIdent]

-- | Parse many variables
pVars :: Parser TT (Exp TT)
pVars = pMany pVarId

-- | Parse a nextline token (the nexLine token is inserted by Layout.hs)
nextLine :: Token
nextLine = Special '.'

-- | Parse a startBlock token
startBlock :: Token
startBlock = Special '<'

-- | Parse a endBlock token
endBlock :: Token
endBlock = Special '>'

pEmpty :: Applicative f =>  f [a]
pEmpty = pure []

pToList :: Applicative f =>  f a -> f [a]
pToList = (box <$>)
    where box x = [x]

-- | @sym f@ returns a parser parsing @f@ as a special symbol
sym :: (Token -> Bool) -> Parser TT TT
sym f = symbol (f . tokT)

-- | @exact tokList@ parse anything that is in @tokList@
exact :: [Token] -> Parser TT TT
exact = sym . flip elem


-- | @please p@ returns a parser parsing either @p@ or recovers with the
-- (Special '!') token.
please :: Parser TT (Exp TT) -> Parser TT (Exp TT)
please = (<|>) (PError <$> recoverWith errTok
                <*> errTok
                <*> pEmpty)

-- | Parse anything, as errors
pErr :: Parser TT (Exp TT)
pErr = PError <$> recoverWith (sym $ not . uncurry (||) . (&&&) isComment
                               (== CppDirective))
   <*> errTok
   <*> pComments

-- | Parse an ConsIdent
ppCons :: Parser TT (Exp TT)
ppCons = ppAtom [ConsIdent]

-- | Parse a keyword
pKW :: [Token] -> Parser TT (Exp TT) -> Parser TT (Exp TT)
pKW k r = Bin <$> pAtom k <*> r

-- | Parse an unary operator with and without using please
pOP :: [Token] -> Parser TT (Exp TT) -> Parser TT (Exp TT)
pOP op r = Bin <$> pAtom op <*> r

--ppOP op r = Bin <$> ppAtom op <*> r

-- | Parse comments
pComments :: Parser TT [TT]
pComments = many $ sym $ uncurry (||) . (&&&) isComment (== CppDirective)

-- | Parse something thats optional
pOpt :: Parser TT (Exp TT) -> Parser TT (Exp TT)
pOpt x = Opt <$> optional x

-- | Parse an atom with, and without using please
pAtom, ppAtom :: [Token] -> Parser TT (Exp TT)
pAtom = flip pCAtom pComments

ppAtom at =  pAtom at <|> recoverAtom

recoverAtom :: Parser TT (Exp TT)
recoverAtom = PAtom <$> recoverWith errTok <*> pEmpty

-- | Parse an atom with optional comments
pCAtom :: [Token] -> Parser TT [TT] -> Parser TT (Exp TT)
pCAtom r c = PAtom <$> exact r <*> c

pBareAtom a = pCAtom a pEmpty

-- | @pSepBy p sep@ parse /zero/ or more occurences of @p@, separated
-- by @sep@, with optional ending @sep@,
-- this is quite similar to the sepBy function provided in
-- Parsec, but this one allows an optional extra separator at the end.
--
-- > commaSep p = p `pSepBy` (symbol (==(Special ',')))

pSepBy :: Parser TT (Exp TT) -> Parser TT (Exp TT) -> Parser TT [Exp TT]
pSepBy p sep = pEmpty
           <|> (:) <$> p <*> (pSepBy1 p sep <|> pEmpty)
           <|> pToList sep -- optional ending separator
    where pSepBy1 r p' = (:) <$> p' <*> (pEmpty <|> pSepBy1 p' r)

-- | Separate a list of things separated with comma inside of parenthesis
pParenSep :: Parser TT (Exp TT) -> Parser TT (Exp TT)
pParenSep = pParen . flip pSepBy pComma

-- | Parse a comma separator
pComma :: Parser TT (Exp TT)
pComma = pAtom [Special ',']

-- End of helper functions Parsing different parts follows

-- | Parse a Module declaration
pModuleDecl :: Parser TT (PModuleDecl TT)
pModuleDecl = PModuleDecl <$> pAtom [Reserved Module]
          <*> ppAtom [ConsIdent]
          <*> pOpt (pParenSep pExport)
          <*> (optional (exact [nextLine]) *>
               (Bin <$> ppAtom [Reserved Where])
               <*> pMany pErr) <* pTestTok elems
    where elems = [nextLine, startBlock, endBlock]

pExport :: Parser TT (Exp TT)
pExport = optional (exact [nextLine]) *> please
        ( pVarId
          <|> pEModule
          <|> Bin <$> pQvarsym <*> (DC <$> pOpt expSpec) -- typeOperator
          <|> Bin <$> (TC <$> pQtycon) <*> (DC <$> pOpt expSpec)
        )
        where expSpec = pParen (pToList (please (pAtom [ReservedOp DoubleDot]))
                                <|> pSepBy pQvarid pComma)

-- | Check if next token is in given list
pTestTok :: [Token] -> Parser TT ()
pTestTok f = testNext (uncurry (||) . (&&&) isNothing
                       (flip elem f . tokT . fromJust))

-- | Parse several imports
pImports :: Parser TT (Exp TT) -- [PImport TT]
pImports = Expr <$> many (pImport
                 <* pTestTok pEol
                 <* optional (some $ exact [nextLine, Special ';']))
        where pEol = [Special ';', nextLine, endBlock]

-- | Parse one import
pImport :: Parser TT (PImport TT)
pImport = PImport  <$> pAtom [Reserved Import]
      <*> pOpt (pAtom [Reserved Qualified])
      <*> ppAtom [ConsIdent]
      <*> pOpt (pKW [Reserved As] ppCons)
      <*> (TC <$> pImpSpec)
        where pImpSpec = Bin <$> pKW [Reserved Hiding]
                                  (please pImpS) <*> pMany pErr
                     <|> Bin <$> pImpS <*> pMany pErr
                     <|> pMany pErr
              pImpS    = DC <$> pParenSep pExp'
              pExp'    = Bin
                     <$> (PAtom <$> sym
                          (uncurry (||) . (&&&)
                           (`elem` [VarIdent, ConsIdent])
                           isOperator) <*> pComments
                          <|>  pQvarsym)
                     <*> pOpt pImpS

-- | Parse simple type synonyms
pType :: Parser TT (Exp TT)
pType = PType <$> (Bin <$> pAtom [Reserved Type]
                   <*> pOpt (pAtom [Reserved Instance]))
     <*> (TC . Expr <$> pTypeExpr')
     <*> ppAtom [ReservedOp Equal]
     <*> (TC . Expr <$> pTypeExpr')

-- | Parse data declarations
pData :: Parser TT (Exp TT)
pData = PData <$> pAtom [Reserved Data, Reserved NewType]
     <*> (TC . Expr <$> pTypeExpr')
     <*> pOpt (pDataRHS <|> pGadt)
     <*> pOpt pDeriving


pGadt :: Parser TT (Exp TT)
pGadt = pWhere pTypeDecl

-- | Parse second half of the data declaration, if there is one
pDataRHS :: Parser TT (Exp TT)
pDataRHS = PData' <$> pAtom [ReservedOp Equal]  <*> pConstrs


-- | Parse a deriving
pDeriving :: Parser TT (Exp TT)
pDeriving = pKW [Reserved Deriving] (TC . Expr <$> pTypeExpr')

pAtype :: Parser TT (Exp TT)
pAtype = pAtype'
     <|> pErr

pAtype' :: Parser TT (Exp TT)
pAtype' = pTypeCons
      <|> pParen (many $ pExprElem [])
      <|> pBrack (many $ pExprElem [])

pTypeCons :: Parser TT (Exp TT)
pTypeCons = Bin <$> pAtom [ConsIdent]
            <*> please (pMany $ pAtom [VarIdent, ConsIdent])

pContext :: Parser TT (Exp TT)
pContext = Context <$> pOpt pForAll
       <*> (TC <$> (pClass' <|> pParenSep pClass'))
       <*> ppAtom [ReservedOp DoubleRightArrow]
        where pClass' :: Parser TT (Exp TT)
              pClass' = Bin <$> pQtycon
                   <*> (please pVarId
                        <|> pParen ((:) <$> please pVarId
                                    <*> many pAtype'))

-- | Parse for all
pForAll :: Parser TT (Exp TT)
pForAll = pKW [Reserved Forall]
          (Bin <$> pVars <*> ppAtom [Operator "."])

pConstrs :: Parser TT (Exp TT)
pConstrs = Bin <$> (Bin <$> pOpt pContext <*> pConstr)
       <*> pMany (pOP [ReservedOp Pipe]
                  (Bin <$> pOpt pContext <*> please pConstr))

pConstr :: Parser TT (Exp TT)
pConstr = Bin <$> pOpt pForAll
      <*> (Bin <$>
           (Bin <$> (DC <$> pAtype) <*>
            (TC <$> pMany (strictF pAtype))) <*> pOpt st)
      <|> Bin <$> lrHs <*> pMany (strictF pAtype)
      <|> pErr
    where lrHs = pOP [Operator "!"] pAtype
          st = pEBrace (pTypeDecl `sepBy1` pBareAtom [Special ','])
          -- named fields declarations

-- | Parse optional strict variables
strictF :: Parser TT (Exp TT) -> Parser TT (Exp TT)
strictF a = Bin <$> pOpt (pAtom [Operator "!"]) <*> a


-- | Exporting module
pEModule ::Parser TT (Exp TT)
pEModule = pKW [Reserved Module]
         $ please (Modid <$> exact [ConsIdent] <*> pComments)

-- | Parse a Let expression
pLet :: Parser TT (Exp TT)
pLet = PLet <$> pAtom [Reserved Let]
   <*> pBlock pFunDecl
   <*> pOpt (pBareAtom [Reserved In])

-- | Parse a Do block
pDo :: Parser TT (Exp TT)
pDo = Bin <$> pAtom [Reserved Do]
          <*> pBlock (pExpr ((Special ';' : recognizedSometimes)
                             \\ [ReservedOp LeftArrow]))

-- | Parse part of a lambda binding.
pLambda :: Parser TT (Exp TT)
pLambda = Bin <$> pAtom [ReservedOp BackSlash]
          <*> (Bin <$> (Expr <$> pPattern)
               <*> please (pBareAtom [ReservedOp RightArrow]))

-- | Parse an Of block
pOf :: Parser TT (Exp TT)
pOf = Bin <$> pAtom [Reserved Of]
          <*> pBlock pAlternative

pAlternative = Bin <$> (Expr <$> pPattern)
                   <*> please (pFunRHS (ReservedOp RightArrow))

-- | Parse classes and instances
-- This is very imprecise, but shall suffice for now.
-- At least is does not complain too often.
pClass :: Parser TT (Exp TT)
pClass = PClass <$> pAtom [Reserved Class, Reserved Instance]
                <*> (TC . Expr <$>  pTypeExpr')
                <*> pOpt (please (pWhere pTopDecl))
                -- use topDecl since we have associated types and such.


-- | Parse some guards and a where clause
pGuard :: Token -> Parser TT (Exp TT)
pGuard equalSign = PGuard
     <$> some (PGuard' <$> pCAtom [ReservedOp Pipe] pEmpty <*>
               -- comments are by default parsed after this
               pExpr (recognizedSometimes
                      -- these two symbols can appear in guards.
                      \\ [ReservedOp LeftArrow, Special ','])
               <*> please (pEq equalSign))
               -- this must be -> if used in case

-- | Right-hand-side of a function or case equation (after the pattern)
pFunRHS :: Token -> Parser TT (Exp TT)
pFunRHS equalSign =
  Bin <$> (pGuard equalSign <|> pEq equalSign) <*> pOpt (pWhere pFunDecl)

pWhere :: Parser TT (Exp TT) -> Parser TT (Exp TT)
pWhere p =
  PWhere <$> pAtom [Reserved Where] <*> please (pBlock p) <*> pMany pErr
-- After a where there might "misaligned" code that do not "belong" to anything.
-- Here we swallow it as errors.

-- Note that this can both parse an equation and a type declaration.
-- Since they can start with the same token, the left part is factored here.
pDecl :: Bool -> Bool -> Parser TT (Exp TT)
pDecl acceptType acceptEqu =
  Expr <$> ((Yuck $
               Enter "missing end of type or equation declaration" $ pure [])
            <|> ((:) <$> pElem False recognizedSometimes
                 <*> pToList (pDecl acceptType acceptEqu))
            <|> ((:) <$> pBareAtom [Special ',']
                 <*> pToList (pDecl acceptType False))
                 -- if a comma is found, then the rest must be a type
                 -- declaration.
            <|> (if acceptType then pTypeEnding else empty)
            <|> (if acceptEqu  then pEquEnding else empty))
    where pTypeEnding = (:) <$> (TS <$> exact [ReservedOp DoubleColon]
                                 <*> pTypeExpr') <*> pure []
          pEquEnding =  (:) <$> pFunRHS (ReservedOp Equal) <*> pure []

pFunDecl = pDecl True True
pTypeDecl = pDecl True False
--pEquation = pDecl False True


-- | The RHS of an equation.
pEq :: Token -> Parser TT (Exp TT)
pEq equalSign = RHS <$> pBareAtom [equalSign] <*> pExpr'

-- | Parse many of something
pMany :: Parser TT (Exp TT) -> Parser TT (Exp TT)
pMany p = Expr <$> many p

-- | Parse a some of something separated by the token (Special '.')
pBlocks :: Parser TT r -> Parser TT [r]
pBlocks p =  p `sepBy1` exact [nextLine]

-- | Parse a some of something separated by the token (Special '.'), or nothing
--pBlocks' :: Parser TT r -> Parser TT (BL.BList r)
pBlocks' p =  pBlocks p <|> pure []

-- | Parse a block of some something separated by the tok (Special '.')
pBlockOf :: Parser TT (Exp TT) -> Parser TT (Exp TT)
pBlockOf p  = Block <$> pBlockOf' (pBlocks p) -- see HACK above


pBlock :: Parser TT (Exp TT) -> Parser TT (Exp TT)
pBlock p = pBlockOf' (Block <$> pBlocks' p)
       <|> pEBrace (p `sepBy1` exact [Special ';'] <|> pure [])
       <|> (Yuck $ Enter "block expected" pEmptyBL)

-- | Parse something surrounded by (Special '<') and (Special '>')
pBlockOf' :: Parser TT a -> Parser TT a
pBlockOf' p = exact [startBlock] *> p <* exact [endBlock] -- see HACK above
-- note that, by construction, '<' and '>' will always be matched, so
-- we don't try to recover errors with them.

-- | Parse something that can contain a data, type declaration or a class
pTopDecl :: Parser TT (Exp TT)
pTopDecl =    pFunDecl
          <|> pType
          <|> pData
          <|> pClass
          <|> pure emptyNode


-- | A "normal" expression, where none of the following symbols are acceptable.
pExpr' = pExpr recognizedSometimes

recognizedSometimes = [ReservedOp DoubleDot,
                       Special ',',
                       ReservedOp Pipe,
                       ReservedOp Equal,
                       ReservedOp LeftArrow,
                       ReservedOp RightArrow,
                       ReservedOp DoubleRightArrow,
                       ReservedOp BackSlash,
                       ReservedOp DoubleColon
                      ]

-- | Parse an expression, as a concatenation of elements.
pExpr :: [Token] -> Parser TT (Exp TT)
pExpr at = Expr <$> pExprOrPattern True at

-- | Parse an expression, as a concatenation of elements.
pExprOrPattern :: Bool -> [Token] -> Parser TT [Exp TT]
pExprOrPattern isExpresssion at =
  pure []
  <|> ((:) <$> pElem isExpresssion at          <*> pExprOrPattern True at)
  <|> ((:) <$> (TS <$> exact [ReservedOp DoubleColon] <*> pTypeExpr')
       <*> pure [])
     -- TODO: not really correct: in (x :: X , y :: Z), all after the
     -- first :: will be a "type".

pPattern = pExprOrPattern False recognizedSometimes

pExprElem = pElem True

-- | Parse an "element" of an expression or a pattern.
-- "at" is a list of symbols that, if found, should be considered errors.
pElem :: Bool -> [Token] -> Parser TT (Exp TT)
pElem isExpresssion at =
  pCParen (pExprOrPattern isExpresssion
           -- might be a tuple, so accept commas as noise
           (recognizedSometimes \\ [Special ','])) pEmpty
  <|> pCBrack (pExprOrPattern isExpresssion
               (recognizedSometimes \\ [ ReservedOp DoubleDot, ReservedOp Pipe
                                       , ReservedOp LeftArrow
                                       , Special ','])) pEmpty -- list thing
  <|> pCBrace (many $ pElem isExpresssion
               -- record: TODO: improve
               (recognizedSometimes \\ [ ReservedOp Equal, Special ','
                                       , ReservedOp Pipe])) pEmpty
  <|> (Yuck $ Enter "incorrectly placed block" $
        -- no error token, but the previous keyword will be one. (of, where, ...)
         pBlockOf (pExpr recognizedSometimes))
  <|> (PError <$> recoverWith
       (sym $ flip elem $ isNoiseErr at) <*> errTok <*> pEmpty)
  <|> (PAtom <$> sym (`notElem` isNotNoise at) <*> pEmpty)
  <|> if isExpresssion then pLet <|> pDo <|> pOf <|> pLambda else empty
  -- TODO: support type expressions

pTypeExpr at = many (pTypeElem at)
pTypeExpr' = pTypeExpr (recognizedSometimes \\ [ReservedOp RightArrow,
                                                ReservedOp DoubleRightArrow])

pTypeElem :: [Token] -> Parser TT (Exp TT)
pTypeElem at
    = pCParen (pTypeExpr (recognizedSometimes
                          \\ [ ReservedOp RightArrow,
                              ReservedOp DoubleRightArrow,
                              -- might be a tuple, so accept commas as noise
                              Special ','])) pEmpty
  <|> pCBrack pTypeExpr' pEmpty
  <|> pCBrace pTypeExpr' pEmpty -- TODO: this is an error: mark as such.
  <|> (Yuck $ Enter "incorrectly placed block" $
         pBlockOf (pExpr recognizedSometimes))
  <|> (PError <$> recoverWith
       (sym $ flip elem $ isNoiseErr at) <*> errTok <*> pEmpty)
  <|> (PAtom <$> sym (`notElem` isNotNoise at) <*> pEmpty)

-- | List of things that always should be parsed as errors
isNoiseErr :: [Token] -> [Token]
isNoiseErr r = recoverableSymbols ++ r

recoverableSymbols = recognizedSymbols \\ fmap Special "([{<>."
-- We just don't recover opening symbols (only closing are "fixed").
-- Layout symbols "<>." are never recovered, because layout is
-- constructed correctly.

-- | List of things that should not be parsed as noise
isNotNoise :: [Token] -> [Token]
isNotNoise r = recognizedSymbols ++ r

-- | These symbols are always properly recognized, and therefore they
-- should never be accepted as "noise" inside expressions.
recognizedSymbols =
    [ Reserved Let
    , Reserved In
    , Reserved Do
    , Reserved Of
    , Reserved Class
    , Reserved Instance
    , Reserved Deriving
    , Reserved Module
    , Reserved Import
    , Reserved Type
    , Reserved Data
    , Reserved NewType
    , Reserved Where] ++ fmap Special "()[]{}<>."

-- | Parse parenthesis, brackets and braces containing
-- an expression followed by possible comments
pCParen, pCBrace, pCBrack
       :: Parser TT [Exp TT] -> Parser TT [TT] -> Parser TT (Exp TT)

pCParen p c = Paren <$> pCAtom [Special '('] c
          <*> p <*> (recoverAtom <|> pCAtom [Special ')'] c)

pCBrace p c = Paren  <$> pCAtom [Special '{'] c
          <*> p <*> (recoverAtom <|> pCAtom [Special '}'] c)

pCBrack p c = Paren  <$>  pCAtom [Special '['] c
          <*> p <*> (recoverAtom <|> pCAtom [Special ']'] c)

pParen, pBrack :: Parser TT [Exp TT] -> Parser TT (Exp TT)

pParen = flip pCParen pComments

--pBrace = flip pCBrace pComments

pBrack = flip pCBrack pComments

-- pEBrace parse an opening brace, followed by zero comments
-- then followed by an closing brace and some comments
pEBrace p = Paren  <$> pCAtom [Special '{'] pEmpty
        <*> p <*> (recoverAtom <|> pCAtom [Special '}'] pComments)

-- | Create a special error token. (e.g. fill in where there is no
-- correct token to parse) Note that the position of the token has to
-- be correct for correct computation of node spans.
errTok = mkTok <$> curPos
   where curPos = tB <$> lookNext
         tB Nothing = maxBound
         tB (Just x) = tokBegin x
         mkTok p = Tok (Special '!') 0 (startPosn {posnOfs = p})
