Last modified on Thu Jan 26 13:58:35 PST 1995 by kalsow modified on Fri Jun 18 18:12:46 PDT 1993 by wobber modified on Tue Jun 15 13:41:05 1993 by gnelson modified on Mon May 31 06:25:34 PDT 1993 by swart modified on Mon Apr 26 17:22:58 PDT 1993 by mcjones modified on Tue Apr 21 15:56:06 PDT 1992 by muller
This module is very similar to the Wr module, so we will list its code with only a few comments.
MODULEFastGetChar and GetChar are identical except that GetChar acquires and releases the lock while FastGetChar assumes it is already held.Rd EXPORTSRd ,RdClass ,UnsafeRd ; IMPORT Text, Text8, Thread, Word; FROM Thread IMPORT Alerted; REVEAL Private = Thread.Mutex BRANDED OBJECT END;
It is invariant that for a closed reader rd
, rd.buff = NIL
and
rd.lo = rd.hi
. Therefore the check that rd
is ready need
not inspect rd.closed
on the fast path.
<*INLINE*> PROCEDUREEOF and FastEOF are identical except that EOF acquires and releases the reader lock while FastEOF assumes it is already held.GetChar (rd: T): CHAR RAISES {EndOfFile, Failure, Alerted} = BEGIN LOCK rd DO RETURN FastGetChar(rd); END END GetChar; <*INLINE*> PROCEDUREFastGetChar (rd: T): CHAR RAISES {EndOfFile, Failure, Alerted} = (* rd is locked *) VAR res: CHAR; BEGIN IF rd.cur = rd.hi THEN DoSeek(rd) END; res := rd.buff[rd.st + (rd.cur - rd.lo)]; INC(rd.cur); RETURN res END FastGetChar; PROCEDUREDoSeek (rd: T) RAISES {EndOfFile, Failure, Alerted} = BEGIN (* rd.cur = rd.hi here *) IF rd.closed THEN Die() END; IF rd.seek(rd.cur, FALSE) = SeekResult.Eof THEN RAISE EndOfFile END END DoSeek; <*INLINE*> PROCEDUREGetWideChar (rd: T): WIDECHAR RAISES {EndOfFile, Failure, Alerted} = BEGIN LOCK rd DO RETURN FastGetWideChar(rd); END END GetWideChar; <*INLINE*> PROCEDUREFastGetWideChar (rd: T): WIDECHAR RAISES {EndOfFile, Failure, Alerted} = (* rd is locked *) VAR ch: WIDECHAR; BEGIN IF rd.closed THEN Die() END; IF NOT GetWC (rd, ch) THEN RAISE EndOfFile; END; RETURN ch; END FastGetWideChar; <*INLINE*> PROCEDUREGetWC (rd: T; VAR(*OUT*) ch: WIDECHAR): BOOLEAN RAISES {Failure, Alerted} = (* rd is locked *) VAR c1, c2: CHAR; BEGIN IF rd.cur = rd.hi THEN IF rd.seek(rd.cur, FALSE) = SeekResult.Eof THEN RETURN FALSE; END; END; c1 := rd.buff[rd.st + (rd.cur - rd.lo)]; INC(rd.cur); IF rd.cur # rd.hi THEN c2 := rd.buff[rd.st + (rd.cur - rd.lo)]; INC(rd.cur); ELSIF rd.seek(rd.cur, FALSE) = SeekResult.Eof THEN c2 := '\000'; ELSE c2 := rd.buff[rd.st + (rd.cur - rd.lo)]; INC(rd.cur); END; ch := VAL (Word.LeftShift (ORD (c2), 8) + ORD (c1), WIDECHAR); RETURN TRUE; END GetWC; PROCEDUREGetSub (rd: T; VAR (*out*) str: ARRAY OF CHAR): CARDINAL RAISES {Failure, Alerted} = BEGIN LOCK rd DO RETURN FastGetSub(rd, str); END END GetSub; PROCEDUREFastGetSub (rd: T; VAR (*out*) str: ARRAY OF CHAR): CARDINAL RAISES {Failure, Alerted} = BEGIN IF rd.closed THEN Die() END; RETURN rd.getSub(str) END FastGetSub; PROCEDUREGetWideSub (rd: T; VAR (*out*) str: ARRAY OF WIDECHAR): CARDINAL RAISES {Failure, Alerted} = BEGIN LOCK rd DO RETURN FastGetWideSub(rd, str); END; END GetWideSub; PROCEDUREFastGetWideSub (rd: T; VAR (*out*) str: ARRAY OF WIDECHAR): CARDINAL RAISES {Failure, Alerted} = VAR len := 0; ch: WIDECHAR; BEGIN IF rd.closed THEN Die() END; WHILE (len < NUMBER (str)) AND GetWC (rd, ch) DO str[len] := ch; INC (len); END; RETURN len; END FastGetWideSub; PROCEDUREGetSubDefault (rd: T; VAR (*out*) str: ARRAY OF CHAR): CARDINAL RAISES {Failure, Alerted} = VAR i := 0; BEGIN LOOP (* i chars have been read into str *) IF i = NUMBER(str) THEN EXIT END; IF rd.cur = rd.hi THEN IF rd.seek(rd.cur, FALSE) = SeekResult.Eof THEN EXIT END END; (* rd.lo <= rd.cur < rd.hi *) VAR n := MIN(rd.hi - rd.cur, NUMBER(str) - i); BEGIN SUBARRAY(str, i, n) := SUBARRAY(rd.buff^, rd.cur - rd.lo + rd.st, n); INC(i, n); INC(rd.cur, n) END END; RETURN i END GetSubDefault;
<*INLINE*> PROCEDURE---------------------------------------------------------- internal ---EOF (rd: T): BOOLEAN RAISES {Failure, Alerted} = (* rd is unlocked *) BEGIN LOCK rd DO RETURN FastEOF(rd); END END EOF; <*INLINE*> PROCEDUREFastEOF (rd: T): BOOLEAN RAISES {Failure, Alerted} = BEGIN (* rd is locked *) IF rd.cur # rd.hi THEN RETURN FALSE ELSE IF rd.closed THEN Die() END; RETURN rd.seek(rd.cur, FALSE) = SeekResult.Eof END END FastEOF; PROCEDUREUnGetChar (rd: T) = BEGIN LOCK rd DO FastUnGetChar (rd) END; END UnGetChar; PROCEDUREFastUnGetChar (rd: T) = BEGIN IF rd.closed OR rd.cur = rd.lo THEN Die() END; DEC(rd.cur) END FastUnGetChar; PROCEDURECharsReady (rd: T): CARDINAL RAISES {Failure} = <*FATAL Thread.Alerted*> BEGIN LOCK rd DO IF rd.cur = rd.hi THEN IF rd.closed THEN Die() END; IF rd.seek(rd.cur, TRUE) = SeekResult.Eof THEN RETURN 1 END END; RETURN rd.hi - rd.cur; END END CharsReady; PROCEDUREIndex (rd: T): CARDINAL = BEGIN LOCK rd DO IF rd.closed THEN Die() END; RETURN rd.cur END END Index; PROCEDURELength (rd: T): INTEGER RAISES {Failure, Alerted} = BEGIN LOCK rd DO IF rd.closed THEN Die() END; RETURN rd.length() END END Length; PROCEDURESeek (rd: T; n: CARDINAL) RAISES {Failure, Alerted} = BEGIN LOCK rd DO IF rd.closed OR NOT rd.seekable THEN Die() END; IF n < rd.lo OR n > rd.hi THEN EVAL rd.seek(n, FALSE); ELSE rd.cur := n; END END END Seek; PROCEDUREClose (rd: T) RAISES {Failure, Alerted} = BEGIN LOCK rd DO FastClose (rd); END; END Close; PROCEDUREFastClose (rd: T) RAISES {Failure, Alerted} = BEGIN IF NOT rd.closed THEN TRY rd.close() FINALLY rd.closed := TRUE; rd.cur := rd.hi; rd.lo := rd.hi; rd.buff := NIL END END END FastClose; PROCEDUREGetSubLine (rd: T; VAR(*out*) str: ARRAY OF CHAR): CARDINAL RAISES {Failure, Alerted} = VAR i: CARDINAL := 0; BEGIN LOCK rd DO LOOP (* i chars have been read into str *) IF i = NUMBER (str) THEN RETURN i END; IF rd.cur = rd.hi THEN IF rd.closed THEN Die () END; IF rd.seek (rd.cur, FALSE) = SeekResult.Eof THEN RETURN i END END; (* rd is ready *) VAR n := MIN (rd.hi, rd.cur + NUMBER (str) - i) - rd.lo + rd.st; j := rd.cur - rd.lo + rd.st; BEGIN WHILE (j # n) AND (rd.buff[j] # '\n') DO INC (j) END; VAR rd_cur := rd.cur - rd.lo + rd.st; k := j - rd_cur; BEGIN SUBARRAY (str, i, k) := SUBARRAY (rd.buff^, rd_cur, k); INC (i, k); INC (rd.cur, k); END; IF (j # n) THEN (* we found a newline *) str[i] := '\n'; INC (i); INC (rd.cur); RETURN i; END; END; END; END; END GetSubLine; PROCEDUREGetWideSubLine (rd: T; VAR(*out*) str: ARRAY OF WIDECHAR): CARDINAL RAISES {Failure, Alerted} = VAR i: CARDINAL := 0; ch: WIDECHAR; BEGIN LOCK rd DO IF rd.closed THEN Die () END; WHILE (i < NUMBER (str)) AND GetWC (rd, ch) DO str[i] := ch; INC (i); IF ch = W'\n' THEN RETURN i; END; END; RETURN i; END; END GetWideSubLine; PROCEDUREGetText (rd: T; length: CARDINAL): TEXT RAISES { Failure, Alerted } = VAR txt: TEXT; BEGIN LOCK rd DO IF rd.closed THEN Die () END; IF (rd.lo <= rd.cur) AND (rd.hi - rd.cur >= length) THEN (* the bytes we need are already in the buffer *) txt := Text.FromChars ( SUBARRAY (rd.buff^, rd.cur - rd.lo + rd.st, length)); INC (rd.cur, length); ELSIF (NOT rd.intermittent) THEN (* we know how long the reader is... *) VAR len := MIN (length, rd.length () - rd.cur); txt8 := Text8.Create (len); BEGIN txt := txt8; EVAL FastGetSub (rd, SUBARRAY (txt8.contents^, 0, len)); END; ELSE (* general case *) txt := SlowGetText (rd, length); END; END; RETURN txt; END GetText; TYPE Buffer = REF RECORD next: Buffer; buf: ARRAY [0..2039] OF CHAR END; PROCEDURESlowGetText (rd: T; length: CARDINAL): TEXT RAISES { Failure, Alerted } = VAR copied: CARDINAL := 0; head : Buffer := NIL; tail : Buffer := NIL; BEGIN (* build a list of buffers *) LOOP IF (copied = length) THEN EXIT END; VAR b := NEW (Buffer, next := NIL); BEGIN IF (head = NIL) THEN head := b; ELSE tail.next := b; END; tail := b; END; VAR n := MIN (length - copied, NUMBER (tail.buf)); i := FastGetSub (rd, SUBARRAY (tail.buf, 0, n)); BEGIN INC (copied, i); IF (i < n) THEN EXIT END; END; END; (* assemble the result *) VAR txt := Text8.Create (copied); i := 0; n : INTEGER; BEGIN WHILE (head # NIL) DO n := MIN (copied - i, NUMBER (head.buf)); SUBARRAY (txt.contents^, i, n) := SUBARRAY (head.buf, 0, n); head := head.next; INC (i, n); END; RETURN txt; END; END SlowGetText; PROCEDUREGetWideText (rd: T; len: CARDINAL): TEXT RAISES {Failure, Alerted} = VAR res, tmp: TEXT; i, j, n_read: CARDINAL; buf: ARRAY [0..127] OF WIDECHAR; BEGIN IF (len <= NUMBER (buf)) THEN i := GetWideSub(rd, SUBARRAY(buf, 0, len)); RETURN Text.FromWideChars (SUBARRAY(buf, 0, i)); ELSE res := NIL; n_read := 0; WHILE (n_read < len) DO i := MIN (NUMBER (buf), len - n_read); j := GetWideSub(rd, SUBARRAY (buf, 0, i)); INC (n_read, j); IF (j > 0) THEN tmp := Text.FromWideChars (SUBARRAY (buf, 0, j)); IF (res = NIL) THEN res := tmp; ELSE res := res & tmp; END; END; IF (j < i) THEN EXIT; END; END; IF (res = NIL) THEN res := ""; END; RETURN res; END; END GetWideText; PROCEDUREGetLine (rd: T): TEXT RAISES {EndOfFile, Failure, Alerted} = VAR txt := ""; j, n: INTEGER; BEGIN LOCK rd DO LOOP (* INV: txt contains the partial result *) IF rd.cur = rd.hi THEN IF rd.closed THEN Die () END; IF rd.seek (rd.cur, FALSE) = SeekResult.Eof THEN IF (Text.Length (txt) > 0) THEN RETURN txt END; RAISE EndOfFile; END; END; (* rd is ready *) n := rd.hi - rd.lo + rd.st; j := rd.cur - rd.lo + rd.st; WHILE (j # n) AND rd.buff[j] # '\n' DO INC(j) END; VAR rd_cur := rd.cur - rd.lo + rd.st; len := j - rd_cur; BEGIN IF len >= 1 AND j # n AND rd.buff[j-1] = '\r' THEN (* segment ends in \r\n *) txt := txt & Text.FromChars (SUBARRAY (rd.buff^, rd_cur, len-1)); INC (rd.cur, len+1); RETURN txt; ELSIF j # n THEN (* segment ends in \n *) txt := txt & Text.FromChars (SUBARRAY (rd.buff^, rd_cur, len)); INC (rd.cur, len+1); IF NOT Text.Empty(txt) AND Text.GetChar(txt, Text.Length(txt)-1) = '\r' THEN txt := Text.Sub(txt, 0, Text.Length(txt)-1) END; RETURN txt; ELSE (* segment does not contain line break *) txt := txt & Text.FromChars (SUBARRAY (rd.buff^, rd_cur, len)); INC (rd.cur, len); END; END; END; (* LOOP *) END; END GetLine; PROCEDUREGetWideLine (rd: T): TEXT RAISES {EndOfFile, Failure, Alerted} = VAR txt, tmp : TEXT := NIL; len : CARDINAL := 0; last_ch, ch: WIDECHAR; buf : ARRAY [0..127] OF WIDECHAR; BEGIN LOCK rd DO last_ch := W' '; LOOP IF FastEOF(rd) THEN IF (txt = NIL) AND (len = 0) THEN RAISE EndOfFile; END; EXIT; END; ch := FastGetWideChar (rd); IF (ch = W'\n') THEN EXIT; END; IF len >= NUMBER (buf) THEN tmp := Text.FromWideChars (buf); IF (txt = NIL) THEN txt := tmp; ELSE txt := txt & tmp; END; len := 0; END; buf[len] := ch; INC (len); last_ch := ch; END; END; IF (ch = W'\n') AND (last_ch = W'\r') AND (len > 0) THEN (* remove the carriage return before allocating the text *) DEC (len); last_ch := W' '; END; IF (len > 0) THEN tmp := Text.FromWideChars (SUBARRAY (buf, 0, len)); IF (txt = NIL) THEN txt := tmp; ELSE txt := txt & tmp; END; END; IF (txt = NIL) THEN txt := ""; END; IF (ch = W'\n') AND (last_ch = W'\r') THEN txt := Text.Sub (txt, 0, Text.Length (txt) - 1); END; RETURN txt; END GetWideLine; PROCEDUREIntermittent (rd: T): BOOLEAN = BEGIN RETURN (rd.intermittent); END Intermittent; PROCEDURESeekable (rd: T): BOOLEAN = BEGIN RETURN (rd.seekable); END Seekable; PROCEDUREClosed (rd: T): BOOLEAN = BEGIN RETURN (rd.closed); END Closed; PROCEDURELock (rd: T) = BEGIN Thread.Acquire (rd) END Lock; PROCEDUREUnlock (rd: T) = BEGIN Thread.Release (rd) END Unlock; PROCEDURELengthDefault (<*UNUSED*> rd: T): INTEGER = BEGIN <*NOWARN*> Die() END LengthDefault; PROCEDURECloseDefault (<*UNUSED*> rd: T) = BEGIN END CloseDefault;
EXCEPTION FatalError; PROCEDUREDie () = <* FATAL FatalError *> BEGIN RAISE FatalError; END Die; BEGIN END Rd.