m3middle/src/M3Buf.m3


 Copyright (C) 1994, Digital Equipment Corporation           
 All rights reserved.                                        
 See the file COPYRIGHT for a full description.              
                                                             
 File: M3Buf.m3                                              
 Last modified on Tue Sep 27 09:28:40 PDT 1994 by kalsow     
      modified on Tue May 25 14:34:11 PDT 1993 by muller     

MODULE M3Buf;

IMPORT Wr, Convert, Text, Text8, Thread, Target, TInt, M3FP;

CONST
  ChunkSize = 16_FF00; (* approx. 64K *)
  (* leave some slop for the 'next' pointer & the allocator overhead *)

TYPE
  Chunk = REF RECORD
            next : Chunk := NIL;
            buf  : ARRAY [0..ChunkSize-1] OF CHAR;
          END;

REVEAL
  T = BRANDED "MBuf.T" REF RECORD
        nFull : INTEGER;
        next  : INTEGER;
        head  : Chunk;
        tail  : Chunk;
        drain : Wr.T;
      END;

PROCEDURE New (): T =
  VAR t := NEW (T);
  BEGIN
    t.nFull := 0;
    t.next  := 0;
    t.head  := NEW (Chunk);
    t.tail  := t.head;
    t.drain := NIL;
    RETURN t;
  END New;

PROCEDURE PutChar (t: T;  ch: CHAR) =
  BEGIN
    IF (t.next >= ChunkSize) THEN Expand (t) END;
    t.tail.buf[t.next] := ch;
    INC (t.next);
  END PutChar;

PROCEDURE PutSub (t: T;  READONLY x: ARRAY OF CHAR) =
  VAR
    next := 0;
    len  := NUMBER (x);
    n : INTEGER;
  BEGIN
    IF (len < ChunkSize - t.next) THEN
      SUBARRAY (t.tail.buf, t.next, len) := x;
      INC (t.next, len);
    ELSE
      WHILE (len > 0) DO
        n := MIN (len, ChunkSize - t.next);
        SUBARRAY (t.tail.buf, t.next, n) := SUBARRAY (x, next, n);
        DEC (len, n);
        INC (next, n);
        INC (t.next, n);
        IF (len > 0) THEN Expand (t) END;
      END;
    END;
  END PutSub;

PROCEDURE PutText (t: T;  txt: TEXT) =
  BEGIN
    PutSubText (t, txt, 0, LAST (CARDINAL));
  END PutText;

PROCEDURE PutSubText (t: T;  txt: TEXT;  start, len: CARDINAL) =
  VAR
    cnt := MIN (len, Text.Length (txt) - start);
    buf : ARRAY [0..255] OF CHAR;
  BEGIN
    WHILE (cnt > 0) DO
      Text.SetChars (buf, txt, start);
      PutSub (t, SUBARRAY (buf, 0, MIN (NUMBER (buf), cnt)));
      INC (start, NUMBER (buf));  DEC (cnt, NUMBER (buf));
    END;
  END PutSubText;

CONST digits = ARRAY [0..9] OF CHAR {'0','1','2','3','4','5','6','7','8','9'};

PROCEDURE PutInt  (t: T;  i: INTEGER) =
  <*FATAL Convert.Failed*>
  VAR len: INTEGER;  buf: ARRAY [0..BITSIZE(INTEGER) + 3] OF CHAR;
  BEGIN
    IF (i >= 0) THEN
      IF (i < 10) THEN
        PutChar (t, digits[i]);
        RETURN;
      ELSIF (i < 100) THEN
        PutChar (t, digits[i DIV 10]);
        PutChar (t, digits[i MOD 10]);
        RETURN;
      ELSIF (i < 1000) THEN
        PutChar (t, digits[i DIV 100]);
        PutChar (t, digits[(i DIV 10) MOD 10]);
        PutChar (t, digits[i MOD 10]);
        RETURN;
      END;
    END;
    len := Convert.FromInt (buf, i, 10);
    PutSub (t, SUBARRAY (buf, 0, len));
  END PutInt;

PROCEDURE PutIntt (t: T;  READONLY i: Target.Int) =
  VAR j: INTEGER;
      buf: ARRAY [0..BITSIZE (Target.Int)] OF CHAR;
  BEGIN
    IF TInt.ToInt (i, j) THEN
      PutInt (t, j);
    ELSE
      j := TInt.ToChars (i, buf);
      PutSub (t, SUBARRAY (buf, 0, j));
    END;
  END PutIntt;

PROCEDURE PutFloat (t: T;  READONLY f: Target.Float) =
  <*FATAL Convert.Failed*>
  VAR len: INTEGER;  buf: ARRAY [0..BITSIZE(EXTENDED) + 3] OF CHAR;
  BEGIN
    <*ASSERT f.exponent = 0*>
    len := Convert.FromExtended (buf, f.fraction, 13, Convert.Style.Sci);
    PutSub (t, SUBARRAY (buf, 0, len));
  END PutFloat;

PROCEDURE ToText (t: T): TEXT =
  VAR txt: TEXT;
  BEGIN
    IF (t.nFull = 0)
      THEN txt := Text.FromChars (SUBARRAY (t.head.buf, 0, t.next));
      ELSE txt := MessyToText (t);
    END;
    Reset (t);
    RETURN txt;
  END ToText;

PROCEDURE MessyToText (t: T): TEXT =
  VAR
    len := t.nFull * ChunkSize + t.next;
    txt := Text8.Create (len);
    c := t.head;
    n := 0;
  BEGIN
    FOR i := 1 TO t.nFull DO
      SUBARRAY (txt.contents^, n, ChunkSize) := c.buf;
      c := c.next;
      INC (n, ChunkSize);
    END;
    IF (t.next # 0) THEN
      SUBARRAY (txt.contents^, n, t.next) := SUBARRAY (c.buf, 0, t.next);
    END;
    RETURN txt;
  END MessyToText;

PROCEDURE ToFP (t: T): M3FP.T =
  VAR fp := M3FP.OfEmpty;
  VAR c := t.head;
  BEGIN
    FOR i := 1 TO t.nFull DO
      fp := M3FP.FromChars (c.buf, fp);
      c := c.next;
    END;
    IF (t.next # 0) THEN
      fp := M3FP.FromChars (SUBARRAY (c.buf, 0, t.next), fp);
    END;
    Reset (t);
    RETURN fp;
  END ToFP;

PROCEDURE Flush (t: T;  wr: Wr.T) =
  <*FATAL Wr.Failure, Thread.Alerted*>
  VAR c := t.head;
  BEGIN
    FOR i := 1 TO t.nFull DO  Wr.PutString (wr, c.buf);  c := c.next;  END;
    IF (t.next # 0) THEN Wr.PutString (wr, SUBARRAY (c.buf, 0, t.next)) END;
    Reset (t);
  END Flush;
******* PROCEDURE Append (src, dest: T) = VAR c := src.head; BEGIN FOR i := 1 TO src.nFull DO PutSub (dest, c.buf); c := c.next; END; IF (src.next # 0) THEN PutSub (dest, SUBARRAY (c.buf, 0, src.next)) END; Reset (src); END Append; *********

PROCEDURE AttachDrain (t: T;  wr: Wr.T) =
  BEGIN
    t.drain := wr;
  END AttachDrain;
------------------------------------ internal ----------------------------

PROCEDURE Expand (t: T) =
  BEGIN
    <* ASSERT t.next = ChunkSize *>
    IF (t.drain # NIL) THEN
      t.next := 0;
      INC (t.nFull);
      Flush(t, t.drain);
    ELSE
      IF (t.tail.next = NIL) THEN t.tail.next := NEW (Chunk); END;
      t.tail := t.tail.next;
      t.next := 0;
      INC (t.nFull);
    END;
  END Expand;

PROCEDURE Reset (t: T) =
  BEGIN
    (* NOTE: we're not freeing the allocated chunks... *)
    t.tail  := t.head;
    t.nFull := 0;
    t.next  := 0;
  END Reset;

BEGIN
END M3Buf.