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