mirror of
https://github.com/zap-proto/haskell.git
synced 2026-10-30 15:05:00 +00:00
..rather than BS.pack [0,1,2,...] This makes a *dramatic* difference on both the (haskell source) size and (ghc) build time of the modules generated from the sandstorm schema. One particularly pathological example: sandstorm's mime.capnp is dominated by one giant table in the form of a constant declaration, with both keys and values being Text. The source file is about 56KiB. The size of the output, with the old version of the code generator, was almost 4MiB! The new output size is 196KiB, which is much more reasonable. Build time for that file is now quite fast, whereas before it was one of the slowest ones. Part of the problem is the way we were pretty-printing things, which could have been solved in other ways, but this will also improve the size of constants that are mostly text, even without that. I also suspect this will improve build times more than just fixing the pretty-printing would.
236 lines
7.7 KiB
Haskell
236 lines
7.7 KiB
Haskell
{-# 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 System.FilePath (splitDirectories)
|
|
import Text.Printf (printf)
|
|
|
|
import qualified Data.Set as S
|
|
import qualified Data.Text as T
|
|
|
|
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)
|
|
|
|
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 }
|
|
|
|
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 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
|