sdl3-bindgen-sys-0.0.0.1: runtime-cexpr/C/Operator/Internal.hs
{-# LANGUAGE ScopedTypeVariables #-}
module C.Operator.Internal where
import Control.Arrow (first, second, (***))
import Control.Exception (assert)
import Data.Kind qualified as Hs
import Data.Nat (Nat (..))
import Data.Vec.Lazy (Vec (..))
import Data.Void qualified as Absurd
import GHC.Generics (Generic)
import C.Type
--------------------------------------------------------------------------------
-- | **Internal implementation detail**
--
-- How is a C operator, when instantiated at a particular type, implemented?
type OpImpl :: Nat -> Hs.Type
data OpImpl n
= -- | Convert arguments with the given conversions and then apply
-- the implied function (e.g. addition for the 'Add' class)
-- at the resulting type.
ConvertThenOp
{argumentConversions :: !(Vec n [Conversion])}
| -- | Add an integral value and a pointer.
AddIntegralAndPtr
| -- | Add a pointer and an integral value.
AddPtrAndIntegral
| -- | Get the difference between two pointers.
SubPtrAndPtr
| -- | Subtract an integral value from a pointer.
SubPtrAndIntegral
deriving stock (Generic, Show)
data Conversion
= FromIntegralTo {fromIntegralTo :: !(Type Absurd.Void)}
| RealToFracTo {realToFracTo :: !(Type Absurd.Void)}
| PtrToInt
deriving stock (Generic, Show)
--------------------------------------------------------------------------------
type Op :: Nat -> Hs.Type
data Op arity where
UnaryOp :: UnaryOp -> Op (S Z)
BinaryOp :: BinaryOp -> Op (S (S Z))
data UnaryOp
= -- | @+@
UnaryPlus
| -- | @-@
UnaryMinus
| -- | @!@
LogicalNot
| -- | @~@
BitwiseNot
deriving stock (Bounded, Enum, Eq, Ord, Show)
data BinaryOp
= -- | @*@
Mult
| -- | @/@
Div
| -- | @%@
Rem
| -- | @+@
Add
| -- | @-@
Sub
| -- | @<<@
ShiftLeft
| -- | @>>@
ShiftRight
| -- | @<@
RelLT
| -- | @<=@
RelLE
| -- | @>@
RelGT
| -- | @>=@
RelGE
| -- | @==@
RelEQ
| -- | @!=@
RelNE
| -- | @&@
BitwiseAnd
| -- | @^@
BitwiseXor
| -- | @|@
BitwiseOr
| -- | @&&@
LogicalAnd
| -- | @||@
LogicalOr
deriving stock (Bounded, Enum, Eq, Ord, Show)
pprOp :: Op arity -> String
pprOp = \case
UnaryOp op ->
case op of
UnaryPlus -> "+"
UnaryMinus -> "-"
LogicalNot -> "!"
BitwiseNot -> "~"
BinaryOp op ->
case op of
Mult -> "*"
Div -> "/"
Rem -> "%"
Add -> "+"
Sub -> "-"
ShiftLeft -> "<<"
ShiftRight -> ">>"
RelLT -> "<"
RelLE -> "<="
RelGT -> ">"
RelGE -> ">="
RelEQ -> "=="
RelNE -> "!="
BitwiseAnd -> "&"
BitwiseXor -> "^"
BitwiseOr -> "|"
LogicalAnd -> "&&"
LogicalOr -> "||"
pprOpApp :: forall arity. Op arity -> Vec arity String -> String
pprOpApp op args =
case op of
UnaryOp{} -> unary
BinaryOp{} -> binary
where
unary :: (arity ~ S Z) => String
unary =
case args of
a ::: VNil ->
pprOp op ++ a
binary :: (arity ~ S (S Z)) => String
binary =
case args of
a ::: b ::: VNil ->
a ++ pprOp op ++ b
--------------------------------------------------------------------------------
opResTypeAndImpl
:: forall arity a
. (Eq a) => Platform -> Op arity -> Vec arity (Type a) -> Maybe (Type a, OpImpl arity)
opResTypeAndImpl plat op args =
case op of
UnaryOp o ->
case o of
UnaryPlus -> unary unaryPlusType
UnaryMinus -> unary unaryMinusType
LogicalNot -> unary unaryLogicalType
BitwiseNot -> unary integralUnaryType
BinaryOp o ->
case o of
Mult -> binary binaryMultiplicativeType
Div -> binary binaryMultiplicativeType
Rem -> binary integralBinaryType
Add -> binary binaryAddType
Sub -> binary binarySubType
ShiftLeft -> binary shiftType
ShiftRight -> binary shiftType
RelLT -> binary binaryRelType
RelLE -> binary binaryRelType
RelGT -> binary binaryRelType
RelGE -> binary binaryRelType
RelEQ -> binary binaryEqType
RelNE -> binary binaryEqType
BitwiseAnd -> binary integralBinaryType
BitwiseXor -> binary integralBinaryType
BitwiseOr -> binary integralBinaryType
LogicalAnd -> binary binaryLogicalType
LogicalOr -> binary binaryLogicalType
where
unary :: (arity ~ S Z) => (Platform -> Type a -> Maybe r) -> Maybe r
unary f =
case args of
a ::: VNil ->
f plat a
binary :: (arity ~ S (S Z)) => (Platform -> Type a -> Type a -> Maybe r) -> Maybe r
binary f =
case args of
a ::: b ::: VNil ->
f plat a b
--------------------------------------------------------------------------------
-- | Result type of unary @+@
unaryPlusType :: Platform -> Type a -> Maybe (Type a, OpImpl (S Z))
unaryPlusType plat = \case
Arithmetic ty ->
Just $ mkArithConv1 $ arithmeticPromotion plat ty
Ptr{} ->
-- The C++ standard allows unary plus on pointers, but the C standard doesn't.
Nothing
Void -> Nothing
-- | Result type of unary @-@
unaryMinusType :: Platform -> Type a -> Maybe (Type a, OpImpl (S Z))
unaryMinusType plat = \case
Arithmetic ty ->
Just $ mkArithConv1 $ arithmeticPromotion plat ty
Ptr{} -> Nothing
Void -> Nothing
-- | Result type of binary @+@
binaryAddType :: Platform -> Type a -> Type a -> Maybe (Type a, OpImpl (S (S Z)))
binaryAddType plat (Arithmetic a1) (Arithmetic a2) =
Just $ mkArithConv2 $ arithmeticConversion plat a1 a2
binaryAddType _ (Arithmetic (Integral{})) ptr@(Ptr{}) =
Just (ptr, AddIntegralAndPtr)
binaryAddType _ ptr@(Ptr{}) (Arithmetic (Integral{})) =
Just (ptr, AddPtrAndIntegral)
binaryAddType _ _ _ =
Nothing
-- | Result type of binary @-@
binarySubType :: (Eq a) => Platform -> Type a -> Type a -> Maybe (Type a, OpImpl (S (S Z)))
binarySubType plat (Arithmetic a1) (Arithmetic a2) =
Just $ mkArithConv2 $ arithmeticConversion plat a1 a2
binarySubType _ ptr@(Ptr{}) (Arithmetic (Integral{})) =
Just (ptr, SubPtrAndIntegral)
binarySubType _ (Ptr ty1) (Ptr ty2)
| ty1 == ty2 =
-- TODO: do we want to be more lenient in allowing subtraction of pointers
-- with different pointee types, e.g. allow @(x :: Ptr Void) - (y :: Ptr Int)@?
Just (Arithmetic $ Integral $ IntLike PtrDiff, SubPtrAndPtr)
binarySubType _ _ _ =
Nothing
-- | Result type for multiplication and division (integral and floating-point)
binaryMultiplicativeType :: Platform -> Type a -> Type a -> Maybe (Type a, OpImpl (S (S Z)))
binaryMultiplicativeType plat (Arithmetic a1) (Arithmetic a2) =
Just $ mkArithConv2 $ arithmeticConversion plat a1 a2
binaryMultiplicativeType _ _ _ = Nothing
-- | Result type for bitwise not operator
integralUnaryType :: Platform -> Type a -> Maybe (Type a, OpImpl (S Z))
integralUnaryType plat (Arithmetic a1)
| Integral{} <- a1 =
Just $ mkArithConv1 $ arithmeticPromotion plat a1
integralUnaryType _ _ = Nothing
-- | Type for integral remainder and binary bitwise logic operators
integralBinaryType :: Platform -> Type a -> Type a -> Maybe (Type a, OpImpl (S (S Z)))
integralBinaryType plat (Arithmetic a1) (Arithmetic a2)
| Integral{} <- a1
, Integral{} <- a2 =
Just $ mkArithConv2 $ arithmeticConversion plat a1 a2
integralBinaryType _ _ _ = Nothing
-- | Type for binary shift operators
shiftType
:: Platform
-> Type a
-- ^ type of the value being shifted
-> Type a
-- ^ type of the shift amount
-> Maybe (Type a, OpImpl (S (S Z)))
shiftType plat (Arithmetic a1@(Integral{})) (Arithmetic a2@(Integral{})) =
let (i1, c1) = arithmeticPromotion plat a1
(_, c2) = arithmeticPromotion plat a2
in Just (Arithmetic i1, ConvertThenOp (c1 ::: c2 ::: VNil))
shiftType _ _ _ =
Nothing
intType, uintType :: Type a
intType = Arithmetic $ Integral $ IntLike $ Int Signed
uintType = Arithmetic $ Integral $ IntLike $ Int Unsigned
convertToInt :: Platform -> Type a -> Maybe [Conversion]
convertToInt _ = \case
Arithmetic a -> Just $ case a of
Integral i ->
case i of
IntLike (Int Signed) -> []
_ -> [FromIntegralTo intType]
FloatLike{} ->
[RealToFracTo intType]
Ptr{} ->
Just [PtrToInt]
_ ->
Nothing
-- | Type for logical not operation @!@.
unaryLogicalType :: Platform -> Type a -> Maybe (Type a, OpImpl (S Z))
unaryLogicalType plat a = do
_conv <- convertToInt plat a
return $ (intType, ConvertThenOp ([] ::: VNil))
-- | Type for binary equality operators @==@ and @!=@.
binaryEqType :: (Eq a) => Platform -> Type a -> Type a -> Maybe (Type a, OpImpl (S (S Z)))
binaryEqType plat (Arithmetic a1) (Arithmetic a2) =
Just $ mkArithConv2 (Integral $ IntLike $ Int Signed, snd $ arithmeticConversion plat a1 a2)
binaryEqType _ (Ptr ty1) (Ptr ty2)
| ty1 == ty2 =
-- TODO: does the C standard allow "Ptr Int" == "Ptr Void"?
Just (intType, ConvertThenOp ([] ::: [] ::: VNil))
binaryEqType _ _ _ = Nothing
-- | Type for binary logical operators @&&@ and @||@.
binaryLogicalType :: Platform -> Type a -> Type a -> Maybe (Type a, OpImpl (S (S Z)))
binaryLogicalType plat a1 a2 = do
_c1 <- convertToInt plat a1
_c2 <- convertToInt plat a2
return (intType, ConvertThenOp ([] ::: [] ::: VNil))
-- | Type for binary comparison operators @<@, @<=@, @>@, @>=@.
binaryRelType :: (Eq a) => Platform -> Type a -> Type a -> Maybe (Type a, OpImpl (S (S Z)))
binaryRelType plat (Arithmetic a1) (Arithmetic a2) =
Just $ mkArithConv2 (Integral $ IntLike $ Int Signed, snd $ arithmeticConversion plat a1 a2)
binaryRelType _ (Ptr ty1) (Ptr ty2)
| ty1 == ty2 =
-- TODO: C is a bit more lenient than requiring the inner types to
-- match exactly.
Just (intType, ConvertThenOp ([] ::: [] ::: VNil))
binaryRelType _ _ _ = Nothing
mkArithConv1 :: (ArithmeticType, [Conversion]) -> (Type a, OpImpl (S Z))
mkArithConv1 =
(Arithmetic *** (\a -> ConvertThenOp (a ::: VNil)))
mkArithConv2
:: (ArithmeticType, ([Conversion], [Conversion])) -> (Type a, OpImpl (S (S Z)))
mkArithConv2 =
(Arithmetic *** (\(a, b) -> ConvertThenOp (a ::: b ::: VNil)))
--------------------------------------------------------------------------------
arithmeticPromotion :: Platform -> ArithmeticType -> (ArithmeticType, [Conversion])
arithmeticPromotion _ f@(FloatLike{}) =
(f, [])
arithmeticPromotion plat (Integral i) =
first (Integral . IntLike) $ integralPromotion plat i
integralPromotion :: Platform -> IntegralType -> (IntLikeType, [Conversion])
-- C standard: Promotion from integral types (non bit-field case).
--
-- If the integer conversion rank of T is lower than that of int:
--
-- 1. promote T to int if int can represent all the values of T,
-- 2. otherwise promote T to unsigned int
integralPromotion plat (IntLike i)
| intLikeTypeConversionRank plat i < intLikeTypeConversionRank plat (Int Signed) =
if intLikeTypeFitsInInt plat i then
(Int Signed, [FromIntegralTo intType])
else
(Int Unsigned, [FromIntegralTo uintType])
| otherwise =
(i, [])
integralPromotion plat (CharLike c) =
assert
(charLikeTypeSizeInBits plat c < intLikeTypeSizeInBits plat (Int Signed))
(Int Signed, [FromIntegralTo intType])
integralPromotion _ Bool =
(Int Signed, [FromIntegralTo intType])
--------------------------------------------------------------------------------
-- Arithmetic conversion
arithmeticConversion
:: Platform
-> ArithmeticType
-> ArithmeticType
-> (ArithmeticType, ([Conversion], [Conversion]))
arithmeticConversion plat (Integral i1) (Integral i2) =
-- Both arguments are integral: do integral promotion then integral conversion.
let
(j1, c1) = integralPromotion plat i1
(j2, c2) = integralPromotion plat i2
(r, (d1, d2)) = integralArithmeticConversion plat j1 j2
in
(Integral $ IntLike r, (c1 ++ d1, c2 ++ d2))
-- At least one of the arguments is of floating-point type:
-- pick the largest floating-point type.
arithmeticConversion _ (FloatLike f1) (FloatLike f2) =
( FloatLike (max f1 f2)
, (if f2 > f1 then [rf f2] else [], if f1 > f2 then [rf f1] else [])
)
where
rf f = RealToFracTo $ Arithmetic $ FloatLike f
arithmeticConversion _ f@(FloatLike{}) (Integral{}) =
(f, ([], [FromIntegralTo (Arithmetic f)]))
arithmeticConversion _ (Integral{}) f@(FloatLike{}) =
(f, ([FromIntegralTo (Arithmetic f)], []))
integralArithmeticConversion
:: Platform -> IntLikeType -> IntLikeType -> (IntLikeType, ([Conversion], [Conversion]))
integralArithmeticConversion plat t1 t2
-- The following rules are applied to determine the arithmetic conversion result type 'C':
--
-- 1. If 'T1' and 'T2' are the same type, 'C' is that type.
| t1 == t2 =
(t1, ([], []))
-- 2. If T1 and T2 are both signed integer types or both unsigned integer types,
-- C is the type of greater integer conversion rank.
| s1 == s2 =
if rk1 >= rk2 then
(t1, ([], [FromIntegralTo (Arithmetic $ Integral $ IntLike t1)]))
else
(t2, ([FromIntegralTo (Arithmetic $ Integral $ IntLike t2)], []))
| otherwise =
-- 3. Otherwise, the types are of different signs.
-- Implement the logic in 'integralArithmeticConversion_differentSigns'.
case s1 of
Signed ->
integralArithmeticConversion_differentSigns plat (t1, rk1) (t2, rk2)
Unsigned ->
second (\(c1, c2) -> (c2, c1)) $
integralArithmeticConversion_differentSigns plat (t2, rk2) (t1, rk1)
where
s1, s2 :: Sign
s1 = intLikeTypeSign t1
s2 = intLikeTypeSign t2
rk1, rk2 :: IntegerConversionRank
rk1 = intLikeTypeConversionRank plat t1
rk2 = intLikeTypeConversionRank plat t2
integralArithmeticConversion_differentSigns
:: Platform
-> (IntLikeType, IntegerConversionRank)
-- ^ the signed type
-> (IntLikeType, IntegerConversionRank)
-- ^ the unsigned type
-> (IntLikeType, ([Conversion], [Conversion]))
integralArithmeticConversion_differentSigns plat s@(t_s, rk_s) u@(t_u, rk_u)
-- Implement the following rules to determine the arithmetic conversion
-- result type C for signed type S and unsigned type U:
--
-- 1. If the rank of U is greater than or equal to the rank of S, C is U.
| rk_u >= rk_s =
(t_u, ([FromIntegralTo (Arithmetic $ Integral $ IntLike t_u)], []))
-- Otherwise, S has (strictly) greater rank than U.
--
-- 2. If S can represent all of the values of U, C is S.
| unsignedFitsInSigned plat t_u t_s =
(t_s, ([], [FromIntegralTo (Arithmetic $ Integral $ IntLike t_s)]))
-- 3. Otherwise, C is the unsigned integer type corresponding to S.
| otherwise =
( \iTy ->
let ty = Arithmetic $ Integral $ IntLike iTy
in (iTy, ([FromIntegralTo ty], [FromIntegralTo ty]))
)
$ case t_s of
Short{} -> Short Unsigned
Int{} -> Int Unsigned
Long{} -> Long Unsigned
LongLong{} -> LongLong Unsigned
_ ->
-- Should never happen, because any unsigned type of rank strictly
-- less than that of ptrdiff_t fits into ptrdiff_t.
error $
unlines
[ "integralArithmeticConversion_differentSigns: extended type"
, "ty: " ++ show t_s
, "s: " ++ show s
, "u: " ++ show u
]
-- | Does the given unsigned type fit into the given signed type?
unsignedFitsInSigned
:: Platform
-> IntLikeType
-- ^ the unsigned type
-> IntLikeType
-- ^ the signed type
-> Bool
unsignedFitsInSigned plat u s =
intLikeTypeSizeInBits plat s > intLikeTypeSizeInBits plat u
--------------------------------------------------------------------------------