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
#if __GLASGOW_HASKELL__ < 702
import TypeRep (Type(..), PredType(..))
import VarSet
#elif __GLASGOW_HASKELL__ < 704
import TypeRep (Type(..), Pred(..), tyVarsOfType)
import VarSet (isEmptyVarSet)
#else
import TypeRep (Type(..), tyVarsOfType)
import VarSet (isEmptyVarSet)
#endif
#if __GLASGOW_HASKELL__ >= 704
import TcEvidence
#endif
import qualified Data.ByteString.Lazy as BS
import qualified Data.ByteString.Lazy.Char8 as BSC (putStrLn)
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))
import System.Time (ClockTime)
import Type (splitFunTys)
import Unique (getUnique)
import Data.List (sortBy)
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 :: TypecheckedModule -> Value
generateGHCInfo tcm=let
tcvals=extractUsages $ dataToJSON $ typecheckedSource tcm
tcByNameLoc=foldr buildMap DM.empty tcvals
rnvals=extractUsages $ dataToJSON $ tm_renamed_source tcm
typedVals=map (addType tcByNameLoc) rnvals
in (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 :: FilePath
-> TypecheckedModule
-> IO()
storeGHCInfo fp tcm=
setStoredInfo fp "AST" $ generateGHCInfo tcm
readGHCInfo :: FilePath
-> IO(Maybe Value)
readGHCInfo fp=do
(Object hm)<-readStoredInfo fp
return $ HM.lookup "AST" hm
readBuildFlagsInfo :: FilePath
-> ClockTime
-> 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 =>a -> Value
dataToJSON =
generic `ext1Q` list `extQ` string `extQ` fastString `extQ` srcSpanToJSON
`extQ` name `extQ` occName `extQ` modName `extQ` var `extQ` exprVar `extQ` dataCon
`extQ` bagName `extQ` bagRdrName `extQ` bagVar `extQ` nameSet
`extQ` postTcType `extQ` fixity `extQ` hsBind
where generic :: Data a => a -> Value
generic t =arr $ gmapQ dataToJSON t
string = Data.Aeson.String . T.pack :: String -> Value
fastString:: FastString -> Value
fastString fs= object ["FastString" .= T.pack (show fs)]
list l = arr $ map dataToJSON 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")])
occName :: OccName -> Value
occName o = name (mkSystemName (getUnique o) o)
modName :: ModuleName -> Value
modName m= object [ "Name" .= string (showSDoc $ ppr m),"GType" .= string "ModuleName","HType" .= string "m"]
var :: Var -> Value
var v = 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)) -> Value
bagRdrName = simpleV "Bag(Located (HsBind RdrName))" . list . bagToList
bagName :: Bag (Located (HsBind Name)) -> Value
bagName = simpleV "Bag(Located (HsBind Name))" . list . bagToList
bagVar :: Bag (Located (HsBind Var)) -> Value
bagVar = simpleV "Bag(Located (HsBind Var))". list . bagToList
exprVar :: HsExpr Var -> Value
exprVar ev = let
mt=typeOfExpr ev
in case mt of
Just (t,v)-> typedVar v t
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 (showSDocUnqual $ pprTypeForUser True t),
"QType" .= string (showSDoc $ pprTypeForUser True t)]
hsBind :: HsBindLR Name Name -> Value
hsBind (FunBind fid _ (MatchGroup matches _) _ _ _) =arr $ map (\m->arr [arr [dataToJSON $ getLoc m,dataToJSON $ unLoc fid],dataToJSON m]) matches
hsBind a=generic a
nameAndModule n=let
mm=nameModule_maybe n
mn=maybe "" (showSDoc . ppr . moduleName) mm
pkg=maybe "" (showSDoc . ppr . modulePackageId) mm
na=showSDocUnqual $ ppr n
in ["Module" .= string mn,"Package" .= string pkg, "Name" .= string na]
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
debugToJSON :: Data a =>a -> IO()
debugToJSON = BSC.putStrLn . encode . dataToJSON
debugFindInJSON :: Data a => Int -> Int -> a -> IO()
debugFindInJSON l c a= do
let v=dataToJSON a
let mv=findInJSON (overlap l c) v
case mv of
Just rv->do
putStrLn "something found!"
BSC.putStrLn $ encode rv
Nothing->putStrLn "nothing found!"
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
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
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
typeOfExpr :: HsExpr Var -> Maybe (Type,Var)
typeOfExpr (HsWrap wr (HsVar ident)) =
let
unwrap WpHole t = t
unwrap (WpCompose w1 w2) t = unwrap w1 (unwrap w2 t)
unwrap (WpCast _) t = t
unwrap (WpTyApp t') t = AppTy t t'
unwrap (WpTyLam tv) t = ForAllTy tv t
#if __GLASGOW_HASKELL__ < 700
unwrap (WpApp v) t = AppTy t (TyVarTy v)
unwrap (WpLam v) t = ForAllTy v t
#else
unwrap (WpEvLam v) t = ForAllTy v t
unwrap (WpEvApp _) t = t
#endif
unwrap (WpLet _) t = t
#ifdef WPINLINE
unwrap WpInline t = t
#endif
in Just (reduceType $ unwrap wr (varType ident), ident)
typeOfExpr _ = Nothing
reduceType :: Type -> Type
reduceType = reduce [] []
where reduce :: [Type] -> [(Var,Type)] -> Type -> Type
reduce _ env t@(TyVarTy var) | Just arg <- lookup var env = arg
| otherwise = t
reduce _ env (TyConApp tycon kts) = TyConApp tycon $ map (reduce [] env) kts
reduce stck env (AppTy typ1 typ2) = reduce (reduce [] env typ2 : stck) env typ1
reduce [] env (ForAllTy var typ) = ForAllTy var $ reduce [] env typ
reduce (arg:stck) env (ForAllTy var typ) = reduce stck ((var,arg) : env) typ
reduce _ env (FunTy typ1 typ2) =
let rtyp1 = reduce [] env typ1
rtyp2 = reduce [] env typ2
in case typ1 of
#if __GLASGOW_HASKELL__ < 704
PredTy _ | isEmptyVarSet (tyVarsOfType rtyp1) -> rtyp2
#else
TyConApp tycon _ | isClassTyCon tycon && isEmptyVarSet (tyVarsOfType rtyp1) -> rtyp2
#endif
_ -> FunTy rtyp1 rtyp2
#if __GLASGOW_HASKELL__ < 704
reduce _ env (PredTy pt) = PredTy $ reducePredType pt
where reducePredType (ClassP c ts) = ClassP c $ map (reduce [] env) ts
reducePredType (IParam i t) = IParam i (reduce [] env t)
reducePredType (EqPred t1 t2) = EqPred (reduce [] env t1) (reduce [] env t2)
#if __GLASGOW_HASKELL__ < 702
tyVarsOfType :: Type -> VarSet
tyVarsOfType (TyVarTy v) = unitVarSet v
tyVarsOfType (TyConApp _ tys) = tyVarsOfTypes tys
tyVarsOfType (PredTy sty) = varsOfPred tyVarsOfType sty
tyVarsOfType (FunTy arg res) = tyVarsOfType arg `unionVarSet` tyVarsOfType res
tyVarsOfType (AppTy fun arg) = tyVarsOfType fun `unionVarSet` tyVarsOfType arg
tyVarsOfType (ForAllTy tyvar ty) = delVarSet (tyVarsOfType ty) tyvar
tyVarsOfTypes :: [Type] -> TyVarSet
tyVarsOfTypes tys = foldr (unionVarSet . tyVarsOfType) emptyVarSet tys
varsOfPred :: (Type -> VarSet) -> PredType -> VarSet
varsOfPred f (IParam _ ty) = f ty
varsOfPred f (ClassP _ tys) = foldr (unionVarSet . f) emptyVarSet tys
varsOfPred f (EqPred ty1 ty2) = f ty1 `unionVarSet` f ty2
#endif
#endif