-- 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.