packages feed

monad-actions-2.0.1.0: examples/Calculator.hs

{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MonoLocalBinds #-}
{-# LANGUAGE QualifiedDo #-}
{-# LANGUAGE UndecidableInstances #-}

{- HLINT ignore "Redundant pure" -}

module Main (main) where

import Control.Applicative
import Control.Monad
import Control.Monad.Action
import Control.Monad.Action.Left qualified as L
import Control.Monad.Action.Right qualified as R
import Control.Monad.State hiding (get, put)
import Control.Monad.State qualified as State
import Data.Char
import Data.Complex
import Data.Functor
import Data.List
import System.IO
import Text.Read hiding (get)

newtype Parser a = Parser {getParser :: StateT String Maybe a}
  deriving (Functor, Applicative, Alternative, Monad, MonadState String)

runParser :: Parser a -> String -> Maybe a
runParser = evalStateT . getParser

instance {-# INCOHERENT #-} (Monad m, LeftModule m (StateT String Maybe)) => LeftModule m Parser where
  ljoin = Parser . ljoin . fmap getParser

instance {-# INCOHERENT #-} (Monad m, RightModule m (StateT String Maybe)) => RightModule m Parser where
  rjoin = Parser . rjoin . getParser

instance {-# INCOHERENT #-} (Functor f, LeftModule (StateT String Maybe) f) => LeftModule Parser f where
  ljoin = ljoin . getParser

instance {-# INCOHERENT #-} (Functor f, RightModule (StateT String Maybe) f) => RightModule Parser f where
  rjoin = rjoin . fmap getParser

get :: State s s
get = State.get

put :: s -> State s ()
put = State.put

satisfy :: (Char -> Bool) -> Parser Char
satisfy p = L.do
  s <- get
  (c, s') <- uncons s
  put s'
  if p c then pure c else empty

char :: Char -> Parser Char
char = satisfy . (==)

string :: String -> Parser String
string = traverse char

eof :: Parser ()
eof = L.do
  s <- get
  unless (null s) empty

num :: (Read a, Fractional a) => Parser a
num = R.do
  s <- some (satisfy (`elem` ('.' : ['0' .. '9'])))
  readMaybe s

chainl1 :: (Alternative f, Monad f) => f t -> f (t -> t -> t) -> f t
chainl1 p o = p >>= rest
  where
    rest x =
      ( o >>= \f ->
          p >>= \y -> rest $ f x y
      )
        <|> pure x

chainr1 :: (Alternative f, Monad f) => f t -> f (t -> t -> t) -> f t
chainr1 p o =
  p
    >>= \x ->
      ( fmap ($ x) o
          <*> chainr1 p o
      )
        <|> pure x

addOp :: (Num a) => Parser (a -> a -> a)
addOp = char '+' $> (+) <|> char '-' $> (-)

multOp :: (Fractional a) => Parser (a -> a -> a)
multOp = char '*' $> (*) <|> char '/' $> (/)

powerOp :: (Floating a) => Parser (a -> a -> a)
powerOp = (string "^" <|> string "**") $> (**)

func :: (Floating a) => Parser (a -> a)
func =
  string "exp" $> exp
    <|> string "log" $> log
    <|> string "sqrt" $> sqrt
    <|> string "sin" $> sin
    <|> string "cos" $> cos
    <|> string "tan" $> tan
    <|> string "asin" $> asin
    <|> string "acos" $> acos
    <|> string "atan" $> atan
    <|> string "sinh" $> sinh
    <|> string "cosh" $> cosh
    <|> string "tanh" $> tanh
    <|> string "asinh" $> asinh
    <|> string "acosh" $> acosh
    <|> string "atanh" $> atanh

constant :: (RealFloat a) => Parser (Complex a)
constant = string "pi" $> pi <|> string "e" $> exp 1 <|> char 'i' $> (0 :+ 1)

skipSpaces :: Parser a -> Parser a
skipSpaces p = many (satisfy isSpace) *> p <* many (satisfy isSpace)

complexExpr :: (RealFloat a, Read a) => Parser (Complex a)
complexExpr = chainl1 summand addOp
  where
    summand = chainl1 factor multOp
    factor = do
      sign <- skipSpaces $ fmap (maybe 1 (\case '-' -> -1; _ -> 1)) . optional $ satisfy (`elem` "+-")
      p <- chainl1 implicitFactor $ many (satisfy isSpace) $> (*)
      pure $ sign * p
    implicitFactor = chainr1 operand powerOp
    operand =
      skipSpaces $
        fmap (:+ 0) num
          <|> func <*> factor
          <|> constant
          <|> (char '(' *> complexExpr <* char ')')

toString :: (Num a, Eq a, Show a, Ord a) => Complex a -> String
toString = \case
  (0 :+ 0) -> "0"
  (0 :+ 1) -> "i"
  (0 :+ (-1)) -> "-i"
  (0 :+ y) -> show' y ++ " i"
  (x :+ 0) -> show' x
  (x :+ 1) -> show' x ++ " + i"
  (x :+ (-1)) -> show' x ++ " - i"
  (x :+ y) -> show' x ++ (if y >= 0 then " + " else " - ") ++ show' (abs y) ++ " i"
  where
    show' x = if '.' `elem` show x then reverse . dropWhile (== '.') . dropWhile (== '0') . reverse $ show x else show x

main :: IO ()
main = forever $ do
  putStr "> "
  hFlush stdout
  x <- getLine
  let g = runParser (complexExpr @Double <* eof) x
  maybe (hPutStrLn stderr "?") (putStrLn . toString) g