packages feed

idris-0.9.9.2: src/Idris/Parser.hs

{-# LANGUAGE GeneralizedNewtypeDeriving, ConstraintKinds, PatternGuards #-}
module Idris.Parser where

import Prelude hiding (pi)

import Text.Trifecta.Delta
import Text.Trifecta hiding (span, token, whiteSpace, stringLiteral, charLiteral, natural, symbol, char, string)
import Text.Parser.LookAhead
import Text.Parser.Expression
import qualified Text.Parser.Token as Tok
import qualified Text.Parser.Char as Chr
import qualified Text.Parser.Token.Highlight as Hi

import Idris.AbsSyntax
import Idris.DSL
import Idris.Imports
import Idris.Error
import Idris.ElabDecls
import Idris.ElabTerm hiding (namespace, params)
import Idris.Coverage
import Idris.IBC
import Idris.Unlit
import Idris.Providers
import Paths_idris

import Util.DynamicLinker

import Core.TT
import Core.Evaluate

import Control.Applicative
import Control.Monad
import Control.Monad.State.Strict

import Data.Maybe
import qualified Data.List.Split as Spl
import Data.List
import Data.Monoid
import Data.Char
import qualified Data.HashSet as HS
import qualified Data.Text as T
import qualified Data.ByteString.UTF8 as UTF8

import Debug.Trace

import System.FilePath
{-
 grammar shortcut notation:
    ~CHARSEQ = complement of char sequence (i.e. any character except CHARSEQ)
    RULE? = optional rule (i.e. RULE or nothing)
    RULE* = repeated rule (i.e. RULE zero or more times)
    RULE+ = repeated rule with at least one match (i.e. RULE one or more times)
    RULE! = invalid rule (i.e. rule that is not valid in context, report meaningful error in case)
    RULE{n} = rule repeated n times
-}


-- | Idris parser with state used during parsing
type IdrisParser = StateT IState Parser

-- | Generalized monadic parsing constraint type
type MonadicParsing m = (DeltaParsing m, LookAheadParsing m, TokenParsing m, Monad m)

{- * Space, comments and literals (token/lexing like parsers) -}
-- | Parses a token by applying parser and then consuming all following whiteSpace
lexeme :: MonadicParsing m => m a -> m a
lexeme p = p <* whiteSpace

-- | Consumes any simple whitespace (any character which satisfies Char.isSpace)
simpleWhiteSpace :: MonadicParsing m => m ()
simpleWhiteSpace = satisfy isSpace *> pure ()

-- | Checks if a charcter is end of line
isEol :: Char -> Bool
isEol '\n' = True
isEol '\0' = True -- Check eof too
isEol  _   = False

-- | Checks if a character is a documentation comment marker
isDocCommentMarker :: Char -> Bool
isDocCommentMarker '|' = True
isDocCommentMarker '^' = True
isDocCommentMarker   _  = False

{- | Consumes a single-line comment
     SingleLineComment_t ::= '--' EOL_t
                        |     '--' ~DocCommentMarker_t ~EOL_t* EOL_t
                        ;
 -}
singleLineComment :: MonadicParsing m => m ()
singleLineComment =     try (string "--" *> satisfy isEol *> pure ())
                    <|> try (string "--" *> satisfy (not . isDocCommentMarker) *> many (satisfy (not . isEol)) *> (satisfy isEol <?> "end of line") *> pure ())
                    <?> "single-line comment"

{- | Consumes a multi-line comment
  MultiLineComment_t ::=
     '{ -- }'
   | '{ -' ~DocCommentMarker_t InCommentChars_t
  ;

  InCommentChars_t ::=
   '- }'
   | MultiLineComment_t InCommentChars_t
   | ~'- }'+ InCommentChars_t
  ;
 -}

multiLineComment :: MonadicParsing m => m ()
multiLineComment =     try (string "{-" *> (string "-}") *> pure ())
                   <|> try (string "{-" *> satisfy (not . isDocCommentMarker) *> inCommentChars)
                   <?> "multi-line comment"
  where inCommentChars :: MonadicParsing m => m ()
        inCommentChars =     try (string "-}" *> pure ())
                         <|> try (multiLineComment *> inCommentChars)
                         <|> try (docComment '|' *> inCommentChars)
                         <|> try (docComment '^' *> inCommentChars)
                         <|> try (skipSome (noneOf startEnd) *> inCommentChars)
                         <|> oneOf startEnd *> inCommentChars
                         <?> "end of comment"
        startEnd :: String
        startEnd = "{}-"

{-| Parses a documentation comment (similar to haddoc) given a marker character
  DocComment_t ::=   '--' DocCommentMarker_t ~EOL_t* EOL_t
                  | '{ -' DocCommentMarket_t ~'- }'* '- }'
                 ;
 -}
docComment :: MonadicParsing m => Char -> m String
docComment marker | isDocCommentMarker marker = do dc <- docComment' marker; return (T.unpack $ T.strip $ T.pack dc)
                       | otherwise            = fail "internal error: tried to parse a documentation comment with invalid marker"
  where docComment' :: MonadicParsing m => Char -> m String
        docComment' marker  =     string "--" *> char marker *> many (satisfy (not . isEol)) <* satisfy isEol
                              <|> string "{-" *> char marker *> (manyTill anyChar (try (string "-}")) <?> "end of comment")
                              <?> "documentation comment"

-- | Consumes whitespace (and comments)
whiteSpace :: MonadicParsing m => m ()
whiteSpace = many (simpleWhiteSpace <|> singleLineComment <|> multiLineComment) *> pure ()

-- | Parses a string literal
stringLiteral :: MonadicParsing m => m String
stringLiteral = lexeme $ Tok.stringLiteral

-- | Parses a char literal
charLiteral :: MonadicParsing m => m Char
charLiteral = lexeme $ Tok.charLiteral

-- | Parses a natural number
natural :: MonadicParsing m => m Integer
natural = lexeme $ Tok.natural

-- | Parses an integral number
integer :: MonadicParsing m => m Integer
integer = lexeme $ Tok.integer

-- | Parses a floating point number
float :: MonadicParsing m => m Double
float = lexeme $ Tok.double

{- * Symbols, identifiers, names and operators -}


-- | Idris Style for parsing identifiers/reserved keywords
idrisStyle :: MonadicParsing m => IdentifierStyle m
idrisStyle = IdentifierStyle _styleName _styleStart _styleLetter _styleReserved Hi.Identifier Hi.ReservedIdentifier
  where _styleName = "Idris"
        _styleStart = satisfy isAlpha
        _styleLetter = satisfy isAlphaNum <|> oneOf "_'" <|> (lchar '.')
        _styleReserved = HS.fromList ["let", "in", "data", "codata", "record", "Type",
                                      "do", "dsl", "import", "impossible",
                                      "case", "of", "total", "partial", "mutual",
                                      "infix", "infixl", "infixr", "rewrite",
                                      "where", "with", "syntax", "proof", "postulate",
                                      "using", "namespace", "class", "instance",
                                      "public", "private", "abstract", "implicit",
                                      "quoteGoal",
                                      "Int", "Integer", "Float", "Char", "String", "Ptr",
                                      "Bits8", "Bits16", "Bits32", "Bits64",
                                      "Bits8x16", "Bits16x8", "Bits32x4", "Bits64x2"]

char :: MonadicParsing m => Char -> m Char
char = Chr.char

string :: MonadicParsing m => String -> m String
string = Chr.string

-- | Parses a character as a lexeme
lchar :: MonadicParsing m => Char -> m Char
lchar = lexeme . char

-- | Parses string as a lexeme
symbol :: MonadicParsing m => String -> m String
symbol = lexeme . Tok.symbol

-- | Parses a reserved identifier
reserved :: MonadicParsing m => String -> m ()
reserved = lexeme . Tok.reserve idrisStyle

-- Taken from Parsec (c) Daan Leijen 1999-2001, (c) Paolo Martini 2007
-- | Parses a reserved operator
reservedOp :: MonadicParsing m => String -> m ()
reservedOp name = lexeme $ try $
  do string name
     notFollowedBy (operatorLetter) <?> ("end of " ++ show name)

-- | Parses an identifier as a lexeme
identifier :: MonadicParsing m => m String
identifier = lexeme $ Tok.ident idrisStyle

-- | Parses an identifier with possible namespace as a name
iName :: MonadicParsing m => [String] -> m Name
iName bad = maybeWithNS identifier False bad <?> "name"

-- | Parses an string possibly prefixed by a namespace
maybeWithNS :: MonadicParsing m => m String -> Bool -> [String] -> m Name
maybeWithNS parser ascend bad = do
  i <- option "" (lookAhead identifier)
  when (i `elem` bad) $ unexpected "reserved identifier"
  let transf = if ascend then id else reverse
  (x, xs) <- choice (transf (parserNoNS parser : parsersNS parser i))
  return $ mkName (x, xs)
  where parserNoNS :: MonadicParsing m => m String -> m (String, String)
        parserNoNS parser = do x <- parser; return (x, "")
        parserNS   :: MonadicParsing m => m String -> String -> m (String, String)
        parserNS   parser ns = do xs <- string ns; lchar '.';  x <- parser; return (x, xs)
        parsersNS  :: MonadicParsing m => m String -> String -> [m (String, String)]
        parsersNS parser i = [try (parserNS parser ns) | ns <- (initsEndAt (=='.') i)]

-- | Parses a name
name :: IdrisParser Name
name = do i <- get
          iName (syntax_keywords i)
       <?> "name"


{- | List of all initial segments in ascending order of a list.  Every such
 initial segment ends right before an element satisfying the given
 condition.
-}
initsEndAt :: (a -> Bool) -> [a] -> [[a]]
initsEndAt p [] = []
initsEndAt p (x:xs) | p x = [] : x_inits_xs
                    | otherwise = x_inits_xs
  where x_inits_xs = [x : cs | cs <- initsEndAt p xs]


{- | Create a `Name' from a pair of strings representing a base name and its
 namespace.
-}
mkName :: (String, String) -> Name
mkName (n, "") = UN n
mkName (n, ns) = NS (UN n) (reverse (parseNS ns))
  where parseNS x = case span (/= '.') x of
                      (x, "")    -> [x]
                      (x, '.':y) -> x : parseNS y

operatorLetter :: MonadicParsing m => m Char
operatorLetter = oneOf ":!#$%&*+./<=>?@\\^|-~"

-- | Parses an operator
operator :: MonadicParsing m => m String
operator = lexeme . some $ operatorLetter

{- * Position helpers -}
{- | Get filename from position (returns "(interactive)" when no source file is given)  -}
fileName :: Delta -> String
fileName (Directed fn _ _ _ _) = UTF8.toString fn
fileName _                     = "(interactive)"

{- | Get line number from position -}
lineNum :: Delta -> Int
lineNum (Lines l _ _ _)      = fromIntegral l + 1
lineNum (Directed _ l _ _ _) = fromIntegral l + 1

{- | Get file position as FC -}
getFC :: MonadicParsing m => m FC
getFC = do s <- position
           let (dir, file) = splitFileName (fileName s)
           let f = if dir == addTrailingPathSeparator "." then file else fileName s
           return $ FC f (lineNum s)

{-* Syntax helpers-}
-- | Bind constraints to term
bindList :: (Name -> PTerm -> PTerm -> PTerm) -> [(Name, PTerm)] -> PTerm -> PTerm
bindList b []          sc = sc
bindList b ((n, t):bs) sc = b n t (bindList b bs sc)

{- |Creates table for fixtiy declarations to build expression parser using
  pre-build and user-defined operator/fixity declarations -}
table :: [FixDecl] -> OperatorTable IdrisParser PTerm
table fixes
   = [[prefix "-" (\fc x -> PApp fc (PRef fc (UN "-"))
        [pexp (PApp fc (PRef fc (UN "fromInteger")) [pexp (PConstant (BI 0))]), pexp x])]]
       ++ toTable (reverse fixes) ++
      [[backtick],
       [binary "="  PEq AssocLeft],
       [binary "->" (\fc x y -> PPi expl (MN 42 "__pi_arg") x y) AssocRight]]

{- |Calculates table for fixtiy declarations -}
toTable :: [FixDecl] -> OperatorTable IdrisParser PTerm
toTable fs = map (map toBin)
                 (groupBy (\ (Fix x _) (Fix y _) -> prec x == prec y) fs)
   where toBin (Fix (PrefixN _) op) = prefix op
                                       (\fc x -> PApp fc (PRef fc (UN op)) [pexp x])
         toBin (Fix f op)
            = binary op (\fc x y -> PApp fc (PRef fc (UN op)) [pexp x,pexp y]) (assoc f)
         assoc (Infixl _) = AssocLeft
         assoc (Infixr _) = AssocRight
         assoc (InfixN _) = AssocNone

{- |Binary operator -}
binary :: String -> (FC -> PTerm -> PTerm -> PTerm) -> Assoc -> Operator IdrisParser PTerm
binary name f = Infix (do fc <- getFC
                          reservedOp name
                          doc <- option "" (docComment '^')
                          return (f fc))

{- |Prefix operator -}
prefix :: String -> (FC -> PTerm -> PTerm) -> Operator IdrisParser PTerm
prefix name f = Prefix (do reservedOp name
                           fc <- getFC
                           return (f fc))

{- |Backtick operator -}
backtick :: Operator IdrisParser PTerm
backtick = Infix (do lchar '`'; n <- fnName
                     lchar '`'
                     fc <- getFC
                     return (\x y -> PApp fc (PRef fc n) [pexp x, pexp y])) AssocNone

-- | Allow implicit type declarations
allowImp :: SyntaxInfo -> SyntaxInfo
allowImp syn = syn { implicitAllowed = True }

-- | Disallow implicit type declarations
disallowImp :: SyntaxInfo -> SyntaxInfo
disallowImp syn = syn { implicitAllowed = False }

-- | Adds accessibility option for function
addAcc :: Name -> Maybe Accessibility -> IdrisParser ()
addAcc n a = do i <- get
                put (i { hide_list = (n, a) : hide_list i })

{- | Add accessbility option for data declarations
 (works for classes too - 'abstract' means the data/class is visible but members not) -}
accData :: Maybe Accessibility -> Name -> [Name] -> IdrisParser ()
accData (Just Frozen) n ns = do addAcc n (Just Frozen)
                                mapM_ (\n -> addAcc n (Just Hidden)) ns
accData a n ns = do addAcc n a
                    mapM_ (`addAcc` a) ns


{- * Error reporting helpers -}
{- | Error message with possible fixes list -}
fixErrorMsg :: String -> [String] -> String
fixErrorMsg msg fixes = msg ++ ", possible fixes:\n" ++ (concat $ intersperse "\n\nor\n\n" fixes)

{- * Layout helpers -}

-- | Push indentation to stack
pushIndent :: IdrisParser ()
pushIndent = do pos <- position
                ist <- get
                put (ist { indent_stack = (fromIntegral (column pos) + 1) : indent_stack ist })

-- | Pops indentation from stack
popIndent :: IdrisParser ()
popIndent = do ist <- get
               let (x : xs) = indent_stack ist
               put (ist { indent_stack = xs })

-- | Gets current indentation
indent :: IdrisParser Int
indent = liftM ((+1) . fromIntegral . column) position

-- | Gets last indentation
lastIndent :: IdrisParser Int
lastIndent = do ist <- get
                case indent_stack ist of
                  (x : xs) -> return x
                  _        -> return 1

-- | Applies parser in an indented position
indented :: IdrisParser a -> IdrisParser a
indented p = notEndBlock *> p <* keepTerminator

-- | Applies parser to get a block (which has possibly indented statements)
indentedBlock :: IdrisParser a -> IdrisParser [a]
indentedBlock p = do openBlock
                     pushIndent
                     res <- many (indented p)
                     popIndent
                     closeBlock
                     return res

-- | Applies parser to get a block with at least one statement (which has possibly indented statements)
indentedBlock1 :: IdrisParser a -> IdrisParser [a]
indentedBlock1 p = do openBlock
                      pushIndent
                      res <- some (indented p)
                      popIndent
                      closeBlock
                      return res

-- | Applies parser to get a block with exactly one (possibly indented) statement
indentedBlockS :: IdrisParser a -> IdrisParser a
indentedBlockS p = do openBlock
                      pushIndent
                      res <- indented p
                      popIndent
                      closeBlock
                      return res


-- | Checks if the following character matches provided parser
lookAheadMatches :: MonadicParsing m => m a -> m Bool
lookAheadMatches p = do match <- lookAhead (optional p)
                        return $ isJust match

-- | Parses a start of block
openBlock :: IdrisParser ()
openBlock =     do lchar '{'
                   ist <- get
                   put (ist { brace_stack = Nothing : brace_stack ist })
            <|> do ist <- get
                   lvl' <- indent
                    -- if we're not indented further, it's an empty block, so
                    -- increment lvl to ensure we get to the end
                   let lvl = case brace_stack ist of
                                   Just lvl_old : _ ->
                                       if lvl' <= lvl_old then lvl_old+1
                                                          else lvl'
                                   [] -> if lvl' == 1 then 2 else lvl'
                                   _ -> lvl'
                   put (ist { brace_stack = Just lvl : brace_stack ist })
            <?> "start of block"

-- | Parses an end of block
closeBlock :: IdrisParser ()
closeBlock = do ist <- get
                bs <- case brace_stack ist of
                        []  -> eof >> return []
                        Nothing : xs -> lchar '}' >> return xs <?> "end of block"
                        Just lvl : xs -> (do i   <- indent
                                             isParen <- lookAheadMatches (char ')')
                                             if i >= lvl && not isParen
                                                then fail "not end of block"
                                                else return xs)
                                          <|> (do notOpenBraces
                                                  eof
                                                  return [])
                put (ist { brace_stack = bs })

-- | Parses a terminator
terminator :: IdrisParser ()
terminator =     do lchar ';'; popIndent
             <|> do c <- indent; l <- lastIndent
                    if c <= l then popIndent else fail "not a terminator"
             <|> do isParen <- lookAheadMatches (oneOf ")}")
                    if isParen then popIndent else fail "not a termiantor"
             <|> lookAhead eof

-- | Parses and keeps a terminator
keepTerminator :: IdrisParser ()
keepTerminator =  do lchar ';'; return ()
              <|> do c <- indent; l <- lastIndent
                     unless (c <= l) $ fail "not a terminator"
              <|> do isParen <- lookAheadMatches (oneOf ")}|")
                     unless isParen $ fail "not a terminator"
              <|> lookAhead eof

-- | Checks if application expression does not end
notEndApp :: IdrisParser ()
notEndApp = do c <- indent; l <- lastIndent
               when (c <= l) (fail "terminator")

-- | Checks that it is not end of block
notEndBlock :: IdrisParser ()
notEndBlock = do ist <- get
                 case brace_stack ist of
                      Just lvl : xs -> do i <- indent
                                          isParen <- lookAheadMatches (char ')')
                                          when (i < lvl || isParen) (fail "end of block")
                      _ -> return ()

notOpenBraces :: IdrisParser ()
notOpenBraces = do ist <- get
                   when (hasNothing $ brace_stack ist) $ fail "end of input"
  where hasNothing :: [Maybe a] -> Bool
        hasNothing = any isNothing

{- * Main grammar -}

{- | Parses module definition
      ModuleHeader ::= 'module' Identifier_t ';'?;
-}
moduleHeader :: IdrisParser [String]
moduleHeader =     try (do reserved "module"
                           i <- identifier
                           option ';' (lchar ';')
                           return (moduleName i))
               <|> return []
  where moduleName x = case span (/='.') x of
                           (x, "")    -> [x]
                           (x, '.':y) -> x : moduleName y

{- | Parses an import statement
  Import ::= 'import' Identifier_t ';'?;
 -}
import_ :: IdrisParser String
import_ = do reserved "import"
             id <- identifier
             option ';' (lchar ';')
             return (toPath id)
          <?> "import statement"
  where toPath f = foldl1' (</>) (Spl.splitOn "." f)

{- | Parses program source
     Prog ::= Decl* EOF;
 -}
prog :: SyntaxInfo -> IdrisParser [PDecl]
prog syn = do whiteSpace
              decls <- many (decl syn)
              notOpenBraces
              eof
              let c = (concat decls)
              return c

{- | Parses a top-level declaration
Decl ::=
    Decl'
  | Using
  | Params
  | Mutual
  | Namespace
  | Class
  | Instance
  | DSL
  | Directive
  | Provider
  | Transform
  | Import!
  ;
-}
decl :: SyntaxInfo -> IdrisParser [PDecl]
decl syn = do notEndBlock
              declBody
  where declBody :: IdrisParser [PDecl]
        declBody =     declBody'
                   <|> using_ syn
                   <|> params syn
                   <|> mutual syn
                   <|> namespace syn
                   <|> class_ syn
                   <|> instance_ syn
                   <|> do d <- dsl syn; return [d]
                   <|> directive syn
                   <|> try(provider syn)
                   <|> transform syn
                   <|> try(do import_; fail "imports must be at top of file")
                   <?> "declaration"
        declBody' :: IdrisParser [PDecl]
        declBody' = do d <- decl' syn
                       i <- get
                       let d' = fmap (desugar syn i) d
                       return [d']

{- | Parses a top-level declaration with possible syntax sugar
Decl' ::=
    Fixity
  | FunDecl'
  | Data
  | Record
  | SyntaxDecl
  ;
-}
decl' :: SyntaxInfo -> IdrisParser PDecl
decl' syn =    try fixity
           <|> try (fnDecl' syn)
           <|> try (data_ syn)
           <|> try (record syn)
           <|> try (syntaxDecl syn)
           <?> "declaration"

{- | Parses a syntax extension declaration (and adds the rule to parser state)
  SyntaxDecl ::= SyntaxRule;
-}
syntaxDecl :: SyntaxInfo -> IdrisParser PDecl
syntaxDecl syn = do s <- syntaxRule syn
                    i <- get
                    let rs = syntax_rules i
                    let ns = syntax_keywords i
                    let ibc = ibc_write i
                    let ks = map show (names s)
                    put (i { syntax_rules = s : rs,
                             syntax_keywords = ks ++ ns,
                             ibc_write = IBCSyntax s : map IBCKeyword ks ++ ibc })
                    fc <- getFC
                    return (PSyntax fc s)
  where names (Rule syms _ _) = mapMaybe ename syms
        ename (Keyword n) = Just n
        ename _           = Nothing

{- | Parses a syntax extension declaration
SyntaxRuleOpts ::= 'term' | 'pattern';

SyntaxRule ::=
  SyntaxRuleOpts? 'syntax' SyntaxSym+ '=' TypeExpr Terminator;

SyntaxSym ::=   '[' Name_t ']'
             |  '{' Name_t '}'
             |  Name_t
             |  StringLiteral_t
             ;
-}
syntaxRule :: SyntaxInfo -> IdrisParser Syntax
syntaxRule syn
    = do pushIndent
         sty <- option AnySyntax (do reserved "term"; return TermSyntax
                                  <|> do reserved "pattern"; return PatternSyntax)
         reserved "syntax"
         syms <- some syntaxSym
         when (all isExpr syms) $ unexpected "missing keywords in syntax rule"
         let ns = mapMaybe getName syms
         when (length ns /= length (nub ns))
            $ unexpected "repeated variable in syntax rule"
         lchar '='
         tm <- typeExpr (allowImp syn)
         terminator
         return (Rule (mkSimple syms) tm sty)
  where
    isExpr (Expr _) = True
    isExpr _ = False
    getName (Expr n) = Just n
    getName _ = Nothing
    -- Can't parse two full expressions (i.e. expressions with application) in a row
    -- so change them both to a simple expression
    mkSimple (Expr e : es) = SimpleExpr e : mkSimple' es
    mkSimple xs = mkSimple' xs

    mkSimple' (Expr e : Expr e1 : es) = SimpleExpr e : SimpleExpr e1 :
                                           mkSimple es
    mkSimple' (e : es) = e : mkSimple' es
    mkSimple' [] = []


{- | Parses a syntax symbol (either binding variable, keyword or expression)
SyntaxSym ::=   '[' Name_t ']'
             |  '{' Name_t '}'
             |  Name_t
             |  StringLiteral_t
             ;
 -}
syntaxSym :: IdrisParser SSymbol
syntaxSym =    try (do lchar '['; n <- name; lchar ']'
                       return (Expr n))
            <|> try (do lchar '{'; n <- name; lchar '}'
                        return (Binding n))
            <|> do n <- iName []
                   return (Keyword n)
            <|> do sym <- stringLiteral
                   return (Symbol sym)
            <?> "syntax symbol"

{- | Parses a function declaration with possible syntax sugar
  FunDecl ::= FunDecl';
-}
fnDecl :: SyntaxInfo -> IdrisParser [PDecl]
fnDecl syn
      = try (do notEndBlock
                d <- fnDecl' syn
                i <- get
                let d' = fmap (desugar syn i) d
                return [d'])
        <?> "function declaration"

{- Parses a function declaration
 FunDecl' ::=
  DocComment_t? FnOpts* Accessibility? FnOpts* FnName TypeSig Terminator
  | Postulate
  | Pattern
  | CAF
  ;
-}
fnDecl' :: SyntaxInfo -> IdrisParser PDecl
fnDecl' syn = try (do doc <- option "" (docComment '|')
                      pushIndent
                      ist <- get
                      let initOpts = if default_total ist
                                        then [TotalFn]
                                        else []
                      opts <- fnOpts initOpts
                      acc <- optional accessibility
                      opts' <- fnOpts opts
                      n_in <- fnName
                      let n = expandNS syn n_in
                      fc <- getFC
                      ty <- typeSig (allowImp syn)
                      terminator
                      addAcc n acc
                      return (PTy doc syn fc opts' n ty))
            <|> try (postulate syn)
            <|> try (pattern syn)
            <|> try (caf syn)
            <?> "function declaration"


{- Parses function options given initial options
FnOpts ::= 'total'
  | 'partial'
  | 'implicit'
  | '%' 'assert_total'
  | '%' 'reflection'
  | '%' 'specialise' '[' NameTimesList? ']'
  ;

NameTimes ::= FnName Natural?;

NameTimesList ::=
  NameTimes
  | NameTimes ',' NameTimesList
  ;

-}
-- FIXME: Check compatability for function options (i.e. partal/total)
fnOpts :: [FnOpt] -> IdrisParser [FnOpt]
fnOpts opts
        = do reserved "total"; fnOpts (TotalFn : opts)
      <|> do reserved "partial"; fnOpts (PartialFn : (opts \\ [TotalFn]))
      <|> try (do lchar '%'; reserved "export"; c <- stringLiteral;
                  fnOpts (CExport c : opts))
      <|> try (do lchar '%'; reserved "assert_total";
                  fnOpts (AssertTotal : opts))
      <|> try (do lchar '%'; reserved "reflection";
                  fnOpts (Reflection : opts))
      <|> do lchar '%'; reserved "specialise";
             lchar '['; ns <- sepBy nameTimes (lchar ','); lchar ']'
             fnOpts (Specialise ns : opts)
      <|> do reserved "implicit"; fnOpts (Implicit : opts)
      <|> return opts
      <?> "function modifier"
  where nameTimes :: IdrisParser (Name, Maybe Int)
        nameTimes = do n <- fnName
                       t <- option Nothing (do reds <- natural
                                               return (Just (fromInteger reds)))
                       return (n, t)

{- | Parses an operator in function position i.e. enclosed by `()', with an
 optional namespace

  OperatorFront ::= (Identifier_t '.')? '(' Operator_t ')';
-}
operatorFront :: IdrisParser Name
operatorFront = maybeWithNS (lchar '(' *> operator <* lchar ')') False []

{- | Parses a function (either normal name or operator)
  FnName ::= Name | OperatorFront;
-}
fnName :: IdrisParser Name
fnName = try operatorFront <|> name <?> "function name"

{- | Parses an accessibilty modifier (e.g. public, private) -}
accessibility :: IdrisParser Accessibility
accessibility = do reserved "public";   return Public
            <|> do reserved "abstract"; return Frozen
            <|> do reserved "private";  return Hidden
            <?> "accessibility modifier"



{- | Parses a postulate

Postulate ::=
  DocComment_t? 'postulate' FnOpts* Accesibility? FnOpts* FnName TypeSig Terminator
  ;
-}
postulate :: SyntaxInfo -> IdrisParser PDecl
postulate syn = do doc <- option "" (docComment '|')
                   pushIndent
                   reserved "postulate"
                   ist <- get
                   let initOpts = if default_total ist
                                     then [TotalFn]
                                     else []
                   opts <- fnOpts initOpts
                   acc <- optional accessibility
                   opts' <- fnOpts opts
                   n_in <- fnName
                   let n = expandNS syn n_in
                   ty <- typeSig (allowImp syn)
                   fc <- getFC
                   terminator
                   addAcc n acc
                   return (PPostulate doc syn fc opts' n ty)
                 <?> "postulate"

{- | Parses a using declaration

Using ::=
  'using' '(' UsingDeclList ')' OpenBlock Decl* CloseBlock
  ;
 -}
using_ :: SyntaxInfo -> IdrisParser [PDecl]
using_ syn =
    do reserved "using"; lchar '('; ns <- usingDeclList syn; lchar ')'
       openBlock
       let uvars = using syn
       ds <- many (decl (syn { using = uvars ++ ns }))
       closeBlock
       return (concat ds)
    <?> "using declaration"

{- | Parses a parameters declaration

Params ::=
  'parameters' '(' TypeDeclList ')' OpenBlock Decl* CloseBlock
  ;
 -}
params :: SyntaxInfo -> IdrisParser [PDecl]
params syn =
    do reserved "parameters"; lchar '('; ns <- typeDeclList syn; lchar ')'
       openBlock
       let pvars = syn_params syn
       ds <- many (decl syn { syn_params = pvars ++ ns })
       closeBlock
       fc <- getFC
       return [PParams fc ns (concat ds)]
    <?> "parameters declaration"

{- | Parses a mutual declaration (for mutually recursive functions)

Mutual ::=
  'mutual' OpenBlock Decl* CloseBlock
  ;
-}
mutual :: SyntaxInfo -> IdrisParser [PDecl]
mutual syn =
    do reserved "mutual"
       openBlock
       let pvars = syn_params syn
       ds <- many (decl syn)
       closeBlock
       fc <- getFC
       return [PMutual fc (concat ds)]
    <?> "mutual block"

{- | Parses a namespace declaration

Namespace ::=
  'namespace' identifier OpenBlock Decl+ CloseBlock
  ;
-}
namespace :: SyntaxInfo -> IdrisParser [PDecl]
namespace syn =
    do reserved "namespace"; n <- identifier;
       openBlock
       ds <- some (decl syn { syn_namespace = n : syn_namespace syn })
       closeBlock
       return [PNamespace n (concat ds)]
     <?> "namespace declaration"

{- | Parses a fixity declaration

Fixity ::=
  FixityType Natural_t OperatorList Terminator
  ;
-}
fixity :: IdrisParser PDecl
fixity = do pushIndent
            f <- fixityType; i <- natural; ops <- sepBy1 operator (lchar ',')
            terminator
            let prec = fromInteger i
            istate <- get
            let infixes = idris_infixes istate
            let fs      = map (Fix (f prec)) ops
            let redecls = map (alreadyDeclared infixes) fs
            let ill     = filter (not . checkValidity) redecls
            if null ill
               then do put (istate { idris_infixes = nub $ sort (fs ++ infixes)
                                     , ibc_write     = map IBCFix fs ++ ibc_write istate
                                   })
                       fc <- getFC
                       return (PFix fc (f prec) ops)
               else fail $ concatMap (\(f, (x:xs)) -> "Illegal redeclaration of fixity:\n\t\""
                                                ++ show f ++ "\" overrides \"" ++ show x ++ "\"") ill
         <?> "fixity declaration"
             where alreadyDeclared :: [FixDecl] -> FixDecl -> (FixDecl, [FixDecl])
                   alreadyDeclared fs f = (f, filter ((extractName f ==) . extractName) fs)

                   checkValidity :: (FixDecl, [FixDecl]) -> Bool
                   checkValidity (f, fs) = all (== f) fs

                   extractName :: FixDecl -> String
                   extractName (Fix _ n) = n

{- | Parses a fixity declaration type (i.e. infix or prefix, associtavity)
FixityType ::=
  'infixl'
  | 'infixr'
  | 'infix'
  | 'prefix'
  ;
 -}
fixityType :: IdrisParser (Int -> Fixity)
fixityType = try (do reserved "infixl"; return Infixl)
         <|> try (do reserved "infixr"; return Infixr)
         <|> try (do reserved "infix";  return InfixN)
         <|> try (do reserved "prefix"; return PrefixN)
         <?> "fixity type"

{- |Parses a methods block (for type classes and instances)
  MethodsBlock ::= 'where' OpenBlock FnDecl* CloseBlock
 -}
methodsBlock :: SyntaxInfo -> IdrisParser [PDecl]
methodsBlock syn = do reserved "where"
                      openBlock
                      ds <- many (fnDecl syn)
                      closeBlock
                      return (concat ds)
                   <?> "methods block"

{- |Parses a type class declaration

ClassArgument ::=
   Name
   | '(' Name ':' Expr ')'
   ;

Class ::=
  DocComment_t? Accessibility? 'class' ConstraintList? Name ClassArgument* MethodsBlock?
  ;
-}
class_ :: SyntaxInfo -> IdrisParser [PDecl]
class_ syn = do doc <- option "" (docComment '|')
                acc <- optional accessibility
                reserved "class"; fc <- getFC; cons <- constraintList syn; n_in <- name
                let n = expandNS syn n_in
                cs <- many carg
                ds <- option [] (methodsBlock syn)
                accData acc n (concatMap declared ds)
                return [PClass doc syn fc cons n cs ds]
             <?> "type-class declaration"
  where
    carg :: IdrisParser (Name, PTerm)
    carg = do lchar '('; i <- name; lchar ':'; ty <- expr syn; lchar ')'
              return (i, ty)
       <|> do i <- name;
              return (i, PType)

{- |Parses a type class instance declaration

  Instance ::=
    'instance' InstanceName? ConstraintList? Name SimpleExpr* MethodsBlock?
    ;

  InstanceName ::= '[' Name ']';
-}
instance_ :: SyntaxInfo -> IdrisParser [PDecl]
instance_ syn = do reserved "instance"; fc <- getFC
                   en <- optional instanceName
                   cs <- constraintList syn
                   cn <- name
                   args <- many (simpleExpr syn)
                   let sc = PApp fc (PRef fc cn) (map pexp args)
                   let t = bindList (PPi constraint) (map (\x -> (MN 0 "c", x)) cs) sc
                   ds <- option [] (methodsBlock syn)
                   return [PInstance syn fc cs cn args t en ds]
                 <?> "instance declaratioN"
  where instanceName :: IdrisParser Name
        instanceName = do lchar '['; n_in <- fnName; lchar ']'
                          let n = expandNS syn n_in
                          return n
                       <?> "instance name"


{- | Parses an expression as a whole
  FullExpr ::= Expr EOF_t;
 -}
fullExpr :: SyntaxInfo -> IdrisParser PTerm
fullExpr syn = do x <- expr syn
                  eof
                  i <- get
                  return $ desugar syn i x


{- |Parses an expression
  Expr ::= Expr';
-}
expr :: SyntaxInfo -> IdrisParser PTerm
expr syn = do i <- get
              buildExpressionParser (table (idris_infixes i)) (expr' syn)

{- | Parses either an internally defined expression or
    a user-defined one

Expr' ::=  "External (User-defined) Syntax"
      |   InternalExpr;

 -}
expr' :: SyntaxInfo -> IdrisParser PTerm
expr' syn =     try (externalExpr syn)
            <|> internalExpr syn
            <?> "expression"

{- | Parses a user-defined expression -}
externalExpr :: SyntaxInfo -> IdrisParser PTerm
externalExpr syn = do i <- get
                      extensions syn (syntax_rules i)
                   <?> "user-defined expression"

{- | Parses a simple user-defined expression -}
simpleExternalExpr :: SyntaxInfo -> IdrisParser PTerm
simpleExternalExpr syn = do i <- get
                            extensions syn (filter isSimple (syntax_rules i))
  where
    isSimple (Rule (Expr x:xs) _ _) = False
    isSimple (Rule (SimpleExpr x:xs) _ _) = False
    isSimple (Rule [Keyword _] _ _) = True
    isSimple (Rule [Symbol _]  _ _) = True
    isSimple (Rule (_:xs) _ _) = case last xs of
        Keyword _ -> True
        Symbol _  -> True
        _ -> False
    isSimple _ = False

{- | Tries to parse a user-defined expression given a list of syntactic extensions -}
extensions :: SyntaxInfo -> [Syntax] -> IdrisParser PTerm
extensions syn rules = choice (map (try . extension syn) (filter isValid rules))
                       <?> "user-defined expression"
  where
    isValid :: Syntax -> Bool
    isValid (Rule _ _ AnySyntax) = True
    isValid (Rule _ _ PatternSyntax) = inPattern syn
    isValid (Rule _ _ TermSyntax) = not (inPattern syn)


data SynMatch = SynTm PTerm | SynBind Name

{- | Tries to parse an expression given a user-defined rule -}
extension :: SyntaxInfo -> Syntax -> IdrisParser PTerm
extension syn (Rule ssym ptm _)
    = do smap <- mapM extensionSymbol ssym
         let ns = mapMaybe id smap
         return (update ns ptm) -- updated with smap
  where
    extensionSymbol :: SSymbol -> IdrisParser (Maybe (Name, SynMatch))
    extensionSymbol (Keyword n)    = do reserved (show n); return Nothing
    extensionSymbol (Expr n)       = do tm <- expr syn
                                        return $ Just (n, SynTm tm)
    extensionSymbol (SimpleExpr n) = do tm <- simpleExpr syn
                                        return $ Just (n, SynTm tm)
    extensionSymbol (Binding n)    = do b <- name
                                        return $ Just (n, SynBind b)
    extensionSymbol (Symbol s)     = do symbol s
                                        return Nothing
    dropn :: Name -> [(Name, a)] -> [(Name, a)]
    dropn n [] = []
    dropn n ((x,t) : xs) | n == x = xs
                         | otherwise = (x,t):dropn n xs

    updateB :: [(Name, SynMatch)] -> Name -> Name
    updateB ns n = case lookup n ns of
                     Just (SynBind t) -> t
                     _ -> n

    update :: [(Name, SynMatch)] -> PTerm -> PTerm
    update ns (PRef fc n) = case lookup n ns of
                              Just (SynTm t) -> t
                              _ -> PRef fc n
    update ns (PLam n ty sc) = PLam (updateB ns n) (update ns ty) (update (dropn n ns) sc)
    update ns (PPi p n ty sc) = PPi p (updateB ns n) (update ns ty) (update (dropn n ns) sc)
    update ns (PLet n ty val sc) = PLet (updateB ns n) (update ns ty) (update ns val)
                                          (update (dropn n ns) sc)
    update ns (PApp fc t args) = PApp fc (update ns t) (map (fmap (update ns)) args)
    update ns (PCase fc c opts) = PCase fc (update ns c) (map (pmap (update ns)) opts)
    update ns (PPair fc l r) = PPair fc (update ns l) (update ns r)
    update ns (PDPair fc l t r) = PDPair fc (update ns l) (update ns t) (update ns r)
    update ns (PAlternative a as) = PAlternative a (map (update ns) as)
    update ns (PHidden t) = PHidden (update ns t)
    update ns (PDoBlock ds) = PDoBlock $ upd ns ds
      where upd :: [(Name, SynMatch)] -> [PDo] -> [PDo]
            upd ns (DoExp fc t : ds) = DoExp fc (update ns t) : upd ns ds
            upd ns (DoBind fc n t : ds) = DoBind fc n (update ns t) : upd (dropn n ns) ds
            upd ns (DoLet fc n ty t : ds) = DoLet fc n (update ns ty) (update ns t)
                                                : upd (dropn n ns) ds
            upd ns (DoBindP fc i t : ds) = DoBindP fc (update ns i) (update ns t)
                                                : upd ns ds
            upd ns (DoLetP fc i t : ds) = DoLetP fc (update ns i) (update ns t)
                                                : upd ns ds
    update ns (PGoal fc r n sc) = PGoal fc (update ns r) n (update ns sc)
    update ns t = t

{- |Parses a (normal) built-in expression

InternalExpr ::=
  App
  | MatchApp
  | UnifyLog
  | RecordType
  | SimpleExpr
  | Lambda
  | QuoteGoal
  | Let
  | RewriteTerm
  | Pi
  | DoBlock
  ;
-}
internalExpr :: SyntaxInfo -> IdrisParser PTerm
internalExpr syn =
         try (app syn)
     <|> try (matchApp syn)
     <|> try (unifyLog syn)
     <|> recordType syn
     <|> try (simpleExpr syn)
     <|> lambda syn
     <|> quoteGoal syn
     <|> let_ syn
     <|> rewriteTerm syn
     <|> pi syn
     <|> doBlock syn
     <?> "expression"

{- | Parses a case expression
CaseExpr ::=
  'case' Expr 'of' OpenBlock CaseOption+ CloseBlock;
-}
caseExpr :: SyntaxInfo -> IdrisParser PTerm
caseExpr syn = do reserved "case"; fc <- getFC
                  scr <- expr syn; reserved "of";
                  opts <- indentedBlock1 (caseOption syn)
                  return (PCase fc scr opts)
               <?> "case expression"

{- | Parses a case in a case expression
CaseOption ::=
  Expr '=>' Expr Terminator
  ;
-}
caseOption :: SyntaxInfo -> IdrisParser (PTerm, PTerm)
caseOption syn = do lhs <- expr (syn { inPattern = True })
                    symbol "=>"; r <- expr syn
                    return (lhs, r)
                 <?> "case option"

{- | Parses a proof block
ProofExpr ::=
  'proof' OpenBlock Tactic'* CloseBlock
  ;
-}
proofExpr :: SyntaxInfo -> IdrisParser PTerm
proofExpr syn = do reserved "proof"
                   ts <- indentedBlock (tactic syn)
                   return $ PProof ts
                <?> "proof block"

{- | Parses a tactics block
TacticsExpr :=
  'tactics' OpenBlock Tactic'* CloseBlock
;
-}
tacticsExpr :: SyntaxInfo -> IdrisParser PTerm
tacticsExpr syn = do reserved "tactics"
                     ts <- indentedBlock (tactic syn)
                     return $ PTactics ts
                  <?> "tactics block"

{- | Parses a simple expresion
SimpleExpr ::=
  '![' Term ']'
  | '?' Name
  | % 'instance'
  | 'refl' ('{' Expr '}')?
  | ProofExpr
  | TacticsExpr
  | CaseExpr
  | FnName
  | List
  | Comprehension
  | Alt
  | Idiom
  | '(' Bracketed
  | Constant
  | Type
  | '()'
  | '_|_'
  | '_'
  | {- External (User-defined) Simple Expression -}
  ;
-}
simpleExpr :: SyntaxInfo -> IdrisParser PTerm
simpleExpr syn =
        {-try (do symbol "!["; t <- term; lchar ']'; return $ PQuote t)
        <|>-} do lchar '?'; x <- name; return (PMetavar x)
        <|> do lchar '%'; fc <- getFC; reserved "instance"; return (PResolveTC fc)
        <|> do reserved "refl"; fc <- getFC;
               tm <- option Placeholder (do lchar '{'; t <- expr syn; lchar '}';
                                            return t)
               return (PRefl fc tm)
        <|> proofExpr syn
        <|> tacticsExpr syn
        <|> caseExpr syn
        <|> try (do fc <- getFC
                    x <- fnName
                    return (PRef fc x))
        <|> try (listExpr syn)
        <|> try (comprehension syn)
        <|> try (alt syn)
        <|> try (idiom syn)
        <|> try (do lchar '('
                    bracketed (disallowImp syn))
        <|> try (do c <- constant
                    fc <- getFC
                    return (modifyConst syn fc (PConstant c)))
        <|> do reserved "Type"; return PType
        <|> try (do symbol "()"
                    fc <- getFC
                    return (PTrue fc))
        <|> try (do symbol "_|_"
                    fc <- getFC
                    return (PFalse fc))
        <|> do lchar '_'; return Placeholder
        <|> simpleExternalExpr syn
        <?> "expression"


{- |Parses the rest of an expression in braces
Bracketed ::=
  | Pair
  | Expr ')'
  | Operator Expr ')'
  | Expr Operator ')'
  ;
-}
bracketed :: SyntaxInfo -> IdrisParser PTerm
bracketed syn =
            try (pair syn)
        <|> try (do e <- expr syn; lchar ')'; return e)
        <|> try (do fc <- getFC; o <- operator; e <- expr syn; lchar ')'
                    return $ PLam (MN 1000 "ARG") Placeholder
                                  (PApp fc (PRef fc (UN o)) [pexp (PRef fc (MN 1000 "ARG")),
                                                             pexp e]))
        <|> try (do fc <- getFC; e <- simpleExpr syn; o <- operator; lchar ')'
                    return $ PLam (MN 1000 "ARG") Placeholder
                                  (PApp fc (PRef fc (UN o)) [pexp e,
                                                             pexp (PRef fc (MN 1000 "ARG"))]))
        <?> "end of expression in braces"

-- bit of a hack here. If the integer doesn't fit in an Int, treat it as a
-- big integer, otherwise try fromInteger and the constants as alternatives.
-- a better solution would be to fix fromInteger to work with Integer, as the
-- name suggests, rather than Int
{-| Finds optimal type for integer constant -}
modifyConst :: SyntaxInfo -> FC -> PTerm -> PTerm
modifyConst syn fc (PConstant (BI x))
    | not (inPattern syn)
        = PAlternative False
             (PApp fc (PRef fc (UN "fromInteger")) [pexp (PConstant (BI (fromInteger x)))]
             : consts)
    | otherwise = PAlternative False consts
    where
      consts = [ PConstant (BI x)
               , PConstant (I (fromInteger x))
               , PConstant (B8 (fromInteger x))
               , PConstant (B16 (fromInteger x))
               , PConstant (B32 (fromInteger x))
               , PConstant (B64 (fromInteger x))
               ]
modifyConst syn fc x = x

{- | Parses a list literal expression e.g. [1,2,3]
ListExpr ::=
  '[' ExprList? ']'
;

ExprList ::=
  Expr
  | Expr ',' ExprList
  ;

 -}
listExpr :: SyntaxInfo -> IdrisParser PTerm
listExpr syn = do lchar '['; fc <- getFC; xs <- sepBy (expr syn) (lchar ','); lchar ']'
                  return (mkList fc xs)
               <?> "list expression"
  where
    mkList :: FC -> [PTerm] -> PTerm
    mkList fc [] = PRef fc (UN "Nil")
    mkList fc (x : xs) = PApp fc (PRef fc (UN "::")) [pexp x, pexp (mkList fc xs)]

{- | Parses rest of pair expression
Pair ::=
    Expr RestTuple? ')'
  | NTuple ')'
  | Name ':' Expr '**' 'Expr' ')'
  ;

RestTuple ::=
    ',' Expr
  | '**' Expr
  ;

NTuple ::=
     Expr ',' Expr
   | Expr ',' NTuple
   ;
-}
pair :: SyntaxInfo -> IdrisParser PTerm
pair syn = try (do l <- expr syn
                   fc <- getFC
                   rest <- restTuple
                   case rest of
                       [] -> return l
                       [Left r] -> return (PPair fc l r)
                       [Right r] -> return (PDPair fc l Placeholder r))
        <|> try (do x <- ntuple
                    lchar ')'
                    return x)
        <|> do ln <- name; lchar ':'
               lty <- expr syn
               reservedOp "**"
               fc <- getFC
               r <- expr syn
               lchar ')'
               return (PDPair fc (PRef fc ln) lty r)
        <?> "pair expression"
  where
    restTuple :: IdrisParser [Either PTerm PTerm]
    restTuple = do lchar ')'; return []
            <|> do lchar ','
                   r <- expr syn
                   lchar ')'
                   return [Left r]
            <|> do reservedOp "**"
                   r <- expr syn
                   lchar ')'
                   return [Right r]
            <?> "end of pair expression"
    ntuple :: IdrisParser PTerm
    ntuple = try (do l <- expr syn; fc <- getFC; lchar ','
                     rest <- ntuple
                     return (PPair fc l rest))
             <|> (do l <- expr syn; fc <- getFC; lchar ','
                     r <- expr syn
                     return (PPair fc l r))
             <?> "tuple expression"

{- | Parses an alternative expression
  Alt ::= '(|' Expr_List '|)';

  Expr_List ::=
    Expr'
    | Expr' ',' Expr_List
  ;
-}
alt :: SyntaxInfo -> IdrisParser PTerm
alt syn = do symbol "(|"; alts <- sepBy1 (expr' syn) (lchar ','); symbol "|)"
             return (PAlternative False alts)

{- | Parses a possibly hidden simple expression
HSimpleExpr ::=
  '.' SimpleExpr
  | SimpleExpr
  ;
-}
hsimpleExpr :: SyntaxInfo -> IdrisParser PTerm
hsimpleExpr syn =
  do lchar '.'
     e <- simpleExpr syn
     return $ PHidden e
  <|> simpleExpr syn
  <?> "expression"

{- | Parses a matching application expression
MatchApp ::=
  SimpleExpr '<==' FnName
  ;
-}
matchApp :: SyntaxInfo -> IdrisParser PTerm
matchApp syn = do ty <- simpleExpr syn
                  symbol "<=="
                  fc <- getFC
                  f <- fnName
                  return (PLet (MN 0 "match")
                                ty
                                (PMatchApp fc f)
                                (PRef fc (MN 0 "match")))
               <?> "matching application expression"

{- | Parses a unification log expression
UnifyLog ::=
  '%' 'unifyLog' SimpleExpr
  ;
-}
unifyLog :: SyntaxInfo -> IdrisParser PTerm
unifyLog syn = do lchar '%'; reserved "unifyLog";
                  tm <- simpleExpr syn
                  return (PUnifyLog tm)
               <?> "unification log expression"

{- | Parses a function application expression
App ::=
  'mkForeign' Arg Arg*
  | SimpleExpr Arg+
  ;
-}
app :: SyntaxInfo -> IdrisParser PTerm
app syn = do f <- reserved "mkForeign"
             fc <- getFC
             fn <- arg syn
             args <- many (do notEndApp; arg syn)
             i <- get
             -- mkForeign f args ==>
             -- liftPrimIO (\w => mkForeignPrim f args w)
             let ap = PApp fc (PRef fc (UN "liftPrimIO"))
                       [pexp (PLam (MN 0 "w")
                             Placeholder
                             (PApp fc (PRef fc (UN "mkForeignPrim"))
                                         (fn : args ++
                                            [pexp (PRef fc (MN 0 "w"))])))]
             return (dslify i ap)

       <|> do f <- simpleExpr syn
              fc <- getFC
              args <- some (do notEndApp; arg syn)
              i <- get
              return (dslify i $ PApp fc f args)
       <?> "function application"
  where
    dslify :: IState -> PTerm -> PTerm
    dslify i (PApp fc (PRef _ f) [a])
        | [d] <- lookupCtxt f (idris_dsls i)
            = desugar (syn { dsl_info = d }) i (getTm a)
    dslify i t = t

{- |Parses a function argument
Arg ::=
  ImplicitArg
  | ConstraintArg
  | SimpleExpr
  ;
-}
arg :: SyntaxInfo -> IdrisParser PArg
arg syn =  try (implicitArg syn)
       <|> try (constraintArg syn)
       <|> do e <- simpleExpr syn
              return (pexp e)
       <?> "function argument"

{- |Parses an implicit function argument
ImplicitArg ::=
  '{' Name ('=' Expr)? '}'
  ;
-}
implicitArg :: SyntaxInfo -> IdrisParser PArg
implicitArg syn = do lchar '{'
                     n <- name
                     fc <- getFC
                     v <- option (PRef fc n) (do lchar '='
                                                 expr syn)
                     lchar '}'
                     return (pimp n v)
                  <?> "implicit function argument"

{- |Parses a constraint argument (for selecting a named type class instance)
ConstraintArg ::=
  '@{' Expr '}'
  ;
-}
constraintArg :: SyntaxInfo -> IdrisParser PArg
constraintArg syn = do symbol "@{"
                       e <- expr syn
                       symbol "}"
                       return (pconst e)
                    <?> "constraint argument"


{- |Parses a record field setter expression
RecordType ::=
  'record' '{' FieldTypeList '}';

FieldTypeList ::=
  FieldType
  | FieldType ',' FieldTypeList
  ;

FieldType ::=
  FnName '=' Expr
  ;
-}
recordType :: SyntaxInfo -> IdrisParser PTerm
recordType syn
    = do reserved "record"
         lchar '{'
         fields <- sepBy1 fieldType (lchar ',')
         lchar '}'
         fc <- getFC
         rec <- optional (simpleExpr syn)
         case rec of
            Nothing ->
                return (PLam (MN 0 "fldx") Placeholder
                            (applyAll fc fields (PRef fc (MN 0 "fldx"))))
            Just v -> return (applyAll fc fields v)
       <?> "record setting expression"
   where fieldType :: IdrisParser (Name, PTerm)
         fieldType = do n <- fnName
                        lchar '='
                        e <- expr syn
                        return (n, e)
                     <?> "field setter"
         applyAll :: FC -> [(Name, PTerm)] -> PTerm -> PTerm
         applyAll fc [] x = x
         applyAll fc ((n, e) : es) x
            = applyAll fc es (PApp fc (PRef fc (mkType n)) [pexp e, pexp x])

{- |Creates setters for record types on necessary functions -}
mkType :: Name -> Name
mkType (UN n) = UN ("set_" ++ n)
mkType (MN 0 n) = MN 0 ("set_" ++ n)
mkType (NS n s) = NS (mkType n) s

{- |Parses a type for an expression
TypeSig ::=
  ':' Expr
  ;
-}
typeSig :: SyntaxInfo -> IdrisParser PTerm
typeSig syn = lchar ':' *> typeExpr syn <?> "type"

{- |Parses a type signature
TypeExpr ::= ConstraintList? Expr;
 -}
typeExpr :: SyntaxInfo -> IdrisParser PTerm
typeExpr syn = do cs <- if implicitAllowed syn then constraintList syn else return []
                  sc <- expr syn
                  return (bindList (PPi constraint) (map (\x -> (MN 0 "c", x)) cs) sc)
               <?> "type signature"

{- |Parses a lambda expression
Lambda ::=
    '\\' TypeOptDeclList '=>' Expr
  | '\\' SimpleExprList  '=>' Expr
  ;
SimpleExprList ::=
  SimpleExpr
  | SimpleExpr ',' SimpleExprList
  ;
-}
lambda :: SyntaxInfo -> IdrisParser PTerm
lambda syn = do lchar '\\'
                try (do xt <- tyOptDeclList syn
                        symbol "=>"
                        sc <- expr syn
                        return (bindList PLam xt sc)
                 <|> (do ps <- sepBy (do fc <- getFC
                                         e <- simpleExpr syn
                                         return (fc, e)) (lchar ',')
                         symbol "=>"
                         sc <- expr syn
                         return (pmList (zip [0..] ps) sc)))
                 <?> "lambda expression"
    where pmList :: [(Int, (FC, PTerm))] -> PTerm -> PTerm
          pmList [] sc = sc
          pmList ((i, (fc, x)) : xs) sc
                = PLam (MN i "lamp") Placeholder
                        (PCase fc (PRef fc (MN i "lamp"))
                                [(x, pmList xs sc)])

{- |Parses a term rewrite expression
RewriteTerm ::=
  'rewrite' Expr ('==>' Expr)? 'in' Expr
  ;
-}
rewriteTerm :: SyntaxInfo -> IdrisParser PTerm
rewriteTerm syn = do reserved "rewrite"
                     fc <- getFC
                     prf <- expr syn
                     giving <- optional (do symbol "==>"; expr' syn)
                     reserved "in";  sc <- expr syn
                     return (PRewrite fc
                             (PApp fc (PRef fc (UN "sym")) [pexp prf]) sc
                               giving)
                  <?> "term rewrite expression"

{- |Parses a let binding
Let ::=
  'let' Name TypeSig'? '=' Expr  'in' Expr
| 'let' Expr'          '=' Expr' 'in' Expr

TypeSig' ::=
  ':' Expr'
  ;
 -}
let_ :: SyntaxInfo -> IdrisParser PTerm
let_ syn = try (do reserved "let"; n <- name;
                   ty <- option Placeholder (do lchar ':'; expr' syn)
                   lchar '='
                   v <- expr syn
                   reserved "in";  sc <- expr syn
                   return (PLet n ty v sc))
           <|> (do reserved "let"; fc <- getFC; pat <- expr' (syn { inPattern = True } )
                   symbol "="; v <- expr syn
                   reserved "in"; sc <- expr syn
                   return (PCase fc v [(pat, sc)]))
           <?> "let binding"

{- |Parses a quote goal
QuoteGoal ::=
  'quoteGoal' Name 'by' Expr 'in' Expr
  ;
 -}
quoteGoal :: SyntaxInfo -> IdrisParser PTerm
quoteGoal syn = do reserved "quoteGoal"; n <- name;
                   reserved "by"
                   r <- expr syn
                   reserved "in"
                   fc <- getFC
                   sc <- expr syn
                   return (PGoal fc r n sc)
                <?> "quote goal expression"

{- |Parses a dependent type signature
Pi ::=
    '|'? Static? '('           TypeDeclList ')' DocComment '->' Expr
  | '|'? Static? '{'           TypeDeclList '}'            '->' Expr
  |              '{' 'auto'    TypeDeclList '}'            '->' Expr
  |              '{' 'default' TypeDeclList '}'            '->' Expr
  |              '{' 'static'               '}' Expr'      '->' Expr
  ;
 -}
pi syn =
     try (do lazy <- if implicitAllowed syn -- laziness is top level only
                        then option False (do lchar '|'; return True)
                        else return False
             st <- static
             lchar '('; xt <- typeDeclList syn; lchar ')'
             doc <- option "" (docComment '^')
             symbol "->"
             sc <- expr syn
             return (bindList (PPi (Exp lazy st doc)) xt sc))
 <|> try (if implicitAllowed syn
             then do lazy <- option False (do lchar '|'
                                              return True)
                     st <- static
                     lchar '{'
                     xt <- typeDeclList syn
                     lchar '}'
                     symbol "->"
                     sc <- expr syn
                     return (bindList (PPi (Imp lazy st "")) xt sc)
             else fail "no implicit arguments allowed here")
 <|> try (do lchar '{'
             reserved "auto"
             xt <- typeDeclList syn
             lchar '}'
             symbol "->"
             sc <- expr syn
             return (bindList (PPi
                      (TacImp False Dynamic (PTactics [Trivial]) "")) xt sc))
 <|> try (do lchar '{'
             reserved "default"
             script <- simpleExpr syn
             xt <- typeDeclList syn
             lchar '}'
             symbol "->"
             sc <- expr syn
             return (bindList (PPi (TacImp False Dynamic script "")) xt sc))
 <|> do lchar '{'
        reserved "static"
        lchar '}'
        t <- expr' syn
        symbol "->"
        sc <- expr syn
        return (PPi (Exp False Static "") (MN 42 "__pi_arg") t sc)
  <?> "dependent type signature"

{- | Parses a type constraint list
ConstraintList ::=
    '(' Expr_List ')' '=>'
  | Expr              '=>'
  ;
-}
constraintList :: SyntaxInfo -> IdrisParser [PTerm]
constraintList syn = try (do lchar '('
                             tys <- sepBy1 (expr' (disallowImp syn)) (lchar ',')
                             lchar ')'
                             reservedOp "=>"
                             return tys)
                 <|> try (do t <- expr (disallowImp syn)
                             reservedOp "=>"
                             return [t])
                 <|> return []
                 <?> "type constraint list"

{- | Parses a using declaration list
UsingDeclList ::=
  UsingDeclList'
  | NameList TypeSig
  ;

UsingDeclList' ::=
  UsingDecl
  | UsingDecl ',' UsingDeclList'
  ;

NameList ::=
  Name
  | Name ',' NameList
  ;
-}
usingDeclList :: SyntaxInfo -> IdrisParser [Using]
usingDeclList syn
               = try (sepBy1 (usingDecl syn) (lchar ','))
             <|> do ns <- sepBy1 name (lchar ',')
                    t <- typeSig (disallowImp syn)
                    return (map (\x -> UImplicit x t) ns)
             <?> "using declaration list"

{- |Parses a using declaration
UsingDecl ::=
  FnName TypeSig
  | FnName FnName+
  ;
-}
usingDecl :: SyntaxInfo -> IdrisParser Using
usingDecl syn = try (do x <- fnName
                        t <- typeSig (disallowImp syn)
                        return (UImplicit x t))
            <|> do c <- fnName
                   xs <- some fnName
                   return (UConstraint c xs)
            <?> "using declaration"

{- |Parses a type declaration list
TypeDeclList ::=
    FunctionSignatureList
  | NameList TypeSig
  ;

FunctionSignatureList ::=
    Name TypeSig
  | Name TypeSig ',' FunctionSignatureList
  ;
-}
typeDeclList :: SyntaxInfo -> IdrisParser [(Name, PTerm)]
typeDeclList syn = try (sepBy1 (do x <- fnName
                                   t <- typeSig (disallowImp syn)
                                   return (x,t))
                           (lchar ','))
                   <|> do ns <- sepBy1 name (lchar ',')
                          t <- typeSig (disallowImp syn)
                          return (map (\x -> (x, t)) ns)
                   <?> "type declaration list"

{- |Parses a type declaration list with optional parameters
TypeOptDeclList ::=
    NameOrPlaceholder TypeSig?
  | NameOrPlaceholder TypeSig? ',' TypeOptDeclList
  ;

NameOrPlaceHolder ::= Name | '_';
-}
tyOptDeclList :: SyntaxInfo -> IdrisParser [(Name, PTerm)]
tyOptDeclList syn = sepBy1 (do x <- nameOrPlaceholder
                               t <- option Placeholder (do lchar ':'
                                                           expr syn)
                               return (x,t))
                           (lchar ',')
                    <?> "type declaration list"
    where  nameOrPlaceholder :: IdrisParser Name
           nameOrPlaceholder = fnName
                           <|> do symbol "_"
                                  return (MN 0 "underscore")
                           <?> "name or placeholder"

{- |Parses a list comprehension
Comprehension ::= '[' Expr '|' DoList ']';

DoList ::=
    Do
  | Do ',' DoList
  ;
-}
comprehension :: SyntaxInfo -> IdrisParser PTerm
comprehension syn
    = do lchar '['
         fc <- getFC
         pat <- expr syn
         lchar '|'
         qs <- sepBy1 (do_ syn) (lchar ',')
         lchar ']'
         return (PDoBlock (map addGuard qs ++
                    [DoExp fc (PApp fc (PRef fc (UN "return"))
                                 [pexp pat])]))
      <?> "list comprehension"
    where addGuard :: PDo -> PDo
          addGuard (DoExp fc e) = DoExp fc (PApp fc (PRef fc (UN "guard"))
                                                    [pexp e])
          addGuard x = x

{- |Parses a do-block
Do' ::= Do KeepTerminator;

DoBlock ::=
  'do' OpenBlock Do'+ CloseBlock
  ;
 -}
doBlock :: SyntaxInfo -> IdrisParser PTerm
doBlock syn
    = do reserved "do"
         ds <- indentedBlock (do_ syn)
         return (PDoBlock ds)
      <?> "do block"

{- |Parses an expression inside a do block
Do ::=
    'let' Name  TypeSig'?      '=' Expr
  | 'let' Expr'                '=' Expr
  | Name  '<-' Expr
  | Expr' '<-' Expr
  | Expr
  ;
-}
do_ :: SyntaxInfo -> IdrisParser PDo
do_ syn
     = try (do reserved "let"
               i <- name
               ty <- option Placeholder (do lchar ':'
                                            expr' syn)
               reservedOp "="
               fc <- getFC
               e <- expr syn
               return (DoLet fc i ty e))
   <|> try (do reserved "let"
               i <- expr' syn
               reservedOp "="
               fc <- getFC
               sc <- expr syn
               return (DoLetP fc i sc))
   <|> try (do i <- name
               symbol "<-"
               fc <- getFC
               e <- expr syn;
               return (DoBind fc i e))
   <|> try (do i <- expr' syn
               symbol "<-"
               fc <- getFC
               e <- expr syn;
               return (DoBindP fc i e))
   <|> try (do e <- expr syn
               fc <- getFC
               return (DoExp fc e))
   <?> "do block expression"

{- |Parses an expression in idiom brackets
Idiom ::= '[|' Expr '|]';
-}
idiom :: SyntaxInfo -> IdrisParser PTerm
idiom syn
    = do symbol "[|"
         fc <- getFC
         e <- expr syn
         symbol "|]"
         return (PIdiom fc e)
      <?> "expression in idiom brackets"

{- |Parses a constant or literal expression
Constant ::=
    'Integer'
  | 'Int'
  | 'Char'
  | 'Float'
  | 'String'
  | 'Ptr'
  | 'Bits8'
  | 'Bits16'
  | 'Bits32'
  | 'Bits64'
  | 'Bits8x16'
  | 'Bits16x8'
  | 'Bits32x4'
  | 'Bits64x2'
  | Float_t
  | Natural_t
  | String_t
  | Char_t
  ;
-}
constant :: IdrisParser Core.TT.Const
constant =  do reserved "Integer";return (AType (ATInt ITBig))
        <|> do reserved "Int";    return (AType (ATInt ITNative))
        <|> do reserved "Char";   return (AType (ATInt ITChar))
        <|> do reserved "Float";  return (AType ATFloat)
        <|> do reserved "String"; return StrType
        <|> do reserved "Ptr";    return PtrType
        <|> do reserved "Bits8";  return (AType (ATInt (ITFixed IT8)))
        <|> do reserved "Bits16"; return (AType (ATInt (ITFixed IT16)))
        <|> do reserved "Bits32"; return (AType (ATInt (ITFixed IT32)))
        <|> do reserved "Bits64"; return (AType (ATInt (ITFixed IT64)))
        <|> do reserved "Bits8x16"; return (AType (ATInt (ITVec IT8 16)))
        <|> do reserved "Bits16x8"; return (AType (ATInt (ITVec IT16 8)))
        <|> do reserved "Bits32x4"; return (AType (ATInt (ITVec IT32 4)))
        <|> do reserved "Bits64x2"; return (AType (ATInt (ITVec IT64 2)))
        <|> try (do f <- float;   return $ Fl f)
        <|> try (do i <- natural; return $ BI i)
        <|> try (do s <- stringLiteral;  return $ Str s)
        <|> try (do c <- charLiteral;   return $ Ch c)
        <?> "constant or literal"

{- |Parses a static modifier
Static ::=
  '[' static ']'
;
-}
static :: IdrisParser Static
static =     do lchar '['; reserved "static"; lchar ']'; return Static
         <|> return Dynamic
         <?> "static modifier"

{- |Parses a record type declaration
Record ::=
    DocComment Accessibility? 'record' FnName TypeSig 'where' OpenBlock Constructor KeepTerminator CloseBlock;
-}
record :: SyntaxInfo -> IdrisParser PDecl
record syn = do doc <- option "" (docComment '|')
                acc <- optional accessibility
                reserved "record"
                fc <- getFC
                tyn_in <- fnName
                ty <- typeSig (allowImp syn)
                let tyn = expandNS syn tyn_in
                reserved "where"
                (cdoc, cn, cty, _) <- indentedBlockS (constructor syn)
                accData acc tyn [cn]
                let rsyn = syn { syn_namespace = show (nsroot tyn) :
                                                    syn_namespace syn }
                let fns = getRecNames rsyn cty
                mapM_ (\n -> addAcc n acc) fns
                return $ PRecord doc rsyn fc tyn ty cdoc cn cty
             <?> "record type declaration"
  where
    getRecNames :: SyntaxInfo -> PTerm -> [Name]
    getRecNames syn (PPi _ n _ sc) = [expandNS syn n, expandNS syn (mkType n)]
                                       ++ getRecNames syn sc
    getRecNames _ _ = []

    toFreeze :: Maybe Accessibility -> Maybe Accessibility
    toFreeze (Just Frozen) = Just Hidden
    toFreeze x = x

{- | Parses data declaration type (normal or codata)
DataI ::= 'data' | 'codata';
-}
dataI :: IdrisParser Bool
dataI = do reserved "data"; return False
    <|> do reserved "codata"; return True

{- | Parses a data type declaration
Data ::= DocComment? Accessibility? DataI FnName TypeSig ExplicitTypeDataRest?
       | DocComment? Accessibility? DataI FnName Name*   DataRest?
       ;
Constructor' ::= Constructor KeepTerminator;
ExplicitTypeDataRest ::= 'where' OpenBlock Constructor'* CloseBlock;

DataRest ::= '=' SimpleConstructorList Terminator
            | 'where'!
           ;
SimpleConstructorList ::=
    SimpleConstructor
  | SimpleConstructor '|' SimpleConstructorList
  ;
-}
data_ :: SyntaxInfo -> IdrisParser PDecl
data_ syn = try (do doc <- option "" (docComment '|')
                    acc <- optional accessibility
                    co <- dataI
                    fc <- getFC
                    tyn_in <- fnName
                    ty <- typeSig (allowImp syn)
                    let tyn = expandNS syn tyn_in
                    option (PData doc syn fc co (PLaterdecl tyn ty)) (do
                      reserved "where"
                      cons <- indentedBlock (constructor syn)
                      accData acc tyn (map (\ (_, n, _, _) -> n) cons)
                      return $ PData doc syn fc co (PDatadecl tyn ty cons)))
        <|> try (do doc <- option "" (docComment '|')
                    pushIndent
                    acc <- optional accessibility
                    co <- dataI
                    fc <- getFC
                    tyn_in <- fnName
                    args <- many name
                    let ty = bindArgs (map (const PType) args) PType
                    let tyn = expandNS syn tyn_in
                    option (PData doc syn fc co (PLaterdecl tyn ty)) (do
                      try (lchar '=') <|> do reserved "where"
                                             let kw = (if co then "co" else "") ++ "data "
                                             let n  = show tyn_in ++ " "
                                             let s  = kw ++ n
                                             let as = concat (intersperse " " $ map show args) ++ " "
                                             let ns = concat (intersperse " -> " $ map ((\x -> "(" ++ x ++ " : Type)") . show) args)
                                             let ss = concat (intersperse " -> " $ map (const "Type") args)
                                             let fix1 = s ++ as ++ " = ..."
                                             let fix2 = s ++ ": " ++ ns ++ " -> Type where\n  ..."
                                             let fix3 = s ++ ": " ++ ss ++ " -> Type where\n  ..."
                                             fail $ fixErrorMsg "unexpected \"where\"" [fix1, fix2, fix3]
                      cons <- sepBy1 (simpleConstructor syn) (lchar '|')
                      terminator
                      let conty = mkPApp fc (PRef fc tyn) (map (PRef fc) args)
                      cons' <- mapM (\ (doc, x, cargs, cfc) ->
                                   do let cty = bindArgs cargs conty
                                      return (doc, x, cty, cfc)) cons
                      accData acc tyn (map (\ (_, n, _, _) -> n) cons')
                      return $ PData doc syn fc co (PDatadecl tyn ty cons')))
        <?> "data type declaration"
  where
    mkPApp :: FC -> PTerm -> [PTerm] -> PTerm
    mkPApp fc t [] = t
    mkPApp fc t xs = PApp fc t (map pexp xs)
    bindArgs :: [PTerm] -> PTerm -> PTerm
    bindArgs xs t = foldr (PPi expl (MN 0 "t")) t xs


{- | Parses a type constructor declaration
  Constructor ::= DocComment? FnName TypeSig;
-}
constructor :: SyntaxInfo -> IdrisParser (String, Name, PTerm, FC)
constructor syn
    = do doc <- option "" (docComment '|')
         cn_in <- fnName; fc <- getFC
         let cn = expandNS syn cn_in
         ty <- typeSig (allowImp syn)
         return (doc, cn, ty, fc)
      <?> "constructor"

{- | Parses a constructor for simple discriminative union data types
  SimpleConstructor ::= FnName SimpleExpr* DocComment?
-}
simpleConstructor :: SyntaxInfo -> IdrisParser (String, Name, [PTerm], FC)
simpleConstructor syn
     = do cn_in <- fnName
          let cn = expandNS syn cn_in
          fc <- getFC
          args <- many (do notEndApp
                           simpleExpr syn)
          doc <- option "" (docComment '^')
          return (doc, cn, args, fc)
       <?> "constructor"

{- | Parses a dsl block declaration
DSL ::= 'dsl' FnName OpenBlock Overload'+ CloseBlock;
 -}
dsl :: SyntaxInfo -> IdrisParser PDecl
dsl syn = do reserved "dsl"
             n <- fnName
             bs <- indentedBlock (overload syn)
             let dsl = mkDSL bs (dsl_info syn)
             checkDSL dsl
             i <- get
             put (i { idris_dsls = addDef n dsl (idris_dsls i) })
             return (PDSL n dsl)
          <?> "dsl block declaration"
    where mkDSL :: [(String, PTerm)] -> DSL -> DSL
          mkDSL bs dsl = let var    = lookup "variable" bs
                             first  = lookup "index_first" bs
                             next   = lookup "index_next" bs
                             leto   = lookup "let" bs
                             lambda = lookup "lambda" bs in
                             initDSL { dsl_var = var,
                                       index_first = first,
                                       index_next = next,
                                       dsl_lambda = lambda,
                                       dsl_let = leto }

{- | Checks DSL for errors -}
-- FIXME: currently does nothing, check if DSL is really sane
checkDSL :: DSL -> IdrisParser ()
checkDSL dsl = return ()

{- | Parses a DSL overload declaration
OverloadIdentifier ::= 'let' | Identifier;
Overload ::= OverloadIdentifier '=' Expr;
-}
overload :: SyntaxInfo -> IdrisParser (String, PTerm)
overload syn = do o <- identifier <|> do reserved "let"
                                         return "let"
                  if o `notElem` overloadable
                     then fail $ show o ++ " is not an overloading"
                     else do
                       lchar '='
                       t <- expr syn
                       return (o, t)
               <?> "dsl overload declaratioN"
    where overloadable = ["let","lambda","index_first","index_next","variable"]

{- | Parse a clause with patterns
Pattern ::= Clause;
-}
pattern :: SyntaxInfo -> IdrisParser PDecl
pattern syn = do fc <- getFC
                 clause <- clause syn
                 return (PClauses fc [] (MN 2 "_") [clause]) -- collect together later
              <?> "pattern"

{- | Parse a constant applicative form declaration
  CAF ::= 'let' FnName '=' Expr Terminator;
-}
caf :: SyntaxInfo -> IdrisParser PDecl
caf syn = do reserved "let"
             n_in <- fnName; let n = expandNS syn n_in
             lchar '='
             t <- expr syn
             terminator
             fc <- getFC
             return (PCAF fc n t)
           <?> "constant applicative form declaration"

{- | Parse an argument expression
  ArgExpr ::= HSimpleExpr | {- In Pattern External (User-defined) Expression -};
-}
argExpr :: SyntaxInfo -> IdrisParser PTerm
argExpr syn = let syn' = syn { inPattern = True } in
                  try (hsimpleExpr syn') <|> simpleExternalExpr syn'
              <?> "argument expression"

{- | Parse a right hand side of a function
RHS ::= '='            Expr
     |  '?='  RHSName? Expr
     |  'impossible'
     ;

RHSName ::= '{' FnName '}';
-}
rhs :: SyntaxInfo -> Name -> IdrisParser PTerm
rhs syn n = do lchar '='; expr syn
        <|> do symbol "?=";
               name <- option n' (do symbol "{"; n <- fnName; symbol "}";
                                     return n)
               r <- expr syn
               return (addLet name r)
        <|> do reserved "impossible"; return PImpossible
        <?> "function right hand side"
  where mkN :: Name -> Name
        mkN (UN x)   = UN (x++"_lemma_1")
        mkN (NS x n) = NS (mkN x) n
        n' :: Name
        n' = mkN n
        addLet :: Name -> PTerm -> PTerm
        addLet nm (PLet n ty val r) = PLet n ty val (addLet nm r)
        addLet nm (PCase fc t cs) = PCase fc t (map addLetC cs)
          where addLetC (l, r) = (l, addLet nm r)
        addLet nm r = (PLet (UN "value") Placeholder r (PMetavar nm))

{- |Parses a function clause
Clause ::=                   FnName ConstraintArg* ImplicitOrArgExpr*    WExpr* RHS WhereOrTerminator
       |   SimpleExpr '<=='  FnName                                             RHS WhereOrTerminator
       |                                                                 WExpr+ RHS WhereOrTerminator
       |                     FnName ConstraintArg* ImplicitOrArgExpr*    WExpr* 'with' SimpleExpr OpenBlock FnDecl+ CloseBlock
       |                                                                 WExpr+ 'with' SimpleExpr OpenBlock FnDecl+ CloseBlock
       |   ArgExpr Operator ArgExpr                                      WExpr* RHS WhereOrTerminator
       |   ArgExpr Operator ArgExpr                                      WExpr* 'with' SimpleExpr OpenBlock FnDecl+ CloseBlock
       ;
ImplicitOrArgExpr ::= ImplicitArg | ArgExpr;
WhereOrTerminator ::= WhereBlock | Terminator;
-}
clause :: SyntaxInfo -> IdrisParser PClause
clause syn
         = try (do pushIndent
                   n_in <- fnName; let n = expandNS syn n_in
                   cargs <- many (constraintArg syn)
                   fc <- getFC
                   args <- many (try (implicitArg (syn { inPattern = True } ))
                                 <|> (fmap pexp (argExpr syn)))
                   wargs <- many (wExpr syn)
                   r <- rhs syn n
                   ist <- get
                   let ctxt = tt_ctxt ist
                   let wsyn = syn { syn_namespace = [] }
                   (wheres, nmap) <- choice [do x <- whereBlock n wsyn
                                                popIndent
                                                return x,
                                             do terminator
                                                return ([], [])]
                   let capp = PApp fc (PRef fc n)
                                (cargs ++ args)
                   ist <- get
                   put (ist { lastParse = Just n })
                   return $ PClause fc n capp wargs r wheres)
       <|> try (do pushIndent
                   ty <- simpleExpr syn
                   symbol "<=="
                   fc <- getFC
                   n_in <- fnName; let n = expandNS syn n_in
                   r <- rhs syn n
                   ist <- get
                   let ctxt = tt_ctxt ist
                   let wsyn = syn { syn_namespace = [] }
                   (wheres, nmap) <- choice [do x <- whereBlock n wsyn
                                                popIndent
                                                return x,
                                             do terminator
                                                return ([], [])]
                   let capp = PLet (MN 0 "match")
                                   ty
                                   (PMatchApp fc n)
                                   (PRef fc (MN 0 "match"))
                   ist <- get
                   put (ist { lastParse = Just n })
                   return $ PClause fc n capp [] r wheres)
       <|> try (do pushIndent
                   wargs <- some (wExpr syn)
                   ist <- get
                   n <- case lastParse ist of
                             Just t -> return t
                             Nothing -> fail "Invalid clause"
                   fc <- getFC
                   r <- rhs syn n
                   let ctxt = tt_ctxt ist
                   let wsyn = syn { syn_namespace = [] }
                   (wheres, nmap) <- choice [do x <- whereBlock n wsyn
                                                popIndent
                                                return x,
                                             do terminator
                                                return ([], [])]
                   return $ PClauseR fc wargs r wheres)

       <|> try (do pushIndent
                   n_in <- fnName; let n = expandNS syn n_in
                   cargs <- many (constraintArg syn)
                   fc <- getFC
                   args <- many (try (implicitArg (syn { inPattern = True } ))
                                 <|> (fmap pexp (argExpr syn)))
                   wargs <- many (wExpr syn)
                   let capp = PApp fc (PRef fc n)
                                (cargs ++ args)
                   ist <- get
                   put (ist { lastParse = Just n })
                   reserved "with"
                   wval <- simpleExpr syn
                   openBlock
                   ds <- some $ fnDecl syn
                   let withs = map (fillLHSD n capp wargs) $ concat ds
                   closeBlock
                   popIndent
                   return $ PWith fc n capp wargs wval withs)

       <|> try (do wargs <- some (wExpr syn)
                   fc <- getFC
                   reserved "with"
                   wval <- simpleExpr syn
                   openBlock
                   ds <- some $ fnDecl syn
                   let withs = concat ds
                   closeBlock
                   return $ PWithR fc wargs wval withs)

       <|> try(do pushIndent
                  l <- argExpr syn
                  op <- operator
                  let n = expandNS syn (UN op)
                  r <- argExpr syn
                  fc <- getFC
                  wargs <- many (wExpr syn)
                  rs <- rhs syn n
                  let wsyn = syn { syn_namespace = [] }
                  (wheres, nmap) <- choice [do x <- whereBlock n wsyn
                                               popIndent
                                               return x,
                                            do terminator
                                               return ([], [])]
                  ist <- get
                  let capp = PApp fc (PRef fc n) [pexp l, pexp r]
                  put (ist { lastParse = Just n })
                  return $ PClause fc n capp wargs rs wheres)

       <|> do l <- argExpr syn
              op <- operator
              let n = expandNS syn (UN op)
              r <- argExpr syn
              fc <- getFC
              wargs <- many (wExpr syn)
              reserved "with"
              wval <- simpleExpr syn
              openBlock
              ds <- some $ fnDecl syn
              closeBlock
              ist <- get
              let capp = PApp fc (PRef fc n) [pexp l, pexp r]
              let withs = map (fillLHSD n capp wargs) $ concat ds
              put (ist { lastParse = Just n })
              return $ PWith fc n capp wargs wval withs
      <?> "function clause"
  where
    fillLHS :: Name -> PTerm -> [PTerm] -> PClause -> PClause
    fillLHS n capp owargs (PClauseR fc wargs v ws)
       = PClause fc n capp (owargs ++ wargs) v ws
    fillLHS n capp owargs (PWithR fc wargs v ws)
       = PWith fc n capp (owargs ++ wargs) v
            (map (fillLHSD n capp (owargs ++ wargs)) ws)
    fillLHS _ _ _ c = c

    fillLHSD :: Name -> PTerm -> [PTerm] -> PDecl -> PDecl
    fillLHSD n c a (PClauses fc o fn cs) = PClauses fc o fn (map (fillLHS n c a) cs)
    fillLHSD n c a x = x

{- |Parses with pattern
 WExpr ::= '|' Expr';
-}
wExpr :: SyntaxInfo -> IdrisParser PTerm
wExpr syn = do lchar '|'
               expr' syn
            <?> "with pattern"

{- |Parses a where block
WhereBlock ::= 'where' OpenBlock Decl+ CloseBlock;
 -}
whereBlock :: Name -> SyntaxInfo -> IdrisParser ([PDecl], [(Name, Name)])
whereBlock n syn
    = do reserved "where"
         ds <- indentedBlock1 (decl syn)
         let dns = concatMap (concatMap declared) ds
         return (concat ds, map (\x -> (x, decoration syn x)) dns)
      <?> "where block"

{- |Parses a code generation target language name
Codegen ::= 'C'
        |   'Java'
        |   'JavaScript'
        |   'Node'
        |   'LLVM'
        |   'Bytecode'
        ;
-}
codegen_ :: IdrisParser Codegen
codegen_ = try (do reserved "C"; return ViaC)
      <|> try (do reserved "Java"; return ViaJava)
      <|> try (do reserved "JavaScript"; return ViaJavaScript)
      <|> try (do reserved "Node"; return ViaNode)
      <|> try (do reserved "LLVM"; return ViaLLVM)
      <|> try (do reserved "Bytecode"; return Bytecode)
      <?> "code generation language"

{- |Parses a compiler directive
StringList ::=
  String
  | String ',' StringList
  ;

Directive ::= '%' Directive';

Directive' ::= 'lib'      CodeGen String_t
           |   'link'     CodeGen String_t
           |   'flag'     CodeGen String_t
           |   'include'  CodeGen String_t
           |   'hide'     Name
           |   'freeze'   Name
           |   'access'   Accessibility
           |   'default'  Totality
           |   'logging'  Natural
           |   'dynamic'  StringList
           |   'language' 'TypeProviders'
           ;
-}
directive :: SyntaxInfo -> IdrisParser [PDecl]
directive syn = try (do lchar '%'; reserved "lib"; cgn <- codegen_; lib <- stringLiteral;
                        return [PDirective (do addLib cgn lib
                                               addIBC (IBCLib cgn lib))])
             <|> try (do lchar '%'; reserved "link"; cgn <- codegen_; obj <- stringLiteral;
                         return [PDirective (do dirs <- allImportDirs
                                                o <- liftIO $ findInPath dirs obj
                                                addIBC (IBCObj cgn obj) -- just name, search on loading ibc
                                                addObjectFile cgn o)])
             <|> try (do lchar '%'; reserved "flag"; cgn <- codegen_;
                         flag <- stringLiteral
                         return [PDirective (do addIBC (IBCCGFlag cgn flag)
                                                addFlag cgn flag)])
             <|> try (do lchar '%'; reserved "include"; cgn <- codegen_; hdr <- stringLiteral;
                         return [PDirective (do addHdr cgn hdr
                                                addIBC (IBCHeader cgn hdr))])
             <|> try (do lchar '%'; reserved "hide"; n <- iName []
                         return [PDirective (do setAccessibility n Hidden
                                                addIBC (IBCAccess n Hidden))])
             <|> try (do lchar '%'; reserved "freeze"; n <- iName []
                         return [PDirective (do setAccessibility n Frozen
                                                addIBC (IBCAccess n Frozen))])
             <|> try (do lchar '%'; reserved "access"; acc <- accessibility
                         return [PDirective (do i <- get
                                                put(i { default_access = acc }))])
             <|> try (do lchar '%'; reserved "default"; tot <- totality
                         i <- get
                         put (i { default_total = tot } )
                         return [PDirective (do i <- get
                                                put(i { default_total = tot }))])
             <|> try (do lchar '%'; reserved "logging"; i <- natural;
                         return [PDirective (setLogLevel (fromInteger i))])
             <|> try (do lchar '%'; reserved "dynamic"; libs <- sepBy1 stringLiteral (lchar ',');
                         return [PDirective (do added <- addDyLib libs
                                                case added of
                                                  Left lib -> addIBC (IBCDyLib (lib_name lib))
                                                  Right msg ->
                                                      fail $ msg)])
             <|> try (do lchar '%'; reserved "language"; ext <- reserved "TypeProviders";
                         return [PDirective (addLangExt TypeProviders)])
             <?> "directive"

{- | Parses a totality
Totality ::= 'partial' | 'total'
-}
totality :: IdrisParser Bool
totality
        = do reserved "total";   return True
      <|> do reserved "partial"; return False

{- | Parses a type provider
Provider ::= '%' 'provide' '(' FnName TypeSig ')' 'with' Expr;
 -}
provider :: SyntaxInfo -> IdrisParser [PDecl]
provider syn = do lchar '%'; reserved "provide";
                  lchar '('; n <- fnName; t <- typeSig syn; lchar ')'
                  fc <- getFC
                  reserved "with"
                  e <- expr syn
                  return  [PProvider syn fc n t e]
               <?> "type provider"

{- | Parses a transform
Transform ::= '%' 'transform' Expr '==>' Expr
-}
transform :: SyntaxInfo -> IdrisParser [PDecl]
transform syn = do lchar '%'; reserved "transform";
                    -- leave it unchecked, until we work out what this should
                    -- actually mean...
--                     safety <- option True (do reserved "unsafe"
--                                               return False)
                   l <- expr syn
                   fc <- getFC
                   symbol "==>"
                   r <- expr syn
                   return [PTransform fc False l r]
                <?> "transform"

{- | Parses a tactic script
Tactic ::= 'intro' NameList?
       |   'intros'
       |   'refine'      Name Imp+
       |   'mrefine'     Name
       |   'rewrite'     Expr
       |   'equiv'       Expr
       |   'let'         Name ':' Expr' '=' Expr
       |   'let'         Name           '=' Expr
       |   'focus'       Name
       |   'exact'       Expr
       |   'applyTactic' Expr
       |   'reflect'     Expr
       |   'fill'        Expr
       |   'try'         Tactic '|' Tactic
       |   '{' TacticSeq '}'
       |   'compute'
       |   'trivial'
       |   'solve'
       |   'attack'
       |   'state'
       |   'term'
       |   'undo'
       |   'qed'
       |   'abandon'
       |   ':' 'q'
       ;

Imp ::= '?' | '_';

TacticSeq ::=
    Tactic ';' Tactic
  | Tactic ';' TacticSeq
  ;

-}

tactic :: SyntaxInfo -> IdrisParser PTactic
tactic syn = do reserved "intro"; ns <- sepBy name (lchar ',')
                return $ Intro ns
          <|> do reserved "intros"; return Intros
          <|> try (do reserved "refine"; n <- name
                      imps <- some imp
                      return $ Refine n imps)
          <|> do reserved "refine"; n <- name
                 i <- get
                 return $ Refine n []
          <|> do reserved "mrefine"; n <- name
                 i <- get
                 return $ MatchRefine n
          <|> do reserved "rewrite"; t <- expr syn;
                 i <- get
                 return $ Rewrite (desugar syn i t)
          <|> do reserved "equiv"; t <- expr syn;
                 i <- get
                 return $ Equiv (desugar syn i t)
          <|> try (do reserved "let"; n <- name; lchar ':';
                      ty <- expr' syn; lchar '='; t <- expr syn;
                      i <- get
                      return $ LetTacTy n (desugar syn i ty) (desugar syn i t))
          <|> try (do reserved "let"; n <- name; lchar '=';
                      t <- expr syn;
                      i <- get
                      return $ LetTac n (desugar syn i t))
          <|> do reserved "focus"; n <- name
                 return $ Focus n
          <|> do reserved "exact"; t <- expr syn;
                 i <- get
                 return $ Exact (desugar syn i t)
          <|> do reserved "applyTactic"; t <- expr syn;
                 i <- get
                 return $ ApplyTactic (desugar syn i t)
          <|> do reserved "reflect"; t <- expr syn;
                 i <- get
                 return $ Reflect (desugar syn i t)
          <|> do reserved "fill"; t <- expr syn;
                 i <- get
                 return $ Fill (desugar syn i t)
          <|> do reserved "try"; t <- tactic syn;
                 lchar '|';
                 t1 <- tactic syn
                 return $ Try t t1
          <|> do lchar '{'
                 t <- tactic syn;
                 lchar ';';
                 ts <- sepBy1 (tactic syn) (lchar ';')
                 lchar '}'
                 return $ TSeq t (mergeSeq ts)
          <|> do reserved "compute"; return Compute
          <|> do reserved "trivial"; return Trivial
          <|> do reserved "solve"; return Solve
          <|> do reserved "attack"; return Attack
          <|> do reserved "state"; return ProofState
          <|> do reserved "term"; return ProofTerm
          <|> do reserved "undo"; return Undo
          <|> do reserved "qed"; return Qed
          <|> do reserved "abandon"; return Abandon
          <|> do lchar ':'; reserved "q"; return Abandon
          <?> "tactic"
  where
    imp :: IdrisParser Bool
    imp = do lchar '?'; return False
      <|> do lchar '_'; return True
    mergeSeq :: [PTactic] -> PTactic
    mergeSeq [t]    = t
    mergeSeq (t:ts) = TSeq t (mergeSeq ts)

{- | Parses a tactic as a whole -}
fullTactic :: SyntaxInfo -> IdrisParser PTactic
fullTactic syn = do t <- tactic syn
                    eof
                    return t

{- * Loading and parsing -}
{- | Parses an expression from input -}
parseExpr :: IState -> String -> Result PTerm
parseExpr st = parseString (evalStateT (fullExpr defaultSyntax) st) (Directed (UTF8.fromString "(input)") 0 0 0 0)

{- | Parses a tactic from input -}
parseTactic :: IState -> String -> Result PTactic
parseTactic st = parseString (evalStateT (fullTactic defaultSyntax) st) (Directed (UTF8.fromString "(input)") 0 0 0 0)

-- | Parse module header and imports
parseImports :: FilePath -> String -> Idris ([String], [String], Maybe Delta)
parseImports fname input
    = do i <- getIState
         case parseString (evalStateT imports i) (Directed (UTF8.fromString fname) 0 0 0 0) input of
              Failure err    -> fail (show err)
              Success (x, i) -> do -- Discard state updates (there should be
                                   -- none anyway)
                                   return x
  where imports :: IdrisParser (([String], [String], Maybe Delta), IState)
        imports = do whiteSpace
                     mname <- moduleHeader
                     ps    <- many import_
                     mrk   <- mark
                     isEof <- lookAheadMatches eof
                     let mrk' = if isEof
                                   then Nothing
                                   else Just mrk
                     i     <- get
                     return ((mname, ps, mrk'), i)


-- | A program is a list of declarations, possibly with associated
-- documentation strings.
parseProg :: SyntaxInfo -> FilePath -> String -> Maybe Delta ->
             Idris [PDecl]
parseProg syn fname input mrk
    = do i <- getIState
         case parseString (evalStateT mainProg i) (Directed (UTF8.fromString fname) 0 0 0 0) input of
            Failure doc     -> do iputStrLn (show doc)
                                  -- FIXME: Get error location from trifecta
                                  --let errl = sourceLine (errorPos err)
                                  i <- getIState
                                  putIState (i { errLine = Just 0 }) -- Just errl })
                                  return []
            Success (x, i)  -> do putIState i
                                  return $ collect x
  where mainProg :: IdrisParser ([PDecl], IState)
        mainProg = case mrk of
                        Nothing -> do i <- get; return ([], i)
                        Just mrk -> do
                          release mrk
                          ds <- prog syn
                          i' <- get
                          return (ds, i')

-- | Collect 'PClauses' with the same function name
collect :: [PDecl] -> [PDecl]
collect (c@(PClauses _ o _ _) : ds)
    = clauses (cname c) [] (c : ds)
  where clauses :: Maybe Name -> [PClause] -> [PDecl] -> [PDecl]
        clauses j@(Just n) acc (PClauses fc _ _ [PClause fc' n' l ws r w] : ds)
           | n == n' = clauses j (PClause fc' n' l ws r (collect w) : acc) ds
        clauses j@(Just n) acc (PClauses fc _ _ [PWith fc' n' l ws r w] : ds)
           | n == n' = clauses j (PWith fc' n' l ws r (collect w) : acc) ds
        clauses (Just n) acc xs = PClauses (fcOf c) o n (reverse acc) : collect xs
        clauses Nothing acc (x:xs) = collect xs
        clauses Nothing acc [] = []

        cname :: PDecl -> Maybe Name
        cname (PClauses fc _ _ [PClause _ n _ _ _ _]) = Just n
        cname (PClauses fc _ _ [PWith   _ n _ _ _ _]) = Just n
        cname (PClauses fc _ _ [PClauseR _ _ _ _]) = Nothing
        cname (PClauses fc _ _ [PWithR _ _ _ _]) = Nothing
        fcOf :: PDecl -> FC
        fcOf (PClauses fc _ _ _) = fc
collect (PParams f ns ps : ds) = PParams f ns (collect ps) : collect ds
collect (PMutual f ms : ds) = PMutual f (collect ms) : collect ds
collect (PNamespace ns ps : ds) = PNamespace ns (collect ps) : collect ds
collect (PClass doc f s cs n ps ds : ds') 
    = PClass doc f s cs n ps (collect ds) : collect ds'
collect (PInstance f s cs n ps t en ds : ds') 
    = PInstance f s cs n ps t en (collect ds) : collect ds'
collect (d : ds) = d : collect ds
collect [] = []

{- | Load idris module -}
loadModule :: FilePath -> Idris String
loadModule f
   = idrisCatch (do i <- getIState
                    let file = takeWhile (/= ' ') f
                    ibcsd <- valIBCSubDir i
                    ids <- allImportDirs
                    fp <- liftIO $ findImport ids ibcsd file
                    if file `elem` imported i
                       then iLOG $ "Already read " ++ file
                       else do putIState (i { imported = file : imported i })
                               case fp of
                                   IDR fn  -> loadSource False fn
                                   LIDR fn -> loadSource True  fn
                                   IBC fn src ->
                                     idrisCatch (loadIBC fn)
                                                (\c -> do iLOG $ fn ++ " failed " ++ show c
                                                          case src of
                                                            IDR sfn -> loadSource False sfn
                                                            LIDR sfn -> loadSource True sfn)
                    let (dir, fh) = splitFileName file
                    return (dropExtension fh))
                (\e -> do let msg = show e
                          setErrLine (getErrLine msg)
                          iputStrLn msg
                          return "")

{- | Load idris code from file -}
loadFromIFile :: IFileType -> Idris ()
loadFromIFile i@(IBC fn src)
   = do iLOG $ "Skipping " ++ getSrcFile i
        idrisCatch (loadIBC fn)
                (\c -> do fail $ fn ++ " failed " ++ show c)
  where
    getSrcFile (IDR fn) = fn
    getSrcFile (LIDR fn) = fn
    getSrcFile (IBC f src) = getSrcFile src

loadFromIFile (IDR fn) = loadSource' False fn
loadFromIFile (LIDR fn) = loadSource' True fn

{-| Load idris source code and show error if something wrong happens -}
loadSource' :: Bool -> FilePath -> Idris ()
loadSource' lidr r
   = idrisCatch (loadSource lidr r)
                (\e -> do let msg = show e
                          setErrLine (getErrLine msg)
                          iputStrLn msg)

{- | Load Idris source code-}
loadSource :: Bool -> FilePath -> Idris ()
loadSource lidr f
             = do iLOG ("Reading " ++ f)
                  i <- getIState
                  let def_total = default_total i
                  file_in <- liftIO $ readFile f
                  file <- if lidr then tclift $ unlit f file_in else return file_in
                  (mname, modules, pos) <- parseImports f file
                  i <- getIState
                  putIState (i { default_access = Hidden })
                  clearIBC -- start a new .ibc file
                  mapM_ (addIBC . IBCImport) modules
                  ds' <- parseProg (defaultSyntax {syn_namespace = reverse mname })
                                   f file pos
                  unless (null ds') $ do
                    let ds = namespaces mname ds'
                    logLvl 3 (dumpDecls ds)
                    i <- getIState
                    logLvl 10 (show (toAlist (idris_implicits i)))
                    logLvl 3 (show (idris_infixes i))
                    -- Now add all the declarations to the context
                    v <- verbose
                    when v $ iputStrLn $ "Type checking " ++ f
                    -- we totality check after every Mutual block, so if
                    -- anything is a single definition, wrap it in a
                    -- mutual block on its own
                    elabDecls toplevel (map toMutual ds)
                    i <- getIState
                    -- simplify every definition do give the totality checker
                    -- a better chance
                    mapM_ (\n -> do logLvl 5 $ "Simplifying " ++ show n
                                    updateContext (simplifyCasedef n))
                             (map snd (idris_totcheck i))
                    -- build size change graph from simplified definitions
                    iLOG "Totality checking"
                    i <- getIState
                    mapM_ buildSCG (idris_totcheck i)
                    mapM_ checkDeclTotality (idris_totcheck i)
                    iLOG ("Finished " ++ f)
                    ibcsd <- valIBCSubDir i
                    iLOG "Universe checking"
                    iucheck
                    let ibc = ibcPathNoFallback ibcsd f
                    i <- getIState
                    addHides (hide_list i)
                    ok <- noErrors
                    when ok $
                      idrisCatch (do writeIBC f ibc; clearIBC)
                                 (\c -> return ()) -- failure is harmless
                    i <- getIState
                    putIState (i { default_total = def_total,
                                   hide_list = [] })
                    return ()
                  return ()
  where
    namespaces :: [String] -> [PDecl] -> [PDecl]
    namespaces []     ds = ds
    namespaces (x:xs) ds = [PNamespace x (namespaces xs ds)]

    toMutual :: PDecl -> PDecl
    toMutual m@(PMutual _ d) = m
    toMutual x = let r = PMutual (FC "single mutual" 0) [x] in
                 case x of
                   PClauses _ _ _ _ -> r
                   PClass _ _ _ _ _ _ _ -> r
                   PInstance _ _ _ _ _ _ _ _ -> r
                   _ -> x

{- | Adds names to hide list -}
addHides :: [(Name, Maybe Accessibility)] -> Idris ()
addHides xs = do i <- getIState
                 let defh = default_access i
                 let (hs, as) = partition isNothing xs
                 unless (null as) $
                   mapM_ doHide
                     (map (\ (n, _) -> (n, defh)) hs ++
                       map (\ (n, Just a) -> (n, a)) as)
  where isNothing (_, Nothing) = True
        isNothing _            = False

        doHide (n, a) = do setAccessibility n a
                           addIBC (IBCAccess n a)