packages feed

hatt-1.0: src/hatt.hs

{-# LANGUAGE DeriveDataTypeable #-}

module Main
    ( main
    ) where

import Data.Logic.Propositional

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.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.
          if evalMode
            then putStr $ eval printer expStr
            else return ()
          
          -- If the --evaluate flag is passed with an expression and
          -- interactive mode is NOT explicitly requested, terminate the
          -- program; otherwise, enter interactive mode.
          if evalMode && not interMode
            then return ()
            else putStrLn replIntroText >> repl opts

repl :: ProgramMode -> IO ()
repl mode = do putStr "> "
               hFlush stdout
               cmd <- getLine
               case parseCommand cmd of
                 Exit        -> return ()
                 Help        -> putStr replHelpText
                                >> 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 :: String
replHelpText = 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)"
  , "    A B | (A ∨ B)"
  , "    -------------"
  , "    T T | T"
  , "    T F | T"
  , "    F T | T"
  , "    F F | F"
  , "    > foobar"
  , "    Error: parse error at (line 1, column 1):"
  , "    unexpected \"f\""
  , "    expecting white space, \"(\" or \"~\""
  , "   > 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