capnp-0.17.0.0: cmd/capnpc-haskell/Trans/ToHaskellCommon.hs
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ViewPatterns #-}
-- Things used by both RawToHaskell and PureToHaskell.
module Trans.ToHaskellCommon where
import qualified Capnp as Capnp
import qualified Capnp.Basics as B
import Capnp.Classes (encode)
import Capnp.Repr.Parsed (Parsed)
import qualified Data.ByteString.Lazy as LBS
import Data.Char (toUpper)
import Data.Maybe (fromJust)
import qualified Data.Set as S
import qualified Data.Text as T
import Data.Word
import GHC.Exts (fromList)
import qualified IR.Common as C
import IR.Haskell
import qualified IR.Name as Name
import System.FilePath (splitDirectories)
import Text.Printf (printf)
-- Misc shortcuts for common constructs:
std_ :: Name.UnQ -> Name.GlobalQ
std_ name = gName ["Std_"] (Name.mkLocal Name.emptyNS name)
eStd_ :: Name.UnQ -> Exp
eStd_ = EGName . std_
ePureUnit :: Exp
ePureUnit = EApp (eStd_ "pure") [ETup []]
tStd_ :: Name.UnQ -> Type
tStd_ = TGName . std_
gName :: [T.Text] -> Name.LocalQ -> Name.GlobalQ
gName parts local =
Name.GlobalQ
{ globalNS = Name.NS parts,
local
}
egName :: [T.Text] -> Name.LocalQ -> Exp
egName parts local = EGName $ gName parts local
euName :: Name.UnQ -> Exp
euName = ELName . Name.mkLocal Name.emptyNS
tgName :: [T.Text] -> Name.LocalQ -> Type
tgName parts local = TGName $ gName parts local
pgName :: [T.Text] -> Name.LocalQ -> [Pattern] -> Pattern
pgName parts local = PGCtor (gName parts local)
puName :: Name.UnQ -> [Pattern] -> Pattern
puName = PLCtor . Name.unQToLocal
tuName :: Name.UnQ -> Type
tuName = TLName . Name.unQToLocal
iValue :: Name.UnQ -> [Pattern] -> Exp -> InstanceDef
iValue name params value = IdValue DfValue {name, params, value}
iType :: Name.UnQ -> [Type] -> Type -> InstanceDef
iType name params value = IdType $ TypeAlias name params value
readCtx :: T.Text -> T.Text -> Type
readCtx m msg =
TApp
(tgName ["Untyped"] "ReadCtx")
[ TVar m,
TVar msg
]
rwCtx :: T.Text -> T.Text -> Type
rwCtx m s =
TApp
(tgName ["Untyped"] "RWCtx")
[ TVar m,
TVar s
]
eGetWordField :: Exp -> C.DataLoc -> Exp
eGetWordField struct C.DataLoc {dataIdx, dataOff, dataDef} =
EApp
(egName ["GenHelpers"] "getWordField")
[ struct,
EInt $ fromIntegral dataIdx,
EInt $ fromIntegral dataOff,
EInt $ fromIntegral dataDef
]
idToModule :: Word64 -> [Name.UnQ]
idToModule fileId =
["Capnp", "Gen", "ById", Name.UnQ $ T.pack $ printf "X%x" fileId]
instance_ :: [Type] -> [T.Text] -> Name.LocalQ -> [Type] -> [InstanceDef] -> Decl
instance_ ctx [] className tys defs =
DcInstance
{ ctx,
typ = TApp (TLName className) tys,
defs
}
instance_ ctx classNS className tys defs =
DcInstance
{ ctx,
typ = TApp (tgName classNS className) tys,
defs
}
-- | Transform the file path into a valid haskell module name.
-- TODO: this is a best-effort transformation; it gives good
-- results on the schema I(zenhack) have found in the wild, but
-- may fail to generate valid/non-overlapping module names in
-- all cases.
--
-- This generates the bit that is unique to the specific file
-- name and common to both raw and pure backends, so e.g. for
-- @myorg/example.capnp@ it generates @["Myorg", "Example"]@.
makeModName :: FilePath -> [Name.UnQ]
makeModName fileName =
[Name.UnQ (T.pack (mangleSegment seg)) | seg <- splitDirectories fileName]
where
mangleSegment "c++.capnp" = "Cxx"
mangleSegment "" = error "Unexpected empty file name"
mangleSegment (c : cs) = go (toUpper c : cs)
where
go ('-' : c : cs) = toUpper c : go cs
go ".capnp" = ""
go [] = ""
go (c : cs) = c : go cs
-- | Fix the capnp imports of a module, so that they correspond to the
-- imports actually used in the body of the module and/or export list.
--
-- Note that this only looks at imports of the form Capnp.Gen....; other
-- imports are not touched.
fixImports :: Module -> Module
fixImports m@Module {modImports} =
let namespaces =
S.toList $
S.fromList -- deduplicate
[ nsParts
| Name.GlobalQ
{ globalNS = Name.NS nsParts@(map T.unpack -> "Capnp" : "Gen" : _)
} <-
S.toList (findGNames m)
]
neededImports =
[ ImportQual {parts = map Name.UnQ nsParts}
| nsParts <- namespaces
]
in m {modImports = modImports ++ neededImports}
toTVars :: [Name.UnQ] -> [Type]
toTVars = map (TVar . Name.typeVarName)
makePtrBytes :: Maybe (Parsed B.AnyPointer) -> LBS.ByteString
makePtrBytes ptr =
Capnp.msgToLBS $ fromJust $ Capnp.createPure Capnp.defaultLimit $ do
msg <- Capnp.newMessage Nothing
rootPtr <-
encode msg $
B.Struct
(fromList [])
(fromList [ptr])
Capnp.setRoot rootPtr
pure msg
class HasGNames a where
-- | Collect all of the 'Name.GlobalQ's used in the module.
--
-- This seems like it would be the perfect use case for something
-- like syb or similar libraries, but I(zenhack) haven't taken the
-- time to fully wrap my head around how to use those yet, so we do
-- it the boilerplate-heavy way.
findGNames :: a -> S.Set Name.GlobalQ
instance HasGNames Module where
findGNames Module {modExports = Just exports, modDecls} =
S.unions $ map findGNames exports ++ map findGNames modDecls
findGNames Module {modExports = Nothing, modDecls} =
S.unions $ map findGNames modDecls
instance HasGNames Export where
findGNames (ExportGCtors name) = S.singleton name
findGNames (ExportGName name) = S.singleton name
findGNames _ = S.empty
instance HasGNames Decl where
findGNames (DcData d) = findGNames d
findGNames DcValue {typ, def} = findGNames typ `S.union` findGNames def
findGNames DcInstance {ctx, typ, defs} =
S.unions
[ S.unions $ map findGNames ctx,
findGNames typ,
S.unions $ map findGNames defs
]
findGNames (DcTypeInstance alias orig) = findGNames alias `S.union` findGNames orig
findGNames (DcDeriveInstance ctx typ) = findGNames (TCtx ctx typ)
findGNames DcClass {ctx, decls} =
S.unions $ map findGNames ctx ++ map findGNames decls
instance HasGNames DataDecl where
findGNames Data {typeArgs, dataVariants} =
S.unions $ map findGNames typeArgs ++ map findGNames dataVariants
instance HasGNames ClassDecl where
findGNames (CdValueDecl _ ty) = findGNames ty
findGNames (CdValueDef d) = findGNames d
findGNames (CdMinimal _) = S.empty
instance HasGNames InstanceDef where
findGNames (IdValue d) = findGNames d
findGNames (IdData d) = findGNames d
findGNames (IdType t) = findGNames t
instance HasGNames TypeAlias where
findGNames (TypeAlias _ ts t) = S.unions $ map findGNames (t : ts)
instance HasGNames ValueDef where
findGNames DfValue {value, params} =
S.unions $ findGNames value : map findGNames params
instance HasGNames DataVariant where
findGNames DataVariant {dvArgs} = findGNames dvArgs
instance HasGNames DataArgs where
findGNames (APos tys) = S.unions $ map findGNames tys
findGNames (ARec fields) = S.unions $ map (findGNames . snd) fields
instance HasGNames Type where
findGNames (TGName n) = S.singleton n
findGNames (TApp t ts) = S.unions $ map findGNames (t : ts)
findGNames (TFn ts) = S.unions $ map findGNames ts
findGNames (TCtx ts t) = S.unions $ map findGNames (t : ts)
findGNames _ = S.empty
instance HasGNames Exp where
findGNames (EApp e es) = S.unions $ map findGNames (e : es)
findGNames (EFApp e es) = S.unions $ map findGNames (e : es)
findGNames (EDo ds e) = S.unions $ findGNames e : map findGNames ds
findGNames (EBind x y) = findGNames x `S.union` findGNames y
findGNames (ETup es) = S.unions $ map findGNames es
findGNames (ECase e arms) =
S.unions
[ findGNames e,
S.unions $ map (findGNames . fst) arms,
S.unions $ map (findGNames . snd) arms
]
findGNames (ETypeAnno e t) = findGNames e `S.union` findGNames t
findGNames (ELambda ps e) = S.unions $ findGNames e : map findGNames ps
findGNames (ERecord e fields) = S.unions $ findGNames e : map (findGNames . snd) fields
findGNames _ = S.empty
instance HasGNames Do where
findGNames (DoBind _ e) = findGNames e
findGNames (DoE e) = findGNames e
instance HasGNames Pattern where
findGNames (PLCtor _ ps) = S.unions $ map findGNames ps
findGNames (PGCtor n ps) = S.unions $ S.singleton n : map findGNames ps
findGNames _ = S.empty