hatt-1.2.1: src/hatt.hs
{-# LANGUAGE DeriveDataTypeable #-}
module Main (main) where
import Data.Logic.Propositional
import Control.Monad (when, unless)
import Data.Char (isSpace, toLower)
import System.Console.CmdArgs
import System.IO
data Command = Exit
| Help
| Pretty
| Eval Expr
| Error String
data ProgramMode = ProgramMode
{ evaluate :: String
, interactive :: Bool
, pretty :: Bool
} deriving (Show, Data, Typeable)
programMode :: ProgramMode
programMode = ProgramMode
{ evaluate = "" &= typ "EXPRESSION"
&= help "Print the truth table for the given expression"
, interactive = False &= help "Enter interactive mode"
, pretty = False &= help "Use Unicode logic symbols"
} &= summary "Hatt 1.2.1, (c) Benedict Eastaugh 2011"
&= program "hatt"
main :: IO ()
main = do opts <- cmdArgs programMode
let expStr = evaluate opts
interMode = interactive opts
evalMode = (not . null) expStr
printer = selectPrinter opts
-- If the --evaluate flag is passed with an expression, print the
-- truth table for that expression.
when evalMode $ putStr (eval printer expStr)
-- Unless the --evaluate flag is passed with an expression and
-- interactive mode is NOT explicitly requested, terminate the
-- program; otherwise, enter interactive mode.
unless (evalMode && not interMode) $
putStrLn replIntroText >> repl opts
repl :: ProgramMode -> IO ()
repl mode = do putStr "> "
hFlush stdout
cmd <- getLine
case parseCommand cmd of
Exit -> return ()
Help -> putStr (replHelpText printer)
>> repl mode
Pretty -> putStrLn ppMessage
>> repl (mode {pretty = not isPretty})
(Eval expr) -> putStr (truthTableP printer expr)
>> repl mode
(Error err) -> putStrLn ("Error: " ++ err)
>> repl mode
where
printer = selectPrinter mode
isPretty = pretty mode
ppMessage = (if isPretty then "Dis" else "En") ++ "abling pretty-printing."
eval :: (Expr -> String) -> String -> String
eval p str = case parseExpr "" str of
Left err -> "Parse error at " ++ show err ++ "\n"
Right expr -> truthTableP p expr
parseCommand :: String -> Command
parseCommand input = case cmd . words . dropWhile isSpace $ input of
"" -> Error "you must enter an expression or a command."
"exit" -> Exit
"help" -> Help
"pretty" -> Pretty
_ -> eval_ input
where
cmd [] = ""
cmd ws = map toLower . head $ ws
eval_ str = case parseExpr "" str of
Left err -> Error $ "parse error at " ++ show err
Right expr -> Eval expr
replIntroText :: String
replIntroText = unwords
[ "Entering interactive mode."
, "Type `help` if you don't know what to do!"
]
replHelpText :: (Expr -> String) -> String
replHelpText printer = unlines
[ "Hatt's interactive mode has several commands."
, ""
, "help"
, " Print this help text."
, ""
, "pretty"
, " Pretty-print expressions using Unicode logic symbols. Only employ this"
, " option if your console is Unicode-aware. If pretty-printing is already"
, " enabled, using this command will disable it."
, ""
, "exit"
, " Quit the program."
, ""
, "If you don't type in a command, the program will assume you're writing a"
, "logical expression to be evaluated and attempt to parse it."
, ""
, "For example, if you enter \"(A -> B)\" at the prompt, Hatt will print the"
, "truth table for that expression. Here's an example console session."
, ""
, " > (A | B)"
, indentBy 4 $ truthTableP printer (Disjunction (Variable "A") (Variable "B"))
++ "> foobar\n"
++ eval printer "foobar"
++ "> exit"
, "If none of this makes any sense, try reading the README file."
]
selectPrinter :: ProgramMode -> Expr -> String
selectPrinter m = if pretty m then show else showAscii
indentBy :: Int -> String -> String
indentBy n = unlines . map (replicate n ' ' ++) . lines