zuramaru-0.1.0.0: src/Maru/Eval/RuntimeOperation.hs
{-# LANGUAGE GADTs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedLabels #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ViewPatterns #-}
--TODO: Introduce LiquidHaskell
-- |
-- Define functions and macros, these are used in the runtime,
--
-- also these are through with `MaruEnv` (it should be `Maru.Eval.initialEnv`)
--
-- These respects clisp's behavior basically.
--
-- `add`, `sub`, `times`, and `div` are regarded as axiomly functions.
module Maru.Eval.RuntimeOperation
( add
, sub
, times
, div
) where
import Control.Lens hiding (set)
import Control.Monad.Fail (fail)
import Data.List (foldl')
import Data.List.NonEmpty (NonEmpty(..))
import Data.Maybe (maybeToList)
import Data.Semigroup ((<>))
import Maru.Type
import Numeric.Extra (intToDouble)
import Prelude hiding (div, fail)
import TextShow (showt)
import qualified Data.List.NonEmpty as NE
import qualified Maru.Type as MT
-- $setup
-- >>> :set -XOverloadedStrings
-- >>> import Control.Lens hiding (set)
-- >>> import Data.Either
-- >>> import Maru.Eval
-- >>> import Maru.Type
-- >>> import qualified Data.Map.Lazy as M
ignoreAtomInt :: [SExpr] -> [SExpr]
ignoreAtomInt xs = xs ^.. folded . filtered (not . MT.isAtomInt)
-- |
-- Find all `AtomInt` elements,
-- Calculate its summation,
-- and wrap by `AtomInt`.
--
-- Return `AtomInt 0` if the given list is empty.
sumOfAtomInt :: [SExpr] -> SExpr
sumOfAtomInt = (AtomInt .) . sumOf $ folded . MT._AtomInt
-- |
-- >>> runMaruCalculator $ execFunc add [AtomInt 1, AtomInt 2]
-- Right (AtomInt 3)
-- >>> runMaruCalculator $ execFunc add []
-- Right (AtomInt 0)
-- >>> isLeft . runMaruCalculator $ execFunc add [AtomSymbol "xD"]
-- True
add :: MaruFunc
add = MaruFunc $ \xs -> case ignoreAtomInt xs of
[] -> return $ sumOfAtomInt xs
invalidArgs -> fail $ "add: invalid arguments are given to (+): " ++ show invalidArgs
-- |
-- >>> runMaruCalculator $ execFunc sub [AtomInt 3, AtomInt 1]
-- Right (AtomInt 2)
-- >>> runMaruCalculator $ execFunc sub [AtomInt 1]
-- Right (AtomInt (-1))
-- >>> isLeft . runMaruCalculator $ execFunc sub []
-- True
-- >>> isLeft . runMaruCalculator $ execFunc sub [AtomSymbol "xD"]
-- True
sub :: MaruFunc
sub = MaruFunc $ \case
[] -> fail "sub: takes a list of integer values, but took list is empty"
w@(x:xs) -> case ignoreAtomInt w of
[] -> do
let cause = "sub: fatal error! with `" <> showt w <> "`"
let result = negativeSumOfAtomInt (x:|xs)
includeFail cause $ return result
invalidArgs -> fail $ "sub: invalid arguments are given to (-): " ++ show invalidArgs
where
-- head - tail
{-@ negativeSumOfAtomInt :: {(x:|xs):NonEmpty SExpr | null $ ignoreAtomInt (x:xs) } -> SExpr @-}
negativeSumOfAtomInt :: NonEmpty SExpr -> Maybe SExpr
negativeSumOfAtomInt (AtomInt x:|[]) = Just $ AtomInt (-x)
negativeSumOfAtomInt (x:|xs) = foldl' subSExpr (Just x) xs
subSExpr :: Maybe SExpr -> SExpr -> Maybe SExpr
subSExpr Nothing _ = Nothing
subSExpr (Just (AtomInt x)) (AtomInt y) = Just . AtomInt $ x - y
subSExpr _ _ = Nothing
-- |
-- >>> runMaruCalculator $ execFunc times [AtomInt 3, AtomInt 3]
-- Right (AtomInt 9)
-- >>> runMaruCalculator $ execFunc times []
-- Right (AtomInt 1)
-- >>> isLeft . runMaruCalculator $ execFunc times [AtomSymbol "xD"]
-- True
times :: MaruFunc
times = MaruFunc $ \xs -> case ignoreAtomInt xs of
[] -> return . AtomInt $ productOf (folded . MT._AtomInt) xs
invalidArgs -> fail $ "times: invalid arguments are given to (*): " ++ show invalidArgs
--TODO: This makes an integral number unless like AtomRatio is implemented to SExpr
-- |
-- >>> runMaruCalculator $ execFunc div [AtomInt 3, AtomInt 3]
-- Right (AtomInt 1)
-- >>> isLeft . runMaruCalculator $ execFunc div []
-- True
-- >>> isLeft . runMaruCalculator $ execFunc div [AtomSymbol "xD"]
-- True
-- >>> isLeft . runMaruCalculator $ execFunc div [AtomInt 0, AtomInt 1]
-- True
-- >>> runMaruCalculator $ execFunc div [AtomInt 10, AtomInt 3]
-- Right (AtomInt 3)
-- >>> runMaruCalculator $ execFunc div [AtomInt 3, AtomInt 5]
-- Right (AtomInt 0)
div :: MaruFunc
div = MaruFunc $ \case
[] -> fail "div: takes a non empty list, but took list is empty"
w@(x:xs) -> case (ignoreAtomInt w, negativeProductOfAtomInt (x:|xs)) of
([], Nothing) -> fail "div: 0 is divided by anything"
([], Just z) -> return z
(invalidArgs, _) -> fail $ "div: invalid arguments are given to (/): " ++ show invalidArgs
where
-- Safe (/)
(/?) :: Maybe Double -> Double -> Maybe Double
Nothing /? _ = Nothing
Just 0 /? _ = Nothing
Just x /? y = Just $ x / y
-- Extract [`Int`] from [`AtomInt`],
-- and Convert each `Int` to `Double`
doublesFromAtomInt :: NonEmpty SExpr -> [Double]
doublesFromAtomInt = concatMap (maybeToList . (intToDouble <$>) . MT.unAtomInt) . NE.filter MT.isAtomInt
-- All `NonEmpty SExpr` element are `AtomInt`.
-- If 0 was devided, return `Nothing`.
negativeProductOfAtomInt :: NonEmpty SExpr -> Maybe SExpr
negativeProductOfAtomInt (doublesFromAtomInt -> (x:xs))
= AtomInt . truncate <$> foldl' (/?) (Just x) xs
negativeProductOfAtomInt _
= Nothing