hatt-1.4.0.2: src/hatt.hs
{-# LANGUAGE DeriveDataTypeable #-}
module Main (main) where
import Data.Logic.Propositional
import Data.Logic.Propositional.Tables
import Control.Monad (when, unless)
import Data.Char (isSpace, toLower)
import System.Console.CmdArgs
import System.Console.Haskeline (InputT, runInputT, defaultSettings, getInputLine, outputStr, outputStrLn)
data Command = Exit
| Help
| Pretty
| Coloured
| Eval Expr
| Error String
data ProgramMode = ProgramMode
{ evaluate :: String
, interactive :: Bool
, pretty :: Bool
, coloured :: 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"
, coloured = False &= help "Use colour-coded symbols"
} &= summary "Hatt 1.4.0.2, (c) Benedict Eastaugh 2012"
&= 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
>> runInputT defaultSettings (repl opts)
repl :: ProgramMode -> InputT IO ()
repl mode = do
minput <- getInputLine "> "
case minput of
Nothing -> return ()
Just cmd -> case parseCommand cmd of
Exit -> return ()
Help -> outputStr (replHelpText printer)
>> repl mode
Pretty -> outputStrLn ppMessage
>> repl (mode {pretty = not isPretty})
Coloured -> outputStrLn cpMessage
>> repl (mode {coloured = not isColoured})
(Eval expr) -> outputStr (truthTableP printer expr)
>> repl mode
(Error err) -> outputStrLn ("Error: " ++ err)
>> repl mode
where
printer = selectPrinter mode
isPretty = pretty mode
isColoured = coloured mode
ppMessage = (if isPretty then "Dis" else "En") ++ "abling pretty-printing."
cpMessage = (if isColoured then "Dis" else "En") ++ "abling colour-coding."
eval :: Printer -> 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
"colour" -> Coloured
_ -> 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 :: Printer -> 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."
, ""
, "colour"
, " Colour truth values: green for true, red for false. This feature needs"
, " your console to support ANSI colour codes. If coloured mode is already"
, " enabled, 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"))
++ "> P -> (Q & R)\n"
++ truthTableP printer (Conditional
(Variable "P")
(Conjunction (Variable "Q") (Variable "R")))
, "If none of this makes any sense, try reading the README file."
]
selectPrinter :: ProgramMode -> Printer
selectPrinter m = let expPrinter = if pretty m then show else showAscii
tablePrinter = if coloured m then colourBool else showBool
in (expPrinter, tablePrinter)
indentBy :: Int -> String -> String
indentBy n = unlines . map (replicate n ' ' ++) . lines