(* Copyright (C) 1989, Digital Equipment Corporation *) (* All rights reserved. *) (* See the file COPYRIGHT for a full description. *) (* Last modified on Thu Nov 2 21:55:19 1989 by muller *) (* A Wr.T (or ``writer'') is a character output stream. The basic operation on a writer is PutChar, which extends a writer's character sequence by one character. Some writers (called ``seekable writers'') also allow overwriting in the middle of the sequence. For example, writers to random access files are seekable, but writers to terminals and sequential files are not. Writers can be (and usually are) buffered. This means that operations on the writer don't immediately affect the underlying target of the writer, but are saved up and performed later. For example, a writer to a disk file is not likely to update the disk after each character. Abstractly, a writer wr consists of: len(wr) a non-negative integer c(wr) a character sequence of length len(wr) cur(wr) an integer in the range [0..len(wr) target(wr) a character sequence closed(wr) a boolean seekable(wr) a boolean buffered(wr) a boolean These values are generally not directly represented in the data fields of a writer object, but in principle they determine the state of the writer. The sequence c(wr) is zero-based: c(wr)[i] is valid for i from 0 through len(wr)-1. The value of cur(wr) is the index of the character in c(wr) that will be replaced or appended by the next call to PutChar. If wr is not seekable, then cur(wr) is always equal to len(wr), since in this case all writing happens at the end. The difference between c(wr) and target(wr) reflects the buffering: if wr is not buffered, then target(wr) is updated to equal c(wr) after every operation; ifwr is buffered, then updates to target(wr) can be delayed. For example, in a writer to a file, target(wr) is the actual sequence of characters on the disk; in a writer to a terminal, target(wr) is the sequence of characters that have actually been transmitted (this sequence may not exist in any data structure, but it still exists in the eye of God). Every writer is a monitor; that is, it contains an internal lock that is acquired and held for each operation in this interface, so that concurrent operations will appear atomic. For faster, unmonitored access, see the UnsafeWr interface. Since there are many classes of writers, there are many ways that a writer can break---for example, the network can go down, the disk can fill up, etc. All problems of this sort are reported by raising the exception Failure. Each writer class should specify what failures it can raise and how they are encoded in the argument to Wr.Failure (which has type REFANY). Illegal operations (for example, writing to a closed writer) raise the exception Error. Many operations on a writer can wait indefinitely. For example, PutChar can wait if the user has suspended output to his terminal. These waits can be alertable, so each procedure that might wait includes Thread.Alerted in its raises clause. The rest of this section is a listing of the Wr interface, together with comments specifying the effect of each procedure. It is convenient to define the action PutC(wr, ch), which outputs the character ch to the writer wr: PutC(wr, ch) = IF closed(wr) THEN RAISE Error(Code.Closed) END; IF cur(wr) = len(wr) THEN "Extend c(wr) by one character, incrementing len(wr)" END; c(wr)[cur(wr)] := ch; INC(cur(wr)); "Possibly Flush wr" where "Possibly Flush wr" specifies a non-deterministic choice between assigning target(wr):=c(wr) and doing nothing. PutC is only used in specifying the interface; it is not a real procedure. *) INTERFACE Wr; FROM Thread IMPORT Alerted; TYPE T <: ROOT; Code = {Closed, Unseekable}; EXCEPTION Failure(REFANY); Error(Code); PROCEDURE PutChar(wr: T; ch: CHAR) RAISES {Failure, Alerted, Error}; (* Output ch to wr. More precisely, this is equivalent to: PutC(wr, ch); IF NOT buffered(wr) THEN Flush(wr) END *) PROCEDURE PutText(wr: T; t: TEXT) RAISES {Failure, Alerted, Error}; (* Output t to wr. More precisely, this is equivalent to: FOR i := 0 TO Text.Length(t) - 1 DO PutC(wr, Text.GetChar(t, i)) END; IF NOT buffered(wr) THEN Flush(wr) END except that, like all operations in this interface, it is atomic with respect to other operations in the interface. *) PROCEDURE PutString(wr: T; a: ARRAY OF CHAR) RAISES {Failure, Alerted, Error}; (* Output a to wr. More precisely, this is equivalent to: FOR i := FIRST(a) TO LAST(a) DO PutC(wr, a[i]) END; IF NOT buffered(wr) THEN Flush(wr) END except that it is atomic. *) PROCEDURE Seek(wr: T; n: CARDINAL) RAISES {Failure, Alerted, Error}; (* Set the current position of wr to n. This is a no-op if wr is closed. More precisely, this is equivalent to: IF NOT seekable(wr) THEN RAISE Error(Code.Unseekable) END; cur(wr) := MIN(n, len(wr)); "Possibly Flush wr" *) PROCEDURE Flush(wr: T) RAISES {Failure, Alerted, Error}; (* Perform all buffered operations. That is, set target(wr) := c(wr). This is a no-op if wr is closed. *) PROCEDURE Close(wr: T) RAISES {Failure, Alerted, Error}; (* Flush wr, release any resources associated with wr (such as buffer space, file write locks, etc.) and finally set closed(wr) := true. The resources released depend on the class of the writer; consult the specification for the procedure that creates readers of wr's class. This leaves closed(wr) equal to TRUE even if it raises an exception, and is a no-op if wr is closed. *) PROCEDURE Length(wr: T): CARDINAL RAISES {Failure, Alerted, Error}; PROCEDURE GetIndex(wr: T): CARDINAL RAISES {}; PROCEDURE Seekable(wr: T): BOOLEAN RAISES {}; PROCEDURE Closed(wr: T): BOOLEAN RAISES {}; PROCEDURE Buffered(wr: T): BOOLEAN RAISES {}; (* These procedures return len(wr), cur(wr), seekable(wr), closed(wr), and buffered(wr), respectively. *) END Wr.