--- /dev/null
+DEFINITION MODULE Streams;
+(*
+ * This module provides sequential IO through streams.
+ * A stream is either a text stream or a binary stream, and is either in
+ * reading, writing or appending mode.
+ * By default, there are three open text streams, connected to standard input,
+ * standard output, and standard error respectively.
+ * These are text streams.
+ * When connected to a terminal, the standard output and standard error
+ * streams are linebuffered.
+ * The user can create more streams with the OpenStream call, and
+ * delete streams with the CloseStream call.
+ * Streams are automatically closed at program termination.
+ *)
+
+ FROM SYSTEM IMPORT BYTE;
+
+ TYPE StreamKind = (text, binary, none);
+ StreamMode = (reading, writing, appending);
+ StreamResult = (succeeded, illegaloperation,
+ nomemory, openfailed, nostream, endoffile);
+ StreamBuffering = (unbuffered, linebuffered, blockbuffered);
+ TYPE Stream;
+
+ VAR InputStream, OutputStream, ErrorStream: Stream;
+
+ PROCEDURE OpenStream(VAR stream: Stream;
+ filename: ARRAY OF CHAR;
+ kind: StreamKind;
+ mode: StreamMode;
+ VAR result: StreamResult);
+ (* Associates a stream with the file named filename.
+ If kind = none, result is set to illegaloperation.
+ mode has one of the follwing values:
+ reading: the file is opened for reading
+ writing: the file is truncated or created for writing
+ appending: the file is opened for writing at end of file,
+ or created for writing.
+ On failure, result is set to openfailed.
+ On success, result is set to succeeded.
+ *)
+
+ PROCEDURE SetStreamBuffering( stream: Stream;
+ b: StreamBuffering;
+ VAR result: StreamResult);
+ (* This procedure is only allowed for output streams.
+ The three types of buffering available are unbuffered, linebuffered,
+ and blockbuffered. When an output stream is unbuffered, the output
+ appears as soon as written; when it is blockbuffered, output is saved
+ up and written as a block; When it is linebufferded (only possible for
+ text output streams), output is saved up until a newline is encountered
+ or input is read from standard input.
+ *)
+
+ PROCEDURE CloseStream(VAR stream: Stream; VAR result: StreamResult);
+ (* Closes the stream.
+ result is set to nostream if "stream" was not associated with a stream.
+ *)
+
+ PROCEDURE FlushStream(stream: Stream; VAR result: StreamResult);
+ (* Flushes the stream.
+ result is set to nostream if "stream" was not associated with a stream.
+ It is set to illegaloperation if "stream" is not an output or
+ appending stream.
+ *)
+
+ PROCEDURE EndOfStream(stream: Stream; VAR result: StreamResult): BOOLEAN;
+ (* Returns true if the stream is an input stream, and the end of the file
+ has been reached.
+ result is set to nostream if "stream" was not associated with a stream.
+ It is set to illegaloperation if the stream is not an input stream.
+ *)
+
+ PROCEDURE Read(stream: Stream; VAR ch: CHAR; VAR result: StreamResult);
+ (* reads a character from the stream. Certain character translations may
+ occur, such as the mapping of the end-of-line sequence to the
+ character 12C.
+ result is set to nostream if "stream" was not associated with a stream.
+ It is set to endoffile if EndOfStream would have returned TRUE before the
+ call to Read. In this case, "ch" is set to 0C.
+ It is set to illegaloperation if the stream is not a text input stream.
+ *)
+
+ PROCEDURE ReadByte(stream: Stream; VAR byte: BYTE; VAR result: StreamResult);
+ (* reads a byte from the stream. No character translations occur.
+ result is set to nostream if "stream" was not associated with a stream.
+ It is set to endoffile if EndOfStream would have returned TRUE before the
+ call to ReadByte. In this case, "byte" is set to 0C.
+ It is set to illegaloperation if the stream is not a binary input stream.
+ *)
+
+ PROCEDURE ReadBytes(stream: Stream;
+ VAR bytes: ARRAY OF BYTE;
+ VAR result: StreamResult);
+ (* reads bytes from the stream. No character translations occur.
+ The number of bytes is determined by the size of the parameter.
+ result is set to nostream if "stream" was not associated with a stream.
+ It is set to endoffile if there are not enough bytes left on the stream.
+ In this case, the rest of the bytes are set to 0C.
+ It is set to illegaloperation if the stream is not a binary input stream.
+ *)
+
+ PROCEDURE Write(stream: Stream; ch: CHAR; VAR result: StreamResult);
+ (* writes a character to the stream. Certain character translations may
+ occur, such as the mapping of a line-feed or carriage return (12C or 15C)
+ to the end-of-line sequence of the system.
+ result is set to nostream if "stream" was not associated with a stream.
+ It is set to illegaloperation if the stream is not a text output stream.
+ *)
+
+ PROCEDURE WriteByte(stream: Stream; byte: BYTE; VAR result: StreamResult);
+ (* writes a byte to the stream. No character translations occur.
+ result is set to nostream if "stream" was not associated with a stream.
+ It is set to illegaloperation if the stream is not a binary output stream.
+ *)
+
+ PROCEDURE WriteBytes(stream: Stream;
+ VAR bytes: ARRAY OF BYTE;
+ VAR result: StreamResult);
+ (* writes bytes to the stream. No character translations occur.
+ The number of bytes written is equal to the size of the parameter.
+ result is set to nostream if "stream" was not associated with a stream.
+ It is set to illegaloperation if the stream is not a binary output stream.
+ *)
+
+ PROCEDURE GetPosition(stream: Stream;
+ VAR position: LONGINT;
+ VAR result: StreamResult);
+ (* gives the actual read/write position in "position".
+ "result" is set to illegaloperation if "stream" is not a stream.
+ *)
+
+ PROCEDURE SetPosition(stream: Stream;
+ position: LONGINT;
+ VAR result: StreamResult);
+ (* sets the actual read/write position to "position".
+ "result" is set to illegaloperation if "stream" is not a stream or
+ the stream was opened for appending and the position is in front of
+ the current position, or it failed for some other
+ reason (f.i. when the stream is connected to a terminal).
+ *)
+
+END Streams.
--- /dev/null
+(*$R-*)
+IMPLEMENTATION MODULE Streams;
+
+ FROM SYSTEM IMPORT BYTE, ADR;
+ IMPORT Unix, TTY, Storage, Epilogue;
+
+ CONST BUFSIZ = 1024; (* tunable *)
+ TYPE IOB = RECORD
+ kind: StreamKind;
+ mode: StreamMode;
+ eof: BOOLEAN;
+ buffering: StreamBuffering;
+ next : Stream;
+ fildes: INTEGER;
+ cnt, maxcnt: INTEGER;
+ bufferedcnt: INTEGER;
+ buf: ARRAY[1..BUFSIZ] OF BYTE;
+ END;
+ Stream = POINTER TO IOB;
+ VAR
+ ibuf, obuf, ebuf: IOB;
+ head: Stream;
+
+ PROCEDURE getstruct(VAR stream: Stream);
+ BEGIN
+ stream := head;
+ WHILE (stream # NIL) AND (stream^.kind # none) DO
+ stream := stream^.next;
+ END;
+ IF stream = NIL THEN
+ IF NOT Storage.Available(SIZE(IOB)) THEN
+ RETURN;
+ END;
+ Storage.ALLOCATE(stream,SIZE(IOB));
+ stream^.next := head;
+ head := stream;
+ END;
+ END getstruct;
+
+ PROCEDURE freestruct(stream: Stream);
+ BEGIN
+ stream^.kind := none;
+ END freestruct;
+
+ PROCEDURE OpenStream(VAR stream: Stream;
+ filename: ARRAY OF CHAR;
+ kind: StreamKind;
+ mode: StreamMode;
+ VAR result: StreamResult);
+ VAR fd: INTEGER;
+ i: CARDINAL;
+ BEGIN
+ IF kind = none THEN
+ result := illegaloperation;
+ RETURN;
+ END;
+ getstruct(stream);
+ IF stream = NIL THEN
+ result := nomemory;
+ RETURN;
+ END;
+ WITH stream^ DO
+ FOR i := 0 TO HIGH(filename) DO
+ buf[i+1] := BYTE(filename[i]);
+ END;
+ buf[HIGH(filename)+2] := BYTE(0C);
+ END;
+ IF (mode = reading) THEN
+ fd := Unix.open(ADR(stream^.buf), 0);
+ ELSE
+ fd := -1;
+ IF (mode = appending) THEN
+ fd := Unix.open(ADR(stream^.buf), 1);
+ IF fd >= 0 THEN
+ IF (Unix.lseek(fd, 0D , 2) < 0D) THEN ; END;
+ END;
+ END;
+ IF fd < 0 THEN
+ fd := Unix.creat(ADR(stream^.buf), 666B);
+ END;
+ END;
+ IF fd < 0 THEN
+ result := openfailed;
+ freestruct(stream);
+ stream := NIL;
+ RETURN;
+ END;
+ result := succeeded;
+ stream^.fildes := fd;
+ stream^.kind := kind;
+ stream^.mode := mode;
+ stream^.buffering := blockbuffered;
+ stream^.bufferedcnt := BUFSIZ;
+ stream^.maxcnt := 0;
+ stream^.eof := FALSE;
+ IF mode = reading THEN
+ stream^.cnt := 1;
+ ELSE
+ stream^.cnt := 0;
+ END;
+ END OpenStream;
+
+ PROCEDURE SetStreamBuffering( stream: Stream;
+ b: StreamBuffering;
+ VAR result: StreamResult);
+ BEGIN
+ result := succeeded;
+ IF (stream = NIL) OR (stream^.kind = none) THEN
+ result := nostream;
+ RETURN;
+ END;
+ IF (stream^.mode = reading) OR
+ ((b = linebuffered) AND (stream^.kind = binary)) THEN
+ result := illegaloperation;
+ RETURN;
+ END;
+ FlushStream(stream, result);
+ IF b = unbuffered THEN
+ stream^.bufferedcnt := 1;
+ END;
+ stream^.buffering := b;
+ END SetStreamBuffering;
+
+ PROCEDURE FlushStream(stream: Stream; VAR result: StreamResult);
+ BEGIN
+ result := succeeded;
+ IF (stream = NIL) OR (stream^.kind = none) THEN
+ result := nostream;
+ RETURN;
+ END;
+ WITH stream^ DO
+ IF mode = reading THEN
+ result := illegaloperation;
+ RETURN;
+ END;
+ IF (cnt > 0) AND (Unix.write(fildes, ADR(buf), cnt) < 0) THEN
+ ;
+ END;
+ cnt := 0;
+ END;
+ END FlushStream;
+
+ PROCEDURE CloseStream(VAR stream: Stream; VAR result: StreamResult);
+ BEGIN
+ IF (stream # NIL) AND (stream^.kind # none) THEN
+ result := succeeded;
+ IF stream^.mode # reading THEN
+ FlushStream(stream, result);
+ END;
+ IF Unix.close(stream^.fildes) < 0 THEN ; END;
+ freestruct(stream);
+ ELSE
+ result := nostream;
+ END;
+ stream := NIL;
+ END CloseStream;
+
+ PROCEDURE EndOfStream(stream: Stream; VAR result: StreamResult): BOOLEAN;
+ BEGIN
+ result := succeeded;
+ IF (stream = NIL) OR (stream^.kind = none) THEN
+ result := nostream;
+ RETURN FALSE;
+ END;
+ IF stream^.mode # reading THEN
+ result := illegaloperation;
+ RETURN FALSE;
+ END;
+ IF stream^.eof THEN RETURN TRUE; END;
+ RETURN (CHAR(NextByte(stream)) = 0C) AND stream^.eof;
+ END EndOfStream;
+
+ PROCEDURE FlushLineBuffers();
+ VAR s: Stream;
+ result: StreamResult;
+ BEGIN
+ s := head;
+ WHILE s # NIL DO
+ IF (s^.kind # none) AND (s^.buffering = linebuffered) THEN
+ FlushStream(s, result);
+ END;
+ s := s^.next;
+ END;
+ END FlushLineBuffers;
+
+ PROCEDURE NextByte(stream: Stream): BYTE;
+ VAR c: BYTE;
+ BEGIN
+ WITH stream^ DO
+ IF cnt <= maxcnt THEN
+ c := buf[cnt];
+ ELSE
+ IF eof THEN RETURN BYTE(0C); END;
+ IF stream = InputStream THEN
+ FlushLineBuffers();
+ END;
+ maxcnt := Unix.read(fildes, ADR(buf), bufferedcnt);
+ cnt := 1;
+ IF maxcnt <= 0 THEN
+ eof := TRUE;
+ c := BYTE(0C);
+ ELSE
+ c := buf[1];
+ END;
+ END;
+ END;
+ RETURN c;
+ END NextByte;
+
+ PROCEDURE Read(stream: Stream; VAR ch: CHAR; VAR result: StreamResult);
+ VAR EoF: BOOLEAN;
+ BEGIN
+ ch := 0C;
+ EoF := EndOfStream(stream, result);
+ IF result # succeeded THEN RETURN; END;
+ IF EoF THEN
+ result := endoffile;
+ RETURN;
+ END;
+ WITH stream^ DO
+ ch := CHAR(buf[cnt]);
+ INC(cnt);
+ END;
+ END Read;
+
+ PROCEDURE ReadByte(stream: Stream; VAR byte: BYTE; VAR result: StreamResult);
+ VAR EoF: BOOLEAN;
+ BEGIN
+ byte := BYTE(0C);
+ EoF := EndOfStream(stream, result);
+ IF result # succeeded THEN RETURN; END;
+ IF EoF THEN
+ result := endoffile;
+ RETURN;
+ END;
+ WITH stream^ DO
+ byte := buf[cnt];
+ INC(cnt);
+ END;
+ END ReadByte;
+
+ PROCEDURE ReadBytes(stream: Stream;
+ VAR bytes: ARRAY OF BYTE;
+ VAR result: StreamResult);
+ VAR i: CARDINAL;
+ BEGIN
+ FOR i := 0 TO HIGH(bytes) DO
+ ReadByte(stream, bytes[i], result);
+ END;
+ END ReadBytes;
+
+ PROCEDURE Write(stream: Stream; ch: CHAR; VAR result: StreamResult);
+ BEGIN
+ IF (stream = NIL) OR (stream^.kind = none) THEN
+ result := nostream;
+ RETURN;
+ END;
+ IF (stream^.kind # text) OR (stream^.mode = reading) THEN
+ result := illegaloperation;
+ RETURN;
+ END;
+ WITH stream^ DO
+ INC(cnt);
+ buf[cnt] := BYTE(ch);
+ IF (cnt >= bufferedcnt) OR
+ ((ch = 12C) AND (buffering = linebuffered))
+ THEN
+ FlushStream(stream, result);
+ END;
+ END;
+ END Write;
+
+ PROCEDURE WriteByte(stream: Stream; byte: BYTE; VAR result: StreamResult);
+ BEGIN
+ IF (stream = NIL) OR (stream^.kind = none) THEN
+ result := nostream;
+ RETURN;
+ END;
+ IF (stream^.kind # binary) OR (stream^.mode = reading) THEN
+ result := illegaloperation;
+ RETURN;
+ END;
+ WITH stream^ DO
+ INC(cnt);
+ buf[cnt] := byte;
+ IF cnt >= bufferedcnt THEN
+ FlushStream(stream, result);
+ END;
+ END;
+ END WriteByte;
+
+ PROCEDURE WriteBytes(stream: Stream; VAR bytes: ARRAY OF BYTE; VAR result: StreamResult);
+ VAR i: CARDINAL;
+ BEGIN
+ FOR i := 0 TO HIGH(bytes) DO
+ WriteByte(stream, bytes[i], result);
+ END;
+ END WriteBytes;
+
+ PROCEDURE EndIt;
+ VAR h, h1 : Stream;
+ result: StreamResult;
+ BEGIN
+ h := head;
+ WHILE h # NIL DO
+ h1 := h;
+ CloseStream(h1, result);
+ h := h^.next;
+ END;
+ END EndIt;
+
+ PROCEDURE GetPosition(s: Stream; VAR position: LONGINT;
+ VAR result: StreamResult);
+ BEGIN
+ IF (s = NIL) OR (s^.kind = none) THEN
+ result := illegaloperation;
+ RETURN;
+ END;
+ IF (s^.mode # reading) THEN FlushStream(s, result); END;
+ position := Unix.lseek(s^.fildes, 0D, 1);
+ IF position < 0D THEN
+ result := illegaloperation;
+ RETURN;
+ END;
+ IF s^.mode = reading THEN
+ position := position + LONG(s^.maxcnt - s^.cnt + 1);
+ END;
+ END GetPosition;
+
+ PROCEDURE SetPosition(s: Stream; position: LONGINT; VAR result: StreamResult);
+ VAR currpos: LONGINT;
+ BEGIN
+ currpos := 0D;
+ IF (s = NIL) OR (s^.kind = none) THEN
+ result := illegaloperation;
+ RETURN;
+ END;
+ IF (s^.mode # reading) THEN
+ FlushStream(s, result);
+ ELSE
+ s^.maxcnt := 0;
+ s^.eof := FALSE;
+ END;
+ IF s^.mode = appending THEN
+ currpos := Unix.lseek(s^.fildes, 0D, 1);
+ IF currpos < 0D THEN
+ result := illegaloperation;
+ RETURN;
+ END;
+ END;
+ IF position < currpos THEN
+ result := illegaloperation;
+ RETURN;
+ END;
+ currpos := Unix.lseek(s^.fildes, position, 0);
+ IF currpos < 0D THEN
+ result := illegaloperation;
+ RETURN;
+ END;
+ result := succeeded;
+ END SetPosition;
+
+BEGIN
+ InputStream := ADR(ibuf);
+ OutputStream := ADR(obuf);
+ ErrorStream := ADR(ebuf);
+ WITH ibuf DO
+ kind := text;
+ mode := reading;
+ eof := FALSE;
+ next := ADR(obuf);
+ fildes := 0;
+ maxcnt := 0;
+ cnt := 1;
+ bufferedcnt := BUFSIZ;
+ END;
+ WITH obuf DO
+ kind := text;
+ mode := writing;
+ eof := TRUE;
+ next := ADR(ebuf);
+ fildes := 1;
+ maxcnt := 0;
+ cnt := 0;
+ bufferedcnt := BUFSIZ;
+ IF TTY.isatty(1) THEN
+ buffering := linebuffered;
+ ELSE
+ buffering := blockbuffered;
+ END;
+ END;
+ WITH ebuf DO
+ kind := text;
+ mode := writing;
+ eof := TRUE;
+ next := NIL;
+ fildes := 2;
+ maxcnt := 0;
+ cnt := 0;
+ bufferedcnt := BUFSIZ;
+ IF TTY.isatty(2) THEN
+ buffering := linebuffered;
+ ELSE
+ buffering := blockbuffered;
+ END;
+ END;
+ head := InputStream;
+ IF Epilogue.CallAtEnd(EndIt) THEN ; END;
+END Streams.