<> <> <> <> <> <> <> <> DIRECTORY Ascii USING [Lower], Booting USING [RegisterProcs, RollbackProc], Commander USING [CommandObject, Handle, Register], CommandTool USING [ArgumentVector, Failed, Parse, RegistrationDirectory, Run], CommandUtil USING [PairList, Failed, GetNth, GetRootName, ListLength, Parse, SetExtension], CompilerOps USING [LetterSwitches, StreamId, Transaction], ComputeServerClient USING [BestServerStats, RemoteSuccess, StartService], ComputeServerServer USING [Lookup, RegisteredProcHandle], FileNames USING [ConvertToSlashFormat, CurrentWorkingDirectory, GetShortName, ResolveRelativePath], FileParms USING [BindingProc], FileViewerOps USING [AttachErrorLog, ShowLog, RemoveErrorLog, WaitUntilSaved], FS USING [Delete, Error, ExpandName, nullOpenFile, OpenFile, StreamOpen], IO USING [Close, EndOfStream, GetTokenRope, IDProc, PutRope, RIS, STREAM], MessageWindow USING [Append, Blink, Clear], Process USING [GetPriority, InitializeCondition, MsecToTicks, Priority, SetPriority], PupDefs USING [GetHostName, GetPupAddress, PupAddress, PupNameTrouble], PupTypes USING [fillInSocketID], Rope USING [Cat, Concat, Fetch, Find, FromChar, Equal, IsEmpty, Length, ROPE, Substr], RopeFile USING [FromStream], SystemVersion USING [machineType], UserProfile USING [Boolean, Token]; CompilerClientCommandImpl: MONITOR IMPORTS Ascii, Booting, Commander, CommandTool, CommandUtil, ComputeServerClient, ComputeServerServer, FileNames, FileViewerOps, FS, IO, MessageWindow, Process, PupDefs, Rope, RopeFile, SystemVersion, UserProfile = { ROPE: TYPE = Rope.ROPE; Outcome: TYPE = {ok, warnings, errors, aborted}; standardDefaults: CompilerOps.LetterSwitches = DefaultSwitches[]; directoriesToUseExistingLog: LIST OF ROPE _ NIL; CompilerCondition: CONDITION; Compile: SAFE PROC [cmd: Commander.Handle] RETURNS [result: REF _ NIL, msg: Rope.ROPE _ NIL] ~ TRUSTED { <> parms: REF CompilerOps.Transaction = NEW[CompilerOps.Transaction]; sourceName, objectName, errorName, rootName, wDir: Rope.ROPE _ NIL; sourceStream, objectStream, errorStream: IO.STREAM _ NIL; <<>> useLog: BOOL; -- use compiler.log for error reporting makeNewLog: BOOL _ TRUE; destroyLogOnSuccess: BOOL; createIconic: BOOL; blinkIfIconic: BOOL; viewSeparateLogs: BOOL; userAbort: BOOL _ FALSE; -- set by ^DEL, STOP errors, warnings: BOOL _ FALSE; someNonSuccess: BOOL _ FALSE; remoteMsg: Rope.ROPE _ NIL; priority: Process.Priority = Process.GetPriority[]; switchDefaults: CompilerOps.LetterSwitches; verboseComputeServer: BOOL; moduleCount: CARDINAL _ 0; complex: BOOL = SELECT cmd.procData.clientData FROM $Vanilla => FALSE, $Complex => TRUE, $RemoteVanilla => FALSE, ENDCASE => ERROR; preferRemote: BOOL = cmd.procData.clientData = $RemoteVanilla; compilerSwitches: Rope.ROPE _ UserProfile.Token["Compiler.Switches"]; cmdStream: IO.STREAM; filesInit: BOOL _ FALSE; started: BOOL _ FALSE; Cleanup: PROC = { Process.SetPriority[priority]; IF useLog THEN FileViewerOps.ShowLog[ fileName: FS.ExpandName["Compiler.log", wDir].fullFName, destroyIt: ~someNonSuccess AND destroyLogOnSuccess, createIconic: createIconic, blinkIfIconic: blinkIfIconic ]; }; Finalize: PROC [userAbort: BOOL] = { sourceFileName: Rope.ROPE; IF sourceName # NIL THEN { <> sourceFileName _ FS.ExpandName[sourceName, wDir].fullFName; IF Rope.Length[remoteMsg] > 3 THEN appendRopeToLog[Rope.Substr[remoteMsg, 3]]; IF useLog OR ~(errors OR warnings) THEN FileViewerOps.RemoveErrorLog[sourceFileName] -- removes ErrorLog button ELSE { FileViewerOps.ShowLog[fileName: sourceFileName, createIconic: createIconic, blinkIfIconic: blinkIfIconic]; IF viewSeparateLogs THEN { IF ~errorName.IsEmpty[] THEN FileViewerOps.ShowLog[fileName: errorName, createIconic: createIconic, blinkIfIconic: blinkIfIconic]; } ELSE FileViewerOps.AttachErrorLog[sourceFileName]; }; }; IF userAbort OR errors THEN { IF objectName # NIL THEN DO FS.Delete[name: objectName, wDir: wDir ! FS.Error => IF error.group = lock OR error.code = $unknownFile THEN EXIT]; ENDLOOP; }; IF errorName = NIL THEN { errlogFileName: Rope.ROPE _ CommandUtil.SetExtension[rootName, "errlog"]; DO FS.Delete[name: errlogFileName, wDir: wDir ! FS.Error => IF error.group = lock OR error.code = $unknownFile THEN EXIT]; ENDLOOP; }; }; appendRopeToLog: SAFE PROC [logEntry: ROPE] = CHECKED { slashWDir: ROPE _ FileNames.ConvertToSlashFormat[wDir]; tries: INT _ 0; consCurrentDirectory: BOOL _ FALSE; innerAppendRopeToLog: ENTRY SAFE PROC = CHECKED { ENABLE UNWIND => NULL; DO openFailed: BOOL _ FALSE; needNewLog: BOOL; log: IO.STREAM; oldLog: IO.STREAM; consCurrentDirectory _ FALSE; IF makeNewLog THEN { needNewLog _ TRUE; consCurrentDirectory _ TRUE; } ELSE { FOR l: LIST OF ROPE _ directoriesToUseExistingLog, l.rest UNTIL l = NIL DO IF Rope.Equal[l.first, slashWDir] THEN { needNewLog _ FALSE; EXIT; }; REPEAT FINISHED => { consCurrentDirectory _ TRUE; needNewLog _ TRUE; }; ENDLOOP; }; IF ~needNewLog THEN oldLog _ FS.StreamOpen[fileName: "Compiler.log", accessOptions: $read ! FS.Error => { IF error.group = lock THEN { openFailed _ TRUE; tries _ tries + 1; IF tries > 10 THEN { MessageWindow.Clear[]; MessageWindow.Append["Compiler lock failure after 10 retries for Compiler.log read"]; MessageWindow.Blink[]; }; CONTINUE; } ELSE CONTINUE; }; ]; IF openFailed THEN { IF tries > 10 THEN EXIT; WAIT CompilerCondition; LOOP; }; log _ FS.StreamOpen[fileName: "Compiler.log", accessOptions: $create ! FS.Error => { IF error.group = lock THEN { openFailed _ TRUE; tries _ tries + 1; IF tries > 10 THEN { MessageWindow.Clear[]; MessageWindow.Append["Compiler lock failure after 10 retries for Compiler.log create"]; MessageWindow.Blink[]; }; CONTINUE; }; }; ]; IF openFailed THEN { IF tries > 10 THEN EXIT; WAIT CompilerCondition; LOOP; }; IF ~needNewLog AND log # NIL THEN { log.PutRope[RopeFile.FromStream[oldLog]]; }; IF openFailed THEN { IF tries > 10 THEN EXIT; WAIT CompilerCondition; LOOP; }; log.PutRope[logEntry]; log.Close[]; IF consCurrentDirectory THEN directoriesToUseExistingLog _ CONS[slashWDir, directoriesToUseExistingLog]; EXIT; ENDLOOP; makeNewLog _ FALSE; NOTIFY CompilerCondition; }; innerAppendRopeToLog[]; }; <> verboseComputeServer _ UserProfile.Boolean["Compiler.VerboseComputeServer", FALSE]; destroyLogOnSuccess _ UserProfile.Boolean["Compiler.DestroyLogOnSuccess", TRUE]; createIconic _ UserProfile.Boolean["Compiler.IconicLogs", FALSE]; blinkIfIconic _ UserProfile.Boolean["Compiler.BlinkLogs", TRUE]; viewSeparateLogs _ UserProfile.Boolean["Compiler.ViewSeparateLogs", FALSE]; IF UserProfile.Boolean["Compiler.SeparateLogs", FALSE] THEN compilerSwitches _ Rope.Cat[compilerSwitches, "~g"]; IF compilerSwitches.Length[] # 0 THEN cmd.commandLine _ Rope.Cat[(IF complex THEN "/" ELSE "-"), compilerSwitches, " ", cmd.commandLine]; cmdStream _ IO.RIS[cmd.commandLine]; sourceName _ rootName _ objectName _ errorName _ NIL; parms.objectName _ NIL; <> BEGIN ENABLE UNWIND => Cleanup[]; switchDefaults _ DefaultSwitches[]; wDir _ FileNames.CurrentWorkingDirectory[]; DO first: BOOL; args, results: CommandUtil.PairList; switches: Rope.ROPE _ NIL; localPause: BOOL; sense: BOOL; { -- start scope for EXITS cmdLine: Rope.ROPE _ NIL; originalSourceName: Rope.ROPE _ NIL; parms.switches _ switchDefaults; parms.switches['p] _ FALSE; IF complex THEN [sourceName, args, results, switches] _ CommandUtil.Parse[cmdStream ! CommandUtil.Failed => GOTO badSyntax] ELSE { <> token: Rope.ROPE; token _ cmdStream.GetTokenRope[IO.IDProc ! IO.EndOfStream => EXIT].token; IF token.Length[] > 0 THEN SELECT token.Fetch[0] FROM '- => switches _ token.Substr[1, token.Length[]-1]; ENDCASE => sourceName _ token}; IF sourceName = NIL AND switches = NIL THEN EXIT; IF CommandUtil.ListLength[results] > 1 THEN GOTO badSemantics; IF sourceName = NIL THEN GOTO globalSwitches; originalSourceName _ sourceName; rootName _ CommandUtil.GetRootName[IF CommandUtil.ListLength[results] = 1 THEN CommandUtil.GetNth[results, 0] ELSE FileNames.GetShortName[sourceName]]; errorName _ CommandUtil.SetExtension[rootName, "errlog"]; IF switches # NIL THEN { sense _ TRUE; FOR i: INT IN [0..switches.Length[]) DO c: CHAR = switches.Fetch[i]; SELECT c FROM '-, '~ => {sense _ ~sense; LOOP}; IN ['a..'z], IN ['A..'Z] => parms.switches[Ascii.Lower[c]] _ sense; IN ['1..'5] => parms.debugPass _ c-'0; ENDCASE; sense _ TRUE; ENDLOOP; switches _ NIL; }; sourceName _ CommandUtil.SetExtension[sourceName, "mesa"]; FileViewerOps.WaitUntilSaved[ IF Rope.Find[sourceName, ">"] < 0 AND Rope.Find[sourceName, "/"] < 0 THEN FS.ExpandName[sourceName, wDir].fullFName ELSE sourceName, cmd.out ! ABORTED => {userAbort _ TRUE; GOTO truncateList}]; parms.source.locator _ FileNames.GetShortName[sourceName]; IF CommandUtil.ListLength[results] # 0 THEN { objectName _ CommandUtil.GetNth[list: results, n: 0, delete: TRUE]; results _ NIL; } ELSE objectName _ rootName; objectName _ CommandUtil.SetExtension[objectName, "bcd"]; parms.objectName _ objectName; parms.objectFile _ FS.nullOpenFile; moduleCount _ moduleCount + 1; first _ TRUE; makeNewLog _ ~parms.switches['h]; parms.switches['h] _ FALSE; -- set append or force new log FOR c: CHAR IN ['a..'z] DO sd: BOOL = (IF c = 'p THEN FALSE ELSE standardDefaults[c]); IF parms.switches[c] # sd THEN { IF first THEN {first _ FALSE; cmdLine _ IF complex THEN "/" ELSE "-"}; IF sd THEN cmdLine _ cmdLine.Concat["-"]; cmdLine _ cmdLine.Concat[Rope.FromChar[c]]; }; ENDLOOP; cmdLine _ cmdLine.Concat[" "]; useLog _ parms.switches['g]; parms.switches['g] _ FALSE; localPause _ parms.switches['p]; parms.switches['p] _ FALSE; <
> sourceName _ FileNames.GetShortName[sourceName]; IF complex THEN { first: BOOL _ TRUE; cmdLine _ cmdLine.Cat[rootName, " _ ", originalSourceName, "["]; FOR p: CommandUtil.PairList _ args, p.rest UNTIL p=NIL DO IF ~first THEN cmdLine _ cmdLine.Concat[", "]; cmdLine _ cmdLine.Cat[p.first.key, ": ", p.first.val]; first _ FALSE; ENDLOOP; cmdLine _ cmdLine.Concat["]"]; } ELSE cmdLine _ cmdLine.Concat[sourceName]; { ENABLE UNWIND => Finalize[userAbort]; found: BOOL _ FALSE; success: ComputeServerClient.RemoteSuccess _ false; serverInstance: Rope.ROPE _ NIL; result _ $Blocked; remoteMsg _ NIL; WHILE result = $Blocked DO IF SystemVersion.machineType # dorado THEN { remSuccess: ComputeServerClient.RemoteSuccess; selfBest: BOOL; FigOfMerit: REAL; [success: remSuccess, selfIsBest: selfBest, FOM: FigOfMerit] _ ComputeServerClient.BestServerStats[]; IF remSuccess # true OR selfBest OR FigOfMerit > 0.5 THEN { result _ $TryLocal; EXIT; }; }; IF ~preferRemote THEN result _ $TryLocal; IF result # $TryLocal THEN { IF verboseComputeServer THEN cmd.out.PutRope[" -- Trying remote compile\n"]; IF complex THEN { [found: found, success: success, remoteMsg: remoteMsg, serverInstance: serverInstance] _ ComputeServerClient.StartService[service: "RemoteComplexCompiler", cmdLine: cmdLine, in: NIL, out: cmd.out, queueService: TRUE, timeToWait: 600]; } ELSE { [found: found, success: success, remoteMsg: remoteMsg, serverInstance: serverInstance] _ ComputeServerClient.StartService[service: "RemoteCompiler", cmdLine: cmdLine, in: NIL, out: cmd.out, queueService: TRUE, timeToWait: 600]; }; SELECT TRUE FROM ~found OR success = false OR success = cantImportController OR success = cantImportServer OR success = communicationFailure OR success = commandNotFound OR success = clientNotRunning => { IF verboseComputeServer THEN cmd.out.PutRope[" -- Remote compile did not work; do it locally\n"]; result _ $TryLocal; }; success = aborted => { userAbort _ TRUE; }; success = timeOut OR success = serverTooBusy => { IF verboseComputeServer THEN cmd.out.PutRope[Rope.Cat[" -- Remote compile ", (IF success = timeOut THEN "timed out queuing" ELSE "found a server that was too busy"), "; try again\n"]]; LOOP; }; found AND success = true => { printServerName: PROC = { badName: BOOL _ FALSE; serverName: ROPE _ serverInstance; address: PupDefs.PupAddress; address _ PupDefs.GetPupAddress[PupTypes.fillInSocketID, serverInstance ! PupDefs.PupNameTrouble => { badName _ TRUE; CONTINUE; }; ]; IF ~badName THEN serverName _ PupDefs.GetHostName[address]; IF ~serverName.IsEmpty[] THEN cmd.out.PutRope[Rope.Cat[" -- Compute Server used was ", serverName, "\n"]]; }; IF ~remoteMsg.IsEmpty[] THEN SELECT remoteMsg.Fetch[0] FROM 'F => { -- failure result _ $Failure; errors _ TRUE; warnings _ FALSE; IF verboseComputeServer THEN { printServerName[]; } ELSE { IF ~serverInstance.IsEmpty[] THEN cmd.out.PutRope[Rope.Cat[" Compute Server used was ", serverInstance, "\n"]]; }; }; 'W => { -- warnings result _ $Warning; errors _ FALSE; warnings _ TRUE; IF verboseComputeServer THEN printServerName[]; }; 'S => { -- success result _ $Success; errorName _ NIL; errors _ FALSE; warnings _ FALSE; IF verboseComputeServer THEN printServerName[]; }; 'A => { -- aborted result _ $Aborted; errors _ TRUE; warnings _ FALSE; userAbort _ TRUE; IF verboseComputeServer THEN printServerName[]; }; 'B => { -- blocked bestSuccess: ComputeServerClient.RemoteSuccess; selfIsBest: BOOL; bestFOM: REAL; result _ $Blocked; [success: bestSuccess, selfIsBest: selfIsBest, FOM: bestFOM] _ ComputeServerClient.BestServerStats[]; IF verboseComputeServer THEN { cmd.out.PutRope[" -- Server tried was already busy.\n"]; printServerName[]; }; }; ENDCASE; }; ENDCASE; }; ENDLOOP; IF result = $TryLocal THEN { commandName: ROPE = IF complex THEN "RemoteComplexCompiler" ELSE "RemoteCompiler"; lookupProcData: ComputeServerServer.RegisteredProcHandle; errMsg: ROPE _ NIL; runError: BOOL _ FALSE; lookupProcData _ ComputeServerServer.Lookup["RemoteCompiler", NIL]; IF verboseComputeServer THEN cmd.out.PutRope[" -- Trying to do the compile locally.\n"]; IF lookupProcData = NIL THEN { dir: ROPE; dir _ CommandTool.RegistrationDirectory[cmd]; IF verboseComputeServer THEN cmd.out.PutRope[Rope.Cat[" -- Trying to run CompilerServer.bcd from directory", dir, ".\n"]]; [errMsg: errMsg, error: runError] _ CommandTool.Run[bcdName: dir.Concat["CompilerServer.bcd"], runEvenIfAlreadyRun: FALSE, runEvenIfUnbound: FALSE]; lookupProcData _ ComputeServerServer.Lookup["RemoteCompiler", NIL]; }; IF lookupProcData = NIL THEN { -- try the standard place IF verboseComputeServer THEN cmd.out.PutRope[Rope.Cat[" -- Trying to run CompilerServer.bcd from directory ///Commands/\n"]]; [] _ CommandTool.Run[bcdName: "///Commands/CompilerServer.bcd", runEvenIfAlreadyRun: FALSE, runEvenIfUnbound: FALSE]; lookupProcData _ ComputeServerServer.Lookup["RemoteCompiler", NIL]; }; IF lookupProcData = NIL THEN { msg _ "Failed to start up compiler (CompilerServer.bcd) on the workstation.\n"; IF runError AND ~errMsg.IsEmpty[] THEN msg _ msg.Cat[" Error from Run is ", errMsg]; appendRopeToLog[Rope.Concat[" -- ", msg]]; result _ $Failure; } ELSE { myCmd: Commander.Handle; myCmd _ NEW[Commander.CommandObject _ [ in: cmd.in, out: cmd.out, err: cmd.err, commandLine: cmdLine, command: commandName, propertyList: cmd.propertyList, procData: cmd.procData ]]; result _ $Blocked; WHILE result = $Blocked DO [result: result, msg: remoteMsg] _ lookupProcData.commanderProcHandle.proc[myCmd]; IF ~remoteMsg.IsEmpty[] THEN SELECT remoteMsg.Fetch[0] FROM 'F => { -- failure result _ $Failure; errors _ TRUE; warnings _ FALSE; }; 'W => { -- warnings result _ $Warning; errors _ FALSE; warnings _ TRUE; }; 'S => { -- success result _ $Success; errorName _ NIL; errors _ FALSE; warnings _ FALSE; }; 'A => { -- aborted result _ $Aborted; errors _ TRUE; userAbort _ TRUE; warnings _ FALSE; }; 'B => { -- blocked result _ $Blocked; }; ENDCASE; ENDLOOP; }; }; }; -- end ENABLE UNWIND => Finalize[]; IF result # $Success THEN someNonSuccess _ TRUE; Finalize[userAbort]; EXITS globalSwitches => { objectName _ NIL; sense _ TRUE; FOR i: INT IN [0..switches.Length[]) DO c: CHAR = switches.Fetch[i]; SELECT c FROM '-, '~ => sense _ ~sense; IN ['a..'z] => {switchDefaults[c] _ sense; sense _ TRUE}; IN ['A..'Z] => { switchDefaults[VAL['a.ORD+(c.ORD-'A.ORD)]] _ sense; sense _ TRUE}; ENDCASE => EXIT; ENDLOOP; switches _ NIL; args _ NIL; }; badSemantics => { objectName _ NIL; errors _ TRUE; appendRopeToLog[" -- Illegal command"]; args _ NIL; }; }; <> sourceName _ rootName _ objectName _ errorName _ NIL; parms.objectName _ NIL; results _ NIL; IF userAbort THEN {appendRopeToLog["\n... command aborted\n"]; GOTO truncateList}; IF (errors OR warnings) AND localPause THEN GOTO truncateList; REPEAT badSyntax => {appendRopeToLog["\n-- Illegal syntax"]; errors _ TRUE}; truncateList => switchDefaults['p] _ TRUE; ENDLOOP; <> END; -- end catch phrase to release the resource and reset the process priority Cleanup[]; }; <<>> <> RollbackRecovery: Booting.RollbackProc = CHECKED { directoriesToUseExistingLog _ NIL; }; <<>> <<>> <> NewCompilerLog: ENTRY SAFE PROC [cmd: Commander.Handle] RETURNS [result: REF _ NIL, msg: Rope.ROPE _ NIL] = CHECKED { ENABLE UNWIND => NULL; argv: CommandTool.ArgumentVector; dir: ROPE; prev: LIST OF ROPE; argv _ CommandTool.Parse[cmd ! CommandTool.Failed => { msg _ errorMsg; CONTINUE; }]; dir _ FileNames.ConvertToSlashFormat[IF argv.argc < 2 THEN FileNames.CurrentWorkingDirectory[] ELSE FileNames.ResolveRelativePath[argv[1]]]; FOR l: LIST OF ROPE _ directoriesToUseExistingLog, l.rest UNTIL l = NIL DO IF Rope.Equal[l.first, dir] THEN { IF l = directoriesToUseExistingLog THEN directoriesToUseExistingLog _ l.rest ELSE prev.rest _ l.rest; EXIT; }; prev _ l; ENDLOOP; }; <> DefaultSwitches: PROC RETURNS [CompilerOps.LetterSwitches] = { RETURN [[ TRUE , -- A Address fault for NIL checks TRUE , -- B Bounds checking TRUE , -- C compile for Cedar (special FORK) FALSE, -- D call Debugger on compiler error (FALSE => just log error) TRUE , -- E fixed (big Eval stack) TRUE , -- F Floating point microcode TRUE , -- G TRUE => log goes to compiler.log, FALSE => use foo.errlog FALSE, -- H TRUE => append to compiler.log, FALSE => write new compiler.log FALSE, -- I unused FALSE, -- J cross-Jumping optimization FALSE, -- K unused TRUE , -- L allocate space for code Links TRUE , -- M reference counting Microcode TRUE , -- N Nil pointer checking FALSE, -- O unused FALSE, -- P Pause after compilation with errors FALSE, -- Q unused FALSE, -- R unused TRUE , -- S Sort (by static frequency) global vars & entry indexes FALSE, -- T unused FALSE, -- U uninitialized variable checking FALSE, -- V unused TRUE , -- W log Warning messages FALSE, -- X unused FALSE, -- Y complain about KFCB TRUE -- Z FALSE => use old-style method to access ROPE & ATOM literals ]]}; <> Booting.RegisterProcs[r: RollbackRecovery]; Process.InitializeCondition[@CompilerCondition, Process.MsecToTicks[250]]; Commander.Register["Compile", Compile, "Compile {-switches} xxx* - A simple syntax for compiling a list of cedar modules. (See 'ComplexCompile' for another syntax). Some useful switches are: -b => bounds checking (default: TRUE -- don't turn this off unless you know what you are doing), -d => call debugger on compiler error (else just log error), -g => log goes to compiler.log (-~g => log goes to foo.errlog), -j => cross-jump (default: FALSE -- don't turn this on unless you know what you are doing), -n => nil checking (default: TRUE -- don't turn this off unless you know what you are doing), -u => check for uninitialized variables, -w => log warning messages (default: TRUE) Switches can be negated with '~' (i.e. '-~bj~n'). The complete list of switches is in CompilerSequencer.DefaultSwitches. The defaults can be set in your user profile with Compiler.Switches.", $Vanilla]; Commander.Register["RCompile", Compile, "RCompile {-switches} xxx* - Same switches as Compile, but this prefers remote compilation", $RemoteVanilla]; Commander.Register["ComplexCompile", Compile, "ComplexCompile /j-b-n BcdTreeOps _ ProtoTreeOps[Literals: BcdSymbols, Symbols: BcdSymbols, Tree: BcdTree] . . . - Allows a parameterized compilation. Switches are marked with '/ and negated with '-. (See 'Compile' for more on switches.)", $Complex]; Commander.Register["NewCompilerLog", NewCompilerLog, "NewCompilerLog [directory] - Start a new Compiler.log for compiles done in the current directory or the specified directory. Useful only when the compiles are done with the g switch on "]; }. <> <> <> <> <> <> <> <<>>