-- JaMIO.mesa
-- Written by John Warnock, January, 1979.
-- Last changed by Doug Wyatt, January 12, 1981 5:43 PM

DIRECTORY
JaMIODefs
USING [EditedHandle],
JaMMasterDefs
USING [Frame, Object, Stack],
JaMControlDefs
USING [GetCurrentFrame, NotifyCommand, NotifyStringObject],
JaMExecDefs
USING [JaMError],
JaMFnsDefs
USING [PopInteger, PopObject, PopString, PushBoolean, PushInteger],
JaMInterruptDefs
USING [SetDisplayOn],
JaMLiteralDefs
USING [StreamLit],
JaMStackDefs
USING [Exch, Pop, Push, StackForall],
JaMTypeChkDefs
USING [DescStreamType, DescStringType],
JaMVMDefs
USING [AllocateCharsVM, GetCharsVM, GetCharVM, PutCharVM],
Ascii
USING [CR, ESC, LF, NUL],
Inline
USING [BITAND, BITXOR],
IODefs
USING [GetInputStream, GetOutputStream, LineOverflow,
ReadEditedString, Rubout, SetInputStream, SetOutputStream, WriteChar],
KeyDefs
USING [Mouse, MouseButton],
Process
USING [Yield],
SegmentDefs
USING [FileNameError],
Storage
USING [Node, String, Free, FreeString],
StreamDefs
USING [CloseKeyStream, CreateKeyStream, NewByteStream, NewWordStream,
OpenKeyStream, StreamError, StreamHandle, StreamObject, Read],
String
USING [AppendChar, AppendString, StringBoundsFault];

JaMIO: PROGRAM
IMPORTS JaMFnsDefs,JaMTypeChkDefs,JaMLiteralDefs,JaMExecDefs,
vm: JaMVMDefs,JaMControlDefs,JaMStackDefs,JaMInterruptDefs,
Inline,IODefs,Process,SegmentDefs,Storage,StreamDefs,String
EXPORTS JaMIODefs
SHARES StreamDefs =
BEGIN OPEN StreamDefs,JaMMasterDefs,JaMStackDefs;

-- RdLine and WrtString get and put StringType Objects on the stack.
ReadLine: PUBLIC PROCEDURE = {
c: CHARACTER;
i: CARDINAL←0;
frame: Frame ← JaMControlDefs.GetCurrentFrame[];
stringob: StringType Object←[lit,StringType[,,]];
streamob: StreamType Object ←
JaMTypeChkDefs.DescStreamType[Pop[frame.opstk]];
[stringob.Address,stringob.Offset] ← vm.AllocateCharsVM[0];
DO
IF streamob.SHandle.endof[streamob.SHandle] THEN {
IF i = 0 THEN {
JaMFnsDefs.PushBoolean[FALSE];
streamob.SHandle.destroy[streamob.SHandle];
RETURN;
}
ELSE {
stringob.Length←i;
[,]←vm.AllocateCharsVM[i];
Push[stringob,frame.opstk];
JaMFnsDefs.PushBoolean[TRUE];
RETURN;
};
}
ELSE {
c←streamob.SHandle.get[streamob.SHandle];
IF c = 15C THEN {
stringob.Length←i;
[,]←vm.AllocateCharsVM[i];
Push[stringob,frame.opstk];
JaMFnsDefs.PushBoolean[TRUE];
RETURN;
};
vm.PutCharVM[c,stringob.Address,stringob.Offset,i];
i←i+1;
};
ENDLOOP;
};

ReadItem
: PUBLIC PROCEDURE = {
frame:Frame ←JaMControlDefs.GetCurrentFrame[];
streamob: StreamType Object ←
JaMTypeChkDefs.DescStreamType[Pop[frame.opstk]];
IF streamob.SHandle.endof[streamob.SHandle] THEN {
JaMFnsDefs.PushBoolean[FALSE];
streamob.SHandle.destroy[streamob.SHandle];
RETURN;
}
ELSE {
JaMFnsDefs.PushInteger[
LOOPHOLE[streamob.SHandle.get[streamob.SHandle],INTEGER]];
JaMFnsDefs.PushBoolean[TRUE];
};
};


WriteItem
: PUBLIC PROCEDURE = {
i:INTEGER;
streamob: StreamType Object;
frame:Frame ←JaMControlDefs.GetCurrentFrame[];
i ← JaMFnsDefs.PopInteger[];
streamob ← JaMTypeChkDefs.DescStreamType[Pop[frame.opstk]];
streamob.SHandle.put[streamob.SHandle,i];
};

WrtString: PUBLIC PROCEDURE[stack: Stack] = {
i:CARDINAL;
c:CHARACTER;
stream: StreamHandle;
stringob: StringType Object;
stringob ← JaMTypeChkDefs.DescStringType[Pop[stack]];
stream ← JaMTypeChkDefs.DescStreamType[Pop[stack]].SHandle;
FOR i IN [0..stringob.Length) DO
c←vm.GetCharVM[stringob.Address,stringob.Offset,i];
stream.put[stream,c];
ENDLOOP;
};


WriteByteStream
: PUBLIC PROCEDURE = {
frame: Frame ← JaMControlDefs.GetCurrentFrame[];
WrtString[frame.opstk];
};


Print: PUBLIC PROCEDURE = {
frame:Frame ← JaMControlDefs.GetCurrentFrame[];
JaMLiteralDefs.StreamLit[IODefs.GetOutputStream[],frame.opstk];
Exch[frame.opstk];
WrtString[frame.opstk];
};

Run: PUBLIC PROCEDURE = {
frame: Frame ← JaMControlDefs.GetCurrentFrame[];
s: STRING ← [256];
strm: StreamDefs.StreamHandle;
JaMFnsDefs.PopString[s];
strm ← NewByteStream[s,Read ! SegmentDefs.FileNameError =>
IF AddExtension[s,".jam"] THEN RETRY
ELSE JaMExecDefs.JaMError[BadFileName,TRUE]];
strm ← MakeIntoEditedStream[strm];
Push[[nolit,StreamType[strm]],frame.execstk];
};

AddExtension: PROC[s: STRING, ext: STRING] RETURNS[BOOLEAN] = {
FOR i: CARDINAL IN[0..s.length) DO
IF s[i]=’. THEN RETURN[FALSE];
ENDLOOP;
IF s.length+ext.length>s.maxlength THEN RETURN[FALSE];
FOR j: CARDINAL IN[0..ext.length) DO
s[s.length]←ext[j]; s.length←s.length+1;
ENDLOOP;
RETURN[TRUE];
};

-- NByteStream, given the file name and options,
-- returns a stream on the stack.

NByteStream: PUBLIC PROCEDURE = {
s: STRING ← [256];
frame: Frame ← JaMControlDefs.GetCurrentFrame[];
i: INTEGER ← JaMFnsDefs.PopInteger[];
ob: StringType Object ← JaMTypeChkDefs.DescStringType[Pop[frame.opstk]];
strm: StreamDefs.StreamHandle;
IF ob.Length > 256 THEN JaMExecDefs.JaMError[LongFileName,TRUE];
vm.GetCharsVM[ob.Address,ob.Offset,@s.text,0,ob.Length];
s.length ← ob.Length;
strm ← NewByteStream[s,i ! SegmentDefs.FileNameError =>
JaMExecDefs.JaMError[BadFileName,TRUE]];
-- Check if for reading - kludge for now
IF Inline.BITAND[i,1] = 1 THEN strm ← MakeIntoEditedStream[strm];
Push[[lit,StreamType[strm]],frame.opstk];
};


-- NWordStream, given the file name and options,
-- returns a stream on the stack.

NWordStream: PUBLIC PROCEDURE = {
s: STRING ← [256];
frame: Frame ←JaMControlDefs.GetCurrentFrame[];
i: INTEGER ← JaMFnsDefs.PopInteger[];
ob: StringType Object ← JaMTypeChkDefs.DescStringType[Pop[frame.opstk]];
strm: StreamDefs.StreamHandle;
IF ob.Length > 256 THEN
ERROR JaMExecDefs.JaMError[LongFileName,TRUE];
vm.GetCharsVM[ob.Address,ob.Offset,@s.text,0,ob.Length];
s.length ← ob.Length;
strm ← NewWordStream[s,i ! SegmentDefs.FileNameError =>
ERROR JaMExecDefs.JaMError[BadFileName,TRUE]];
Push[[lit,StreamType[strm]],frame.opstk];
};


NKeyStream
: PUBLIC PROCEDURE = {
Push[[lit,StreamType[MakeIntoEditedStream[CreateKeyStream[]]]],
JaMControlDefs.GetCurrentFrame[].opstk];
};


DestroyStream
: PUBLIC PROCEDURE = {
s: StreamHandle ← JaMTypeChkDefs.DescStreamType[
Pop[JaMControlDefs.GetCurrentFrame[].opstk]].SHandle;
s.destroy[s];
};

-- MyStream searches the execution stack for the first stream from the top
-- and puts the stream object on the operand stack.

MyStream: PUBLIC PROCEDURE = {
frame: Frame ← JaMControlDefs.GetCurrentFrame[];
IF JaMStackDefs.StackForall[frame.execstk,StreamChk] THEN RETURN;
ERROR JaMExecDefs.JaMError[nostream,TRUE];
};

StreamChk: PROCEDURE[ob:Object] RETURNS [ok:BOOLEAN]=
BEGIN
frame:Frame ← JaMControlDefs.GetCurrentFrame[];
WITH dob:ob SELECT FROM
StreamType => BEGIN JaMStackDefs.Push[ob,frame.opstk]; RETURN[TRUE] END;
ENDCASE => RETURN[FALSE];
END;

--** Edited streams **

streamType: TYPE = {edited, null};

edData: TYPE = POINTER TO edDataRecord;
edDataRecord: TYPE = RECORD[
stream: StreamDefs.StreamHandle,
line: STRING,
chptr: CARDINAL,
mouseXYProc: PROCEDURE[CARDINAL, CARDINAL]];
defaultLineLength: CARDINAL = 64;

cursorX: POINTER TO CARDINAL = LOOPHOLE[426B];
cursorY: POINTER TO CARDINAL = LOOPHOLE[427B];

MakeIntoEditedStream: PUBLIC PROCEDURE [stream: StreamDefs.StreamHandle]
RETURNS [edstream: JaMIODefs.EditedHandle] =
-- Takes a stream as input and returns another stream which will be
--
edited (by IODefs.ReadEditedStream) on a line by line basis.
BEGIN
eD: edData ← Storage.Node[SIZE[edDataRecord]];
eD↑ ← [
stream: stream,
line: Storage.String[defaultLineLength],
chptr: 1,
mouseXYProc: DefaultMouseXYProc];
edstream ← Storage.Node[SIZE[StreamDefs.StreamObject]];
edstream↑ ← [
reset: EdReset,
get: EdGet,
putback: EdPutBack,
put: EdPut,
endof: EdEndOf,
destroy: EdDestroy,
link: NIL,
body: Other[type: streamType[edited], data: eD]];
END;

DefaultMouseXYProc: PROCEDURE[x:CARDINAL, y:CARDINAL] =
BEGIN
JaMFnsDefs.PushInteger[x];
JaMFnsDefs.PushInteger[y];
END;

SetMouseXYProc: PUBLIC PROCEDURE [stream: StreamDefs.StreamHandle,
mouseXYProc: PROCEDURE[CARDINAL, CARDINAL]] =
BEGIN
WITH s:stream SELECT FROM
Other =>
BEGIN
eD: edData ← s.data;
eD.mouseXYProc ← mouseXYProc;
END;
ENDCASE;
END;

EdReset: PROCEDURE [stream: StreamDefs.StreamHandle] =
BEGIN
WITH s:stream SELECT FROM
Other =>
BEGIN
eD: edData ← s.data;
eD.line.length ← 0;
eD.stream.reset[eD.stream];
END;
ENDCASE;
END;

oldMouse: KeyDefs.MouseButton ← None;

EdGet: PROCEDURE [stream: StreamDefs.StreamHandle]
RETURNS [ch: CHARACTER] = {
WITH s:stream SELECT FROM
Other => {
eD: edData ← s.data;
IF eD.chptr<eD.line.length THEN ch ← eD.line[eD.chptr]
ELSE {
si: StreamDefs.StreamHandle ← IODefs.GetInputStream[];
so: StreamDefs.StreamHandle ← IODefs.GetOutputStream[];
newMouse: KeyDefs.MouseButton;
keyboard: BOOLEAN;
WITH s1:eD.stream SELECT FROM
Keyboard =>{
StreamDefs.OpenKeyStream[eD.stream];
keyboard ← TRUE;
};
ENDCASE => {
IODefs.SetOutputStream[nullStream];
keyboard ← FALSE;
};
IODefs.SetInputStream[eD.stream];
DO SELECT TRUE FROM
~eD.stream.endof[eD.stream] => {
EndLine: PROCEDURE [char: CHARACTER] RETURNS [BOOLEAN] =
{ RETURN[char=Ascii.CR OR char=eofchar] };
lastch: CHARACTER ← eofchar;
lastch ← IODefs.ReadEditedString[eD.line,EndLine,TRUE !
IODefs.LineOverflow => {
nl: STRING ← Storage.String[2*eD.line.maxlength];
String.AppendString[nl,eD.line];
Storage.FreeString[eD.line];
eD.line ← nl;
RESUME[nl];
};
IODefs.Rubout => RESUME;
StreamDefs.StreamError --[stream, error]-- =>
IF error=StreamAccess THEN CONTINUE
];
String.AppendChar[eD.line,lastch !
String.StringBoundsFault => {
nl: STRING ← Storage.String[eD.line.maxlength + 1];
String.AppendString[nl,eD.line];
Storage.FreeString[eD.line];
eD.line ← nl;
RESUME[nl];
}
];
IODefs.WriteChar[Ascii.CR];
EXIT;
};
~keyboard => --here if endof
--attempt to read past end
StreamDefs.StreamError[eD.stream, StreamAccess];
oldMouse#(newMouse ← KeyDefs.Mouse.buttons) => {
key: KeyDefs.MouseButton ← Inline.BITXOR[oldMouse,newMouse];
down: BOOLEAN ← Inline.BITAND[key,newMouse]=0; --down is 0
eD.line.length ← 0;
eD.mouseXYProc[cursorX↑, cursorY↑];
String.AppendString[eD.line,
SELECT key FROM --key is inverted
BlueYellow => ".red",
RedBlue => ".yellow",
RedYellow => ".blue",
ENDCASE => ""
];
String.AppendString[eD.line, IF down THEN "down" ELSE "up"];
String.AppendChar[eD.line, Ascii.ESC];
oldMouse ← newMouse;
EXIT;
};
ENDCASE;
Process.Yield[];
ENDLOOP;
IODefs.SetInputStream[si];
IODefs.SetOutputStream[so];
eD.chptr ← 0;
ch ← IF eD.line.length=0 THEN Ascii.CR ELSE eD.line[0];
};
IF ch=eofchar THEN ERROR StreamDefs.StreamError[stream,StreamAccess];
eD.chptr ← eD.chptr + 1;
};
ENDCASE;
};

EdPutBack: PROCEDURE [stream: StreamDefs.StreamHandle, ch: CHARACTER] =
BEGIN
WITH s:stream SELECT FROM
Other =>
BEGIN
eD: edData ← s.data;
IF eD.chptr=0
THEN eD.stream.putback[eD.stream,ch]
ELSE
BEGIN
eD.chptr ← eD.chptr-1;
eD.line[eD.chptr] ← ch;
END;
END;
ENDCASE;
END;

EdPut: PROCEDURE [stream: StreamDefs.StreamHandle, ch: CHARACTER] =
BEGIN
WITH s:stream SELECT FROM
Other => SIGNAL StreamDefs.StreamError[stream, StreamAccess];
ENDCASE;
END;

EdEndOf: PROCEDURE [stream: StreamDefs.StreamHandle]
RETURNS [BOOLEAN] =
BEGIN
WITH s:stream SELECT FROM
Other =>
BEGIN
eD: edData ← s.data;
IF eD.chptr<eD.line.length THEN RETURN[eD.line[eD.chptr]=eofchar]
ELSEBEGIN
WITH s1:eD.stream SELECT FROM
Keyboard =>
BEGIN--try to read one
end: BOOLEAN ← FALSE;
EdPutBack[stream,EdGet[stream
! StreamDefs.StreamError =>
BEGIN
end ← TRUE;
CONTINUE;
END]];
RETURN[end];
END;
ENDCASE => RETURN[eD.stream.endof[eD.stream]];
END;
END;
ENDCASE;
RETURN[FALSE];
END;

EdDestroy: PROCEDURE [stream: StreamDefs.StreamHandle] =
BEGIN
WITH s:stream SELECT FROM
Other =>
BEGIN
eD: edData ← s.data;
WITH s1:eD.stream SELECT FROM
Keyboard => StreamDefs.CloseKeyStream[eD.stream];
ENDCASE;
eD.stream.destroy[eD.stream];
Storage.Free[eD];
Storage.Free[stream];
END;
ENDCASE;
END;

nullStream: StreamDefs.StreamHandle =
Storage.Node[SIZE[StreamDefs.StreamObject]];

Nop1: PROCEDURE [stream: StreamDefs.StreamHandle] =
BEGIN END;

Nop2: PROCEDURE [stream: StreamDefs.StreamHandle, ch: UNSPECIFIED] =
BEGIN END;

NullGet: PROCEDURE [stream: StreamDefs.StreamHandle] RETURNS[CHARACTER] =
BEGIN
RETURN[Ascii.NUL];
END;

NullEndOf: PROCEDURE [stream: StreamDefs.StreamHandle] RETURNS[BOOLEAN] =
BEGIN
RETURN[TRUE];
END;

MouseNOP: PROCEDURE =
BEGIN
[] ← JaMFnsDefs.PopObject[];
[] ← JaMFnsDefs.PopObject[];
END;

DisplayOn: PROCEDURE =
BEGIN JaMInterruptDefs.SetDisplayOn[TRUE] END;

DisplayOff: PROCEDURE =
BEGIN JaMInterruptDefs.SetDisplayOn[FALSE] END;

--START Code--

eofchar: CHARACTER = Ascii.LF;

LongFileName: StringType Object;
BadFileName: StringType Object;
nostream: StringType Object;

StartJaMIO: PROCEDURE =
BEGIN OPEN JaMControlDefs;
NotifyStringObject[@LongFileName, ".longname"L];
NotifyStringObject[@BadFileName, ".badname"L];
NotifyStringObject[@nostream, ".nostream"L];

NotifyCommand[".print"L,Print];
NotifyCommand[".readline"L,ReadLine];
NotifyCommand[".readitem"L,ReadItem];
NotifyCommand[".writeitem"L,WriteItem];
NotifyCommand[".writebytes"L,WriteByteStream];
NotifyCommand[".bytestream"L,NByteStream];
NotifyCommand[".wordstream"L,NWordStream];
NotifyCommand[".mystream"L,MyStream];
NotifyCommand[".keystream"L,NKeyStream];
NotifyCommand[".killstream"L,DestroyStream];
NotifyCommand[".run"L,Run];
NotifyCommand[".reddown"L,MouseNOP];
NotifyCommand[".redup"L,MouseNOP];
NotifyCommand[".yellowdown"L,MouseNOP];
NotifyCommand[".yellowup"L,MouseNOP];
NotifyCommand[".bluedown"L,MouseNOP];
NotifyCommand[".blueup"L,MouseNOP];
NotifyCommand[".displayon"L,DisplayOn];
NotifyCommand[".displayoff"L,DisplayOff];
END;

nullStream↑ ← [
reset: Nop1,
get: NullGet,
putback: Nop2,
put: Nop2,
endof: NullEndOf,
destroy: Nop1,
link: NIL,
body: Other[type: streamType[null], data: NIL]];

StartJaMIO;

END.

MN March 21, 1980 11:04 AM
added MouseXYProc facility

DKW March 25, 1980 2:12 PM
added .displayon, .displayoff

DKW March 28, 1980 4:54 PM
added StartJaMIO

DKW April 1, 1980 3:42 PM
now uses NotifyCommand, NotifyStringObject

DKW December 8, 1980 5:18 PM
uses new Mesa interfaces: Ascii,Inline,Process,Storage,String
no longer calls DisplayDefs.StartCursor

DKW January 12, 1981 5:43 PM
Run tries default extension .jam