{-# LANGUAGE ScopedTypeVariables #-} module Zinza.Class ( Zinza (..), ) where import Control.Exception (throw) import Data.Foldable (toList) import Data.Proxy (Proxy (..)) import qualified Data.List.NonEmpty as NE import qualified Data.Map.Lazy as Map import qualified Data.Set as Set import qualified Data.Text as T import qualified Data.Text.Lazy as LT import Zinza.Errors import Zinza.Type import Zinza.Pos import Zinza.Value -- -- $setup -- >>> :set -XDeriveGeneric -- >>> import Data.Proxy (Proxy (..)) -- >>> import GHC.Generics (Generic) -- >>> import Zinza -- | 'Zinza' class tells how to convert the type into template parameters, -- and their types. -- -- Class can be auto-derived for product types. -- -- >>> data R = R { recFoo :: String, recBar :: Char } deriving Generic -- >>> instance Zinza R where toType = genericToTypeSFP; toValue = genericToValueSFP; fromValue = genericFromValueSFP -- >>> displayTy $ toType (Proxy :: Proxy R) -- "{bar: String, foo: String}" -- class Zinza a where toType :: Proxy a -> Ty toTypeList :: Proxy a -> Ty toTypeList = TyList Nothing . toType toValue :: a -> Value toValueList :: [a] -> Value toValueList = VList . map toValue fromValue :: Loc -> Value -> Either RuntimeError a fromValueList :: Loc -> Value -> Either RuntimeError [a] fromValueList l (VList xs) = traverse (fromValue l) xs fromValueList l v = throwRuntime $ NotList l (valueType v) instance Zinza () where toType _ = tyUnit toValue _ = VRecord mempty -- we can be strict, but it's easy to just eat some errors. fromValue _ _ = return () instance Zinza Bool where toType _ = TyBool toValue = VBool fromValue _ (VBool b) = return b fromValue l v = throwRuntime (NotBool l (valueType v)) instance Zinza Char where toType _ = TyString (Just "return") toTypeList _ = TyString Nothing toValue = VString . return toValueList = VString fromValue _ (VString [c]) = return c fromValue l v = throwRuntime $ CustomError l "Not Char" (valueType v) fromValueList _ (VString s) = return s fromValueList l v = throwRuntime (NotString l (valueType v)) instance Zinza a => Zinza [a] where toType _ = toTypeList (Proxy :: Proxy a) toValue = toValueList fromValue = fromValueList instance (Zinza a, Zinza b) => Zinza (a, b) where toType _ = TyRecord $ Map.fromList [ ("fst", ("fst", toType (Proxy :: Proxy a))) , ("snd", ("snd", toType (Proxy :: Proxy b))) ] toValue (a, b) = VRecord $ Map.fromList [ ("fst", toValue a) , ("snd", toValue b) ] fromValue l (VRecord m) | [("fst", x), ("snd", y)] <- Map.toList m = (,) <$> fromValue l x <*> fromValue l y fromValue l v = throwRuntime $ CustomError l "Not pair" (valueType v) ------------------------------------------------------------------------------- -- Functions ------------------------------------------------------------------------------- -- | The 'fromValue' for function produces partial functions. -- Use with care. -- -- This means that higher order functions in templates might throw -- pure exception. They wont, if they are well-typed. -- instance (Zinza a, Zinza b) => Zinza (a -> b) where toType _ = TyFun (toType (Proxy :: Proxy a)) (toType (Proxy :: Proxy b)) toValue f = VFun $ fmap (toValue . f) . fromValue zeroLoc fromValue l (VFun f) = return $ either throw id . (>>= fromValue l) . f . toValue fromValue l v = throwRuntime $ NotFunction l (valueType v) ------------------------------------------------------------------------------- -- semigroups ------------------------------------------------------------------------------- instance Zinza a => Zinza (NE.NonEmpty a) where toType _ = TyList Nothing (toType (Proxy :: Proxy a)) toValue = VList . map toValue . toList fromValue l v = do xs <- fromValue l v case xs of [] -> throwRuntime $ CustomError l "Not NonEmpty" (valueType v) (y:ys) -> return (y NE.:| ys) ------------------------------------------------------------------------------- -- containers ------------------------------------------------------------------------------- instance (Zinza a, Ord a) => Zinza (Set.Set a) where toType _ = TyList Nothing (toType (Proxy :: Proxy a)) toValue = VList . map toValue . toList fromValue l = fmap Set.fromList . fromValue l -- | Pairs are encoded as @{ key: k, val: v }@ instance (Zinza k, Zinza v, Ord k) => Zinza (Map.Map k v) where toType _ = TyList (Just "Map.toList") $ TyRecord $ Map.fromList [ ("key", ("fst", toType (Proxy :: Proxy k))) , ("val", ("snd", toType (Proxy :: Proxy v))) ] toValue m = VList [ VRecord $ Map.fromList [ ("key", toValue k) , ("val", toValue v) ] | (k, v) <- Map.toList m ] fromValue l (VList xs) = do kvs <- traverse fromPair xs return (Map.fromList kvs) where fromPair (VRecord m) | [("key", x), ("val", y)] <- Map.toList m = (,) <$> fromValue l x <*> fromValue l y fromPair v = throwRuntime $ CustomError l "Not pair" (valueType v) fromValue l v = throwRuntime $ NotList l (valueType v) ------------------------------------------------------------------------------- -- text ------------------------------------------------------------------------------- instance Zinza T.Text where toType _ = TyString (Just "T.unpack") toValue = VString . T.unpack fromValue l = fmap T.pack . fromValue l instance Zinza LT.Text where toType _ = TyString (Just "LT.unpack") toValue = VString . LT.unpack fromValue l = fmap LT.pack . fromValue l