I'm writing a REPL in Haskell. The basic idea looks like this:
repl :: IO ()
repl = do putStr ">> "
hFlush stdout
input <- getLine
...
repl
I'd like to mimic some GHCi behaviour that when CTRL-C is pressed, the REPL discards the current line and starts a new empty line for user input. It has come to my mind that SIGINT raises UserInterrupt of AsyncException, which can be handled by catch. Therefore some modifications on the input line:
repl :: IO ()
repl = do putStr ">> "
hFlush stdout
input <- getLine `catch` handleCC
repl
handleCC :: AsyncException -> IO String
handleCC _ = do putStr "\n>> "
hFlush stdout
getLine `catch` handleCC
Within handleCC, AsyncException will be handled once more by recursion, so surely I can interrupt the REPL infinite times, right...?
Of course I can't; a second CTRL-C still terminates the REPL. But why?
>> something^C
>> another^C
[Process exited 130]
After digging deeper, I believe I can conclude that
catchdoes not properly handle asynchronous exceptions, neithertryor similar.maskseems to be a potential solution but I found its usage a bit of sophisticated even with the documentation.This answer provides a working solution by installing a persistent signal handler using the POSIX API. Integrated with my code: