Carp/src/Types.hs
Tim Dévé da25a255e9
feat: Adds flag to always output C id with headerparse (#1353)
Adds -c|--emitcname flag to headerparse to always emit the C identifier
in `register` definitions (useful when the register will be in a
module).

Fixes a bug where the kebab case flag would not output C identifiers
making the emitted C identifiers not match with the ones in the headers.

Adds docs entry about headerparse in CInterop doc.

Makes headerparse emit `CChar` instead of `Char` when encountering a
signature containing `char`.
2021-11-03 09:09:26 +01:00

382 lines
14 KiB
Haskell

{-# LANGUAGE DeriveGeneric #-}
module Types
( TypeMappings,
Ty (..),
showMaybeTy,
unifySignatures,
replaceTyVars,
areUnifiable,
typesDeleterFunctionType,
typesCopyFunctionType,
doesTypeContainTyVarWithName,
replaceConflicted,
lambdaEnvTy,
typeEqIgnoreLifetimes,
checkKinds,
-- SymPath imports
SymPath (..),
mangle,
pathToC,
consPath,
Kind,
tyToKind,
areKindsConsistent,
createStructName,
getStructName,
getPathFromStructName,
getNameFromStructName,
getStructPath,
promoteNumber,
)
where
import Data.Hashable
import Data.List (intercalate)
import Data.Maybe (fromMaybe)
import Data.Text (pack, splitOn, unpack)
import GHC.Generics (Generic)
import qualified Map
import SymPath
import Util
--import Debug.Trace
-- | Carp types.
data Ty
= IntTy
| LongTy
| ByteTy
| BoolTy
| FloatTy
| DoubleTy
| StringTy
| PatternTy
| CharTy
| CCharTy
| FuncTy [Ty] Ty Ty -- In order of appearance: (1) Argument types, (2) Return type, (3) Lifetime
| VarTy String
| UnitTy
| ModuleTy
| PointerTy Ty
| RefTy Ty Ty -- second Ty is the lifetime
| StaticLifetimeTy
| StructTy Ty [Ty] -- the name (possibly a var) of the struct, and it's type parameters
| ConcreteNameTy SymPath -- the name of a struct
| TypeTy -- the type of types
| MacroTy
| DynamicTy -- the type of dynamic functions (used in REPL and macros)
| InterfaceTy
| CTy -- C literals
| Universe -- the type of types of types (the type of TypeTy)
deriving (Eq, Ord, Generic)
instance Hashable Ty
-- | Kinds checking
-- Carp's system is simple enough that we do not need to describe kinds by their airty.
-- After confirming two tys have either base or higher kind
-- unification checks are sufficient to determine whether their arities are compatible.
data Kind
= Base
| Higher
deriving (Eq, Ord, Show)
tyToKind :: Ty -> Kind
tyToKind (StructTy _ _) = Higher
tyToKind FuncTy {} = Higher -- the type of functions, consider the (->) constructor in Haskell
tyToKind (PointerTy _) = Higher
tyToKind (RefTy _ _) = Higher -- Refs may also be treated as a data constructor
tyToKind _ = Base
-- | Check whether or not the kinds of type variables are consistent.
-- This function will return Left as soon as a variable is used inconsistently,
-- reporting which variable triggered the issue.
-- If all variables are used consistently, it will process the whole list and
-- return ().
--
-- Kind arity matters; that is, `(f a b)` is not consistent with
-- `(f b)`. So long as the kind of a variable is the same across its uses,
-- everything is OK, for example:
-- ((Foo f a b) [x (f a) y (f b)])
-- is valid, and so is
-- ((Foo f a b) [x f y a z b])
-- But a definition such as:
-- ((Foo f a b) [x (f a b) y (f a)])
-- is inconsistent (kind of `f` differs) and so is
-- ((Foo f a b) [x (f a) y b (b a)])
-- (kind of `b` is inconsistent.
areKindsConsistent :: [Ty] -> Either String ()
areKindsConsistent typeVars =
assignKinds typeVars Map.empty
where
assignKinds :: [Ty] -> Map.Map String Int -> Either String ()
assignKinds ((StructTy (VarTy name) vars) : rest) arityMap =
case Map.lookup name arityMap of
Nothing -> assignKinds next (Map.insert name kind arityMap)
Just k ->
if k == kind
then assignKinds next arityMap
else Left name
where
next = vars ++ rest
kind = length vars
assignKinds ((VarTy v) : rest) arityMap =
case Map.lookup v arityMap of
Nothing -> assignKinds rest (Map.insert v kind arityMap)
Just k ->
if k == kind
then assignKinds rest arityMap
else Left v
where
kind = 0
assignKinds (FuncTy args ret _ : rest) arityMap =
assignKinds (args ++ ret : rest) arityMap
assignKinds ((PointerTy p) : rest) arityMap =
assignKinds (p : rest) arityMap
assignKinds ((RefTy r _) : rest) arityMap =
assignKinds (r : rest) arityMap
assignKinds (_ : rest) arityMap = assignKinds rest arityMap
assignKinds [] _ = pure ()
-- Exactly like '==' for Ty, but ignore lifetime parameter
typeEqIgnoreLifetimes :: Ty -> Ty -> Bool
typeEqIgnoreLifetimes (RefTy a _) (RefTy b _) = a == b
typeEqIgnoreLifetimes (FuncTy argsA retA _) (FuncTy argsB retB _) =
all (== True) (zipWith typeEqIgnoreLifetimes argsA argsB)
&& typeEqIgnoreLifetimes retA retB
typeEqIgnoreLifetimes (StructTy a tyVarsA) (StructTy b tyVarsB) =
a == b
&& all (== True) (zipWith typeEqIgnoreLifetimes tyVarsA tyVarsB)
typeEqIgnoreLifetimes a b = a == b
data SumTyCase = SumTyCase
{ caseName :: String,
caseMembers :: [(String, Ty)]
}
deriving (Show, Ord, Eq)
fnOrLambda :: String
fnOrLambda =
case platform of
Windows -> "Fn"
_ -> "Fn" -- "λ"
instance Show Ty where
show IntTy = "Int"
show FloatTy = "Float"
show DoubleTy = "Double"
show LongTy = "Long"
show ByteTy = "Byte"
show BoolTy = "Bool"
show StringTy = "String"
show PatternTy = "Pattern"
show CharTy = "Char"
show CCharTy = "CChar"
show (FuncTy argTys retTy StaticLifetimeTy) = "(" ++ fnOrLambda ++ " [" ++ joinWithComma (map show argTys) ++ "] " ++ show retTy ++ ")"
show (FuncTy argTys retTy lt) = "(" ++ fnOrLambda ++ " [" ++ joinWithComma (map show argTys) ++ "] " ++ show retTy ++ " " ++ show lt ++ ")"
show (VarTy t) = t
show UnitTy = "()"
show ModuleTy = "Module"
show TypeTy = "Type"
show InterfaceTy = "Interface"
show (StructTy s []) = show s
show (StructTy s typeArgs) = "(" ++ show s ++ " " ++ joinWithSpace (map show typeArgs) ++ ")"
show (ConcreteNameTy spath) = show spath
show (PointerTy p) = "(Ptr " ++ show p ++ ")"
show (RefTy r lt) =
-- case r of
-- PointerTy _ -> listView
-- StructTy _ _ -> listView
-- FuncTy _ _ -> listView
-- _ -> "&" ++ show r
-- where listView = "(Ref " ++ show r ++ ")"
"(Ref " ++ show r ++ " " ++ show lt ++ ")"
show StaticLifetimeTy = "StaticLifetime"
show MacroTy = "Macro"
show DynamicTy = "Dynamic"
show Universe = "Universe"
show CTy = "C"
showMaybeTy :: Maybe Ty -> String
showMaybeTy (Just t) = show t
showMaybeTy Nothing = "(missing-type)"
doesTypeContainTyVarWithName :: String -> Ty -> Bool
doesTypeContainTyVarWithName name (VarTy n) = name == n
doesTypeContainTyVarWithName name (FuncTy argTys retTy lt) =
doesTypeContainTyVarWithName name lt
|| any (doesTypeContainTyVarWithName name) argTys
|| doesTypeContainTyVarWithName name retTy
doesTypeContainTyVarWithName name (StructTy n tyArgs) = doesTypeContainTyVarWithName name n || any (doesTypeContainTyVarWithName name) tyArgs
doesTypeContainTyVarWithName name (PointerTy p) = doesTypeContainTyVarWithName name p
doesTypeContainTyVarWithName name (RefTy r lt) =
doesTypeContainTyVarWithName name r
|| doesTypeContainTyVarWithName name lt
doesTypeContainTyVarWithName _ _ = False
replaceConflicted :: String -> Ty -> Ty
replaceConflicted name (VarTy n) =
if n == name
then VarTy (n ++ "conflicted")
else VarTy n
replaceConflicted name (FuncTy argTys retTy lt) =
FuncTy
(map (replaceConflicted name) argTys)
(replaceConflicted name retTy)
(replaceConflicted name lt)
replaceConflicted name (StructTy n tyArgs) = StructTy (replaceConflicted name n) (map (replaceConflicted name) tyArgs)
replaceConflicted name (PointerTy p) = PointerTy (replaceConflicted name p)
replaceConflicted name (RefTy r lt) =
RefTy
(replaceConflicted name r)
(replaceConflicted name lt)
replaceConflicted _ t = t
-- | Map type variable names to actual types, eg. t0 => Int, t1 => Float
type TypeMappings = Map.Map String Ty
-- | From two types, one with type variables and one without (e.g. (Fn ["t0"] "t1") and (Fn [Int] Bool))
-- create mappings that translate from the type variables to concrete types, e.g. "t0" => Int, "t1" => Bool
unifySignatures :: Ty -> Ty -> TypeMappings
unifySignatures at ct = Map.fromList (unify at ct)
where
unify :: Ty -> Ty -> [(String, Ty)]
unify (VarTy _) (VarTy _) = [] -- if a == b then [] else error ("Can't unify " ++ show a ++ " with " ++ show b)
unify (VarTy a) value = [(a, value)]
unify (StructTy v'@(VarTy _) aArgs) (StructTy n bArgs) = unify v' n ++ concat (zipWith unify aArgs bArgs)
unify (StructTy a@(ConcreteNameTy _) aArgs) (StructTy b bArgs)
| a == b = concat (zipWith unify aArgs bArgs)
| otherwise = [] -- error ("Can't unify " ++ a ++ " with " ++ b)
unify (StructTy _ _) _ = [] -- error ("Can't unify " ++ show a ++ " with " ++ show b)
unify (PointerTy a) (PointerTy b) = unify a b
unify (PointerTy _) _ = [] -- error ("Can't unify " ++ show a ++ " with " ++ show b)
unify (RefTy a ltA) (RefTy b ltB) = unify a b ++ unify ltA ltB
unify (RefTy _ _) _ = [] -- error ("Can't unify " ++ show a ++ " with " ++ show b)
unify (FuncTy argTysA retTyA ltA) (FuncTy argTysB retTyB ltB) =
let argToks = concat (zipWith unify argTysA argTysB)
retToks = unify retTyA retTyB
ltToks = unify ltA ltB
in ltToks ++ argToks ++ retToks
unify FuncTy {} _ = [] -- error ("Can't unify " ++ show a ++ " with " ++ show b)
unify a b
| a == b = []
| otherwise = [] -- error ("Can't unify " ++ show a ++ " with " ++ show b)
-- | Checks if two types will unify
areUnifiable :: Ty -> Ty -> Bool
areUnifiable (VarTy _) (VarTy _) = True
areUnifiable (VarTy _) _ = True
areUnifiable _ (VarTy _) = True
areUnifiable (StructTy a aArgs) (StructTy b bArgs)
| length aArgs /= length bArgs = False
| areUnifiable a b =
let argBools = zipWith areUnifiable aArgs bArgs
in all (== True) argBools
| otherwise = False
areUnifiable (StructTy (VarTy _) aArgs) (FuncTy bArgs _ _)
| length aArgs /= length bArgs = False
| otherwise = all (== True) (zipWith areUnifiable aArgs bArgs)
areUnifiable (StructTy (VarTy _) args) (RefTy _ _)
| length args == 2 = True
| otherwise = False
areUnifiable (StructTy _ _) _ = False
areUnifiable (PointerTy a) (PointerTy b) = areUnifiable a b
areUnifiable (PointerTy _) _ = False
areUnifiable (RefTy a ltA) (RefTy b ltB) = areUnifiable a b && areUnifiable ltA ltB
areUnifiable RefTy {} _ = False
areUnifiable (FuncTy argTysA retTyA ltA) (FuncTy argTysB retTyB ltB)
| length argTysA /= length argTysB = False
| otherwise =
let argBools = zipWith areUnifiable argTysA argTysB
retBool = areUnifiable retTyA retTyB
ltBool = areUnifiable ltA ltB
in all (== True) (ltBool : retBool : argBools)
areUnifiable FuncTy {} _ = False
areUnifiable CTy _ = True
areUnifiable _ CTy = True
areUnifiable a b
| a == b = True
| otherwise = False
-- Checks whether or not the kindedness of types match
-- Kinds are polymorphic constructors such as (f a)
-- Note that this disagrees with the notion of unifiablitity in areUnifiable
checkKinds :: Ty -> Ty -> Bool
-- Base < Higher
checkKinds (FuncTy argTysA retTyA _) (FuncTy argTysB retTyB _) =
let argKinds = zipWith checkKinds argTysA argTysB
retKinds = tyToKind retTyA <= tyToKind retTyB
in all (== True) (retKinds : argKinds)
checkKinds t t' = tyToKind t <= tyToKind t'
-- | Put concrete types into the places where there are type variables.
-- For example (Fn [a] b) => (Fn [Int] Bool)
-- NOTE: If a concrete type can't be found, the type variable will stay the same.
replaceTyVars :: TypeMappings -> Ty -> Ty
replaceTyVars mappings t =
case t of
(VarTy key) -> fromMaybe t (Map.lookup key mappings)
(FuncTy argTys retTy lt) -> FuncTy (map (replaceTyVars mappings) argTys) (replaceTyVars mappings retTy) (replaceTyVars mappings lt)
(StructTy name tyArgs) ->
case replaceTyVars mappings name of
-- special case, struct (f a b) mapped to (RefTy a lt)
-- We f in such a case to the full (Ref a lt) in constraints; we also still map
-- individual members a and b, as these need mappings since they may be
-- referred to in other places (e.g. (Fn [(f a b)] a)--without a mapping,
-- a would remain generic here.
(RefTy a lt) -> replaceTyVars mappings (RefTy a lt)
_ -> StructTy (replaceTyVars mappings name) (fmap (replaceTyVars mappings) tyArgs)
(PointerTy x) -> PointerTy (replaceTyVars mappings x)
(RefTy x lt) -> RefTy (replaceTyVars mappings x) (replaceTyVars mappings lt)
_ -> t
-- | The type of a type's copying function.
typesCopyFunctionType :: Ty -> Ty
typesCopyFunctionType memberType = FuncTy [RefTy memberType (VarTy "q")] memberType StaticLifetimeTy
-- | The type of a type's deleter function.
typesDeleterFunctionType :: Ty -> Ty
typesDeleterFunctionType memberType = FuncTy [memberType] UnitTy StaticLifetimeTy
-- | The type of environments sent to Lambdas (used in emitted C code)
lambdaEnvTy :: Ty
lambdaEnvTy = StructTy (ConcreteNameTy (SymPath [] "LambdaEnv")) []
createStructName :: [String] -> String -> String
createStructName path name = intercalate "." (path ++ [name])
getStructName :: Ty -> String
getStructName (StructTy (ConcreteNameTy spath) _) = show spath
getStructName (StructTy (VarTy name) _) = name
getStructName _ = ""
getPathFromStructName :: String -> [String]
getPathFromStructName structName =
let path = map unpack (splitOn (pack ".") (pack structName))
in if length path > 1 then init path else []
getNameFromStructName :: String -> String
getNameFromStructName structName = last (map unpack (splitOn (pack ".") (pack structName)))
getStructPath :: Ty -> SymPath
getStructPath (StructTy (ConcreteNameTy spath) _) = spath
getStructPath (StructTy (VarTy name) _) = (SymPath [] name)
getStructPath _ = (SymPath [] "")
-- N.B.: promoteNumber is only safe for numeric types!
promoteNumber :: Ty -> Ty -> Ty
promoteNumber a b | a == b = a
promoteNumber ByteTy other = other
promoteNumber other ByteTy = other
promoteNumber IntTy other = other
promoteNumber other IntTy = other
promoteNumber LongTy other = other
promoteNumber other LongTy = other
promoteNumber FloatTy other = other
promoteNumber other FloatTy = other
promoteNumber DoubleTy _ = DoubleTy
promoteNumber _ DoubleTy = DoubleTy
promoteNumber a b =
error ("promoteNumber called with non-numbers: " ++ show a ++ ", " ++ show b)