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 := '\x00'; 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; PROCEDURE GetSub (rd: T; VAR (*out*) str: ARRAY OF CHAR): CARDINAL RAISES {Failure, Alerted} = BEGIN LOCK rd DO RETURN FastGetSub(rd, str); END END GetSub; PROCEDURE FastGetSub (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; PROCEDURE GetWideSub (rd: T; VAR (*out*) str: ARRAY OF WIDECHAR): CARDINAL RAISES {Failure, Alerted} = BEGIN LOCK rd DO RETURN FastGetWideSub(rd, str); END; END GetWideSub; PROCEDURE FastGetWideSub (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; PROCEDURE GetSubDefault (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 EOF (rd: T): BOOLEAN
  RAISES {Failure, Alerted} =
  (* rd is unlocked *)
  BEGIN
    LOCK rd DO
      RETURN FastEOF(rd);
    END
  END EOF;
<*INLINE*>
PROCEDURE FastEOF (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;
PROCEDURE UnGetChar(rd: T) =
  BEGIN
    LOCK rd DO FastUnGetChar (rd) END;
  END UnGetChar;
PROCEDURE FastUnGetChar(rd: T) =
  BEGIN
    IF rd.closed OR rd.cur = rd.lo THEN Die() END;
    DEC(rd.cur)
  END FastUnGetChar;
PROCEDURE CharsReady(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;
PROCEDURE Index(rd: T): CARDINAL =
  BEGIN
    LOCK rd DO
      IF rd.closed THEN Die() END;
      RETURN rd.cur
    END
  END Index;
PROCEDURE Length(rd: T): INTEGER
  RAISES {Failure, Alerted} =
  BEGIN
    LOCK rd DO
      IF rd.closed THEN Die() END;
      RETURN rd.length()
    END
  END Length;
PROCEDURE Seek(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;
PROCEDURE Close(rd: T)
  RAISES {Failure, Alerted} =
  BEGIN
    LOCK rd DO FastClose (rd); END;
  END Close;
PROCEDURE FastClose(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;
PROCEDURE GetSubLine (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;
PROCEDURE GetWideSubLine (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;
PROCEDURE GetText (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;
PROCEDURE SlowGetText (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;
PROCEDURE GetWideText(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;
PROCEDURE GetLine (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;
PROCEDURE GetWideLine (rd: T): TEXT
  RAISES {EndOfFile, Failure, Alerted} =
  VAR
    txt, tmp : TEXT := NIL;
    len : CARDINAL := 0;
    last_ch, ch: WIDECHAR := W'\x0000';
    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