{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeOperators #-}
module Toml.Type.Value
(
TValue (..)
, showType
, Value (..)
, eqValueList
, valueType
, TypeMismatchError (..)
, sameValue
) where
import Control.DeepSeq (NFData (..), rnf)
import Data.String (IsString (..))
import Data.Text (Text)
import Data.Time (Day, LocalTime, TimeOfDay, ZonedTime, zonedTimeToUTC)
import Data.Type.Equality ((:~:) (..))
import GHC.Generics (Generic)
data TValue = TBool | TInteger | TDouble | TText | TZoned | TLocal | TDay | THours | TArray
deriving (Eq, Show, Read, NFData, Generic)
showType :: TValue -> String
showType = drop 1 . show
data Value (t :: TValue) where
Bool :: Bool -> Value 'TBool
Integer :: Integer -> Value 'TInteger
Double :: Double -> Value 'TDouble
Text :: Text -> Value 'TText
Zoned :: ZonedTime -> Value 'TZoned
Local :: LocalTime -> Value 'TLocal
Day :: Day -> Value 'TDay
Hours :: TimeOfDay -> Value 'THours
Array :: [Value t] -> Value 'TArray
deriving instance Show (Value t)
instance NFData (Value t) where
rnf (Bool n) = rnf n
rnf (Integer n) = rnf n
rnf (Double n) = rnf n
rnf (Text n) = rnf n
rnf (Zoned n) = rnf n
rnf (Local n) = rnf n
rnf (Day n) = rnf n
rnf (Hours n) = rnf n
rnf (Array n) = rnf n
instance (t ~ 'TInteger) => Num (Value t) where
(Integer a) + (Integer b) = Integer $ a + b
(Integer a) * (Integer b) = Integer $ a * b
abs (Integer a) = Integer (abs a)
signum (Integer a) = Integer (signum a)
fromInteger = Integer
negate (Integer a) = Integer (negate a)
instance (t ~ 'TText) => IsString (Value t) where
fromString = Text . fromString @Text
instance Eq (Value t) where
(Bool b1) == (Bool b2) = b1 == b2
(Integer i1) == (Integer i2) = i1 == i2
(Double f1) == (Double f2)
| isNaN f1 && isNaN f2 = True
| otherwise = f1 == f2
(Text s1) == (Text s2) = s1 == s2
(Zoned a) == (Zoned b) = zonedTimeToUTC a == zonedTimeToUTC b
(Local a) == (Local b) = a == b
(Day a) == (Day b) = a == b
(Hours a) == (Hours b) = a == b
(Array a1) == (Array a2) = eqValueList a1 a2
eqValueList :: [Value a] -> [Value b] -> Bool
eqValueList [] [] = True
eqValueList (x:xs) (y:ys) = case sameValue x y of
Right Refl -> x == y && eqValueList xs ys
Left _ -> False
eqValueList _ _ = False
valueType :: Value t -> TValue
valueType (Bool _) = TBool
valueType (Integer _) = TInteger
valueType (Double _) = TDouble
valueType (Text _) = TText
valueType (Zoned _) = TZoned
valueType (Local _) = TLocal
valueType (Day _) = TDay
valueType (Hours _) = THours
valueType (Array _) = TArray
data TypeMismatchError = TypeMismatchError
{ typeExpected :: TValue
, typeActual :: TValue
} deriving (Eq)
instance Show TypeMismatchError where
show TypeMismatchError{..} = "Expected type '" ++ showType typeExpected
++ "' but actual type: '" ++ showType typeActual ++ "'"
sameValue :: Value a -> Value b -> Either TypeMismatchError (a :~: b)
sameValue Bool{} Bool{} = Right Refl
sameValue Integer{} Integer{} = Right Refl
sameValue Double{} Double{} = Right Refl
sameValue Text{} Text{} = Right Refl
sameValue Zoned{} Zoned{} = Right Refl
sameValue Local{} Local{} = Right Refl
sameValue Day{} Day{} = Right Refl
sameValue Hours{} Hours{} = Right Refl
sameValue Array{} Array{} = Right Refl
sameValue l r = Left $ TypeMismatchError
{ typeExpected = valueType l
, typeActual = valueType r
}