{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ViewPatterns #-} -- Things used by both RawToHaskell and PureToHaskell. module Trans.ToHaskellCommon where import Data.Word import Data.Char (toUpper) import Data.Maybe (fromJust) import GHC.Exts (fromList) import System.FilePath (splitDirectories) import Text.Printf (printf) import qualified Data.ByteString.Lazy as LBS import qualified Data.Set as S import qualified Data.Text as T import Capnp.New.Classes (encode) import Capnp.Repr.Parsed (Parsed) import qualified Capnp import qualified Capnp.New.Basics as B import qualified IR.Common as C import qualified IR.Name as Name import IR.Haskell -- 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 :: 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