haskell-dap-0.0.1.0: app/Haskell/DAP/GHCi/Command.hs
{-# LANGUAGE LambdaCase #-}
module Haskell.DAP.GHCi.Command where
import qualified GHC
import HscTypes
import Outputable
import PprTyThing
import Debugger
import Exception
import FastString
import DataCon
import DynFlags
import RtClosureInspect
import qualified GHCi.UI.Monad as G
import Control.DeepSeq (deepseq)
import Control.Monad.IO.Class
import Control.Monad.Trans.Class
import Control.Concurrent
import Control.Monad
import Data.Char
import qualified Data.Map as M
import System.Console.Haskeline
import qualified GHCi.DAP.IFData as D
import Haskell.DAP.GHCi.Type
import Haskell.DAP.GHCi.Constant
import Haskell.DAP.GHCi.Utility
-- |
--
dapCommands :: MVar DAPContext -> [G.Command]
dapCommands ctx = map mkCmd [
("dap-echo", dapEcho, noCompletion)
, ("dap-bindings", dapBindingsCommand ctx, noCompletion)
, ("dap-force", dapForceCommand ctx, noCompletion)
, ("dap-scopes", dapScopesCommand ctx, noCompletion)
, ("dap-history", dapHistoryCommand ctx, noCompletion)
]
where
mkCmd (n,a,c) = G.Command { G.cmdName = n
, G.cmdAction = a
, G.cmdHidden = False
, G.cmdCompletionFunc = c
}
------------------------------------------------------------------------------------------------
-- DAP Command :dap-echo
------------------------------------------------------------------------------------------------
-- |
--
dapEcho :: String -> InputT G.GHCi Bool
dapEcho str = do
liftIO $ putStrLn $ "[DAP][INFO] dap-echo \"" ++ str ++ "\""
return False
------------------------------------------------------------------------------------------------
-- DAP Command :dap-history
------------------------------------------------------------------------------------------------
-- |
--
dapHistoryCommand :: MVar DAPContext -> String -> InputT G.GHCi Bool
dapHistoryCommand ctxMVar _ = do
ctx <- liftIO $ takeMVar ctxMVar
liftIO $ putMVar ctxMVar ctx {frameIdDAPContext = 0}
let outStr = _DAP_HEADER ++ " frame id cleared."
liftIO $ putStrLn outStr
return False
------------------------------------------------------------------------------------------------
-- DAP Command :dap-bindings
------------------------------------------------------------------------------------------------
-- |
--
--
dapBindingsCommand :: MVar DAPContext -> String -> InputT G.GHCi Bool
dapBindingsCommand ctxMVar idxStr = do
body <- lift $ getVariablesBody ctxMVar idxStr
let outStr = _DAP_HEADER ++ (show body)
liftIO $ putStrLn outStr
return False
-- |
--
getVariablesBody :: MVar DAPContext -> String -> G.GHCi D.VariablesBody
getVariablesBody ctxMVar idxStr = do
vals <- getBindigVariables ctxMVar idxStr
return $ D.VariablesBody vals
-- |
--
getBindigVariables :: MVar DAPContext -> String -> G.GHCi [D.Variable]
getBindigVariables ctx idStr
| "1" == idStr = getBindigVariablesRoot ctx
| otherwise = getBindigVariablesNode ctx idStr
-- |
--
getBindigVariablesRoot :: MVar DAPContext -> G.GHCi [D.Variable]
getBindigVariablesRoot ctxMVar = do
bindings <- liftIO $ bindingDAPContext <$> readMVar ctxMVar
-- liftIO $ putStrLn $ "[DAP][INFO] bindings " ++ show (length bindings)
mapM tyThing2Val bindings
where
-- |
-- TyThings https://hackage.haskell.org/package/ghc-8.2.1/docs/HscTypes.html#t:TyThing
--
tyThing2Val (AnId i) = do
let isForce = False
GHC.obtainTermFromId maxBound isForce i >>= withTerm i
tyThing2Val x = do
dflags <- getDynFlags
return D.defaultVariable {
D.nameVariable = showSDoc dflags (ppr x)
, D.typeVariable = "not yet supported tything."
, D.valueVariable = "not yet supported tything."
, D.evaluateNameVariable = Just (showSDoc dflags (ppr x))
, D.variablesReferenceVariable = 0
}
-- |
-- Term https://hackage.haskell.org/package/ghc-8.2.1/docs/RtClosureInspect.html
--
withTerm :: GHC.Id -> Term -> G.GHCi D.Variable
withTerm i t@(Term ty _ _ _) = do
dflags <- getDynFlags
-- liftIO $ putStrLn $ "[DAP][DEBUG]" ++ " subTerms. [" ++ show (length subTerms) ++ "]"
termSDoc <- gcatch (showTerm t) showTermErrorHandler
let nameStr = showSDoc dflags (ppr i)
typeStr = showSDoc dflags (pprTypeForUser ty)
valStr = showSDoc dflags termSDoc
return D.defaultVariable {
D.nameVariable = nameStr
, D.typeVariable = typeStr
, D.valueVariable = valStr
, D.evaluateNameVariable = Just nameStr
, D.variablesReferenceVariable = 0
}
withTerm i _ = do
dflags <- getDynFlags
idSDoc <- pprTypeAndContents i
let (nameStr, typeStr, valStr) = getNameTypeValue (showSDoc dflags idSDoc)
return D.defaultVariable {
D.nameVariable = nameStr
, D.typeVariable = typeStr
, D.valueVariable = valStr
, D.evaluateNameVariable = Just nameStr
, D.variablesReferenceVariable = 0
}
-- |
--
getNextIdx :: MVar DAPContext -> Term -> String -> G.GHCi Int
getNextIdx ctxMVar t@(Term ty _ _ subTerms) str = getDynFlags >>= withDynFlags
where
withDynFlags dflags
| 0 == length subTerms = return 0
| 1 == length subTerms && isPrim (head subTerms) = return 0
| "[Char]" == showSDoc dflags (pprTypeForUser ty) = return 0
| "String" == showSDoc dflags (pprTypeForUser ty) = return 0
| otherwise = liftIO $ addTerm2VariableReferenceMap ctxMVar t str
getNextIdx ctxMVar t str = liftIO $ addTerm2VariableReferenceMap ctxMVar t str
-- |
--
addTerm2VariableReferenceMap :: MVar DAPContext -> Term -> String -> IO Int
addTerm2VariableReferenceMap ctxMVar t str = do
ctx <- takeMVar ctxMVar
let curMap = variableReferenceMapDAPContext ctx
nextId = (M.size curMap) + 2
putMVar ctxMVar $ ctx {variableReferenceMapDAPContext = M.insert (show nextId) (t, str) curMap}
return nextId
-- |
--
getDataConstructor :: Term -> G.GHCi String
getDataConstructor (Term _ (Left dc) _ _) = return dc
getDataConstructor (Term _ (Right dc) _ _) = do
dflags <- getDynFlags
let conStr = if isTupleDataCon dc then "Tuple" else showSDoc dflags $ ppr $ dataConName dc
conStr' = if ":" == conStr then "List" else conStr
typeStr = showSDoc dflags (pprTypeForUser (dataConRepType dc))
return $ conStr' ++ " :: " ++ typeStr
getDataConstructor _ = return "[getDataConstructor] not supported type."
-- |
--
showTermErrorHandler :: SomeException -> G.GHCi SDoc
showTermErrorHandler e = return $ text $ show e
-- |
--
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_
-- |
--
getBindigVariablesNode :: MVar DAPContext -> String -> G.GHCi [D.Variable]
getBindigVariablesNode ctxMVar idStr = do
ctx <- liftIO $ readMVar ctxMVar
case M.lookup idStr (variableReferenceMapDAPContext ctx) of
Just (t, str) -> withTerm t str
Nothing -> do
liftIO $ putStrLn $ "[DAP][ERROR][getBindigVariablesNode] id not found. " ++ idStr
return []
where
withTerm (Term _ (Right dc) _ subTerms) str = do
let labels = if 0 == length (dataConFieldLabels dc)
then map (\i->"_" ++ show i) [1..(length subTerms)]
else map (unpackFS . flLabel) (dataConFieldLabels dc)
mapM (withSubTerm str) $ zip labels subTerms
withTerm (Term _ (Left _) _ subTerms) str = do
let labels = map (\i->"_" ++ show i) [1..(length subTerms)]
mapM (withSubTerm str) $ zip labels subTerms
withTerm _ _ = do
liftIO $ putStrLn $ "[DAP][ERROR][getBindigVariablesNode] invalid map term type. " ++ idStr
return []
withSubTerm evalStr (label, t@(Term ty _ _ _)) = do
-- liftIO $ putStrLn $ "[DEBUG]" ++ " subTerms. [" ++ show (length subTerms) ++ "]"
termSDoc <- gcatch (showTerm t) showTermErrorHandler
dflags <- getDynFlags
let nameStr = label
typeStr = showSDoc dflags (pprTypeForUser ty)
valStr = showSDoc dflags termSDoc
nextIdx <- getNextIdx ctxMVar t evalStr
valStr' <- if 0 == nextIdx then return valStr
else getDataConstructor t
return D.defaultVariable {
D.nameVariable = nameStr
, D.typeVariable = typeStr
, D.valueVariable = valStr'
, D.evaluateNameVariable = Just evalStr
, D.variablesReferenceVariable = nextIdx
}
withSubTerm evalStr (label, (Prim ty val)) = do
dflags <- getDynFlags
return D.defaultVariable {
D.nameVariable = label
, D.typeVariable = showSDoc dflags (pprTypeForUser ty)
, D.valueVariable = showSDoc dflags (ppr val)
, D.evaluateNameVariable = Just evalStr
, D.variablesReferenceVariable = 0
}
withSubTerm evalStr (label, (Suspension _ ty _ _)) = do
dflags <- getDynFlags
return D.defaultVariable {
D.nameVariable = label
, D.typeVariable = showSDoc dflags (pprTypeForUser ty)
, D.valueVariable = "function :: " ++ showSDoc dflags (pprTypeForUser ty)
, D.evaluateNameVariable = Just evalStr
, D.variablesReferenceVariable = 0
}
withSubTerm evalStr (label, _) = do
return D.defaultVariable {
D.nameVariable = label
, D.typeVariable = "not supported subTerm."
, D.valueVariable = "not supported subTerm."
, D.evaluateNameVariable = Just evalStr
, D.variablesReferenceVariable = 0
}
------------------------------------------------------------------------------------------------
-- DAP Command :dap-force
------------------------------------------------------------------------------------------------
-- |
--
--
dapForceCommand :: MVar DAPContext -> String -> InputT G.GHCi Bool
dapForceCommand ctx valStr = do
body <- lift $ getForceEvalBody ctx valStr
let outStr = _DAP_HEADER ++ (show body)
liftIO $ putStrLn outStr
return False
-- |
--
parseNameErrorHandler :: SomeException -> G.GHCi [GHC.Name]
parseNameErrorHandler e = liftIO $ print e >> return []
-- |
--
--
getForceEvalBody :: MVar DAPContext -> String -> G.GHCi D.EvaluateBody
getForceEvalBody ctxMVar nameStr =
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) = do
let isForce = True
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
nextIdx <- getNextIdx ctxMVar t nameStr
valStr' <- if 0 == nextIdx then return valStr
else getDataConstructor t
return D.defaultEvaluateBody {
D.resultEvaluateBody = valStr'
, D.typeEvaluateBody = typeStr
, D.variablesReferenceEvaluateBody = nextIdx
}
withTerm i _ = do
dflags <- getDynFlags
idSDoc <- pprTypeAndContents i
let (_, typeStr, valStr) = getNameTypeValue (showSDoc dflags idSDoc)
return D.defaultEvaluateBody {
D.resultEvaluateBody = valStr
, D.typeEvaluateBody = typeStr
, D.variablesReferenceEvaluateBody = 0
}
------------------------------------------------------------------------------------------------
-- DAP Command :dap-scopes
------------------------------------------------------------------------------------------------
-- |
--
dapScopesCommand :: MVar DAPContext -> String -> InputT G.GHCi Bool
dapScopesCommand ctx idxStr = do
vals <- lift $ getScopesBody ctx idxStr
let outStr = _DAP_HEADER ++ (show vals)
liftIO $ putStrLn outStr
return False
-- |
--
getScopesBody :: MVar DAPContext -> String -> G.GHCi D.ScopesBody
getScopesBody ctxMVar frameIdStr
| all isDigit frameIdStr = do
-- liftIO $ putStrLn $ "[DAP][getScopesBody] frame id." ++ frameIdStr
oldIdx <- liftIO $ frameIdDAPContext <$> readMVar ctxMVar
let curIdx = read frameIdStr
moveIdx = curIdx - oldIdx
tyThings <- withMoveIdx moveIdx
-- liftIO $ putStrLn $ "[DAP][getScopesBody] tyThings count." ++ show (length tyThings)
ctx <- liftIO $ takeMVar ctxMVar
liftIO $ putMVar ctxMVar ctx {
variableReferenceMapDAPContext = M.empty
, bindingDAPContext = tyThings
, frameIdDAPContext = curIdx
}
return D.ScopesBody {
D.scopesScopesBody = [
D.defaultScope{
D.nameScope = _GHCi_SCOPE
, D.variablesReferenceScope = 1
, D.namedVariablesScope = Nothing
, D.indexedVariablesScope = Nothing
, D.expensiveScope = False
}
]
}
| otherwise = do
liftIO $ putStrLn $ "[DAP][ERROR][getScopesBody] invalid frame id." ++ frameIdStr
return D.ScopesBody {
D.scopesScopesBody = [D.defaultScope{D.nameScope = "invalid frame id." ++ frameIdStr}]
}
where
-- |
--
withMoveIdx moveIdx
| 0 == moveIdx = GHC.getBindings
| 0 < moveIdx = back moveIdx
| otherwise = forward moveIdx
-- |
--
back num = do
(names, _, _, _) <- GHC.back num
st <- G.getGHCiState
enqueueCommands [G.stop st]
foldM withName [] $ reverse names
-- |
--
forward num = do
(names, _, _, _) <- GHC.forward num
st <- G.getGHCiState
enqueueCommands [G.stop st]
foldM withName [] $ reverse names
-- |
--
enqueueCommands :: [String] -> G.GHCi ()
enqueueCommands cmds = do
-- make sure we force any exceptions in the commands while we're
-- still inside the exception handler, otherwise bad things will
-- happen (see #10501)
cmds `deepseq` return ()
G.modifyGHCiState $ \st -> st{ G.cmdqueue = cmds ++ G.cmdqueue st }
-- |
--
withName acc n = GHC.lookupName n >>= \case
Just ty -> return (ty : acc)
Nothing -> do
dflags <- getDynFlags
liftIO $ putStrLn $ "[DAP][ERROR][getScopesBody] variable not found. " ++ showSDoc dflags (ppr n)
return acc