module Language.Haskell.BuildWrapper.GHCStorage where
import Language.Haskell.BuildWrapper.Base
import Data.Generics
import System.Directory
import System.FilePath
import PprTyThing
import GHC
import Outputable
import Bag(Bag,bagToList)
import Var(Var,varType,varName)
import FastString(FastString)
import NameSet(NameSet)
import Name hiding (varName)
import DataCon (dataConName)
#if __GLASGOW_HASKELL__ < 700
import GHC.SYB.Instances
#endif
import qualified Data.ByteString.Lazy as BS
import qualified Data.ByteString as BSS
import Data.Aeson
import Data.Maybe
import qualified Data.Text as T
import qualified Data.HashMap.Lazy as HM
import qualified Data.Map as DM
import qualified Data.Vector as V
import Data.Attoparsec.Number (Number(I))
#if __GLASGOW_HASKELL__ < 706
import System.Time (ClockTime)
#else
import Data.Time.Clock (UTCTime)
#endif
import Type (splitFunTys)
import Unique (getUnique)
import Data.List (sortBy)
import qualified MonadUtils as GMU
import TcRnTypes (tcg_type_env,tcg_rdr_env)
import qualified CoreUtils as CoreUtils (exprType)
import Desugar (deSugarExpr)
import Control.Monad (liftM)
import Data.Aeson.Types (Pair)
getInfoFile :: FilePath
-> FilePath
getInfoFile fp= let
(dir,file)=splitFileName fp
in combine dir ('.' : addExtension file ".bwinfo")
getUsageFile :: FilePath
-> FilePath
getUsageFile fp= let
(dir,file)=splitFileName fp
in combine dir ('.' : addExtension file ".bwusage")
clearInfo :: FilePath
-> IO()
clearInfo fp =do
let ghcInfoFile=getInfoFile fp
removeFile ghcInfoFile
storeBuildFlagsInfo :: FilePath
-> (BuildFlags,[BWNote])
-> IO()
storeBuildFlagsInfo fp bf=setStoredInfo fp "BuildFlags" (toJSON bf)
generateGHCInfo :: DynFlags -> HscEnv -> TypecheckedModule -> IO Value
generateGHCInfo df env tcm=do
tcvals<-liftM extractUsages $ dataToJSON df env tcm $ typecheckedSource tcm
let tcByNameLoc=foldr buildMap DM.empty tcvals
rnvals<-liftM extractUsages $ dataToJSON df env tcm $ tm_renamed_source tcm
let typedVals=map (addType tcByNameLoc) rnvals
return (Array $ V.fromList typedVals)
where
buildMap v@(Object m) dm |
Just pos<-HM.lookup "Pos" m,
Success ifs <- fromJSON pos,
Just (String s)<-HM.lookup "Name" m,
Just (String mo)<-HM.lookup "Module" m,
Just _<-HM.lookup "QType" m,
Just _<-HM.lookup "Type" m,
Just "v"<-HM.lookup "HType" m,
Just _<-HM.lookup "GType" m=
DM.insert (mo,s,iflLine $ ifsStart ifs,0) v $
DM.insert (mo,s,iflLine $ ifsStart ifs,iflColumn $ ifsStart ifs) v dm
buildMap _ dm=dm
addType dm v@(Object m1) |
Just pos<-HM.lookup "Pos" m1,
Success ifs <- fromJSON pos,
Just (String s)<-HM.lookup "Name" m1,
Just (String mo)<-HM.lookup "Module" m1,
Just "v"<-HM.lookup "HType" m1=let
mv=DM.lookup (mo,s,iflLine $ ifsStart ifs,iflColumn $ ifsStart ifs) dm
mv2=case mv of
Nothing -> DM.lookup (mo,s,iflLine $ ifsStart ifs,0) dm
a->a
in case mv2 of
Just (Object m2) |
Just qt<-HM.lookup "QType" m2,
Just t<-HM.lookup "Type" m2,
Just gt<-HM.lookup "GType" m2 -> Object (HM.insert "QType" qt $
HM.insert "Type" t $
HM.insert "GType" gt
m1)
_ -> v
addType _ v=v
storeGHCInfo ::
DynFlags
-> HscEnv
-> FilePath
-> TypecheckedModule
-> IO()
storeGHCInfo df env fp tcm= do
generateGHCInfo df env tcm >>= setStoredInfo fp "AST"
readGHCInfo :: FilePath
-> IO(Maybe Value)
readGHCInfo fp=do
(Object hm)<-readStoredInfo fp
return $ HM.lookup "AST" hm
readBuildFlagsInfo :: FilePath
#if __GLASGOW_HASKELL__ < 706
-> ClockTime
#else
-> UTCTime
#endif
-> IO (Maybe (BuildFlags,[BWNote]))
readBuildFlagsInfo fp ct=do
let ghcInfoFile=getInfoFile fp
ex<-doesFileExist ghcInfoFile
if ex then do
ctF<-getModificationTime ghcInfoFile
if ctF>ct
then do
(Object hm)<-readStoredInfo fp
return $ maybe Nothing (\x-> case fromJSON x of
Success a->Just a
Error _->Nothing) $ HM.lookup "BuildFlags" hm
else return Nothing
else return Nothing
setStoredInfo :: FilePath
-> T.Text
-> Value
-> IO()
setStoredInfo fp k v=do
let ghcInfoFile=getInfoFile fp
(Object hm)<-readStoredInfo fp
let hm2=HM.insert k v hm
BSS.writeFile ghcInfoFile $ BSS.concat $ BS.toChunks $ encode $ Object hm2
readStoredInfo :: FilePath
-> IO Value
readStoredInfo fp=do
let ghcInfoFile=getInfoFile fp
ex<-doesFileExist ghcInfoFile
mv<-if ex
then do
bs<-BSS.readFile ghcInfoFile
return $ decode' $ BS.fromChunks [bs]
else return Nothing
return $ fromMaybe (object []) mv
setUsageInfo :: FilePath
-> Value
-> IO()
setUsageInfo fp v=do
let usageFile=getUsageFile fp
BSS.writeFile usageFile $ BSS.concat $ BS.toChunks $ encode v
getUsageInfo :: FilePath
-> IO Value
getUsageInfo fp=do
let usageFile=getUsageFile fp
ex<-doesFileExist usageFile
mv<-if ex
then do
bs<-BSS.readFile usageFile
return $ decode' $ BS.fromChunks [bs]
else return Nothing
return $ fromMaybe (object []) mv
dataToJSON :: Data a => DynFlags -> HscEnv -> TypecheckedModule -> a -> IO Value
dataToJSON df env tcm=
generic `ext1Q` list `extQ` (return . string) `extQ` (return . fastString) `extQ` (return . srcSpanToJSON)
`extQ` (return . name) `extQ` (return . ocName) `extQ` (return . modName) `extQ` var `extQ` exprVar `extQ` (return . dataCon)
`extQ` bagName `extQ` bagRdrName `extQ` bagVar `extQ` (return . nameSet)
`extQ` (return . postTcType) `extQ` (return . fixity) `extQ` hsBind
where generic :: Data a => a -> IO Value
generic t=do
let sub=gmapQ (dataToJSON df env tcm) t
liftM arr $ sequence sub
string = Data.Aeson.String . T.pack :: String -> Value
fastString:: FastString -> Value
fastString fs=object ["FastString" .= T.pack (show fs)]
list l = liftM arr $ mapM (dataToJSON df env tcm) l
arr a = let
sub=filter (/= Null) a
in case sub of
[] -> Null
[x] -> x
_ -> toJSON sub
name :: Name -> Value
name n = object (nameAndModule n ++["GType" .= string "Name","HType".= string (if isValOcc (nameOccName n) then "v" else "t")])
ocName :: OccName -> Value
ocName o = name (mkSystemName (getUnique o) o)
modName :: ModuleName -> Value
modName m= object [ "Name" .= string (showSD True df $ ppr m),"GType" .= string "ModuleName","HType" .= string "m"]
var :: Var -> IO Value
var v = return $ typedVar v (varType v)
dataCon :: DataCon -> Value
dataCon d = let
t=dataConUserType d
in object (nameAndModule (dataConName d) ++ typeToJSON t ++ [
"GType" .= string "DataCon",
"HType" .= string "v"])
simpleV:: T.Text -> Value -> Value
simpleV nm v=object [nm .= v]
bagRdrName:: Bag (Located (HsBind RdrName)) -> IO Value
bagRdrName = liftM (simpleV "Bag(Located (HsBind RdrName))") . list . bagToList
bagName :: Bag (Located (HsBind Name)) -> IO Value
bagName = liftM (simpleV "Bag(Located (HsBind Name))") . list . bagToList
bagVar :: Bag (Located (HsBind Var)) -> IO Value
bagVar = liftM (simpleV "Bag(Located (HsBind Var))") . list . bagToList
exprVar :: HsExpr Var ->IO Value
exprVar ev = do
mt<- getType env tcm (L noSrcSpan ev)
case mt of
Just t-> case identOfExpr ev of
(Just v)->do
return $ typedVar v t
Nothing->generic ev
--do
Nothing->generic ev
typedVar :: Var -> Type -> Value
typedVar v t=object (nameAndModule (varName v) ++ typeToJSON t ++
["GType" .= string "Var",
"HType" .= string (if isValOcc (nameOccName (Var.varName v)) then "v" else "t")])
nameSet = const $ Data.Aeson.String "{!NameSet placeholder here!}" :: NameSet -> Value
postTcType = const Null :: Type -> Value
fixity = const Null :: GHC.Fixity -> Value
typeToJSON :: Type -> [(T.Text,Value)]
typeToJSON t =
["Type" .= string (showSD False df $ pprTypeForUser True t),
"QType" .= string (showSD True df $ pprTypeForUser True t)]
hsBind :: HsBindLR Name Name -> IO Value
hsBind (FunBind fid _ (MatchGroup matches _) _ _ _) =do
d2<-dataToJSON df env tcm $ unLoc fid
liftM arr $ mapM (\m->do
d1<-dataToJSON df env tcm $ getLoc m
d3<-dataToJSON df env tcm m
return $ arr [arr [d1,d2],d3]) matches
hsBind a=generic a
nameAndModule :: Name -> [Pair]
nameAndModule n=let
mm=nameModule_maybe n
mn=maybe "" (showSD True df . ppr . moduleName) mm
pkg=maybe "" (showSD True df . ppr . modulePackageId) mm
na=showSD False df $ ppr n
in ["Module" .= string mn,"Package" .= string pkg, "Name" .= string na]
getType :: HscEnv -> TypecheckedModule -> LHsExpr Var -> IO(Maybe Type)
getType hs_env tcm e = do
(_, mbe) <- GMU.liftIO $ deSugarExpr hs_env modu rn_env ty_env e
return $ fmap (CoreUtils.exprType) mbe
where
modu = ms_mod $ pm_mod_summary $ tm_parsed_module tcm
rn_env = tcg_rdr_env $ fst $ tm_internals_ tcm
ty_env = tcg_type_env $ fst $ tm_internals_ tcm
showSD :: Bool
-> DynFlags
-> SDoc
-> String
#if __GLASGOW_HASKELL__ < 706
showSD True _ =showSDoc
showSD False _ =showSDocUnqual
#else
showSD True df =showSDoc df
showSD False df =showSDocUnqual df
#endif
showSDUser :: PrintUnqualified
-> DynFlags
-> SDoc
-> String
#if __GLASGOW_HASKELL__ < 706
showSDUser unqual _ =showSDocForUser unqual
#else
showSDUser unqual df =showSDocForUser df unqual
#endif
showSDDump :: DynFlags
-> SDoc
-> String
#if __GLASGOW_HASKELL__ < 706
showSDDump _ =showSDocDump
#else
showSDDump df =showSDocDump df
#endif
srcSpanToJSON :: SrcSpan -> Value
srcSpanToJSON src
| isGoodSrcSpan src = object[ "SrcSpan" .= toJSON [srcLocToJSON $ srcSpanStart src, srcLocToJSON $ srcSpanEnd src]]
| otherwise = Null
#if __GLASGOW_HASKELL__ < 702
srcLocToJSON :: SrcLoc -> Value
srcLocToJSON sl
| isGoodSrcLoc sl=object ["line" .= toJSON (srcLocLine sl),"column" .= toJSON (srcLocCol sl)]
| otherwise = Null
#else
srcLocToJSON :: SrcLoc -> Value
srcLocToJSON (RealSrcLoc sl)=object ["line" .= toJSON (srcLocLine sl),"column" .= toJSON (srcLocCol sl)]
srcLocToJSON _ = Null
#endif
typesInsideType :: Type -> [Type]
typesInsideType t=let
(f1,f2)=splitFunTys t
in f2 : concatMap typesInsideType f1
type FindFunc= Value -> Bool
findInJSONFormatted :: Bool
-> Bool
-> Maybe Value
-> String
findInJSONFormatted qual typed (Just (Object m)) | Just (String name)<-HM.lookup "Name" m=let
tn=T.unpack name
qn=if qual
then
let mo=maybe "" addDot $ HM.lookup "Module" m
in mo ++ tn
else tn
in if typed then
let mt=HM.lookup (if qual then "QType" else "Type") m
in case mt of
Just (String t)->qn ++ " :: " ++ T.unpack t
_ -> tn
else
let mt=HM.lookup "HType" m
in case mt of
Just (String t)->qn ++ " " ++ T.unpack t
_ -> tn
where
addDot :: Value -> String
addDot (String s)=T.unpack s ++ "."
addDot _=error "expected String value for Module key"
findInJSONFormatted _ _ _="no info"
findInJSONData :: Maybe Value -> Maybe ThingAtPoint
findInJSONData (Just o@(Object m)) | Just (String _)<-HM.lookup "Name" m=case fromJSON o of
Success tap->tap
Error _ -> Nothing
findInJSONData _=Nothing
findInJSON :: FindFunc
-> Value
-> Maybe Value
findInJSON f (Array vals)=listToMaybe $ sortBy lastPos $ filter f $ V.toList vals
findInJSON _ _=Nothing
findAllInJSON :: FindFunc
-> Value
-> [Value]
findAllInJSON f (Array vals)=filter f $ V.toList vals
findAllInJSON _ _=[]
lastPos :: Value -> Value -> Ordering
lastPos (Object m1) (Object m2) |
Just pos1<-HM.lookup "Pos" m1,
Success ifs1 <- fromJSON pos1,
Just pos2<-HM.lookup "Pos" m2,
Success ifs2 <- fromJSON pos2 =let
c1=compare (iflLine $ ifsStart ifs2) (iflLine $ ifsStart ifs1)
in case c1 of
EQ -> compare (iflColumn $ ifsStart ifs2) (iflColumn $ ifsStart ifs1)
a -> a
lastPos _ _=EQ
overlap :: Int
-> Int
-> FindFunc
overlap l c (Object m) |
Just pos<-HM.lookup "Pos" m,
Success ifs <- fromJSON pos=iflOverlap ifs (InFileLoc l c)
overlap _ _ _=False
contains :: Int
-> Int
-> Int
-> Int
-> FindFunc
contains sl sc el ec (Object m) |
Just pos<-HM.lookup "Pos" m,
Success ifs <- fromJSON pos=iflOverlap (InFileSpan (InFileLoc sl sc) (InFileLoc el ec)) (ifsStart ifs)
contains _ _ _ _ _=False
isGHCType :: String
-> FindFunc
isGHCType tp (Object m) |
Just pos<-HM.lookup "GType" m,
Success ghcType <- fromJSON pos=tp == ghcType
isGHCType _ _ =False
extractUsages :: Value
-> [Value]
extractUsages (Array arr) | not $ V.null arr=let
v1=arr V.! 0
msrc=extractSource v1
in if isJust msrc && V.length arr==2
then extractName v1 (arr V.! 1) ++ extractUsages (arr V.! 1)
else concat $ V.toList $ fmap extractUsages arr
extractUsages (Object obj)=concatMap extractUsages $ HM.elems obj
extractUsages _= []
extractName :: Value -> Value -> [Value]
extractName src (Object m) |
Just ifl<-extractSource src,
Just (String s)<-HM.lookup "Name" m,
Just (String mo)<-HM.lookup "Module" m,
Just (String p)<-HM.lookup "Package" m,
mqt<-HM.lookup "QType" m,
mst<-HM.lookup "Type" m,
mgt<-HM.lookup "GType" m,
Just (String t)<-HM.lookup "HType" m,
at<-fromMaybe (Array V.empty) $ HM.lookup "AllTypes" m
=let
atts=["Name" .= s,"Module" .= mo,"Package" .= p,"HType" .= t,"AllTypes" .= at, "Pos" .= toJSON ifl, "QType" .= mqt, "Type" .= mst,"GType" .= mgt]
in [object atts]
extractName _ _=[]
extractSource :: Value -> Maybe InFileSpan
extractSource (Object m) |
Just pos<-HM.lookup "SrcSpan" m,
Just (sl,sc,el,ec)<-extractSourceSpan pos=Just $ InFileSpan (InFileLoc sl sc) (InFileLoc el ec)
extractSource _=Nothing
extractSourceSpan :: Value -> Maybe (Int,Int,Int,Int)
extractSourceSpan (Array arr) | V.length arr==2 = do
let v1=arr V.! 0
let v2=arr V.! 1
(l1,c1)<-extractSourceLoc v1
(l2,c2)<-extractSourceLoc v2
return (l1,c1,l2,c2)
extractSourceSpan _ =Nothing
extractSourceLoc :: Value -> Maybe (Int,Int)
extractSourceLoc (Object m) |
Just (Number(I l))<-HM.lookup "line" m,
Just (Number(I c))<-HM.lookup "column" m=Just (fromIntegral l,fromIntegral c)
extractSourceLoc _ = Nothing
identOfExpr :: HsExpr Var -> Maybe Var
identOfExpr (HsWrap _ (HsVar ident)) = Just ident
identOfExpr (HsWrap _ wr1) =identOfExpr wr1
identOfExpr _ = Nothing