packages feed

inferno-core-0.1.0.0: src/Inferno/Module/Cast.hs

{-# LANGUAGE ExplicitForAll #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}

-- TODO export only needed?
-- module Inferno.Module.Cast (FromValue, ToValue) where
module Inferno.Module.Cast where

import Control.Monad.Except (MonadError (..))
import Control.Monad.Reader (ask)
import Data.Int (Int64)
import qualified Data.Map as Map
import Data.Proxy (Proxy (..))
import qualified Data.Set as Set
import Data.Text (Text, pack, unpack)
import Data.Typeable (Typeable, typeRep)
import Data.Word (Word16, Word32, Word64)
import Foreign.C.Types (CTime (..))
import GHC.TypeLits (KnownSymbol, symbolVal)
import Inferno.Eval.Error (EvalError (CastError, NotFoundInImplicitEnv))
import Inferno.Module.Builtin (enumBoolHash)
import Inferno.Types.Syntax (ExtIdent (..), Lit (..), TList (..))
import Inferno.Types.Type (BaseType (..), InfernoType (..))
import Inferno.Types.Value (ImplEnvM, ImplicitCast (..), Value (..))
import Inferno.Utils.Prettyprinter (renderPretty)
import Prettyprinter (Pretty)

type Either3 a b c = Either a (Either b c)

type Either4 a b c d = Either a (Either3 b c d)

type Either5 a b c d e = Either a (Either4 b c d e)

type Either6 a b c d e f = Either a (Either5 b c d e f)

type Either7 a b c d e f g = Either a (Either6 b c d e f g)

-- | Types that can be converted to script values, allowing IO in the process.
class ToValue c m a where
  toValue :: MonadError EvalError m => a -> m (Value c m)

-- | Class of types that can be converted from script values.
class FromValue c m a where
  fromValue :: MonadError EvalError m => (Value c m) -> m a

-- | Haskell types that can be casted to mask script types.
class Kind0 a where
  toType :: Proxy a -> InfernoType

-- Instances

couldNotCast :: forall c m a. (Pretty c, MonadError EvalError m, Typeable a) => Value c m -> m a
couldNotCast v =
  throwError $
    CastError $
      "Could not cast value " <> (unpack $ renderPretty v)
        <> " to "
        <> (show $ typeRep (Proxy :: Proxy a))

instance ToValue c m (m (Value c m)) where
  toValue = id

instance ToValue c m (Value c m) where
  toValue = pure

instance FromValue c m (Value c m) where
  fromValue = pure

instance ToValue c m Lit where
  toValue l = pure $ case l of
    LInt i -> VInt i
    LDouble x -> VDouble x
    LText t -> VText t
    LHex w -> VWord64 w

instance ToValue c m Bool where
  toValue True = pure $ VEnum enumBoolHash "true"
  toValue False = pure $ VEnum enumBoolHash "false"

instance Pretty c => FromValue c m Bool where
  fromValue (VEnum hash ident) =
    if hash == enumBoolHash
      then
        if ident == "true"
          then pure True
          else pure False
      else couldNotCast $ (VEnum hash ident :: Value c m)
  fromValue v = couldNotCast v

instance ToValue c m Double where
  toValue = pure . VDouble

instance Pretty c => FromValue c m Double where
  fromValue (VDouble x) = pure x
  -- fromValue (VInt x) = pure $ fromIntegral x
  fromValue v = couldNotCast v

instance ToValue c m Int64 where
  toValue = pure . VInt

instance Pretty c => FromValue c m Int64 where
  fromValue (VInt x) = pure x
  fromValue v = couldNotCast v

instance ToValue c m Int where
  toValue = toValue . (fromIntegral :: Int -> Int64)

instance Pretty c => FromValue c m Int where
  fromValue v = do
    i <- fromValue v `catchError` (\_ -> couldNotCast v)
    if (i :: Int64) < fromIntegral (minBound :: Int) || i > fromIntegral (maxBound :: Int)
      then couldNotCast v
      else pure $ fromIntegral i

instance ToValue c m Integer where
  toValue = pure . VInt . fromInteger

instance Pretty c => FromValue c m Integer where
  fromValue (VInt x) = pure $ fromIntegral x
  fromValue v = couldNotCast v

instance ToValue c m Word16 where
  toValue = pure . VWord16

instance Pretty c => FromValue c m Word16 where
  fromValue (VWord16 w) = pure w
  fromValue v = couldNotCast v

instance ToValue c m Word32 where
  toValue = pure . VWord32

instance Pretty c => FromValue c m Word32 where
  fromValue (VWord32 w) = pure w
  fromValue v = couldNotCast v

instance ToValue c m Word64 where
  toValue = pure . VWord64

instance Pretty c => FromValue c m Word64 where
  fromValue (VWord64 w) = pure w
  fromValue v = couldNotCast v

instance ToValue c m () where
  toValue _ = pure $ VTuple []

instance Pretty c => FromValue c m () where
  fromValue (VTuple []) = pure ()
  fromValue v = couldNotCast v

instance ToValue c m CTime where
  toValue = pure . VEpochTime

instance Pretty c => FromValue c m CTime where
  fromValue (VEpochTime t) = pure t
  fromValue v = couldNotCast v

instance ToValue c m Text where
  toValue = pure . VText

instance Pretty c => FromValue c m Text where
  fromValue (VText t) = pure t
  fromValue v = couldNotCast v

instance Kind0 Bool where
  toType _ = TBase $ TEnum "bool" $ Set.fromList ["true", "false"]

instance Kind0 Float where
  toType _ = TBase $ TDouble

instance Kind0 Double where
  toType _ = TBase $ TDouble

instance Kind0 Int where
  toType _ = TBase $ TInt

instance Kind0 Int64 where
  toType _ = TBase $ TInt

instance Kind0 Integer where
  toType _ = TBase $ TInt

instance Kind0 Word16 where
  toType _ = TBase $ TWord16

instance Kind0 Word32 where
  toType _ = TBase $ TWord32

instance Kind0 Word64 where
  toType _ = TBase $ TWord64

instance Kind0 () where
  toType _ = TTuple TNil

instance Kind0 CTime where
  toType _ = TBase $ TTime

instance Kind0 Text where
  toType _ = TBase $ TText

instance (Kind0 a, Kind0 b) => Kind0 (a -> b) where
  toType _ = TArr (toType (Proxy :: Proxy a)) (toType (Proxy :: Proxy b))

instance (Kind0 a) => Kind0 [a] where
  toType _ = TArray (toType (Proxy :: Proxy a))

instance (FromValue c m a, ToValue c m b) => ToValue c m (a -> b) where
  toValue f = pure $
    VFun $ \v -> do
      x <- fromValue v
      toValue $ f x

instance (Monad m, FromValue c (ImplEnvM m c) a1, FromValue c (ImplEnvM m c) a2, ToValue c (ImplEnvM m c) a3, KnownSymbol lbl) => ToValue c (ImplEnvM m c) (ImplicitCast lbl a1 a2 a3) where
  toValue (ImplicitCast f) = pure $
    VFun $ \b' -> do
      impl <- ask
      let i = ExtIdent $ Right $ pack $ symbolVal (Proxy :: Proxy lbl)
      case Map.lookup i impl of
        Just v -> do
          x <- fromValue v
          b <- fromValue b'
          toValue $ f x b
        Nothing -> throwError $ NotFoundInImplicitEnv i

-- | In this instance, the 'IO' in the type is ignored.
instance Kind0 a => Kind0 (IO a) where
  toType _ = toType (Proxy :: Proxy a)

instance ToValue c m a => ToValue c m (Maybe a) where
  toValue (Just x) = VOne <$> toValue x
  toValue _ = pure VEmpty

instance (Typeable a, FromValue c m a, Pretty c) => FromValue c m (Maybe a) where
  fromValue VEmpty = pure Nothing
  fromValue (VOne v) = Just <$> fromValue v
  fromValue v = couldNotCast v

instance Kind0 a => Kind0 (Maybe a) where
  toType _ = TOptional (toType (Proxy :: Proxy a))

instance (ToValue c m a, ToValue c m b) => ToValue c m (Either a b) where
  toValue (Left x) = toValue x
  toValue (Right x) = toValue x

instance ToValue c m a => ToValue c m [a] where
  toValue xs = VArray <$> (mapM toValue xs)

instance (Typeable a, FromValue c m a, Pretty c) => FromValue c m [a] where
  fromValue (VArray vs) = mapM fromValue vs
  fromValue v = couldNotCast v

instance (FromValue c m a, FromValue c m b) => FromValue c m (Either a b) where
  fromValue v = (Left <$> fromValue v) `catchError` (\_ -> Right <$> fromValue v)

instance Kind0 (Either a b) where
  toType _ = error "Definitions with Either must have explicit type signature"

-- instance ToValue IO a => ToValue IO (IO a) where
--   toValue io = io >>= toValue

-- instance FromValue m a => FromValue m (IO a) where
--   fromValue = fmap pure . fromValue

-- instance FromValue m (EitherN '[]) where
--   fromValue v = undefined

-- instance (FromValue m a, FromValue m (EitherN as)) => FromValue m (EitherN (a ': as)) where
--   fromValue v = (Here <$> fromValue v) `catchError` (\_ -> Next <$> fromValue v)

-- instance ToValue m (EitherN '[]) where
--   toValue = undefined

-- instance (ToValue m a, ToValue m (EitherN as)) => ToValue m (EitherN (a ': as)) where
--   toValue (Here  x) = toValue x
--   toValue (Next x) = toValue x

-- serializeToDouble :: MonadError EvalError m => Env -> Value m' -> m Double
-- serializeToDouble TypeEnv{..} = \case
--   VInt i -> return $ fromIntegral i
--   VDouble d -> return d
--   VEnum "true" -> return 1.0
--   VEnum "false" -> return 0.0
--   VEnum e -> case Map.lookup e enums of
--     Just (EnumMeta _ _ cs _) -> case fromIntegral <$> elemIndex e cs of
--       Just d -> return d
--       Nothing -> throwError $ RuntimeError $ "Malformed environment! Could not find enum constructor in the list"
--     Just _ -> throwError $ RuntimeError $ "Malformed environment! Was expecting enum metadata"
--     Nothing -> throwError $ CastError $ "Enum #" <> Text.unpack e <> " could not be found in the environment."
--   VWord16 w -> return $ fromIntegral w
--   VWord32 w -> return $ fromIntegral w
--   VWord64 w -> return $ fromIntegral w

-- -- deserializeFromDouble :: MonadError EvalError m => Env -> Double -> InfernoType -> m (Value m')
-- -- deserializeFromDouble env d = \case
-- --   TBase TInt -> return $ VInt $