-- 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[]; }.