{-# LANGUAGE OverloadedStrings #-}
module Main (main) where
-- System
import System.Process
import System.Environment (getArgs)
import System.FilePath
import System.Directory
-- Text
import Data.Text (pack,unpack)
import qualified Data.Text as T
import qualified Data.Text.IO as T
-- Parser
import Data.Attoparsec.Text
-- Transformers
import Control.Monad (when,unless)
import Control.Monad.Trans.Class
import Control.Monad.Trans.Reader
-- LaTeX
import Text.LaTeX
-- Utils
import Control.Applicative
import Data.Foldable (foldMap)
-- Syntax
data Syntax =
WriteLaTeX Text
| WriteHaskell Bool Text -- False for Hidden, True for Visible
| EvalHaskell Bool Text -- False for Command, True for Environment
| Sequence [Syntax]
-- Parsing
parseSyntax :: Bool -> Parser Syntax
parseSyntax v = fmap Sequence $ many $ choice [ p_writehaskell v, p_evalhaskell, p_writelatex ]
p_writehaskell :: Bool -> Parser Syntax
p_writehaskell v = do
_ <- string "\\begin{writehaskell}"
b <- choice [ string "[hidden]" >> return False
, string "[visible]" >> return True
, return v ]
h <- manyTill anyChar $ try $ string "\\end{writehaskell}"
return $ WriteHaskell b $ pack h
p_evalhaskell :: Parser Syntax
p_evalhaskell = choice [ p_evalhaskellenv, p_evalhaskellcomm ]
p_evalhaskellenv :: Parser Syntax
p_evalhaskellenv = do
_ <- string "\\begin{evalhaskell}"
h <- manyTill anyChar $ try $ string "\\end{evalhaskell}"
return $ EvalHaskell True $ pack h
p_evalhaskellcomm :: Parser Syntax
p_evalhaskellcomm = do
_ <- string "\\evalhaskell{"
h <- manyTill anyChar $ char '}'
return $ EvalHaskell False $ pack h
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 [ string "\\begin{writehaskell}" >> return False
, string "\\begin{evalhaskell}" >> return False
, string "\\evalhaskell" >> return False
, return True
]
-- PASS 1: Extract code from processed Syntax.
extractCode :: Syntax -> Text
extractCode (WriteHaskell _ t) = t
extractCode (Sequence xs) = foldMap extractCode xs
extractCode _ = mempty
-- PASS 2: Evaluate Haskell expressions from processed Syntax.
evalCode :: String -> Syntax -> Haskintex Text
evalCode modName = go
where
go (WriteLaTeX t) = return t
go (WriteHaskell b t) =
return $ if b then render (verbatim t :: LaTeX)
else mempty
go (EvalHaskell b t) =
let f :: Text -> LaTeX
f = if b then verbatim . layout else verb
in (render . f . pack) <$> ghc modName t
go (Sequence xs) = mconcat <$> mapM go xs
ghc :: String -> Text -> Haskintex String
ghc modName e = do
let e' = unpack $ T.strip e
outputStr $ "Evaluation: " ++ e'
lift $ init <$> readProcess "ghc" [ "-e", e', modName ++ ".hs" ] []
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
-- Configuration
data Conf = Conf
{ keepFlag :: Bool
, visibleFlag :: Bool
, verboseFlag :: Bool
, inputs :: [FilePath]
}
isArg :: String -> Bool
isArg [] = False
isArg (x:_) = x == '-'
readConf :: [String] -> Conf
readConf xs =
Conf ("-keep" `elem` xs)
("-visible" `elem` xs)
("-verbose" `elem` xs)
(filter (not . isArg) xs)
-- Haskintex
type Haskintex = ReaderT Conf IO
outputStr :: String -> Haskintex ()
outputStr str = do
b <- verboseFlag <$> ask
when b $ lift $ putStrLn str
-- MAIN
main :: IO ()
main = (readConf <$> getArgs) >>= runReaderT haskintex
haskintex :: Haskintex ()
haskintex = ask >>= mapM_ haskintexFile . inputs
haskintexFile :: FilePath -> Haskintex ()
haskintexFile fp_ = do
-- If the given file does not exist, try adding '.tex'.
b <- lift $ doesFileExist fp_
let fp = if b then fp_ else fp_ ++ ".tex"
-- Read visible flag. It will be required in the file parsing.
vFlag <- visibleFlag <$> ask
outputStr $ "Visible flag: " ++ (if vFlag then "enabled" else "disabled") ++ "."
-- File parsing.
outputStr $ "Reading " ++ fp ++ "..."
t <- lift $ T.readFile fp
case parseOnly (parseSyntax vFlag) t of
Left err -> outputStr $ "Reading of " ++ fp ++ " failed: " ++ err
Right s -> do
-- 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 hs = extractCode s
moduleHeader = pack $ "module " ++ modName ++ " where\n\n"
lift $ T.writeFile (modName ++ ".hs") $ moduleHeader <> hs
-- Second pass: Evaluate expressions using 'evalCode'.
outputStr $ "Evaluating expressions in " ++ fp ++ "..."
l <- evalCode modName s
let fp' = "haskintex_" ++ fp
-- Write final output.
outputStr $ "Writing final file at " ++ fp' ++ "..."
lift $ T.writeFile fp' l
-- If the keep flag is not set, remove the haskell source file.
kFlag <- keepFlag <$> ask
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 ++ "."