haskell-dap-0.0.4.0: app/Haskell/DAP/GHCi/Utility.hs
{-# LANGUAGE LambdaCase #-}
module Haskell.DAP.GHCi.Utility where
import qualified GHC
import qualified GHCi.UI.Monad as G
import HscTypes
import Outputable
import PprTyThing
import Debugger
import Exception
import DynFlags
import RtClosureInspect
import qualified Data.Char as CH
import qualified Data.ByteString as BS
import qualified Data.Text.Encoding as T
import qualified Data.Text as T
import qualified Data.List as L
import qualified Text.Read as R
import Data.Word
import Control.Monad.IO.Class
import Haskell.DAP.GHCi.Constant
import qualified GHCi.DAP.IFData as D
-- |
--
_SLASH :: Char
_SLASH = '/'
-- |
--
_BACK_SLASH :: Char
_BACK_SLASH = '\\'
-- |
--
_SPACES :: [Char]
_SPACES = [' ', '\n', '\t']
-- |
--
lstrip, rstrip, strip :: String -> String
lstrip = dropWhile (flip elem _SPACES)
rstrip = reverse . lstrip . reverse
strip = lstrip . rstrip
-- |
--
toLower :: String -> String
toLower = map CH.toLower
-- |
--
toUpper :: String -> String
toUpper = map CH.toUpper
-- |
--
win2unixSlash :: String -> String
win2unixSlash = map (\c -> if c == _BACK_SLASH then _SLASH else c)
-- |
--
unix2winSlash :: String -> String
unix2winSlash = map (\c -> if c == _SLASH then _BACK_SLASH else c)
-- |
-- normalized path
--
nzPath :: FilePath -> FilePath
nzPath = drive2lower . win2unixSlash
-- |
-- to lowercase Windows drive letter
--
drive2lower :: FilePath -> FilePath
drive2lower (x : ':' : xs) = CH.toLower x : ':' : xs
drive2lower xs = xs
------------------------------------------------------------------------------------------------
-- DAP Utility
------------------------------------------------------------------------------------------------
-- |
--
readDAP :: Read a => String -> Either String a
readDAP argsStr = case R.readEither argsStr :: Either String [Word8] of
Left err -> Left $ "read [Word8] failed. " ++ err ++ " : " ++ argsStr
Right bs -> case R.readEither (toStr bs) of
Left err -> Left $ "read response body failed. " ++ err ++ " : " ++ (toStr bs)
Right a -> Right a
where
toStr = T.unpack . T.decodeUtf8 . BS.pack
-- |
--
showDAP :: Show a => a -> String
showDAP = show . BS.unpack . T.encodeUtf8 . T.pack . show
-- |
--
printDAP :: Show a => a -> G.GHCi ()
printDAP dat = do
let outStr = _DAP_HEADER ++ showDAP dat
liftIO $ putStrLn outStr
-- |
--
showStoppedEventBody :: Maybe GHC.ExecResult -> G.GHCi ()
showStoppedEventBody Nothing = return ()
showStoppedEventBody (Just (GHC.ExecComplete { GHC.execResult = Right _ })) = do
let body = Right D.defaultStoppedEventBody {
D.reasonStoppedEventBody = "complete"
} :: Either String D.StoppedEventBody
outStr = _DAP_HEADER ++ showDAP body
liftIO $ putStrLn "[DAP][INFO] debug complete"
liftIO $ putStrLn outStr
showStoppedEventBody (Just (GHC.ExecComplete { GHC.execResult = Left (SomeException e)})) = do
let body = Right D.defaultStoppedEventBody {
D.reasonStoppedEventBody = "complete"
, D.descriptionStoppedEventBody = show e
, D.textStoppedEventBody = show e
} :: Either String D.StoppedEventBody
outStr = _DAP_HEADER ++ showDAP body
liftIO $ putStrLn $ "[DAP][INFO] debug complete with exception. " ++ show e
liftIO $ putStrLn outStr
showStoppedEventBody (Just GHC.ExecBreak{}) = getExceptionResume >>= \case
Nothing -> stoppedByBreak
Just _ -> stoopedWithException
where
stoopedWithException = do
evalBody <- getEvalBody "_exception" True
let body = Right D.defaultStoppedEventBody {
D.reasonStoppedEventBody = "exception"
, D.descriptionStoppedEventBody = D.resultEvaluateBody evalBody
, D.textStoppedEventBody = D.resultEvaluateBody evalBody
} :: Either String D.StoppedEventBody
outStr = _DAP_HEADER ++ showDAP body
liftIO $ putStrLn "[DAP][INFO] break with exception."
liftIO $ putStrLn outStr
stoppedByBreak = do
let body = Right D.defaultStoppedEventBody {
D.reasonStoppedEventBody = "step"
} :: Either String D.StoppedEventBody
outStr = _DAP_HEADER ++ showDAP body
liftIO $ putStrLn "[DAP][INFO] breaks."
liftIO $ putStrLn outStr
-- |
--
getExceptionResume :: G.GHCi (Maybe GHC.Resume)
getExceptionResume = GHC.getResumeContext >>= \case
[] -> return Nothing
(x:_) -> if isExceptionResume x then return (Just x)
else return Nothing
-- |
--
isExceptionResume :: GHC.Resume -> Bool
isExceptionResume r = L.isInfixOf "exception" (GHC.resumeDecl r)
-- |
--
parseNameErrorHandler :: SomeException -> G.GHCi [GHC.Name]
parseNameErrorHandler e = liftIO $ print e >> return []
-- |
--
showTermErrorHandler :: SomeException -> G.GHCi SDoc
showTermErrorHandler e = return $ text $ show e
-- |
--
--
getEvalBody :: String -> Bool -> G.GHCi D.EvaluateBody
getEvalBody nameStr isForce =
gcatch (GHC.parseName nameStr) parseNameErrorHandler >>= withNames
where
withNames [] = return D.defaultEvaluateBody {
D.resultEvaluateBody = "Not in scope: " ++ nameStr
, D.typeEvaluateBody = "force error."
, D.variablesReferenceEvaluateBody = 0
}
withNames (n:[]) = GHC.lookupName n >>= \case
Just ty -> withTyThing ty
Nothing -> return D.defaultEvaluateBody {
D.resultEvaluateBody = "variable not found. " ++ nameStr
, D.typeEvaluateBody = "force error."
, D.variablesReferenceEvaluateBody = 0
}
withNames _ = return D.defaultEvaluateBody {
D.resultEvaluateBody = "ambiguous name" ++ nameStr
, D.typeEvaluateBody = "force error."
, D.variablesReferenceEvaluateBody = 0
}
withTyThing (AnId i) = GHC.obtainTermFromId maxBound isForce i >>= withTerm i
withTyThing x = do
dflags <- getDynFlags
return D.defaultEvaluateBody {
D.resultEvaluateBody = "unsupported tything. " ++ showSDoc dflags (ppr x)
, D.typeEvaluateBody = "force error."
, D.variablesReferenceEvaluateBody = 0
}
-- |
-- Term https://hackage.haskell.org/package/ghc-8.2.1/docs/RtClosureInspect.html
--
withTerm :: GHC.Id -> Term -> G.GHCi D.EvaluateBody
withTerm _ t@(Term ty _ _ _) = do
dflags <- getDynFlags
termSDoc <- gcatch (showTerm t) showTermErrorHandler
let typeStr = showSDoc dflags (pprTypeForUser ty)
valStr = showSDoc dflags termSDoc
return D.defaultEvaluateBody {
D.resultEvaluateBody = valStr
, D.typeEvaluateBody = typeStr
}
withTerm i _ = do
dflags <- getDynFlags
idSDoc <- pprTypeAndContents i
let (_, typeStr, valStr) = getNameTypeValue (showSDoc dflags idSDoc)
return D.defaultEvaluateBody {
D.resultEvaluateBody = valStr
, D.typeEvaluateBody = typeStr
}
-- |
--
getNameTypeValue :: String -> (String, String, String)
getNameTypeValue str = (strip nameStr, strip typeStr, strip valueStr)
where
nameStr = takeWhile (/= ' ') str
typeStr = takeWhile (/= '=') $ drop 4 $ dropWhile (/= ' ') str
valueStr_ = tail $ dropWhile (/= '=') str
valueStr = if elem "->" (words typeStr) then "function :: " ++ typeStr
else valueStr_