-%
-% (c) The AQUA Project, Glasgow University, 1994-1996
+% -----------------------------------------------------------------------------
+% $Id: PrelIOBase.lhs,v 1.9 1999/04/27 17:41:19 sof Exp $
+%
+% (c) The AQUA Project, Glasgow University, 1994-1998
%
\section[PrelIOBase]{Module @PrelIOBase@}
concretely; the @IO@ module itself exports abstractly.
\begin{code}
-{-# OPTIONS -fno-implicit-prelude #-}
-#include "error.h"
+{-# OPTIONS -fno-implicit-prelude -#include "cbits/stgio.h" #-}
+#include "cbits/error.h"
+#ifndef __HUGS__ /* Hugs just includes this in PreludeBuiltin so no header needed */
module PrelIOBase where
import {-# SOURCE #-} PrelErr ( error )
+
import PrelST
-import PrelTup
-import PrelMaybe
-import PrelAddr
-import PrelPack ( unpackCString )
import PrelBase
-import PrelArr ( ByteArray(..), MutableVar(..) )
-import PrelGHC
+import {-# SOURCE #-} PrelException ( ioError )
+import PrelST ( ST(..), STret(..) )
+import PrelMaybe ( Maybe(..) )
+import PrelAddr ( Addr(..), nullAddr )
+import PrelPack ( unpackCString )
+
+#if !defined(__CONCURRENT_HASKELL__)
+import PrelArr ( MutableVar, readVar )
+#endif
+#endif
+#ifdef __HUGS__
+#define cat2(x,y) x/**/y
+#define CCALL(fun) cat2(prim_,fun)
+#define __CONCURRENT_HASKELL__
+#define stToIO id
+#define unpackCString primUnpackString
+#else
+#define CCALL(fun) _ccall_ fun
+#define ref_freeStdFileObject (``&freeStdFileObject''::Addr)
+#endif
+
+#ifndef __PARALLEL_HASKELL__
+#define FILE_OBJECT ForeignObj
+#else
+#define FILE_OBJECT Addr
+#endif
\end{code}
%*********************************************************
%* *
%*********************************************************
-IO is no longer built on top of PrimIO (which used to be a specialised
-version of the ST monad), instead it is now has its own type. This is
-purely for efficiency purposes, since we get to remove several levels
-of lifting in the type of the monad.
+The IO Monad is just an instance of the ST monad, where the state is
+the real world. We use the exception mechanism (in PrelException) to
+implement IO exceptions.
\begin{code}
-newtype IO a = IO (State# RealWorld -> IOResult a)
+#ifndef __HUGS__
+newtype IO a = IO (State# RealWorld -> (# State# RealWorld, a #))
-{-# INLINE unIO #-}
+unIO :: IO a -> (State# RealWorld -> (# State# RealWorld, a #))
unIO (IO a) = a
-data IOResult a = IOok (State# RealWorld) a
- | IOfail (State# RealWorld) IOError
-
instance Functor IO where
- map f x = x >>= (return . f)
+ fmap f x = x >>= (return . f)
instance Monad IO where
{-# INLINE return #-}
{-# INLINE (>>) #-}
{-# INLINE (>>=) #-}
m >> k = m >>= \ _ -> k
- return x = IO $ \ s -> IOok s x
+ return x = IO $ \ s -> (# s, x #)
- (IO m) >>= k =
- IO $ \s ->
- case m s of
- IOfail new_s err -> IOfail new_s err
- IOok new_s a -> unIO (k a) new_s
+ m >>= k = bindIO m k
+ fail s = error s -- not ioError?
-fixIO :: (a -> IO a) -> IO a
-- not required but worth having around
+fixIO :: (a -> IO a) -> IO a
+fixIO m = stToIO (fixST (ioToST . m))
-fixIO k = IO $ \ s ->
- let
- (IO k_loop) = k loop
- result = k_loop s
- IOok _ loop = result
- in
- result
-
-fail :: IOError -> IO a
-fail err = IO $ \ s -> IOfail s err
+liftIO :: IO a -> State# RealWorld -> STret RealWorld a
+liftIO (IO m) = \s -> case m s of (# s', r #) -> STret s' r
-userError :: String -> IOError
-userError str = IOError Nothing UserError str
+bindIO :: IO a -> (a -> IO b) -> IO b
+bindIO (IO m) k = IO ( \ s ->
+ case m s of
+ (# new_s, a #) -> unIO (k a) new_s
+ )
-catch :: IO a -> (IOError -> IO a) -> IO a
-catch (IO m) k = IO $ \ s ->
- case m s of
- IOok new_s a -> IOok new_s a
- IOfail new_s e -> unIO (k e) new_s
-
-instance Show (IO a) where
- showsPrec p f = showString "<<IO action>>"
- showList = showList__ (showsPrec 0)
+#endif
\end{code}
%*********************************************************
%*********************************************************
\begin{code}
-stToIO :: ST RealWorld a -> IO a
-stToIO (ST m) = IO $ \ s -> case (m s) of STret new_s r -> IOok new_s r
-
-ioToST :: IO a -> ST RealWorld a
-ioToST (IO io) = ST $ \ s ->
- case (io s) of
- IOok new_s a -> STret new_s a
- IOfail new_s e -> error ("I/O Error (ioToST): " ++ showsPrec 0 e "\n")
+#ifdef __HUGS__
+/* Hugs doesn't distinguish these types so no coercion required) */
+#else
+stToIO :: ST RealWorld a -> IO a
+stToIO (ST m) = (IO m)
+
+ioToST :: IO a -> ST RealWorld a
+ioToST (IO m) = (ST m)
+#endif
\end{code}
%*********************************************************
%* *
-\subsection{Utility functions}
+\subsection{Unsafe @IO@ operations}
%* *
%*********************************************************
-I'm not sure why this little function is here...
-
\begin{code}
-fputs :: Addr{-FILE*-} -> String -> IO Bool
-
-fputs stream [] = return True
+#ifndef __HUGS__
+{-# NOINLINE unsafePerformIO #-}
+unsafePerformIO :: IO a -> a
+unsafePerformIO (IO m) = case m realWorld# of (# _, r #) -> r
-fputs stream (c : cs)
- = _ccall_ stg_putc c stream >> -- stg_putc expands to putc
- fputs stream cs -- (just does some casting stream)
+unsafeInterleaveIO :: IO a -> IO a
+unsafeInterleaveIO = stToIO . unsafeInterleaveST . ioToST
+#endif
\end{code}
-
%*********************************************************
%* *
\subsection{Type @IOError@}
(Maybe Handle) -- the handle used by the action flagging the
-- the error.
IOErrorType -- what it was.
+ String -- location
String -- error type specific information.
| TimeExpired | UnsatisfiedConstraints
| UnsupportedOperation | UserError
| EOF
- deriving (Eq, Show)
+#ifdef _WIN32
+ | ComError Int -- HRESULT
+#endif
+ deriving (Eq)
+
+instance Show IOErrorType where
+ showsPrec _ e =
+ showString $
+ case e of
+ AlreadyExists -> "already exists"
+ HardwareFault -> "hardware fault"
+ IllegalOperation -> "illegal operation"
+ InappropriateType -> "inappropriate type"
+ Interrupted -> "interrupted"
+ InvalidArgument -> "invalid argument"
+ NoSuchThing -> "does not exist"
+ OtherError -> "failed"
+ PermissionDenied -> "permission denied"
+ ProtocolError -> "protocol error"
+ ResourceBusy -> "resource busy"
+ ResourceExhausted -> "resource exhausted"
+ ResourceVanished -> "resource vanished"
+ SystemError -> "system error"
+ TimeExpired -> "timeout"
+ UnsatisfiedConstraints -> "unsatisified constraints" -- ultra-precise!
+ UserError -> "failed"
+ UnsupportedOperation -> "unsupported operation"
+ EOF -> "end of file"
+#ifdef _WIN32
+ ComError _ -> "COM error"
+#endif
+
+
+userError :: String -> IOError
+userError str = IOError Nothing UserError "" str
\end{code}
Predicates on IOError; little effort made on these so far...
\begin{code}
-isAlreadyExistsError (IOError _ AlreadyExists _) = True
-isAlreadyExistsError _ = False
+isAlreadyExistsError :: IOError -> Bool
+isAlreadyExistsError (IOError _ AlreadyExists _ _) = True
+isAlreadyExistsError _ = False
-isAlreadyInUseError (IOError _ ResourceBusy _) = True
-isAlreadyInUseError _ = False
+isAlreadyInUseError :: IOError -> Bool
+isAlreadyInUseError (IOError _ ResourceBusy _ _) = True
+isAlreadyInUseError _ = False
-isFullError (IOError _ ResourceExhausted _) = True
-isFullError _ = False
+isFullError :: IOError -> Bool
+isFullError (IOError _ ResourceExhausted _ _) = True
+isFullError _ = False
-isEOFError (IOError _ EOF _) = True
-isEOFError _ = True
+isEOFError :: IOError -> Bool
+isEOFError (IOError _ EOF _ _) = True
+isEOFError _ = False
-isIllegalOperation (IOError _ IllegalOperation _) = True
-isIllegalOperation _ = False
+isIllegalOperation :: IOError -> Bool
+isIllegalOperation (IOError _ IllegalOperation _ _) = True
+isIllegalOperation _ = False
-isPermissionError (IOError _ PermissionDenied _) = True
-isPermissionError _ = False
+isPermissionError :: IOError -> Bool
+isPermissionError (IOError _ PermissionDenied _ _) = True
+isPermissionError _ = False
-isDoesNotExistError (IOError _ NoSuchThing _) = True
-isDoesNotExistError _ = False
+isDoesNotExistError :: IOError -> Bool
+isDoesNotExistError (IOError _ NoSuchThing _ _) = True
+isDoesNotExistError _ = False
-isUserError (IOError _ UserError _) = True
-isUserError _ = False
+isUserError :: IOError -> Bool
+isUserError (IOError _ UserError _ _) = True
+isUserError _ = False
\end{code}
Showing @IOError@s
\begin{code}
+#ifdef __HUGS__
+-- For now we give a fairly uninformative error message which just happens to
+-- be like the ones that Hugs used to give.
instance Show IOError where
- showsPrec p (IOError _ UserError s) rs =
- showString s rs
-{-
- showsPrec p (IOError _ EOF _) rs =
- showsPrec p EOF rs
--}
- showsPrec p (IOError _ iot s) rs =
- showsPrec p
- iot
- (case s of {
- "" -> rs;
- _ -> showString ": " $
- showString s rs})
+ showsPrec p (IOError hdl iot loc s) = showString s . showChar '\n'
+#else
+instance Show IOError where
+ showsPrec p (IOError hdl iot loc s) =
+ showsPrec p iot .
+ showChar '\n' .
+ (case loc of
+ "" -> id
+ _ -> showString "Action: " . showString loc . showChar '\n') .
+ showHdl .
+ (case s of
+ "" -> id
+ _ -> showString "Reason: " . showString s)
+ where
+ showHdl =
+ case hdl of
+ Nothing -> id
+ Just h -> showString "Handle: " . showsPrec p h
+#endif
\end{code}
The @String@ part of an @IOError@ is platform-dependent. However, to
explicitly mentioned in the standard, any descriptive string may be
used.
-\begin{change}
-SOF & 4/96 & added argument to indicate function that flagged error
-\end{change}
-% Hmm..does these envs work?!...SOF
-
\begin{code}
constructErrorAndFail :: String -> IO a
constructErrorAndFail call_site
= constructError call_site >>= \ io_error ->
- fail io_error
+ ioError io_error
+
+constructErrorAndFailWithInfo :: String -> String -> IO a
+constructErrorAndFailWithInfo call_site reason
+ = constructErrorMsg call_site (Just reason) >>= \ io_error ->
+ ioError io_error
\end{code}
return code of the external call), is to set the @ghc_errtype@
to a value that is one of the \tr{#define}s in @includes/error.h@.
@ghc_errstr@ holds a character string providing error-specific
-information.
+information. Error constructing functions will then reach out
+and grab these values when generating
\begin{code}
constructError :: String -> IO IOError
-constructError call_site =
- _casm_ ``%r = ghc_errtype;'' >>= \ (I# errtype#) ->
- _casm_ ``%r = ghc_errstr;'' >>= \ str ->
+constructError call_site = constructErrorMsg call_site Nothing
+
+constructErrorMsg :: String -> Maybe String -> IO IOError
+constructErrorMsg call_site reason =
+ CCALL(getErrType__) >>= \ errtype ->
+ CCALL(getErrStr__) >>= \ str ->
let
iot =
- case errtype# of
- ERR_ALREADYEXISTS# -> AlreadyExists
- ERR_HARDWAREFAULT# -> HardwareFault
- ERR_ILLEGALOPERATION# -> IllegalOperation
- ERR_INAPPROPRIATETYPE# -> InappropriateType
- ERR_INTERRUPTED# -> Interrupted
- ERR_INVALIDARGUMENT# -> InvalidArgument
- ERR_NOSUCHTHING# -> NoSuchThing
- ERR_OTHERERROR# -> OtherError
- ERR_PERMISSIONDENIED# -> PermissionDenied
- ERR_PROTOCOLERROR# -> ProtocolError
- ERR_RESOURCEBUSY# -> ResourceBusy
- ERR_RESOURCEEXHAUSTED# -> ResourceExhausted
- ERR_RESOURCEVANISHED# -> ResourceVanished
- ERR_SYSTEMERROR# -> SystemError
- ERR_TIMEEXPIRED# -> TimeExpired
- ERR_UNSATISFIEDCONSTRAINTS# -> UnsatisfiedConstraints
- ERR_UNSUPPORTEDOPERATION# -> UnsupportedOperation
- ERR_EOF# -> EOF
+ case (errtype::Int) of
+ ERR_ALREADYEXISTS -> AlreadyExists
+ ERR_HARDWAREFAULT -> HardwareFault
+ ERR_ILLEGALOPERATION -> IllegalOperation
+ ERR_INAPPROPRIATETYPE -> InappropriateType
+ ERR_INTERRUPTED -> Interrupted
+ ERR_INVALIDARGUMENT -> InvalidArgument
+ ERR_NOSUCHTHING -> NoSuchThing
+ ERR_OTHERERROR -> OtherError
+ ERR_PERMISSIONDENIED -> PermissionDenied
+ ERR_PROTOCOLERROR -> ProtocolError
+ ERR_RESOURCEBUSY -> ResourceBusy
+ ERR_RESOURCEEXHAUSTED -> ResourceExhausted
+ ERR_RESOURCEVANISHED -> ResourceVanished
+ ERR_SYSTEMERROR -> SystemError
+ ERR_TIMEEXPIRED -> TimeExpired
+ ERR_UNSATISFIEDCONSTRAINTS -> UnsatisfiedConstraints
+ ERR_UNSUPPORTEDOPERATION -> UnsupportedOperation
+ ERR_EOF -> EOF
_ -> OtherError
msg =
- call_site ++ ':' : ' ' : unpackCString str ++
- case iot of
- OtherError -> "(error code: " ++ show (I# errtype#) ++ ")"
- _ -> ""
+ unpackCString str ++
+ (case iot of
+ OtherError -> "(error code: " ++ show errtype ++ ")"
+ _ -> "") ++
+ (case reason of
+ Nothing -> ""
+ Just m -> ' ':m)
in
- return (IOError Nothing iot msg)
+ return (IOError Nothing iot call_site msg)
+\end{code}
+
+File names are specified using @FilePath@, a OS-dependent
+string that (hopefully, I guess) maps to an accessible file/object.
+
+\begin{code}
+type FilePath = String
\end{code}
%*********************************************************
\begin{code}
+#ifndef __HUGS__
{-
Sigh, the MVar ops in ConcBase depend on IO, the IO
representation here depend on MVars for handles (when
- compiling a concurrent way). Break the cycle by having
+ compiling in a concurrent way). Break the cycle by having
the definition of MVars go here:
-}
-data MVar a = MVar (SynchVar# RealWorld a)
+data MVar a = MVar (MVar# RealWorld a)
{-
Double sigh - ForeignObj is needed here too to break a cycle.
-}
data ForeignObj = ForeignObj ForeignObj# -- another one
+instance CCallable ForeignObj
+instance CCallable ForeignObj#
+#endif /* ndef __HUGS__ */
#if defined(__CONCURRENT_HASKELL__)
newtype Handle = Handle (MVar Handle__)
newtype Handle = Handle (MutableVar RealWorld Handle__)
#endif
+{-
+ A Handle is represented by (a reference to) a record
+ containing the state of the I/O port/device. We record
+ the following pieces of info:
+
+ * type (read,write,closed etc.)
+ * pointer to the external file object.
+ * buffering mode
+ * user-friendly name (usually the
+ FilePath used when IO.openFile was called)
+
+Note: when a Handle is garbage collected, we want to flush its buffer
+and close the OS file handle, so as to free up a (precious) resource.
+-}
data Handle__
- = ErrorHandle IOError
- | ClosedHandle
-#ifndef __PARALLEL_HASKELL__
- | SemiClosedHandle ForeignObj (Addr, Int)
- | ReadHandle ForeignObj (Maybe BufferMode) Bool
- | WriteHandle ForeignObj (Maybe BufferMode) Bool
- | AppendHandle ForeignObj (Maybe BufferMode) Bool
- | ReadWriteHandle ForeignObj (Maybe BufferMode) Bool
+ = Handle__ {
+ haFO__ :: FILE_OBJECT,
+ haType__ :: Handle__Type,
+ haBufferMode__ :: BufferMode,
+ haFilePath__ :: FilePath
+ }
+
+{-
+ Internally, we classify handles as being one
+ of the following:
+-}
+data Handle__Type
+ = ErrorHandle IOError
+ | ClosedHandle
+ | SemiClosedHandle
+ | ReadHandle
+ | WriteHandle
+ | AppendHandle
+ | ReadWriteHandle
+
+
+-- handle types are 'show'ed when printing error msgs, so
+-- we provide a more user-friendly Show instance for it
+-- than the derived one.
+instance Show Handle__Type where
+ showsPrec p t =
+ case t of
+ ErrorHandle iot -> showString "error " . showsPrec p iot
+ ClosedHandle -> showString "closed"
+ SemiClosedHandle -> showString "semi-closed"
+ ReadHandle -> showString "readable"
+ WriteHandle -> showString "writeable"
+ AppendHandle -> showString "writeable (append)"
+ ReadWriteHandle -> showString "read-writeable"
+
+instance Show Handle where
+ showsPrec p (Handle h) =
+ let
+#if defined(__CONCURRENT_HASKELL__)
+#ifdef __HUGS__
+ hdl_ = unsafePerformIO (primTakeMVar h)
#else
- | SemiClosedHandle Addr (Addr, Int)
- | ReadHandle Addr (Maybe BufferMode) Bool
- | WriteHandle Addr (Maybe BufferMode) Bool
- | AppendHandle Addr (Maybe BufferMode) Bool
- | ReadWriteHandle Addr (Maybe BufferMode) Bool
+ -- (Big) SIGH: unfolded defn of takeMVar to avoid
+ -- an (oh-so) unfortunate module loop with PrelConc.
+ hdl_ = unsafePerformIO (IO $ \ s# ->
+ case h of { MVar h# ->
+ case takeMVar# h# s# of { (# s2# , r #) ->
+ (# s2#, r #) }})
#endif
-
--- Standard Instances as defined by the Report..
--- instance Eq Handle (defined in IO)
--- instance Show Handle ""
+#else
+ hdl_ = unsafePerformIO (stToIO (readVar h))
+#endif
+ in
+ showChar '{' .
+ showHdl (haType__ hdl_)
+ (showString "loc=" . showString (haFilePath__ hdl_) . showChar ',' .
+ showString "type=" . showsPrec p (haType__ hdl_) . showChar ',' .
+ showString "buffering=" . showBufMode (haFO__ hdl_) (haBufferMode__ hdl_) . showString "}\n" )
+ where
+ showHdl :: Handle__Type -> ShowS -> ShowS
+ showHdl ht cont =
+ case ht of
+ ClosedHandle -> showsPrec p ht . showString "}\n"
+ ErrorHandle _ -> showsPrec p ht . showString "}\n"
+ _ -> cont
+
+ showBufMode :: FILE_OBJECT -> BufferMode -> ShowS
+ showBufMode fo bmo =
+ case bmo of
+ NoBuffering -> showString "none"
+ LineBuffering -> showString "line"
+ BlockBuffering (Just n) -> showString "block " . showParen True (showsPrec p n)
+ BlockBuffering Nothing -> showString "block " . showParen True (showsPrec p def)
+ where
+ def :: Int
+ def = unsafePerformIO (CCALL(getBufSize) fo)
+
+mkBuffer__ :: FILE_OBJECT -> Int -> IO ()
+mkBuffer__ fo sz_in_bytes = do
+ chunk <-
+ case sz_in_bytes of
+ 0 -> return nullAddr -- this has the effect of overwriting the pointer to the old buffer.
+ _ -> do
+ chunk <- CCALL(allocMemory__) sz_in_bytes
+ if chunk == nullAddr
+ then ioError (IOError Nothing ResourceExhausted "mkBuffer__" "not enough virtual memory")
+ else return chunk
+ CCALL(setBuf) fo chunk sz_in_bytes
\end{code}
the next block of data is read into this buffer.
\item[no-buffering] the next input item is read and returned.
\end{itemize}
+
For most implementations, physical files will normally be block-buffered
-and terminals will normally be line-buffered.
+and terminals will normally be line-buffered. (the IO interface provides
+operations for changing the default buffering of a handle tho.)
\begin{code}
data BufferMode
{- Read instance defined in IO. -}
\end{code}
-
-\begin{code}
-performGC :: IO ()
-performGC = _ccall_GC_ StgPerformGarbageCollection
-\end{code}