inferno-core-0.1.0.0: src/Inferno/Module/Prelude/Defs.hs
module Inferno.Module.Prelude.Defs where
import Control.Monad (foldM)
import Control.Monad.Except (MonadError (throwError))
import Data.Bifunctor (bimap)
import Data.Bits
( clearBit,
complement,
complementBit,
setBit,
shift,
testBit,
xor,
(.&.),
(.|.),
)
import Data.Foldable (foldrM, maximumBy, minimumBy)
import Data.Int (Int64)
import Data.List (sortOn)
import Data.Maybe (mapMaybe)
import Data.Ord (comparing)
import Data.Text (Text, pack, unpack)
import qualified Data.Text as Text
import Data.Time.Calendar (Day, addGregorianMonthsClip, addGregorianYearsClip, fromGregorian, toGregorian)
import Data.Time.Clock (DiffTime, UTCTime (..), diffTimeToPicoseconds, picosecondsToDiffTime)
import Data.Time.Clock.POSIX (posixSecondsToUTCTime, utcTimeToPOSIXSeconds)
import qualified Data.Time.Format as Time.Format
import Data.Word (Word16, Word32, Word64)
import Debug.Trace (trace)
import Foreign.C.Types (CTime (..))
import Foreign.Marshal.Utils (fromBool)
import Inferno.Eval.Error (EvalError (RuntimeError))
import Inferno.Module.Builtin (enumBoolHash)
import Inferno.Module.Cast (Either3, Either4, Either5, Either6)
import Inferno.Types.Type (BaseType (..), InfernoType (..))
import Inferno.Types.Value (Value (..))
import Inferno.Utils.Prettyprinter (renderPretty)
import Prettyprinter (Pretty)
import System.Posix.Types (EpochTime)
zeroVal :: Value c m
zeroVal = VInt 0
secondsFun, minutesFun, hoursFun, daysFun, weeksFun, monthsFun, yearsFun :: Int64 -> CTime
secondsFun = CTime . fromIntegral
minutesFun = timeMultiplier 60
hoursFun = timeMultiplier 3600
daysFun = timeMultiplier 86400
weeksFun = timeMultiplier 604800
monthsFun = timeMultiplier 2592000
yearsFun = timeMultiplier 31536000
hourFun, dayFun, monthFun, yearFun :: CTime -> CTime
hourFun = mapEpochAsUTC $ \(UTCTime d diff) -> UTCTime d $ hourDiffTime diff
dayFun = mapEpochAsUTC midnightUTCTime
monthFun = mapEpochAsUTC $ mapUTCTimeDay truncateMonth
yearFun = mapEpochAsUTC $ mapUTCTimeDay truncateYear
mapUTCTimeDay :: (Day -> Day) -> (UTCTime -> UTCTime)
mapUTCTimeDay f (UTCTime d diff) = UTCTime (f d) diff
mapEpochAsUTC :: (UTCTime -> UTCTime) -> (CTime -> CTime)
mapEpochAsUTC f = CTime . round . utcTimeToPOSIXSeconds . f . posixSecondsToUTCTime . realToFrac
midnightUTCTime :: UTCTime -> UTCTime
midnightUTCTime (UTCTime d _) = UTCTime d 0
-- | Truncate a 'DiffTime' to the beginning of the hour.
hourDiffTime :: DiffTime -> DiffTime
hourDiffTime t =
-- Note: one second is 10^12 picoseconds
let hourlength = 60 * 60 * 10 ^ (12 :: Int)
in picosecondsToDiffTime $ hourlength * div (diffTimeToPicoseconds t) hourlength
truncateMonth :: Day -> Day
truncateMonth day =
let (y, m, _) = toGregorian day
in fromGregorian y m 1
truncateYear :: Day -> Day
truncateYear day =
let (y, _, _) = toGregorian day
in fromGregorian y 1 1
secondsBeforeFun, minutesBeforeFun, hoursBeforeFun, daysBeforeFun, weeksBeforeFun :: CTime -> Int64 -> CTime
secondsBeforeFun t i = t - (secondsFun i)
minutesBeforeFun t i = t - (minutesFun i)
hoursBeforeFun t i = t - (hoursFun i)
daysBeforeFun t i = t - (daysFun i)
weeksBeforeFun t i = t - (weeksFun i)
monthsBeforeFun, yearsBeforeFun :: CTime -> Integer -> CTime
monthsBeforeFun t m = advanceMonths (negate m) t
yearsBeforeFun t y = advanceYears (negate y) t
advanceMonths :: Integer -> EpochTime -> EpochTime
advanceMonths months = mapEpochAsUTC $ \(UTCTime d diff) -> UTCTime (addGregorianMonthsClip months d) diff
advanceYears :: Integer -> EpochTime -> EpochTime
advanceYears years = mapEpochAsUTC $ \(UTCTime d diff) -> UTCTime (addGregorianYearsClip years d) diff
timeIntervalFun :: CTime -> CTime -> CTime -> [CTime]
timeIntervalFun every from to = [from, from + every .. to]
timeMultiplier :: CTime -> Int64 -> CTime
timeMultiplier t m = t * fromIntegral m
timeToInt :: CTime -> Int64
timeToInt (CTime t) = fromIntegral t
formatTime :: CTime -> Text -> Text
formatTime t f =
let t1 = posixSecondsToUTCTime $ realToFrac t
in pack $ Time.Format.formatTime Time.Format.defaultTimeLocale (unpack f) t1
keepSomesFun :: (MonadError EvalError m) => Value c m
keepSomesFun =
VFun $ \case
VArray xs ->
pure $
VArray $
foldr
( \v vs -> case v of
VOne a -> a : vs
_ -> vs
)
[]
xs
_ -> throwError $ RuntimeError "keepSomes: expecting an array"
foldlFun :: (MonadError EvalError m) => Value c m
foldlFun =
VFun $ \case
VFun f ->
return $
VFun $ \z -> return $
VFun $ \case
VArray xs ->
foldM
( \acc x ->
f acc >>= \case
VFun f' -> f' x
_ -> throwError $ RuntimeError "reduce: expecting a function when folding"
)
z
xs
_ -> throwError $ RuntimeError "reduce: expecting an array in the third argument"
_ -> throwError $ RuntimeError "reduce: expecting a function in the first argument"
foldrFun :: (MonadError EvalError m) => Value c m
foldrFun =
VFun $ \case
VFun f ->
return $
VFun $ \z -> return $
VFun $ \case
VArray xs ->
foldrM
( \x acc ->
f x >>= \case
VFun f' -> f' acc
_ -> throwError $ RuntimeError "reduceRight: expecting a function when folding"
)
z
xs
_ -> throwError $ RuntimeError "reduceRight: expecting an array in the third argument"
_ -> throwError $ RuntimeError "reduceRight: expecting a function in the first argument"
traceFun :: (Monad m, Pretty c) => (Value c m)
traceFun = VFun $ \msg -> trace ("TRACE: " <> unpack (renderPretty msg)) $ return idFun
idFun :: Monad m => (Value c m)
idFun = VFun $ \x -> return x
eqFun :: (Monad m, Eq c) => (Value c m)
eqFun = VFun $ \x -> return $ VFun $ \y -> return $ if x == y then VEnum enumBoolHash "true" else VEnum enumBoolHash "false"
neqFun :: (Monad m, Eq c) => (Value c m)
neqFun = VFun $ \x -> return $ VFun $ \y -> return $ if x == y then VEnum enumBoolHash "false" else VEnum enumBoolHash "true"
enumFromToInt64 :: Int64 -> Int64 -> [Int64]
enumFromToInt64 = enumFromTo
sumFun ::
Either6 Double Int64 EpochTime Word16 Word32 Word64 ->
Either6
(Either Double Int64 -> Double)
(Either Double Int64 -> Either Double Int64)
(EpochTime -> EpochTime)
(Word16 -> Word16)
(Word32 -> Word32)
(Word64 -> Word64)
sumFun =
bimap (\x -> either ((+) x) ((+) x . fromIntegral)) $
bimap (\i -> bimap ((+) $ fromIntegral i) ((+) i)) $
bimap (+) $ bimap (+) $ bimap (+) (+)
divFun ::
Either Double Int64 ->
Either
(Either Double Int64 -> Double)
(Either Double Int64 -> Either Double Int64)
divFun =
bimap (\x -> either ((/) x) ((/) x . fromIntegral)) $
(\i -> bimap ((/) $ fromIntegral i) ((div) i))
modFun :: Int64 -> Int64 -> Int64
modFun = mod
mulFun ::
Either3 Double Int64 EpochTime ->
Either3
(Either Double Int64 -> Double)
(Either3 Double Int64 EpochTime -> Either3 Double Int64 EpochTime)
(Int64 -> EpochTime)
mulFun =
bimap (\x -> either ((*) x) ((*) x . fromIntegral)) $
bimap
(\i -> bimap ((*) $ fromIntegral i) (bimap ((*) i) ((*) $ secondsFun i)))
(\x -> ((*) x . secondsFun))
subFun ::
Either6 Double Int64 EpochTime Word16 Word32 Word64 ->
Either6
(Either Double Int64 -> Double)
(Either Double Int64 -> Either Double Int64)
(EpochTime -> EpochTime)
(Word16 -> Word16)
(Word32 -> Word32)
(Word64 -> Word64)
subFun =
bimap (\x -> either ((-) x) ((-) x . fromIntegral)) $
bimap (\i -> bimap ((-) $ fromIntegral i) ((-) i)) $
bimap (-) $ bimap (-) $ bimap (-) (-)
recipFun :: Double -> Double
recipFun = recip
powFun :: Either Int64 Double -> Either (Int64 -> Int64) (Double -> Double)
powFun = bimap (^) (**)
expFun :: Double -> Double
expFun = exp
lnFun :: Double -> Double
lnFun = log
logFun :: Double -> Double
logFun = logBase 10
logBaseFun :: Double -> Double -> Double
logBaseFun = logBase
sqrtFun :: Double -> Double
sqrtFun = sqrt
negateFun :: Either3 Int64 Double EpochTime -> Either3 Int64 Double EpochTime
negateFun = bimap negate (bimap negate negate)
absFun :: Either3 Int64 Double EpochTime -> Either3 Int64 Double EpochTime
absFun = bimap abs (bimap abs abs)
floorFun :: Either Double Int64 -> Int64
floorFun = either floor id
ceilingFun :: Either Double Int64 -> Int64
ceilingFun = either ceiling id
roundFun :: Either Double Int64 -> Int64
roundFun = either round id
roundToFun :: Int64 -> Double -> Double
roundToFun n x =
let q = 10 ^ n
in fromIntegral (round (x * q) :: Int64) / q
truncateFun :: Either Double Int64 -> Int64
truncateFun = either truncate id
truncateToFun :: Int64 -> Double -> Double
truncateToFun n x =
let q = 10 ^ n
in fromIntegral (truncate (x * q) :: Int64) / q
limitFun :: Double -> Double -> Double -> Double
limitFun = (\l u -> min u . max l)
piFun :: Double
piFun = pi
sinFun :: Double -> Double
sinFun = sin
sinhFun :: Double -> Double
sinhFun = sinh
arcSinFun :: Double -> Double
arcSinFun = asin
cosFun :: Double -> Double
cosFun = cos
coshFun :: Double -> Double
coshFun = cosh
arcCosFun :: Double -> Double
arcCosFun = acos
tanFun :: Double -> Double
tanFun = tan
tanhFun :: Double -> Double
tanhFun = tanh
arcTanFun :: Double -> Double
arcTanFun = atan
intToDouble :: Int64 -> Double
intToDouble = fromIntegral
doubleToInt :: Double -> Int64
doubleToInt = truncate
-- random :: () -> IO Double -- TODO types?
-- random = const randomIO
gtFun :: Either3 Int64 Double EpochTime -> Either3 (Int64 -> Bool) (Double -> Bool) (EpochTime -> Bool)
gtFun = bimap (>) (bimap (>) (>))
geqFun :: Either3 Int64 Double EpochTime -> Either3 (Int64 -> Bool) (Double -> Bool) (EpochTime -> Bool)
geqFun = bimap (>=) (bimap (>=) (>=))
ltFun :: Either3 Int64 Double EpochTime -> Either3 (Int64 -> Bool) (Double -> Bool) (EpochTime -> Bool)
ltFun = bimap (<) (bimap (<) (<))
leqFun :: Either3 Int64 Double EpochTime -> Either3 (Int64 -> Bool) (Double -> Bool) (EpochTime -> Bool)
leqFun = bimap (<=) (bimap (<=) (<=))
minFun :: Either3 Int64 Double EpochTime -> Either3 (Int64 -> Int64) (Double -> Double) (EpochTime -> EpochTime)
minFun = bimap (min) (bimap (min) (min))
maxFun :: Either3 Int64 Double EpochTime -> Either3 (Int64 -> Int64) (Double -> Double) (EpochTime -> EpochTime)
maxFun = bimap (max) (bimap (max) (max))
singletonFun :: Monad m => (Value c m)
singletonFun = VFun $ \v -> return $ VArray [v]
-- The following functions use Int and not Int64, but that should be fine
-- because they don't create ints, these are only argument types.
testBitFun :: Either3 Word16 Word32 Word64 -> Int -> Bool
testBitFun = either testBit (either testBit testBit)
setBitFun :: Either3 Word16 Word32 Word64 -> Either3 (Int -> Word16) (Int -> Word32) (Int -> Word64)
setBitFun = bimap setBit (bimap setBit setBit)
clearBitFun :: Either3 Word16 Word32 Word64 -> Either3 (Int -> Word16) (Int -> Word32) (Int -> Word64)
clearBitFun = bimap clearBit (bimap clearBit clearBit)
complementBitFun :: Either3 Word16 Word32 Word64 -> Either3 (Int -> Word16) (Int -> Word32) (Int -> Word64)
complementBitFun = bimap complementBit (bimap complementBit complementBit)
complementFun :: Either4 Bool Word16 Word32 Word64 -> Either4 Bool Word16 Word32 Word64
complementFun = bimap not (bimap complement (bimap complement complement))
andFun :: Either4 Bool Word16 Word32 Word64 -> Either4 (Bool -> Bool) (Word16 -> Word16) (Word32 -> Word32) (Word64 -> Word64)
andFun = bimap (&&) (bimap (.&.) (bimap (.&.) (.&.)))
orFun :: Either4 Bool Word16 Word32 Word64 -> Either4 (Bool -> Bool) (Word16 -> Word16) (Word32 -> Word32) (Word64 -> Word64)
orFun = bimap (||) (bimap (.|.) (bimap (.|.) (.|.)))
xorFun :: Either4 Bool Word16 Word32 Word64 -> Either4 (Bool -> Bool) (Word16 -> Word16) (Word32 -> Word32) (Word64 -> Word64)
xorFun = bimap (xor) (bimap (xor) (bimap (xor) (xor)))
shiftFun :: Either3 Word16 Word32 Word64 -> Either3 (Int -> Word16) (Int -> Word32) (Int -> Word64)
shiftFun = bimap shift (bimap shift shift)
toWord64Fun :: Either5 Bool Word16 Word32 Word64 Int64 -> Word64
toWord64Fun = either fromBool (either fromIntegral (either fromIntegral $ either id fromIntegral))
toWord32Fun :: Either5 Bool Word16 Word32 Word64 Int64 -> Word32
toWord32Fun = either fromBool (either fromIntegral (either id (either (fromIntegral . (.&.) 0xFFFFFFFF) fromIntegral)))
toWord16Fun :: Either5 Bool Word16 Word32 Word64 Int64 -> Word16
toWord16Fun = either fromBool (either id (either (fromIntegral . (.&.) 0xFFFF) (either (fromIntegral . (.&.) 0xFFFF) fromIntegral)))
fromWordFun :: Either4 Bool Word16 Word32 Word64 -> Int64
fromWordFun = either fromBool (either fromIntegral (either fromIntegral fromIntegral))
zeroFun :: MonadError EvalError m => (Value c m)
zeroFun = VFun $ \case
VTypeRep (TBase TInt) -> return $ VInt 0
VTypeRep (TBase TDouble) -> return $ VDouble 0
VTypeRep (TBase TTimeDiff) -> return $ VEpochTime 0
VTypeRep (TBase TWord16) -> return $ VWord16 0
VTypeRep (TBase TWord32) -> return $ VWord32 0
VTypeRep (TBase TWord64) -> return $ VWord64 0
VTypeRep ty -> throwError $ RuntimeError $ "zeroFun: unexpected runtimeRep " <> show ty
_ -> throwError $ RuntimeError "zeroFun: expecting a runtimeRep"
lengthFun :: (MonadError EvalError m) => Value c m
lengthFun =
VFun $ \case
VArray xs -> pure $ VInt $ fromIntegral $ length xs
_ -> throwError $ RuntimeError "length: expecting an array"
-- | Convenience function for comparing numbered value
-- in an array while maintaining the original value type
keepNumberValues :: [Value c m] -> [(Value c m, Double)]
keepNumberValues =
mapMaybe
( \case
m@(VInt v) -> Just (m, fromIntegral v)
m@(VDouble v) -> Just (m, v)
_ -> Nothing
)
minimumFun :: (MonadError EvalError m) => Value c m
minimumFun =
VFun $ \case
VArray [] -> throwError $ RuntimeError "minimum: expecting a non-empty array"
VArray xs -> return $ fst $ minimumBy (comparing snd) $ keepNumberValues xs
_ -> throwError $ RuntimeError "minimum: expecting an array"
maximumFun :: (MonadError EvalError m) => Value c m
maximumFun =
VFun $ \case
VArray [] -> throwError $ RuntimeError "maximum: expecting a non-empty array"
VArray xs -> return $ fst $ maximumBy (comparing snd) $ keepNumberValues xs
_ -> throwError $ RuntimeError "maximum: expecting an array"
averageFun :: (MonadError EvalError m) => Value c m
averageFun =
VFun $ \case
VArray [] -> throwError $ RuntimeError "average: expecting a non-empty array"
VArray xs -> return $ VDouble $ sum (mapMaybe toDouble xs) / fromIntegral (length xs)
_ -> throwError $ RuntimeError "average: expecting an array"
where
toDouble :: Value c m -> Maybe Double
toDouble = \case
VInt v -> Just $ fromIntegral v
VDouble v -> Just v
_ -> Nothing
argminFun :: (MonadError EvalError m) => Value c m
argminFun =
VFun $ \case
VArray xs -> pure $ VInt $ fromIntegral $ argMin' $ map snd $ keepNumberValues xs
_ -> throwError $ RuntimeError "argmin: expecting an array"
where
argMin' :: [Double] -> Int
argMin' = fst . minimumBy (comparing snd) . zip [0 ..]
argmaxFun :: (MonadError EvalError m) => Value c m
argmaxFun =
VFun $ \case
VArray xs -> pure $ VInt $ fromIntegral $ argMax' $ map snd $ keepNumberValues xs
_ -> throwError $ RuntimeError "argmax: expecting an array"
where
argMax' :: [Double] -> Int
argMax' = fst . maximumBy (comparing snd) . zip [0 ..]
argsortFun :: (MonadError EvalError m) => Value c m
argsortFun =
VFun $ \case
VArray xs -> pure $ VArray $ argsort' $ keepNumberValues xs
_ -> throwError $ RuntimeError "argsort: expecting an array"
where
argsort' :: [(Value c m, Double)] -> [Value c m]
argsort' xs = map (VInt . fst) $ sortOn (snd . snd) $ zip [0 ..] xs
magnitudeFun :: (MonadError EvalError m) => Value c m
magnitudeFun =
VFun $ \case
VDouble x -> pure $ VDouble $ abs x
VInt x -> pure $ VInt $ abs x
VArray xs -> pure $ VDouble $ sqrt $ sum $ map (\x -> x ** 2) $ map snd (keepNumberValues xs)
_ -> throwError $ RuntimeError "magnitude: expecting a number"
normFun :: (MonadError EvalError m) => Value c m
normFun = magnitudeFun
appendText :: Text -> Text -> Text
appendText = Text.append
textLength :: Text -> Int64
textLength = fromIntegral . Text.length
stripText :: Text -> Text
stripText = Text.strip
textSplitAt :: (MonadError EvalError m) => Value c m
textSplitAt =
VFun $ \case
VInt n -> pure $
VFun $ \case
VText txt ->
let (a, b) = Text.splitAt (fromIntegral n) txt
in pure $ VTuple [VText a, VText b]
_ -> throwError $ RuntimeError "splitAt: expecting text for the second argument"
_ -> throwError $ RuntimeError "splitAt: expecting an int for the first argument"