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
catch
does not properly handle asynchronous exceptions, neithertry
or similar.mask
seems 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: