-- Copyright (C) 1986 by Xerox Corporation. All rights reserved. -- BWSCEnvUtil.mesa -- DWR 17-Mar-86 20:27:30 -- NFS 27-May-86 13:16:03 -- MEW 8-May-86 8:57:46 -- Implementation of CEnvUtil for BWS. DIRECTORY Ascii, Atom, -- BucketAlloc, -- BWSCEnvUtilExtra, StartState, BWSUtil, CEnvUtil, Containee, Context, CRuntime, CString, CWindowLib, Display, Emulator, Format, FormWindow, Inline, MLoader, MessageWindow, MFile, MFileOnNSFile, MStream, NSFile, NSString, OptionFile, Pipe, Process, Prototype, Runtime, Selection, SimpleTextFont, StarWindowShell, Stream, String, Time, TIP, TTY, TTYConstants, UserTerminal, Window, XChar, XString, XToken; BWSCEnvUtil: MONITOR IMPORTS Atom, --BucketAlloc, --BWSUtil, CEnvUtil, Containee, Context, CRuntime, CString, Display, Emulator, Format, FormWindow, Inline, MessageWindow, MFile, MFileOnNSFile, MStream, NSFile, NSString, OptionFile, Pipe, Process, Prototype, Runtime, Selection, SimpleTextFont, StartState, StarWindowShell, Stream, String, Time, TIP, TTY, UserTerminal, XChar, XString, XToken EXPORTS CEnvUtil --, BWSCEnvUtilExtra -- SHARES XString = { OPEN CEnvUtil; Data:PUBLIC TYPE = --MACHINE DEPENDENT-- RECORD [ -- stdin(0): LONG STRING ← zone.NEW[StringBody[100]], -- stdout(2): LONG STRING ← zone.NEW[StringBody[100]], -- stderr(4): LONG STRING ← zone.NEW[StringBody[100]], debug--(6)--:BOOLEAN ← FALSE, stopOnError--(7)--:BOOLEAN ← TRUE, endingToolInstance--(8)--:BOOLEAN ← FALSE, busy: BOOLEAN ← FALSE, -- for icon dropping in BWS iomode: INTEGER ← 0, cToolPart--(9)--:SELECT tool--(9)--:ToolType FROM ctool => [ msgSW--(10)--: Window.Handle ← NIL, formSW--(12)--: Window.Handle ← NIL, ttySW--(14)--: Window.Handle ← NIL, p--(16)--:PROCESS, emuPart--(17)--:SELECT tty--(17)--:* FROM emulation => [ pH--(18)--:Pipe.Handle, toBuffer--(20)--,ttyStream--(22)--:Stream.Handle, modeVal--(24)--:LONG CARDINAL ← 0, mode--(26)--:Stream.SubSequenceType ← TTYConstants.normal ], regular =>[logStream--(18)--: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 ← NIL] = { 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]; }; -- check for file, file.archivebcd, file.bcd, file*.archivebcd, file*.bcd RemoveExtension: PROCEDURE [s: LONG STRING, ext: LONG STRING] = { IF ext = NIL THEN RETURN; s.length ← s.length - ext.length }; UniqueFilePrefix: PUBLIC PROCEDURE [fileName: LONG STRING] RETURNS [matches: CARDINAL] = { -- complete the fileName and return whether it was unique -- assume fileName is long enough to be appended to -- FIX THIS TO USE MFILE ONLY !!!!!!! scope: NSFile.Scope ← []; selections: NSFile.Selections ← [interpreted: [name: TRUE]]; Check: PROCEDURE [ext: LONG STRING] RETURNS [BOOLEAN] = { matchedName: LONG STRING ← [50]; oldLength: CARDINAL = fileName.length; List: NSFile.AttributesProc = { matchedName.length ← 0; NSString.AppendToMesaString[matchedName, attributes.name]; RETURN[(matches ← matches + 1) = 1] }; EachDirectory: MFileOnNSFile.EachElementProc = { dfh: NSFile.Handle ← NSFile.OpenByReference [element]; NSFile.List[dfh, List, selections, scope ! NSFile.Error => GOTO FileError]; NSFile.Close[dfh]; IF matches = 1 THEN { -- found a unique name fileName.length ← 0; String.AppendString[to: fileName, from: matchedName]; }; RETURN [matches # 0]; -- TRUE if we have a match or an ambiguous name EXITS FileError => { -- stop enumeration and indicate not found. matches ← 0; RETURN [TRUE];}; }; IF ext # NIL THEN String.AppendString[fileName, ext]; scope.filter ← [matches[[name[NSString.StringFromMesaString[fileName]]]]]; MFileOnNSFile.EnumeratePath [EachDirectory]; IF matches # 1 THEN fileName.length ← oldLength; -- undo append; RETURN [matches # 0] }; -- TRUE if we have a match or an ambiguous name matches ← 0; 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] = { h: ECTHandle = LOOPHOLE[data]; rb, rb1: XString.ReaderBody; -- The following is a hack until we figure out what to do. -- We don't have the stdin,... fields in a data object, so -- we can't get the stream names from there. And when this -- is the temp variant, there's no form subwindow to use. WITH data SELECT FROM temp => RETURN[defaultStdin, defaultStdout, defaultStderr] ENDCASE; rb ← FormWindow.LookAtTextItemValue[h.formSW, FormItems.stdin.ORD]; IF XString.Empty[@rb] THEN { stdin ← defaultStdin; FormWindow.DoneLookingAtTextItemValue[h.formSW, FormItems.stdin.ORD]; } ELSE { stdin ← BWSUtil.MakeStreamReader[file: @rb, access: readOnly]; FormWindow.DoneLookingAtTextItemValue[h.formSW, FormItems.stdin.ORD]; IF stdin = NIL THEN StdStreamError[stdin]; }; rb ← FormWindow.LookAtTextItemValue[h.formSW, FormItems.stdout.ORD]; IF XString.Empty[@rb] THEN { stdout ← defaultStdout; } ELSE { stdout ← BWSUtil.MakeStreamReader[file: @rb, access: writeOnly]; IF stdout = NIL THEN { Stream.Delete[stdin]; FormWindow.DoneLookingAtTextItemValue[h.formSW, FormItems.stdout.ORD]; ERROR StdStreamError[stdout]; }; }; rb1 ← FormWindow.LookAtTextItemValue[h.formSW, FormItems.stderr.ORD]; SELECT TRUE FROM XString.Empty[@rb1] => stderr ← defaultStderr; XString.Equivalent[@rb, @rb1] => stderr ← stdout; ENDCASE => { stderr ← BWSUtil.MakeStreamReader[file: @rb1, access: writeOnly]; IF stderr = NIL THEN { Stream.Delete[stdin]; Stream.Delete[stdout]; FormWindow.DoneLookingAtTextItemValue[h.formSW, FormItems.stdout.ORD]; FormWindow.DoneLookingAtTextItemValue[h.formSW, FormItems.stderr.ORD]; ERROR StdStreamError[stderr]; }; }; FormWindow.DoneLookingAtTextItemValue[h.formSW, FormItems.stdout.ORD]; FormWindow.DoneLookingAtTextItemValue[h.formSW, FormItems.stderr.ORD]; }; StdStreamError: PUBLIC ERROR [whichStream: StandardStream] = CODE; GetFileInputStream: PUBLIC PROCEDURE [buffer: LONG STRING] RETURNS[sH: Stream.Handle ← NIL] = { fileName: XString.ReaderBody; rb: XString.ReaderBody ← XString.FromSTRING[buffer, TRUE]; tH: XToken.Handle ← XToken.ReaderToHandle[@rb]; XToken.Skip[tH, NIL, XToken.WhiteSpace, TRUE]; fileName ← XToken.Item[tH]; IF ~XString.Empty[@fileName] THEN { sH ← BWSUtil.MakeStreamReader[file: @fileName, access: readOnly]; IF sH = NIL THEN { -- Try adding ".cscript" extension. ext: XString.ReaderBody ← XString.FromSTRING[".cscript"L, TRUE]; extendedName: XString.WriterBody ← XString.NewWriterBody[ maxLength: XString.ByteLength[@fileName] + XString.ByteLength[@ext], z: zone]; XString.AppendReader[@extendedName, @fileName]; IF XString.AppendExtensionIfNeeded[ to: @extendedName, extension: @ext] THEN sH ← BWSUtil.MakeStreamReader[ file: XString.ReaderFromWriter[@extendedName], access: readOnly]; XString.FreeWriterBytes[@extendedName]; }; }; [] ← XToken.FreeTokenString[@fileName]; [] ← XToken.FreeReaderHandle[tH]; }; AppendLine: PUBLIC PROCEDURE [ line: LONG POINTER TO LONG STRING, getChar: PROC RETURNS [c: CHAR]] = { DO c: CHAR ← getChar[! MFile.Error, MStream.Error, Stream.EndOfStream => EXIT]; String.AppendCharAndGrow[to: line, c: c, z: zone]; IF c = Ascii.CR THEN EXIT; ENDLOOP; }; contextType: Context.Type; ToolList: TYPE = LONG POINTER TO ToolInstance; ToolInstance: TYPE = RECORD [ ref: NSFile.Reference, file: NSFile.Handle, bw: Window.Handle, sws: StarWindowShell.Handle, next: ToolList ]; tools: ToolList ← NIL; OpenCTool: ENTRY PROCEDURE [data: Containee.DataHandle] RETURNS [sws: StarWindowShell.Handle, bw: Window.Handle] = { tool: ToolList ← FindRunningToolFromIcon[data]; IF tool # NIL AND tool.sws # NIL THEN RETURN [tool.sws, tool.bw]; BEGIN name: LONG STRING ← [60]; xname: XString.ReaderBody; String.AppendString[name, "CTool of "L]; Time.Append[name, Time.Unpack[Runtime.GetBcdTime[]]]; xname ← XString.FromSTRING[name, TRUE]; sws ← StarWindowShell.Create[name: @xname, transitionProc: ClientTransition, sleeps: TRUE]; StarWindowShell.SetIsCloseLegalProc[sws, IsCloseLegal]; StarWindowShell.SetPreferredDims[sws, [h: windowDims.h, w: windowDims.w + 15]]; IF tool = NIL THEN { bw ← MakeSWs[sws]; AddRunningTool[data, sws, bw]; } ELSE { StarWindowShell.InstallBody[sws, tool.bw]; tool.sws ← sws; bw ← tool.bw; }; END; }; IsCloseLegal: ENTRY StarWindowShell.IsCloseLegalProc = { tool: ToolList = FindRunningToolFromSWS[sws]; IF tool = NIL THEN RETURN [TRUE]; StarWindowShell.DestallBody[tool.bw]; tool.sws ← [NIL]; RETURN [TRUE]; }; FindRunningToolFromIcon: INTERNAL PROCEDURE [data: Containee.DataHandle] RETURNS [t: ToolList] = { FOR t ← tools, t.next UNTIL t = NIL DO IF t.ref = data.reference THEN RETURN [t]; ENDLOOP; RETURN [NIL]; }; FindRunningToolFromSWS: INTERNAL PROCEDURE [sws: StarWindowShell.Handle] RETURNS [t: ToolList] = { FOR t ← tools, t.next UNTIL t = NIL DO IF t.sws = sws THEN RETURN [t]; ENDLOOP; RETURN [NIL]; }; FindSWSFromBW: ENTRY PROCEDURE [bw: Window.Handle] RETURNS [sws: StarWindowShell.Handle] = { FOR t: ToolList ← tools, t.next UNTIL t = NIL DO IF t.bw = bw THEN RETURN [t.sws]; ENDLOOP; RETURN [[NIL]]; }; AddRunningTool: INTERNAL PROCEDURE [data: Containee.DataHandle, sws: StarWindowShell.Handle, bw: Window.Handle] = { tools ← zone.NEW[ToolInstance ← [ ref: data.reference, bw: bw, sws: sws, file: NSFile.OpenByReference[data.reference], -- so icon won't be deleted next: tools]]; }; DeleteRunningTool: PROCEDURE [bw: Window.Handle] = { t: ToolList; sws: StarWindowShell.Handle; DeleteInstance: ENTRY PROC = { prev: LONG POINTER TO ToolList ← @tools; FOR t ← prev↑, prev↑ UNTIL t = NIL DO IF t.bw = bw THEN { prev↑ ← t.next; NSFile.Close[t.file]; zone.FREE[@t]; RETURN; }; prev ← @t.next; ENDLOOP; }; sws ← FindSWSFromBW[bw]; IF sws # NIL THEN [] ← StarWindowShell.Pop[sws]; DeleteInstance[]; DestroyCToolInstance[bw]; }; DestroyInstance: FormWindow.CommandProc = { h: CTHandle; bw: Window.Handle; [h, bw] ← LocalFind[window]; IF h = NIL THEN RETURN; IF h.busy THEN { rb: XString.ReaderBody ← XString.FromSTRING["CTool is busy. Hit STOP to abort current program."L]; MessageWindow.Post[h.msgSW, @rb, TRUE]; } ELSE { DeleteRunningTool[bw]; }; }; MakeSWs: INTERNAL PROCEDURE [sws: StarWindowShell.Handle] RETURNS [bw: Window.Handle] = { h: ECTHandle ← zone.NEW[emulation ctool Data]; tH: TTY.Handle; bw ← StarWindowShell.CreateBody[ sws: sws, box: [place: [x: 0, y: 0], dims: windowDims]]; FormWindow.Create[ window: bw, makeItemsProc: MakeItems, layoutProc: DoLayout, clientData: h]; Context.Create[contextType, h, FreeToolData, bw]; [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; tH ← TTY.Create[name:NIL,backingStream:NIL,ttyImpl:h.ttyStream]; TIP.SetAttention[h.ttySW, AbortProgram]; h.p ← FORK ReadAndRunThenCleanUp[h,tH]; }; WriteToWindow:Stream.PutProcedure = { h:ECTHandle = sH.clientData; crMode: BOOLEAN ← Inline.BITAND[h.iomode, CWindowLib.crMode] #0; SELECT h.mode FROM TTYConstants.normal => { start: CARDINAL ← block.startIndex; stop: CARDINAL ← block.stopIndexPlusOne; IF crMode THEN Emulator.PutBlock[h.ttySW, [block.blockPointer, start, stop]] ELSE FOR i:CARDINAL IN [block.startIndex..block.stopIndexPlusOne) DO IF block.blockPointer[i] # Ascii.CR.ORD THEN LOOP; stop ← i + 1; Emulator.PutBlock[h.ttySW, [block.blockPointer, start, stop]]; start ← stop; Emulator.PutChar[h.ttySW,Ascii.LF]; REPEAT FINISHED => IF block.stopIndexPlusOne > start THEN Emulator.PutBlock[ h.ttySW, [block.blockPointer, start, block.stopIndexPlusOne]]; 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; s: LONG STRING ← [3]; s[0] ← s[2] ← Ascii.BS; s[1] ← Ascii.SP; s.length ← 3; SELECT h.mode FROM TTYConstants.removeChars => FOR i: CARDINAL IN [0..Inline.LowHalf[h.modeVal]) DO Emulator.PutSTRING[h.ttySW,s]; ENDLOOP; TTYConstants.setBackingSize => {}; TTYConstants.blinkDisplay => UserTerminal.BlinkDisplay[]; ENDCASE; h.mode ← sst; h.modeVal ← 0; }; 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]; }; FormItems: TYPE = {msg, form, tty, --another,-- destroy, debug, stopOnError, stdin, stdout, stderr}; margin: INTEGER = 2; windowDims: Window.Dims = [w: 630, h: 500]; msgWindowDims: Window.Dims = [w: windowDims.w - 2*margin, h: 20]; msgWindowBox: Window.Box = [ place: [ x: margin, y: margin], dims: msgWindowDims]; formWindowDims: Window.Dims = [w: windowDims.w - 2*margin, h: 46]; formWindowBox: Window.Box = [ place: [ x: margin, y: msgWindowBox.place.y + msgWindowBox.dims.h + 2*margin], dims: formWindowDims]; ttyWindowDims: Window.Dims = [w: windowDims.w - 2*margin, h: 300]; ttyWindowBox: Window.Box = [ place: [ x: margin, y: formWindowBox.place.y + formWindowBox.dims.h + 2*margin], dims: ttyWindowDims]; MakeItems: FormWindow.MakeItemsProc = { h: ECTHandle = clientData; h.msgSW ← FormWindow.MakeWindowItem[ window: window, myKey: FormItems.msg.ORD, size: msgWindowDims]; MessageWindow.Create[window: h.msgSW, lines: 1]; h.formSW ← FormWindow.MakeWindowItem [ -- the formSW is inside a FormWindow window item window: window, myKey: FormItems.form.ORD, size: formWindowDims]; FormWindow.Create[ window: h.formSW, makeItemsProc: MakeFormItems, layoutProc: DoFormLayout, clientData: h]; h.ttySW ← FormWindow.MakeWindowItem [ window: window, myKey: FormItems.tty.ORD, size: ttyWindowDims]; Emulator.Create[ sw: h.ttySW, data:Emulator.table[vt100], typeIn:WriteToBuffer, otherData: Emulator.table[xvt52], refresh: never, logfile:NIL ,writeToLog:FALSE]; Emulator.SetRefresh[h.ttySW, always]; }; WriteToBuffer:Emulator.TypeInProcType = { h: ECTHandle = LOOPHOLE[LocalFind[sw].h]; IF h = NIL THEN RETURN; Stream.PutBlock[h.toBuffer,input]; }; FormTags: TYPE = ARRAY FormItems[destroy..stderr] OF XString.ReaderBody; MakeFormItems: FormWindow.MakeItemsProc = { h: ECTHandle = clientData; tags: FormTags ← [ XString.FromSTRING["Destroy"L, TRUE], XString.FromSTRING["Debug"L, TRUE], XString.FromSTRING["StopScriptOnError"L, TRUE], XString.FromSTRING["stdin"L, TRUE], XString.FromSTRING["stdout"L, TRUE], XString.FromSTRING["stderr"L, TRUE]]; FormWindow.MakeCommandItem[ window: window, myKey: FormItems.destroy.ORD, commandProc: DestroyInstance, commandName: @tags[destroy]]; FormWindow.MakeBooleanItem[ window: window, myKey: FormItems.debug.ORD, changeProc: BooleanChange, label: [string[tags[debug]]], initBoolean: h.debug]; FormWindow.MakeBooleanItem[ window: window, myKey: FormItems.stopOnError.ORD, changeProc: BooleanChange, label: [string[tags[stopOnError]]], initBoolean: h.stopOnError]; FormWindow.MakeTextItem[ window: window, myKey: FormItems.stdin.ORD, width: 130, tag: @tags[stdin]]; FormWindow.MakeTextItem[ window: window, myKey: FormItems.stdout.ORD, width: 130, tag: @tags[stdout]]; FormWindow.MakeTextItem[ window: window, myKey: FormItems.stderr.ORD, width: 130, tag: @tags[stderr]]; }; LocalFind: ENTRY PROC [window: Window.Handle] RETURNS [h: CTHandle, bw: Window.Handle] = { sws: StarWindowShell.Handle = StarWindowShell.ShellFromChild[window]; tool: ToolList = FindRunningToolFromSWS[sws]; IF tool = NIL THEN RETURN [NIL, NIL]; bw ← tool.bw; h ← Context.Find[contextType, bw]; }; BooleanChange: FormWindow.BooleanChangeProc = { h: CTHandle = LocalFind[window].h; IF h = NIL THEN RETURN; SELECT item FROM FormItems.debug.ORD => h.debug ← newValue; FormItems.stopOnError.ORD => h.stopOnError ← newValue; ENDCASE; }; DoFormLayout: FormWindow.LayoutProc = { line: FormWindow.Line; line ← FormWindow.AppendLine[window, margin]; -- FormWindow.AppendItem[window, FormItems.another.ORD, line, 20]; FormWindow.AppendItem[window, FormItems.destroy.ORD, line, 50]; FormWindow.AppendItem[window, FormItems.debug.ORD, line, 50]; FormWindow.AppendItem[window, FormItems.stopOnError.ORD, line, 50]; line ← FormWindow.AppendLine[window, margin]; FormWindow.AppendItem[window, FormItems.stdin.ORD, line, 10]; FormWindow.AppendItem[window, FormItems.stdout.ORD, line, 10]; FormWindow.AppendItem[window, FormItems.stderr.ORD, line, 10]; }; DoLayout: FormWindow.LayoutProc = { -- for three windows FormWindow.SetItemBox[window, FormItems.msg.ORD, msgWindowBox]; FormWindow.SetItemBox[window, FormItems.form.ORD, formWindowBox]; FormWindow.SetItemBox[window, FormItems.tty.ORD, ttyWindowBox]; }; CallReadAndRun: PROCEDURE [h:CTHandle, tH:TTY.Handle] = { AppendCmd:AppendCmdProc = { h.busy ← FALSE; Process.SetPriority[Process.priorityForeground]; lineDeleted ← CallGetEditedString[tH, buffer]; Process.SetPriority[Process.priorityBackground]; h.busy ← TRUE; }; [] ← ReadAndRun[h,tH,AppendCmd]; }; AbortProgram: TIP.AttentionProc = { h: CTHandle = LocalFind[window].h; IF h = NIL THEN RETURN; CRuntime.NoteAbortedProcess[h.p]; }; FreeToolData: PROCEDURE[d: Context.Data, bw: Window.Handle] = { h: CTHandle ← LOOPHOLE[d]; -- TTYStringWindow.Destroy[h.ttySW]; -- FormWindow.Destroy[h.formSW]; ++ maybe these are done by BWS -- MessageWindow.Destroy[h.msgSW]; zone.FREE[@h]; }; FreeData:PUBLIC PROCEDURE[h: Handle] = {}; ClientTransition: StarWindowShell.TransitionProc = {}; DestroyCToolInstance: PROCEDURE [bw: Window.Handle] = { h: CTHandle ← Context.Find[contextType, bw]; h.endingToolInstance ← TRUE; Process.Abort[h.p]; JOIN h.p; Context.Destroy[contextType, bw]; }; 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;}]; }; RegisterItems: PUBLIC PROCEDURE = {}; GetPrompt: PUBLIC PROCEDURE RETURNS [prompt: LONG STRING ← NIL] = { section, entry: XString.ReaderBody; CallBack: PROC [value: XString.Reader] = { ss: String.SubStringDescriptor; prompt ← zone.NEW[StringBody[XString.ByteLength[value]+1]]; prompt.length ← 0; String.AppendChar[s: prompt, c: Ascii.CR]; ss ← BWSUtil.SubStringFromXString[value]; String.AppendSubString[prompt, @ss]; }; section ← XString.FromSTRING["CTool"L, TRUE]; entry ← XString.FromSTRING["Prompt"L, TRUE]; OptionFile.GetStringValue[@section, @entry, CallBack ! OptionFile.Error => CONTINUE]; IF prompt = NIL THEN { prompt ← zone.NEW[StringBody[5]]; prompt.length ← 0; String.AppendString[to:prompt, from:"\N>>> "L]; }; }; Unload: PUBLIC PROCEDURE [cmdLine: LONG STRING, tty: TTY.Handle] RETURNS[outcome: Outcome ← normal] = { rb: XString.ReaderBody ← XString.FromSTRING[cmdLine]; th: XToken.Handle ← NIL; item: XString.ReaderBody ← XString.nullReaderBody; sd: String.SubStringDescriptor; -- fileName:LONG STRING ← zone.NEW[StringBody[100]]; {ENABLE UNWIND => { IF item # XString.nullReaderBody THEN [] ← XToken.FreeTokenString[@item]; IF th # NIL THEN [] ← XToken.FreeReaderHandle[th];}; th ← XToken.ReaderToHandle[@rb]; item ← XToken.Item[th]; -- Get past "Unload.~" [] ← XToken.FreeTokenString[@item]; DO item ← XToken.Item[th]; IF item = XString.nullReaderBody THEN EXIT; TTY.PutString[tty,"\NUnloading "L]; sd ← BWSUtil.SubStringFromXString[@item]; BEGIN lh: MLoader.Handle ←LOOPHOLE[ XString.ReaderToNumber[r: @item, radix: 8, signed: FALSE ! XString.InvalidNumber => GOTO mustBeAFile], LONG POINTER]; TTY.PutSubString[tty, @sd]; TTY.PutString[tty,"..."L]; StartState.Unload[lh ! StartState.UnloadError => { outcome ← error; Blink[]; TTY.PutString[tty, message]; GOTO done}]; TTY.PutString[tty,"done."L]; EXITS done => NULL; mustBeAFile => { name: LONG STRING ← [50]; fh: MFile.Handle; found: CARDINAL; instancesUnloaded:CARDINAL; String.AppendSubString[name, @sd]; found ← UniqueFilePrefix[name]; IF found # 1 THEN { TTY.PutSubString[tty, @sd]; TTY.PutString[ tty,IF found = 0 THEN ": not found"L ELSE ": ambiguous"L]; GOTO done; }; TTY.PutString[tty, name]; TTY.PutString[tty,"..."L]; fh ← MFile.Acquire[name, anchor, [] !MFile.Error => { TTY.PutString[tty, "Could not aquire file.\n"L]; outcome ← error; GOTO done; }]; instancesUnloaded ← StartState.UnloadFromFile[fh ! StartState.UnloadError => { outcome ← error; Blink[]; TTY.PutString[tty, message]; instancesUnloaded ← instancesAlreadyUnloaded; MFile.Release[fh]; GOTO done;}]; MFile.Release[fh]; TTY.PutCR[tty]; TTY.PutNumber[tty, instancesUnloaded, []]; TTY.PutString[tty," instance"L]; IF instancesUnloaded # 1 THEN TTY.PutChar[tty, 's]; TTY.PutString[tty, " unloaded."L]; EXITS done => NULL; }; END; [] ← XToken.FreeTokenString[@item]; ENDLOOP; [] ← XToken.FreeTokenString[@item]; th ← XToken.FreeReaderHandle[th]; }; }; 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,' ]; }; rb:XString.ReaderBody ← XString.FromSTRING[cmdLine]; th:XToken.Handle ← NIL; item:XString.ReaderBody ← XString.nullReaderBody; {ENABLE UNWIND => { IF item # XString.nullReaderBody THEN [] ← XToken.FreeTokenString[@item]; IF th # NIL THEN [] ← XToken.FreeReaderHandle[th];}; th ← XToken.ReaderToHandle[@rb]; item ← XToken.Item[th]; -- Get past "ShowHandles.~" [] ← XToken.FreeTokenString[@item]; DO file:MFile.Handle; found: CARDINAL; name:LONG STRING ← zone.NEW[StringBody[100]]; sd: String.SubStringDescriptor; item ← XToken.Item[th]; IF item = XString.nullReaderBody THEN EXIT; sd ← BWSUtil.SubStringFromXString[@item]; String.AppendSubString[name, @sd]; found ← UniqueFilePrefix[name]; IF found # 1 THEN { TTY.PutSubString[tty, @sd]; TTY.PutString[tty, IF found = 0 THEN ": not found"L ELSE ": ambiguous"L]; GOTO error; }; TTY.PutChar[tty,'\N]; TTY.PutString[tty,name]; TTY.PutString[tty,": "L]; file ← MFile.Acquire[name, anchor, []! MFile.Error => { TTY.PutString[tty, "Unable to acquire file."L]; zone.FREE[@name]; outcome ← error; GOTO Done; }]; StartState.EnumerateHandles[file,PrintLoadHandle]; MFile.Release[file]; zone.FREE[@name]; [] ← XToken.FreeTokenString[@item]; ENDLOOP; EXITS Done => NULL; error => outcome ← error; }; th ← XToken.FreeReaderHandle[th]; }; ChangeStandardStreams: PUBLIC PROCEDURE [buffer: LONG STRING, data: Handle] = { rb: XString.ReaderBody ← XString.FromSTRING[buffer]; tH: XToken.Handle = XToken.ReaderToHandle[@rb]; token: XString.ReaderBody; XToken.Skip[tH, NIL, XToken.WhiteSpace, TRUE]; -- stdin, stdout, stderr FOR std: FormItems IN [stdin..stderr] DO token ← XToken.Filtered[tH, NIL, FilterFileName]; WITH hh:data SELECT FROM ctool => FormWindow.SetTextItemValue[hh.formSW, std.ORD, @token]; ENDCASE; [] ← XToken.FreeTokenString[@token]; ENDLOOP; [] ← XToken.FreeReaderHandle[tH]; }; FilterFileName: XToken.FilterProcType = { char: CHARACTER = LOOPHOLE[XChar.Code[c], CHARACTER]; inClass ← char # Ascii.SP AND char # ', AND char # Ascii.CR AND char # Ascii.TAB;}; ChangeSwitches: PUBLIC PROCEDURE [buffer: LONG STRING, data: Handle] = { rb: XString.ReaderBody ← XString.FromSTRING[buffer]; tH: XToken.Handle = XToken.ReaderToHandle[@rb]; switches: XString.ReaderBody; sense: BOOLEAN ← TRUE; XToken.Skip[tH, NIL, XToken.WhiteSpace, TRUE]; switches ← XToken.Filtered[tH, NIL, XToken.Switches]; DO SELECT XString.Lop[@switches] FROM '~.ORD, '-.ORD => sense ← ~sense; 'd.ORD => { data.debug ← sense; WITH hh:data SELECT FROM ctool => FormWindow.SetBooleanItemValue[hh.formSW, FormItems.debug.ORD, data.debug]; ENDCASE; sense ← TRUE;}; 's.ORD => { data.stopOnError ← sense; WITH hh:data SELECT FROM ctool => FormWindow.SetBooleanItemValue[hh.formSW, FormItems.stopOnError.ORD, data.stopOnError]; ENDCASE; sense ← TRUE;}; XChar.not => EXIT; ENDCASE; ENDLOOP; [] ← XToken.FreeTokenString[@switches]; [] ← XToken.FreeReaderHandle[tH]; }; PrintStatus: PUBLIC PROCEDURE [data: Handle, tty: TTY.Handle] = { ss: String.SubStringDescriptor; rb: XString.ReaderBody; FOR std: FormItems IN [stdin..stderr] DO TTY.PutString[tty, (SELECT std FROM stdin => "stdin: "L, stdout => "\Nstdout: "L, stderr => "\Nstderr: "L, ENDCASE => "bogus:"L)]; WITH h: data SELECT FROM ctool => { rb ← FormWindow.LookAtTextItemValue[h.formSW, std.ORD]; ss ← BWSUtil.SubStringFromXString[@rb]; IF ss.length = 0 THEN TTY.PutString[tty, "window"L] ELSE TTY.PutSubString[tty, @ss]; FormWindow.DoneLookingAtTextItemValue[h.formSW, std.ORD]; }; ENDCASE; ENDLOOP; TTY.PutString[tty,"\NDebug: "L]; 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: XToken.Handle ← NIL; tempName: XString.ReaderBody ← XString.nullReaderBody; rb: XString.ReaderBody ← XString.FromSTRING[buffer]; done: BOOLEAN ← FALSE; {ENABLE UNWIND => { IF tH # NIL THEN tH ← XToken.FreeReaderHandle[tH]; IF tempName # XString.nullReaderBody THEN [] ← XToken.FreeTokenString[@tempName];}; IF buffer.length = 0 THEN RETURN; tH ← XToken.ReaderToHandle[@rb]; WHILE ~done DO tempName ← XToken.MaybeQuoted[tH, NIL !XToken.UnterminatedQuote => RESUME]; IF ~XString.Empty[@tempName] THEN { ss: String.SubStringDescriptor ← BWSUtil.SubStringFromXString[@tempName]; s: LONG STRING ← String.MakeString[z: zone, maxlength: ss.length]; String.AppendSubString[s, @ss]; IF argV = NIL THEN { argV ← zone.NEW[StringSeq[initArgSeqLength]]; argV.length ← 0; argC ← 0; name ← String.CopyToNewString[s: s, z: zone, longer: 32]; }; argC ← argC + 1; IF argV.length < argV.maxlength THEN { argV[argV.length] ← LongStringToCString[s, 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[s, zone]; zone.FREE[@oldArgV];}; }; String.FreeString[z: zone, s: s]; } ELSE { done ← TRUE; }; [] ← XToken.FreeTokenString[@tempName]; ENDLOOP; tH ← XToken.FreeReaderHandle[tH]; };}; GetFirstWord: PUBLIC PROCEDURE [buffer: LONG STRING] RETURNS[firstWord: LONG STRING ← NIL] = { rb: XString.ReaderBody ← XString.FromSTRING[buffer]; th: XToken.Handle ← NIL; ts: XString.ReaderBody ← XString.nullReaderBody; {ENABLE UNWIND => { IF ts # XString.nullReaderBody THEN [] ← XToken.FreeTokenString[@ts]; IF th # NIL THEN th ← XToken.FreeReaderHandle[th];}; th ← XToken.ReaderToHandle[@rb]; ts ← XToken.Item[th]; IF ~XString.Empty[@ts] THEN { ss: String.SubStringDescriptor ← BWSUtil.SubStringFromXString[@ts]; firstWord ← String.MakeString[z: zone, maxlength: ss.length]; String.AppendSubString[firstWord, @ss]; }; [] ← XToken.FreeTokenString[@ts]; th ← XToken.FreeReaderHandle[th]; }; }; Blink:PUBLIC PROCEDURE = {UserTerminal.BlinkDisplay[];}; Print:PUBLIC PROCEDURE[msg: LONG STRING, data:Handle] = { rb: XString.ReaderBody ← XString.FromSTRING[msg, TRUE]; WITH hh:data SELECT FROM ctool => MessageWindow.Post[window: hh.msgSW, r: @rb, clear: FALSE]; ENDCASE; }; <<PrintErrorReader: PUBLIC PROCEDURE [msg: XString.Reader, data:Handle, tH: TTY.Handle] = { ss: String.SubStringDescriptor ← BWSUtil.SubStringFromXString[msg]; WITH hh:data SELECT FROM ctool => MessageWindow.Post[window: hh.msgSW, r: msg, clear: FALSE]; ENDCASE; };>> PrintTTYReader:PUBLIC PROCEDURE[msg:XString.Reader,tH: TTY.Handle] = { ss: String.SubStringDescriptor ← BWSUtil.SubStringFromXString[msg]; TTY.PutSubString[h: tH, ss: @ss]; }; ClearError:PUBLIC PROCEDURE[data:Handle] = { WITH hh:data SELECT FROM ctool => MessageWindow.Clear[hh.msgSW]; ENDCASE;}; 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] = {}; UpdateLogReadLength:PUBLIC PROCEDURE[data:Handle] = {}; MakeDataForSystemCall:PUBLIC PROCEDURE RETURNS[newData:Handle] = { newData ← zone.NEW[temp Data]; newData.cToolPart ← temp[]; }; -- BWS Icon stuff follows; cToolIconType: NSFile.Type = 30304; -- one past compiler open, canYouTakeSelection, takeSelection, takeSelectionCopy: Atom.ATOM; old, new: Containee.Implementation ← []; ch: XChar.Character; true: BOOLEAN ← TRUE; -- for GenericProc false: BOOLEAN ← FALSE; Picture: TYPE = ARRAY [0..256) OF CARDINAL; MakeGenericProcAtoms: PROC = { open ← Atom.MakeAtom["Open"L]; canYouTakeSelection ← Atom.MakeAtom["CanYouTakeSelection"L]; takeSelection ← Atom.MakeAtom["TakeSelection"L]; takeSelectionCopy ← Atom.MakeAtom["TakeSelectionCopy"L]; }; FindOrCreateIconFile: PROC = { name: XString.ReaderBody ← XString.FromSTRING["CTool Icon"L]; version: CARDINAL = 0; IF (Prototype.Find[type: cToolIconType, version: version] = NSFile.nullReference) THEN NSFile.Close[Prototype.Create [ name: @name, type: cToolIconType, version: version] ]; }; SetImplementation: PROC = { old ← new ← Containee.GetImplementation[cToolIconType]; new.genericProc ← GenericProc; new.pictureProc ← PictureProc; InitSmallPicture[]; new.smallPictureProc ← SmallPictureProc; [] ← Containee.SetImplementation[cToolIconType, new]; }; TakeSelection: PROC [data: Containee.DataHandle] RETURNS [worked: LONG POINTER TO BOOLEAN ← @false] = { value: Selection.Value ← Selection.Convert[target: file]; bw: Window.Handle = OpenCTool[data].bw; -- not literally, but it gets this one working h: ECTHandle = Context.Find[contextType, bw]; IF ~h.busy AND value # Selection.nullValue THEN { rb: XString.ReaderBody; cr: XString.ReaderBody ← XString.FromSTRING["\n"L]; ticket: Containee.Ticket; [rb, ticket] ← Containee.GetCachedName[ LOOPHOLE[value.value, Containee.DataHandle]]; Emulator.PutString[h.ttySW, @rb]; -- stuff in the name of the file to CTool Emulator.CR[h.ttySW]; Containee.ReturnTicket[ticket]; worked ← @true; }; Selection.Free[@value]; }; GenericProc: Containee.GenericProc = { val: LONG UNSPECIFIED; SELECT atom FROM open => val ← OpenCTool[data].sws; canYouTakeSelection => val ← IF Selection.CanYouConvert[target: file] THEN @true ELSE @false; takeSelection, takeSelectionCopy => val ← TakeSelection[data]; ENDCASE => RETURN old.genericProc[atom, data, changeProc, changeProcData]; IF changeProc # NIL THEN changeProc[changeProcData: changeProcData, data: data, noChanges: TRUE]; RETURN [val]; }; PictureProc: Containee.PictureProc = { <<[data: Containee.DataHandle, window: Window.Handle, box: Window.Box, old, new: Containee.PictureState]>> normalPic: Picture ← [ 17777B, 177777B, 177777B, 177770B, 37777B, 177777B, 177777B, 177774B, 70000B, 17B, 177000B, 16B, 160000B, 377B, 177740B, 7B, 140000B, 1777B, 177770B, 3B, 140000B, 7777B, 177776B, 3B, 140000B, 37777B, 177777B, 100003B, 140000B, 77777B, 177777B, 140003B, 140001B, 177777B, 177777B, 170003B, 140003B, 177777B, 177777B, 174003B, 140007B, 177777B, 177777B, 176003B, 140017B, 177777B, 177777B, 177003B, 140037B, 177777B, 177777B, 177403B, 140077B, 177777B, 177777B, 177603B, 140177B, 177760B, 777B, 177703B, 140177B, 177700B, 177B, 177703B, 140377B, 177400B, 37B, 177743B, 140777B, 176000B, 7B, 177763B, 140777B, 174000B, 3B, 177763B, 141777B, 170000B, 1B, 177773B, 141777B, 160000B, 0B, 3B, 143777B, 140000B, 0B, 3B, 143777B, 140000B, 0B, 3B, 147777B, 100000B, 0B, 3B, 147777B, 100000B, 0B, 3B, 147777B, 0B, 0B, 3B, 147777B, 0B, 0B, 3B, 157776B, 0B, 0B, 3B, 157776B, 0B, 0B, 3B, 157776B, 0B, 0B, 3B, 157776B, 0B, 0B, 3B, 157776B, 0B, 0B, 3B, 157776B, 0B, 0B, 3B, 157776B, 0B, 0B, 3B, 157776B, 0B, 0B, 3B, 157776B, 0B, 0B, 3B, 157776B, 0B, 0B, 3B, 147777B, 0B, 0B, 3B, 147777B, 0B, 0B, 3B, 147777B, 100000B, 0B, 3B, 147777B, 100000B, 0B, 3B, 143777B, 140000B, 0B, 3B, 143777B, 140000B, 0B, 3B, 141777B, 160000B, 0B, 3B, 141777B, 170000B, 1B, 177773B, 140777B, 174000B, 3B, 177763B, 140777B, 176000B, 7B, 177763B, 140377B, 177400B, 37B, 177743B, 140177B, 177700B, 177B, 177703B, 140177B, 177760B, 777B, 177703B, 140077B, 177777B, 177777B, 177603B, 140037B, 177777B, 177777B, 177403B, 140017B, 177777B, 177777B, 177003B, 140007B, 177777B, 177777B, 176003B, 140003B, 177777B, 177777B, 174003B, 140001B, 177777B, 177777B, 170003B, 140000B, 77777B, 177777B, 140003B, 140000B, 37777B, 177777B, 100003B, 140000B, 7777B, 177776B, 3B, 140000B, 1777B, 177770B, 3B, 160000B, 377B, 177740B, 7B, 70000B, 17B, 177000B, 16B, 37777B, 177777B, 177777B, 177774B, 17777B, 177777B, 177777B, 177770B ]; ghostPic: Picture ← [ 17777B, 177777B, 177777B, 177770B, 37777B, 177777B, 177777B, 177774B, 70000B, 25B, 52400B, 16B, 160000B, 252B, 125240B, 7B, 140000B, 2525B, 52520B, 3B, 140000B, 5252B, 125252B, 3B, 140000B, 12525B, 52525B, 3B, 140000B, 25252B, 125252B, 100003B, 140001B, 52525B, 52525B, 50003B, 140002B, 125252B, 125252B, 124003B, 140005B, 52525B, 52525B, 52003B, 140012B, 125252B, 125252B, 125003B, 140025B, 52525B, 52525B, 52403B, 140052B, 125252B, 125252B, 125203B, 140125B, 52520B, 525B, 52403B, 140052B, 125200B, 52B, 125203B, 140125B, 52400B, 25B, 52503B, 140252B, 124000B, 2B, 125243B, 140525B, 50000B, 1B, 52523B, 141252B, 120000B, 0B, 125253B, 140525B, 40000B, 0B, 3B, 141252B, 100000B, 0B, 3B, 142525B, 40000B, 0B, 3B, 145252B, 100000B, 0B, 3B, 142525B, 0B, 0B, 3B, 145252B, 0B, 0B, 3B, 152525B, 0B, 0B, 3B, 145252B, 0B, 0B, 3B, 152524B, 0B, 0B, 3B, 145252B, 0B, 0B, 3B, 152524B, 0B, 0B, 3B, 145252B, 0B, 0B, 3B, 152524B, 0B, 0B, 3B, 145252B, 0B, 0B, 3B, 152524B, 0B, 0B, 3B, 145252B, 0B, 0B, 3B, 152524B, 0B, 0B, 3B, 145252B, 0B, 0B, 3B, 152525B, 0B, 0B, 3B, 145252B, 100000B, 0B, 3B, 142525B, 0B, 0B, 3B, 141252B, 100000B, 0B, 3B, 142525B, 40000B, 0B, 3B, 141252B, 120000B, 0B, 3B, 140525B, 50000B, 1B, 52523B, 140252B, 124000B, 2B, 125243B, 140525B, 52000B, 5B, 52523B, 140252B, 125000B, 12B, 125243B, 140125B, 52500B, 125B, 52503B, 140052B, 125240B, 252B, 125203B, 140025B, 52525B, 52525B, 52403B, 140012B, 125252B, 125252B, 125003B, 140005B, 52525B, 52525B, 52003B, 140002B, 125252B, 125252B, 124003B, 140001B, 52525B, 52525B, 50003B, 140000B, 125252B, 125252B, 120003B, 140000B, 52525B, 52525B, 40003B, 140000B, 25252B, 125252B, 100003B, 140000B, 2525B, 52524B, 3B, 140000B, 1252B, 125250B, 3B, 160000B, 125B, 52500B, 7B, 70000B, 12B, 125000B, 16B, 37777B, 177777B, 177777B, 177774B, 17777B, 177777B, 177777B, 177770B ]; invertFlags: Display.BitBltFlags = [ direction: forward, disjoint: TRUE, disjointItems: FALSE, gray: FALSE, srcFunc: complement, dstFunc: null, reserved: 0]; replaceFlags: Display.BitBltFlags = [ direction: forward, disjoint: TRUE, disjointItems: FALSE, gray: FALSE, srcFunc: null, dstFunc: null, reserved: 0]; IF new = garbage THEN RETURN; box.dims ← [w: 64, h: 64]; box.place ← [box.place.x + 4, box.place.y + 4]; Display.Bitmap[ window: window, box: box, address: [word: IF new = ghost THEN @ghostPic ELSE @normalPic, bit: 0], bitmapBitWidth: 64, flags: IF new = highlighted THEN invertFlags ELSE replaceFlags]; }; InitSmallPicture: PROCEDURE = { bits: ARRAY [0..16) OF WORD ← [ 0B, 0B, 3760B, 7770B, 17074B, 16034B, 34000B, 34000B, 34000B, 34000B, 16034B, 17074B, 7770B, 3760B, 0B, 0B]; ch ← SimpleTextFont.AddClientDefinedCharacter [ width: 16, height: 16, bitsPerLine: 16, bits: @bits]; }; SmallPictureProc: Containee.SmallPictureProc = {RETURN[ch]}; SetOutputMode: PUBLIC PROCEDURE [h: Handle, iomode: INTEGER] = { h.iomode ← iomode; }; Init: PROC = { contextType ← Context.UniqueType[]; MakeGenericProcAtoms[]; FindOrCreateIconFile[]; SetImplementation[]; }; -- mainline code Init[]; }.