capnp-0.3.0.0: exe/capnpc-haskell/Backends/Pure.hs
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
-- Generate idiomatic haskell data types from the types in IR.
module Backends.Pure
( fmtModule
) where
import Data.Monoid ((<>))
import Data.String (IsString(..))
import GHC.Exts (IsList(..))
import Text.PrettyPrint.Leijen.Text (hcat, vcat)
import Text.Printf (printf)
import qualified Data.Map.Strict as M
import qualified Data.Text as T
import qualified Text.PrettyPrint.Leijen.Text as PP
import Fmt
import IR
import Util
-- | If a module reference refers to a generated module, does it
-- refer to the raw, low-level module or the *.Pure variant (which
-- this module generates)?
data ModRefType = Pure | Raw
deriving(Show, Read, Eq)
fmtName :: ModRefType -> Id -> Name -> PP.Doc
fmtName refTy thisMod Name{..} = modPrefix <> localName
where
localName = mintercalate "'" $
map PP.textStrict $ fromList $ toList nameLocalNS ++ [nameUnqualified]
modPrefix
| null nsParts = ""
| refTy == Pure && modRefToNS refTy (ByCapnpId thisMod) == ns = ""
| otherwise = fmtModRef refTy nameModule <> "."
ns@(Namespace nsParts) = modRefToNS refTy nameModule
modRefToNS :: ModRefType -> ModuleRef -> Namespace
modRefToNS _ (FullyQualified ns) = ns
modRefToNS ty (ByCapnpId id) = Namespace $ case ty of
Pure -> ["Capnp", "ById", T.pack (printf "X%x" id), "Pure"]
Raw -> ["Capnp", "ById", T.pack (printf "X%x" id)]
fmtModule :: Module -> [(FilePath, PP.Doc)]
fmtModule mod@Module{modName=Namespace modNameParts,..} =
[ ( T.unpack $ mintercalate "/" humanParts <> ".hs"
, mainContent
)
, ( printf "Capnp/ById/X%x/Pure.hs" modId
, vcat
[ "{-# OPTIONS_GHC -Wno-unused-imports #-}"
, "{-# OPTIONS_HADDOCK hide #-}"
, "{- |"
, hcat [ "Module: ", machineMod ]
, hcat [ "Description: Machine-addressable alias for '", humanMod, "'." ]
, "-}"
, hcat [ "module ", machineMod, "(module ", humanMod, ") where" ]
, ""
, hcat [ "import ", humanMod ]
]
)
]
where
machineMod = fmtModRef Pure (ByCapnpId modId)
humanMod = fmtModRef Pure $ FullyQualified $ Namespace humanParts
humanParts = "Capnp":modNameParts ++ ["Pure"]
modFileText = PP.textStrict modFile
mainContent = vcat
[ "{-# LANGUAGE DuplicateRecordFields #-}"
, "{-# LANGUAGE RecordWildCards #-}"
, "{-# LANGUAGE FlexibleInstances #-}"
, "{-# LANGUAGE FlexibleContexts #-}"
, "{-# LANGUAGE MultiParamTypeClasses #-}"
, "{-# LANGUAGE ScopedTypeVariables #-}"
, "{-# LANGUAGE TypeFamilies #-}"
, "{-# LANGUAGE DeriveGeneric #-}"
, "{-# OPTIONS_GHC -Wno-unused-imports #-}"
, "{- |"
, "Module: " <> humanMod
, "Description: " <> "High-level generated module for " <> modFileText
, ""
, "This module is the generated code for " <> modFileText <> ","
, "for the high-level api."
, "-}"
, "module " <> humanMod <> " (" <> fmtExportList mod, ") where"
, ""
, "-- Code generated by capnpc-haskell. DO NOT EDIT."
, "-- Generated from schema file: " <> modFileText
, ""
, "import Data.Int"
, "import Data.Word"
, ""
, "import Data.Default (Default(def))"
, "import GHC.Generics (Generic)"
, ""
, "import Data.Capnp.Basics.Pure (Data, Text)"
, "import Control.Monad.Catch (MonadThrow)"
, "import Data.Capnp.TraversalLimit (MonadLimit)"
, ""
, "import Control.Monad (forM_)"
, ""
, "import qualified Data.Capnp.Message as M'"
, "import qualified Data.Capnp.Untyped as U'"
, "import qualified Data.Capnp.Untyped.Pure as PU'"
, "import qualified Data.Capnp.GenHelpers.Pure as PH'"
, "import qualified Data.Capnp.Classes as C'"
, ""
, "import qualified Data.Vector as V"
, "import qualified Data.ByteString as BS"
, ""
, fmtImport Raw $ Import (ByCapnpId modId)
, vcat $ map (fmtImport Pure) modImports
, vcat $ map (fmtImport Raw) modImports
, ""
, vcat $ map (fmtDecl modId) (M.toList modDecls)
]
fmtExportList :: Module -> PP.Doc
fmtExportList Module{modId,modDecls} =
mintercalate ", " (map (fmtExport modId) (M.toList modDecls))
fmtExport :: Id -> (Name, Decl) -> PP.Doc
fmtExport thisMod (name, DeclDef DefStruct{}) =
fmtName Pure thisMod name <> "(..)"
fmtExport thisMod (name, DeclDef DefUnion{}) =
fmtName Pure thisMod name <> "(..)"
-- These are 'Raw' because we're just re-exporting them:
fmtExport thisMod (name, DeclDef DefEnum{}) =
fmtName Raw thisMod name <> "(..)"
fmtExport thisMod (name, DeclConst VoidConst) =
fmtName Raw thisMod (valueName name)
fmtExport thisMod (name, DeclConst WordConst{}) =
fmtName Raw thisMod (valueName name)
fmtExport thisMod (name, DeclConst _) =
fmtName Pure thisMod (valueName name)
fmtImport :: ModRefType -> Import -> PP.Doc
fmtImport ty (Import ref) = "import qualified " <> fmtModRef ty ref
fmtModRef :: ModRefType -> ModuleRef -> PP.Doc
fmtModRef ty ref = mintercalate "." (map PP.textStrict $ toList $ modRefToNS ty ref)
fmtType :: Id -> Type -> PP.Doc
fmtType thisMod (CompositeType (StructType name params)) =
fmtName Pure thisMod name
<> hcat [" (" <> fmtType thisMod ty <> ")" | ty <- params]
fmtType thisMod (WordType (EnumType name)) = fmtName Raw thisMod name
fmtType thisMod (PtrType (ListOf eltType)) = "PU'.ListOf (" <> fmtType thisMod eltType <> ")"
fmtType thisMod (PtrType (PtrComposite ty)) = fmtType thisMod (CompositeType ty)
fmtType _ VoidType = "()"
fmtType _ (WordType (PrimWord prim)) = fmtPrimWord prim
fmtType _ (PtrType (PrimPtr PrimText)) = "Text"
fmtType _ (PtrType (PrimPtr PrimData)) = "Data"
fmtType _ (PtrType (PrimPtr (PrimAnyPtr ty))) = "Maybe (" <> fmtAnyPtr ty <> ")"
fmtPrimWord :: PrimWord -> PP.Doc
fmtPrimWord PrimInt{isSigned=True,size} = "Int" <> fromString (show size)
fmtPrimWord PrimInt{isSigned=False,size} = "Word" <> fromString (show size)
fmtPrimWord PrimFloat32 = "Float"
fmtPrimWord PrimFloat64 = "Double"
fmtPrimWord PrimBool = "Bool"
fmtAnyPtr :: AnyPtr -> PP.Doc
fmtAnyPtr Struct = "PU'.Struct"
fmtAnyPtr List = "PU'.List"
fmtAnyPtr Cap = "PU'.Cap"
fmtAnyPtr Ptr = "PU'.PtrType"
fmtVariant :: Id -> Variant -> PP.Doc
fmtVariant thisMod Variant{variantName,variantParams} =
fmtName Pure thisMod variantName
<> case variantParams of
Unnamed VoidType _ -> ""
Unnamed ty _ -> " (" <> fmtType thisMod ty <> ")"
Record [] -> ""
Record fields -> PP.line <> indent
(PP.braces $ vcat $
PP.punctuate "," $ map (fmtField thisMod) fields)
fmtField :: Id -> Field -> PP.Doc
fmtField thisMod Field{fieldName,fieldLocType} =
PP.textStrict fieldName <> " :: " <> fmtType thisMod fieldType
where
fieldType = case fieldLocType of
VoidField -> VoidType
DataField _ ty -> WordType ty
PtrField _ ty -> PtrType ty
HereField ty -> CompositeType ty
fmtDecl :: Id -> (Name, Decl) -> PP.Doc
fmtDecl thisMod (name, DeclDef d) = fmtDataDef thisMod name d
fmtDecl thisMod (name, DeclConst c) = fmtConst thisMod name c
-- | Format a constant declaration.
fmtConst :: Id -> Name -> Const -> PP.Doc
fmtConst thisMod name value =
let pureName = fmtName Pure thisMod (valueName name)
rawName = fmtName Raw thisMod (valueName name)
in case value of
-- For word types, We just re-export the definitions from the
-- low-level module, so we don't need to declare anything here:
VoidConst -> ""
WordConst{} -> ""
PtrConst{ptrType} -> vcat
[ hcat [ pureName, " :: ", fmtType thisMod (PtrType ptrType) ]
, hcat [ pureName, " = PH'.toPurePtrConst ", rawName ]
]
fmtDataDef :: Id -> Name -> DataDef -> PP.Doc
fmtDataDef thisMod dataName DefEnum{} =
-- We end up re-exporting these, but doing nothing else.
""
fmtDataDef thisMod dataName dataDef =
let rawName = fmtName Raw thisMod dataName
pureName = fmtName Pure thisMod dataName
unknownName = subName dataName "unknown'"
in vcat
[ case dataDef of
DefEnum{} ->
-- TODO: refactor so we don't need this case.
error "BUG: this should have been ruled out above."
DefStruct StructDef{fields,info} ->
let dataVariant =
-- TODO: some of the functions we use still expect structs
-- to just be single-variant unions; this is a stand-in,
-- but we should update those at some point.
Variant
{ variantName = dataName
, variantParams = Record fields
, variantTag = 0 -- doesn't matter; just formatting
-- the data constructor, so this
-- isn't used.
}
in vcat
[ data_ (fmtName Pure thisMod dataName)
[ fmtVariant thisMod dataVariant ]
["Show", "Read", "Eq", "Generic"]
, instance_ [] ("C'.Decerialize " <> pureName)
[ hcat [ "type Cerial msg ", pureName, " = ", rawName, " msg" ]
, "decerialize raw = do"
, indent $ fmtDecerializeArgs dataName fields
]
, instance_ [] ("C'.Marshal " <> pureName)
[ "marshalInto raw value = do"
, indent $ vcat
[ "case value of\n"
, indent $ vcat
[ fmtCerializeVariant False dataVariant ]
]
]
, case info of
IsStandalone{} ->
instance_ [] ("C'.Cerialize s " <> pureName)
[]
_ ->
""
]
DefUnion{dataVariants,parentStructName,parentStruct=StructDef{info}} ->
let unknownVariantName = subName parentStructName "unknown'"
in vcat
[ data_
(fmtName Pure thisMod dataName)
(map (fmtVariant thisMod) dataVariants ++
[ fmtName Pure thisMod unknownVariantName <> " Word16" ]
)
["Show", "Read", "Eq", "Generic"]
, instance_ [] ("C'.Decerialize " <> pureName)
[ hcat [ "type Cerial msg ", pureName, " = ", rawName, " msg" ]
, "decerialize raw = do"
, indent $ vcat
[ hcat [ "raw <- ", fmtName Raw thisMod $ prefixName "get_" (subName dataName ""), " raw" ]
, "case raw of"
, indent $ vcat
[ vcat (map fmtDecerializeVariant dataVariants)
, hcat
[ fmtName Raw thisMod unknownName, " val -> pure $ "
, fmtName Pure thisMod unknownVariantName, " val"
]
]
]
]
, instance_ [] ("C'.Marshal " <> pureName)
[ "marshalInto raw value = do"
, indent $ vcat
[ "case value of\n"
, indent $ vcat
[ vcat $ map (fmtCerializeVariant True) dataVariants
, hcat
[ fmtName Pure thisMod unknownVariantName, " arg_ -> "
, fmtName Raw thisMod $ prefixName "set_" unknownName, " raw arg_"
]
]
]
]
, case info of
IsStandalone{} ->
instance_ [] ("C'.Cerialize s " <> pureName)
[]
_ ->
""
]
, instance_ [] ("C'.FromStruct M'.ConstMsg " <> pureName)
[ "fromStruct struct = do"
, indent $ vcat
[ "raw <- C'.fromStruct struct"
, hcat [ "C'.decerialize (raw :: ", rawName, " M'.ConstMsg)" ]
]
]
, instance_ [] ("Default " <> pureName)
[ "def = PH'.defaultStruct"
]
]
where
fmtDecerializeArgs variantName fields = vcat
[ hcat [ fmtName Pure thisMod variantName, " <$>" ]
, indent $ vcat $ PP.punctuate " <*>" $
flip map fields $ \Field{fieldName,fieldLocType} -> hcat
[ "(", fmtName Raw thisMod $ prefixName "get_" (subName variantName fieldName)
, " raw", case fieldLocType of
-- Data and void fields are always the same type in Raw and Pure forms,
-- so we don't need to convert them.
DataField _ _ -> ""
VoidField -> ""
_ -> " >>= C'.decerialize"
, ")"
]
]
fmtDecerializeVariant Variant{variantName,variantParams} =
fmtName Raw thisMod variantName <>
case variantParams of
Unnamed VoidType _ -> " -> pure " <> fmtName Pure thisMod variantName
Record fields ->
" raw -> " <> fmtDecerializeArgs variantName fields
Unnamed (WordType _) _ -> hcat
[ " val -> pure (", fmtName Pure thisMod variantName, " val)" ]
_ -> hcat
[ " val -> ", fmtName Pure thisMod variantName, " <$> C'.decerialize val" ]
fmtCerializeVariant isUnion Variant{variantName, variantParams} =
fmtName Pure thisMod variantName <>
let accessorName prefix = fmtName Raw thisMod (prefixName prefix variantName)
setterName = accessorName "set_"
in case variantParams of
Unnamed VoidType VoidField ->
hcat [ " -> ", setterName, " raw" ]
Unnamed _ (DataField _ _) ->
hcat [ " arg_ -> ", setterName, " raw arg_"]
Unnamed (WordType _) VoidField ->
-- TODO: this is the unknown variant. We should find a better
-- way to represent this; the structure of the IR here is sloppy
-- work.
hcat [ " arg_ -> ", setterName, " raw arg_" ]
Unnamed _ fieldLocType -> vcat
[ " arg_ -> do"
, indent (fmtUseAccessors accessorName "arg_" fieldLocType)
]
Record fields -> vcat
[ "{..} -> do"
, indent $ vcat
[ if isUnion
then "raw <- " <> setterName <> " raw"
else ""
, vcat (map (fmtCerializeField variantName) fields)
]
]
fmtCerializeField variantName Field{fieldName,fieldLocType} =
let accessorName prefix = fmtName Raw thisMod $ prefixName prefix (subName variantName fieldName)
in fmtUseAccessors accessorName fieldName fieldLocType
fmtUseAccessors accessorName fieldVarName fieldLocType =
let setterName = accessorName "set_"
getterName = accessorName "get_"
newName = accessorName "new_"
fieldNameText = PP.textStrict fieldVarName
in case fieldLocType of
DataField _ _ -> hcat [ setterName, " raw ", fieldNameText ]
VoidField -> hcat [ setterName, " raw" ]
HereField _ -> vcat
[ hcat [ "field_ <- ", getterName, " raw" ]
, hcat [ "C'.marshalInto field_ ", fieldNameText ]
]
PtrField _ ty -> case ty of
PrimPtr PrimData -> vcat
[ hcat [ "field_ <- ", newName, " (BS.length ", fieldNameText, ") raw" ]
, hcat [ "C'.marshalInto field_ ", fieldNameText ]
]
PrimPtr PrimText -> vcat
[ hcat [ "field_ <- C'.cerialize (U'.message raw) ", fieldNameText ]
, hcat [ setterName, " raw field_" ]
]
PrimPtr (PrimAnyPtr _) -> vcat
[ hcat [ "field_ <- C'.cerialize (U'.message raw) ", fieldNameText ]
, hcat [ setterName, " raw field_" ]
]
ListOf eltType -> vcat
[ hcat [ "let len_ = V.length ", fieldNameText ]
, hcat [ "field_ <- ", newName, " len_ raw" ]
, case eltType of
VoidType ->
""
CompositeType (StructType _ _) -> vcat
[ "forM_ [0..len_ - 1] $ \\i -> do"
, indent $ vcat
[ "elt <- C'.index i field_"
, hcat [ "C'.marshalInto elt (", fieldNameText, " V.! i)" ]
]
]
WordType _ -> vcat
[ "forM_ [0..len_ - 1] $ \\i -> do"
, indent $ vcat
[ hcat [ "C'.setIndex (", fieldNameText, " V.! i) i field_" ] ]
]
PtrType _ ->
"pure ()" -- TODO
]
PtrComposite _ -> vcat
[ hcat [ "field_ <- ", newName, " raw" ]
, hcat [ "C'.marshalInto field_ ", fieldNameText ]
]