module Idris.Parser(IdrisParser(..), ImportInfo(..), moduleName, addReplSyntax, clearParserWarnings,
                    decl, fixColour, loadFromIFile, loadModule, name, opChars, parseElabShellStep, parseConst, parseExpr, parseImports, parseTactic,
                    runparser, ParseError, parseErrorDoc) where
import Idris.AbsSyntax hiding (namespace, params)
import Idris.Core.Evaluate
import Idris.Core.TT
import Idris.Delaborate
import Idris.Docstrings hiding (Unchecked)
import Idris.DSL
import Idris.Elab.Value
import Idris.ElabDecls
import Idris.Error
import Idris.IBC
import Idris.Imports
import Idris.Options
import Idris.Output
import Idris.Parser.Data
import Idris.Parser.Expr
import Idris.Parser.Helpers
import Idris.Parser.Ops
import Idris.Termination
import Idris.Unlit
import Util.System (readSource)
import Prelude hiding (pi)
import Control.Applicative hiding (Const)
import Control.Monad
import Control.Monad.State.Strict
import Data.Char
import Data.Foldable (asum)
import Data.Function
import Data.Generics.Uniplate.Data (descendM)
import Data.List
import qualified Data.List.NonEmpty as NonEmpty
import qualified Data.List.Split as Spl
import qualified Data.Map as M
import Data.Maybe
import Data.Ord
import qualified Data.Text as T
import qualified System.Directory as Dir (makeAbsolute)
import System.FilePath
import Text.Megaparsec ((<?>))
import qualified Text.Megaparsec as P
import qualified Text.Megaparsec.Char as P
import qualified Text.PrettyPrint.ANSI.Leijen as PP
moduleName :: Parsing m => m Name
moduleName = mkName [] . map T.pack <$> moduleNamePieces where
  mkName :: [T.Text] -> [T.Text] -> Name
  mkName ts [x]    = if null ts then UN x else NS (UN x) ts
  mkName ts (x:xs) = mkName (x : ts) xs
moduleNamePieces :: Parsing m => m [String]
moduleNamePieces = Spl.splitOn "." <$> identifier
moduleHeader :: IdrisParser (Maybe (Docstring ()), [String], [(FC, OutputAnnotation)])
moduleHeader =     P.try (do docs <- optional docComment
                             noArgs docs
                             keyword "module"
                             (modName, ifc) <- withExtent moduleNamePieces
                             P.option ';' (lchar ';')
                             return (fmap fst docs,
                                     modName,
                                     [(ifc, AnnNamespace (map T.pack modName) Nothing)]))
               <|> P.try (do lchar '%'; reserved "unqualified"
                             return (Nothing, [], []))
               <|> return (Nothing, ["Main"], [])
  where noArgs (Just (_, args)) | not (null args) = fail "Modules do not take arguments"
        noArgs _ = return ()
data ImportInfo = ImportInfo { import_reexport :: Bool
                             , import_path :: FilePath
                             , import_rename :: Maybe (String, FC)
                             , import_namespace :: [T.Text]
                             , import_location :: FC
                             , import_modname_location :: FC
                             }
import_ :: IdrisParser ImportInfo
import_ = do fc <- extent $ keyword "import"
             reexport <- P.option False (True <$ keyword "public")
             (ns, idfc) <- withExtent moduleNamePieces
             newName <- optional (do keyword "as"
                                     withExtent identifier)
             P.option ';' (lchar ';')
             return $ ImportInfo reexport (toPath ns)
                        (fmap (\(n, fc) -> (toPath (Spl.splitOn "." n), fc)) newName)
                        (map T.pack ns) fc idfc
          <?> "import statement"
  where toPath = foldl1' (</>)
prog :: SyntaxInfo -> IdrisParser [PDecl]
prog syn = do (decls, fc) <- withExtent $ do
                  whiteSpace
                  decls <- concat <$> many (decl syn)
                  case maxline syn of
                       Nothing -> do notOpenBraces; P.eof
                       _ -> return ()
                  return decls
              ist <- get
              put ist { idris_parsedSpan = Just (FC (fc_fname fc) (0,0) (fc_end fc)),
                        ibc_write = IBCParsedRegion fc : ibc_write ist }
              return decls
decl :: SyntaxInfo -> IdrisParser [PDecl]
decl syn = P.try (externalDecl syn)
           <|> internalDecl syn
           <?> "declaration"
internalDecl :: SyntaxInfo -> IdrisParser [PDecl]
internalDecl syn
         = do fc <- getFC
              
              
              
              let continue = case maxline syn of
                                Nothing -> True
                                Just l -> if fst (fc_end fc) > l
                                             then mut_nesting syn /= 0
                                             else True
              
              
              
              
              
              if continue then
                 do notEndBlock
                    declBody continue
                else P.try (do notEndBlock
                               declBody continue)
                     <|> fail "End of readable input"
  where declBody :: Bool -> IdrisParser [PDecl]
        declBody b =
                   P.try (implementation syn)
                   <|> P.try (openInterface syn)
                   <|> declBody' b
                   <|> using_ syn
                   <|> params syn
                   <|> mutual syn
                   <|> namespace syn
                   <|> interface_ syn
                   <|> do d <- dsl syn; return [d]
                   <|> directive syn
                   <|> provider syn
                   <|> transform syn
                   <|> do import_; fail "imports must be at top of file"
                   <?> "declaration"
        declBody' :: Bool -> IdrisParser [PDecl]
        declBody' cont = do d <- decl' syn
                            i <- get
                            let d' = fmap (debindApp syn . (desugar syn i)) d
                            if continue cont d'
                               then return [d']
                               else fail "End of readable input"
        
        continue False (PClauses _ _ _ _) = True
        continue c _ = c
decl' :: SyntaxInfo -> IdrisParser PDecl
decl' syn =    fixity
           <|> syntaxDecl syn
           <|> fnDecl' syn
           <|> data_ syn
           <|> record syn
           <|> runElabDecl syn
           <?> "declaration"
externalDecl :: SyntaxInfo -> IdrisParser [PDecl]
externalDecl syn = do i <- get
                      notEndBlock
                      (decls, fc@(FC fn _ _)) <- withExtent $ declExtensions syn (syntaxRulesList $ syntax_rules i)
                      return $ map (mapPDeclFC (fixFC fc) (fixFCH fn fc)) decls
  where
    
    fixFC :: FC -> FC -> FC
    fixFC outer inner | inner `fcIn` outer = inner
                      | otherwise          = outer
    
    fixFCH fn outer inner | inner `fcIn` outer = inner
                          | otherwise          = FileFC fn
declExtensions :: SyntaxInfo -> [Syntax] -> IdrisParser [PDecl]
declExtensions syn rules = declExtension syn [] (filter isDeclRule rules)
                           <?> "user-defined declaration"
   where
     isDeclRule (DeclRule _ _) = True
     isDeclRule _ = False
declExtension :: SyntaxInfo -> [Maybe (Name, SynMatch)] -> [Syntax]
                 -> IdrisParser [PDecl]
declExtension syn ns rules =
  P.choice $ flip map (groupBy (ruleGroup `on` syntaxSymbols) rules) $ \rs ->
    case head rs of 
      DeclRule (symb:_) _ -> P.try $ do
        n <- extSymbol symb
        declExtension syn (n : ns) [DeclRule ss t | (DeclRule (_:ss) t) <- rs]
      
      
      DeclRule [] dec -> let r = map (update (mapMaybe id ns)) dec in
                             return r
  where
    update :: [(Name, SynMatch)] -> PDecl -> PDecl
    update ns = updateNs ns . fmap (updateRefs ns) . fmap (updateSynMatch ns)
    updateRefs ns = mapPT newref
      where
        newref (PRef fc fcs n) = PRef fc fcs (updateB ns n)
        newref t = t
    
    
    
    
    updateB :: [(Name, SynMatch)] -> Name -> Name
    updateB ns (NS n mods) = NS (updateB ns n) mods
    updateB ns n = case lookup n ns of
                        Just (SynBind tfc t) -> t
                        _ -> n
    updateNs :: [(Name, SynMatch)] -> PDecl -> PDecl
    updateNs ns (PTy doc argdoc s fc o n fc' t)
          = PTy doc argdoc s fc o (updateB ns n) fc' t
    updateNs ns (PClauses fc o n cs)
         = PClauses fc o (updateB ns n) (map (updateClause ns) cs)
    updateNs ns (PCAF fc n t) = PCAF fc (updateB ns n) t
    updateNs ns (PData ds cds s fc o dat)
         = PData ds cds s fc o (updateData ns dat)
    updateNs ns (PParams fc ps ds) = PParams fc ps (map (updateNs ns) ds)
    updateNs ns (PNamespace s fc ds) = PNamespace s fc (map (updateNs ns) ds)
    updateNs ns (PRecord doc syn fc o n fc' ps pdocs fields cname cdoc s)
         = PRecord doc syn fc o (updateB ns n) fc' ps pdocs
                   (map (updateField ns) fields)
                   (updateRecCon ns cname)
                   cdoc
                   s
    updateNs ns (PInterface docs s fc cs cn fc' ps pdocs pdets ds cname cdocs)
         = PInterface docs s fc cs (updateB ns cn) fc' ps pdocs pdets
                      (map (updateNs ns) ds)
                      (updateRecCon ns cname)
                      cdocs
    updateNs ns (PImplementation docs pdocs s fc cs pnames acc opts cn fc' ps pextra ity ni ds)
         = PImplementation docs pdocs s fc cs pnames acc opts (updateB ns cn) fc'
                           ps pextra ity (fmap (updateB ns) ni)
                           (map (updateNs ns) ds)
    updateNs ns (PMutual fc ds) = PMutual fc (map (updateNs ns) ds)
    updateNs ns (PProvider docs s fc fc' pw n)
        = PProvider docs s fc fc' pw (updateB ns n)
    updateNs ns d = d
    updateRecCon ns Nothing = Nothing
    updateRecCon ns (Just (n, fc)) = Just (updateB ns n, fc)
    updateField ns (m, p, t, doc) = (updateRecCon ns m, p, t, doc)
    updateClause ns (PClause fc n t ts t' ds)
       = PClause fc (updateB ns n) t ts t' (map (update ns) ds)
    updateClause ns (PWith fc n t ts t' m ds)
       = PWith fc (updateB ns n) t ts t' m (map (update ns) ds)
    updateClause ns (PClauseR fc ts t ds)
       = PClauseR fc ts t (map (update ns) ds)
    updateClause ns (PWithR fc ts t m ds)
       = PWithR fc ts t m (map (update ns) ds)
    updateData ns (PDatadecl n fc t cs)
       = PDatadecl (updateB ns n) fc t (map (updateCon ns) cs)
    updateData ns (PLaterdecl n fc t)
       = PLaterdecl (updateB ns n) fc t
    updateCon ns (cd, ads, cn, fc, ty, fc', fns)
       = (cd, ads, updateB ns cn, fc, ty, fc', fns)
    ruleGroup [] [] = True
    ruleGroup (s1:_) (s2:_) = s1 == s2
    ruleGroup _ _ = False
    extSymbol :: SSymbol -> IdrisParser (Maybe (Name, SynMatch))
    extSymbol (Keyword n) = Nothing <$ keyword (show n)
    extSymbol (Expr n) = do tm <- expr syn
                            return $ Just (n, SynTm tm)
    extSymbol (SimpleExpr n) = do tm <- simpleExpr syn
                                  return $ Just (n, SynTm tm)
    extSymbol (Binding n) = do (b, fc) <- withExtent name
                               return $ Just (n, SynBind fc b)
    extSymbol (Symbol s) = Nothing <$ highlight AnnKeyword (symbol s)
syntaxDecl :: SyntaxInfo -> IdrisParser PDecl
syntaxDecl syn = do (s, fc) <- withExtent $ syntaxRule syn
                    modify $ \i -> i `addSyntax` s
                    return (PSyntax fc s)
addSyntax :: IState -> Syntax -> IState
addSyntax i s = i { syntax_rules = updateSyntaxRules [s] rs,
                    syntax_keywords = ks ++ ns,
                    ibc_write = IBCSyntax s : map IBCKeyword ks ++ ibc }
  where rs = syntax_rules i
        ns = syntax_keywords i
        ibc = ibc_write i
        ks = map show (syntaxNames s)
addReplSyntax :: IState -> Syntax -> IState
addReplSyntax i s = i { syntax_rules = updateSyntaxRules [s] rs,
                        syntax_keywords = ks ++ ns }
  where rs = syntax_rules i
        ns = syntax_keywords i
        ks = map show (syntaxNames s)
syntaxRule :: SyntaxInfo -> IdrisParser Syntax
syntaxRule syn
    = do sty <- P.try (do
            pushIndent
            sty <- P.option AnySyntax
                            (TermSyntax <$ keyword "term"
                             <|> PatternSyntax <$ keyword "pattern")
            keyword "syntax"
            return sty)
         syms <- some syntaxSym
         when (all isExpr syms) $ P.unexpected . P.Label . NonEmpty.fromList $ "missing keywords in syntax rule"
         let ns = mapMaybe getName syms
         when (length ns /= length (nub ns))
            $ P.unexpected . P.Label . NonEmpty.fromList $ "repeated variable in syntax rule"
         lchar '='
         tm <- typeExpr (allowImp syn) >>= uniquifyBinders [n | Binding n <- syms]
         terminator
         return (Rule (mkSimple syms) tm sty)
  <|> do keyword "decl"; keyword "syntax"
         syms <- some syntaxSym
         when (all isExpr syms) $ P.unexpected . P.Label . NonEmpty.fromList $ "missing keywords in syntax rule"
         let ns = mapMaybe getName syms
         when (length ns /= length (nub ns))
            $ P.unexpected . P.Label . NonEmpty.fromList $ "repeated variable in syntax rule"
         lchar '='
         openBlock
         dec <- some (decl syn)
         closeBlock
         return (DeclRule (mkSimple syms) (concat dec))
  where
    isExpr (Expr _) = True
    isExpr _ = False
    getName (Expr n) = Just n
    getName _ = Nothing
    
    
    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' (Expr e : Symbol s : es)
      | takeWhile (`elem` opChars) ts /= "" = SimpleExpr e : Symbol s : mkSimple' es
       where ts = dropWhile isSpace . dropWhileEnd isSpace $ s
    mkSimple' (e : es) = e : mkSimple' es
    mkSimple' [] = []
    
    
    uniquifyBinders :: [Name] -> PTerm -> IdrisParser PTerm
    uniquifyBinders userNames = fixBind 0 []
      where
        fixBind :: Int -> [(Name, Name)] -> PTerm -> IdrisParser PTerm
        fixBind 0 rens (PRef fc hls n) | Just n' <- lookup n rens =
          return $ PRef fc hls n'
        fixBind 0 rens (PPatvar fc n) | Just n' <- lookup n rens =
          return $ PPatvar fc n'
        fixBind 0 rens (PLam fc n nfc ty body)
          | n `elem` userNames = liftM2 (PLam fc n nfc)
                                        (fixBind 0 rens ty)
                                        (fixBind 0 rens body)
          | otherwise =
            do ty' <- fixBind 0 rens ty
               n' <- gensym n
               body' <- fixBind 0 ((n,n'):rens) body
               return $ PLam fc n' nfc ty' body'
        fixBind 0 rens (PPi plic n nfc argTy body)
          | n `elem` userNames = liftM2 (PPi plic n nfc)
                                        (fixBind 0 rens argTy)
                                        (fixBind 0 rens body)
          | otherwise =
            do ty' <- fixBind 0 rens argTy
               n' <- gensym n
               body' <- fixBind 0 ((n,n'):rens) body
               return $ (PPi plic n' nfc ty' body')
        fixBind 0 rens (PLet fc rig n nfc ty val body)
          | n `elem` userNames = liftM3 (PLet fc rig n nfc)
                                        (fixBind 0 rens ty)
                                        (fixBind 0 rens val)
                                        (fixBind 0 rens body)
          | otherwise =
            do ty' <- fixBind 0 rens ty
               val' <- fixBind 0 rens val
               n' <- gensym n
               body' <- fixBind 0 ((n,n'):rens) body
               return $ PLet fc rig n' nfc ty' val' body'
        fixBind 0 rens (PMatchApp fc n) | Just n' <- lookup n rens =
          return $ PMatchApp fc n'
        
        
        fixBind 0 rens (PQuoteName n True fc) | Just n' <- lookup n rens =
          return $ PQuoteName n' True fc
        
        fixBind q rens (PQuasiquote tm goal) =
          flip PQuasiquote goal <$> fixBind (q + 1) rens tm
        fixBind q rens (PUnquote tm) =
          PUnquote <$> fixBind (q  1) rens tm
        fixBind q rens x = descendM (fixBind q rens) x
        gensym :: Name -> IdrisParser Name
        gensym n = do ist <- get
                      let idx = idris_name ist
                      put ist { idris_name = idx + 1 }
                      return $ sMN idx (show n)
syntaxSym :: IdrisParser SSymbol
syntaxSym =    P.try (do lchar '['; n <- name; lchar ']'
                         return (Expr n))
            <|> P.try (do lchar '{'; n <- name; lchar '}'
                          return (Binding n))
            <|> do n <- iName []
                   return (Keyword n)
            <|> do sym <- stringLiteral
                   return (Symbol sym)
            <?> "syntax symbol"
fnDecl :: SyntaxInfo -> IdrisParser [PDecl]
fnDecl syn = P.try (do notEndBlock
                       d <- fnDecl' syn
                       i <- get
                       let d' = fmap (debindApp syn . desugar syn i) d
                       return [d']) <?> "function declaration"
fnDecl' :: SyntaxInfo -> IdrisParser PDecl
fnDecl' syn = (checkDeclFixity $
              do (doc, argDocs, fc, opts', n, nfc, acc) <- P.try (do
                        pushIndent
                        (doc, argDocs) <- docstring syn
                        (opts, acc) <- fnOpts
                        (n_in, nfc) <- withExtent fnName
                        let n = expandNS syn n_in
                        fc <- extent $ lchar ':'
                        return (doc, argDocs, fc, opts, n, nfc, acc))
                 ty <- typeExpr (allowImp syn)
                 terminator
                 
                 
                 when (syn_toplevel syn) $ addAcc n acc
                 return (PTy doc argDocs syn fc opts' n nfc ty)
            <|> postulate syn
            <|> caf syn
            <|> pattern syn)
            <?> "function declaration"
fnOpts :: IdrisParser ([FnOpt], Accessibility)
fnOpts = do
    opts <- many fnOpt
    acc <- accessibility
    opts' <- many fnOpt
    let allOpts = opts ++ opts'
    let existingTotality = allOpts `intersect` [TotalFn, CoveringFn, PartialFn]
    opts'' <- addDefaultTotality (nub existingTotality) allOpts
    return (opts'', acc)
  where prettyTot TotalFn = "total"
        prettyTot PartialFn = "partial"
        prettyTot CoveringFn = "covering"
        addDefaultTotality [] opts = do
          ist <- get
          case default_total ist of
            DefaultCheckingTotal    -> return (TotalFn:opts)
            DefaultCheckingCovering -> return (CoveringFn:opts)
            DefaultCheckingPartial  -> return opts 
        addDefaultTotality [tot] opts = return opts
        
        addDefaultTotality (tot1:tot2:tots) opts =
          fail ("Conflicting totality modifiers specified " ++ prettyTot tot1 ++ " and " ++ prettyTot tot2)
fnOpt :: IdrisParser FnOpt
fnOpt = do keyword "total"; return TotalFn
        <|> PartialFn <$ keyword "partial"
        <|> CoveringFn <$ keyword "covering"
        <|> do P.try (lchar '%' *> reserved "export"); c <- stringLiteral;
                      return $ CExport c
        <|> NoImplicit <$ P.try (lchar '%' *> reserved "no_implicit")
        <|> Inlinable <$ P.try (lchar '%' *> reserved "inline")
        <|> StaticFn <$ P.try (lchar '%' *> reserved "static")
        <|> ErrorHandler <$ P.try (lchar '%' *> reserved "error_handler")
        <|> ErrorReverse <$ P.try (lchar '%' *> reserved "error_reverse")
        <|> ErrorReduce  <$ P.try (lchar '%' *> reserved "error_reduce")
        <|> Reflection   <$ P.try (lchar '%' *> reserved "reflection")
        <|> AutoHint     <$ P.try (lchar '%' *> reserved "hint")
        <|> OverlappingDictionary <$ P.try (lchar '%' *> reserved "overlapping")
        <|> do lchar '%'; reserved "specialise";
               lchar '['; ns <- P.sepBy nameTimes (lchar ','); lchar ']';
               return $ Specialise ns
        <|> Implicit <$ keyword "implicit"
        <?> "function modifier"
  where nameTimes :: IdrisParser (Name, Maybe Int)
        nameTimes = do n <- fnName
                       t <- P.option Nothing (do reds <- natural
                                                 return (Just (fromInteger reds)))
                       return (n, t)
postulate :: SyntaxInfo -> IdrisParser PDecl
postulate syn = do (doc, ext)
                       <- P.try $ do (doc, _) <- docstring syn
                                     pushIndent
                                     ext <- ppostDecl
                                     return (doc, ext)
                   ist <- get
                   (opts, acc) <- fnOpts
                   (n_in, nfc) <- withExtent fnName
                   let n = expandNS syn n_in
                   lchar ':'
                   ty <- typeExpr (allowImp syn)
                   fc <- extent $ terminator
                   addAcc n acc
                   return (PPostulate ext doc syn fc nfc opts n ty)
                 <?> "postulate"
   where ppostDecl = do fc <- keyword "postulate"; return False
                 <|> do lchar '%'; reserved "extern"; return True
using_ :: SyntaxInfo -> IdrisParser [PDecl]
using_ syn =
    do keyword "using"
       lchar '('; ns <- usingDeclList syn; lchar ')'
       openBlock
       let uvars = using syn
       ds <- many (decl (syn { using = uvars ++ ns }))
       closeBlock
       return (concat ds)
    <?> "using declaration"
params :: SyntaxInfo -> IdrisParser [PDecl]
params syn =
    do (ns, fc) <- withExtent $ do
          keyword "parameters"
          lchar '(' *> typeDeclList syn <* lchar ')'
       let ns' = [(n, ty) | (_, n, _, ty) <- ns]
       openBlock
       let pvars = syn_params syn
       ds <- many (decl syn { syn_params = pvars ++ ns' })
       closeBlock
       return [PParams fc ns' (concat ds)]
    <?> "parameters declaration"
openInterface :: SyntaxInfo -> IdrisParser [PDecl]
openInterface syn =
    do (ns, fc) <- withExtent $ do
         keyword "using"
         keyword "implementation"
         P.sepBy1 (withExtent fnName) (lchar ',')
       openBlock
       ds <- many (decl syn)
       closeBlock
       return [POpenInterfaces fc (map fst ns) (concat ds)]
    <?> "open interface declaration"
mutual :: SyntaxInfo -> IdrisParser [PDecl]
mutual syn =
    do fc <- extent $ keyword "mutual"
       openBlock
       ds <- many (decl (syn { mut_nesting = mut_nesting syn + 1 } ))
       closeBlock
       return [PMutual fc (concat ds)]
    <?> "mutual block"
namespace :: SyntaxInfo -> IdrisParser [PDecl]
namespace syn =
    do keyword "namespace"
       (n, nfc) <- withExtent identifier
       openBlock
       ds <- some (decl syn { syn_namespace = n : syn_namespace syn })
       closeBlock
       return [PNamespace n nfc (concat ds)]
     <?> "namespace declaration"
implementationBlock :: SyntaxInfo -> IdrisParser [PDecl]
implementationBlock syn = do keyword "where"
                             openBlock
                             ds <- many (fnDecl syn)
                             closeBlock
                             return (concat ds)
                          <?> "implementation block"
interfaceBlock :: SyntaxInfo -> IdrisParser (Maybe (Name, FC), Docstring (Either Err PTerm), [PDecl])
interfaceBlock syn = do keyword "where"
                        openBlock
                        (cn, cd) <- P.option (Nothing, emptyDocstring) $
                                    P.try (do (doc, _) <- P.option noDocs docComment
                                              n <- constructor
                                              return (Just n, doc))
                        ist <- get
                        let cd' = annotate syn ist cd
                        ds <- many (notEndBlock >> P.try (implementation syn)
                                                   <|> do x <- data_ syn
                                                          return [x]
                                                   <|> fnDecl syn)
                        closeBlock
                        return (cn, cd', concat ds)
                     <?> "interface block"
  where
    constructor :: IdrisParser (Name, FC)
    constructor = keyword "constructor" *> withExtent fnName
    annotate :: SyntaxInfo -> IState -> Docstring () -> Docstring (Either Err PTerm)
    annotate syn ist = annotCode $ tryFullExpr syn ist
interface_ :: SyntaxInfo -> IdrisParser [PDecl]
interface_ syn = do (doc, argDocs, acc)
                      <- P.try (do (doc, argDocs) <- docstring syn
                                   acc <- accessibility
                                   interfaceKeyword
                                   return (doc, argDocs, acc))
                    ((cons', n, nfc, cs, fds), fc) <- withExtent $ do
                        cons <- constraintList syn
                        let cons' = [(c, ty) | (_, c, _, ty) <- cons]
                        (n_in, nfc) <- withExtent fnName
                        let n = expandNS syn n_in
                        cs <- many carg
                        fds <- P.option [(cn, NoFC) | (cn, _, _) <- cs] fundeps
                        return (cons', n, nfc, cs, fds)
                    (cn, cd, ds) <- P.option (Nothing, fst noDocs, []) (interfaceBlock syn)
                    accData acc n (concatMap declared ds)
                    return [PInterface doc syn fc cons' n nfc cs argDocs fds ds cn cd]
                 <?> "interface declaration"
  where
    fundeps :: IdrisParser [(Name, FC)]
    fundeps = do lchar '|'; P.sepBy (withExtent name) (lchar ',')
    classWarning :: String
    classWarning = "Use of a fragile keyword `class`. " ++
                   "`class` is provided for those coming from Haskell. " ++
                   "Please use `interface` instead, which is equivalent."
    interfaceKeyword :: IdrisParser ()
    interfaceKeyword = keyword "interface"
               <|> do fc <- extent $ keyword "class"
                      parserWarning fc Nothing (Msg classWarning)
    carg :: IdrisParser (Name, FC, PTerm)
    carg = do lchar '('; (i, ifc) <- withExtent name; lchar ':'; ty <- expr syn; lchar ')'
              return (i, ifc, ty)
       <|> do (i, ifc) <- withExtent name
              return (i, ifc, PType ifc)
implementation :: SyntaxInfo -> IdrisParser [PDecl]
implementation syn = do (doc, argDocs) <- docstring syn
                        (opts, acc) <- fnOpts
                        optional implementationKeyword
                        ((en, cs, cs', cn, cnfc, args, pnames), fc) <- withExtent $ do
                            en <- optional implementationName
                            cs <- constraintList syn
                            let cs' = [(c, ty) | (_, c, _, ty) <- cs]
                            (cn, cnfc) <- withExtent fnName
                            args <- many (simpleExpr syn)
                            pnames <- implementationUsing
                            return (en, cs, cs', cn, cnfc, args, pnames)
                        let sc = PApp fc (PRef cnfc [cnfc] cn) (map pexp args)
                        let t = bindList (\r -> PPi constraint { pcount = r }) cs sc
                        ds <- implementationBlock syn
                        return [PImplementation doc argDocs syn fc cs' pnames acc opts cn cnfc args [] t en ds]
                      <?> "implementation declaration"
  where implementationName :: IdrisParser Name
        implementationName = do lchar '['; n_in <- fnName; lchar ']'
                                let n = expandNS syn n_in
                                return n
                             <?> "implementation name"
        instanceWarning :: String
        instanceWarning = "Use of fragile keyword `instance`. " ++
                          "`instance` is provided for those coming from Haskell. " ++
                          "Please use `implementation` (which is equivalent) instead, or omit it."
        implementationKeyword :: IdrisParser ()
        implementationKeyword = keyword "implementation"
                         <|> do fc <- extent $ keyword "instance"
                                parserWarning fc Nothing (Msg instanceWarning)
        implementationUsing :: IdrisParser [Name]
        implementationUsing = do keyword "using"
                                 ns <- P.sepBy1 (withExtent fnName) (lchar ',')
                                 return (map fst ns)
                              <|> return []
docstring :: SyntaxInfo
          -> IdrisParser (Docstring (Either Err PTerm),
                          [(Name,Docstring (Either Err PTerm))])
docstring syn = do (doc, argDocs) <- P.option noDocs docComment
                   ist <- get
                   let doc' = annotCode (tryFullExpr syn ist) doc
                       argDocs' = [ (n, annotCode (tryFullExpr syn ist) d)
                                  | (n, d) <- argDocs ]
                   return (doc', argDocs')
usingDeclList :: SyntaxInfo -> IdrisParser [Using]
usingDeclList syn
               = P.try (P.sepBy1 (usingDecl syn) (lchar ','))
             <|> do ns <- P.sepBy1 name (lchar ',')
                    lchar ':'
                    t <- typeExpr (disallowImp syn)
                    return (map (\x -> UImplicit x t) ns)
             <?> "using declaration list"
usingDecl :: SyntaxInfo -> IdrisParser Using
usingDecl syn = P.try (do x <- fnName
                          lchar ':'
                          t <- typeExpr (disallowImp syn)
                          return (UImplicit x t))
            <|> do c <- fnName
                   xs <- many fnName
                   return (UConstraint c xs)
            <?> "using declaration"
pattern :: SyntaxInfo -> IdrisParser PDecl
pattern syn = do (clause, fc) <- withExtent (clause syn)
                 return (PClauses fc [] (sMN 2 "_") [clause]) 
              <?> "pattern"
caf :: SyntaxInfo -> IdrisParser PDecl
caf syn = do keyword "let"
             (n, fc) <- withExtent (expandNS syn <$> fnName)
             pushIndent
             lchar '='
             t <- indented $ expr syn
             terminator
             return (PCAF fc n t)
           <?> "constant applicative form declaration"
argExpr :: SyntaxInfo -> IdrisParser PTerm
argExpr syn = let syn' = syn { inPattern = True } in
                  P.try (hsimpleExpr syn') <|> simpleExternalExpr syn'
              <?> "argument expression"
rhs :: SyntaxInfo -> Name -> IdrisParser (PTerm, FC)
rhs syn n = do lchar '='
               indentGt
               withExtent $ expr syn
        <|> do symbol "?=";
               (name, fc) <- withExtent $ P.option n' (symbol "{" *> fnName <* symbol "}")
               r <- expr syn
               return (addLet fc name r, fc)
        <|> withExtent impossible
        <?> "function right hand side"
  where mkN :: Name -> Name
        mkN (UN x)   = if (tnull x || not (isAlpha (thead x)))
                         then sUN "infix_op_lemma_1"
                         else sUN (str x++"_lemma_1")
        mkN (NS x n) = NS (mkN x) n
        n' :: Name
        n' = mkN n
        addLet :: FC -> Name -> PTerm -> PTerm
        addLet fc nm (PLet fc' rig n nfc ty val r) = PLet fc' rig n nfc ty val (addLet fc nm r)
        addLet fc nm (PCase fc' t cs) = PCase fc' t (map addLetC cs)
          where addLetC (l, r) = (l, addLet fc nm r)
        addLet fc nm r = (PLet fc RigW (sUN "value") NoFC Placeholder r (PMetavar NoFC nm))
clause :: SyntaxInfo -> IdrisParser PClause
clause syn
           
         = do wargs <- P.try (do pushIndent; some (wExpr syn))
              ist <- get
              n <- case lastParse ist of
                        Just t -> return t
                        Nothing -> fail "Invalid clause"
              (do (r, fc) <- rhs syn n
                  let wsyn = syn { syn_namespace = [], syn_toplevel = False }
                  (wheres, nmap) <-     whereBlock n wsyn <* popIndent
                                    <|> ([], []) <$ terminator
                  return $ PClauseR fc wargs r wheres) <|> (do
                  popIndent
                  ((wval, pn), fc) <- withExtent $ do
                      keyword "with"
                      wval <- bracketed syn
                      pn <- optProof
                      return (wval, pn)
                  openBlock
                  ds <- some $ fnDecl syn
                  let withs = concat ds
                  closeBlock
                  return $ PWithR fc wargs wval pn withs)
           
       <|> do ty <- P.try (do pushIndent
                              ty <- simpleExpr syn
                              symbol "<=="
                              return ty)
              (n, fc) <- withExtent (expandNS syn <$> fnName)
              (r, _) <- rhs syn n
              let wsyn = syn { syn_namespace = [] }
              (wheres, nmap) <-   whereBlock n wsyn <* popIndent
                                <|> ([], []) <$ terminator
              let capp = PLet fc RigW (sMN 0 "match") NoFC
                              ty
                              (PMatchApp fc n)
                              (PRef fc [] (sMN 0 "match"))
              ist <- get
              put (ist { lastParse = Just n })
              return $ PClause fc n capp [] r wheres
           
       <|> do pushIndent
              (n, nfc, capp, wargs) <- lhs
              modify $ \ist -> ist { lastParse = Just n }
              (do (rs, fc) <- rhs syn n
                  let wsyn = syn { syn_namespace = [] }
                  (wheres, nmap) <-     whereBlock n wsyn <* popIndent
                                    <|> ([], []) <$ terminator
                  return $ PClause fc n capp wargs rs wheres) <|> (do
                   popIndent
                   ((wval, pn), fc) <- withExtent $ do
                       keyword "with"
                       wval <- bracketed syn
                       pn <- optProof
                       return (wval, pn)
                   openBlock
                   ds <- some $ fnDecl syn
                   closeBlock
                   let withs = map (fillLHSD n capp wargs) $ concat ds
                   return $ PWith fc n capp wargs wval pn withs)
      <?> "function clause"
  where
    lhsInfixApp :: IdrisParser (Name, FC, [PArg], [PTerm])
    lhsInfixApp = do l <- argExpr syn
                     (op, nfc) <- withExtent symbolicOperator
                     when (op == "=" || op == "?=" ) $
                          fail "infix clause definition with \"=\" and \"?=\" not supported "
                     let n = expandNS syn (sUN op)
                     r <- argExpr syn
                     wargs <- many (wExpr syn)
                     return (n, nfc, [pexp l, pexp r], wargs)
    lhsPrefixApp :: IdrisParser (Name, FC, [PArg], [PTerm])
    lhsPrefixApp = do (n, nfc) <- withExtent (expandNS syn <$> fnName)
                      args <- many (P.try (implicitArg (syn { inPattern = True } ))
                                    <|> P.try (constraintArg (syn { inPattern = True }))
                                    <|> (fmap pexp (argExpr syn)))
                      wargs <- many (wExpr syn)
                      return (n, nfc, args, wargs)
    lhs :: IdrisParser (Name, FC, PTerm, [PTerm])
    lhs = do ((n, nfc, args, wargs), lhs_fc) <- withExtent (P.try lhsInfixApp <|> lhsPrefixApp)
             let capp = PApp lhs_fc (PRef nfc [nfc] n) args
             return (n, nfc, capp, wargs)
    optProof = P.option Nothing (do keyword "proof"
                                    n <- withExtent fnName
                                    return (Just n))
    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 pn ws)
       = PWith fc n capp (owargs ++ wargs) v pn
            (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
wExpr :: SyntaxInfo -> IdrisParser PTerm
wExpr syn = do lchar '|'
               expr' (syn { inPattern = True })
            <?> "with pattern"
whereBlock :: Name -> SyntaxInfo -> IdrisParser ([PDecl], [(Name, Name)])
whereBlock n syn
    = do keyword "where"
         ds <- indentedBlock1 (decl syn)
         let dns = concatMap (concatMap declared) ds
         return (concat ds, map (\x -> (x, decoration syn x)) dns)
      <?> "where block"
codegen_ :: IdrisParser Codegen
codegen_ = do n <- identifier
              return (Via IBCFormat (map toLower n))
       <|> do reserved "Bytecode"; return Bytecode
       <?> "code generation language"
directive :: SyntaxInfo -> IdrisParser [PDecl]
directive syn = do P.try (lchar '%' *> reserved "lib")
                   cgn <- codegen_
                   lib <- stringLiteral
                   return [PDirective (DLib cgn lib)]
             <|> do P.try (lchar '%' *> reserved "link")
                    cgn <- codegen_; obj <- stringLiteral
                    return [PDirective (DLink cgn obj)]
             <|> do P.try (lchar '%' *> reserved "flag")
                    cgn <- codegen_; flag <- stringLiteral
                    return [PDirective (DFlag cgn flag)]
             <|> do P.try (lchar '%' *> reserved "include")
                    cgn <- codegen_
                    hdr <- stringLiteral
                    return [PDirective (DInclude cgn hdr)]
             <|> do P.try (lchar '%' *> reserved "hide"); n <- fnName
                    return [PDirective (DHide n)]
             <|> do P.try (lchar '%' *> reserved "freeze"); n <- iName []
                    return [PDirective (DFreeze n)]
             <|> do P.try (lchar '%' *> reserved "thaw"); n <- iName []
                    return [PDirective (DThaw n)]
             
             
             <|> do P.try (lchar '%' *> reserved "assert_injective"); n <- fnName
                    return [PDirective (DInjective n)]
             
             
             <|> do P.try (lchar '%' *> reserved "access")
                    acc <- accessibility
                    ist <- get
                    put ist { default_access = acc }
                    return [PDirective (DAccess acc)]
             <|> do P.try (lchar '%' *> reserved "default"); tot <- totality
                    i <- get
                    put (i { default_total = tot } )
                    return [PDirective (DDefault tot)]
             <|> do P.try (lchar '%' *> reserved "logging")
                    i <- natural
                    return [PDirective (DLogging i)]
             <|> do P.try (lchar '%' *> reserved "dynamic")
                    libs <- P.sepBy1 stringLiteral (lchar ',')
                    return [PDirective (DDynamicLibs libs)]
             <|> do P.try (lchar '%' *> reserved "name")
                    (ty, tyFC) <- withExtent fnName
                    ns <- P.sepBy1 (withExtent name) (lchar ',')
                    return [PDirective (DNameHint ty tyFC ns)]
             <|> do P.try (lchar '%' *> reserved "error_handlers")
                    (fn, nfc) <- withExtent fnName
                    (arg, afc) <- withExtent fnName
                    ns <- P.sepBy1 (withExtent name) (lchar ',')
                    return [PDirective (DErrorHandlers fn nfc arg afc ns) ]
             <|> do P.try (lchar '%' *> reserved "language"); ext <- pLangExt;
                    return [PDirective (DLanguage ext)]
             <|> do P.try (lchar '%' *> reserved "deprecate")
                    n <- fnName
                    alt <- P.option "" stringLiteral
                    return [PDirective (DDeprecate n alt)]
             <|> do P.try (lchar '%' *> reserved "fragile")
                    n <- fnName
                    alt <- P.option "" stringLiteral
                    return [PDirective (DFragile n alt)]
             <|> do fc <- extent $ P.try (lchar '%' *> reserved "used")
                    fn <- fnName
                    arg <- iName []
                    return [PDirective (DUsed fc fn arg)]
             <|> do P.try (lchar '%' *> reserved "auto_implicits")
                    b <- on_off
                    return [PDirective (DAutoImplicits b)]
             <?> "directive"
  where on_off = do reserved "on"; return True
             <|> do reserved "off"; return False
pLangExt :: IdrisParser LanguageExt
pLangExt = (reserved "TypeProviders" >> return TypeProviders)
       <|> (reserved "ErrorReflection" >> return ErrorReflection)
       <|> (reserved "UniquenessTypes" >> return UniquenessTypes)
       <|> (reserved "LinearTypes" >> return LinearTypes)
       <|> (reserved "DSLNotation" >> return DSLNotation)
       <|> (reserved "ElabReflection" >> return ElabReflection)
       <|> (reserved "FirstClassReflection" >> return FCReflection)
totality :: IdrisParser DefaultTotality
totality
        = do keyword "total";   return DefaultCheckingTotal
      <|> do keyword "partial"; return DefaultCheckingPartial
      <|> do keyword "covering"; return DefaultCheckingCovering
provider :: SyntaxInfo -> IdrisParser [PDecl]
provider syn = do doc <- P.try (do (doc, _) <- docstring syn
                                   highlight AnnKeyword $ lchar '%' *> reserved "provide"
                                   return doc)
                  provideTerm doc <|> providePostulate doc
               <?> "type provider"
  where provideTerm doc =
          do lchar '('; (n, nfc) <- withExtent fnName; lchar ':'; t <- typeExpr syn; lchar ')'
             keyword "with"
             (e, fc) <- withExtent (expr syn) <?> "provider expression"
             return  [PProvider doc syn fc nfc (ProvTerm t e) n]
        providePostulate doc =
          do keyword "postulate"
             (n, nfc) <- withExtent fnName
             keyword "with"
             (e, fc) <- withExtent (expr syn) <?> "provider expression"
             return [PProvider doc syn fc nfc (ProvPostulate e) n]
transform :: SyntaxInfo -> IdrisParser [PDecl]
transform syn = do P.try (lchar '%' *> reserved "transform")
                    
                    
                   l <- expr syn
                   fc <- extent $ symbol "==>"
                   r <- expr syn
                   return [PTransform fc False l r]
                <?> "transform"
runElabDecl :: SyntaxInfo -> IdrisParser PDecl
runElabDecl syn =
  do kwFC <- P.try (highlight AnnKeyword (extent $ lchar '%' *> reserved "runElab"))
     script <- expr syn <?> "elaborator script"
     return $ PRunElabDecl kwFC script (syn_namespace syn)
  <?> "top-level elaborator script"
parseExpr :: IState -> String -> Either ParseError PTerm
parseExpr st = runparser (fullExpr defaultSyntax) st "(input)"
parseConst :: IState -> String -> Either ParseError Const
parseConst st = runparser constant st "(input)"
parseTactic :: IState -> String -> Either ParseError PTactic
parseTactic st = runparser (fullTactic defaultSyntax) st "(input)"
parseElabShellStep :: IState -> String -> Either ParseError (Either ElabShellCmd PDo)
parseElabShellStep ist = runparser (Right <$> do_ defaultSyntax <|> Left <$> elabShellCmd) ist "(input)"
  where elabShellCmd = char ':' >>
                       (reserved "qed"     >> pure EQED       ) <|>
                       (reserved "abandon" >> pure EAbandon   ) <|>
                       (reserved "undo"    >> pure EUndo      ) <|>
                       (reserved "state"   >> pure EProofState) <|>
                       (reserved "term"    >> pure EProofTerm ) <|>
                       (expressionTactic ["e", "eval"] EEval ) <|>
                       (expressionTactic ["t", "type"] ECheck) <|>
                       (expressionTactic ["search"] ESearch   ) <|>
                       (do reserved "doc"
                           doc <- (Right <$> constant) <|> (Left <$> fnName)
                           P.eof
                           return (EDocStr doc))
                       <?> "elab command"
        expressionTactic cmds tactic =
           do asum (map reserved cmds)
              t <- spaced (expr defaultSyntax)
              i <- get
              return $ tactic (desugar defaultSyntax i t)
        spaced parser = indentGt *> parser
parseImports :: FilePath -> String -> Idris (Maybe (Docstring ()), [String], [ImportInfo], Maybe Mark)
parseImports fname input
    = do i <- getIState
         case runparser imports i fname input of
              Left err -> formatMessage err >>= ifail . show
              Right (x, annots, i) ->
                do putIState i
                   fname' <- runIO $ Dir.makeAbsolute fname
                   sendHighlighting $ addPath annots fname'
                   return x
  where imports :: IdrisParser ((Maybe (Docstring ()), [String],
                                 [ImportInfo],
                                 Maybe Mark),
                                [(FC, OutputAnnotation)], IState)
        imports = do optional shebang
                     whiteSpace
                     (mdoc, mname, annots) <- moduleHeader
                     ps_exp        <- many import_
                     mrk           <- mark
                     isEof         <- lookAheadMatches P.eof
                     let mrk' = if isEof
                                   then Nothing
                                   else Just mrk
                     i     <- get
                     
                     
                     let ps = ps_exp 
                     return ((mdoc, mname, ps, mrk'), annots, i)
        addPath :: [(FC, OutputAnnotation)] -> FilePath -> [(FC, OutputAnnotation)]
        addPath [] _ = []
        addPath ((fc, AnnNamespace ns Nothing) : annots) path =
           (fc, AnnNamespace ns (Just path)) : addPath annots path
        addPath (annot:annots) path = annot : addPath annots path
        shebang :: IdrisParser ()
        shebang = string "#!" *> many (P.satisfy $ not . isEol) *> eol *> pure ()
fixColour :: Bool -> PP.Doc -> PP.Doc
fixColour False doc = PP.plain doc
fixColour True doc  = doc
parseProg :: SyntaxInfo -> FilePath -> String -> Maybe Mark -> Idris [PDecl]
parseProg syn fname input mrk
    = do i <- getIState
         case runparser mainProg i fname input of
            Left err -> do emitWarning err
                           i <- getIState
                           putIState (i { errSpan = Just (messageExtent err) })
                           return []
            Right (x, i)  -> do putIState i
                                reportParserWarnings
                                return $ collect x
  where mainProg :: IdrisParser ([PDecl], IState)
        mainProg = case mrk of
                        Nothing -> do i <- get; return ([], i)
                        Just mrk -> do
                          restore mrk
                          ds <- prog syn
                          i' <- get
                          return (ds, i')
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 pn w] : ds)
           | n == n' = clauses j (PWith fc' n' l ws r pn (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 (POpenInterfaces f ns ps : ds) = POpenInterfaces f ns (collect ps) : collect ds
collect (PMutual f ms : ds) = PMutual f (collect ms) : collect ds
collect (PNamespace ns fc ps : ds) = PNamespace ns fc (collect ps) : collect ds
collect (PInterface doc f s cs n nfc ps pdocs fds ds cn cd : ds')
    = PInterface doc f s cs n nfc ps pdocs fds (collect ds) cn cd : collect ds'
collect (PImplementation doc argDocs f s cs pnames acc opts n nfc ps pextra t en ds : ds')
    = PImplementation doc argDocs f s cs pnames acc opts n nfc ps pextra t en (collect ds) : collect ds'
collect (d : ds) = d : collect ds
collect [] = []
loadModule :: FilePath -> IBCPhase -> Idris (Maybe String)
loadModule f phase
   = idrisCatch (loadModule' f phase)
                (\e -> do setErrSpan (getErrSpan e)
                          ist <- getIState
                          iWarn (getErrSpan e) $ pprintErr ist e
                          return Nothing)
loadModule' :: FilePath -> IBCPhase -> Idris (Maybe String)
loadModule' f phase
   = do i <- getIState
        let file = takeWhile (/= ' ') f
        ibcsd <- valIBCSubDir i
        ids <- rankedImportDirs file
        fp <- findImport ids ibcsd file
        if file `elem` imported i
          then do logParser 1 $ "Already read " ++ file
                  return Nothing
          else do putIState (i { imported = file : imported i })
                  case fp of
                    IDR fn  -> loadSource False fn Nothing
                    LIDR fn -> loadSource True  fn Nothing
                    IBC fn src ->
                      idrisCatch (loadIBC True phase fn)
                                 (\c -> do logParser 1 $ fn ++ " failed " ++ pshow i c
                                           case src of
                                             IDR sfn -> loadSource False sfn Nothing
                                             LIDR sfn -> loadSource True sfn Nothing)
                  return $ Just file
loadFromIFile :: Bool -> IBCPhase -> IFileType -> Maybe Int -> Idris ()
loadFromIFile reexp phase i@(IBC fn src) maxline
   = do logParser 1 $ "Skipping " ++ getSrcFile i
        logParser 3 $ "loadFromIFile i" ++ show i
        idrisCatch (loadIBC reexp phase fn)
                (\err -> ierror $ LoadingFailed fn err)
  where
    getSrcFile (IDR fn) = fn
    getSrcFile (LIDR fn) = fn
    getSrcFile (IBC f src) = getSrcFile src
loadFromIFile _ _ (IDR fn) maxline = loadSource' False fn maxline
loadFromIFile _ _ (LIDR fn) maxline = loadSource' True fn maxline
loadSource' :: Bool -> FilePath -> Maybe Int -> Idris ()
loadSource' lidr r maxline
   = idrisCatch (loadSource lidr r maxline)
                (\e -> do setErrSpan (getErrSpan e)
                          ist <- getIState
                          case e of
                            At f e' -> iWarn f (pprintErr ist e')
                            _ -> iWarn (getErrSpan e) (pprintErr ist e))
loadSource :: Bool -> FilePath -> Maybe Int -> Idris ()
loadSource lidr f toline
             = do logParser 1 ("Reading " ++ f)
                  iReport   2 ("Reading " ++ f)
                  i <- getIState
                  let def_total = default_total i
                  file_in <- runIO $ readSource f
                  file <- if lidr then tclift $ unlit f file_in else return file_in
                  (mdocs, mname, imports_in, pos) <- parseImports f file
                  ai <- getAutoImports
                  let imports = map (\n -> ImportInfo True n Nothing [] NoFC NoFC) ai ++ imports_in
                  ids <- rankedImportDirs f
                  ibcsd <- valIBCSubDir i
                  mapM_ (\(re, f, ns, nfc) ->
                               do fp <- findImport ids ibcsd f
                                  case fp of
                                      LIDR fn -> ifail $ "No ibc for " ++ f
                                      IDR fn -> ifail $ "No ibc for " ++ f
                                      IBC fn src ->
                                        do loadIBC True IBC_Building fn
                                           let srcFn = case src of
                                                         IDR fn -> Just fn
                                                         LIDR fn -> Just fn
                                                         _ -> Nothing
                                           srcFnAbs <- case srcFn of
                                                         Just fn -> fmap Just (runIO $ Dir.makeAbsolute fn)
                                                         Nothing -> return Nothing
                                           sendHighlighting [(nfc, AnnNamespace ns srcFnAbs)])
                        [(re, fn, ns, nfc) | ImportInfo re fn _ ns _ nfc <- imports]
                  reportParserWarnings
                  sendParserHighlighting
                  
                  let modAliases = M.fromList
                        [ (prep alias, prep realName)
                        | ImportInfo { import_reexport = reexport
                                     , import_path = realName
                                     , import_rename = Just (alias, _)
                                     , import_location = fc } <- imports
                        ]
                      prep = map T.pack . reverse . Spl.splitOn [pathSeparator]
                      aliasNames = [ (alias, fc)
                                   | ImportInfo { import_rename = Just (alias, fc)
                                                } <- imports
                                   ]
                      histogram = groupBy ((==) `on` fst) . sortBy (comparing fst) $ aliasNames
                  case map head . filter ((/= 1) . length) $ histogram of
                    []       -> logParser 3 $ "Module aliases: " ++ show (M.toList modAliases)
                    (n,fc):_ -> throwError . At fc . Msg $ "import alias not unique: " ++ show n
                  i <- getIState
                  putIState (i { default_access = Private, module_aliases = modAliases })
                  clearIBC 
                  
                  imps <- allImportDirs
                  mapM_ addIBC (map IBCImportDir imps)
                  mapM_ (addIBC . IBCImport)
                    [ (reexport, realName)
                    | ImportInfo { import_reexport = reexport
                                 , import_path = realName
                                 } <- imports
                    ]
                  let syntax = defaultSyntax{ syn_namespace = reverse mname,
                                              maxline = toline }
                  ist <- getIState
                  
                  
                  let oldSpan = idris_parsedSpan ist
                  ds' <- parseProg syntax f file pos
                  case (ds', oldSpan) of
                    ([], Just fc) ->
                      
                      
                      
                      do ist <- getIState
                         putIState ist { idris_parsedSpan = oldSpan
                                       , ibc_write = IBCParsedRegion fc :
                                                     ibc_write ist
                                       }
                    _ -> return ()
                  sendParserHighlighting
                  
                  let ds = namespaces mname ds'
                  logParser 3 (show $ showDecls verbosePPOption ds)
                  i <- getIState
                  logLvl 10 (show (toAlist (idris_implicits i)))
                  logLvl 3 (show (idris_infixes i))
                  
                  iReport 1 $ "Type checking " ++ f
                  
                  
                  
                  elabDecls (toplevelWith f) (map toMutual ds)
                  i <- getIState
                  
                  
                  mapM_ (\n -> do logLvl 5 $ "Simplifying " ++ show n
                                  ctxt' <-
                                    do ctxt <- getContext
                                       tclift $ simplifyCasedef n [] [] (getErasureInfo i) ctxt
                                  setContext ctxt')
                           (map snd (idris_totcheck i))
                  
                  iReport 3 $ "Totality checking " ++ f
                  logLvl 1 $ "Totality checking " ++ f
                  i <- getIState
                  mapM_ buildSCG (idris_totcheck i)
                  mapM_ checkDeclTotality (idris_totcheck i)
                  mapM_ verifyTotality (idris_totcheck i)
                  
                  let deftots = idris_defertotcheck i
                  logLvl 2 $ "Totality checking " ++ show deftots
                  mapM_ (\x -> do tot <- getTotality x
                                  case tot of
                                       Total _ ->
                                         do let opts = case lookupCtxtExact x (idris_flags i) of
                                                          Just os -> os
                                                          Nothing -> []
                                            when (AssertTotal `notElem` opts) $
                                                setTotality x Unchecked
                                       _ -> return ()) (map snd deftots)
                  mapM_ buildSCG deftots
                  mapM_ checkDeclTotality deftots
                  logLvl 1 ("Finished " ++ f)
                  ibcsd <- valIBCSubDir i
                  logLvl  1 $ "Universe checking " ++ f
                  iReport 3 $ "Universe checking " ++ f
                  iucheck
                  let ibc = ibcPathNoFallback ibcsd f
                  i <- getIState
                  addHides (hide_list i)
                  
                  i <- getIState
                  case mdocs of
                    Nothing   -> return ()
                    Just docs -> addModDoc syntax mname docs
                  
                  ok <- noErrors
                  when ok $
                    do idrisCatch (do writeIBC f ibc; clearIBC)
                                  (\c -> return ()) 
                       hl <- getDumpHighlighting
                       when hl $
                         idrisCatch (writeHighlights f)
                                    (const $ return ()) 
                  clearHighlights
                  i <- getIState
                  putIState (i { default_total = def_total,
                                 hide_list = emptyContext })
                  return ()
  where
    namespaces :: [String] -> [PDecl] -> [PDecl]
    namespaces []     ds = ds
    namespaces (x:xs) ds = [PNamespace x NoFC (namespaces xs ds)]
    toMutual :: PDecl -> PDecl
    toMutual m@(PMutual _ d) = m
    toMutual (PNamespace x fc ds) = PNamespace x fc (map toMutual ds)
    toMutual (POpenInterfaces f ns ds) = POpenInterfaces f ns (map toMutual ds)
    toMutual x = let r = PMutual (fileFC "single mutual") [x] in
                 case x of
                   PClauses{} -> r
                   PInterface{} -> r
                   PData{} -> r
                   PImplementation{} -> r
                   _ -> x
    addModDoc :: SyntaxInfo -> [String] -> Docstring () -> Idris ()
    addModDoc syn mname docs =
      do ist <- getIState
         docs' <- elabDocTerms (toplevelWith f) (parsedDocs ist)
         let modDocs' = addDef docName docs' (idris_moduledocs ist)
         putIState ist { idris_moduledocs = modDocs' }
         addIBC (IBCModDocs docName)
      where
        docName = NS modDocName (map T.pack (reverse mname))
        parsedDocs ist = annotCode (tryFullExpr syn ist) docs
addHides :: Ctxt Accessibility -> Idris ()
addHides xs = mapM_ doHide (toAlist xs)
  where doHide (n, a) = do setAccessibility n a
                           addIBC (IBCAccess n a)