(*
    Title:	TermStream- Implementation
    LastEdit:	"Wed Oct 31 15:47:29 1984"
    Author:	Mick Jordan
		Cambridge University Computer Laboratory

    NS32016/Panos version - uses Panos error stream
*)

(* $T-,$R- *)

IMPLEMENTATION MODULE TermStream;


FROM SYSTEM IMPORT WORD;
FROM Storage IMPORT ALLOCATE, DEALLOCATE;
FROM Streams IMPORT
  Create, Stream, StreamProcs, ErrorCode, OptionalFunction;
FROM StreamRep IMPORT Procs, DefaultProcs, SetStreamError;
IMPORT BadStream;

IMPORT IO;


TYPE
  TermStreamType = (Terminal, Error);

  Status =
    RECORD
      CASE opened: BOOLEAN OF
	TRUE: stream: Stream;
      | FALSE:
      END;
      procs: Procs;
    END;

  StreamData = POINTER TO
    RECORD
      io: CARDINAL;
      byte: INTEGER;
      pending: BOOLEAN;
    END;


VAR status: ARRAY TermStreamType OF Status;
(* only ever two streams to terminal - global so can return same streams on
   repeated calls of 'CreateInOutput' and 'CreateOutputForErrors'
*)


PROCEDURE StreamErrorCode(s: Stream): INTEGER;
  BEGIN
    RETURN INTEGER(s^.streamError);
  END StreamErrorCode;


PROCEDURE TermImplements(s: Stream; f: OptionalFunction): BOOLEAN;
  BEGIN
    RETURN FALSE;
  END TermImplements;


PROCEDURE TermBackspace(s: Stream);

  VAR d: StreamData;

  BEGIN
    d := StreamData(s^.instanceData);
    d^.pending := TRUE;
  END TermBackspace;


PROCEDURE TermGet(s: Stream): WORD;

  VAR d: StreamData;

  BEGIN
    d := StreamData(s^.instanceData);
    IF d^.pending THEN
      d^.pending := FALSE;
    ELSE d^.byte := IO.SReadByte(d^.io);
      IF d^.byte < 0 THEN
        SetStreamError(s, d^.byte);
        IF IO.EndOfFile(d^.io) > 0 THEN s^.status := EndOfStream END;
	d^.byte := 0;
      END;
    END;
    RETURN WORD(d^.byte);
  END TermGet;


PROCEDURE TermPut(s: Stream; ch: WORD);

  VAR d: StreamData;
    r: INTEGER;

  BEGIN
    d := StreamData(s^.instanceData);
    r := IO.SWriteByte(d^.io, CARDINAL(ch));
    IF r < 0 THEN SetStreamError(s, r) END;
  END TermPut;


PROCEDURE TermDelete(s: Stream);

  VAR d: StreamData;
    r: INTEGER;

  BEGIN
    TermFlush(s);
    d := StreamData(s^.instanceData);              
    r := IO.CloseStream(d^.io);
    IF r < 0 THEN SetStreamError(s, r) END;
    status[Terminal].opened := FALSE;
    DISPOSE(d);
  END TermDelete;


PROCEDURE TermFlush(s: Stream);

  VAR d: StreamData;
    r: INTEGER;

  BEGIN
    d := StreamData(s^.instanceData);
    r := IO.SFlushOutput(d^.io);
    IF r < 0 THEN SetStreamError(s, r) END;
  END TermFlush;


PROCEDURE TermProcs(): Procs;

  VAR p: Procs;

  BEGIN
    p := DefaultProcs(8);
    p^.Put := TermPut; 
    p^.Get := TermGet;
    p^.Backspace := TermBackspace;
    p^.Flush := TermFlush;
    p^.Delete := TermDelete;
    p^.Implements := TermImplements;
    RETURN p;
  END TermProcs;


PROCEDURE MakeStreamData(s: CARDINAL): StreamData;

  VAR d: StreamData;

  BEGIN
    NEW(d);
    d^.io := s;
    d^.pending := FALSE;
    d^.byte := 0;
    RETURN d;
  END MakeStreamData;


PROCEDURE CreateInOutput(): Stream;

  VAR r: INTEGER;
    s: Stream;

  BEGIN
    WITH status[Terminal] DO
      IF NOT opened THEN
        r := IO.FindUpdate("tt:");
	IF r < 0 THEN
	  s := BadStream.CreateInOutput();
	  SetStreamError(s, r);
	  RETURN s;
	ELSE opened := TRUE;
	  stream :=
	    Create(StreamProcs(procs), MakeStreamData(CARDINAL(r)));
	  RETURN stream;
	END;
      ELSE RETURN stream;
      END;
    END;
  END CreateInOutput;


PROCEDURE ErrorImplements(s: Stream; f: OptionalFunction): BOOLEAN;
  BEGIN
    RETURN FALSE;
  END ErrorImplements;


PROCEDURE ErrorPut(s: Stream; ch: WORD);

  VAR err, r: INTEGER;

  BEGIN
    err := IO.ErrorStream();
    IF err >= 0 THEN
      r := IO.SWriteByte(CARDINAL(err), CARDINAL(ch));
      IF r < 0 THEN SetStreamError(s, r) END;
    ELSE SetStreamError(s, err);
    END;
  END ErrorPut;


PROCEDURE ErrorDelete(s: Stream);
  BEGIN         
    ErrorFlush(s);
    status[Error].opened := FALSE;
  END ErrorDelete;


PROCEDURE ErrorFlush(s: Stream);

  VAR err, r: INTEGER;

  BEGIN
    err := IO.ErrorStream();
    IF err >= 0 THEN
      r := IO.SFlushOutput(CARDINAL(err));
      IF r < 0 THEN SetStreamError(s, r) END;
    ELSE SetStreamError(s, err);
    END;
  END ErrorFlush;


PROCEDURE CreateOutputForErrors(): Stream;
  BEGIN                       
    WITH status[Error] DO
      IF NOT opened THEN
        stream := Create(StreamProcs(procs), 0);
      END;
      RETURN stream;
    END;
  END CreateOutputForErrors;


PROCEDURE ErrorProcs(): Procs;

  VAR p: Procs;

  BEGIN
    p := DefaultProcs(8);
    p^.Put := ErrorPut; 
    p^.Flush := ErrorFlush;
    p^.Delete := ErrorDelete;
    p^.Implements := ErrorImplements;
    RETURN p;
  END ErrorProcs;


BEGIN
  status[Terminal].opened := FALSE;
  status[Terminal].procs := TermProcs();
  status[Error].opened := FALSE;
  status[Error].procs := ErrorProcs();
END TermStream .
