-- Copyright (C) 1986  by Xerox Corporation. All rights reserved. 
-- CWindow.mesa
-- DWR		20-Mar-86 16:15:30
-- NFS		29-May-86 15:01:38
-- MEW		 9-May-86 16:53:09
-- BGY		27-Aug-86 12:19:41

-- Parts of CTool and CExec that do not depend on Tajo or BWS.

-- All dependencies on these environments are absorbed by CEnvUtil.

DIRECTORY
  Ascii USING [ControlC],
  Beta USING [GetVersion],
  CEnvUtil USING [AcquireFile, AppendCmdProc, AppendLine, ArgSeq, Blink,
    ChangeStandardStreams, ChangeSwitches, DataHandle,
    Debugging, EndingToolInstance, error, FileError,
    FileHandle, FreeData, GetFileInputStream, GetFirstWord,
    GetPrompt, GetStdStreams,
    MakeDataForSystemCall, normal, Outcome,
    ParseLine, Print, PrintStatus, RegisterItems,
    ReleaseFile, SetOutputMode, ShowHandles, StandardStream,
    StdinHandle, StdinObject,
    StdStreamError, StopIfCExec, StopOnError,
    StringSeq, UniqueFilePrefix, Unload, UpdateLogReadLength],
  CRuntime USING [CanDelete, GetStdin, GetStdout, GetStderr,
    ProcessNotRegistered, ProgramExited,
    StopIfUserAborted, TooMuchGlobalArraySpace],
  CString USING [CString, ToWordPointer],
  CWindowLibExtras USING [],
  Environment USING [Byte],
  FBasics USING [LibraryProblem],
  Format USING [Decimal, LongOctal, StringProc],
  Heap USING [Create],
  Inline USING [BITAND, LongCOPY],
  CWindowLib USING[cBreakMode, echoMode, normalMode, rawMode],
  Runtime USING [CallDebugger, IsBound],
  StartState USING [GetHandle, GetLoadHandle, Handle, Load,
    LoadError,normalOutcome, Object, Restart, Start, UnloadError,
    UnloadUnstartedProgram, VersionMismatch],
  Stream USING [defaultObject, Delete, DeleteProcedure,
    EndOfStream, GetByteProcedure, GetChar, Handle,
    InvalidOperation, Object, PutByteProcedure, SendNowProcedure],
  String USING [AppendCharAndGrow, AppendString, CopyToNewString, Empty,
    FreeString],
  System,
  Time,
  TTY USING [CharStatus, GetChar, GetEditedString, Handle,
    LineOverflow, PutChar, PutString, Rubout, SetEcho];

CWindow: MONITOR
    IMPORTS 
      Beta, CEnvUtil, CRuntime, CString, FBasics, Format, Heap, Inline,
      Runtime, StartState, Stream,
      String, TTY, Time
    EXPORTS CEnvUtil, CWindowLibExtras = {
  OPEN CEnvUtil, CWindowLib;

  zone: PUBLIC UNCOUNTED ZONE ← Heap.Create[initial:1];
  
  printLoadMsg:BOOLEAN ← TRUE;
  NullFileHandle:FileHandle = LOOPHOLE[LONG[0]];
  
  hash: System.GreenwichMeanTime = [24204357100B];
  
  SystemCall:PUBLIC SIGNAL[string:LONG STRING] RETURNS[exitStatus:INTEGER] = CODE;
    
  ProcessLineProc:TYPE = PROCEDURE[buffer:LONG STRING,data:DataHandle,
     defaultStdin,defaultStdout,defaultStderr:Stream.Handle]
     RETURNS[outcome:Outcome ← normal];

  ReadAndRun:PUBLIC PROCEDURE[
    data:DataHandle,tH:TTY.Handle,appendCmd:AppendCmdProc]
    RETURNS[outcome: Outcome ← normal] = {
    inStream,outStream,stdin,stdout,stderr:Stream.Handle;
    argV,copyArgV:ArgSeq ← NIL;
    buffer:LONG STRING ← zone.NEW[StringBody[80]];
    bufferPos:CARDINAL;
    prompt: LONG STRING ← GetPrompt[];
        
    Write:Format.StringProc = {Print[s,data];};
        
    MakeStandardStreams:PROCEDURE = {
      inStream ← LOOPHOLE[
        zone.NEW[StdinObject ← [Stream.defaultObject,normalMode]]];
      outStream ← LOOPHOLE[
        zone.NEW[StdinObject ← [Stream.defaultObject,normalMode]]];
      inStream.getByte ← ReadFromWindow;
      inStream.putByte ← ErrorPutByte;
      outStream.getByte ← ErrorGetByte;
      outStream.putByte ← WriteToWindow;
      inStream.delete ← outStream.delete ← NopDelete;
      inStream.sendNow ← outStream.sendNow ← NopSendNow;
      inStream.clientData ← outStream.clientData ← data;
    };
    
    DeleteStdStreams:PROCEDURE = {
      IF CRuntime.CanDelete[stdin] THEN Stream.Delete[stdin];
      IF CRuntime.CanDelete[stdout] THEN Stream.Delete[stdout];
      IF CRuntime.CanDelete[stderr] THEN Stream.Delete[stderr];
    };
    
    ReadFromWindow:Stream.GetByteProcedure = {
      controlD:Environment.Byte = 4;
      InStreamRead:PROCEDURE[c:CHAR] RETURNS[status:TTY.CharStatus] = {
        SELECT c FROM
	  '\N => {
	    TTY.PutChar[tH,c];
	    lastChar ← c;
            RETURN[stop];};
	  LOOPHOLE[controlD] => {
	    lastChar ← c;
            RETURN[stop];};
	  Ascii.ControlC => ERROR CRuntime.ProgramExited[1];
	  ENDCASE => RETURN[ok];
	};
      lastChar:CHAR;
      iS:StdinHandle = LOOPHOLE[inStream];
      IF IsRawMode[iS.mode] OR IsCBreakMode[iS.mode] THEN {
        -- flush buffer in case of return to buffered input.
        bufferPos ← buffer.length; 
        byte ← LOOPHOLE[TTY.GetChar[tH]];
	IF LOOPHOLE[byte,CHAR] = Ascii.ControlC AND ~IsRawMode[iS.mode] THEN
	  ERROR CRuntime.ProgramExited[1];
	IF IsEchoMode[iS.mode] THEN
	  TTY.PutChar[tH,LOOPHOLE[byte]];
	RETURN[byte];
      };
      IF bufferPos = buffer.length THEN {
        [] ← TTY.SetEcho[tH,
          IF IsEchoMode[iS.mode] THEN plain ELSE none]; 
        bufferPos ← 0;
	DO
	  lineDeleted:BOOLEAN ← FALSE;
	  buffer.length ← 0;
	  [] ← TTY.GetEditedString[h:tH,s:buffer,t:InStreamRead
            !TTY.LineOverflow => {
	       ns ← String.CopyToNewString[s:s,z:zone,longer:s.maxlength / 2];
	       String.FreeString[z:zone,s:s];
	       buffer ← ns;
	       RESUME[ns];};
	     TTY.Rubout => {
	       TTY.PutString[tH," XXX\N"L];
	       lineDeleted ← TRUE;
	       CONTINUE;}];
	  IF NOT lineDeleted THEN EXIT;
	  ENDLOOP;
	String.AppendCharAndGrow[to:@buffer,c:lastChar,z:zone];
	};
      
      byte ← LOOPHOLE[buffer.text[bufferPos]];
      IF byte = controlD THEN SIGNAL Stream.EndOfStream[0];
      bufferPos ← bufferPos + 1;
    };
      
    WriteToWindow:Stream.PutByteProcedure = {
      TTY.PutChar[tH,LOOPHOLE[byte]];};
    
   ProcessLine:PROCEDURE[buffer:LONG STRING,data:DataHandle,
    defaultStdin,defaultStdout,defaultStderr:Stream.Handle]
    RETURNS[outcome:Outcome ← normal] = {
    firstWord:LONG STRING ← GetFirstWord[buffer];
    IF firstWord = NIL THEN RETURN;
    {ENABLE UNWIND => zone.FREE[@firstWord];
    SELECT firstWord[0] FROM
      '@ => outcome ← ProcessFile[
        buffer,data,defaultStdin,defaultStdout,defaultStderr];
      '! => ChangeStandardStreams[buffer,data];
      '/ => ChangeSwitches[buffer,data];
      '? => PrintStatus[data,tH];
      '- => NULL;
      '↑ => StopIfCExec[data]
      ENDCASE => IF (Runtime.IsBound[LOOPHOLE[Beta.GetVersion]] 
        AND Time.Current[] > hash) THEN {
          Write["\nThe C Environment will not run after March 1, 1987."L];
          TTY.PutString[
	    tH, "The C Environment will not run after March 1, 1987.\n"L];
	  }
        ELSE outcome ← 
	  SELECT TRUE FROM
            ValidPrefix[firstWord,"unload.~"L] => Unload[buffer,tH],
	    ValidPrefix[firstWord,"show.~"L] => ShowHandles[buffer,tH],
            ENDCASE => Run[buffer,data,defaultStdin,defaultStdout,defaultStderr];
     };
     zone.FREE[@firstWord];
     UpdateLogReadLength[data];
     CRuntime.StopIfUserAborted[];
   };
   
   ValidPrefix: PROCEDURE [prefix, string: LONG STRING]
     RETURNS [BOOLEAN] = {
     FOR i: CARDINAL IN [0..MIN[prefix.length, string.length]) DO
       IF ToLower[prefix[i]] # string[i] THEN RETURN [FALSE];
     ENDLOOP;
     IF prefix.length # 0 THEN {
       buf: LONG STRING ← [50];
       String.AppendString[to: buf, from: prefix];
       RETURN [CEnvUtil.UniqueFilePrefix[buf] = 0]; -- doesn't conflict with a file name
     };
     RETURN [FALSE];
   };
   
   ToLower: PROCEDURE [c: CHARACTER]
     RETURNS [CHARACTER] = {
     IF c IN ['A..'Z] THEN RETURN [VAL[ORD[c] - ORD['A] + ORD['a]]];
     RETURN [c];
   };
   
   ProcessFile:PROCEDURE[
     buffer:LONG STRING,data:DataHandle,
     defaultStdin,defaultStdout,defaultStderr:Stream.Handle]
     RETURNS[outcome:Outcome ← normal] = {
     nextChar:PROCEDURE RETURNS[c:CHAR] = {
       c ← Stream.GetChar[in];};  -- AppendLine catches signals from read.
     in:Stream.Handle = GetFileInputStream[buffer];
     line:LONG STRING ← zone.NEW[StringBody[80]];
     IF in = NIL THEN {
       Blink[];
       Write["\nUnable to open script file"L];
       RETURN[error];};
     DO
       line.length ← 0;
       AppendLine[@line,nextChar];
       IF String.Empty[line] THEN EXIT;
       TTY.PutString[tH,prompt];
       TTY.PutString[tH,line];
       IF ProcessLine[line,data,defaultStdin,defaultStdout,defaultStderr] # normal
	 AND StopOnError[data] THEN {
           outcome ← error; EXIT;};
       ENDLOOP;
     Stream.Delete[in];
     zone.FREE[@line];
   };
  
         
   Run:PROCEDURE[
     cmdLine:LONG STRING,data:DataHandle,
     defaultStdin,defaultStdout,defaultStderr:Stream.Handle]
     RETURNS[outcome:Outcome ← normal] = {
     argC:CARDINAL;
     fh:FileHandle ← NullFileHandle;
     fileName:LONG STRING ← NIL;
     ssh:StartState.Handle;
     badVersion:BOOLEAN ← FALSE;
     found: CARDINAL;
     LOOPHOLE[inStream,StdinHandle].mode ← CWindowLib.normalMode;
     [fileName,argC,argV] ← ParseLine[cmdLine];
     IF String.Empty[fileName] THEN RETURN;
     copyArgV ← zone.NEW[StringSeq[argV.maxlength]];
     Inline.LongCOPY[
       from:argV,
       nwords:(2 * CARDINAL.SIZE) + (argV.maxlength * CString.CString.SIZE),
       to: copyArgV];
     cmdLine.length ← 0;
     found ← CEnvUtil.UniqueFilePrefix[fileName]; -- appends rest of file name
     IF found # 1 THEN { -- not a unique name
       Blink[];
       Write["\N"L];
       Write[fileName];
       Write[
         IF found = 0 THEN ": No such file or directory."L ELSE ": Ambiguous."L];
       zone.FREE[@fileName];
       RETURN[error];
     };
     {ENABLE {
      UNWIND => IF fileName # NIL THEN zone.FREE[@fileName];
      FileError => {
	Blink[];
	Write["\N"L];
	Write[message];
	outcome ← error;
	CONTINUE;};
      FBasics.LibraryProblem => {
        Blink[];
	Write["\N"L];
	Write[s];
	outcome ← error;
	CONTINUE;};
      StartState.LoadError => {
	Blink[];
	Write[message];
	ReleaseFile[fh];
	outcome ← error;
	DeleteStdStreams[];
	CONTINUE;};
      StartState.VersionMismatch => {
	  Blink[];
	  badVersion ← TRUE;
	  Write["\NVersion mismatch of "L];
	  Write[module];
	  Write["..."L];
	  RESUME;
       };
      CRuntime.TooMuchGlobalArraySpace => {
        Write["\nToo much global array space required"L];
	outcome ← error;
	DeleteStdStreams[];
	CONTINUE;};
      StdStreamError => {
	 Blink[];
	 Write["\NError opening "L];
	 Write[SELECT whichStream FROM
	   stdin => "stdin"L,
	   stdout => "stdout"L,
	   stderr => "stderr"L,
	   ENDCASE => "?"L];
	 Write[" stream."L];
	 outcome ← error;
	 CONTINUE;};
      CRuntime.ProcessNotRegistered => {
	 Write["\nProcess not registered"L];
	 outcome ← error;
	 CONTINUE;};};
     started:BOOLEAN;
     fh ← AcquireFile[name:fileName];
     [stdin,stdout,stderr] ← GetStdStreams[
       data,defaultStdin,defaultStdout,defaultStderr];
     [ssh,started] ← StartState.GetHandle[LOOPHOLE[fh]];
     IF ~started THEN {
       IF printLoadMsg THEN {
         Write["\nLoading "L];
         Write[fileName];
         Write["..."L];};
       StartState.Load[ssh,LOOPHOLE[fh]];
       IF printLoadMsg THEN
         Format.LongOctal[Write,LOOPHOLE[StartState.GetLoadHandle[ssh]]];
       IF badVersion THEN {
         outcome ← error;
	 Write["\NUnloading..."L];
	 StartState.UnloadUnstartedProgram[ssh
	   !StartState.UnloadError => {
	     Blink[]; Write[message]; CONTINUE;}];
	 Write["...done."L];
	 DeleteStdStreams[];}
       ELSE {
	 IF Debugging[data] THEN Runtime.CallDebugger[fileName];
	 IF printLoadMsg THEN Write["...Starting..."];
	 buffer.length ← bufferPos ← 0;
	 outcome ← StartState.Start[
	   ssh,argC,@(copyArgV[0]),stdin,stdout,stderr].outcome;
	 IF outcome # StartState.normalOutcome THEN {
	   Write["\NExit code: "L];
	   Format.Decimal[Write,outcome];};
       };
     }
     ELSE { -- already loaded
       ReleaseFile[fh];
       IF printLoadMsg THEN {
         Write["\nRestarting "L];
         Write[fileName];
         Write[" ("L];
         Format.LongOctal[Write,LOOPHOLE[StartState.GetLoadHandle[ssh]]];
         Write[")..."L];};
       IF Debugging[data] THEN Runtime.CallDebugger[fileName];
       buffer.length ← bufferPos ← 0;
       outcome ← StartState.Restart[
	   ssh,argC,@(copyArgV[0]),stdin,stdout,stderr].outcome;
       IF outcome # StartState.normalOutcome THEN {
	   Write["\NExit code: "L];
	   Format.Decimal[Write,outcome];};
     };
     };
     CEnvUtil.SetOutputMode[data, CWindowLib.normalMode];
     CleanUp[@fileName,@argV,@copyArgV];
  };  
        
     -- ReadAndRun starts here --    
     {ENABLE  ABORTED => {
        String.FreeString[zone,buffer];
        CleanUp[NIL,@argV,@copyArgV];
	zone.FREE[@inStream]; zone.FREE[@outStream];
	zone.FREE[@prompt];
        CONTINUE;};
     MakeStandardStreams[];
     DO
       ENABLE ABORTED =>
         IF EndingToolInstance[data] THEN REJECT ELSE {
	   TTY.PutString[h:tH,s:"\NAborted"L];CONTINUE;};
       TTY.PutString[h:tH,s:prompt];
       buffer.length ← 0;
       IF appendCmd[@buffer].lineDeleted THEN LOOP;
       outcome ← ProcessLine[buffer,data,inStream,outStream,outStream
         !SystemCall =>
	  RESUME[HandleSystemCall[string,inStream,ProcessLine]]];
       ENDLOOP;
   };
 };
 
  HandleSystemCall:PROCEDURE[
    string:LONG STRING, inStream: Stream.Handle, processLine:ProcessLineProc] 
    RETURNS[outcome:INTEGER] = {
    pos:CARDINAL ← 0;
    nextChar:PROCEDURE RETURNS[c:CHAR] = {
      IF  pos >= string.length THEN SIGNAL Stream.EndOfStream[0];
      c ← string[pos];
      pos ← pos.SUCC;
    };
    inheritedStdin:Stream.Handle = CRuntime.GetStdin[]↑;
    inheritedStdout:Stream.Handle = CRuntime.GetStdout[]↑;
    inheritedStderr:Stream.Handle = CRuntime.GetStderr[]↑;
    newData:DataHandle ← NIL;
    line:LONG STRING ← NIL;
    {ENABLE UNWIND => {
      IF line # NIL THEN zone.FREE[@line];
      IF newData # NIL THEN zone.FREE[@newData];};
    saveMode:INTEGER = LOOPHOLE[inStream,StdinHandle].mode;
    newData ← MakeDataForSystemCall[];
    line ← zone.NEW[StringBody[80]];
    DO
      line.length ← 0;
      AppendLine[@line,nextChar];
      IF String.Empty[line] THEN EXIT;
      outcome ← processLine[
        line,newData,inheritedStdin,inheritedStdout,inheritedStderr
        !SystemCall => 
          RESUME[HandleSystemCall[string,inStream,processLine]]];
    ENDLOOP;
    LOOPHOLE[inStream, StdinHandle].mode ← saveMode;
    zone.FREE[@line];
   FreeData[newData];
   zone.FREE[@newData];
   };
 };
  
  CleanUp:PROCEDURE[
    fileName: POINTER TO LONG STRING, argV,copyArgV:POINTER TO ArgSeq] = {
    IF fileName # NIL AND fileName↑ # NIL THEN zone.FREE[fileName];
    IF argV↑ # NIL THEN {
      FOR i:CARDINAL IN [0..argV.length) DO
        wptr:LONG POINTER ← CString.ToWordPointer[argV[i]];
        zone.FREE[@wptr];
        ENDLOOP;
      zone.FREE[argV];
    };
    IF copyArgV # NIL THEN zone.FREE[copyArgV];
  }; 
  
  ErrorGetByte:Stream.GetByteProcedure = {ERROR Stream.InvalidOperation};            ErrorPutByte:PUBLIC Stream.PutByteProcedure = {ERROR Stream.InvalidOperation};
  NopDelete: Stream.DeleteProcedure = {};
  NopSendNow:Stream.SendNowProcedure = {};
  
  IsItDefaultStdin:PUBLIC PROCEDURE[sH:Stream.Handle] RETURNS[BOOLEAN] = {
    RETURN[sH.sendNow = NopSendNow];};
  
  IsRawMode:PROCEDURE[mode:INTEGER] RETURNS[BOOLEAN] = INLINE {
    RETURN[Inline.BITAND[rawMode,mode] # 0];};
  
  IsCBreakMode:PROCEDURE[mode:INTEGER] RETURNS[BOOLEAN] = INLINE {
    RETURN[Inline.BITAND[cBreakMode,mode] # 0];};
  
  IsEchoMode:PROCEDURE[mode:INTEGER] RETURNS[BOOLEAN] = INLINE {
    RETURN[Inline.BITAND[echoMode,mode] # 0];};

  SetPrintLoadMessage:PUBLIC ENTRY PROCEDURE[
    printMsg:BOOLEAN] RETURNS[oldPrintMsg:BOOLEAN] = {
    oldPrintMsg ← printLoadMsg;
    printLoadMsg ← printMsg;};
    
  RegisterItems[];
 
}.