-- Copyright (C) 1986 by Xerox Corporation. All rights reserved. -- TajoCEnvUtil.mesa -- NFS 29-May-86 11:57:28 -- MEW 14-Apr-86 16:37:19 -- Implementation of CEnvUtil for Tajo. DIRECTORY Ascii USING [BS, CR, LF, NUL, SP, TAB], BucketAlloc USING [Destroy], CEnvUtil USING [ AppendCmdProc, ArgSeq, Data, DataHandle, error, normal, Outcome, ReadAndRun, StandardStream, StringSeq, ToolType, zone], CmFile USING [Error, FreeString, UserDotCmLine], Context USING [ Create, Data, Destroy, Find, Type, UniqueType], CRuntime USING [arrayZone, log, NoteAbortedProcess, z], CString USING [CString, LongStringToCString], CWindowLib, Emulator USING [ Create, Destroy, EmuSWType, PutChar, StartLog, StopLog, SetRefresh, table], Exec USING [ AddCommand, CheckForAbort, ExecProc, FreeTokenString, GetChar, GetToken, GetTTY, Handle, Object, OutputProc, RemoveCommand], FileSW USING [GetFile], Format USING [LongOctal, StringProc], FormSW USING [ AllocateItemDescriptor, BooleanItem, ClientItemsProcType, CommandItem, DisplayItem, NotifyProcType, line0, line1, ProcType, StringItem], Heap USING [Delete], Inline USING [BITAND, LowHalf], MFile USING [ Acquire, AppendErrorMessage, EnumerateDirectory, EnumerateProc, Error, Handle, maxNameLength, Object, Release, ValidFilename], MLoader USING [Handle, Object], MStream USING [ Error, GetLength, Log, PleaseReleaseProc, ReadOnly, SetLogReadLength], Pipe USING [Create, Delete, Handle], Process USING [Abort, GetCurrent, Pause, SecondsToTicks], Put USING [Text], Runtime USING [GetBcdTime, IsBound], StartState USING [ EnumerateHandles, EnumerateProc, Unload, UnloadError, UnloadFromFile, zone], Stream USING [ defaultObject, Delete, EndOfStream, Handle, PutByteProcedure, PutChar, PutProcedure, SetSSTProcedure, SubSequenceType], String USING [ AppendChar, AppendCharAndGrow, AppendExtensionIfNeeded, AppendNumber, AppendString, AppendStringAndGrow, StringBoundsFault, CopyToNewString, Empty, Equivalent, FreeString, InvalidNumber, StringToLongNumber], Time USING [Append, Unpack], Token USING [ Filtered, FilterProcType, FreeStringHandle, FreeTokenString, Handle, Item, MaybeQuoted, Skip, StringToHandle, Switches, UnterminatedQuote, WhiteSpace], Tool USING [ Create, Destroy, MakeClientSW, MakeFormSW, MakeMsgSW, MakeSWsProc, MakeTTYSW, UnusedLogName], ToolWindow USING [ Handle, TransitionProcType, WindowForSubwindow], TTY USING [ CharStatus, Create, Destroy, GetEditedString, Handle, LineOverflow, PutChar, PutString, ResetUserAbort, Rubout], TTYConstants USING [ blinkDisplay, normal, removeChars, setBackingSize], TTYSW USING [GetTTYHandle], UserInput USING [ AttentionProcType, SetAttention, StringProcType], UserTerminal USING [BlinkDisplay], Version USING [Append], Window USING [Handle, Object]; TajoCEnvUtil: PROGRAM IMPORTS BucketAlloc, CEnvUtil, CmFile, Context, CRuntime, CString, Emulator, Exec, FileSW, Format, FormSW, Heap, Inline, MFile, MStream, Pipe, Process, Put, Runtime, StartState, Stream, String, Time, Token, Tool, ToolWindow, TTY, TTYSW, UserInput, UserTerminal, Version EXPORTS CEnvUtil = { OPEN CEnvUtil; Data: PUBLIC TYPE = MACHINE DEPENDENT RECORD [ stdin(0): LONG STRING ← zone.NEW[ StringBody [MFile.maxNameLength]], stdout(2): LONG STRING ← zone.NEW[ StringBody [MFile.maxNameLength]], stderr(4): LONG STRING ← zone.NEW[ StringBody [MFile.maxNameLength]], debug(6): BOOLEAN ← FALSE, stopOnError(7): BOOLEAN ← TRUE, endingToolInstance(8): BOOLEAN ← FALSE, msgSW(9): Window.Handle ← NIL, -- remains nil for CExec iomode(11): INTEGER ← 0, cToolPart(12): SELECT tool(12): ToolType FROM ctool => [ formSW(13): Window.Handle ← NIL, ttySW(15): Window.Handle ← NIL, p(17): PROCESS, emuPart(18): SELECT tty(18): * FROM emulation => [ pH(19): Pipe.Handle, toBuffer(21), ttyStream(23): Stream.Handle, modeVal(25): LONG CARDINAL ← 0, mode(27): Stream.SubSequenceType ← TTYConstants.normal, logging(28): BOOLEAN ← FALSE], regular => [logStream(19): Stream.Handle ← NIL], ENDCASE], cexec => [], temp => [], ENDCASE]; Handle: TYPE = LONG POINTER TO Data; CTHandle: TYPE = LONG POINTER TO ctool Data; RCTHandle: TYPE = LONG POINTER TO regular ctool Data; ECTHandle: TYPE = LONG POINTER TO emulation ctool Data; FileHandle: PUBLIC TYPE = MFile.Handle; LoadHandle: PUBLIC TYPE = MLoader.Handle; FileError: PUBLIC ERROR [message: LONG STRING] = CODE; AcquireFile: PUBLIC PROCEDURE [name: LONG STRING] RETURNS [fh: FileHandle] = { fh ← MFile.Acquire[ name: name, access: readOnly, release: [] ! MFile.Error => { msg: LONG STRING ← [100]; MFile.AppendErrorMessage[msg, code, file]; ERROR FileError[msg]; }]; }; ReleaseFile: PUBLIC PROCEDURE [fh: FileHandle] = { MFile.Release[fh ! MFile.Error => CONTINUE]; }; UniqueFilePrefix: PUBLIC PROCEDURE [fileName: LONG STRING] RETURNS [matches: CARDINAL] = { Check: PROCEDURE [ext: LONG STRING] RETURNS [BOOLEAN] = BEGIN NoteMatch: MFile.EnumerateProc = BEGIN IF matches = 0 THEN { String.AppendString[ to: matchName, from: name]; matches ← matches.SUCC} ELSE IF ~String.Equivalent[name, matchName] THEN { matchName.length ← 0; String.AppendString[to: matchName, from: name]; matches ← matches.SUCC; }; RETURN[matches > 1]; -- stop if already not unique. END; -- NoteMatch extName: LONG STRING ← [MFile.maxNameLength]; matchName: LONG STRING ← [MFile.maxNameLength]; matchName.length ← 0; IF ext # NIL THEN { extName.length ← 0; String.AppendString[ to: extName, from: fileName ! String.StringBoundsFault => GOTO TooLong]; String.AppendString[ to: extName, from: ext ! String.StringBoundsFault => GOTO TooLong]; } ELSE extName ← fileName; MFile.EnumerateDirectory[ extName, NoteMatch, filesOnly]; IF matches = 1 THEN { fileName.length ← 0; String.AppendString[to: fileName, from: matchName];}; RETURN[matches # 0]; -- TRUE if we have a match or an ambiguous name EXITS TooLong => RETURN[matches # 0]; END; -- Check matches ← 0; IF ~MFile.ValidFilename[fileName] THEN RETURN; IF Check[NIL] OR Check[".archivebcd"L] OR Check[".bcd"L] OR Check["*.archivebcd"L] OR Check["*.bcd"L] THEN NULL; }; GetStdStreams: PUBLIC PROCEDURE [ data: Handle, defaultStdin, defaultStdout, defaultStderr: Stream.Handle] RETURNS [stdin, stdout, stderr: Stream.Handle] = { stdin ← IF String.Empty[LOOPHOLE[data, Handle].stdin] THEN defaultStdin ELSE MStream.ReadOnly[ data.stdin, [] ! MStream.Error => ERROR StdStreamError[stdin]]; stdout ← IF String.Empty[data.stdout] THEN defaultStdout ELSE MStream.Log[ data.stdout, [SetReadLengthAndRefuse] ! MStream.Error => { Stream.Delete[stdin]; ERROR StdStreamError[stdout]}]; stderr ← SELECT TRUE FROM String.Empty[data.stderr] => defaultStderr, String.Equivalent[ data.stdout, LOOPHOLE[data, Handle].stderr] => stdout ENDCASE => MStream.Log[ data.stderr, [SetReadLengthAndRefuse] ! MStream.Error => { Stream.Delete[stdin]; Stream.Delete[stdout]; ERROR StdStreamError[stderr]}]; }; SetReadLengthAndRefuse: MStream.PleaseReleaseProc = { MStream.SetLogReadLength[ stream, MStream.GetLength[stream]]; RETURN[no]; -- no conflict if read access requested }; StdStreamError: PUBLIC ERROR [ whichStream: StandardStream] = CODE; GetFileInputStream: PUBLIC PROCEDURE [buffer: LONG STRING] RETURNS [sH: Stream.Handle] = { fileName: LONG STRING; tH: Token.Handle ← Token.StringToHandle[buffer]; Token.Skip[tH, NIL, Token.WhiteSpace, TRUE]; fileName ← Token.Item[tH]; IF fileName = NIL THEN { tH ← Token.FreeStringHandle[tH]; RETURN[NIL]; }; sH ← MStream.ReadOnly[ fileName, [] ! MStream.Error => {sH ← NIL; CONTINUE; }]; IF sH = NIL THEN { -- Try adding ".cscript" extension. extensionLength: CARDINAL = 8; extendedName: LONG STRING ← String.CopyToNewString[ fileName, zone, extensionLength]; IF String.AppendExtensionIfNeeded[ to: @extendedName, extension: ".cscript"L, z: zone] THEN sH ← MStream.ReadOnly[ extendedName, [] ! MStream.Error => {CONTINUE; }]; String.FreeString[zone, extendedName]; }; [] ← Token.FreeTokenString[fileName]; tH ← Token.FreeStringHandle[tH]; }; AppendLine: PUBLIC PROCEDURE [ line: LONG POINTER TO LONG STRING, getChar: PROC RETURNS [c: CHAR]] = { DO c: CHAR ← getChar[ ! MStream.Error, Stream.EndOfStream => EXIT]; String.AppendCharAndGrow[to: line, c: c, z: zone]; IF c = Ascii.CR THEN EXIT; ENDLOOP; }; contextType: Context.Type; MakeCTool: PROCEDURE [emulation: BOOLEAN] = { name: LONG STRING ← zone.NEW[StringBody [60]]; name.length ← 0; String.AppendString[name, "CTool "L]; Version.Append[name]; String.AppendString[name, " of "L]; Time.Append[name, Time.Unpack[Runtime.GetBcdTime[]]]; [] ← Tool.Create[ makeSWsProc: IF emulation THEN MakeSWsEmu ELSE MakeSWs, initialState: default, initialBox: [ [0, 60], [512, IF emulation THEN 480 ELSE 465]], clientTransition: ClientTransition, name: name, tinyName1: "CTool"L, cmSection: "CTool"L]; zone.FREE[@name]; }; Another: FormSW.ProcType = { h: CTHandle ← Context.Find[ contextType, ToolWindow.WindowForSubwindow[sw]]; WITH hh: h SELECT FROM emulation => MakeCTool[TRUE]; regular => MakeCTool[FALSE]; ENDCASE; }; DestroyInstance: FormSW.ProcType = { Tool.Destroy[ToolWindow.WindowForSubwindow[sw]]; }; messageLines: CARDINAL = 3; MakeSWs: Tool.MakeSWsProc = { -- MakeSWsProc without terminal emulation h: RCTHandle; logName: LONG STRING ← [12]; h ← zone.NEW[regular ctool Data]; Tool.UnusedLogName[unused: logName, root: "CTool.log"L]; Context.Create[contextType, h, FreeToolData, window]; h.msgSW ← Tool.MakeMsgSW[ window: window, lines: messageLines]; h.formSW ← Tool.MakeFormSW[ window: window, formProc: MakeForm]; h.ttySW ← Tool.MakeTTYSW[window: window, name: logName]; h.logStream ← FileSW.GetFile[h.ttySW].s; UserInput.SetAttention[h.ttySW, AbortProgram]; h.p ← FORK CallReadAndRun[ LOOPHOLE[h], TTYSW.GetTTYHandle[h.ttySW]]; }; MakeSWsEmu: Tool.MakeSWsProc = { --MakeSWsProc with terminal emulation h: ECTHandle; tH: TTY.Handle; h ← zone.NEW[emulation ctool Data]; Context.Create[contextType, h, FreeToolData, window]; h.msgSW ← Tool.MakeMsgSW[ window: window, lines: messageLines]; h.formSW ← Tool.MakeFormSW[ window: window, formProc: MakeFormEmu]; [h: h.pH, producer: h.toBuffer, consumer: h.ttyStream] ← Pipe.Create[200]; h.ttyStream.put ← WriteToWindow; h.ttyStream.putByte ← Stream.defaultObject.putByte; -- Pipe.Create set putByte to a proc. that raises Stream.InvalidOperation. h.ttyStream.setSST ← SetSST; h.ttyStream.clientData ← h; h.ttySW ← Tool.MakeClientSW[ window: window, clientProc: MakeEmulator, clientData: NIL, swType: Emulator.EmuSWType]; UserInput.SetAttention[h.ttySW, AbortProgram]; tH ← TTY.Create[ name: NIL, backingStream: NIL, ttyImpl: h.ttyStream]; h.p ← FORK ReadAndRunThenCleanUp[h, tH]; }; CallReadAndRun: PROCEDURE [h: CTHandle, tH: TTY.Handle] = { AppendCmd: AppendCmdProc = { RETURN[CallGetEditedString[tH, buffer]]; }; [] ← ReadAndRun[h, tH, AppendCmd]; }; ReadAndRunThenCleanUp: PROCEDURE [ h: ECTHandle, tH: TTY.Handle] = { AppendCmd: AppendCmdProc = { RETURN[CallGetEditedString[tH, buffer]]; }; [] ← ReadAndRun[h, tH, AppendCmd]; TTY.Destroy[tH, TRUE]; Pipe.Delete[h.pH]; Emulator.Destroy[h.ttySW]; }; AbortProgram: UserInput.AttentionProcType = { h: CTHandle = Context.Find[ contextType, ToolWindow.WindowForSubwindow[window]]; CRuntime.NoteAbortedProcess[h.p]; }; FreeToolData: PROCEDURE [ d: Context.Data, w: Window.Handle] = { h: Handle = LOOPHOLE[d]; zone.FREE[@h.stdin]; zone.FREE[@h.stdout]; zone.FREE[@h.stderr]; zone.FREE[@d]; }; FreeData: PUBLIC PROCEDURE [h: Handle] = { IF h = NIL THEN RETURN; IF h.stdin # NIL THEN zone.FREE[@h.stdin]; IF h.stdout # NIL THEN zone.FREE[@h.stdout]; IF h.stderr # NIL THEN zone.FREE[@h.stderr]; }; ClientTransition: ToolWindow.TransitionProcType = { IF new = inactive THEN { h: CTHandle ← Context.Find[contextType, window]; h.endingToolInstance ← TRUE; zone.FREE[@h.stdin]; zone.FREE[@h.stdout]; zone.FREE[@h.stderr]; Process.Abort[h.p]; JOIN h.p; Context.Destroy[contextType, window]; }; }; FormItems: TYPE = { another, destroy, debug, stopOnError, stdin, stdout, stderr}; MakeForm: FormSW.ClientItemsProcType = { OPEN FormSW; nItems: CARDINAL = FormItems.LAST.ORD + 1; h: Handle ← Context.Find[ contextType, ToolWindow.WindowForSubwindow[sw]]; items ← AllocateItemDescriptor[nItems]; items[FormItems.another.ORD] ← CommandItem[ tag: "Another"L, place: [1, line0], proc: Another]; items[FormItems.destroy.ORD] ← CommandItem[ tag: "Destroy"L, place: [150, line0], proc: DestroyInstance]; items[FormItems.debug.ORD] ← BooleanItem[ tag: "Debug"L, place: [300, line0], switch: @h.debug]; items[FormItems.stopOnError.ORD] ← BooleanItem[ tag: "StopScriptOnError"L, place: [365, line0], switch: @h.stopOnError]; items[FormItems.stdin.ORD] ← StringItem[ tag: "stdin"L, place: [1, line1], string: @h.stdin]; items[FormItems.stdout.ORD] ← StringItem[ tag: "stdout"L, place: [150, line1], string: @h.stdout]; items[FormItems.stderr.ORD] ← StringItem[ tag: "stderr"L, place: [300, line1], string: @h.stderr]; RETURN[items: items, freeDesc: TRUE]; }; FormItemsEmu: TYPE = { another, destroy, debug, stopOnError, writeLog, stdin, stdout, stderr}; MakeFormEmu: FormSW.ClientItemsProcType = { OPEN FormSW; nItems: CARDINAL = FormItemsEmu.LAST.ORD + 1; h: ECTHandle ← Context.Find[ contextType, ToolWindow.WindowForSubwindow[sw]]; items ← AllocateItemDescriptor[nItems]; items[FormItemsEmu.another.ORD] ← CommandItem[ tag: "Another"L, place: [1, line0], proc: Another]; items[FormItemsEmu.destroy.ORD] ← CommandItem[ tag: "Destroy"L, place: [90, line0], proc: DestroyInstance]; items[FormItemsEmu.debug.ORD] ← BooleanItem[ tag: "Debug"L, place: [180, line0], switch: @h.debug]; items[FormItemsEmu.stopOnError.ORD] ← BooleanItem[ tag: "StopScriptOnError"L, place: [245, line0], switch: @h.stopOnError]; items[FormItemsEmu.writeLog.ORD] ← BooleanItem[ tag: "WriteLog"L, place: [370, line0], proc: SetEmuLogging, switch: @h.logging]; items[FormItemsEmu.stdin.ORD] ← StringItem[ tag: "stdin"L, place: [1, line1], string: @h.stdin]; items[FormItemsEmu.stdout.ORD] ← StringItem[ tag: "stdout"L, place: [150, line1], string: @h.stdout]; items[FormItemsEmu.stderr.ORD] ← StringItem[ tag: "stderr"L, place: [300, line1], string: @h.stderr]; RETURN[items: items, freeDesc: TRUE]; }; -- Emulation window procs. MakeEmulator: PROCEDURE [ sw: Window.Handle, clientData: LONG POINTER] = { h: ECTHandle = Context.Find[ contextType, ToolWindow.WindowForSubwindow[sw]]; Emulator.Create[ sw: sw, data: Emulator.table[vt100], typeIn: WriteToBuffer, otherData: Emulator.table[xvt52], refresh: never, logfile: "EmuCTool.log"L, writeToLog: h.logging]; Emulator.SetRefresh[sw, always]; }; WriteToBuffer: UserInput.StringProcType = { h: ECTHandle ← Context.Find[ contextType, ToolWindow.WindowForSubwindow[window]]; FOR i: CARDINAL IN [0..string.length) DO Stream.PutChar[h.toBuffer, string[i]]; ENDLOOP; }; WriteToWindow: Stream.PutProcedure = { h: ECTHandle = sH.clientData; crMode: BOOLEAN ← Inline.BITAND[ h.iomode, CWindowLib.crMode] # 0; SELECT h.mode FROM TTYConstants.normal => { FOR i: CARDINAL IN [block.startIndex..block.stopIndexPlusOne) DO Emulator.PutChar[ h.ttySW, LOOPHOLE[block.blockPointer[i]]]; IF ~crMode AND block.blockPointer[i] = Ascii.CR.ORD THEN Emulator.PutChar[h.ttySW, Ascii.LF]; ENDLOOP; }; TTYConstants.setBackingSize, TTYConstants.removeChars => { FOR i: CARDINAL IN [block.startIndex..block.stopIndexPlusOne) DO h.modeVal ← h.modeVal * 256 + block.blockPointer[i]; ENDLOOP; }; ENDCASE; }; SetSST: Stream.SetSSTProcedure = { h: ECTHandle = sH.clientData; SELECT h.mode FROM TTYConstants.removeChars => FOR i: CARDINAL IN [0..Inline.LowHalf[h.modeVal]) DO Emulator.PutChar[h.ttySW, Ascii.BS]; Emulator.PutChar[h.ttySW, Ascii.SP]; Emulator.PutChar[h.ttySW, Ascii.BS]; ENDLOOP; TTYConstants.setBackingSize => {}; TTYConstants.blinkDisplay => UserTerminal.BlinkDisplay[]; ENDCASE; h.mode ← sst; h.modeVal ← 0; }; SetEmuLogging: FormSW.NotifyProcType = { h: ECTHandle = Context.Find[ contextType, ToolWindow.WindowForSubwindow[sw]]; IF h.logging THEN Emulator.StartLog[h.ttySW] ELSE Emulator.StopLog[h.ttySW]; }; CToolCommand: Exec.ExecProc = { token, switches: LONG STRING; sense: BOOLEAN ← TRUE; emulation: BOOLEAN ← FALSE; [token: token, switches: switches] ← Exec.GetToken[h]; IF switches # NIL THEN FOR i: CARDINAL IN [0..switches.length) DO SELECT switches[i] FROM '~, '- => sense ← ~sense; 'e, 'E => {emulation ← sense; EXIT}; ENDCASE => sense ← TRUE; ENDLOOP; [] ← Exec.FreeTokenString[token]; [] ← Exec.FreeTokenString[switches]; IF emulation AND ~Runtime.IsBound[LOOPHOLE[Emulator.Create]] THEN Exec.OutputProc[h]["Emu.bcd not loaded"L] ELSE MakeCTool[emulation]; }; ErrorCToolCommand: Exec.ExecProc = { Exec.OutputProc[h][ "Can not create CTool in this environment. Use CExec.~"L]; }; CExecCommand: Exec.ExecProc = { AbortWatcher: PROCEDURE = { ENABLE ABORTED => CONTINUE; DO Process.Pause[Process.SecondsToTicks[2]]; IF Exec.CheckForAbort[h] THEN { TTY.ResetUserAbort[Exec.GetTTY[h]]; CRuntime.NoteAbortedProcess[cExecProcess]; }; ENDLOOP; }; NormalAppendCmd: AppendCmdProc = { RETURN[CallGetEditedString[tH, buffer]]; }; OneStringAppendCmd: AppendCmdProc = { IF pos >= restOfCmdLine.length THEN String.AppendString[to: buffer↑, from: "↑\N"L] ELSE AppendLine[buffer, NextChar]; RETURN[FALSE]; }; NextChar: PROCEDURE RETURNS [c: CHAR] = { IF pos >= restOfCmdLine.length THEN RETURN[Ascii.CR]; c ← restOfCmdLine[pos]; pos ← pos.SUCC; }; data: Handle ← zone.NEW[cexec Data]; tH: TTY.Handle = Exec.GetTTY[h]; cExecProcess: PROCESS = Process.GetCurrent[]; abortWatchProcess: PROCESS; restOfCmdLine: LONG STRING ← GetRestOfCmdLine[h]; pos: CARDINAL; intOutcome: Outcome; appendCmd: AppendCmdProc; data↑ ← [cToolPart: cexec[]]; IF restOfCmdLine = NIL THEN { name: LONG STRING ← zone.NEW[StringBody [60]]; write: Format.StringProc = Exec.OutputProc[h]; name.length ← 0; String.AppendString[name, "CExec "L]; Version.Append[name]; String.AppendString[name, " of "L]; Time.Append[name, Time.Unpack[Runtime.GetBcdTime[]]]; write[name]; zone.FREE[@name]; write["\NCurrent status:\N"L]; PrintStatus[LOOPHOLE[data], tH]; appendCmd ← NormalAppendCmd; } ELSE {pos ← 0; appendCmd ← OneStringAppendCmd; }; { ENABLE { ABORTED => { Process.Abort[abortWatchProcess]; JOIN abortWatchProcess; CONTINUE; }; UNWIND => { Process.Abort[abortWatchProcess]; JOIN abortWatchProcess; }; }; abortWatchProcess ← FORK AbortWatcher[]; intOutcome ← ReadAndRun[LOOPHOLE[data], tH, appendCmd]; Process.Abort[abortWatchProcess]; JOIN abortWatchProcess; }; zone.FREE[@data.stdin]; zone.FREE[@data.stdout]; zone.FREE[@data.stderr]; zone.FREE[@data]; IF restOfCmdLine # NIL THEN { zone.FREE[@restOfCmdLine]; IF intOutcome # normal THEN outcome ← error;}; }; GetRestOfCmdLine: PROCEDURE [h: Exec.Handle] RETURNS [LONG STRING] = { line: LONG STRING; lineMarker: CHAR = '%; c: CHAR ← Exec.GetChar[h]; WHILE c = Ascii.SP OR c = Ascii.TAB DO c ← Exec.GetChar[h]; ENDLOOP; IF c = Ascii.CR OR c = Ascii.NUL THEN RETURN[NIL]; line ← zone.NEW[StringBody [80]]; line.length ← 0; UNTIL c = Ascii.NUL OR c = Ascii.CR DO String.AppendCharAndGrow[ to: @line, c: IF c = lineMarker THEN Ascii.CR ELSE c, z: zone]; c ← Exec.GetChar[h]; ENDLOOP; String.AppendCharAndGrow[ to: @line, c: Ascii.CR, z: zone]; RETURN[line]; }; CallGetEditedString: PROCEDURE [ tH: TTY.Handle, buffer: LONG POINTER TO LONG STRING] RETURNS [lineDeleted: BOOLEAN ← FALSE] = { CommandLineRead: PROCEDURE [c: CHAR] RETURNS [status: TTY.CharStatus] = { IF c = '\N THEN {TTY.PutChar[tH, c]; RETURN[stop]; } ELSE RETURN[ok]; }; [] ← TTY.GetEditedString[ h: tH, s: buffer↑, t: CommandLineRead ! 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"L]; lineDeleted ← TRUE; CONTINUE; }]; }; CEnvironmentCommand: Exec.ExecProc = { -- This prevents accidentally loading a new instance. -- }; RemoveCommands: Exec.ExecProc = { Exec.RemoveCommand[h, "CTool.~"L]; Exec.RemoveCommand[h, "CExec.~"L]; Exec.RemoveCommand[h, "CEnvironment.~"L]; BucketAlloc.Destroy[]; Heap.Delete[StartState.zone]; Heap.Delete[CRuntime.z]; Heap.Delete[CRuntime.arrayZone]; Heap.Delete[zone]; IF CRuntime.log # NIL THEN Stream.Delete[CRuntime.log]; }; RegisterItems: PUBLIC PROCEDURE = { -- Register the normal CTool.~ command only if it is running -- in a tajo environment, rather than on an integration machine. IF Runtime.IsBound[LOOPHOLE[Tool.Create]] THEN Exec.AddCommand[name: "CTool.~"L, proc: CToolCommand] ELSE Exec.AddCommand[ name: "CTool.~"L, proc: ErrorCToolCommand]; Exec.AddCommand[name: "CExec.~"L, proc: CExecCommand]; Exec.AddCommand[ name: "CEnvironment.~"L, proc: CEnvironmentCommand, unload: RemoveCommands]; }; GetPrompt: PUBLIC PROCEDURE RETURNS [prompt: LONG STRING] = { cmPrompt: LONG STRING ← NIL; cmPrompt ← CmFile.UserDotCmLine[ "CTool"L, "Prompt"L ! CmFile.Error => {cmPrompt ← NIL; CONTINUE}]; IF cmPrompt = NIL THEN { prompt ← zone.NEW[StringBody [4]]; prompt.length ← 0; String.AppendString[to: prompt, from: "\N>>>"L]; } ELSE { prompt ← zone.NEW[StringBody [cmPrompt.length + 1]]; prompt.length ← 0; String.AppendChar[s: prompt, c: Ascii.CR]; String.AppendString[to: prompt, from: cmPrompt]; [] ← CmFile.FreeString[cmPrompt]; }; }; Unload: PUBLIC PROCEDURE [ cmdLine: LONG STRING, tty: TTY.Handle] RETURNS [outcome: Outcome ← normal] = { th: Token.Handle ← NIL; item: LONG STRING ← NIL; fileName: LONG STRING ← zone.NEW[ StringBody [MFile.maxNameLength]]; { ENABLE UNWIND => { IF item # NIL THEN [] ← Token.FreeTokenString[item]; IF th # NIL THEN [] ← Token.FreeStringHandle[th]; IF fileName # NIL THEN zone.FREE[@fileName]; }; th ← Token.StringToHandle[cmdLine]; item ← Token.Item[th]; -- Get past "Unload.~" item ← Token.FreeTokenString[item]; DO item ← Token.Item[th]; IF item = NIL THEN EXIT; TTY.PutString[tty, "\NUnloading "L]; IF item[0] IN ['0..'9] THEN { ENABLE { String.InvalidNumber => { Blink[]; TTY.PutString[tty, "Invalid parameter"L]; CONTINUE; }; StartState.UnloadError => { outcome ← error; Blink[]; TTY.PutString[tty, message]; CONTINUE; }; }; h: MLoader.Handle = String.StringToLongNumber[item]; TTY.PutString[tty, item]; TTY.PutString[tty, "..."L]; StartState.Unload[h]; TTY.PutString[tty, "done."L]; } ELSE { ENABLE MFile.Error => { msg: LONG STRING ← [75]; MFile.AppendErrorMessage[msg, code, file]; TTY.PutString[tty, msg]; outcome ← error; CONTINUE; }; num: LONG STRING ← [8]; file: MFile.Handle; found: CARDINAL; instancesUnloaded: CARDINAL; fileName.length ← 0; String.AppendString[to: fileName, from: item]; found ← UniqueFilePrefix[fileName]; IF found # 1 THEN { Blink[]; TTY.PutString[tty, item]; TTY.PutString[ tty, IF found = 0 THEN ": not found"L ELSE ": ambiguous"L]; zone.FREE[@fileName]; th ← Token.FreeStringHandle[th]; RETURN[error]; }; TTY.PutString[tty, fileName]; TTY.PutString[tty, "..."L]; file ← MFile.Acquire[fileName, anchor, []]; instancesUnloaded ← StartState.UnloadFromFile[ file ! StartState.UnloadError => { outcome ← error; Blink[]; TTY.PutString[tty, message]; instancesUnloaded ← instancesAlreadyUnloaded; CONTINUE; }; UNWIND => MFile.Release[file]]; MFile.Release[file]; String.AppendChar[s: num, c: Ascii.CR]; String.AppendNumber[num, instancesUnloaded]; TTY.PutString[tty, num]; TTY.PutString[tty, " instance(s) unloaded."L]; }; item ← Token.FreeTokenString[item]; ENDLOOP; th ← Token.FreeStringHandle[th]; zone.FREE[@fileName]; }; }; ShowHandles: PUBLIC PROCEDURE [ cmdLine: LONG STRING, tty: TTY.Handle] RETURNS [outcome: Outcome ← normal] = { PrintLoadHandle: StartState.EnumerateProc = { Write: Format.StringProc = {TTY.PutString[tty, s]; }; Format.LongOctal[Write, mh]; TTY.PutChar[tty, ' ]; }; th: Token.Handle ← NIL; item: LONG STRING ← NIL; fileName: LONG STRING ← zone.NEW[ StringBody [MFile.maxNameLength]]; { ENABLE UNWIND => { IF item # NIL THEN [] ← Token.FreeTokenString[item]; IF th # NIL THEN [] ← Token.FreeStringHandle[th]; IF fileName # NIL THEN zone.FREE[@fileName]; }; th ← Token.StringToHandle[cmdLine]; item ← Token.Item[th]; -- Get past "ShowHandles.~" item ← Token.FreeTokenString[item]; DO ENABLE MFile.Error => { msg: LONG STRING ← [75]; MFile.AppendErrorMessage[msg, code, file]; TTY.PutString[tty, msg]; outcome ← error; CONTINUE; }; file: MFile.Handle; found: CARDINAL; item ← Token.Item[th]; IF item = NIL THEN EXIT; TTY.PutChar[tty, '\N]; fileName.length ← 0; String.AppendString[from: item, to: fileName]; found ← UniqueFilePrefix[fileName]; IF found # 1 THEN { Blink[]; TTY.PutString[tty, item]; TTY.PutString[ tty, IF found = 0 THEN ": not found"L ELSE ": ambiguous"L]; zone.FREE[@fileName]; th ← Token.FreeStringHandle[th]; RETURN[error]; }; TTY.PutString[tty, fileName]; TTY.PutString[tty, ": "L]; {ENABLE MFile.Error => { TTY.PutString[tty, "Unable to acquire file."L]; CONTINUE;}; file ← MFile.Acquire[fileName, anchor, []]; StartState.EnumerateHandles[file, PrintLoadHandle]; MFile.Release[file]; }; item ← Token.FreeTokenString[item]; ENDLOOP; zone.FREE[@fileName]; th ← Token.FreeStringHandle[th]; }; }; ChangeStandardStreams: PUBLIC PROCEDURE [ buffer: LONG STRING, data: Handle] = { tH: Token.Handle = Token.StringToHandle[buffer]; tokenStdin, tokenStdout, tokenStderr: LONG STRING; Token.Skip[tH, NIL, Token.WhiteSpace, TRUE]; tokenStdin ← Token.Filtered[tH, NIL, FilterFileName]; -- stdin data.stdin.length ← 0; IF tokenStdin # NIL THEN { String.AppendStringAndGrow[ to: @(data.stdin), from: tokenStdin, z: zone]; [] ← Token.FreeTokenString[tokenStdin]; }; WITH hh: data SELECT FROM ctool => FormSW.DisplayItem[ sw: hh.formSW, index: WITH hh SELECT FROM regular => FormItems.stdin.ORD, ENDCASE => FormItemsEmu.stdin.ORD]; ENDCASE; -- stdout tokenStdout ← Token.Filtered[tH, NIL, FilterFileName]; data.stdout.length ← 0; IF tokenStdout # NIL THEN { String.AppendStringAndGrow[ to: @(data.stdout), from: tokenStdout, z: zone]; [] ← Token.FreeTokenString[tokenStdout]; }; WITH hh: data SELECT FROM ctool => FormSW.DisplayItem[ sw: hh.formSW, index: WITH hh SELECT FROM regular => FormItems.stdout.ORD, ENDCASE => FormItemsEmu.stdout.ORD]; ENDCASE; -- stderr tokenStderr ← Token.Filtered[tH, NIL, FilterFileName]; LOOPHOLE[data, Handle].stderr.length ← 0; IF tokenStderr # NIL THEN { String.AppendStringAndGrow[ to: @(LOOPHOLE[data, Handle].stderr), from: tokenStderr, z: zone]; [] ← Token.FreeTokenString[tokenStderr]; }; WITH hh: data SELECT FROM ctool => FormSW.DisplayItem[ sw: hh.formSW, index: WITH hh SELECT FROM regular => FormItems.stderr.ORD, ENDCASE => FormItemsEmu.stderr.ORD]; ENDCASE; [] ← Token.FreeStringHandle[tH]; }; FilterFileName: Token.FilterProcType = { inClass ← c # Ascii.SP AND c # ', AND c # Ascii.CR AND c # Ascii.TAB; }; ChangeSwitches: PUBLIC PROCEDURE [ buffer: LONG STRING, data: Handle] = { tH: Token.Handle = Token.StringToHandle[buffer]; switches: LONG STRING; sense: BOOLEAN ← TRUE; Token.Skip[tH, NIL, Token.WhiteSpace, TRUE]; switches ← Token.Filtered[tH, NIL, Token.Switches]; IF switches # NIL THEN { FOR i: CARDINAL IN [0..switches.length) DO SELECT switches[i] FROM '~, '- => sense ← ~sense; 'd => { data.debug ← sense; WITH hh: data SELECT FROM ctool => FormSW.DisplayItem[ sw: hh.formSW, index: WITH hh SELECT FROM regular => FormItems.debug.ORD, ENDCASE => FormItemsEmu.debug.ORD]; ENDCASE; sense ← TRUE; }; 's => { data.stopOnError ← sense; WITH hh: data SELECT FROM ctool => FormSW.DisplayItem[ sw: hh.formSW, index: WITH hh SELECT FROM regular => FormItems.stopOnError.ORD, ENDCASE => FormItemsEmu.stopOnError.ORD]; ENDCASE; sense ← TRUE; }; ENDCASE; ENDLOOP; [] ← Token.FreeTokenString[switches]; }; [] ← Token.FreeStringHandle[tH]; }; PrintStatus: PUBLIC PROCEDURE [ data: Handle, tty: TTY.Handle] = { TTY.PutString[tty, "stdin: "L]; TTY.PutString[ tty, IF String.Empty[data.stdin] THEN "window"L ELSE data.stdin]; TTY.PutString[tty, "\Nstdout: "L]; TTY.PutString[ tty, IF String.Empty[data.stdout] THEN "window"L ELSE data.stdout]; TTY.PutString[tty, "\Nstderr: "L]; TTY.PutString[ tty, IF String.Empty[data.stderr] THEN "window"L ELSE data.stderr]; TTY.PutString[tty, "\NDebug: "]; TTY.PutString[ tty, IF data.debug THEN "TRUE"L ELSE "FALSE"L]; TTY.PutString[tty, "\NStopScriptOnError: "L]; TTY.PutString[ tty, IF data.stopOnError THEN "TRUE"L ELSE "FALSE"L]; }; ParseLine: PUBLIC PROCEDURE [buffer: LONG STRING] RETURNS [ name: LONG STRING ← NIL, argC: CARDINAL ← 1, argV: ArgSeq ← NIL] = { OPEN CString; initArgSeqLength: CARDINAL = 8; tH: Token.Handle ← NIL; tempName: LONG STRING ← NIL; { ENABLE UNWIND => { IF tH # NIL THEN [] ← Token.FreeStringHandle[tH]; IF tempName # NIL THEN [] ← Token.FreeTokenString[tempName]; }; IF buffer.length = 0 THEN RETURN; tH ← Token.StringToHandle[buffer]; tempName ← Token.Item[tH]; IF tempName # NIL THEN { name ← String.CopyToNewString[ tempName, zone, MFile.maxNameLength - tempName.length]; tempName ← Token.FreeTokenString[tempName]; argV ← zone.NEW[StringSeq [initArgSeqLength]]; argV.length ← 1; argV[0] ← LongStringToCString[name, zone]; }; DO nextArg: LONG STRING ← Token.MaybeQuoted[ tH, NIL ! Token.UnterminatedQuote => RESUME ]; { ENABLE UNWIND => IF nextArg # NIL THEN [] ← Token.FreeTokenString[nextArg]; IF String.Empty[nextArg] THEN EXIT; argC ← argC + 1; IF argV.length < argV.maxlength THEN { argV[argV.length] ← LongStringToCString[ nextArg, zone]; argV.length ← argV.length + 1; } ELSE { oldArgV: ArgSeq ← argV; { ENABLE UNWIND => { IF argV # NIL THEN zone.FREE[@argV]; IF oldArgV # NIL THEN zone.FREE[@oldArgV]; }; argV ← zone.NEW[StringSeq [oldArgV.maxlength * 2]]; FOR i: CARDINAL IN [0..oldArgV.length) DO argV[i] ← oldArgV[i]; ENDLOOP; argV.length ← oldArgV.length + 1; argV[oldArgV.length] ← LongStringToCString[ nextArg, zone]; zone.FREE[@oldArgV]; }; }; nextArg ← Token.FreeTokenString[nextArg]; }; ENDLOOP; tH ← Token.FreeStringHandle[tH]; }; }; GetFirstWord: PUBLIC PROCEDURE [buffer: LONG STRING] RETURNS [firstWord: LONG STRING] = { th: Token.Handle ← NIL; ts: LONG STRING ← NIL; { ENABLE UNWIND => { IF ts # NIL THEN [] ← Token.FreeTokenString[ts]; IF th # NIL THEN [] ← Token.FreeStringHandle[th]; }; th ← Token.StringToHandle[buffer]; ts ← Token.Item[th]; IF ts = NIL THEN { th ← Token.FreeStringHandle[th]; RETURN[NIL]; }; firstWord ← String.CopyToNewString[ts, zone]; ts ← Token.FreeTokenString[ts]; th ← Token.FreeStringHandle[th]; }; }; Blink: PUBLIC PROCEDURE = {UserTerminal.BlinkDisplay[]; }; Print: PUBLIC PROCEDURE [msg: LONG STRING, data: Handle] = {Put.Text[data.msgSW, msg]; }; Debugging: PUBLIC PROCEDURE [data: Handle] RETURNS [BOOLEAN] = {RETURN[data.debug]; }; StopOnError: PUBLIC PROCEDURE [data: Handle] RETURNS [BOOLEAN] = {RETURN[data.stopOnError]; }; EndingToolInstance: PUBLIC PROCEDURE [data: Handle] RETURNS [BOOLEAN] = {RETURN[data.endingToolInstance]; }; StopIfCExec: PUBLIC PROCEDURE [data: Handle] = { IF data.tool = cexec THEN { data.endingToolInstance ← TRUE; ERROR ABORTED; -- quit }; }; UpdateLogReadLength: PUBLIC PROCEDURE [data: Handle] = { WITH hh: data SELECT FROM ctool => WITH hhh: hh SELECT FROM regular => MStream.SetLogReadLength[ stream: hhh.logStream, position: MStream.GetLength[hhh.logStream]]; ENDCASE; ENDCASE; }; MakeDataForSystemCall: PUBLIC PROCEDURE RETURNS [newData: Handle] = { newData ← zone.NEW[temp Data]; newData.cToolPart ← temp[]}; SetOutputMode: PUBLIC PROCEDURE [ h: Handle, iomode: INTEGER] = {h.iomode ← iomode; }; IF Runtime.IsBound[LOOPHOLE[Tool.Create]] THEN contextType ← Context.UniqueType[]; }.