fir-0.1.0.0: src/FIR/Prim/Struct.hs
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
{-|
Module: FIR.Prim.Struct
A __structure__ (or __struct__ for short) can be thought of as an ordered record.
Here, we simply represent a struct as a linked list, with the fields annotated at the type level.
To illustrate:
> struct :: Struct '[ "field1" ':-> Float, "field2" ':-> V 3 Float ]
> struct = 1.0 :& V3 0.0 1.0 0.0 :& End
This defines a struct with two fields:
* first field named @"field1"@, of type @Float@,
* second field named @"field2"@, of type @V 3 Float@.
Note that the field annotations need not be type-level literals.
For instance, when specifying memory layout of vertex input data,
one instead specifies location slots (see "FIR.Layout"):
> inputData :: Struct '[ Slot 0 0 ':-> Float, Slot 0 3 ':-> V 3 Float ]
-}
module FIR.Prim.Struct
( Struct(End, (:&))
, LocationSlot(..)
, ShowLocationSlot
, FieldKind(..)
, StructFieldKind(fieldKind)
, HasStructField(getStructField, setStructField)
, headS, tailS
, foldrStruct
, traverseStruct
, ASTStructFields(..)
)
where
-- base
import Data.Kind
( Type )
import Data.List
( intercalate )
import Data.Typeable
( Typeable )
import Data.Word
( Word32 )
import GHC.TypeLits
( Symbol, KnownSymbol, AppendSymbol )
import GHC.TypeNats
( Nat, KnownNat
, type (+), type(-), type (*)
, Mod, Div
)
-- fir
import Data.Product
( HList(HNil,(:>)), IsProduct(..) )
import Data.Type.List
( type (:++:) )
import Data.Type.Map
( (:->)((:->)) )
import Data.Type.Known
( Demotable(Demote)
, Known(known), knownValue
)
import Data.Type.POrd
( POrd(Compare)
, PEnum(Succ, Pred, FromEnum, ToEnum)
)
import Data.Type.String
( ShowNat )
import FIR.AST.Type
( AugType(Val) )
import Math.Algebra.GradedSemigroup
( GradedSemigroup(..) )
import {-# SOURCE #-} FIR.Prim.Types
( PrimTy
, PrimTyMap(primTyMapSing)
, SPrimTyMap(SNil, SCons)
)
------------------------------------------------------------
-- structs
infixr 4 :&
-- order *matters* for structs (memory layout!)
data Struct :: [fld :-> Type] -> Type where
End :: Struct '[]
(:&) :: forall k a as. a -> Struct as -> Struct ((k ':-> a) ': as)
headS :: Struct ((k ':-> a) ': as) -> a
headS (a :& _) = a
tailS :: Struct ((k ':-> a) ': as) -> Struct as
tailS (_ :& as) = as
data LocationSlot n where
LocationSlot :: n -> n -> LocationSlot n
deriving stock instance Show n => Show (LocationSlot n)
type family ShowLocationSlot ( slot :: LocationSlot Nat ) :: Symbol where
ShowLocationSlot ('LocationSlot l c)
= "Location " `AppendSymbol` ShowNat l `AppendSymbol` ", Component " `AppendSymbol` ShowNat c
data FieldKind (fld :: Type) where
NamedField :: FieldKind Symbol
LocationField :: FieldKind (LocationSlot Nat)
OtherField :: FieldKind fld
class (Show (Demote fld), Typeable fld) => StructFieldKind fld where
fieldKind :: FieldKind fld
instance StructFieldKind Symbol where
fieldKind = NamedField
instance StructFieldKind (LocationSlot Nat) where
fieldKind = LocationField
instance {-# OVERLAPPABLE #-} (Show (Demote fld), Typeable fld) => StructFieldKind fld where
fieldKind = OtherField
class HasStructField k a as | k as -> a where
getStructField :: Struct as -> a
setStructField :: a -> Struct as -> Struct as
instance {-# OVERLAPPING #-}
forall fld (k :: fld) (a :: Type) (as :: [ fld :-> Type ])
. HasStructField k a ((k ':-> a) ': as)
where
getStructField = headS
setStructField b ( _ :& as ) = b :& as
instance forall fld (k :: fld) (a :: Type) (l :: fld) (x :: Type) (as :: [ fld :-> Type ])
. HasStructField k a as
=> HasStructField k a ((l ':-> x) ': as)
where
getStructField = getStructField @k @a @as . tailS
setStructField b ( a :& as ) = a :& setStructField @k @a @as b as
instance {-# OVERLAPPING #-}
forall fld (k :: fld) (a :: Type) (as :: [ fld :-> Type ])
. HasStructField (0 :: Nat) a ((k ':-> a) ': as)
where
getStructField = headS
setStructField b ( _ :& as ) = b :& as
instance forall fld (l :: fld) (i :: Nat) (a :: Type) (x :: Type) (as :: [ fld :-> Type])
. HasStructField (i-1) a as
=> HasStructField i a ((l ':-> x) ': as)
where
getStructField = getStructField @(i-1) @a @as . tailS
setStructField b ( a :& as ) = a :& setStructField @(i-1) @a @as b as
instance PrimTyMap as => Eq (Struct as) where
s1 == s2 = case primTyMapSing @_ @as of
SNil -> True
SCons ->
case (s1, s2) of
(a :& as, b :& bs) ->
a == b && as == bs
instance PrimTyMap as => Ord (Struct as) where
compare s1 s2 = case primTyMapSing @_ @as of
SNil -> EQ
SCons ->
case (s1, s2) of
(a :& as, b :& bs) ->
case compare a b of
EQ -> compare as bs
un -> un
display :: forall fld (as :: [fld :-> Type])
. ( StructFieldKind fld, PrimTyMap as )
=> Struct as -> [String]
display s = case primTyMapSing @_ @as of
SNil -> []
sCons@SCons ->
case sCons of
( _ :: SPrimTyMap ((k ':-> a) ': xs) ) ->
case s of
(a :& as) ->
( show (knownValue @k) ++ " :-> " ++ show a )
: display as
instance forall fld (as :: [fld :-> Type])
. ( StructFieldKind fld, PrimTyMap as )
=> Show (Struct as) where
show s = "{ " ++ intercalate ", " (display s) ++ " }"
instance IsProduct (Struct '[]) '[] where
fromHList _ = End
toHList _ = HNil
instance IsProduct (Struct as) tys => IsProduct (Struct ((fld ':-> ty) ': as)) (ty ': tys) where
fromHList (a :> as) = a :& fromHList as
toHList (a :& as) = a :> toHList as
instance GradedSemigroup (Struct :: [ fld :-> Type ] -> Type) [fld :-> Type] where
type Grade [fld :-> Type] (Struct :: [ fld :-> Type ] -> Type) as = Struct as
type as :<!>: bs = as :++: bs
(<!>) :: Struct as -> Struct bs -> Struct (as :++: bs)
End <!> t = t
(a :& s) <!> t = a :& ( s <!> t )
foldrStruct
:: forall as b. PrimTyMap as
=> ( forall a. PrimTy a => a -> b -> b )
-> b -> Struct as -> b
foldrStruct f b s =
case primTyMapSing @_ @as of
SNil
-> b
SCons {}
-> case s of
(a :& as)
-> f a (foldrStruct f b as)
traverseStruct
:: ( Applicative f, PrimTyMap as )
=> ( forall a. PrimTy a => a -> f b )
-> Struct as
-> f [b]
traverseStruct f
= foldrStruct ( \a bs -> liftA2 (:) (f a) bs )
( pure [] )
class Typeable bs =>
ASTStructFields
( ast :: ( AugType -> Type ) )
( as :: [ Symbol :-> Type ] )
( bs :: [ Symbol :-> Type ] )
| as -> bs, bs ast -> as where
traverseStructASTs
:: forall f b. Applicative f
=> ( forall a. ast (Val a) -> f b ) -> Struct as -> f [b]
instance ASTStructFields ast '[] '[] where
traverseStructASTs _ _ = pure []
instance (KnownSymbol k2, k1 ~ k2, a ~ ast (Val b), Typeable b, ASTStructFields ast as bs)
=> ASTStructFields ast ( (k1 ':-> a) ': as) ( (k2 ':-> b) ': bs) where
traverseStructASTs f (a :& as) = (:) <$> f a <*> traverseStructASTs f as
-----------------------------------------------------------
-- instances for 'LocationSlot'
instance Demotable (LocationSlot Nat) where
type Demote (LocationSlot Nat) = LocationSlot Word32
instance (KnownNat l, KnownNat c) => Known (LocationSlot Nat) ('LocationSlot l c) where
known = LocationSlot (knownValue @l) (knownValue @c)
instance POrd (LocationSlot Nat) where
type Compare ('LocationSlot l1 c1) ('LocationSlot l2 c2)
= Compare '(l1, c1) '(l2, c2) -- lexicographic ordering
type family SuccLocationSlot (slot :: LocationSlot Nat) :: LocationSlot Nat where
SuccLocationSlot ('LocationSlot l 3) = 'LocationSlot (l+1) 0
SuccLocationSlot ('LocationSlot l c) = 'LocationSlot l (c+1)
type family PredLocationSlot (slot :: LocationSlot Nat) :: LocationSlot Nat where
PredLocationSlot ('LocationSlot 0 0) = 'LocationSlot 0 0
PredLocationSlot ('LocationSlot l 0) = 'LocationSlot (l-1) 3
PredLocationSlot ('LocationSlot l c) = 'LocationSlot l (c-1)
instance PEnum (LocationSlot Nat) where
type Succ loc = SuccLocationSlot loc
type Pred loc = PredLocationSlot loc
type FromEnum ('LocationSlot l c)
= 4 * l + c
type ToEnum n
= 'LocationSlot (n `Div` 4) (n `Mod` 4)