packages feed

inferno-types-0.1.0.0: src/Inferno/Types/Type.hs

{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}

module Inferno.Types.Type
  ( BaseType (..),
    ImplType (..),
    Namespace (..),
    TCScheme (..),
    TV (..),
    InfernoType (..),
    TypeClass (..),
    TypeClassShape (..),
    TypeMetadata (..),
    Substitutable (..),
    Subst (..),
    Scheme (..),
    (.->),
    sch,
    var,
    tySig,
    namespaceToIdent,
    typeInt,
    typeBool,
    typeDouble,
    typeWord16,
    typeWord32,
    typeWord64,
    typeText,
    typeResolution,
    typeTimeDiff,
    typeTime,
    punctuate',
  )
where

import Data.Aeson (FromJSON, ToJSON)
import Data.Data (Data)
import Data.List (intercalate)
import Data.Map (Map, empty)
import qualified Data.Map as Map
import Data.Set (Set)
import qualified Data.Set as Set
-- import Data.String (IsString)
import Data.Text (Text, unpack)
import GHC.Generics (Generic)
import Inferno.Types.Syntax (BaseType (..), Expr, ExtIdent (..), Ident (..), InfernoType (..), ModuleName (..), TV (..), punctuate')
import Inferno.Utils.Prettyprinter (renderPretty)
import Prettyprinter
  ( Doc,
    Pretty (pretty),
    align,
    comma,
    enclose,
    encloseSep,
    hsep,
    lbrace,
    lparen,
    rbrace,
    rparen,
    -- sep,
    -- tupled,
    (<+>),
  )
import Test.QuickCheck (Arbitrary (..), oneof)
import Test.QuickCheck.Arbitrary.ADT (ToADTArbitrary)

data ImplType = ImplType (Map ExtIdent InfernoType) InfernoType
  deriving (Show, Eq, Ord, Data, Generic, ToJSON, FromJSON)

data Scheme = Forall [TV] ImplType
  deriving (Show, Eq, Ord, Data, Generic, ToJSON, FromJSON)

typeInt, typeBool, typeDouble, typeWord16, typeWord32, typeWord64, typeText, typeResolution, typeTimeDiff, typeTime :: InfernoType
typeInt = TBase TInt
typeBool = TBase $ TEnum "bool" $ Set.fromList ["true", "false"]
typeDouble = TBase TDouble
typeWord16 = TBase TWord16
typeWord32 = TBase TWord32
typeWord64 = TBase TWord64
typeText = TBase TText
typeTimeDiff = TBase TTimeDiff
typeTime = TBase TTime
typeResolution = TBase TResolution

sch :: InfernoType -> Scheme
sch = Forall [] . ImplType empty

infixr 3 .->

(.->) :: InfernoType -> InfernoType -> InfernoType
x .-> y = TArr x y

var :: Int -> InfernoType
var x = TVar (TV x)

data TypeClass = TypeClass
  { className :: Text,
    params :: [InfernoType]
  }
  deriving (Show, Eq, Ord, Data, Generic, ToJSON, FromJSON)

data TCScheme = ForallTC [TV] (Set TypeClass) ImplType
  deriving (Show, Eq, Ord, Data, Generic, ToJSON, FromJSON)

tySig :: [Doc ann] -> [Doc ann]
tySig [] = []
tySig [d] = [":" <+> d]
tySig (d : ds) = (":" <+> d) : go ds
  where
    go [] = []
    go (d' : ds') = ("|" <+> d') : go ds'

instance Pretty ImplType where
  pretty (ImplType impl ty)
    | Map.null impl = pretty ty
    | otherwise =
      encloseSep
        lbrace
        rbrace
        comma
        (map (\(ExtIdent idt, t) -> "implicit" <+> case idt of { Left i -> "var$" <> pretty i; Right i -> pretty i } <+> ":" <+> align (pretty t)) $ Map.toList impl)
        <+> "⇒"
        <+> pretty ty

instance Pretty Scheme where
  pretty (Forall _ implType) = pretty implType

instance Pretty TypeClass where
  pretty = \case
    TypeClass nm tys -> pretty nm <+> "on" <+> (hsep $ map bracketPretty tys)
    where
      bracketPretty ty = case ty of
        TVar _ -> pretty ty
        TBase _ -> pretty ty
        _ -> enclose lparen rparen $ pretty ty

newtype TypeClassShape = TypeClassShape TypeClass

instance Pretty TypeClassShape where
  pretty = \case
    TypeClassShape (TypeClass nm tys) -> pretty nm <+> "on" <+> (hsep $ map bracketPretty tys)
    where
      bracketPretty ty = case ty of
        TVar _ -> "_"
        TBase _ -> pretty ty
        _ -> enclose lparen rparen $ pretty ty

instance Pretty TCScheme where
  pretty (ForallTC _ tcs (ImplType impl ty))
    | Map.null impl && null tcs = pretty ty
    | otherwise =
      encloseSep
        lbrace
        rbrace
        comma
        ( (map (("requires" <+>) . pretty) $ Set.toList tcs)
            ++ (map (\(ExtIdent idt, t) -> "implicit" <+> case idt of { Left i -> "var$" <> pretty i; Right i -> pretty i } <+> ":" <+> align (pretty t)) $ Map.toList impl)
        )
        <+> "⇒"
        <+> pretty ty

newtype Subst = Subst (Map.Map TV InfernoType)
  deriving stock (Eq, Ord)
  deriving newtype (Semigroup, Monoid)

instance Show Subst where
  show (Subst m) = intercalate "\n" $ map (\(x, t) -> unpack $ renderPretty x <> " ~> " <> renderPretty t) $ Map.toList m

class Substitutable a where
  apply :: Subst -> a -> a
  ftv :: a -> Set.Set TV

instance Substitutable InfernoType where
  apply _ (TBase a) = TBase a
  apply (Subst s) t@(TVar a) = Map.findWithDefault t a s
  apply s (t1 `TArr` t2) = apply s t1 `TArr` apply s t2
  apply s (TArray t) = TArray $ apply s t
  apply s (TSeries t) = TSeries $ apply s t
  apply s (TOptional t) = TOptional $ apply s t
  apply s (TTuple ts) = TTuple $ fmap (apply s) ts
  apply s (TRep t) = TRep $ apply s t

  ftv TBase {} = Set.empty
  ftv (TVar a) = Set.singleton a
  ftv (t1 `TArr` t2) = ftv t1 `Set.union` ftv t2
  ftv (TArray t) = ftv t
  ftv (TSeries t) = ftv t
  ftv (TOptional t) = ftv t
  ftv (TTuple ts) = foldr (Set.union . ftv) Set.empty ts
  ftv (TRep t) = ftv t

instance Substitutable ImplType where
  apply s (ImplType impl t) =
    ImplType (Map.map (apply s) impl) $ apply s t
  ftv (ImplType impl t) = (foldr Set.union Set.empty $ map (ftv . snd) $ Map.toList impl) `Set.union` ftv t

instance Substitutable TypeClass where
  apply s (TypeClass n tys) = TypeClass n $ map (apply s) tys
  ftv (TypeClass _ tys) = Set.unions $ map ftv tys

instance Substitutable TCScheme where
  apply (Subst s) (ForallTC as tcs t) = ForallTC as (Set.map (apply s') tcs) (apply s' t)
    where
      s' = Subst $ foldr Map.delete s as
  ftv (ForallTC as tcs t) = ((ftv t) `Set.union` (Set.unions $ Set.elems $ Set.map ftv tcs)) `Set.difference` Set.fromList as

instance Substitutable a => Substitutable [a] where
  apply = map . apply
  ftv = foldr (Set.union . ftv) Set.empty

data Namespace
  = FunNamespace Ident
  | OpNamespace Ident
  | EnumNamespace Ident
  | ModuleNamespace ModuleName
  | TypeNamespace Ident
  deriving (Eq, Show, Ord, Generic, ToJSON, FromJSON, ToADTArbitrary)

instance Pretty Namespace where
  pretty = \case
    FunNamespace (Ident i) -> pretty i
    OpNamespace (Ident i) -> pretty i
    EnumNamespace (Ident i) -> "#" <> pretty i
    ModuleNamespace (ModuleName m) -> pretty m
    TypeNamespace (Ident i) -> pretty i

instance Arbitrary Namespace where
  arbitrary =
    oneof
      [ FunNamespace <$> arbitrary,
        OpNamespace <$> arbitrary,
        EnumNamespace <$> arbitrary,
        ModuleNamespace <$> arbitrary,
        TypeNamespace <$> arbitrary
      ]

namespaceToIdent :: Namespace -> Ident
namespaceToIdent = \case
  FunNamespace i -> i
  OpNamespace i -> i
  EnumNamespace i -> i
  TypeNamespace i -> i
  ModuleNamespace _ -> error "namespaceToIdent undefined for ModuleNamespace"

data TypeMetadata ty = TypeMetadata
  { identExpr :: Expr () (),
    docs :: Maybe Text,
    ty :: ty
  }
  deriving (Eq, Show, Generic, Data, ToJSON, FromJSON)