{-# 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.IO 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.3.0, (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}) Coloured -> putStrLn cpMessage >> repl (mode {coloured = not isColoured}) (Eval expr) -> putStr (truthTableP printer expr) >> repl mode (Error err) -> putStrLn ("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")) ++ "> foobar\n" ++ eval printer "foobar" ++ "> exit" , "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