-- Transport mechanism: local terminal simulation of TELNET interface

-- [Juniper]<DMS>MS>LocalGlass.mesa

-- Andrew Birrell   7-Jan-81 17:17:52

DIRECTORY
Ascii,
GlassDefs	USING[ HandleObject, Handle, StringType ],
InlineDefs	USING[ BITAND, BITOR ],
IODefs,
ProcessDefs	USING[ Abort, SecondsToTicks, SetTimeout, Yield ],
PupDefs		USING[ PupSocketID ],
StreamDefs	USING[ StreamHandle ],
StringDefs	USING[ AppendDecimal, AppendLongDecimal ];

LocalGlass: MONITOR
IMPORTS InlineDefs, IODefs, ProcessDefs, StringDefs
EXPORTS GlassDefs =

BEGIN

TimeOut:      PUBLIC SIGNAL = CODE;
SynchReply:   PUBLIC SIGNAL = CODE;

ReadString: PUBLIC PROC[str: GlassDefs.Handle, prompt: STRING,
                        s: STRING, type: GlassDefs.StringType]
                RETURNS[end: CHARACTER] =
   BEGIN
   OPEN str;
   dummy: CHARACTER = '*; --used for echo if type = pwd--
   IODefsStream: StreamDefs.StreamHandle = IODefs.GetOutputStream[];
   ShowIt: PROC =
      BEGIN
      IF type # pwd
      THEN WriteString[s]
      ELSE FOR i: CARDINAL IN [0..s.length) DO WriteChar[dummy] ENDLOOP;
      END;
   Unwrite: PROCEDURE =
      BEGIN
      IF s.length > 0
      THEN BEGIN
           old: CHARACTER = IF type = pwd THEN dummy ELSE s[s.length-1];
           WITH IODefsStream SELECT FROM
             Display => clearChar[IODefsStream, old];
           ENDCASE => { WriteChar['\]; WriteChar[old] };
           s.length ← s.length - 1;
           END;
      END;
   ClearWord: PROCEDURE =
      BEGIN
      state: { alpha, other } ← other;
      WHILE s.length > 0
      DO SELECT s[s.length-1] FROM
           IN ['a..'z], IN ['A..'Z], IN ['0..'9] => state ← alpha;
         ENDCASE => IF state # other THEN EXIT;
         Unwrite[];
      ENDLOOP;
      END;
   c: CHARACTER;
   WriteString[prompt]; ShowIt[];
   c ← ReadChar[];
   SELECT c FROM
     Ascii.ControlA, Ascii.BS, Ascii.ControlW, --client wants to edit it--
     Ascii.SP, Ascii.CR, Ascii.DEL => NULL; --client accepts it--
   ENDCASE =>--client rejects it--
     IF s.length > 0
     THEN BEGIN
          IF (WITH IODefsStream SELECT FROM Display=>TRUE,ENDCASE => FALSE)
          THEN WHILE s.length > 0 DO Unwrite[] ENDLOOP
          ELSE { WriteString["← "L]; s.length ← 0; };
          END;
   DO SELECT c FROM
         Ascii.ControlA, Ascii.BS => Unwrite[];
         Ascii.ControlW => ClearWord[];
         Ascii.ControlR =>
           { WriteChar[Ascii.CR]; WriteString[prompt]; ShowIt[] };
      ENDCASE =>
        BEGIN
        SELECT c FROM
          Ascii.SP => IF type # line AND type # any THEN { end ← c; EXIT };
          Ascii.CR => IF type # any THEN { end ← c; EXIT };
          Ascii.ESC, Ascii.DEL => { end ← c; EXIT };
        ENDCASE => NULL;
        IF s.length < s.maxlength
        THEN BEGIN
             s[s.length] ← c; s.length ← s.length+1;
             WriteChar[IF type = pwd THEN dummy ELSE c];
             END
        ELSE WriteChar[Ascii.BEL];
        END;
      c ← ReadChar[];
   ENDLOOP;
   END;

Listen: PUBLIC PROC[ work: PROC[GlassDefs.Handle],
                     socket: PupDefs.PupSocketID ← [0,0] ] =
   BEGIN
      BEGIN
      -- Note that there is an over-all assumption that the client calls
      -- the glass stream from only one process.  The monitor locks are
      -- used only to synchronize between the "Reader" process and the
      -- client.
      readerPSB: PROCESS;
      -- we maintain a circular buffer of incoming characters,
      -- primarily to look ahead for DEL --
      -- rPos = next index for reading from buffer --
      -- wPos = next index for writing into buffer --
      -- rPos = wPos iff buffer is empty --
      -- (wPos+1)MOD bLength = rPos iff buffer is full --
      -- buffer data has "markBit" on iff datum is a "Mark" byte --
      charMask: WORD = 177B;
      markBit:  WORD = 200B;
      bLength: CARDINAL = 100;
      buffer: PACKED ARRAY [0..bLength) OF CHARACTER;
      rPos: CARDINAL ← 0;
      wPos: CARDINAL ← 0;
      bChange: CONDITION;
      delCount: CARDINAL ← 0;
      charsWritten: BOOLEAN ← FALSE;--chars written but not sent--
      ChangeWPos: ENTRY PROC[change: CARDINAL] = INLINE
         BEGIN
         ENABLE UNWIND => NULL;
         IF rPos = wPos THEN NOTIFY bChange;
         wPos ← wPos + change; IF wPos = bLength THEN wPos ← 0;
         END;
      WLimit: ENTRY PROC RETURNS[ limit: CARDINAL ] = INLINE
         BEGIN
         ENABLE UNWIND => NULL;
         WHILE (limit ← IF wPos >= rPos
                        THEN IF rPos = 0 THEN bLength-1 ELSE bLength
                        ELSE rPos-1 )
               = wPos
         DO WAIT bChange ENDLOOP;
         END;
      AddDel: ENTRY PROC = INLINE
         BEGIN
         ENABLE UNWIND => NULL;
         delCount ← delCount + 1;
         END;
      GetByte: ENTRY PROC RETURNS[ c: UNSPECIFIED ] =
         BEGIN
         ENABLE UNWIND => NULL;
         WHILE rPos = wPos
         DO IF charsWritten
            THEN { charsWritten ← FALSE };
            WAIT bChange;
            IF rPos = wPos THEN SIGNAL TimeOut[];
         ENDLOOP;
         c ← buffer[rPos];
         rPos ← rPos+1; IF rPos = bLength THEN rPos ← 0;
         NOTIFY bChange; -- in case buffer was full --
         IF c = Ascii.DEL THEN delCount ← delCount-1;
         END;
      Reader: PROC =
         BEGIN
         ENABLE ABORTED => CONTINUE;
         DO -- always read one character --
            used: CARDINAL = 1;
            [] ← WLimit[];
            buffer[wPos] ← IODefs.ReadChar[];
            FOR index: CARDINAL IN [wPos..wPos+used)
            DO IF (buffer[index]←InlineDefs.BITAND[buffer[index], charMask])
                  = Ascii.DEL
               THEN AddDel[];
            ENDLOOP;
            ChangeWPos[used];
         ENDLOOP;
         END;
      lineWidth:  CARDINAL ← 0;
      pageHeight: CARDINAL ← 0;
      terminal:   CARDINAL ← 0;
      charPos:    CARDINAL ← 0;
      linePos:    CARDINAL ← 0;
      ConsiderSST: PROC[thisSST: [0..128)] =
         BEGIN
         SELECT thisSST FROM
            1 => -- data mark -- NULL;
            2 => lineWidth ← GetByte[];
            3 => pageHeight ← GetByte[];
            4 => terminal ← GetByte[];
            5 => NULL;
            6 => SIGNAL SynchReply[];
         ENDCASE => NULL--ignore--;
         END;
      ReadChar: PROC RETURNS[c: CHARACTER] =
         BEGIN
         DO c ← GetByte[];
            IF InlineDefs.BITAND[c, markBit] # 0
            THEN ConsiderSST[InlineDefs.BITAND[c, charMask]]
            ELSE EXIT
         ENDLOOP;           
         linePos ← 0; -- only count lines between input operations --           
         END;
      MyReadString: PROC[prompt: STRING,
                         s: STRING, type: GlassDefs.StringType]
                 RETURNS[end: CHARACTER] =
         { end ← ReadString[@obj, prompt, s, type] };
      WriteChar: PROC[c: CHARACTER] =
         BEGIN
         IF delCount # 0 THEN RETURN;
         charsWritten←TRUE; ProcessDefs.Yield[]; IODefs.WriteChar[c];
         END;
      WriteString: PROC[s: STRING] =
         BEGIN
         IF delCount # 0 THEN RETURN;
         charsWritten←TRUE; ProcessDefs.Yield[]; IODefs.WriteString[s];
         END;
      WriteDecimal: PROC[n: CARDINAL] =
         BEGIN
         s: STRING = [6] -- -65536 --;
         StringDefs.AppendDecimal[s,n];
         WriteString[s];
         END;
      WriteLongDecimal: PROC[n: LONG CARDINAL] =
         BEGIN
         s: STRING = [11] -- -6553665536 --;
         StringDefs.AppendLongDecimal[s,n];
         WriteString[s];
         END;
      NoteSent: ENTRY PROC = INLINE
         { charsWritten ← FALSE };
      SendNow: PROC =
         { NoteSent[] };
      CharsLeft: PROC RETURNS[CARDINAL] =
         { RETURN[IF lineWidth > 0
                  THEN lineWidth-charPos ELSE LAST[CARDINAL] ] };
      LinesLeft: PROC RETURNS[CARDINAL] =
         { RETURN[IF pageHeight > 0
                  THEN pageHeight-linePos ELSE LAST[CARDINAL] ] };
      SetWidth: PROC[new: CARDINAL] =
         { lineWidth ← new };
      SetHeight: PROC[new: CARDINAL] =
         { pageHeight ← new };
      DelTyped: ENTRY PROC RETURNS[ BOOLEAN ] =
         { RETURN[ delCount # 0 ] };
      Synch: PROC =
         { buffer[wPos] ← InlineDefs.BITOR[6, markBit];
           ChangeWPos[1] };
      Flush: PROC =
         { WHILE delCount > 0 DO [] ← ReadChar[] ENDLOOP };
      obj: GlassDefs.HandleObject ←
            [ReadChar, MyReadString,
             WriteChar, WriteString, WriteDecimal, WriteLongDecimal,
             SendNow, CharsLeft, LinesLeft, SetWidth, SetHeight,
             DelTyped, Synch, Flush];
      ProcessDefs.SetTimeout[@bChange,
            ProcessDefs.SecondsToTicks[150] ];
      readerPSB ← FORK Reader[];
      BEGIN
         ENABLE TimeOut => RESUME;
         work[@obj ! SynchReply => RESUME];
      END--for cleanup--;
      ProcessDefs.Abort[readerPSB];
      JOIN readerPSB;
      END;
   END;
   

END.