{-# LANGUAGE OverloadedStrings, CPP #-}

module Haskintex (haskintex) where

-- System
import System.Process (readProcess)
import System.FilePath
import System.Directory
import System.IO (hFlush,stdout)
-- Text
import Data.Text (pack,unpack)
import qualified Data.Text as T
import qualified Data.Text.IO as T
import Data.Text.Encoding
-- Parser
import Text.Parsec hiding (many,(<|>))
import Text.Parsec.Text ()
-- Transformers
import Control.Monad (when,unless,replicateM)
import Control.Monad.Trans.Class
import Control.Monad.Trans.State
-- LaTeX
import Text.LaTeX hiding (version)
import qualified Text.LaTeX as Hatex
import Text.LaTeX.Base.Syntax
-- Utils
import Control.Applicative
#if !MIN_VERSION_base(4,8,0)
import Data.Foldable (foldMap)
#endif
import Numeric (showFFloat)
-- Paths
import Paths_haskintex (version)
import Data.Version (showVersion)
-- Lists
import Data.List (intersperse, isSuffixOf)
-- GHC
import Language.Haskell.Interpreter hiding (get)
import Language.Haskell.Interpreter.Unsafe (unsafeRunInterpreterWithArgs)
import Data.Typeable
import qualified Language.Haskell.Exts.Pretty as H
import qualified Language.Haskell.Exts.Parser as H
import qualified Language.Haskell.Exts.Syntax as H
-- Map
import qualified Data.Map as M
-- Binary
import Data.Binary.Put
import Data.Binary.Get hiding (lookAhead)
import Data.ByteString.Lazy (ByteString)
import qualified Data.ByteString.Lazy as LB
import qualified Data.ByteString as SB

-- Syntax

-- | The 'Syntax' datatype describes how haskintex see a LaTeX
--   file. When haskintex processes an input file, it parsers
--   to this structure. It differentiates between these parts:
--
-- * writehaskell environments (WriteHaskell), either marked
--   visible or not, located either in the header (for pragmas)
--   or in the body (for regular code).
--
-- * Haskell expression of type 'LaTeX' (InsertHaTeX).
--   See the HaTeX package for details about this type.
--
-- * Haskell expression of tyep 'IO LaTeX' (InsertHaTeXIO).
--   Exactly like InsertHaTeX, but within the IO monad.
--
-- * evalhaskell commands and environments (EvalHaskell).
--
-- * Anything else (WriteLaTeX).
--
data Syntax =
    WriteLaTeX   Text
  | WriteHaskell Bool -- Visibility: False for Hidden, True for Visible
                 Bool -- Location: True for Header, False for Body
                 Text
  | InsertHaTeX  Bool -- Memorized expression?
                 Text
  | InsertHaTeXIO Bool -- Memorized expression?
                  Text
  | EvalHaskell  Bool -- Type: False for Command, True for Environment
                 Bool -- Memorized expression?
                 Text
  | Sequence     [Syntax]
    deriving Show -- Show instance for debugging.

-- Configuration

data Conf = Conf
  { keepFlag      :: Bool
  , visibleFlag   :: Bool
  , verboseFlag   :: Bool
  , manualFlag    :: Bool
  , helpFlag      :: Bool
  , lhs2texFlag   :: Bool
  , stdoutFlag    :: Bool
  , overwriteFlag :: Bool
  , debugFlag     :: Bool
  , memoFlag      :: Bool
  , memocleanFlag :: Bool
  , autotexyFlag  :: Bool
  , nosandboxFlag :: Bool
  , unknownFlags  :: [String]
  , inputs        :: [FilePath]
  , memoTree      :: MemoTree
    }

supportedFlags :: [(String,Conf -> Bool)]
supportedFlags =
  [ ("keep"      , keepFlag)
  , ("visible"   , visibleFlag)
  , ("verbose"   , verboseFlag)
  , ("manual"    , manualFlag)
  , ("help"      , helpFlag)
  , ("lhs2tex"   , lhs2texFlag)
  , ("stdout"    , stdoutFlag)
  , ("overwrite" , overwriteFlag)
  , ("debug"     , debugFlag)
  , ("memo"      , memoFlag)
  , ("memoclean" , memocleanFlag)
  , ("autotexy"  , autotexyFlag)
  , ("nosandbox" , nosandboxFlag)
    ]

readConf :: [String] -> Conf
readConf = go $ Conf False False False False False False False False False False False False False [] [] M.empty
  where
    go c [] = c
    go c (x:xs) =
       case x of
        -- Arguments starting with '-' are considered a flag.
        ('-':flag) ->
           case flag of
             "keep"      -> go (c {keepFlag      = True}) xs
             "visible"   -> go (c {visibleFlag   = True}) xs
             "verbose"   -> go (c {verboseFlag   = True}) xs
             "manual"    -> go (c {manualFlag    = True}) xs
             "help"      -> go (c {helpFlag      = True}) xs
             "lhs2tex"   -> go (c {lhs2texFlag   = True}) xs
             "stdout"    -> go (c {stdoutFlag    = True}) xs
             "overwrite" -> go (c {overwriteFlag = True}) xs
             "debug"     -> go (c {debugFlag     = True}) xs
             "memo"      -> go (c {memoFlag      = True}) xs
             "memoclean" -> go (c {memocleanFlag = True}) xs
             "autotexy"  -> go (c {autotexyFlag  = True}) xs
             "nosandbox" -> go (c {nosandboxFlag = True}) xs
             _           -> go (c {unknownFlags = unknownFlags c ++ [flag]}) xs
        -- Otherwise, an input file.
        _ -> go (c {inputs = inputs c ++ [x]}) xs

-- Haskintex monad

type Haskintex = StateT Conf IO

outputStr :: String -> Haskintex ()
outputStr str = do
  b <- verboseFlag <$> get
  when b $ lift $ putStrLn str

-- PARSING

type Parser = ParsecT Text () Haskintex

parseSyntax :: Parser Syntax
parseSyntax = do
  s <- fmap Sequence $ many $ choice [ p_writehaskell, p_inserthatex False , p_inserthatex True , p_evalhaskell, p_writelatex ]
  eof
  return s

p_writehaskell :: Parser Syntax
p_writehaskell = do
  isH <- (try $ string "\\begin{writehaskell}" >> return False)
           <|> (try $ string "\\begin{haskellpragmas}" >> return True)
  b <- choice $ fmap try [ string "[hidden]"  >> return False
                         , string "[visible]" >> return True
                         , lift $ visibleFlag <$> get ] -- When no option is given, take the default.
  h <- manyTill anyChar $ try $ string $ if isH then "\\end{haskellpragmas}" else "\\end{writehaskell}"
  return $ WriteHaskell b isH $ pack h

readMemo :: Parser Bool
readMemo = (char '[' *> choice xs <* char ']') <|> lift (memoFlag <$> get)
  where
    xs = [ string "memo" >> return True
         , string "notmemo" >> return False ]

processExp :: (H.Exp -> H.Exp) -- ^ Transformation to apply to Haskell Expression
           -> Text -- ^ Haskell expression
           -> Parser Text
processExp f t = do
  return $ case H.parseExp (unpack t) of
    H.ParseOk e -> pack $ H.prettyPrint $ f e
    _ -> t

p_inserthatex :: Bool -- False for pure, True for IO
              -> Parser Syntax
p_inserthatex isIO = do
  --
  let iden = if isIO then "iohatex" else "hatex"
      cons = if isIO then InsertHaTeXIO else InsertHaTeX
  --
  _ <- try $ string $ '\\' : iden
  b <- readMemo
  _ <- char '{'
  h <- p_haskell 0
  auto <- lift $ autotexyFlag <$> get
  let v = H.Var . H.UnQual . H.Ident
      f = if auto then H.App $ if isIO then v "fmap" `H.App` v "texy"
                                       else v "texy" 
                  else id
  cons b <$> processExp f (pack h)

p_evalhaskell :: Parser Syntax
p_evalhaskell = choice [ p_evalhaskellenv, p_evalhaskellcomm ]

p_evalhaskellenv :: Parser Syntax
p_evalhaskellenv = do
  _ <- try $ string "\\begin{evalhaskell}"
  b <- readMemo
  h <- manyTill anyChar $ try $ string "\\end{evalhaskell}"
  EvalHaskell True b <$> processExp id (pack h)

p_evalhaskellcomm :: Parser Syntax
p_evalhaskellcomm = do
  _  <- try $ string "\\evalhaskell"
  b <- readMemo
  _ <- char '{'
  h  <- p_haskell 0
  EvalHaskell False b <$> processExp id (pack h)

p_haskell :: Int -> Parser String
p_haskell n = choice [
    do _ <- char '{'
       ('{':) <$> p_haskell (n+1)
  , do _ <- char '}'
       if n == 0
          then return []
          else ('}':) <$> p_haskell (n-1)
  , do _ <- char '\"'
       liftA2 (++) (('\"':) <$> p_string) (p_haskell n)
  , try (string "'{'") >> return "'{'"
  , try (string "'}'") >> return "'}'"
  , liftA2 (:) anyChar (p_haskell n)
    ]

p_string :: Parser String
p_string = choice [
    try $ liftA2 (++) (char '\\' >> char '\"' >> return "\\\"") p_string
  , liftA2 (:) (char '\"') (return [])
  , liftA2 (:) anyChar p_string
    ]

p_writelatex :: Parser Syntax
p_writelatex = (WriteLaTeX . pack) <$>
  many1 (p_other >>= \b -> if b then anyChar else fail "stop write latex")
  where
    p_other =
      choice $ fmap (try . lookAhead)
             [ string "\\begin{writehaskell}"   >> return False -- starts p_writehaskell (for body)
             , string "\\begin{haskellpragmas}" >> return False -- starts p_writehaskell (for header)
             , string "\\hatex"                 >> return False -- starts p_inserthatex
             , string "\\iohatex"               >> return False -- starts p_inserthatexio
             , string "\\begin{evalhaskell}"    >> return False -- starts p_evalhaskellenv
             , string "\\evalhaskell"           >> return False -- starts p_evalhaskellcomm
             , return True
             ]

----------------------------------------------------------
----------------------------------------------------------
-- MEMO TREE

-- | A 'MemoTree' maps each expression to its reduced form.
type MemoTree = M.Map Text Text

memoreduce :: Typeable t
           => String -- ^ Auxiliar module name
           -> Bool -- ^ Is this expression memorized?
           -> Text -- ^ Input
           -> t -- ^ Type
           -> (t -> Haskintex Text) -- ^ Rendering function
           -> Haskintex Text
memoreduce modName isMemo t ty f = do
  let e = unpack t
  outputStr $ "Evaluation (" ++ showsTypeRep (typeRep $ Just ty) "" ++ "): " ++ e
  memt <- memoTree <$> get
  let p = if isMemo then M.lookup t memt else Nothing
  case p of
    Nothing -> do
      let int = do
             loadModules [modName]
             setTopLevelModules [modName]
             setImports ["Prelude"]
             interpret e ty
      -- Sandbox recognition
      inSandbox <- lift $ doesDirectoryExist ".cabal-sandbox"
      r <- if inSandbox
              then do outputStr "Sandbox detected."
                      noSandbox <- nosandboxFlag <$> get
                      if noSandbox
                         then do outputStr "Ignoring sandbox."
                                 runInterpreter int
                         else do sand <- lift $ getDirectoryContents ".cabal-sandbox"
                                 let pkgdbs = filter (isSuffixOf "packages.conf.d") sand
                                 case pkgdbs of
                                   pkgdb : _ -> do
                                     outputStr $ "Using sandbox package db: " ++ pkgdb
                                     unsafeRunInterpreterWithArgs ["-package-db .cabal-sandbox/" ++ pkgdb] int
                                   _ -> runInterpreter int
              else runInterpreter int
      case r of
        Left err -> do
          outputStr $ "Warning: Error while evaluating the expression.\n"
                   ++ errorString err
          return mempty
        Right x -> do
          -- Render result
          t' <- f x
          -- If the expression is marked to be memorized, store it in the 'MemoTree'.
          when isMemo $ do
            modify $ \st -> st { memoTree = M.insert t t' $ memoTree st }
            outputStr $ "-> Result has been memorized."
          -- Return result
          return t'
    Just o -> do
      outputStr "-> Result of the evaluation recovered from memo tree."
      return o

{- Memo Tree Format

A memo tree is stored as a list of (key,value) in key ascending order.
Keys and values are encoded in UTF-8.

| offset |   description        | size (in bytes) |
---------------------------------------------------
| 00     | Number of blocks     | 2               |
| 02     | Zero or more blocks  | variable        |

Each block has the following structure:

| offset |   description        | size (in bytes) |
---------------------------------------------------
| 00     | Length of key (k)    | 2               |
| 02     | Length of value (v)  | 2               |
| 04     | Key                  | k               |
| 04+k   | Value                | v               |

-}

memoTreeToBinary :: MemoTree -> ByteString
memoTreeToBinary memt = runPut $ do
  putWord16le $ fromIntegral $ M.size memt
  mapM_ (\(t,t') -> do
    let b  = encodeUtf8 t
        b' = encodeUtf8 t'
    putWord16le $ fromIntegral $ SB.length b
    putWord16le $ fromIntegral $ SB.length b'
    putByteString b
    putByteString b'
    ) $ M.toAscList memt

memoTreeFromBinary :: ByteString -> Either String MemoTree
memoTreeFromBinary b =
  case runGetOrFail getMemoTree b of
    Left (_,_,err) -> Left err
    Right (_,_,memt) -> Right memt

getMemoTree :: Get MemoTree
getMemoTree = do
  n <- fromIntegral <$> getWord16le
  fmap M.fromAscList $ replicateM n $ do
    l  <- getWord16le
    l' <- getWord16le
    b  <- getByteString $ fromIntegral l
    b' <- getByteString $ fromIntegral l'
    return (decodeUtf8 b, decodeUtf8 b')

memoTreeOpen :: Haskintex ()
memoTreeOpen = do
  d <- liftIO $ getAppUserDataDirectory "haskintex"
  let fp = d </> "memotree"
  b <- liftIO $ doesFileExist fp
  if b then do t <- liftIO $ LB.readFile fp
               case memoTreeFromBinary t of
                 Left err -> do
                   outputStr $ "Error: memotree failed to read: " ++ err
                   outputStr "-> Using empty memotree."
                   modify $ \st -> st { memoTree = M.empty }
                 Right memt -> do
                   modify $ \st -> st { memoTree = memt }
                   let n = LB.length t
                       kbs :: Double
                       kbs = fromIntegral n / 1024
                       s = if kbs < 1 then show n ++ " Bs"
                                      else showFFloat (Just 2) kbs " KBs"
                   outputStr $ "Info: memotree loaded (" ++ s ++ ")."
       else do outputStr "Info: memotree does not exist."
               outputStr "-> Using empty memotree."
               modify $ \st -> st { memoTree = M.empty }

memoTreeSave :: Haskintex ()
memoTreeSave = do
  memt <- memoTree <$> get
  unless (M.null memt) $ do
    outputStr "Saving memotree..."
    liftIO $ do
      d <- getAppUserDataDirectory "haskintex"
      createDirectoryIfMissing True d
      let fp = d </> "memotree"
      LB.writeFile fp $ memoTreeToBinary memt
    outputStr "Info: memotree saved."

memoTreeClean :: Haskintex ()
memoTreeClean = do
  d <- liftIO $ getAppUserDataDirectory "haskintex"
  let fp = d </> "memotree"
  b <- liftIO $ doesFileExist fp
  when b $ do 
    liftIO $ removeFile fp
    outputStr "Info: memotree removed."

----------------------------------------------------------

-- PASS 1: Extract code from processed Syntax.

extractCode :: Syntax -> (Text,Text)
extractCode (WriteHaskell _ isH t) = if isH then (t,mempty) else (mempty,t)
extractCode (Sequence xs) = foldMap extractCode xs
extractCode _ = mempty

-- PASS 2: Evaluate Haskell expressions from processed Syntax.

evalCode :: String -- ^ Auxiliary module name
         -> Syntax -> Haskintex Text
evalCode modName = go
  where
    go (WriteLaTeX t) = return t
    go (WriteHaskell b _ t) = do
         mFlag <- manualFlag <$> get
         lhsFlag <- lhs2texFlag <$> get
         let f :: Text -> LaTeX
             f x | not b = mempty
                 | mFlag = raw x
                 | lhsFlag = TeXEnv "code" [] $ raw x
                 | otherwise = verbatim x
         return $ render $ f t
    go (InsertHaTeX   isMemo t) = memoreduce modName isMemo t (as ::    LaTeX) (return .      render)
    go (InsertHaTeXIO isMemo t) = memoreduce modName isMemo t (as :: IO LaTeX) (liftIO . fmap render)
    go (EvalHaskell env isMemo t) = do
         mFlag <- manualFlag <$> get
         lhsFlag <- lhs2texFlag <$> get
         let f :: Text -> LaTeX
             f x | mFlag = raw x -- Manual flag overrides lhs2tex flag behavior
                 | env && lhsFlag = TeXEnv "code" [] $ raw x
                 | lhsFlag = raw $ "|" <> x <> "|"
                 | env = verbatim $ layout x
                 | otherwise = verb x
         (render . f) <$> ghc modName isMemo t
    go (Sequence xs) = mconcat <$> mapM go xs

ghc :: String -> Bool -> Text -> Haskintex Text
ghc modName isMemo t = do
  let e = unpack t
  outputStr $ "Evaluation: " ++ e
  memt <- memoTree <$> get
  let p = if isMemo then M.lookup t memt else Nothing
  case p of
    Nothing -> do
      -- Run GHC externally and read the result.
      r <- lift $ pack . init <$> readProcess "ghc" 
                -- Disable reading of .ghci files.
                [ "-ignore-dot-ghci"
                -- Evaluation loading the temporal module.
                , "-e", e, modName ++ ".hs"
                  ] []
      -- If the expression is marked to be memorized, we do so.
      when isMemo $ do
         modify $ \st -> st { memoTree = M.insert t r $ memoTree st }
         outputStr "-> Result has been memorized."
      -- Return result
      return r
    Just o -> do
      outputStr "-> Result of the evaluation recovered from memo tree."
      return o

maxLineLength :: Int
maxLineLength = 60

-- | Break lines longer than 'maxLineLenght'.
layout :: Text -> Text
layout = T.unlines . go . T.lines
  where
    go [] = []
    go (t:ts) =
      if T.length t > maxLineLength
         then let (l,r) = T.splitAt maxLineLength t
              in  l : go (r:ts)
         else t : go ts

-- Errors

errorString :: InterpreterError -> String
errorString (UnknownError e) = "Unknown error: " ++ e
errorString (WontCompile es) = "Compiler error:\n" ++ init (unlines $ fmap errMsg es)
errorString (NotAllowed e) = "Not allowed:" ++ e
errorString (GhcException e) = "GHC exception: " ++ e

-- Haskintex main function

-- | Run haskintex with the given arguments. For example:
--
-- > haskintex ["-visible","-overwrite","foo.htex"]
--
--   Useful if you want to call /haskintex/ from another program.
--
haskintex :: [String] -> IO ()
haskintex = evalStateT haskintexmain . readConf

haskintexmain :: Haskintex ()
haskintexmain = do
  flags <- get
  if -- If the help flag is passed, ignore everything else
     -- and just print the help.
     helpFlag flags
     then lift $ putStr help
     else let xs = inputs flags
          in  if null xs
                 then lift $ putStr noFiles
                 else do memoTreeOpen
                         mapM_ haskintexFile xs
                         willClean <- memocleanFlag <$> get
                         if willClean then memoTreeClean else memoTreeSave

commas :: [String] -> String
commas = concat . intersperse ", "

showEnabledFlags :: Haskintex ()
showEnabledFlags = do
  c <- get
  outputStr $ "Enabled flags: "
           ++ commas (foldr (\(str,f) xs -> if f c then str : xs else xs) [] supportedFlags)
           ++ "."

reportWarnings :: Haskintex ()
reportWarnings = do
  -- Combination of manual and lhs2tex flags.
  manFlag <- manualFlag  <$> get
  lhsFlag <- lhs2texFlag <$> get
  when (manFlag && lhsFlag) $
    outputStr "Warning: lhs2tex flag is useless in presence of manual flag."

haskintexFile :: FilePath -> Haskintex ()
haskintexFile fp_ = do
  -- If the given file does not exist, try adding '.htex'.
  b <- lift $ doesFileExist fp_
  let fp = if b then fp_ else fp_ ++ ".htex"
  -- Report enabled flags
  showEnabledFlags
  -- Warnings
  reportWarnings
  -- Other unknown flags passed.
  uFlags <- unknownFlags <$> get
  unless (null uFlags) $
    outputStr $ "Unsupported flags: " ++ commas uFlags ++ "."
  -- File parsing.
  outputStr $ "Reading " ++ fp ++ "..."
  t <- lift $ T.readFile fp
  pres <- runParserT parseSyntax () fp t
  case pres of
    Left err -> outputStr $ "Reading of " ++ fp ++ " failed:\n" ++ show err
    Right s -> do
      -- Zero pass: In case of debugging, write down the parsed AST.
      dbugFlag <- debugFlag <$> get
      when dbugFlag $ do
        let debugfp = dropExtension (takeFileName fp) ++ ".debughtex"
        outputStr $ "Writing file " ++ debugfp ++ " with debugging output..."
        lift $ writeFile debugfp $ show s
      -- First pass: Create haskell source from the code obtained with 'extractCode'.
      let modName = ("Haskintex_" ++) $ dropExtension $ takeFileName fp
      outputStr $ "Creating Haskell source file " ++ modName ++ ".hs..."
      let (hsH,hs) = extractCode s
          moduleHeader = pack $ "\nmodule " ++ modName ++ " where\n\n"
      lift $ T.writeFile (modName ++ ".hs") $ hsH <> moduleHeader <> hs
      -- Second pass: Evaluate expressions using 'evalCode'.
      outputStr $ "Evaluating expressions in " ++ fp ++ "..."
      l <- evalCode modName s
      -- Write final output.
      let fp' = dropExtension (takeFileName fp) ++ ".tex"
          writeit = do outputStr $ "Writing final file at " ++ fp' ++ "..."
                       lift $ T.writeFile fp' l
      outFlag <- stdoutFlag <$> get
      overFlag <- overwriteFlag <$> get
      nonew <- lift $ doesFileExist fp'
      let finalOutput
           | outFlag = do outputStr "Sending final output to stdout..."
                          lift $ T.putStr l
           | overFlag = writeit
           | nonew = do lift $ putStr $ "File " ++ fp' ++ " already exists. Overwrite?"
                                     ++ " (use -overwrite to overwrite by default) "
                        lift $ hFlush stdout -- To immediately show the text on Windows systems.
                        resp <- lift getLine
                        if resp `elem` ["","y","yes"]
                           then writeit
                           else outputStr "No file was written."
           | otherwise = writeit
      finalOutput
      -- If the keep flag is not set, remove the haskell source file.
      kFlag <- keepFlag <$> get
      unless kFlag $ do
        outputStr $ "Removing Haskell source file " ++ modName ++ ".hs "
                  ++ "(use -keep to avoid this)..."
        lift $ removeFile $ modName ++ ".hs"
      -- End.
      outputStr $ "End of processing of file " ++ fp ++ "."

-- MESSAGES

help :: String
help = unlines [
    "You are using haskintex version " ++ showVersion version ++ "."
  , "http://daniel-diaz.github.io/projects/haskintex"
  , ""
  , "The underlying HaTeX version is " ++ showVersion Hatex.version ++ "."
  , ""
  , "Usage and flags:"
  , "Any argument passed to haskintex that starts with '-' will be considered"
  , "a flag. Otherwise, it will be considered an input file. Every input file"
  , "will be processed with the same set of flags, which will include all the"
  , "flags passed in the call. This is the list of flags supported by haskintex:"
  , ""
  , "  -keep       haskintex creates an intermmediate Haskell file before"
  , "              evaluating any expressions. By default, this file is "
  , "              eliminated after processing the file. Pass this flag to"
  , "              keep the file."
  , ""
  , "  -visible    By default, code written inside a writehaskell environment"
  , "              is not shown in the LaTeX output. This flag changes the"
  , "              default."
  , ""
  , "  -verbose    If this flag is enabled, haskintex will print information"
  , "              about its own execution while running."
  , ""
  , "  -manual     By default, Haskell expressions, either from writehaskell "
  , "              or evalhaskell, appear in the LaTeX output inside verb or"
  , "              verbatim declarations. If this flag is passed, neither verb"
  , "              nor verbatim will be used. The code will be written as text "
  , "              as it is. The user will decide how to handle it."
  , ""
  , "  -help       This flags cancels any other flag or input file and makes"
  , "              the program simply show this help message."
  , ""
  , "  -stdout     Instead of writing the output to a file, send it to the"
  , "              standard output stream (stdout)."
  , ""
  , "  -lhs2tex    Instead of using verb or verbatim declarations, format the"
  , "              output using the syntax accepted by lhs2TeX."
  , ""
  , "  -overwrite  Overwrite the output file if it already exists. If this flag"
  , "              is not set, the program will ask before overwriting."
  , ""
  , "  -debug      Only for debugging purposes. It writes a file with extension"
  , "              .debughtex with the AST of the internal representation of the"
  , "              input file haskintex uses."
  , ""
  , "  -memo       Unless otherwise specified, every evalhaskell, hatex or iohatex"
  , "              command (or environment) will be called with the memo option."
  , ""
  , "  -memoclean  Cleans the memo tree after the execution of haskintex. If "
  , "              several files are processed, the memo tree will be cleaned"
  , "              after processing all of them."
  , ""
  , "  -autotexy   Apply the function texy from HaTeX to every expression in a hatex"
  , "              or iohatex command. This effectively allows the user to write"
  , "              expressions in types other than LaTeX and have haskintex to perform"
  , "              the required transformation."
  , ""
  , "  -nosandbox  Do not use the sandbox package db even in the presence of one."
  , ""
  , "Any unsupported flag will be ignored."
  ]

noFiles :: String
noFiles = "No input file given.\n"