cm3ide/src/utils/Wx.m3


 Copyright (C) 1994, Digital Equipment Corporation           
 All rights reserved.                                        
 See the file COPYRIGHT for a full description.              
                                                             
 Last modified on Thu Dec  8 09:51:33 PST 1994 by kalsow     

MODULE Wx;

IMPORT Text, Text8, Convert, Wr, Thread, TCP;

CONST
  ChunkSize = 2 * 1024 - 3 * BYTESIZE (INTEGER);
  (* 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 = T_ BRANDED "Wx.T" OBJECT
        nFull   : INTEGER := 0;
        next    : INTEGER := 0;
        head    : Chunk   := NIL;
        tail    : Chunk   := NIL;
        drain   : TCP.T   := NIL;
      OVERRIDES
        init    := Init;
        flush   := Flush;
        put     := PutText;
        putChar := PutChar;
        putInt  := PutInt;
        putStr  := PutStr;
        putSub  := PutSub;
        toText  := ToText;
      END;

PROCEDURE Init (t: T;  drain: TCP.T): T =
  BEGIN
    t.nFull   := 0;
    t.next    := 0;
    t.drain   := drain;
    IF (t.head = NIL) THEN
      t.head := NEW (Chunk);
      t.tail := t.head;
    END;
    RETURN t;
  END Init;

PROCEDURE Flush (t: T)
  RAISES {Wr.Failure, Thread.Alerted} =
  VAR c := t.head;
  BEGIN
    IF (t.drain # NIL) THEN
      TRY
        FOR i := 1 TO t.nFull DO  t.drain.put (c.buf); c := c.next;  END;
        IF (t.next # 0) THEN  t.drain.put (SUBARRAY (c.buf, 0, t.next)); END;
      FINALLY
        Reset (t);
      END;
    END;
  END Flush;

PROCEDURE PutText (t: T;  a, b, c, d: TEXT := NIL)
  RAISES {Wr.Failure, Thread.Alerted} =
  BEGIN
    IF (a # NIL) THEN DoPutSub (t, a, 0, Text.Length (a)); END;
    IF (b # NIL) THEN DoPutSub (t, b, 0, Text.Length (b)); END;
    IF (c # NIL) THEN DoPutSub (t, c, 0, Text.Length (c)); END;
    IF (d # NIL) THEN DoPutSub (t, d, 0, Text.Length (d)); END;
  END PutText;

PROCEDURE PutSub (t: T;  txt: TEXT;  start, len: INTEGER)
  RAISES {Wr.Failure, Thread.Alerted} =
  BEGIN
    IF (txt # NIL) THEN
      start := MAX (0, start);
      len   := MIN (len, Text.Length (txt) - start);
      IF (len > 0) THEN DoPutSub (t, txt, start, len); END;
    END;
  END PutSub;

PROCEDURE DoPutSub (t: T;  a: TEXT;  start, len: INTEGER)
  RAISES {Wr.Failure, Thread.Alerted} =
  VAR buf: ARRAY [0..255] OF CHAR;
  BEGIN
    WHILE (len > 0) DO
      Text.SetChars (buf, a, start);
      WITH n = MIN (NUMBER (buf), len) DO
        PutStr (t, SUBARRAY (buf, 0, n));
        INC (start, n);
        DEC (len, n);
      END;
    END;
  END DoPutSub;

PROCEDURE PutChar (t: T;  ch: CHAR)
  RAISES {Wr.Failure, Thread.Alerted} =
  BEGIN
    IF (t.next >= ChunkSize) THEN Expand (t) END;
    t.tail.buf[t.next] := ch;
    INC (t.next);
  END PutChar;

PROCEDURE PutStr (t: T;  READONLY x: ARRAY OF CHAR)
  RAISES {Wr.Failure, Thread.Alerted} =
  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 PutStr;

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

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

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

PROCEDURE PutInt  (t: T;  i: INTEGER)
  RAISES {Wr.Failure, Thread.Alerted} =
  BEGIN
    IF (i < 0) THEN
      PutBigInt (t, i);
    ELSIF (i < 10) THEN
      PutChar (t, digits[i]);
    ELSIF (i < 100) THEN
      PutChar (t, digits_100B[i]);
      PutChar (t, digits_100A[i]);
    ELSIF (i < 1000) THEN
      PutChar (t, digits[i DIV 100]);   i := i MOD 100;
      PutChar (t, digits_100B[i]);
      PutChar (t, digits_100A[i]);
    ELSE
      PutBigInt (t, i);
    END;
  END PutInt;

PROCEDURE PutBigInt  (t: T;  i: INTEGER)
  RAISES {Wr.Failure, Thread.Alerted} =
  <*FATAL Convert.Failed*>
  VAR
    buf : ARRAY [0..BITSIZE(INTEGER) + 3] OF CHAR;
    len := Convert.FromInt (buf, i, 10);
  BEGIN
    PutStr (t, SUBARRAY (buf, 0, len));
  END PutBigInt;

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;
------------------------------------ internal ----------------------------

PROCEDURE Expand (t: T)
  RAISES {Wr.Failure, Thread.Alerted} =
  BEGIN
    <* ASSERT t.next = ChunkSize *>
    IF (t.drain # NIL) THEN
      t.next := 0;
      INC (t.nFull); (* for the flush... *)
      Flush (t);
    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 Wx.

interface Wx is in: