Fungi-1.0: Debug/Debug.hs
module Debug.Debug (
runDebugger
) where
import Control.Monad.State.Strict
import Data.Char (ord)
import qualified Data.Deque as Deque
import qualified Data.History as History
import Data.List (intercalate, genericReplicate, stripPrefix, isPrefixOf, genericTake)
import Data.Maybe (fromMaybe)
import qualified Data.Set as Set
import Data.Vector
import Debug.Debugger hiding (runDebugger)
import Space.Cell
import Space.Space
import System.Exit (exitSuccess)
import System.IO (stdin, stdout, hFlush, hGetLine)
import System.IO.Buffering (withBuffering, BufferMode (..))
import Text.Help.Debug (help)
import Text.PrettyShow
import Env
import Instruction
import Ip
-----------------------------------------------------------
updateHistory :: (I i) => Env i -> Env i
updateHistory env = withHistory' (History.push envNoHist) env
where
withHistory' = withDebugger . withHistory
envNoHist = withHistory' (const $ History.empty 0) env
runDebugger :: (I i) => Instruction i ()
runDebugger = runDebugger' >> modify updateHistory
runDebugger' :: (I i) => Instruction i ()
runDebugger' = do
env <- get
let dim = getDim env
toss = currentToss env
debugger = getDebugger env
breakPoints = getBreakPoints debugger
watchExprs = getWatchExprs debugger
currPos = getPos $ currentIp env
case getDebugMode debugger of
DebugOff -> return ()
DebugContinue
| genericTake dim (unVector currPos) `Set.member` breakPoints -> stepDebug >> go
| any (`isPrefixOf` Deque.toList toss) $ Set.toList watchExprs -> stepDebug >> go
| otherwise -> return ()
DebugStep -> go
where
go :: (I i) => Instruction i ()
go = do
liftIO $ putChar '\n'
cellDebug
ipDebug
sDebug
gets (getLocaleRads . getDebugger) >>= localeDebug . Just . mapPair fromIntegral
fetchDebugCommand
fetchDebugCommand :: (I i) => Instruction i ()
fetchDebugCommand = do
cmd <- liftIO $ do
putStr "> "
hFlush stdout
withBuffering LineBuffering stdin hGetLine
case delimWords "'\"" cmd of
Nothing -> do
liftIO $ putStrLn $ "Unknown command: " ++ cmd
fetchDebugCommand
Just cmd' -> case mapHead (dropWhile (== '-')) cmd' of
[] -> return ()
"?" : []
-> helpDebug >> fetchDebugCommand
"back" : []
-> backDebug "1"
"back" : arg : []
-> backDebug arg
"break" : args
-> breakDebug args >> fetchDebugCommand
"breaks" : []
-> breaksDebug >> fetchDebugCommand
"cell" : []
-> cellDebug >> fetchDebugCommand
"cellat" : args
-> cellatDebug args >> fetchDebugCommand
"clear" : args
-> clearDebug args >> fetchDebugCommand
"continue" : []
-> continueDebug
"dim" : []
-> dimDebug >> fetchDebugCommand
"exit" : []
-> quitDebug
"help" : []
-> helpDebug >> fetchDebugCommand
"ip" : []
-> ipDebug >> fetchDebugCommand
"locale" : []
-> localeDebug Nothing >> fetchDebugCommand
"locale" : rad : []
-> localeDebug (fmap duplicate $ tryRead rad) >> fetchDebugCommand
"locale" : xrad : yrad : []
-> localeDebug (liftM2 (,) (tryRead xrad) (tryRead yrad)) >> fetchDebugCommand
"nodebug" : []
-> nodebugDebug
"pop" : arg : []
-> popDebug arg >> fetchDebugCommand
"push" : args
-> pushDebug args >> fetchDebugCommand
"quit" : []
-> quitDebug
"record" : arg : []
-> recordDebug arg >> fetchDebugCommand
"setpos" : args
-> setposDebug args >> fetchDebugCommand
"space" : []
-> spaceDebug >> fetchDebugCommand
"s" : []
-> sDebug >> fetchDebugCommand
"setlocale" : xrad : yrad : []
-> setlocaleDebug xrad yrad >> fetchDebugCommand
"ss" : []
-> ssDebug >> fetchDebugCommand
"step" : []
-> stepDebug >> fetchDebugCommand
"unshowable" : arg : []
-> unshowableDebug arg >> fetchDebugCommand
"unwatch" : args
-> unwatchDebug args >> fetchDebugCommand
"watch" : args
-> watchDebug args >> fetchDebugCommand
"watches" : []
-> watchesDebug >> fetchDebugCommand
_
-> liftIO (putStrLn $ "Unknown command: " ++ cmd) >> fetchDebugCommand
mapPair :: (a -> b) -> (a, a) -> (b, b)
mapPair f (x, y) = (f x, f y)
duplicate :: a -> (a, a)
duplicate x = (x, x)
mapHead :: (a -> a) -> [a] -> [a]
mapHead _ [] = []
mapHead f (x:xs) = f x : xs
delimWords :: String -> String -> Maybe [String]
delimWords delims str = fmap (reverse . dropWhile (== "") . reverse) $ delimWords' delims str
delimWords' :: String -> String -> Maybe [String]
delimWords' _ "" = Just [""]
delimWords' delims (c:cs)
| c == ' ' = fmap ("" :) $ delimWords' delims $ dropWhile (== ' ') cs
| c `elem` delims = fmap (mapHead (\str -> c : takeWhile (/= c) cs ++ str ++ [c])) $ delimWords'' c cs
| otherwise = fmap (mapHead (c:)) $ delimWords' delims cs
where
delimWords'' currDelim = delimWords' delims <=< stripPrefix [currDelim] . dropWhile (/= currDelim)
-----------------------------------------------------------
readVector :: (I i) => [String] -> Maybe (Vector i)
readVector [] = Nothing
readVector strs = case tryReadList strs of
Nothing -> Nothing
Just ns -> Just $ mkVector $ ns ++ repeat 0
tryRead :: (Read a) => String -> Maybe a
tryRead str = case reads str of
[(x, "")] -> Just x
_ -> Nothing
tryReadList :: (Read a) => [String] -> Maybe [a]
tryReadList [] = Just []
tryReadList (str:strs) = tryRead str >>= \x -> fmap (x :) $ tryReadList strs
readCells :: (I i) => [String] -> [Maybe i]
readCells [] = []
readCells (str:strs) = case str of
'\'' : str' -> readString '\'' str'
'"' : str' -> readString '"' str'
"" -> readCells strs
_ -> case tryRead str of
Just n -> Just n : readCells strs
Nothing -> case str of
[c] -> (Just . fromIntegral . ord) c : readCells strs
_ -> Nothing : readCells strs
where
readString quote s = case sequence (readString' quote s) of
Nothing -> Nothing : readCells strs
Just cells -> cells ++ readCells strs
--
readString' _ "" = [Nothing]
readString' quote (c:cs)
| c == quote = case cs of
"" -> []
_ -> [Nothing]
| otherwise = (Just . Just . fromIntegral . ord) c : readString' quote cs
-----------------------------------------------------------
invalidVector :: Instruction i ()
invalidVector = liftIO $ putStrLn "Invalid vector."
invalidArgument :: Instruction i ()
invalidArgument = liftIO $ putStrLn "Invalid argument."
-----------------------------------------------------------
backDebug :: (I i) => String -> Instruction i ()
backDebug arg = case tryRead arg of
Nothing -> invalidArgument
Just n -> if n <= 0
then invalidArgument >> fetchDebugCommand
else do
hist <- gets $ getHistory . getDebugger
case History.lookup (n - 1) hist of
Nothing -> do
liftIO $ putStrLn "Not enough history. Try again."
fetchDebugCommand
Just env' -> do
let hist' = History.popN n hist
put env'
modify $ withDebugger $ \d -> d { getDebugMode = DebugStep, getHistory = hist' }
runDebugger'
breakDebug :: (I i) => [String] -> Instruction i ()
breakDebug args = case readVector args of
Nothing -> invalidVector
Just pos -> modify $ addBreakPoint $ unVector pos
breaksDebug :: (I i) => Instruction i ()
breaksDebug = do
breakPoints <- gets $ Set.toList . getBreakPoints . getDebugger
liftIO $ mapM_ pprint breakPoints
cellDebug :: (I i) => Instruction i ()
cellDebug = gets currentCell >>= liftIO . pprint
cellatDebug :: (I i) => [String] -> Instruction i ()
cellatDebug args = case readVector args of
Nothing -> invalidVector
Just pos -> do
dim <- gets getDim
gets getSpace >>= liftIO . pprint . flip cellAt (takeV dim $ pos `append` 0)
clearDebug :: (I i) => [String] -> Instruction i ()
clearDebug args
| args == ["*"] = modify $ withDebugger $ withBreakPoints $ const Set.empty
| otherwise = case readVector args of
Nothing -> invalidVector
Just pos -> modify $ removeBreakPoint $ unVector pos
continueDebug :: (I i) => Instruction i ()
continueDebug = modify $ withDebugger $ \d -> d { getDebugMode = DebugContinue }
dimDebug :: (I i) => Instruction i ()
dimDebug = gets getDim >>= liftIO . pprint
helpDebug :: Instruction i ()
helpDebug = liftIO help
ipDebug :: (I i) => Instruction i ()
ipDebug = gets currentIp >>= liftIO . pprint
localeDebug :: (I i) => Maybe (Int, Int) -> Instruction i ()
localeDebug mrads = do
env <- get
let unshowableCellChar = getUnshowableChar env
dim = getDim env
s = getSpace env
pos = getPos . currentIp $ env
defaultRads = getLocaleRads $ getDebugger env
(xrad, yrad) = mapPair (fromIntegral . min 666 . max 1) $ fromMaybe defaultRads mrads
top = "+" ++ rep xrad '-' ++ "v" ++ rep xrad '-' ++ "+"
bot = "+" ++ rep xrad '-' ++ "^" ++ rep xrad '-' ++ "+"
topRows = intercalate "\n" ["|" ++ chars2 [[x, y] | x <- [-xrad .. xrad]] ++ "|" | y <- [-yrad .. -1]]
midRow = ">" ++ chars1 [[x, 0] | x <- [-xrad .. xrad]] ++ "<"
botRows = intercalate "\n" ["|" ++ chars2 [[x, y] | x <- [-xrad .. xrad]] ++ "|" | y <- [1 .. yrad]]
str = unlines [top, topRows, midRow, botRows, bot]
disp xs = takeV dim $ pos + mkVector xs
chars1 = map (showCell . cellAt s . disp)
chars2 = if dim >= 2
then chars1
else const . rep (2 * xrad + 1) $ ' '
showCell = fromMaybe unshowableCellChar . cellToPrintableChar
liftIO $ putStrLn str
where
rep = genericReplicate
nodebugDebug :: (I i) => Instruction i ()
nodebugDebug = modify $ withDebugger $ \d -> d { getDebugMode = DebugOff }
popDebug :: (I i) => String -> Instruction i ()
popDebug arg = case tryRead arg :: Maybe Integer of
Nothing -> invalidArgument
Just n -> popNInstr_ n
pushDebug :: (I i) => [String] -> Instruction i ()
pushDebug args = case sequence $ readCells args of
Nothing -> invalidArgument
Just cells -> pushVectorInstr $ mkVector $ reverse cells
quitDebug :: (I i) => Instruction i ()
quitDebug = liftIO exitSuccess
recordDebug :: (I i) => String -> Instruction i ()
recordDebug arg = case tryRead arg of
Nothing -> invalidArgument
Just n -> modify $ withDebugger $ withHistory $ History.fromHistory $ Just n
setposDebug :: (I i) => [String] -> Instruction i ()
setposDebug args = case readVector args of
Nothing -> invalidVector
Just pos -> setPosInstr pos
spaceDebug :: (I i) => Instruction i ()
spaceDebug = gets getSpace >>= liftIO . pprint
sDebug :: (I i) => Instruction i ()
sDebug = do
toss <- gets currentToss
let ptInt = pshow toss
ptGuts = id
. words
. map (\c -> if c == ',' then ' ' else c)
. reverse
. drop 3
. reverse
. drop 1
$ ptInt
ptChar = "[" ++ intercalate "," (map (showChr . read) ptGuts) ++ "..]"
liftIO $ putStrLn ptChar >> putStrLn ptInt
where
showChr :: Integer -> String
showChr n = maybe "\\???" show $ cellToChar $ chrCell n
setlocaleDebug :: (I i) => String -> String -> Instruction i ()
setlocaleDebug xrad yrad = case tryRead xrad of
Nothing -> invalidArgument
Just xrad' -> case tryRead yrad of
Nothing -> invalidArgument
Just yrad' -> modify $ withDebugger $ \d -> d {
getLocaleRads = (xrad', yrad')
}
ssDebug :: (I i) => Instruction i ()
ssDebug = gets currentSs >>= liftIO . pprint
stepDebug :: (I i) => Instruction i ()
stepDebug = modify $ withDebugger $ \d -> d { getDebugMode = DebugStep }
unshowableDebug :: (I i) => String -> Instruction i ()
unshowableDebug str = case str of
[c] -> modify $ \env -> env { getUnshowableChar = c }
_ -> invalidArgument
unwatchDebug :: (I i) => [String] -> Instruction i ()
unwatchDebug args = case sequence $ readCells args of
Nothing -> invalidArgument
Just cells -> modify $ withDebugger $ withWatchExprs $ Set.delete cells
watchDebug :: (I i) => [String] -> Instruction i ()
watchDebug args = case sequence $ readCells args of
Nothing -> invalidArgument
Just cells -> modify $ withDebugger $ withWatchExprs $ Set.insert cells
watchesDebug :: (I i) => Instruction i ()
watchesDebug = gets (Set.toList . getWatchExprs . getDebugger) >>= liftIO . mapM_ pprint