(* Copyright (C) 1989, Digital Equipment Corporation *) (* All rights reserved. *) (* See the file COPYRIGHT for a full description. *) (* Last modified on Sat Nov 4 21:41:34 1989 by muller *) (* This module is very similar to the WrRep module, so we will list its code with only a few comments. *) UNSAFE MODULE RdRep EXPORTS Rd, RdClass, UnsafeRd; IMPORT Thread, Text; FROM Thread IMPORT Alerted; REVEAL Private = Thread.Mutex BRANDED OBJECT next, stop: UNTRACED REF CHAR := NIL; buffP: REF ARRAY OF CHAR; END; (* The implementation of Rd.Seek is lazy. When a client calls Rd.Seek with an index that does not lie within the buffer, Rd.Seek simply records the destination index in rd.cur and sets both rd.next and rd.stop to NIL. When rd.next # NIL, the Rd implementation ignores the value of rd.cur in determining cur(rd), but when rd.next = NIL the Rd implementation uses the value of rd.cur. A reader rd is "clean" if the following conditions hold (see the WrRep module for more explanation): C1. If rd.next # rd.stop, then Ready(rd) AND (rd.next = ADR(rd.buff[rd.st + cur(rd) - rd.lo]) C2. If rd.next # rd.stop, then rd.stop = ADR(rd.buff[rd.st + (rd.hi - 1) - rd.lo]) + ADRSIZE(CHAR) C3. The validity conditions V1 and V3 hold for rd. C4. If rd.next # NIL then rd.next = ADR(rd.buff[rd.st + cur(rd) - rd.lo]) C5. If rd.next = NIL, then rd is valid. *) <*INLINE*> PROCEDURE MakeValid(rd: T) = (* rd locked and clean *) BEGIN IF rd.next # NIL THEN rd.cur := rd.lo + (rd.next - ADR(rd.buff[rd.st])) DIV ADRSIZE(CHAR) END END MakeValid; (* rd is locked, clean, and valid. Furthermore, if rd.next#NIL, then rd.cur=cur(rd); this is important for the implementation of GetIndex. *) PROCEDURE MakeClean(rd: T) = BEGIN rd.buffP := rd.buff; IF (rd.lo <= rd.cur) AND (rd.cur < rd.hi) AND (rd.buffP # NIL) THEN rd.next := ADR(rd.buffP[rd.st + rd.cur - rd.lo]); rd.stop := ADR(rd.buffP[rd.st + rd.hi - rd.lo - 1]) + ADRSIZE(CHAR); IF rd.stop < rd.next THEN RAISE Failure("Who's changing rd without the lock?") END ELSE rd.stop := NIL; rd.next := NIL END END MakeClean; PROCEDURE SlowGetChar(rd: T): CHAR RAISES {EndOfFile, Failure, Alerted, Error} = (* rd is locked and clean; rd.next = rd.stop *) VAR res: CHAR; BEGIN IF rd.closed THEN RAISE Error(Code.Closed) END; TRY MakeValid(rd); IF rd.seek(dontBlock := FALSE) = SeekResult.Eof THEN RAISE EndOfFile END FINALLY MakeClean(rd) END; IF rd.next = rd.stop THEN RAISE Error(Code.MethodError) END; res := rd.next^; INC(rd.next, ADRSIZE(CHAR)); RETURN res END SlowGetChar; <*INLINE*> PROCEDURE GetChar(rd: T): CHAR RAISES {EndOfFile, Failure, Alerted, Error} = (* rd is unlocked *) BEGIN LOCK rd DO RETURN FastGetChar(rd) END END GetChar; <*INLINE*> PROCEDURE FastGetChar(rd: T): CHAR RAISES {EndOfFile, Failure, Alerted, Error} = (* rd is locked *) VAR res: CHAR; BEGIN IF rd.next # rd.stop THEN res := rd.next^; INC(rd.next, ADRSIZE(CHAR)) ELSE res := SlowGetChar(rd) END; RETURN res END FastGetChar; <*INLINE*> PROCEDURE EOF(rd: T): BOOLEAN RAISES {Failure, Alerted, Error} = (* rd is unlocked *) BEGIN LOCK rd DO RETURN FastEOF(rd) END END EOF; <*INLINE*> PROCEDURE FastEOF(rd: T): BOOLEAN RAISES {Failure, Alerted, Error} = (* rd is locked *) BEGIN IF rd.next # rd.stop THEN RETURN FALSE ELSE RETURN SlowEOF(rd) END END FastEOF; PROCEDURE SlowEOF(rd: T): BOOLEAN RAISES {Failure, Alerted} = (* rd is locked; rd.next = rd.stop *) VAR res: CHAR; BEGIN IF rd.closed THEN RAISE Error(Code.Closed) ELSE MakeValid(rd); TRY RETURN rd.seek(dontBlock := FALSE) = SeekResult.Eof FINALLY MakeClean(rd) END END END SlowEOF; PROCEDURE UnGetChar(rd: T) RAISES {Error} = BEGIN LOCK rd DO IF rd.closed THEN RAISE Error(Code.Closed) END; IF (rd.next = NIL) OR (rd.next = ADR(rd.buff[rd.st])) THEN RAISE Error(Code.CantUnget) END; DEC(rd.next) END END UnGetChar; PROCEDURE CharsReady(rd: T): CARDINAL RAISES {Failure, Alerted, Error} = BEGIN LOCK rd DO IF rd.closed THEN RAISE Error(Code.Closed) END; MakeValid(rd); IF NOT (rd.lo <= rd.cur AND rd.cur < rd.hi) THEN TRY IF rd.seek(dontBlock := TRUE) = SeekResult.Eof THEN RETURN 1 END FINALLY MakeClean(rd) END; IF rd.cur > rd.hi THEN RAISE Error(Code.MethodError) END; END; RETURN rd.hi - rd.cur; END END CharsReady; PROCEDURE GetIndex(rd: T): CARDINAL RAISES {Failure, Alerted, Error} = BEGIN LOCK rd DO IF rd.closed THEN RAISE Error(Code.Closed) END; MakeValid(rd); IF rd.seekable AND (rd.next = NIL) THEN rd.cur := MIN(rd.cur, rd.length()) END; RETURN rd.cur END END GetIndex; PROCEDURE GetLength(rd: T): CARDINAL RAISES {Failure, Alerted, Error} = BEGIN LOCK rd DO IF rd.closed THEN RAISE Error(Code.Closed) ELSIF rd.intermittent THEN RAISE Error(Code.Intermittent) ELSE TRY MakeValid(rd); RETURN rd.length() FINALLY MakeClean(rd) END END END END GetLength; PROCEDURE Seek(rd: T; n: CARDINAL) RAISES {Failure, Alerted, Error} = BEGIN LOCK rd DO IF rd.closed THEN RAISE Error(Code.Closed) ELSIF NOT rd.seekable THEN RAISE Error(Code.Unseekable) ELSE rd.cur := n; MakeClean(rd) END END END Seek; PROCEDURE Close(rd: T) RAISES {Failure, Alerted, Error} = BEGIN LOCK rd DO IF NOT rd.closed THEN TRY MakeValid(rd); rd.close() FINALLY rd.closed := TRUE; rd.next := NIL; rd.stop := NIL; rd.buffP := NIL END END END END Close; (* No matter how straightforward the implementations of the procedures GetSub, GetSubLine, GetText, GetLine, Intermittent, Seekable, and Closed from the Rd interface, and of all the procedures in the RdClass interface are, we cannot omit them. *) PROCEDURE GetSub (rd: T; VAR (* out *) str: ARRAY OF CHAR): CARDINAL RAISES {Failure, Alerted, Error} = VAR i: CARDINAL; BEGIN i := 0; WHILE NOT EOF (rd) AND i # NUMBER(str) DO str[i] := GetChar (rd); INC(i); END; RETURN i; END GetSub; PROCEDURE GetSubLine (rd: T; VAR (* out *) str: ARRAY OF CHAR): CARDINAL RAISES { Failure, Alerted, Error} = VAR i: CARDINAL := 0; BEGIN WHILE NOT EOF (rd) AND i # NUMBER(str) AND (i = 0 OR str[i-1] # '\n') DO str[i] := GetChar(rd); INC(i); END; RETURN i; END GetSubLine; PROCEDURE GetText(rd: T; length: INTEGER): TEXT RAISES { Failure, Alerted, Error} = VAR res: Text.T := ""; BEGIN WHILE NOT EOF(rd) AND length > 0 DO res := res & Text.PutChar (GetChar (rd)); DEC (length); END; RETURN res; END GetText; PROCEDURE GetLine(rd: T): TEXT RAISES { EndOfFile, Failure, Alerted, Error} = VAR res: Text.T := ""; ch: CHAR := '\000' (* anything # from \n would do *); BEGIN IF EOF (rd) THEN RAISE EndOfFile END; WHILE NOT EOF (rd) AND ch # '\n' DO ch := GetChar(rd); IF ch # '\n' THEN res := res & Text.PutChar (ch) END; END; RETURN res; END GetLine; PROCEDURE Intermittent(rd: T): BOOLEAN RAISES { } = BEGIN RETURN (rd.intermittent); END Intermittent; PROCEDURE Seekable(rd: T): BOOLEAN RAISES { } = BEGIN RETURN (rd.seekable); END Seekable; PROCEDURE Closed(rd: T): BOOLEAN RAISES { } = BEGIN RETURN (rd.closed); END Closed; PROCEDURE Lock (rd: T) RAISES { } = BEGIN Thread.Acquire (rd); MakeValid (rd); END Lock; PROCEDURE Unlock (rd: T) RAISES { } = BEGIN Thread.Release (rd); END Unlock; PROCEDURE LengthDefault(rd: T): CARDINAL RAISES { Failure, Alerted, Error} = BEGIN RAISE Error (Code.MethodError); END LengthDefault; PROCEDURE CloseDefault(rd: T) RAISES { Failure, Alerted, Error} = BEGIN rd.buff := NIL; END CloseDefault; BEGIN END RdRep.