packages feed

hsilop-0.2.0.0: src/Main.hs

{-# LANGUAGE ViewPatterns, LambdaCase #-}

import Control.Monad                   (foldM)
import Data.List                       (isPrefixOf)
import Text.Read                       (readMaybe)
import Text.Printf                     (printf)
import System.Directory                (createDirectoryIfMissing)
import System.FilePath                 (takeDirectory)
import System.Environment.XDG.BaseDir  (getUserDataFile)
import System.Console.Haskeline
import System.Console.Haskeline.Completion


-- | Run calculator
main :: IO ()
main = settings >>= flip runInputT repl


-- | Program main loop
repl :: InputT IO ()
repl = getInputLine "ꟼ " >>= \case
  Nothing  -> return ()
  Just ""  -> repl
  Just exp -> outputStrLn (result (rpn exp) ++ "\n") >> repl


-- | Program settings
-- add function name completion and history
settings :: IO (Settings IO)
settings = do
  path <- getUserDataFile "hsilop" "history"
  createDirectoryIfMissing True (takeDirectory path)
  return $ Settings (completeWord Nothing "\t " complete) (Just path) True
  where
    names      = map fst monad ++ map fst nilad
    complete x = return $ map simpleCompletion (filter (isPrefixOf x) names)


-- | Pretty print RPN result/errors
result :: Either String Double -> String
result (Left err) = "Ꞥ∘ " ++ err
result (Right  x) = printf format x where
  format | ceiling x == floor x = "∘ %.0f"
         | otherwise            = "∘ %.10f"


-- | Solve a RPN expression
rpn :: String -> Either String Double
rpn = fmap head . foldM parse [] . words where
  parse (y:x:xs) (flip lookup dyad   -> Just f) = Right (f x y : xs)
  parse (x:xs)   (flip lookup monad  -> Just f) = Right (f x : xs)
  parse xs       (flip lookup nilad  -> Just k) = Right (k : xs)
  parse xs       (readMaybe          -> Just x) = Right (x : xs)
  parse _        _                              = Left "syntax error"


-- Functions --

-- | Dyadic
-- i.e. operators
dyad :: [(String, Double -> Double -> Double)]
dyad = [ ("+", (+))
       , ("-", (-))
       , ("*", (*))
       , ("/", (/))
       , ("^", (**)) ]

-- | Monadic
-- i.e. single argument functions
monad :: [(String, Double -> Double)]
monad = [ ("sin"  , sin )
        , ("asin" , asin)
        , ("cos"  , cos )
        , ("acos" , acos)
        , ("tan"  , tan )
        , ("atan" , atan)
        , ("ln"   , log )
        , ("sqrt" , sqrt)
        , ("abs"  , abs ) 
        , ("sgn"  , signum)
        , ("floor", fromIntegral . floor)
        , ("ceil" , fromIntegral . ceiling) ]

-- | Niladic
-- i.e. constants
nilad :: [(String, Double)]
nilad = [ ("pi" , pi)
        , ("e"  , exp 1)
        , ("phi", (1 + sqrt 5)/2) ]