mirror of
https://github.com/zap-proto/haskell.git
synced 2026-10-30 15:05:00 +00:00
914 lines
35 KiB
Haskell
914 lines
35 KiB
Haskell
{-# LANGUAGE DuplicateRecordFields #-}
|
|
{-# LANGUAGE LambdaCase #-}
|
|
{-# LANGUAGE NamedFieldPuns #-}
|
|
{-# LANGUAGE OverloadedStrings #-}
|
|
|
|
module Trans.PureToHaskell (fileToModules) where
|
|
|
|
import Control.Monad (guard)
|
|
import qualified Data.Text as T
|
|
import Data.Word
|
|
import qualified IR.Common as C
|
|
import IR.Haskell
|
|
import qualified IR.Name as Name
|
|
import qualified IR.Pure as P
|
|
import Text.Printf (printf)
|
|
import Trans.ToHaskellCommon
|
|
|
|
-- | Modules imported by all generated modules.
|
|
commonImports :: [Import]
|
|
commonImports =
|
|
[ ImportAs {importAs = "V", parts = reExp ["Data", "Vector"]},
|
|
ImportAs {importAs = "T", parts = reExp ["Data", "Text"]},
|
|
ImportAs {importAs = "BS", parts = reExp ["Data", "ByteString"]},
|
|
ImportAs {importAs = "Default", parts = reExp ["Data", "Default"]},
|
|
ImportAs {importAs = "Generics", parts = ["GHC", "Generics"]},
|
|
ImportAs {importAs = "MonadIO", parts = ["Control", "Monad", "IO", "Class"]},
|
|
ImportAs {importAs = "UntypedPure", parts = ["Zap", "Untyped", "Pure"]},
|
|
ImportAs {importAs = "Untyped", parts = ["Zap", "Untyped"]},
|
|
ImportAs {importAs = "Message", parts = ["Zap", "Message"]},
|
|
ImportAs {importAs = "Classes", parts = ["Zap", "Classes"]},
|
|
ImportAs {importAs = "BasicsPure", parts = ["Zap", "Basics", "Pure"]},
|
|
ImportAs {importAs = "GenHelpersPure", parts = ["Zap", "GenHelpers", "Pure"]}
|
|
]
|
|
|
|
-- | Modules imported by generated modules that use rpc.
|
|
rpcImports :: [Import]
|
|
rpcImports =
|
|
[ ImportAs {importAs = "Rpc", parts = ["Zap", "Rpc", "Untyped"]},
|
|
ImportAs {importAs = "Server", parts = ["Zap", "Rpc", "Server"]},
|
|
ImportAs {importAs = "RpcHelpers", parts = ["Zap", "GenHelpers", "Rpc"]},
|
|
ImportAs {importAs = "STM", parts = reExp ["Control", "Concurrent", "STM"]},
|
|
ImportAs {importAs = "STM", parts = reExp ["Control", "Monad", "STM", "Class"]},
|
|
ImportAs {importAs = "Supervisors", parts = reExp ["Supervisors"]}
|
|
]
|
|
|
|
reExp :: [Name.UnQ] -> [Name.UnQ]
|
|
reExp parts = ["Zap", "GenHelpers", "ReExports"] ++ parts
|
|
|
|
-- | Whether the serialized and unserialized forms of this type
|
|
-- are the same. If not, there is a marshalling step, if so, the
|
|
-- accessors work with the decerialized form directly.
|
|
--
|
|
-- For example, this is True for Enums, basic integers, etc. but
|
|
-- False for structs, interfaces, etc.
|
|
cerialEq :: C.Type b r -> Bool
|
|
cerialEq C.VoidType = True
|
|
cerialEq (C.WordType _) = True
|
|
cerialEq (C.CompositeType _) = False
|
|
cerialEq (C.PtrType _) = False
|
|
|
|
fileToModules :: P.File -> [Module]
|
|
fileToModules file =
|
|
[ fileToMainModule file,
|
|
fileToModuleAlias file
|
|
]
|
|
|
|
fileToModuleAlias :: P.File -> Module
|
|
fileToModuleAlias P.File {fileName, fileId} =
|
|
let reExport = ["Zap", "Gen"] ++ makeModName fileName ++ ["Pure"]
|
|
in Module
|
|
{ modName = idToModule fileId ++ ["Pure"],
|
|
modLangPragmas = [],
|
|
modExports = Just [ExportMod reExport],
|
|
modImports = [ImportAll {parts = reExport}],
|
|
modDecls = []
|
|
}
|
|
|
|
fileToMainModule :: P.File -> Module
|
|
fileToMainModule P.File {fileName, fileId, decls, reExportEnums, usesRpc} =
|
|
fixImports $
|
|
Module
|
|
{ modName = ["Zap", "Gen"] ++ makeModName fileName ++ ["Pure"],
|
|
modLangPragmas =
|
|
[ "DataKinds",
|
|
"DeriveGeneric",
|
|
"DuplicateRecordFields",
|
|
"FlexibleContexts",
|
|
"FlexibleInstances",
|
|
"FunctionalDependencies",
|
|
"RecordWildCards",
|
|
"MultiParamTypeClasses",
|
|
"TypeFamilies",
|
|
"UndecidableInstances"
|
|
],
|
|
modExports =
|
|
Just $
|
|
[ExportGCtors (gName (rawModule fileId) name) | name <- reExportEnums]
|
|
++ concatMap (declToExport fileId) decls,
|
|
modImports =
|
|
commonImports ++ (guard usesRpc >> rpcImports),
|
|
modDecls =
|
|
concatMap (declToDecls fileId) decls
|
|
++ concatMap (enumInstances fileId) reExportEnums
|
|
}
|
|
|
|
-- | Convert a declaration into the list of related exports we need.
|
|
-- The first argument is the id for this module.
|
|
declToExport :: Word64 -> P.Decl -> [Export]
|
|
declToExport fileId = \case
|
|
P.DataDecl P.Data {typeName} ->
|
|
[ExportLCtors typeName]
|
|
P.ConstDecl P.Constant {name, value = C.WordValue _ _} ->
|
|
[ExportGName $ gName (rawModule fileId) name]
|
|
P.ConstDecl P.Constant {name, value = C.VoidValue} ->
|
|
[ExportGName $ gName (rawModule fileId) name]
|
|
P.ConstDecl P.Constant {name, value = C.PtrValue _ _} ->
|
|
[ExportLName name]
|
|
P.IFaceDecl P.IFace {name = Name.ZapQ {local}} ->
|
|
[ ExportLCtors local,
|
|
ExportLCtors (Name.mkSub local "server_"),
|
|
ExportLName (Name.unQToLocal $ Name.UnQ $ "export_" <> Name.renderLocalQ local)
|
|
]
|
|
|
|
-- | enumInstances' generates some type class instances an enum data type.
|
|
enumInstances :: Word64 -> Name.LocalQ -> [Decl]
|
|
enumInstances thisMod name =
|
|
let rawName = gName (rawModule thisMod) name
|
|
in instance_
|
|
[]
|
|
["Classes"]
|
|
"Decerialize"
|
|
[TGName rawName]
|
|
[ iType "Cerial" [tuName "msg", TGName rawName] (TGName rawName),
|
|
iValue "decerialize" [] (eStd_ "pure")
|
|
]
|
|
: instance_
|
|
[]
|
|
["Classes"]
|
|
"Cerialize"
|
|
[tuName "s", TGName rawName]
|
|
[ iValue "cerialize" [PVar "_"] (eStd_ "pure")
|
|
]
|
|
: [ instance_
|
|
[]
|
|
["Classes"]
|
|
"Cerialize"
|
|
[tuName "s", t]
|
|
[ iValue "cerialize" [] (egName ["Classes"] "cerializeBasicVec")
|
|
]
|
|
| t <-
|
|
take 6 $
|
|
drop 1 $
|
|
iterate
|
|
(\t -> TApp (tgName ["V"] "Vector") [t])
|
|
(TGName rawName)
|
|
]
|
|
|
|
-- | Convert an 'IR.Pure.Decl' into a series of Haskell declarations.
|
|
declToDecls :: Word64 -> P.Decl -> [Decl]
|
|
declToDecls thisMod (P.DataDecl data_) = dataToDecls thisMod data_
|
|
declToDecls thisMod (P.ConstDecl constant) = constToDecls thisMod constant
|
|
declToDecls thisMod (P.IFaceDecl iface) = ifaceToDecls thisMod iface
|
|
|
|
dataToDecls :: Word64 -> P.Data -> [Decl]
|
|
dataToDecls thisMod data_@P.Data {firstClass} =
|
|
concat $
|
|
[ dataToDataDecl thisMod data_,
|
|
dataToSimpleInstances thisMod data_,
|
|
dataToDecerialize thisMod data_,
|
|
dataToMarshal thisMod data_
|
|
]
|
|
++ [firstClassInstances thisMod data_ | firstClass]
|
|
|
|
-- | Get the name of a data constructor for a product type. Arguments are the
|
|
-- name of the type constructor and whether or not the type is "first class".
|
|
productVariantName :: Name.LocalQ -> Bool -> Name.LocalQ
|
|
productVariantName typeName firstClass
|
|
| firstClass = typeName
|
|
| otherwise = Name.mkSub typeName ""
|
|
|
|
-- \^ This is a group. If it's part of a union, then the union
|
|
-- will have a data constructor that is the same as typeName,
|
|
-- so we appent a ' to avoid a colision.
|
|
|
|
dataToDataDecl :: Word64 -> P.Data -> [Decl]
|
|
dataToDataDecl
|
|
thisMod
|
|
P.Data
|
|
{ typeName,
|
|
typeParams,
|
|
cerialName,
|
|
firstClass,
|
|
def
|
|
} =
|
|
let unknownCtor = Name.mkSub cerialName "unknown'"
|
|
in [ DcData
|
|
Data
|
|
{ dataName = Name.localToUnQ typeName,
|
|
typeArgs = map (TVar . Name.typeVarName) typeParams,
|
|
derives =
|
|
["Std_.Show", "Std_.Eq", "Generics.Generic"],
|
|
dataNewtype = False,
|
|
dataInstance = False,
|
|
dataVariants =
|
|
case def of
|
|
P.Product fields ->
|
|
[ DataVariant
|
|
{ dvCtorName = Name.localToUnQ $ productVariantName typeName firstClass,
|
|
dvArgs = ARec (map (fieldToField thisMod) fields)
|
|
}
|
|
]
|
|
P.Sum variants ->
|
|
[ DataVariant
|
|
{ dvCtorName = Name.localToUnQ name,
|
|
dvArgs = case arg of
|
|
Nothing -> APos []
|
|
Just ty -> APos [typeToType thisMod ty]
|
|
}
|
|
| P.Variant {name, arg} <- variants
|
|
]
|
|
++ [ DataVariant
|
|
{ dvCtorName = Name.localToUnQ unknownCtor,
|
|
dvArgs = APos [TPrim $ C.PrimInt $ C.IntType C.Unsigned C.Sz16]
|
|
}
|
|
]
|
|
}
|
|
]
|
|
|
|
typeParamsDecerializeCtx :: [Name.UnQ] -> [Type]
|
|
typeParamsDecerializeCtx typeParams =
|
|
let vars = typeParamVars typeParams
|
|
tConst = tgName ["Message"] "Const"
|
|
cerial msg v = TApp (tgName ["Classes"] "Cerial") [msg, v]
|
|
in concat
|
|
[ [ TApp (tgName ["Classes"] "Decerialize") [v],
|
|
TApp
|
|
(tgName ["Classes"] "FromPtr")
|
|
[ tConst,
|
|
cerial tConst v
|
|
]
|
|
]
|
|
| v <- vars
|
|
]
|
|
|
|
typeParamsCerializeCtx :: [Name.UnQ] -> [Type]
|
|
typeParamsCerializeCtx typeParams =
|
|
typeParamsDecerializeCtx typeParams
|
|
++ concat
|
|
[ [ TApp (tgName ["Classes"] "Cerialize") [tuName "s", v],
|
|
TApp
|
|
(tgName ["Classes"] "ToPtr")
|
|
[ tuName "s",
|
|
TApp
|
|
(tgName ["Classes"] "Cerial")
|
|
[ TApp (tgName ["Message"] "Mut") [tuName "s"],
|
|
v
|
|
]
|
|
]
|
|
]
|
|
| v <- typeParamVars typeParams
|
|
]
|
|
|
|
dataToSimpleInstances :: Word64 -> P.Data -> [Decl]
|
|
dataToSimpleInstances _thisMod P.Data {typeName, typeParams} =
|
|
let typ = typeWithParams typeName typeParams
|
|
ctx = typeParamsDecerializeCtx typeParams
|
|
in [ instance_
|
|
ctx
|
|
["Default"]
|
|
"Default"
|
|
[typ]
|
|
[ iValue "def" [] (egName ["GenHelpersPure"] "defaultStruct")
|
|
],
|
|
instance_
|
|
ctx
|
|
["Classes"]
|
|
"FromStruct"
|
|
[tgName ["Message"] "Const", typ]
|
|
[ iValue "fromStruct" [PVar "struct"] $
|
|
EBind
|
|
(EApp (egName ["Classes"] "fromStruct") [euName "struct"])
|
|
(egName ["Classes"] "decerialize")
|
|
]
|
|
]
|
|
|
|
dataToDecerialize :: Word64 -> P.Data -> [Decl]
|
|
dataToDecerialize
|
|
thisMod
|
|
P.Data
|
|
{ typeName,
|
|
typeParams,
|
|
cerialName,
|
|
firstClass,
|
|
def
|
|
} =
|
|
let typ = typeWithParams typeName typeParams
|
|
ctx = typeParamsDecerializeCtx typeParams
|
|
in [ instance_
|
|
ctx
|
|
["Classes"]
|
|
"Decerialize"
|
|
[typ]
|
|
[ iType "Cerial" [tuName "msg", typ] $
|
|
TApp (tgName (rawModule thisMod) cerialName) $
|
|
[ TApp (tgName ["Classes"] "Cerial") [tuName "msg", v]
|
|
| v <- typeParamVars typeParams
|
|
]
|
|
++ [tuName "msg"],
|
|
iValue "decerialize" [PVar "raw"] $
|
|
case def of
|
|
P.Sum variants ->
|
|
variantsToDecerialize thisMod cerialName variants
|
|
P.Product fields ->
|
|
fieldsToDecerialize thisMod typeName firstClass fields
|
|
]
|
|
]
|
|
|
|
variantsToDecerialize :: Word64 -> Name.LocalQ -> [P.Variant] -> Exp
|
|
variantsToDecerialize thisMod cerialName variants =
|
|
let unknownCtor = Name.mkSub cerialName "unknown'"
|
|
fieldGetter parentName name =
|
|
egName
|
|
(rawModule thisMod)
|
|
(Name.unQToLocal $ Name.getterName $ Name.mkSub parentName name)
|
|
in EDo
|
|
[ DoBind "raw" $ EApp (fieldGetter cerialName "") [euName "raw"]
|
|
]
|
|
( ECase (ELName "raw") $
|
|
[ case arg of
|
|
Nothing ->
|
|
( pgName (rawModule thisMod) name [],
|
|
EApp (eStd_ "pure") [ELName name]
|
|
)
|
|
Just type_
|
|
| cerialEq type_ ->
|
|
( pgName (rawModule thisMod) name [PVar "raw"],
|
|
EApp (eStd_ "pure") [EApp (ELName name) [euName "raw"]]
|
|
)
|
|
Just _ ->
|
|
( pgName (rawModule thisMod) name [PVar "raw"],
|
|
EFApp
|
|
(ELName name)
|
|
[EApp (egName ["Classes"] "decerialize") [euName "raw"]]
|
|
)
|
|
| P.Variant {name, arg} <- variants
|
|
]
|
|
++ [ ( pgName (rawModule thisMod) unknownCtor [PVar "tag"],
|
|
EApp
|
|
(eStd_ "pure")
|
|
[EApp (ELName unknownCtor) [euName "tag"]]
|
|
)
|
|
]
|
|
)
|
|
|
|
fieldsToDecerialize :: Word64 -> Name.LocalQ -> Bool -> [P.Field] -> Exp
|
|
fieldsToDecerialize thisMod typeName firstClass fields =
|
|
let fieldGetter parentName name =
|
|
egName
|
|
(rawModule thisMod)
|
|
(Name.unQToLocal $ Name.getterName $ Name.mkSub parentName name)
|
|
variantName = productVariantName typeName firstClass
|
|
in case fields of
|
|
[] ->
|
|
EApp (eStd_ "pure") [ELName variantName]
|
|
_ ->
|
|
EFApp
|
|
(ELName variantName)
|
|
[ let getter = EApp (fieldGetter typeName name) [euName "raw"]
|
|
in if name == "union'"
|
|
then -- unions decerialize from the same type as their parents. Don't
|
|
-- do anything but pass it off.
|
|
EApp (egName ["Classes"] "decerialize") [euName "raw"]
|
|
else
|
|
if cerialEq type_
|
|
then getter
|
|
else EBind getter (egName ["Classes"] "decerialize")
|
|
| P.Field {name, type_} <- fields
|
|
]
|
|
|
|
dataToMarshal :: Word64 -> P.Data -> [Decl]
|
|
dataToMarshal
|
|
thisMod
|
|
P.Data
|
|
{ typeName,
|
|
typeParams,
|
|
cerialName,
|
|
firstClass,
|
|
def
|
|
} =
|
|
let typ = typeWithParams typeName typeParams
|
|
in [ instance_
|
|
(typeParamsCerializeCtx typeParams)
|
|
["Classes"]
|
|
"Marshal"
|
|
[TVar "s", typ]
|
|
[ iValue "marshalInto" [PVar "raw_", PVar "value_"] $
|
|
case def of
|
|
P.Sum variants ->
|
|
variantsToMarshal thisMod cerialName variants
|
|
P.Product fields ->
|
|
fieldsToMarshal thisMod typeName firstClass fields
|
|
]
|
|
]
|
|
|
|
variantsToMarshal :: Word64 -> Name.LocalQ -> [P.Variant] -> Exp
|
|
variantsToMarshal thisMod cerialName variants =
|
|
let unknownCtor = Name.mkSub cerialName "unknown'"
|
|
in ECase (euName "value_") $
|
|
[ let setter = Name.unQToLocal $ Name.setterName variantName
|
|
setExp = EApp (egName (rawModule thisMod) setter) [euName "raw_"]
|
|
in case arg of
|
|
Nothing ->
|
|
( PLCtor variantName [],
|
|
setExp
|
|
)
|
|
Just type_ ->
|
|
( PLCtor variantName [PVar "arg_"],
|
|
marshalField
|
|
MarshalField
|
|
{ thisMod,
|
|
into = "raw_",
|
|
localQField = variantName,
|
|
from = "arg_",
|
|
type_,
|
|
inUnion = True
|
|
}
|
|
)
|
|
| P.Variant {name = variantName, arg} <- variants
|
|
]
|
|
++ let setter = Name.unQToLocal $ Name.setterName unknownCtor
|
|
in [ ( PLCtor unknownCtor [PVar "tag"],
|
|
EApp (egName (rawModule thisMod) setter) [euName "raw_", euName "tag"]
|
|
)
|
|
]
|
|
|
|
fieldsToMarshal :: Word64 -> Name.LocalQ -> Bool -> [P.Field] -> Exp
|
|
fieldsToMarshal thisMod typeName firstClass fields =
|
|
let variantName = productVariantName typeName firstClass
|
|
in ECase
|
|
(euName "value_")
|
|
[ ( case fields of
|
|
-- If there are no fields, use of RecordWildCards will
|
|
-- cause an error:
|
|
[] -> PLCtor variantName []
|
|
_ -> PLRecordWildCard variantName,
|
|
EDo
|
|
[ DoE $
|
|
marshalField
|
|
MarshalField
|
|
{ thisMod,
|
|
into = "raw_",
|
|
localQField = Name.mkSub typeName fieldName,
|
|
from = fieldName,
|
|
type_,
|
|
inUnion = False
|
|
}
|
|
| P.Field {name = fieldName, type_} <- fields
|
|
]
|
|
ePureUnit
|
|
)
|
|
]
|
|
|
|
firstClassInstances :: Word64 -> P.Data -> [Decl]
|
|
firstClassInstances _thisMod P.Data {typeName, typeParams} =
|
|
let typ = typeWithParams typeName typeParams
|
|
ctx = typeParamsCerializeCtx typeParams
|
|
in [ instance_ ctx ["Classes"] "Cerialize" [tuName "s", typ] [],
|
|
instance_
|
|
ctx
|
|
["Classes"]
|
|
"Cerialize"
|
|
[tuName "s", TApp (tgName ["V"] "Vector") [typ]]
|
|
[ iValue "cerialize" [] (egName ["GenHelpersPure"] "cerializeCompositeVec")
|
|
]
|
|
]
|
|
++
|
|
-- Generate instances of Cerialize s (Vector (Vector ... t)) up to some reasonable
|
|
-- nesting level. I(zenhack) can't figure out how to get a general case
|
|
-- Cerialize s (Vector a) => Cerialize s (Vector (Vector a)) to type check, so this
|
|
-- will have to do for now.
|
|
[ instance_
|
|
ctx
|
|
["Classes"]
|
|
"Cerialize"
|
|
[tuName "s", t]
|
|
[ iValue "cerialize" [] (egName ["GenHelpersPure"] "cerializeBasicVec")
|
|
]
|
|
| t <-
|
|
take 6 $
|
|
drop 2 $
|
|
iterate
|
|
(\t -> TApp (tgName ["V"] "Vector") [t])
|
|
typ
|
|
]
|
|
|
|
ifaceToDecls :: Word64 -> P.Interface -> [Decl]
|
|
ifaceToDecls thisMod iface =
|
|
[ ifaceClientDecl thisMod iface,
|
|
ifaceClassDecl thisMod iface,
|
|
ifaceExportFn thisMod iface
|
|
]
|
|
++ ifaceInstances thisMod iface
|
|
|
|
-- | Declare the newtype wrapper for clients of this interface.
|
|
ifaceClientDecl :: Word64 -> P.Interface -> Decl
|
|
ifaceClientDecl _thisMod P.IFace {name = Name.ZapQ {local = name}, typeParams} =
|
|
DcData
|
|
Data
|
|
{ dataName = Name.localToUnQ name,
|
|
dataNewtype = True,
|
|
dataInstance = False,
|
|
dataVariants =
|
|
[ DataVariant
|
|
{ dvCtorName = Name.localToUnQ name,
|
|
dvArgs = APos [tgName ["Message"] "Client"]
|
|
}
|
|
],
|
|
typeArgs = map (TVar . Name.typeVarName . C.paramName) typeParams,
|
|
derives =
|
|
[ "Std_.Show",
|
|
"Std_.Eq",
|
|
"Generics.Generic"
|
|
]
|
|
}
|
|
|
|
-- | define the *'server_ class for the interface.
|
|
ifaceClassDecl :: Word64 -> P.Interface -> Decl
|
|
ifaceClassDecl thisMod P.IFace {name = Name.ZapQ {local = name}, methods, supers, typeParams} =
|
|
let typeParamNames = map (Name.typeVarName . C.paramName) typeParams
|
|
in DcClass
|
|
{ ctx =
|
|
let superConstraints =
|
|
-- Add class constraints for superclasses:
|
|
[ let superClass = pureTName thisMod fileId (Name.mkSub local "server_")
|
|
in TApp superClass $
|
|
[tuName "m", tuName "cap"]
|
|
++ map (typeToType thisMod . C.PtrType) types
|
|
| (P.IFace {name = Name.ZapQ {local, fileId}}, C.ListBrand types) <- supers
|
|
]
|
|
in TApp (tgName ["MonadIO"] "MonadIO") [tuName "m"]
|
|
: case superConstraints of
|
|
-- If this interface doesn't have any superclasses, then we need to
|
|
-- specify a constraint for the 'Server' class. Otherwise, it's
|
|
-- implied by the supers.
|
|
[] -> [TApp (tgName ["Server"] "Server") [tuName "m", tuName "cap"]]
|
|
_ -> superConstraints,
|
|
name = Name.mkSub name "server_",
|
|
params = map Name.UnQ $ ["m", "cap"] ++ typeParamNames,
|
|
funDeps = [("cap", v) | v <- typeParamNames],
|
|
decls =
|
|
let mkName = mkMethodName name
|
|
in CdMinimal [mkName name | P.Method {name} <- methods]
|
|
: concat
|
|
[ [ CdValueDecl
|
|
(mkName methodName)
|
|
( TFn
|
|
[ tuName "cap",
|
|
TApp
|
|
(tgName ["Server"] "MethodHandler")
|
|
[ tuName "m",
|
|
typeToType thisMod $ C.CompositeType paramType,
|
|
typeToType thisMod $ C.CompositeType resultType
|
|
]
|
|
]
|
|
),
|
|
CdValueDef
|
|
DfValue
|
|
{ name = mkName methodName,
|
|
params = [PVar "_"],
|
|
value = egName ["Server"] "methodUnimplemented"
|
|
}
|
|
]
|
|
| P.Method
|
|
{ name = methodName,
|
|
paramType,
|
|
resultType
|
|
} <-
|
|
methods
|
|
]
|
|
}
|
|
|
|
-- | Define the export_Foo function for the interface.
|
|
ifaceExportFn :: Word64 -> P.Interface -> Decl
|
|
ifaceExportFn thisMod iface@P.IFace {name = Name.ZapQ {local}, typeParams, ancestors} =
|
|
let typeParamVars = map (TVar . Name.typeVarName . C.paramName) typeParams
|
|
in DcValue
|
|
{ typ =
|
|
TCtx
|
|
[ TApp (tgName ["STM"] "MonadSTM") [tuName "m"],
|
|
TApp
|
|
(TLName (Name.mkSub local "server_"))
|
|
$ [tStd_ "IO", tuName "cap"] ++ typeParamVars
|
|
]
|
|
( TFn
|
|
[ tgName ["Supervisors"] "Supervisor",
|
|
tuName "cap",
|
|
TApp (tuName "m") [TApp (TLName local) typeParamVars]
|
|
]
|
|
),
|
|
def =
|
|
DfValue
|
|
{ name = Name.UnQ $ "export_" <> Name.renderLocalQ local,
|
|
params = [PVar "sup_", PVar "server_"],
|
|
value =
|
|
EApp (egName ["STM"] "liftSTM") $
|
|
pure $
|
|
EFApp
|
|
(ELName local)
|
|
[ EApp
|
|
(egName ["Rpc"] "export")
|
|
[ euName "sup_",
|
|
ERecord
|
|
(egName ["Server"] "ServerOps")
|
|
[ ( "handleCast",
|
|
EApp (egName ["Server"] "unwrap") [ELName "server_"]
|
|
),
|
|
( "handleStop",
|
|
EApp (egName ["Server"] "shutdown") [ELName "server_"]
|
|
),
|
|
( "handleCall",
|
|
ELambda [PVar "interfaceId_", PVar "methodId_"] $
|
|
ECase (euName "interfaceId_") $
|
|
[ ( PInt (fromIntegral interfaceId),
|
|
ECase (euName "methodId_") $
|
|
[ ( PInt i,
|
|
EApp
|
|
(egName ["Server"] "toUntypedHandler")
|
|
[ let methodName = mkMethodName ifaceName mname
|
|
in EApp
|
|
( if thisMod == ifaceMod
|
|
then euName methodName
|
|
else egName (pureModule ifaceMod) (Name.unQToLocal methodName)
|
|
)
|
|
[euName "server_"]
|
|
]
|
|
)
|
|
| (i, P.Method {name = mname}) <- zip [0 ..] methods
|
|
]
|
|
++ [ ( PVar "_",
|
|
egName ["Server"] "methodUnimplemented"
|
|
)
|
|
]
|
|
)
|
|
| P.IFace
|
|
{ name = Name.ZapQ {local = ifaceName, fileId = ifaceMod},
|
|
interfaceId,
|
|
methods
|
|
} <-
|
|
iface : map fst ancestors
|
|
]
|
|
++ [ ( PVar "_",
|
|
egName ["Server"] "methodUnimplemented"
|
|
)
|
|
]
|
|
)
|
|
]
|
|
]
|
|
]
|
|
}
|
|
}
|
|
|
|
-- | Declare instances for clients for this interface.
|
|
ifaceInstances :: Word64 -> P.Interface -> [Decl]
|
|
ifaceInstances thisMod iface@P.IFace {name = Name.ZapQ {local = name}, typeParams} =
|
|
let typ = typeWithParams name (map C.paramName typeParams)
|
|
in [ instance_
|
|
[]
|
|
["Rpc"]
|
|
"IsClient"
|
|
[typ]
|
|
[ iValue "fromClient" [] (ELName name),
|
|
iValue "toClient" [PLCtor name [PVar "client"]] (euName "client")
|
|
],
|
|
instance_
|
|
[]
|
|
["Classes"]
|
|
"FromPtr"
|
|
[tuName "msg", typ]
|
|
[ iValue "fromPtr" [] (egName ["RpcHelpers"] "isClientFromPtr")
|
|
],
|
|
instance_
|
|
[]
|
|
["Classes"]
|
|
"ToPtr"
|
|
[tuName "s", typ]
|
|
[ iValue "toPtr" [] (egName ["RpcHelpers"] "isClientToPtr")
|
|
],
|
|
instance_
|
|
[]
|
|
["Classes"]
|
|
"Decerialize"
|
|
[typ]
|
|
[ iType "Cerial" [tuName "msg", typ] $
|
|
TApp (tgName (rawModule thisMod) name) $
|
|
[ TApp
|
|
(tgName ["Classes"] "Cerial")
|
|
[ tuName "msg",
|
|
TVar (Name.typeVarName (C.paramName t))
|
|
]
|
|
| t <- typeParams
|
|
]
|
|
++ [tuName "msg"],
|
|
iValue
|
|
"decerialize"
|
|
[ pgName (rawModule thisMod) (Name.mkSub name "newtype_") [PVar "maybeCap"]
|
|
]
|
|
( ECase
|
|
(euName "maybeCap")
|
|
[ ( PGCtor (std_ "Nothing") [],
|
|
EApp
|
|
(eStd_ "pure")
|
|
[ EApp (ELName name) [egName ["Message"] "nullClient"]
|
|
]
|
|
),
|
|
( PGCtor (std_ "Just") [PVar "cap"],
|
|
EFApp
|
|
(ELName name)
|
|
[EApp (egName ["Untyped"] "getClient") [euName "cap"]]
|
|
)
|
|
]
|
|
)
|
|
],
|
|
instance_
|
|
[]
|
|
["Classes"]
|
|
"Cerialize"
|
|
[tuName "s", typ]
|
|
[ iValue "cerialize" [PVar "msg", PLCtor name [PVar "client"]] $
|
|
EFApp
|
|
(egName (rawModule thisMod) (Name.mkSub name "newtype_"))
|
|
[ EFApp
|
|
(eStd_ "Just")
|
|
[EApp (egName ["Untyped"] "appendCap") [euName "msg", euName "client"]]
|
|
]
|
|
]
|
|
]
|
|
++ ifaceServerInstances thisMod iface
|
|
|
|
-- | Generate a reference to a type defined in a pure module given:
|
|
--
|
|
-- * The id of the module we're in (thisMod)
|
|
-- * The id of the module in which the type is defined (targetMod)
|
|
-- * The local name of the type.
|
|
pureTName :: Word64 -> Word64 -> Name.LocalQ -> Type
|
|
pureTName thisMod targetMod local
|
|
| thisMod == targetMod = TLName local
|
|
| otherwise = tgName (pureModule targetMod) local
|
|
|
|
-- | Instance declarations for this interface's client for its *'server_ class
|
|
-- and those of its ancestors.
|
|
ifaceServerInstances :: Word64 -> P.Interface -> [Decl]
|
|
ifaceServerInstances thisMod iface@P.IFace {name = Name.ZapQ {local = name}, typeParams, ancestors} =
|
|
DcInstance
|
|
{ ctx = [],
|
|
typ = TApp (tgName ["Server"] "Server") [tStd_ "IO", typ],
|
|
defs = []
|
|
}
|
|
: map go ((iface, C.ListBrand (map C.PtrParam typeParams)) : ancestors)
|
|
where
|
|
typ = typeWithParams name (map C.paramName typeParams)
|
|
go (P.IFace {name = Name.ZapQ {local, fileId}, interfaceId, methods}, C.ListBrand types) =
|
|
let className = Name.mkSub local "server_"
|
|
classType = pureTName thisMod fileId className
|
|
classArgs = map (typeToType thisMod . C.PtrType) types
|
|
in -- Can't use 'instance_' here, because we don't know ahead of time what
|
|
-- module the class is in.
|
|
DcInstance
|
|
{ ctx = [],
|
|
typ = TApp classType $ [tStd_ "IO", typ] ++ classArgs,
|
|
defs =
|
|
[ let methodName = mkMethodName local mname
|
|
in iValue methodName [PLCtor name [PVar "client"]] $
|
|
EApp
|
|
(egName ["Rpc"] "clientMethodHandler")
|
|
[ EInt $ fromIntegral interfaceId,
|
|
EInt i,
|
|
euName "client"
|
|
]
|
|
| (i, P.Method {name = mname}) <- zip [0 ..] methods
|
|
]
|
|
}
|
|
|
|
-- | Generate declarations for a constant.
|
|
constToDecls :: Word64 -> P.Constant -> [Decl]
|
|
constToDecls thisMod P.Constant {name, value} = case value of
|
|
C.PtrValue ty _ ->
|
|
-- Generated code parses the corresponding constant from the raw
|
|
-- module.
|
|
[ DcValue
|
|
{ typ = typeToType thisMod (C.PtrType ty),
|
|
def =
|
|
DfValue
|
|
{ name = Name.localToUnQ name,
|
|
params = [],
|
|
value =
|
|
EApp
|
|
(egName ["GenHelpersPure"] "toPurePtrConst")
|
|
[egName (rawModule thisMod) name]
|
|
}
|
|
}
|
|
]
|
|
-- For these two we just re-export the constant from the raw module, so no
|
|
-- need to do anything here:
|
|
C.WordValue _ _ -> []
|
|
C.VoidValue -> []
|
|
|
|
mkMethodName :: Name.LocalQ -> Name.UnQ -> Name.UnQ
|
|
mkMethodName typeName methodName = Name.valueName $ Name.mkSub typeName methodName
|
|
|
|
-- | Arguments to 'marshalField'
|
|
data MarshalField = MarshalField
|
|
{ -- | The id for the module we're generating
|
|
thisMod :: !Word64,
|
|
-- | An variable holding the destination for the marshalled data.
|
|
into :: Name.UnQ,
|
|
-- | The name of the field to marshal, qualified within the current module.
|
|
localQField :: Name.LocalQ,
|
|
-- | A variable holding the value to marshal
|
|
from :: Name.UnQ,
|
|
type_ :: C.Type (C.ListBrand Name.ZapQ) Name.ZapQ,
|
|
-- | whether the parent of this field is a union
|
|
inUnion :: !Bool
|
|
}
|
|
|
|
marshalField :: MarshalField -> Exp
|
|
marshalField MarshalField {thisMod, into, localQField, from, type_, inUnion} =
|
|
let setter = egName (rawModule thisMod) $ Name.unQToLocal (Name.setterName localQField)
|
|
getter = egName (rawModule thisMod) $ Name.unQToLocal (Name.getterName localQField)
|
|
in case type_ of
|
|
C.PtrType _ ->
|
|
EBind
|
|
( EApp
|
|
(egName ["Classes"] "cerialize")
|
|
[ EApp (egName ["Untyped"] "message") [euName "raw_"],
|
|
euName from
|
|
]
|
|
)
|
|
(EApp setter [euName into])
|
|
C.VoidType ->
|
|
ePureUnit
|
|
C.WordType _ ->
|
|
EApp setter [euName into, euName from]
|
|
C.CompositeType _ ->
|
|
EDo
|
|
( if Name.getUnQ localQField == "union'"
|
|
then -- Anonymous union; has the same Cerial as us. Just delegate.
|
|
[]
|
|
else
|
|
if inUnion
|
|
then [DoBind into $ EApp setter [euName into]]
|
|
else [DoBind into $ EApp getter [euName into]]
|
|
)
|
|
(EApp (egName ["Classes"] "marshalInto") [euName into, euName from])
|
|
|
|
fieldToField :: Word64 -> P.Field -> (Name.UnQ, Type)
|
|
fieldToField thisMod P.Field {name, type_} = (name, typeToType thisMod type_)
|
|
|
|
typeToType :: Word64 -> C.Type (C.ListBrand Name.ZapQ) Name.ZapQ -> Type
|
|
typeToType _thisMod C.VoidType =
|
|
TUnit
|
|
typeToType _thisMod (C.WordType (C.PrimWord ty)) =
|
|
TPrim ty
|
|
typeToType thisMod (C.WordType (C.EnumType Name.ZapQ {local, fileId})) =
|
|
-- Enums are just re-exported from the raw module, so we have a choice as to
|
|
-- how to refer to them. Using their 'Pure' module makes sure we pull in any
|
|
-- type class instances in those modules, but we have to make an exception
|
|
-- if the enum is defined in the file we're generating code for, since otherwise
|
|
-- we'd introduce a cyclic dependency. In that case we use the raw name; the
|
|
-- instances are defined in this same module so we don't need to worry about those.
|
|
if thisMod == fileId
|
|
then tgName (rawModule fileId) local
|
|
else tgName (pureModule fileId) local
|
|
typeToType thisMod (C.CompositeType (C.StructType n b)) =
|
|
nameToType thisMod n b
|
|
typeToType thisMod (C.PtrType (C.PtrComposite (C.StructType n b))) =
|
|
nameToType thisMod n b
|
|
typeToType thisMod (C.PtrType (C.ListOf ty)) =
|
|
TApp (tgName ["V"] "Vector") [typeToType thisMod ty]
|
|
typeToType _thisMod (C.PtrType (C.PrimPtr C.PrimText)) =
|
|
tgName ["T"] "Text"
|
|
typeToType _thisMod (C.PtrType (C.PrimPtr C.PrimData)) =
|
|
tgName ["BS"] "ByteString"
|
|
typeToType _thisMod (C.PtrType (C.PrimPtr (C.PrimAnyPtr _))) =
|
|
-- TODO: distinguish different pointer types.
|
|
TApp (tStd_ "Maybe") [tgName ["UntypedPure"] "Ptr"]
|
|
typeToType thisMod (C.PtrType (C.PtrInterface (C.InterfaceType n b))) =
|
|
nameToType thisMod n b
|
|
typeToType _thisMod (C.PtrType (C.PtrParam C.TypeParamRef {paramName})) =
|
|
TVar (Name.typeVarName paramName)
|
|
|
|
typeParamVars :: [Name.UnQ] -> [Type]
|
|
typeParamVars = map (TVar . Name.typeVarName)
|
|
|
|
typeWithParams :: Name.LocalQ -> [Name.UnQ] -> Type
|
|
typeWithParams f xs =
|
|
TApp (TLName f) $ typeParamVars xs
|
|
|
|
nameToType :: Word64 -> Name.ZapQ -> C.ListBrand Name.ZapQ -> Type
|
|
nameToType thisMod Name.ZapQ {local, fileId} (C.ListBrand args) =
|
|
let head
|
|
| thisMod == fileId = TLName local
|
|
| otherwise = tgName (pureModule fileId) local
|
|
in case args of
|
|
[] -> head
|
|
_ -> TApp head (map (typeToType thisMod . C.PtrType) args)
|
|
|
|
rawModule :: Word64 -> [T.Text]
|
|
rawModule modId =
|
|
["Zap", "Gen", "ById", T.pack $ printf "X%x" modId]
|
|
|
|
pureModule :: Word64 -> [T.Text]
|
|
pureModule modId =
|
|
rawModule modId ++ ["Pure"]
|