packages feed

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