-- JaMStreamImpl.mesa
-- Last changed by Doug Wyatt,  7-Oct-81 17:14:51

DIRECTORY
  JaMBasic USING [Object, Root, Tag],
  JaMInternal USING [Frame],
  JaMOps,
  JaMStorage,
  Ascii USING [CR],
  Inline USING [BITAND, LowHalf],
  StreamDefs;

JaMStreamImpl: MONITOR
IMPORTS JaMOps, JaMStorage, Inline, StreamDefs
EXPORTS JaMOps = {
OPEN StreamDefs, JaMStorage, JaMOps, JaMInternal, JaMBasic;

-- Types and Constants

streamMax: CARDINAL = 10;
StreamEntry: TYPE = RECORD[index: CARDINAL, handle: StreamHandle];
StreamTable: TYPE = ARRAY[0..streamMax) OF StreamEntry;

-- Globals

zone: UNCOUNTED ZONE = JaMStorage.Zone[];

streamLen: CARDINAL ← 0;
streamIndex: CARDINAL ← 0; -- next stream index
streams: LONG POINTER TO StreamTable;

deadstream,badname: name Object;

-- Streams

MakeStream: PUBLIC PROC[s: StreamHandle, tag: Tag] RETURNS[stream Object] = {
  New: ENTRY PROC[s: StreamHandle] RETURNS[CARDINAL] = INLINE {
    i: CARDINAL ← 0;
    IF streamLen<streamMax THEN {
      streamIndex ← streamIndex + 1; i ← streamIndex;
      streams[streamLen] ← [i,s]; streamLen ← streamLen + 1 };
    RETURN[i];
    };
  i: CARDINAL ← New[s];
  IF i=0 THEN ERROR Error[limitchk]; -- *** fix this?
  RETURN[[tag,stream[index: i, stamp: root.stamp]]];
  };

GetStream: PUBLIC PROC[ob: stream Object] RETURNS[StreamHandle] = {
  Get: ENTRY PROC[index: CARDINAL] RETURNS[StreamHandle] = INLINE {
    FOR i: CARDINAL DECREASING IN[0..streamLen) DO
      entry: StreamEntry ← streams[i];
      IF entry.index=index THEN RETURN[entry.handle];
      ENDLOOP;
    RETURN[NIL];
    };
  s: StreamHandle ← NIL;
  IF ob.stamp=root.stamp THEN s ← Get[ob.index];
  IF s=NIL THEN ERROR Error[deadstream] ELSE RETURN[s];
  };

KillStream: PUBLIC PROC[ob: stream Object] = {
  Kill: ENTRY PROC[index: CARDINAL] RETURNS[StreamHandle] = {
    FOR i: CARDINAL DECREASING IN[0..streamLen) DO
      entry: StreamEntry ← streams[i];
      IF entry.index=index THEN {
        streamLen ← streamLen - 1;
	FOR j: CARDINAL IN[i..streamLen) DO streams[j] ← streams[j+1] ENDLOOP;
	streams[streamLen] ← [0,NIL]; RETURN[entry.handle] };
      ENDLOOP;
    RETURN[NIL];
    };
  s: StreamHandle ← NIL;
  IF ob.stamp=root.stamp THEN s ← Kill[ob.index];
  IF s#NIL THEN s.destroy[s];
  };

PBData: TYPE = LONG POINTER TO PBDataRecord;
PBDataRecord: TYPE = RECORD[handle: StreamHandle, back: BOOLEAN, item: UNSPECIFIED];

MakeFileStream: PROC[string: string Object, access: AccessOptions,
  items: {bytes,words}] RETURNS[stream Object] = {
  name: STRING ← [100];
  s: StreamHandle ← NIL;
  IF string.length>name.maxlength THEN GOTO BadName;
  StringText[string,name];
  s ← (SELECT items FROM bytes => NewByteStream, words => NewWordStream,
    ENDCASE => ERROR)[name,access ! FileNameError => GOTO BadName];
  IF Inline.BITAND[access,Read]#0 THEN s ← NewStreamWithPutback[s];
  RETURN[MakeStream[s,L]];
  EXITS BadName => ERROR Error[badname];
  };

NewStreamWithPutback: PROC[f: StreamHandle] RETURNS[StreamHandle] = {
  d: PBData ← zone.NEW[PBDataRecord ← [handle: f, back: FALSE, item: 0]];
  s: StreamHandle ← zone.NEW[StreamObject ← [reset: PBReset, get: PBGet,
    putback: PBPutback, endof: PBEndof, put: PBPut, destroy: PBDestroy,
    data: d]];
  RETURN[s];
  };

PBReset: PROC[s: StreamHandle] = {
  d: PBData ← s.data;
  f: StreamHandle ← d.handle;
  f.reset[f];
  };
PBGet: PROC[s: StreamHandle] RETURNS[UNSPECIFIED] = {
  d: PBData ← s.data;
  item: UNSPECIFIED;
  IF d.back THEN { item ← d.item; d.back ← FALSE }
  ELSE { f: StreamHandle ← d.handle; item ← f.get[f] };
  RETURN[item];
  };
PBPutback: PROC[s: StreamHandle, item: UNSPECIFIED] = {
  d: PBData ← s.data;
  d.item ← item; d.back ← TRUE;
  };
PBEndof: PROC[s: StreamHandle] RETURNS[BOOLEAN] = {
  d: PBData ← s.data;
  IF d.back THEN RETURN[FALSE]
  ELSE { f: StreamHandle ← d.handle; RETURN[f.endof[f]] };
  };
PBPut: PROC[s: StreamHandle, item: UNSPECIFIED] = {
  d: PBData ← s.data;
  f: StreamHandle ← d.handle;
  f.put[f,item];
  };
PBDestroy: PROC[s: StreamHandle] = {
  d: PBData ← s.data;
  f: StreamHandle ← d.handle;
  f.destroy[f];
  zone.FREE[@d];
  zone.FREE[@s];
  };

-- Intrinsics

JRun: PROC[frame: Frame] = {
  string: string Object ← PopString[frame.opstk];
  stream: stream Object ← MakeFileStream[string,Read,bytes];
  stream.tag ← X; Execute[frame,stream];
  };

JByteStream: PROC[frame: Frame] = {
  access: AccessOptions ← PopCardinal[frame.opstk];
  string: string Object ← PopString[frame.opstk];
  stream: stream Object ← MakeFileStream[string,access,bytes];
  Push[frame.opstk,stream];
  };

JWordStream: PROC[frame: Frame] = {
  access: AccessOptions ← PopCardinal[frame.opstk];
  string: string Object ← PopString[frame.opstk];
  stream: stream Object ← MakeFileStream[string,access,words];
  Push[frame.opstk,stream];
  };

JReadItem: PROC[frame: Frame] = {
  stream: stream Object ← PopStream[frame.opstk];
  s: StreamHandle ← GetStream[stream];
  ok: BOOLEAN ← NOT s.endof[s];
  IF ok THEN { item: INTEGER ← s.get[s]; PushInteger[frame.opstk,item] }
  ELSE KillStream[stream];
  PushBoolean[frame.opstk,ok];
  };

JWriteItem: PROC[frame: Frame] = {
  item: LONG INTEGER ← PopInteger[frame.opstk];
  stream: stream Object ← PopStream[frame.opstk];
  s: StreamHandle ← GetStream[stream];
  s.put[s,Inline.LowHalf[item]];
  };

JReadLine: PROC[frame: Frame] = {
  stream: stream Object ← PopStream[frame.opstk];
  s: StreamHandle ← GetStream[stream];
  localline: STRING ← [256];
  line: TextObject ← TextNew[localline];
  found: BOOLEAN ← FALSE; string: string Object;
  { ENABLE UNWIND => TextFree[@line];
    UNTIL s.endof[s] DO
      c: CHARACTER ← s.get[s];
      TextPut[@line,c]; found ← TRUE;
      IF c=Ascii.CR THEN EXIT;
      ENDLOOP;
    IF found THEN string ← MakeString[TextRead[@line]];
    };
  TextFree[@line];
  IF found THEN Push[frame.opstk,string] ELSE KillStream[stream];
  PushBoolean[frame.opstk,found];
  };

JWriteBytes: PROC[frame: Frame] = {
  string: string Object ← PopString[frame.opstk];
  stream: stream Object ← PopStream[frame.opstk];
  s: StreamHandle ← GetStream[stream];
  Put: PROC[c: CHARACTER] RETURNS[BOOLEAN] = { s.put[s,c]; RETURN[FALSE] };
  StringForAll[string,Put];
  };

JKillStream: PROC[frame: Frame] = {
  stream: stream Object ← PopStream[frame.opstk];
  KillStream[stream];
  };

-- Initialization

InstallStream: PROC[why: InstallReason, frame: Frame] = { SELECT why FROM
  init => {
    streams ← zone.NEW[StreamTable ← ALL[[0,NIL]]];
    streamLen ← 0; streamIndex ← 0;
    };
  free => {
    zone.FREE[@streams];
    };
  register => {
    deadstream ← MakeName[".deadstream"L];
    badname ← MakeName[".badname"L];
    RegisterExplicit[frame,".run"L,JRun];
    RegisterExplicit[frame,".bytestream"L,JByteStream];
    RegisterExplicit[frame,".wordstream"L,JWordStream];
    RegisterExplicit[frame,".readitem"L,JReadItem];
    RegisterExplicit[frame,".writeitem"L,JWriteItem];
    RegisterExplicit[frame,".readline"L,JReadLine];
    RegisterExplicit[frame,".writebytes"L,JWriteBytes];
    RegisterExplicit[frame,".killstream"L,JKillStream];
    };
  ENDCASE;
  };

Install[InstallStream];

}.