1(* 	$Id: Channel.Mod,v 1.10 1999/10/31 13:35:12 ooc-devel Exp $	 *)
2MODULE oocChannel;
3(*  Provides abstract data types Channel, Reader, and Writer for stream I/O.
4    Copyright (C) 1997-1999  Michael van Acken
5
6    This module is free software; you can redistribute it and/or
7    modify it under the terms of the GNU Lesser General Public License
8    as published by the Free Software Foundation; either version 2 of
9    the License, or (at your option) any later version.
10
11    This module is distributed in the hope that it will be useful, but
12    WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14    Lesser General Public License for more details.
15
16    You should have received a copy of the GNU Lesser General Public
17    License along with OOC. If not, write to the Free Software Foundation,
18    59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19*)
20
21(*
22Note 0:
23All types and procedures declared in this module have to be considered
24abstract, i.e., they are never instanciated or called.  The provided procedure
25bodies are nothing but hints how a specific channel could start implementing
26them.
27
28Note 1:
29A module implementing specific channels (e.g., files, or TCP streams) will
30provide the procedures
31  PROCEDURE New* (...): Channel;
32and (optionally)
33  PROCEDURE Old* (...): Channel.
34
35For channels that correspond to a piece of data that can be both read
36and changed, the first procedure will create a new channel for the
37given data location, deleting all data previously contained in it.
38The latter will open a channel to the existing data.
39
40For channels representing a unidirectional byte stream (like output to
41/ input from terminal, or a TCP stream), only a procedure New is
42provided.  It will create a connection with the designated location.
43
44The formal parameters of these procedures will include some kind of
45reference to the data being opened (e.g. a file name) and, optionally,
46flags that modify the way the channel is opened (e.g. read-only,
47write-only, etc).  Their interface therefore depends on the channel
48and is not part of this specification.  The standard way to create new
49channels is to call the type-bound procedures Locator.New and
50Locator.Old (which in turn will call the above mentioned procedures).
51
52Note 2:
53A channel implementation should state how many channels can be open
54simultaneously.  It's common for the OS to support just so many open files or
55so many open sockets at the same time.  Since this value isn't a constant, it's
56only required to give a statement on the number of open connections for the
57best case, and which factors can lower this number.
58
59Note 3:
60A number of record fields in Channel, Reader, and Writer are exported
61with write permissions.  This is done to permit specializations of the
62classes to change these fields.  The user should consider them
63read-only.
64*)
65
66IMPORT
67  SYSTEM, Strings := oocStrings, Time := oocTime, Msg := oocMsg;
68
69
70TYPE
71  Result* = Msg.Msg;
72
73CONST
74  noLength* = -1;
75  (* result value of Channel.Length if the queried channel has no fixed length
76     (e.g., if it models input from keybord, or output to terminal) *)
77  noPosition* = -2;
78  (* result value of Reader/Writer.Pos if the queried rider has no concept of
79     an indexed reading resp. writing position (e.g., if it models input from
80     keybord, or output to terminal) *)
81
82
83  (* Note: The below list of error codes only covers the most typical errors.
84     A specific channel implementation (like Files) will define its own list
85     own codes, containing aliases for the codes below (when appropriate) plus
86     error codes of its own.  Every module will provide an error context (an
87     instance of Msg.Context) to translate any code into a human readable
88     message.  *)
89
90  (* a `res' value of `done' means successful completion of the I/O
91     operation:  *)
92  done* = NIL;
93
94  (* the following values may appear in the `res.code' field of `Channel',
95     `Reader', or `Writer': *)
96  (* indicates successful completion of last operation *)
97  invalidChannel* = 1;
98  (* the channel channel isn't valid, e.g. because it wasn't opened in the
99     first place or was corrupted somehow; for a rider this refers to the
100     channel in the `base' field *)
101  writeError* = 2;
102  (* a write error occured; usually this error happens with a writer, but for
103     buffered channels this may also occur during a `Flush' or a `Close' *)
104  noRoom* = 3;
105  (* set if a write operation failed because there isn't any space left on the
106     device, e.g. if the disk is full or you exeeded your quota; usually this
107     error happens with a writer, but for buffered channels this may also
108     occur during a `Flush' or a `Close' *)
109
110  (* symbolic values for `Reader.res.code' resp. `Writer.res.code': *)
111  outOfRange* = 4;
112  (* set if `SetPos' has been called with a negative argument or it has been
113     called on a rider that doesn't support positioning *)
114  readAfterEnd* = 5;
115  (* set if a call to `ReadByte' or `ReadBytes' tries to access a byte beyond
116     the end of the file (resp. channel); this means that there weren't enough
117     bytes left or the read operation started at (or after) the end *)
118  channelClosed* = 6;
119  (* set if the rider's channel has been closed, preventing any further read or
120     write operations; this means you called Channel.Close() (in which case you
121     made a programming error), or the process at the other end of the channel
122     closed the connection (examples for this are pipes, FIFOs, tcp streams) *)
123  readError* = 7;
124  (* unspecified read error *)
125  invalidFormat* = 8;
126  (* set by an interpreting Reader (e.g., TextRiders.Reader) if the byte stream
127     at the current reading position doesn't represent an object of the
128     requested type *)
129
130  (* symbolic values for `Channel.res.code': *)
131  noReadAccess* = 9;
132  (* set if NewReader was called to create a reader on a channel that doesn't
133     allow reading access *)
134  noWriteAccess* = 10;
135  (* set if NewWriter was called to create a reader on a channel that doesn't
136     allow reading access *)
137  closeError* = 11;
138  (* set if closing the channel failed for some reason *)
139  noModTime* = 12;
140  (* set if no modification time is available for the given channel *)
141  noTmpName* = 13;
142  (* creation of a temporary file failed because the system was unable to
143     assign an unique name to it; closing or registering an existing temporary
144     file beforehand might help *)
145
146  freeErrorCode* = 14;
147  (* specific channel implemenatations can start defining their own additional
148     error codes for Channel.res, Reader.res, and Writer.res here *)
149
150
151TYPE
152  Channel* = POINTER TO ChannelDesc;
153  ChannelDesc* = RECORD (*[ABSTRACT]*)
154    res*: Result;       (* READ-ONLY *)
155    (* Error flag signalling failure of a call to NewReader, NewWriter, Flush,
156       or Close.  Initialized to `done' when creating the channel.  Every
157       operation sets this to `done' on success, or to a message object to
158       indicate the error source.  *)
159
160    readable*: BOOLEAN;  (* READ-ONLY *)
161    (* TRUE iff readers can be attached to this channel with NewReader *)
162    writable*: BOOLEAN;  (* READ-ONLY *)
163    (* TRUE iff writers can be attached to this channel with NewWriter *)
164
165    open*: BOOLEAN;  (* READ-ONLY *)
166    (* Channel status.  Set to TRUE on channel creation, set to FALSE by
167       calling Close.  Closing a channel prevents all further read or write
168       operations on it.  *)
169  END;
170
171TYPE
172  Reader* = POINTER TO ReaderDesc;
173  ReaderDesc* = RECORD (*[ABSTRACT]*)
174    base*: Channel;  (* READ-ONLY *)
175    (* This field refers to the channel the Reader is connected to.  *)
176
177    res*: Result;   (* READ-ONLY *)
178    (* Error flag signalling failure of a call to ReadByte, ReadBytes, or
179       SetPos.  Initialized to `done' when creating a Reader or by calling
180       ClearError.  The first failed reading (or SetPos) operation changes this
181       to indicate the error, all further calls to ReadByte, ReadBytes, or
182       SetPos will be ignored until ClearError resets this flag.  This means
183       that the successful completion of an arbitrary complex sequence of read
184       operations can be ensured by asserting that `res' equals `done'
185       beforehand and also after the last operation.  *)
186
187    bytesRead*: LONGINT;  (* READ-ONLY *)
188    (* Set by ReadByte and ReadBytes to indicate the number of bytes that were
189       successfully read.  *)
190
191    positionable*: BOOLEAN;  (* READ-ONLY *)
192    (* TRUE iff the Reader can be moved to another position with `SetPos'; for
193       channels that can only be read sequentially, like input from keyboard,
194       this is FALSE.  *)
195  END;
196
197TYPE
198  Writer* = POINTER TO WriterDesc;
199  WriterDesc* = RECORD (*[ABSTRACT]*)
200    base*: Channel;  (* READ-ONLY *)
201    (* This field refers to the channel the Writer is connected to.  *)
202
203    res*: Result;   (* READ-ONLY *)
204    (* Error flag signalling failure of a call to WriteByte, WriteBytes, or
205       SetPos.  Initialized to `done' when creating a Writer or by calling
206       ClearError.  The first failed writing (or SetPos) operation changes this
207       to indicate the error, all further calls to WriteByte, WriteBytes, or
208       SetPos will be ignored until ClearError resets this flag.  This means
209       that the successful completion of an arbitrary complex sequence of write
210       operations can be ensured by asserting that `res' equals `done'
211       beforehand and also after the last operation.  Note that due to
212       buffering a write error may occur when flushing or closing the
213       underlying file, so you have to check the channel's `res' field after
214       any Flush() or the final Close(), too.  *)
215
216    bytesWritten*: LONGINT;  (* READ-ONLY *)
217    (* Set by WriteByte and WriteBytes to indicate the number of bytes that
218       were successfully written.  *)
219
220    positionable*: BOOLEAN;  (* READ-ONLY *)
221    (* TRUE iff the Writer can be moved to another position with `SetPos'; for
222       channels that can only be written sequentially, like output to terminal,
223       this is FALSE.  *)
224  END;
225
226TYPE
227  ErrorContext = POINTER TO ErrorContextDesc;
228  ErrorContextDesc* = RECORD
229     (* this record is exported, so that extensions of Channel can access the
230        error descriptions by extending `ErrorContextDesc' *)
231    (Msg.ContextDesc)
232  END;
233
234
235VAR
236  errorContext: ErrorContext;
237
238PROCEDURE GetError (code: Msg.Code): Result;
239  BEGIN
240    RETURN Msg.New (errorContext, code)
241  END GetError;
242
243PROCEDURE (context: ErrorContext) GetTemplate* (msg: Msg.Msg; VAR templ: Msg.LString);
244(* Translates this module's error codes into strings.  The string usually
245   contains a short error description, possibly followed by some attributes
246   to provide additional information for the problem.
247
248   The method should not be called directly by the user.  It is invoked by
249   `res.GetText()' or `res.GetLText'.  *)
250  VAR
251    str: ARRAY 128 OF CHAR;
252  BEGIN
253    CASE msg. code OF
254    | invalidChannel: str := "Invalid channel descriptor"
255    | writeError:     str := "Write error"
256    | noRoom:         str := "No space left on device"
257
258    | outOfRange:     str := "Trying to set invalid position"
259    | readAfterEnd:   str := "Trying to read past the end of the file"
260    | channelClosed:  str := "Channel has been closed"
261    | readError:      str := "Read error"
262    | invalidFormat:  str := "Invalid token type in input stream"
263
264    | noReadAccess:   str := "No read permission for channel"
265    | noWriteAccess:  str := "No write permission for channel"
266    | closeError:     str := "Error while closing the channel"
267    | noModTime:      str := "No modification time available"
268    | noTmpName:      str := "Failed to create unique name for temporary file"
269    ELSE
270      str := "[unknown error code]"
271    END;
272    COPY (str, templ)
273  END GetTemplate;
274
275
276
277(* Reader methods
278   ------------------------------------------------------------------------ *)
279
280PROCEDURE (r: Reader) (*[ABSTRACT]*) Pos*(): LONGINT;
281(* Returns the current reading position associated with the reader `r' in
282   channel `r.base', i.e. the index of the first byte that is read by the
283   next call to ReadByte resp. ReadBytes.  This procedure will return
284   `noPosition' if the reader has no concept of a reading position (e.g. if it
285   corresponds to input from keyboard), otherwise the result is not negative.*)
286  END Pos;
287
288PROCEDURE (r: Reader) (*[ABSTRACT]*) Available*(): LONGINT;
289(* Returns the number of bytes available for the next reading operation.  For
290   a file this is the length of the channel `r.base' minus the current reading
291   position, for an sequential channel (or a channel designed to handle slow
292   transfer rates) this is the number of bytes that can be accessed without
293   additional waiting.  The result is -1 if Close() was called for the channel,
294   or no more byte are available and the remote end of the channel has been
295   closed.
296   Note that the number of bytes returned is always a lower approximation of
297   the number that could be read at once; for some channels or systems it might
298   be as low as 1 even if tons of bytes are waiting to be processed.  *)
299(* example:
300  BEGIN
301    IF r. base. open THEN
302      i := r. base. Length() - r. Pos();
303      IF (i < 0) THEN
304        RETURN 0
305      ELSE
306        RETURN i
307      END
308    ELSE
309      RETURN -1
310    END
311    *)
312  END Available;
313
314PROCEDURE (r: Reader) (*[ABSTRACT]*) SetPos* (newPos: LONGINT);
315(* Sets the reading position to `newPos'.  A negative value of `newPos' or
316   calling this procedure for a reader that doesn't allow positioning will set
317   `r.res' to `outOfRange'.  A value larger than the channel's length is legal,
318   but the following read operation will most likely fail with an
319   `readAfterEnd' error unless the channel has grown beyond this position in
320   the meantime.
321   Calls to this procedure while `r.res # done' will be ignored, in particular
322   a call with `r.res.code = readAfterEnd' error will not reset `res' to
323   `done'.  *)
324(* example:
325  BEGIN
326    IF (r. res = done) THEN
327      IF ~r. positionable OR (newPos < 0) THEN
328        r. res := GetError (outOfRange)
329      ELSIF r. base. open THEN
330        (* ... *)
331      ELSE  (* channel has been closed *)
332        r. res := GetError (channelClosed)
333      END
334    END
335    *)
336  END SetPos;
337
338PROCEDURE (r: Reader) (*[ABSTRACT]*) ReadByte* (VAR x: SYSTEM.BYTE);
339(* Reads a single byte from the channel `r.base' at the reading position
340   associated with `r' and places it in `x'.  The reading position is moved
341   forward by one byte on success, otherwise `r.res' is changed to indicate
342   the error cause.  Calling this procedure with the reader `r' placed at the
343   end (or beyond the end) of the channel will set `r.res' to `readAfterEnd'.
344   `r.bytesRead' will be 1 on success and 0 on failure.
345   Calls to this procedure while `r.res # done' will be ignored.  *)
346(* example:
347  BEGIN
348    IF (r. res = done) THEN
349      IF r. base. open THEN
350        (* ... *)
351      ELSE  (* channel has been closed *)
352        r. res := GetError (channelClosed);
353        r. bytesRead := 0
354      END
355    ELSE
356      r. bytesRead := 0
357    END
358    *)
359  END ReadByte;
360
361PROCEDURE (r: Reader) (*[ABSTRACT]*) ReadBytes* (VAR x: ARRAY OF SYSTEM.BYTE;
362                                             start, n: LONGINT);
363(* Reads `n' bytes from the channel `r.base' at the reading position associated
364   with `r' and places them in `x', starting at index `start'.  The
365   reading position is moved forward by `n' bytes on success, otherwise
366   `r.res' is changed to indicate the error cause.  Calling this procedure with
367   the reader `r' placed less than `n' bytes before the end of the channel will
368   will set `r.res' to `readAfterEnd'.  `r.bytesRead' will hold the number of
369   bytes that were actually read (being equal to `n' on success).
370   Calls to this procedure while `r.res # done' will be ignored.
371   pre: (n >= 0) & (0 <= start) & (start+n <= LEN (x)) *)
372(* example:
373  BEGIN
374    ASSERT ((n >= 0) & (0 <= start) & (start+n <= LEN (x)));
375    IF (r. res = done) THEN
376      IF r. base. open THEN
377        (* ... *)
378      ELSE  (* channel has been closed *)
379        r. res := GetError (channelClosed);
380        r. bytesRead := 0
381      END
382    ELSE
383      r. bytesRead := 0
384    END
385    *)
386  END ReadBytes;
387
388PROCEDURE (r: Reader) ClearError*;
389(* Sets the result flag `r.res' to `done', re-enabling further read operations
390   on `r'.  *)
391  BEGIN
392    r. res := done
393  END ClearError;
394
395
396
397
398(* Writer methods
399   ------------------------------------------------------------------------ *)
400
401PROCEDURE (w: Writer) (*[ABSTRACT]*) Pos*(): LONGINT;
402(* Returns the current writing position associated with the writer `w' in
403   channel `w.base', i.e. the index of the first byte that is written by the
404   next call to WriteByte resp. WriteBytes.  This procedure will return
405   `noPosition' if the writer has no concept of a writing position (e.g. if it
406   corresponds to output to terminal), otherwise the result is not negative. *)
407  END Pos;
408
409PROCEDURE (w: Writer) (*[ABSTRACT]*) SetPos* (newPos: LONGINT);
410(* Sets the writing position to `newPos'.  A negative value of `newPos' or
411   calling this procedure for a writer that doesn't allow positioning will set
412   `w.res' to `outOfRange'.  A value larger than the channel's length is legal,
413   the following write operation will fill the gap between the end of the
414   channel and this position with zero bytes.
415   Calls to this procedure while `w.res # done' will be ignored.  *)
416(* example:
417  BEGIN
418    IF (w. res = done) THEN
419      IF ~w. positionable OR (newPos < 0) THEN
420        w. res := GetError (outOfRange)
421      ELSIF w. base. open THEN
422        (* ... *)
423      ELSE  (* channel has been closed *)
424        w. res := GetError (channelClosed)
425      END
426    END
427    *)
428  END SetPos;
429
430PROCEDURE (w: Writer) (*[ABSTRACT]*) WriteByte* (x: SYSTEM.BYTE);
431(* Writes a single byte `x' to the channel `w.base' at the writing position
432   associated with `w'.  The writing position is moved forward by one byte on
433   success, otherwise `w.res' is changed to indicate the error cause.
434   `w.bytesWritten' will be 1 on success and 0 on failure.
435   Calls to this procedure while `w.res # done' will be ignored.  *)
436(* example:
437  BEGIN
438    IF (w. res = done) THEN
439      IF w. base. open THEN
440        (* ... *)
441      ELSE  (* channel has been closed *)
442        w. res := GetError (channelClosed);
443        w. bytesWritten := 0
444      END
445    ELSE
446      w. bytesWritten := 0
447    END
448    *)
449  END WriteByte;
450
451PROCEDURE (w: Writer) (*[ABSTRACT]*) WriteBytes* (VAR x: ARRAY OF SYSTEM.BYTE;
452                                              start, n: LONGINT);
453(* Writes `n' bytes from `x', starting at position `start', to the channel
454   `w.base' at the writing position associated with `w'.  The writing position
455   is moved forward by `n' bytes on success, otherwise `w.res' is changed to
456   indicate the error cause.  `w.bytesWritten' will hold the number of bytes
457   that were actually written (being equal to `n' on success).
458   Calls to this procedure while `w.res # done' will be ignored.
459   pre: (n >= 0) & (0 <= start) & (start+n <= LEN (x))  *)
460(* example:
461  BEGIN
462    ASSERT ((n >= 0) & (0 <= start) & (start+n <= LEN (x)));
463    IF (w. res = done) THEN
464      IF w. base. open THEN
465        (* ... *)
466      ELSE  (* channel has been closed *)
467        w. res := GetError (channelClosed);
468        w. bytesWritten := 0
469      END
470    ELSE
471      w. bytesWritten := 0
472    END
473    *)
474  END WriteBytes;
475
476PROCEDURE (w: Writer) ClearError*;
477(* Sets the result flag `w.res' to `done', re-enabling further write operations
478   on `w'.  *)
479  BEGIN
480    w. res := done
481  END ClearError;
482
483
484
485
486(* Channel methods
487   ------------------------------------------------------------------------ *)
488
489PROCEDURE (ch: Channel) (*[ABSTRACT]*) Length*(): LONGINT;
490(* Result is the number of bytes of data that this channel refers to.  If `ch'
491   represents a file, then this value is the file's size.  If `ch' has no fixed
492   length (e.g. because it's interactive), the result is `noLength'.  *)
493  END Length;
494
495PROCEDURE (ch: Channel) (*[ABSTRACT]*) GetModTime* (VAR mtime: Time.TimeStamp);
496(* Retrieves the modification time of the data accessed by the given channel.
497   If no such information is avaiblable, `ch.res' is set to `noModTime',
498   otherwise to `done'.  *)
499  END GetModTime;
500
501PROCEDURE (ch: Channel) NewReader*(): Reader;
502(* Attaches a new reader to the channel `ch'.  It is placed at the very start
503   of the channel, and its `res' field is initialized to `done'.  `ch.res' is
504   set to `done' on success and the new reader is returned.  Otherwise result
505   is NIL and `ch.res' is changed to indicate the error cause.
506   Note that always the same reader is returned if the channel does not support
507   multiple reading positions.  *)
508(* example:
509  BEGIN
510    IF ch. open THEN
511      IF ch. readable THEN
512        (* ... *)
513        ch. ClearError
514      ELSE
515        ch. res := noReadAccess;
516        RETURN NIL
517      END
518    ELSE
519      ch. res := channelClosed;
520      RETURN NIL
521    END
522    *)
523  BEGIN  (* default: channel does not have read access *)
524    IF ch. open THEN
525      ch. res := GetError (noReadAccess)
526    ELSE
527      ch. res := GetError (channelClosed)
528    END;
529    RETURN NIL
530  END NewReader;
531
532PROCEDURE (ch: Channel) NewWriter*(): Writer;
533(* Attaches a new writer to the channel `ch'.  It is placed at the very start
534   of the channel, and its `res' field is initialized to `done'.  `ch.res' is
535   set to `done' on success and the new writer is returned.  Otherwise result
536   is NIL and `ch.res' is changed to indicate the error cause.
537   Note that always the same reader is returned if the channel does not support
538   multiple writing positions.  *)
539(* example:
540  BEGIN
541    IF ch. open THEN
542      IF ch. writable THEN
543        (* ... *)
544        ch. ClearError
545      ELSE
546        ch. res := GetError (noWriteAccess);
547        RETURN NIL
548      END
549    ELSE
550      ch. res := GetError (channelClosed);
551      RETURN NIL
552    END
553    *)
554  BEGIN  (* default: channel does not have write access *)
555    IF ch. open THEN
556      ch. res := GetError (noWriteAccess)
557    ELSE
558      ch. res := GetError (channelClosed)
559    END;
560    RETURN NIL
561  END NewWriter;
562
563PROCEDURE (ch: Channel) (*[ABSTRACT]*) Flush*;
564(* Flushes all buffers related to this channel.  Any pending write operations
565   are passed to the underlying OS and all buffers are marked as invalid.  The
566   next read operation will get its data directly from the channel instead of
567   the buffer.  If a writing error occurs during flushing, the field `ch.res'
568   will be changed to `writeError', otherwise it's assigned `done'.  Note that
569   you have to check the channel's `res' flag after an explicit flush yourself,
570   since none of the attached writers will notice any write error in this
571   case.  *)
572(* example:
573  BEGIN
574    (* ... *)
575    IF (* write error ... *) FALSE THEN
576      ch. res := GetError (writeError)
577    ELSE
578      ch. ClearError
579    END
580    *)
581  END Flush;
582
583PROCEDURE (ch: Channel) (*[ABSTRACT]*) Close*;
584(* Flushes all buffers associated with `ch', closes the channel, and frees all
585   system resources allocated to it.  This invalidates all riders attached to
586   `ch', they can't be used further.  On success, i.e. if all read and write
587   operations (including flush) completed successfully, `ch.res' is set to
588   `done'.  An opened channel can only be closed once, successive calls of
589   `Close' are undefined.
590   Note that unlike the Oberon System all opened channels have to be closed
591   explicitly.  Otherwise resources allocated to them will remain blocked.  *)
592(* example:
593  BEGIN
594    ch. Flush;
595    IF (ch. res = done) THEN
596      (* ... *)
597    END;
598    ch. open := FALSE
599    *)
600  END Close;
601
602PROCEDURE (ch: Channel) ClearError*;
603(* Sets the result flag `ch.res' to `done'.  *)
604  BEGIN
605    ch. res := done
606  END ClearError;
607
608BEGIN
609  NEW (errorContext);
610  Msg.InitContext (errorContext, "OOC:Core:Channel")
611END oocChannel.
612