PreludeIO

-- | This module implements the IO operations that are specified in
-- The Haskell 98 Report and exported from the Prelude, but it is not
-- part of Haskell 98 and should not be directly imported in programs
-- that should be portable to other implementations of Haskell 98.
-- <https://www.altocumulus.org/haskell98-report-html/standard-prelude.html#sect8.3>
module PreludeIO(
    -- * IO monad
    IO(..), -- should be abstract
    -- * StdIO
    putChar, putStr, putStrLn, print,
    getChar, getLine, readIO, readLn,
    -- * Lazy IO
    getContents, interact,
    -- * File IO
    FilePath, readFile, writeFile, appendFile,
    -- * IO errors and error handling
    IOError(..), -- should be abstract
    ioError, userError, catch,
    -- * Extra (not part of Haskell 98)
    IOErrorType(..), ioeAddPath
  ) where
import Prelude
import MonadicIO0
import Monad(liftM,MonadPlus(..))

type  FilePath = String

data IOErrorType = EOFError          
                 | OtherError
                 | UserError
                 | AlreadyExistsError 
                 | DoesNotExistError 
                 | AlreadyInUseError 
                 | FullError         
                 | IllegalOperation  
                 | PermissionError
                 | ThreadDone
                 deriving (Eq,Show)

data IOError = IOE IOErrorType String deriving (Eq)

instance  Show IOError  where show (IOE k s) = "IO error: "++s

userError        ::  String -> IOError
userError        =   IOE UserError

newtype IO a = IO {unIO::(a->Main)->(IOError->Main)->Main}
-- Note: Main is the empty type, functions of type t->Main never return

-- Not in Haskell 98:
returnIO x = IO $ \ ok err -> ok x
fmapIO f (IO m) = IO $ \ ok err -> m (ok . f) err
bindIO (IO m1) xm2 = IO $ \ ok err -> m1 (\x->unIO (xm2 x) ok err) err
thenIO (IO m1) m2  = IO $ \ ok err -> m1 (\x->unIO m2      ok err) err
--thenIO m1 m2 = bindIO m1 (const m2)
failIO = ioError . userError
apIO (IO mf) (IO mx) = IO $ \ ok err -> mf (\f->mx (\x->ok (f x)) err) err

ioeAddPath :: IO a -> FilePath -> IO a
io `ioeAddPath` path = io `catch` \ (IOE k s) -> ioError (IOE k (path++": "++s))

instance Monad IO where
  return = returnIO
  (>>=) = bindIO
  (>>) = thenIO
--fail s = error s -- agrees with the original Haskell 98 report, appendix A
  fail = failIO    -- agrees with the revised  Haskell 98 report, section  8
--ap = apIO

-- Not in Haskell 98:
instance MonadPlus IO where
  mzero = fail "mzero"
  io1 `mplus` io2 = io1 `catch` const io2

instance Functor IO where fmap = fmapIO

ioError e = IO $ \ ok err -> err e

catch :: IO a -> (IOError -> IO a) -> IO a
catch (IO m) h = IO $ \ ok err -> m ok $ \ e -> unIO (h e) ok err

putChar          :: Char -> IO ()
putChar c        =  primPutChar c

putStr           :: String -> IO ()
putStr           =  primPutStr

putStrLn         :: String -> IO ()
putStrLn s       =  do putStr s
                       putChar '\n'

print            :: Show a => a -> IO ()
print x          =  putStrLn (show x)

getChar          :: IO Char
getChar          = primGetChar

getLine          :: IO String
getLine          =  do c <- getChar
                       if c == '\n' then return "" else 
                          do s <- getLine
                             return (c:s)

getContents      :: IO String
getContents      =  primGetContents

interact         ::  (String -> String) -> IO ()
interact f       =  putStr . f =<< getContents

readFile         :: FilePath -> IO String
readFile path    =  primReadFile path `ioeAddPath` path

writeFile        :: FilePath -> String -> IO ()
writeFile path s =  primWriteFile path s
                    `ioeAddPath` path

appendFile       :: FilePath -> String -> IO ()
appendFile path s = primAppendFile path s
                    `ioeAddPath` path

  -- raises an exception instead of an error
readIO   :: Read a => String -> IO a
readIO s =  case [x | (x,t) <- reads s, ("","") <- lex t] of
              [x] -> return x
              []  -> ioError (userError "Prelude.readIO: no parse")
              _   -> ioError (userError "Prelude.readIO: ambiguous parse")

readLn           :: Read a => IO a
readLn           =  readIO =<< getLine