DIRECTORY AMFiles USING [pathPrefixes], BcdDefs USING [NullVersion, VersionStamp, BcdBase, VersionID], Buttons USING [ButtonProc, Create], Commander USING [CommandObject, CommandProcHandle, CommandProcObject, CommandProc, Handle, Register], CommandExtras USING [IsUninterpreted], CommandTool, FileNames USING [ConvertToSlashFormat, CurrentWorkingDirectory, Directory, FileWithSearchRules, IsADirectory, ResolveRelativePath], FS USING [Close, Error, GetName, nullOpenFile, Open, OpenFile, StreamOpen, Read], IO USING [Close, EndOf, Error, Flush, GetChar, int, noInputStream, noWhereStream, PutChar, PutF, PutFR, PutRope, RIS, rope, RopeFromROS, ROS, SetIndex, STREAM], IOClasses USING [CreatePipe], List USING [AList, Assoc, Length, PutAssoc], Loader USING [Error, Instantiate, IRItem, Start], LoadState USING [local, Acquire, Release, ConfigInfo, EnumerateConfigs, nullConfig, ConfigID], PrincOps USING [ControlModule], Process USING [Detach, GetPriority, Priority, priorityNormal, SetPriority], ProcessExtras USING [CheckForAbort], ProcessProps USING [GetPropList, PushPropList], ReadEvalPrint USING [ClientProc, CreateStreamEvaluator, CreateViewerEvaluator, Handle, MainLoop, RObject], Rope USING [Cat, Compare, Concat, Fetch, Find, FromChar, Index, IsEmpty, Length, ROPE, Substr], RopeList USING [CopyTopList, DAppend, Memb], RuntimeError USING [UNCAUGHT], UserProfile USING [Line], ViewerIO USING [CreateViewerStreams], VM USING [Interval, Allocate, AddressForPageNumber, Free]; CommandToolImpl: CEDAR MONITOR IMPORTS AMFiles, Buttons, Commander, CommandExtras, CommandTool, FileNames, FS, IO, IOClasses, List, Loader, LoadState, Process, ProcessExtras, ProcessProps, ReadEvalPrint, Rope, RopeList, RuntimeError, UserProfile, ViewerIO, VM EXPORTS CommandTool = BEGIN defaultPrompt: Rope.ROPE = "%% "; Controls: TYPE = RECORD [ terminator: CHAR _ '\n, uninterpreted: BOOL _ FALSE, quitOnFailure: BOOL _ FALSE, background: BOOL _ FALSE, createViewer: BOOL _ FALSE, pipe: BOOL _ FALSE, inRedirected: BOOL _ FALSE, outRedirected: BOOL _ FALSE, starExpand: BOOL _ FALSE ]; MyCommandLookupHandle: Commander.CommandProcHandle _ NEW[Commander.CommandProcObject _ [CommandTool.LookupWithSearchRules, "LookupWithSearchRules", "DefaultCommandLookup"]]; MyFileLookupHandle: Commander.CommandProcHandle _ NEW[Commander.CommandProcObject _ [CommandTool.CommandFileWithSearchRules, "CommandFileWithSearchRules", "DefaultCommandFileLookup"]]; MyLoadLookupHandle: Commander.CommandProcHandle _ NEW[Commander.CommandProcObject _ [CommandTool.LoadAndRunWithSearchRules, "LoadAndRunWithSearchRules", "DefaultLoadFileLookup"]]; EachCommand: PUBLIC ReadEvalPrint.ClientProc = { cmd: Commander.Handle; propertyList: List.AList _ NIL; errorOut: IO.STREAM _ NIL; remainingCommands: Rope.ROPE; someExpansion: BOOL; expansion: BOOL _ FALSE; cth: Commander.Handle; pipePushStream: IO.STREAM _ NIL; pipePullStream: IO.STREAM _ NIL; lastControls: Controls _ []; controls: Controls _ []; cth _ NARROW[h.clientData]; IF cth = NIL THEN ERROR; -- there must be one! IF h.viewer = NIL THEN h.out.PutRope[command]; propertyList _ cth.propertyList; errorOut _ cth.err; remainingCommands _ command; DO IF remainingCommands.Length[] <= 1 THEN { IF pipePullStream # NIL THEN { pipePullStream.Close[! IO.Error => CONTINUE; ]; pipePullStream _ NIL; RETURN["[[Last command fed a pipe, output discarded]]"]; }; RETURN; }; cmd _ NEW[Commander.CommandObject _ []]; cmd.err _ errorOut; cmd.propertyList _ propertyList; controls _ []; [first: cmd.command, rest: remainingCommands, terminator: controls.terminator, someExpansion: someExpansion] _ CommandTool.Pass1[initial: remainingCommands, nameOnly: TRUE ! CommandTool.Failed => { IF pipePullStream # NIL THEN { pipePullStream.Close[! IO.Error => CONTINUE; ]; pipePullStream _ NIL; errorMsg _ Rope.Concat[errorMsg, "\n Previous command fed a pipe, output discarded."]; }; result _ errorMsg; GOTO Die; }]; expansion _ expansion OR someExpansion; cmd.procData _ NIL; IF cmd.command.Length[] > 0 THEN LookupCommand[cmd]; IF cmd.procData = NIL THEN controls.uninterpreted _ TRUE ELSE { controls.uninterpreted _ CommandExtras.IsUninterpreted[cmd.procData]; controls.starExpand _ FALSE; }; remainingCommands _ Rope.Cat[cmd.commandLine, Rope.FromChar[controls.terminator], remainingCommands]; IF controls.uninterpreted THEN { eol: INT _ Rope.Index[s1: remainingCommands, pos1: 0, s2: "\n", case: FALSE]; cmd.commandLine _ Rope.Substr[base: remainingCommands, start: 0, len: eol + 1]; remainingCommands _ Rope.Substr[base: remainingCommands, start: eol + 1]; controls.terminator _ '\n; } ELSE { [first: cmd.commandLine, rest: remainingCommands, terminator: controls.terminator, someExpansion: someExpansion] _ CommandTool.Pass1[initial: remainingCommands, nameOnly: FALSE ! CommandTool.Failed => { IF pipePullStream # NIL THEN { pipePullStream.Close[! IO.Error => CONTINUE; ]; pipePullStream _ NIL; errorMsg _ Rope.Concat[errorMsg, "\n Previous command fed a pipe, output discarded."]; }; result _ errorMsg; GOTO Die; }]; expansion _ expansion OR someExpansion; }; IF cmd.procData = NIL THEN { IF pipePullStream # NIL THEN { pipePullStream.Close[! IO.Error => CONTINUE; ]; pipePullStream _ NIL; RETURN["[[Last command fed a pipe, output discarded.]]"]; }; lastControls _ controls; ProcessExtras.CheckForAbort[]; LOOP; }; IF controls.uninterpreted THEN { IF pipePullStream # NIL THEN { pipePullStream.Close[! IO.Error => CONTINUE; ]; pipePullStream _ NIL; RETURN["[[Cannot pipe into an uninterpreted command]]"]; }; } ELSE { SELECT controls.terminator FROM '\n => NULL; '; => controls.quitOnFailure _ TRUE; '& => controls.background _ controls.createViewer _ TRUE; '| => controls.background _ controls.pipe _ TRUE; ENDCASE => { IF pipePullStream # NIL THEN { pipePullStream.Close[! IO.Error => CONTINUE; ]; pipePullStream _ NIL; }; RETURN[Rope.Cat["[[Unknown terminating character: ", Rope.FromChar[controls.terminator], "]]"]]; }; { ENABLE CommandTool.Failed => { result _ errorMsg; CONTINUE; }; CommandTool.AmpersandSubstitution[cmd]; CommandTool.DollarSubstitution[cmd]; IF controls.starExpand THEN CommandTool.StarExpansion[cmd]; [inRedirected: controls.inRedirected, outRedirected: controls.outRedirected] _ CommandTool.IORedirection[cmd]; }; IF result # NIL THEN { IF pipePullStream # NIL THEN { pipePullStream.Close[! IO.Error => CONTINUE; ]; pipePullStream _ NIL; }; result _ Rope.Cat["[[", result, "]]"]; GOTO Die; -- discards remaining commands }; IF controls.pipe AND controls.outRedirected THEN { IF pipePullStream # NIL THEN { pipePullStream.Close[! IO.Error => CONTINUE; ]; pipePullStream _ NIL; }; RETURN["[[Cannot redirect output and have a pipe]]"]; -- discards remaining commands on line }; IF lastControls.pipe AND controls.inRedirected THEN { IF pipePullStream # NIL THEN { pipePullStream.Close[! IO.Error => CONTINUE; ]; pipePullStream _ NIL; }; RETURN["[[Previous command was a pipe, cannot redirect input]]"]; -- discards remaining commands on line }; controls.createViewer _ controls.terminator = '& AND NOT ((controls.inRedirected AND controls.outRedirected) OR (lastControls.pipe AND controls.outRedirected)); IF controls.createViewer THEN { viewerOut: IO.STREAM; viewerIn: IO.STREAM; [in: viewerIn, out: viewerOut] _ ViewerIO.CreateViewerStreams[name: cmd.command]; cmd.err _ viewerOut; cmd.propertyList _ CommandTool.PutLocalProperty[key: $ErrorInputStream, val: viewerIn, aList: cmd.propertyList, origList: cmd.propertyList]; IF cmd.in = NIL THEN cmd.in _ viewerIn; IF cmd.out = NIL THEN cmd.out _ viewerOut; }; IF lastControls.pipe THEN { cmd.in _ pipePullStream; pipePullStream _ NIL; -- note that the pipe is finished! }; IF controls.pipe THEN { [push: pipePushStream, pull: pipePullStream] _ IOClasses.CreatePipe[]; cmd.out _ pipePushStream; }; }; -- end of interpreted section IF cmd.in = NIL THEN cmd.in _ CommandTool.Insulate[cth.in]; IF cmd.out = NIL THEN cmd.out _ CommandTool.Insulate[cth.out]; IF expansion THEN { h.out.PutRope["[["]; h.out.PutRope[cmd.command]; h.out.PutRope[Rope.Substr[cmd.commandLine, 0, cmd.commandLine.Length[] - 1]]; h.out.PutRope["]]\n"]; }; { savedOutStream: IO.STREAM _ cmd.out; ch: Commander.Handle _ cmd; cmd.out _ h.out; CommandTool.CallList[property: $Before, cmd: cmd, proc: NIL]; cmd.out _ savedOutStream; IF controls.background THEN ch _ NEW[Commander.CommandObject _ cmd^]; ExecuteCommand[cmd: cmd, background: controls.background]; ch.out _ h.out; CommandTool.CallList[property: $After, cmd: ch, proc: NIL]; }; WITH List.Assoc[key: $Prompt, aList: propertyList] SELECT FROM rope: Rope.ROPE => h.prompt _ rope; ENDCASE => h.prompt _ defaultPrompt; ProcessExtras.CheckForAbort[]; lastControls _ controls; IF controls.quitOnFailure AND List.Assoc[key: $Result, aList: cmd.propertyList] = $Failure THEN { IF pipePullStream # NIL THEN ERROR; -- can't happen! RETURN["[[Command failed]]"]; }; ENDLOOP; -- bottom of the loop which executes multiple commands in a line from ReadEvalPrint EXITS Die => NULL; }; LookupCommand: PUBLIC PROC [cmd: Commander.Handle] = { abmsg: Rope.ROPE _ NIL; printedAMessage: BOOL _ FALSE; previousAmbiguity: BOOL _ FALSE; foundProcData: Commander.CommandProcHandle _ NIL; foundCommand: Rope.ROPE _ NIL; foundCommandLine: Rope.ROPE _ NIL; origCommand: Rope.ROPE _ cmd.command; origCommandLine: Rope.ROPE _ cmd.commandLine; savedResult: REF ANY; TestLookupDone: PROC [result: REF, msg: Rope.ROPE] RETURNS [stop: BOOL] = { ambiguous: BOOL _ result = $Ambiguous; stop _ FALSE; IF msg.Length[] > 0 THEN { -- print any message printedAMessage _ TRUE; cmd.err.PutRope[msg]; IF msg.Fetch[msg.Length[] - 1] # '\n THEN cmd.err.PutChar['\n]; }; IF cmd.procData = NIL THEN { -- nothing found IF ambiguous THEN previousAmbiguity _ TRUE ELSE { IF previousAmbiguity THEN cmd.propertyList _ List.PutAssoc[key: $Result, val: $Ambiguous, aList: cmd.propertyList]; }; RETURN[stop: FALSE]; }; IF cmd.procData.proc = NIL THEN { cmd.procData _ NIL; RETURN[stop: FALSE]; }; IF NOT previousAmbiguity THEN { -- first time we've found anything! foundProcData _ cmd.procData; -- save it foundCommand _ cmd.command; foundCommandLine _ cmd.commandLine; IF ambiguous THEN { -- restore original command and CommandLine previousAmbiguity _ TRUE; cmd.procData _ NIL; cmd.command _ origCommand; cmd.commandLine _ origCommandLine; RETURN[stop: FALSE]; -- keep looking, but only for an exact match }; RETURN[stop: TRUE]; -- this is exact, so stop } ELSE { -- previousAmbiguity = TRUE IF (NOT ambiguous) OR (foundProcData # NIL) THEN { -- remember the data only if it is the first or this is an exact match. foundProcData _ cmd.procData; foundCommand _ cmd.command; foundCommandLine _ cmd.commandLine; }; IF ambiguous THEN { cmd.procData _ NIL; cmd.command _ origCommand; cmd.commandLine _ origCommandLine; RETURN[stop: FALSE]; }; RETURN[stop: TRUE]; }; }; IF List.Assoc[key: $Lookup, aList: cmd.propertyList] = NIL THEN { cmd.propertyList _ CommandTool.AddProcToList[aList: cmd.propertyList, listKey: $Lookup, proc: MyCommandLookupHandle, append: TRUE]; cmd.propertyList _ CommandTool.AddProcToList[aList: cmd.propertyList, listKey: $Lookup, proc: MyLoadLookupHandle, append: TRUE]; cmd.propertyList _ CommandTool.AddProcToList[aList: cmd.propertyList, listKey: $Lookup, proc: MyFileLookupHandle, append: TRUE]; }; savedResult _ List.Assoc[key: $Result, aList: cmd.propertyList]; CommandTool.CallList[property: $Lookup, cmd: cmd, proc: TestLookupDone ! ABORTED => {abmsg _ " . . . Aborted\n"; CONTINUE}; UNWIND => {abmsg _ " . . . Unwound\n"; CONTINUE}; ]; cmd.propertyList _ List.PutAssoc[key: $Result, val: savedResult, aList: cmd.propertyList]; IF abmsg # NIL THEN { cmd.err.PutRope[abmsg ! RuntimeError.UNCAUGHT => CONTINUE]; cmd.propertyList _ List.PutAssoc[key: $Result, val: $Failure, aList: cmd.propertyList]; RETURN; }; cmd.procData _ foundProcData; cmd.command _ foundCommand; cmd.commandLine _ foundCommandLine; IF cmd.procData = NIL THEN { IF NOT printedAMessage THEN { cmd.err.PutRope["[["]; cmd.err.PutRope[cmd.command]; cmd.err.PutRope[" . . . not found]]\n"]; }; RETURN; }; }; Base: PROC [list: List.AList, cmd: Commander.Handle, detached: BOOL] = { innerExecute: PROC = { {ENABLE { UNWIND => { IF cmd.in # NIL THEN cmd.in.Close[! IO.Error => CONTINUE]; IF cmd.out # NIL THEN { cmd.out.Flush[! IO.Error => CONTINUE]; cmd.out.Close[! IO.Error => CONTINUE]; }; IF cmd.err # NIL THEN { cmd.err.Flush[! IO.Error => CONTINUE]; }; }; }; result: REF ANY; msg: Rope.ROPE; originalPropertyList: List.AList _ cmd.propertyList; [result: result, msg: msg] _ cmd.procData.proc[cmd: cmd]; IF msg # NIL THEN { cmd.out.PutRope[msg]; IF msg.Fetch[msg.Length[] - 1] # '\n THEN cmd.out.PutChar['\n]; }; IF NOT detached THEN cmd.propertyList _ List.PutAssoc[key: $Result, val: result, aList: originalPropertyList]; }; -- END of enable }; -- END of innerExecute IF list # NIL THEN ProcessProps.PushPropList[list, innerExecute] ELSE innerExecute[]; IF cmd.in # NIL THEN cmd.in.Close[! IO.Error => CONTINUE]; IF cmd.out # NIL THEN { cmd.out.Flush[! IO.Error => CONTINUE]; cmd.out.Close[! IO.Error => CONTINUE]; }; IF cmd.err # NIL THEN { cmd.err.Flush[! IO.Error => CONTINUE]; }; }; ExecuteCommand: PUBLIC PROC [cmd: Commander.Handle, background: BOOL] = { IF background THEN { processProperties: List.AList _ CommandTool.CopyAList[ProcessProps.GetPropList[]]; processProperties _ List.PutAssoc[key: $CommanderHandle, val: cmd, aList: processProperties]; TRUSTED { Process.Detach[FORK Base[list: processProperties, cmd: cmd, detached: background]]; }; } ELSE { [] _ List.PutAssoc[key: $CommanderHandle, val: cmd, aList: ProcessProps.GetPropList[]]; Base[list: NIL, cmd: cmd, detached: background]; }; }; DoCommand: PUBLIC PROC [commandLine: Rope.ROPE, parent: Commander.Handle] RETURNS [result: REF ANY] = { rep: ReadEvalPrint.Handle; oldREP: ReadEvalPrint.Handle; cll: INT _ commandLine.Length[]; msg: Rope.ROPE; rep _ NEW[ReadEvalPrint.RObject _ [menuHitQueue: NIL]]; IF cll = 0 THEN RETURN[NIL]; IF commandLine.Fetch[cll - 1] # '\n THEN commandLine _ Rope.Concat[commandLine, "\n"]; IF parent = NIL THEN { parent _ NEW[Commander.CommandObject _ []]; parent.err _ IO.noWhereStream; parent.out _ IO.noWhereStream; parent.in _ IO.noInputStream; parent.propertyList _ List.PutAssoc[key: $ErrorInputStream, val: IO.noInputStream, aList: parent.propertyList]; parent.propertyList _ List.PutAssoc[key: $Prompt, val: defaultPrompt, aList: parent.propertyList]; }; rep.prompt _ NARROW[List.Assoc[key: $Prompt, aList: parent.propertyList]]; oldREP _ NARROW[List.Assoc[key: $ReadEvalPrintHandle, aList: parent.propertyList]]; IF rep.prompt.IsEmpty[] THEN rep.prompt _ defaultPrompt; rep.out _ parent.out; rep.clientData _ parent; rep.viewer _ IF oldREP # NIL THEN oldREP.viewer ELSE NIL; IF rep.viewer # NIL THEN { parent.out.PutF[rep.prompt]; parent.out.PutRope[commandLine]; }; msg _ EachCommand[h: rep, command: commandLine]; parent.out.PutRope[msg]; IF msg.Length[] > 0 AND msg.Fetch[msg.Length[] - 1] # '\n THEN parent.out.PutRope["\n"]; result _ List.Assoc[key: $Result, aList: parent.propertyList]; }; DoCommandRope: PUBLIC PROC [commandLine, in: Rope.ROPE _ NIL, parent: Commander.Handle] RETURNS [out: Rope.ROPE, result: REF ANY] = { outS: IO.STREAM _ IO.ROS[]; rep: ReadEvalPrint.Handle; oldREP: ReadEvalPrint.Handle _ NIL; cmd: Commander.Handle _ NEW[Commander.CommandObject _ []]; rep _ NEW[ReadEvalPrint.RObject _ [menuHitQueue: NIL]]; cmd.in _ IO.RIS[in]; cmd.out _ CommandTool.Insulate[outS]; IF parent # NIL THEN { cmd.err _ parent.err; cmd.propertyList _ parent.propertyList; oldREP _ NARROW[List.Assoc[key: $ReadEvalPrintHandle, aList: parent.propertyList]]; rep.prompt _ NARROW[List.Assoc[key: $Prompt, aList: parent.propertyList]]; rep.out _ parent.out; } ELSE { cmd.propertyList _ NIL; cmd.err _ IO.noWhereStream; rep.out _ IO.noWhereStream; cmd.propertyList _ List.PutAssoc[key: $ErrorInputStream, val: IO.noInputStream, aList: cmd.propertyList]; cmd.propertyList _ List.PutAssoc[key: $Prompt, val: defaultPrompt, aList: cmd.propertyList]; }; IF rep.prompt.IsEmpty[] THEN rep.prompt _ defaultPrompt; rep.clientData _ cmd; rep.viewer _ IF oldREP # NIL THEN oldREP.viewer ELSE NIL; parent.out.PutRope[EachCommand[h: rep, command: commandLine]]; result _ List.Assoc[key: $Result, aList: parent.propertyList]; out _ IO.RopeFromROS[outS ! IO.Error => CONTINUE]; outS.Close[]; }; Create: PROC [parentCommander: Commander.Handle, newViewer: BOOL, fork: BOOL, copyProps: BOOL, readProfile: BOOL _ FALSE] = { cmd: Commander.Handle _ NEW[Commander.CommandObject]; readEvalPrint: ReadEvalPrint.Handle; originalReadEvalPrint: ReadEvalPrint.Handle; oldPriority: Process.Priority _ Process.GetPriority[]; prompt: Rope.ROPE; errorInputStream: IO.STREAM; TRUSTED{Process.SetPriority[Process.priorityNormal]}; fork _ fork OR newViewer; copyProps _ copyProps OR fork; IF parentCommander = NIL THEN cmd.propertyList _ NIL ELSE { cmd.propertyList _ parentCommander.propertyList; IF copyProps THEN { cmd.propertyList _ CommandTool.CopyAList[cmd.propertyList]; cmd.propertyList _ CommandTool.CopyListOfRefAny[key: $Before, aList: cmd.propertyList]; cmd.propertyList _ CommandTool.CopyListOfRefAny[key: $After, aList: cmd.propertyList]; cmd.propertyList _ CommandTool.CopyListOfRefAny[key: $Lookup, aList: cmd.propertyList]; cmd.propertyList _ CommandTool.CopyListOfRefAny[key: $SearchRules, aList: cmd.propertyList]; cmd.propertyList _ List.PutAssoc[key: $ParentCommander, val: parentCommander, aList: cmd.propertyList]; }; }; prompt _ NARROW[List.Assoc[key: $Prompt, aList: cmd.propertyList]]; IF prompt.Length[] = 0 THEN { cmd.propertyList _ List.PutAssoc[key: $Prompt, val: defaultPrompt, aList: cmd.propertyList]; prompt _ defaultPrompt; }; IF NOT copyProps THEN originalReadEvalPrint _ NARROW[List.Assoc[key: $ReadEvalPrintHandle, aList: cmd.propertyList]]; IF newViewer OR parentCommander = NIL THEN { readEvalPrint _ ReadEvalPrint.CreateViewerEvaluator[clientProc: EachCommand, prompt: prompt, info: [name: Rope.Concat["CommandTool: WD = ", FileNames.CurrentWorkingDirectory[]], column: right, iconic: FALSE], edited: TRUE, deliverWhen: NIL, clientData: cmd, topLevel: TRUE]; cmd.err _ readEvalPrint.out; errorInputStream _ readEvalPrint.in; } ELSE { readEvalPrint _ ReadEvalPrint.CreateStreamEvaluator[clientProc: EachCommand, prompt: prompt, in: parentCommander.in, out: parentCommander.out, deliverWhen: NIL, clientData: cmd, topLevel: FALSE]; cmd.err _ parentCommander.err; errorInputStream _ NARROW[List.Assoc[key: $ErrorInputStream, aList: cmd.propertyList]]; }; cmd.in _ readEvalPrint.in; cmd.out _ readEvalPrint.out; cmd.propertyList _ List.PutAssoc[key: $ErrorInputStream, val: errorInputStream, aList: cmd.propertyList]; cmd.propertyList _ List.PutAssoc[key: $ReadEvalPrintHandle, val: readEvalPrint, aList: cmd.propertyList]; { maybeList: REF ANY _ List.Assoc[key: $SearchRules, aList: cmd.propertyList]; list: LIST OF REF ANY _ NIL; IF ISTYPE[maybeList, LIST OF REF ANY] THEN list _ NARROW[maybeList]; IF List.Length[list] = 0 THEN { rope: Rope.ROPE _ "///Commands/"; cmd.propertyList _ List.PutAssoc[key: $SearchRules, val: LIST[rope], aList: cmd.propertyList]; }; }; { maybeList: REF ANY _ List.Assoc[key: $Lookup, aList: cmd.propertyList]; list: LIST OF REF ANY _ NIL; IF ISTYPE[maybeList, LIST OF REF ANY] THEN list _ NARROW[maybeList]; IF List.Length[list] = 0 THEN { cmd.propertyList _ CommandTool.AddProcToList[aList: cmd.propertyList, listKey: $Lookup, proc: MyCommandLookupHandle, append: TRUE]; cmd.propertyList _ CommandTool.AddProcToList[aList: cmd.propertyList, listKey: $Lookup, proc: MyLoadLookupHandle, append: TRUE]; cmd.propertyList _ CommandTool.AddProcToList[aList: cmd.propertyList, listKey: $Lookup, proc: MyFileLookupHandle, append: TRUE]; }; }; IF parentCommander # NIL THEN { cmd.commandLine _ parentCommander.commandLine; cmd.command _ parentCommander.command; cmd.procData _ parentCommander.procData; } ELSE { cmd.commandLine _ NIL; cmd.command _ NIL; cmd.procData _ NIL; }; TRUSTED { processProperties: List.AList; processProperties _ ProcessProps.GetPropList[]; IF copyProps THEN processProperties _ CommandTool.CopyAList[processProperties]; processProperties _ List.PutAssoc[key: $CommanderHandle, val: cmd, aList: processProperties]; IF fork THEN TRUSTED { Process.Detach[FORK CommandToolBase[props: IF copyProps THEN processProperties ELSE NIL, rep: readEvalPrint, readProfile: readProfile, readInit: newViewer]]; } ELSE CommandToolBase[props: IF copyProps THEN processProperties ELSE NIL, rep: readEvalPrint, readProfile: readProfile, readInit: newViewer]; IF parentCommander # NIL AND NOT copyProps THEN { parentCommander.propertyList _ List.PutAssoc[key: $ReadEvalPrintHandle, val: originalReadEvalPrint, aList: parentCommander.propertyList]; [] _ List.PutAssoc[key: $CommanderHandle, val: parentCommander, aList: processProperties]; }; Process.SetPriority[oldPriority]; }; }; CommandToolBase: PROC [props: List.AList, rep: ReadEvalPrint.Handle, readProfile: BOOL, readInit: BOOL] = { inner: PROC = { cmd: Commander.Handle _ NARROW[rep.clientData]; IF readProfile THEN { initCommandLine: Rope.ROPE _ UserProfile.Line[key: "CommandTool.BootCommands"]; IF NOT initCommandLine.IsEmpty[] THEN [] _ DoCommand[commandLine: initCommandLine, parent: cmd]; }; IF readInit THEN { startupCommandLine: Rope.ROPE _ UserProfile.Line[key: "CommandTool.EachCommandToolCommands"]; IF NOT startupCommandLine.IsEmpty[] THEN [] _ DoCommand[commandLine: startupCommandLine, parent: cmd]; }; ReadEvalPrint.MainLoop[h: rep, forkAndDetach: FALSE, properties: NIL]; }; IF props # NIL THEN ProcessProps.PushPropList[props, inner] ELSE inner[]; }; CreateCommanderButtonProc: Buttons.ButtonProc = { Create[parentCommander: NIL, newViewer: TRUE, copyProps: TRUE, fork: TRUE]; }; CommandFile: PUBLIC Commander.CommandProc = { argv: CommandTool.ArgumentVector; commandStream: IO.STREAM; commandFileName: Rope.ROPE; failMsg: Rope.ROPE _ NIL; count: NAT; source: BOOL _ cmd.procData.clientData = $Source; prompt: Rope.ROPE; argv _ CommandTool.Parse[cmd: cmd ! CommandTool.Failed => { msg _ errorMsg; CONTINUE; }]; IF argv = NIL THEN RETURN[$Failure, msg]; IF argv.argc = 1 THEN { IF source THEN RETURN[$Failure, "Usage: Source commandFileName {argument list}"]; Create[parentCommander: cmd, newViewer: TRUE, copyProps: TRUE, fork: TRUE]; RETURN; }; commandFileName _ argv[1]; commandFileName _ FileNames.FileWithSearchRules[root: commandFileName, defaultExtension: ".cm", requireExtension: FALSE, searchRules: List.Assoc[key: $SearchRules, aList: cmd.propertyList]].fullPath; IF commandFileName.IsEmpty[] THEN RETURN[$Failure, Rope.Concat["Cannot open command file ", argv[1]]]; commandStream _ FS.StreamOpen[fileName: commandFileName ! FS.Error => IF error.group = $user THEN {msg _ error.explanation; CONTINUE; }]; IF msg.Length[] > 0 THEN RETURN[$Failure, msg]; IF commandStream = NIL THEN RETURN[$Failure, Rope.Concat["Cannot open command file ", argv[1]]]; count _ 0; WHILE NOT commandStream.EndOf[] DO c: CHAR _ commandStream.GetChar[]; IF c = 0C OR c >= 200C THEN { commandStream.Close[]; RETURN[$Failure, Rope.Concat[commandFileName, " appears to be a binary file"]]; }; count _ count + 1; IF count > 256 THEN { ProcessExtras.CheckForAbort[]; count _ 0; }; ENDLOOP; commandStream.SetIndex[0]; cmd.propertyList _ CommandTool.PutLocalProperty[key: $CommandFileArguments, val: argv, aList: cmd.propertyList, origList: cmd.propertyList]; prompt _ NARROW[List.Assoc[key: $Prompt, aList: cmd.propertyList]]; IF Rope.Find[s1: prompt, s2: ">"] # -1 THEN prompt _ Rope.Concat[">", prompt] ELSE prompt _ "> "; cmd.propertyList _ CommandTool.PutLocalProperty[key: $Prompt, val: prompt, aList: cmd.propertyList, origList: cmd.propertyList]; cmd.propertyList _ CommandTool.PutLocalProperty[key: $Result, val: NIL, aList: cmd.propertyList, origList: cmd.propertyList]; cmd.in _ commandStream; Create[parentCommander: cmd, newViewer: FALSE, copyProps: NOT source, fork: FALSE]; commandStream.Close[]; result _ List.Assoc[key: $Result, aList: cmd.propertyList]; }; RunCommand: Commander.CommandProc = TRUSTED { argv: CommandTool.ArgumentVector; bcdName: Rope.ROPE; startIfUnbound: BOOL _ FALSE; runAgain: BOOL _ FALSE; errMsg: Rope.ROPE; error: BOOL; argv _ CommandTool.Parse[cmd ! CommandTool.Failed => { msg _ errorMsg; CONTINUE; }]; IF argv = NIL THEN RETURN[$Failure, msg]; FOR i: NAT IN [1..argv.argc) DO IF argv[i].Fetch[0] = '- THEN { FOR j: INT IN [1..argv[i].Length[]) DO SELECT argv[i].Fetch[j] FROM 's => startIfUnbound _ TRUE; 'a => runAgain _ TRUE; ENDCASE; ENDLOOP; LOOP; }; bcdName _ argv[i]; [errMsg: errMsg, error: error] _ Run[bcdName: FileNames.ResolveRelativePath[argv[i]], runEvenIfAlreadyRun: runAgain, runEvenIfUnbound: startIfUnbound]; IF NOT errMsg.IsEmpty[] THEN { cmd.out.PutRope[errMsg]; cmd.out.PutChar['\n]; }; IF error THEN EXIT; ENDLOOP; IF error THEN result _ $Failure; }; Run: PUBLIC PROC [bcdName: Rope.ROPE, runEvenIfAlreadyRun: BOOL _ FALSE, runEvenIfUnbound: BOOL _ FALSE] RETURNS [errMsg: Rope.ROPE _ NIL, error: BOOL _ FALSE] = TRUSTED { name: Rope.ROPE; f: FS.OpenFile _ FS.nullOpenFile; length: INT; duplicateFound: BOOL _ FALSE; unboundImports: LIST OF Loader.IRItem _ NIL; cm: PrincOps.ControlModule; bcdName _ FileNames.ResolveRelativePath[bcdName]; length _ Rope.Length[bcdName]; IF length < 5 OR (Rope.Compare[Rope.Substr[bcdName, length - 4, 4], ".bcd", FALSE] # equal AND Rope.Find[bcdName, "!", MAX[0, length-6]] = -1) THEN bcdName _ Rope.Concat[bcdName, ".bcd"]; TRUSTED { ENABLE { Loader.Error => { SELECT type FROM invalidBcd => errMsg _ "InvalidBcd["; fileNotFound => errMsg _ "FileNotFound["; versionMismatch => errMsg _ "VersionMismatch["; loadStateFull => errMsg _ "LoadStateFull["; insufficientVM => errMsg _ "InsufficientVM["; ENDCASE => ERROR; errMsg _ Rope.Cat[errMsg, message, "]"]; error _ TRUE; CONTINUE; }; FS.Error => { errMsg _ error.explanation; CONTINUE; }; }; f _ FS.Open[bcdName]; IF errMsg # NIL THEN error _ TRUE ELSE { IF NOT runEvenIfAlreadyRun THEN { LoadState.local.Acquire[]; { ENABLE UNWIND => LoadState.local.Release[]; BcdVersion: SAFE PROC[file: FS.OpenFile] RETURNS [version: BcdDefs.VersionStamp _ BcdDefs.NullVersion] = TRUSTED{ bcdSpace: VM.Interval = VM.Allocate[count: 1]; bcd: BcdDefs.BcdBase _ LOOPHOLE[VM.AddressForPageNumber[bcdSpace.page]]; FS.Read[file: file, from: 0, nPages: 1, to: LOOPHOLE[bcd]]; IF bcd.versionIdent = BcdDefs.VersionID AND NOT bcd.definitions AND bcd.spare1 THEN version _ bcd.version; -- else error, which will be reported later VM.Free[bcdSpace]; }; lookAtConfig: SAFE PROC [config: LoadState.ConfigID] RETURNS [stop: BOOL _ FALSE] = TRUSTED{ IF bcdVersion = LoadState.local.ConfigInfo[config].bcd.version THEN RETURN[TRUE]; }; bcdVersion: BcdDefs.VersionStamp = BcdVersion[f]; IF bcdVersion # BcdDefs.NullVersion AND LoadState.local.EnumerateConfigs[newestFirst, lookAtConfig] # LoadState.nullConfig THEN duplicateFound _ TRUE; }; -- end ENABLE UNWIND => LoadState.local.Release[]; LoadState.local.Release[]; }; -- end IF NOT runEvenIfAlreadyRun THEN { IF errMsg=NIL AND NOT duplicateFound THEN [cm, unboundImports] _ Loader.Instantiate[f ! ABORTED => { errMsg _ "Execution Aborted in "; error _ TRUE; CONTINUE; }]; IF NOT error THEN { name: Rope.ROPE _ FileNames.ConvertToSlashFormat[FS.GetName[f].fullFName]; name _ FileNames.Directory[name]; AddNewUniqueDebugRule[name]; }; IF unboundImports # NIL THEN { errMsg _ "There are unbound imports: "; FOR l: LIST OF Loader.IRItem _ unboundImports, l.rest UNTIL l = NIL DO errMsg _ Rope.Concat[errMsg, IO.PutFR["[%g,%d] ", IO.rope[l.first.interfaceName], IO.int[l.first.index]]]; ProcessExtras.CheckForAbort[]; ENDLOOP; IF runEvenIfUnbound THEN Loader.Start[cm]; }; }; IF error = FALSE AND errMsg=NIL AND NOT duplicateFound THEN Loader.Start[cm]; IF errMsg=NIL THEN { fullFName, attachedTo: Rope.ROPE; [fullFName, attachedTo] _ FS.GetName[f]; name _ IF attachedTo # NIL THEN attachedTo ELSE fullFName; }; }; IF f # NIL THEN FS.Close[f]; IF errMsg = NIL THEN { errMsg _ IF duplicateFound THEN Rope.Cat["Previously loaded and run: ", name, "\n"] ELSE Rope.Cat["Loaded and ran: ", name, "\n"]; } ELSE errMsg _ Rope.Concat[errMsg, "\n"]; }; PrintDebugSearchRules: ENTRY Commander.CommandProc = { rules: LIST OF Rope.ROPE _ AMFiles.pathPrefixes; cmd.out.PutRope["( "]; IF rules = NIL THEN cmd.out.PutChar[' ]; WHILE rules # NIL DO cmd.out.PutRope[rules.first]; rules _ rules.rest; cmd.out.PutChar[' ]; ProcessExtras.CheckForAbort[]; ENDLOOP; cmd.out.PutRope[")\n"]; }; SetDebugSearchRules: ENTRY Commander.CommandProc = { args: LIST OF Rope.ROPE; newList: LIST OF Rope.ROPE _ NIL; CommandTool.StarExpansion[cmd]; args _ CommandTool.ParseToList[cmd ! CommandTool.Failed => { msg _ errorMsg; CONTINUE; }].list; IF args = NIL THEN RETURN[$Failure, msg]; newList _ args; WHILE newList # NIL DO newList.first _ FileNames.ConvertToSlashFormat[FileNames.ResolveRelativePath[newList.first]]; IF NOT FileNames.IsADirectory[newList.first] THEN RETURN[$Failure, Rope.Concat[newList.first, " is not a directory\n"]]; IF newList.first.Length[] < 3 THEN RETURN[$Failure, Rope.Concat["Directory name too short: ", newList.first]]; newList _ newList.rest; ProcessExtras.CheckForAbort[]; ENDLOOP; newList _ NIL; IF cmd.procData.clientData = $SetSearchRules THEN { WHILE args # NIL DO IF NOT RopeList.Memb[newList, args.first, FALSE] THEN newList _ RopeList.DAppend[newList, LIST[args.first]]; args _ args.rest; ProcessExtras.CheckForAbort[]; ENDLOOP; IF NOT RopeList.Memb[newList, "///", FALSE] THEN newList _ RopeList.DAppend[newList, LIST["///"]]; } ELSE { newList _ RopeList.CopyTopList[AMFiles.pathPrefixes]; WHILE args # NIL DO IF NOT RopeList.Memb[newList, args.first, FALSE] THEN newList _ RopeList.DAppend[newList, LIST[args.first]]; args _ args.rest; ProcessExtras.CheckForAbort[]; ENDLOOP; }; AMFiles.pathPrefixes _ newList; }; AddNewUniqueDebugRule: ENTRY PROC [rule: Rope.ROPE] = { newRules: LIST OF Rope.ROPE _ NIL; rule _ CommandTool.ConvertToSlashFormat[CommandTool.ResolveRelativePath[rule]]; IF RopeList.Memb[AMFiles.pathPrefixes, rule, FALSE] THEN RETURN; newRules _ RopeList.CopyTopList[AMFiles.pathPrefixes]; IF RopeList.Memb[newRules, rule, FALSE] THEN RETURN; newRules _ RopeList.DAppend[newRules, LIST[rule]]; AMFiles.pathPrefixes _ newRules; }; Init: PROC = { [] _ Buttons.Create[info: [name: "Cmd"], proc: CreateCommanderButtonProc, fork: TRUE, documentation: "Create a Commander viewer"]; Commander.Register[key: "///Commands/Source", proc: CommandFile, doc: "Source commandFileName {argument list}, execute command file in current context", clientData: $Source]; Commander.Register[key: "///Commands/CommandTool", proc: CommandFile, doc: "Create a new Command Tool"]; Commander.Register[key: "///Commands/Cmd", proc: CommandFile, doc: "Create a new Command Tool"]; Commander.Register[key: "///Commands/Run", proc: RunCommand, doc: "Run {-s} {-a} xxx.bcd* - Load and Start one or more .bcds. -s => ignore unbound, -a ignore previously loaded", clientData: NIL]; Commander.Register[key: "///Commands/AddDebugSearchRules", proc: SetDebugSearchRules, doc: "Add debugger command search rules: AddDebugSearchRules list-of-directories"]; Commander.Register[key: "///Commands/PrintDebugSearchRules", proc: PrintDebugSearchRules, doc: "Print debugger command search rules"]; Commander.Register[key: "///Commands/SetDebugSearchRules", proc: SetDebugSearchRules, doc: "Set debugger command search rules: SetDebugSearchRules list-of-directories", clientData: $SetSearchRules]; Create[parentCommander: NIL, newViewer: TRUE, copyProps: TRUE, fork: TRUE, readProfile: TRUE]; }; Init[]; END. March 27, 1983 3:27 pm, Stewart, Created from ChatImpl April 20, 1983 8:26 pm, Russ Atkinson, added Process Props stuff September 9, 1983 11:33 am, Stewart, Cedar 5 October 5, 1983 4:23 pm, Stewart, New command line processing October 19, 1983 5:17 pm, Stewart, Additional work December 2, 1983 8:03 pm, Stewart, Bug fixes and more bulletproofing December 12, 1983 12:43 pm, Stewart, FileNames, better default $Lookup December 15, 1983 11:09 am, Stewart, User profile and CommandToolStartup.cm December 15, 1983 5:44 pm, Stewart, Debug search rules ´CommandToolImpl.mesa Larry Stewart, December 16, 1983 4:45 pm Russ Atkinson, April 20, 1983 8:25 pm Mike Schroeder, November 18, 1983 1:04 pm Paul Rovner, November 30, 1983 5:07 pm EachCommand handles a line of commands from read eval print up to the point that the commander handle is completely constructed. It then turns over control to ExecuteCommand. If this is not a viewer ReadEvalPrint, then assume it doesn't echo Enter a loop in which we do each of the commands represented by the line we got from ReadEvalPrint. If what is left is too short, then we are done. Allocate the Handle for the next command and fill in the parts that are immediately clear First use Pass1 to find the command name Try looking up the command. controls.uninterpreted _ cmd.procData.uninterpreted; controls.starExpand _ cmd.procData.starExpand; Use Pass1 to get the command arguments Now if there was no command, try again If the command is uninterpreted, we are done what if the previous command was a pipe If we get here, then do command processing Process the terminating character Check for open pipes! Check for open pipes! Now set up the remaining streams according to lastControls and controls First check for the error cases. Check for open pipes! Check for open pipes! Create a viewer when terminator = '& except when both input and output are redirected or when last terminator was '| and output is redirected. When a viewer is created, it always gets the error streams. The in and out streams are set if they were not set before (by redirection). Create a viewer if appropriate. Create a viewer for the command and set up streams In the case that we alter the $ErrorInputStream, it is done as a local property, so that it will pop back later. Check that the error streams are set up: If a new viewer is created above, then they are set Else cmd.err is set earlier to be cth.err and $ErrorInputStream is unchanged from the cth property list Finish the in and out streams Any remaining NIL streams should come from the commander streams If @files are involved, then print the present command The next line is just bulletproofing. This program tries pretty hard to avoid having a NIL property list. Having a NIL property list isn't really bad (and this code doesn't break), but it may break improperly written client code that deals with the property list. IF cmd.propertyList = NIL THEN cmd.propertyList _ List.PutAssoc[key: $Prompt, val: h.prompt, aList: cmd.propertyList]; If the command has changed the prompt, let ReadEvalPrint know about it. Start again with the rest of the command line. The Commander.Handle passed to LookupCommand has nothing filled in except err, command, and propertyList Remember a previous ambiguous search! previousAmbiguity means that only exact searches should succeed from now on If we get here, then cmd.procData # NIL We found a deleted command, so forget about it, ignoring even that it was found through a unique pattern match, if it was. If we get here, then a valid command has been found If we find a unique match, then save it for later use, but keep looking in hopes an exact match will turn up. once previousAmbiguity is set, then only an exact match is good enough, and this one is not exact. Keep trying. We have found an exact match even though previous searches were ambiguous, this is the one we want! Now clean up the streams. This will do nothing if the streams are the ones created above by Insulate, but if the streams have been changed by IO redirection, then these calls will clean them up. cmd.started _ FALSE; Main code of Base Now clean up the streams. This will do nothing if the streams are the ones created above by Insulate, but if the streams have been changed by IO redirection, then these calls will clean them up. cmd.started _ FALSE; Mark the commander handle itself! cmd.started _ TRUE; Make a copy of the process properties list of the caller. Set the $CommanderHandle property. Set the $CommanderHandle property. Execute the given commandLine. Parent will be used to provide streams and the property list. cmd.command and cmd.commandLine will be overwritten. This is particularly useful for commands which wish to execute their own command lines as commands: DoCommand[cmd.commandLine, cmd]; DoCommand is a simple interface to EachCommand. rep: ReadEvalPrint.Handle _ NEW[ReadEvalPrint.RObject _ [NIL, NIL, NIL, NIL, FALSE, NIL, NIL, NIL, NIL, NIL, TRUE, NIL, NIL, NIL, NIL, NIL, NIL]]; Execute the given commandLine. (The command name must be the first token on the commandLine). The in, out, and err streams connected to the corresponding ropes. (Calls EachCommand) The property list and error streams come from parent. rep: ReadEvalPrint.Handle _ NEW[ReadEvalPrint.RObject _ [NIL, NIL, NIL, NIL, FALSE, NIL, NIL, NIL, NIL, NIL, TRUE, NIL, NIL, NIL, NIL, NIL, NIL]]; Create has become quite an involved process. It can be invoked either from the message window or from another commander. It creates a new instance of the commander. It also copies the process property list and creates a new commander handle property list. IF readProfile = TRUE then initial commands will be read from item InitialCommands in the user profile. If newViewer = TRUE, then initial commands will be read from CommandToolStartup.cm in the user's home directory. Create is Called in the following circumstances: To create a new top level command tool: newViewer = TRUE To execute a command file in a protected environment: fork = TRUE To execute a command file in the parents environment: copyProps = FALSE Set process priority to normal for the execution of the create code (and to start off the new commander). If we create a new viewer, then necessarily fork a process! If we start a new process, then necessarily copy properties! Make a copy of the parentCommander property list as the starting place for the property list for our new commander. copy certain top level properties which are known to be lists In this case, save the parentCommander as the $ParentCommander property of the new commander property list. Use the parent's prompt if there is one. save the old originalReadEvalPrint, if the command is source, it will be needed later Create a ReadEvalPrint object using the right prompt and working directory. Hope there is an $ErrorInputStream property! Record the REP streams in the Commander Handle. Record the input stream for use by error handlers. This is here until Commander is revised. If there are no SearchRules, put ///Commands/ on it. If there is no Lookup, add our own Lookup procs to the list Get the process properties list Make a copy of the process properties list of the caller. Now set the CommanderHandle property of the new process Restore smashed things Create a brand-new independent commander Makes no sense to source an empty command file Next line is the case of creating a brand-new independent commander Scan command file for reasonableness Get the old prompt. If it has a '> in it, append one, otherwise set the prompt to "> " Create a new commander, but let it use the present viewer. If the command was source, then don't copy properties or create a new process. source xxx& is dangerous! Load and start a BCD. If there is no .bcd on bcdName one will be appended. errMsg will always either return "Loaded and ran: XXX" or an error message, depending on the value of error. If there is no .bcd on bcdName one will be appended. If runEvenIfAlreadyRun is FALSE, then a BCD will only be run if it has not already been run by someone else. If runEvenIfAlreadyRun is TRUE, then the bcd will be run regardless. Unless runEvenIfUnbound is TRUE, the BCD will only be STARTed if there were no unbound imports. Can't set error here due to name conflict, thus the error _ TRUE after the FS.Open below. main program for Commander Ê$!˜šœ™Jšœ(™(J™%J™)Jšœ&™&—J˜šÏk ˜ Jšœœ˜Jšœœ1˜>Jšœœ˜#Jšœ œV˜eJšœœ˜&Jšœ ˜ Jšœ œt˜ƒJšœœI˜QJš œœiœœ œ˜ Jšœ œ˜Jšœœ"˜,Jšœœ%˜1Jšœ œO˜^Jšœ œ˜Jšœœ>˜KJšœœ˜$Jšœ œ˜/JšœœW˜jJšœœGœ ˜_Jšœ œ˜,Jšœ œœ˜Jšœ œ˜Jšœ œ˜%Jšœœ2˜:J˜—šœœ˜š˜JšœDœœ˜ÜJšœ ˜——Jšœ˜Jšœœ ˜!J˜šœ œœ˜Jšœ œ˜Jšœœœ˜Jšœœœ˜Jšœ œœ˜Jšœœœ˜Jšœœœ˜Jšœœœ˜Jšœœœ˜Jšœ œ˜J˜—J˜Jšœ5œu˜­Jšœ2œƒ˜¸Jšœ2œ~˜³J˜J™¯šœ œ˜0Jšœ˜Jšœœ˜Jšœ œœœ˜Jšœœ˜Jšœœ˜Jšœ œœ˜Jšœ˜Jšœœœœ˜ Jšœœœœ˜ J˜Jšœ˜Jšœ˜J˜Jšœœ˜Jš œœœœÏc˜/J˜J™BJšœ œœ˜.J˜Jšœ ˜ Jšœ˜J™J™cJšœ˜š˜Jšœ/™/šœ!œ˜)šœœœ˜Jšœœ œ˜/Jšœœ˜Jšœ2˜8J˜—Jšœ˜Jšœ˜—J™J™YJšœœ˜(Jšœ˜J˜ J˜J˜J™(šœ§œ˜Åšœœœ˜Jšœœ œ˜/Jšœœ˜JšœV˜VJ˜—Jšœ˜Jšœ˜ Jšœ˜—Jšœœ˜'J™Jšœœ˜Jšœœ˜4Jšœœœ˜9šœ˜J™4JšœE˜EJ˜J™.Jšœœ˜J˜—J˜Jšœe˜eJ˜šœœ˜ Jšœœ>œ˜MJšœO˜OJšœI˜IJšœ˜J˜—J™&šœ˜šœ«œ˜Êšœœœ˜Jšœœ œ˜/Jšœœ˜JšœV˜VJ˜—Jšœ˜Jšœ˜ Jšœ˜—Jšœœ˜'J˜—J˜J™&šœœœ˜šœœœ˜Jšœœ œ˜/Jšœœ˜Jšœ3˜9J˜—Jšœ˜Jšœ˜Jšœ˜J˜—J˜J™Tšœœ˜ šœœœ˜Jšœœ œ˜/Jšœœ˜Jšœ2˜8J˜—J˜—Jšœ˜˜J™*J˜J™!šœ˜Jšœœ˜ Jšœœ˜$Jšœ4œ˜9Jšœ,œ˜1šœ˜ J™šœœœ˜Jšœœ œ˜/Jšœœ˜J˜—JšœZ˜`J˜——J˜J˜šœ˜Jšœ˜Jšœ˜ J˜—Jšœ'˜'Jšœ$˜$Jšœœ ˜;Jšœn˜nJ˜šœ œœ˜J™šœœœ˜Jšœœ œ˜/Jšœœ˜J˜—Jšœ&˜&Jšœž˜)J˜—JšœG™GJ™J™ šœœœ˜2J™šœœœ˜Jšœœ œ˜/Jšœœ˜J˜—Jšœ1ž&˜]J˜—šœœœ˜5J™šœœœ˜Jšœœ œ˜/Jšœœ˜J˜—Jšœ=ž&˜iJ˜—J˜J™™Jš œ1œœœœœ˜ J˜J™šœœ˜Jšœ œœ˜Jšœ œœ˜J™2JšœQ˜QJšœ˜Jšœp™pJšœŒ˜ŒJšœ œœ˜'Jšœ œœ˜*J˜—™(J™3Jšœg™g—J˜J™šœœ˜J˜Jšœœž"˜9J˜—šœœ˜J˜FJ˜J˜—Jšœž˜!—J™J™@Jšœ œœ'˜;Jšœ œœ)˜>J˜J™6šœ œ˜Jšœ˜Jšœ˜JšœM˜MJšœ˜J˜—J™J™‰J™JšœœœX™vJ˜J˜Jšœœœ ˜$Jšœ˜J˜Jšœ8œ˜=J˜Jšœœœ!˜EJšœ:˜:Jšœ˜Jšœ6œ˜;J˜J˜J™Gšœ/œ˜>Jšœ œ˜#Jšœ˜$—J˜Jšœ˜J™J˜J™.šœœ>œ˜aJš œœœœž˜5Jšœ˜J˜—J˜JšœžS˜\—Jš˜Jšœœ˜ J˜—J˜Jšœh™hšÏn œœœ˜6Jšœ œœ˜Jšœœœ˜Jšœœœ˜ Jšœ-œ˜1Jšœœœ˜Jšœœœ˜"Jšœœ˜%Jšœœ˜-Jšœ œœ˜š Ÿœœ œ œœœ˜KJšœ œ˜&Jšœœ˜ šœœž˜0Jšœœ˜Jšœ˜Jšœ#œ˜?J˜—šœœœž˜.Jšœ œ˜*šœ˜J™%JšœœZ˜sJ˜—JšœK™KJšœœ˜J˜—Jšœ'™'šœœœ˜!Jšœœ˜J™zJšœœ˜J˜—Jšœ3™3šœœœž#˜DJšœž ˜)Jšœ˜Jšœ#˜#J™mšœ œž+˜?Jšœœ˜Jšœœ˜Jšœ˜Jšœ"˜"Jšœœž,˜BJ˜—Jšœœž˜.J˜—šœž˜#š œœ œœœžG˜{Jšœ˜Jšœ˜Jšœ#˜#J˜—šœ œ˜Jšœp™pJšœœ˜Jšœ˜Jšœ"˜"Jšœœ˜J˜—Jšœd™dJšœœ˜J˜—J˜—šœ5œœ˜AJšœ}œ˜ƒJšœzœ˜€Jšœzœ˜€J˜—Jšœ@˜@šœH˜HJšœ!œ˜2Jšœ!œ˜1Jšœ˜—JšœZ˜Zšœ œœ˜Jšœ%œœ˜;JšœW˜WJšœ˜J˜—Jšœ˜Jšœ˜Jšœ#˜#šœœœ˜šœœœ˜Jšœ˜Jšœ˜Jšœ(˜(J˜—Jšœ˜Jšœ˜—J˜—J˜šŸœœ5œ˜Hšœœ˜šœœ˜ šœ˜ J™ÃJš œ œœœ œ˜:šœ œœ˜Jšœœ œ˜&Jšœœ œ˜&J˜—šœ œœ˜Jšœœ œ˜&J˜—J™Jšœ˜—J˜—Jšœœœ˜Jšœ œ˜Jšœ4˜4Jšœ9˜9šœœœ˜Jšœ˜Jšœ#œ˜?J˜—Jšœœ œZ˜nJšœž˜Jšœž˜—J˜Jšœ™Jšœœœ.˜@Jšœ˜J™ÃJš œ œœœ œ˜:šœ œœ˜Jšœœ œ˜&Jšœœ œ˜&J˜—šœ œœ˜Jšœœ œ˜&J˜—J™J˜—J˜šŸœœœ%œ˜IJ™!Jšœ™šœ œ˜J™9JšœR˜RJšœ"™"Jšœ]˜]JšœœC˜`J˜—šœ˜Jšœ"™"JšœW˜WJšœ œ"˜0J˜—J˜—J˜JšœÉ™ÉšŸ œœœœœ œœ˜gJš%œœœœœœœœœœœœœœœœœœœ™’Jšœ˜Jšœ˜Jšœœ˜ Jšœ œ˜J˜Jšœœ.˜7Jšœ œœœ˜Jšœ"œ.˜VJ˜šœ œœ˜Jšœ œ˜+Jšœ œ˜Jšœ œ˜Jšœ œ˜JšœAœ,˜oJšœb˜bJ˜—J˜Jšœ œ7˜JJšœ œD˜SJ˜Jšœœ˜8J˜Jšœ˜Jšœ˜Jš œ œ œœœœ˜9šœœœ˜Jšœ˜Jšœ ˜ J˜—Jšœ0˜0Jšœ˜Jšœœ#œ˜XJšœ>˜>J˜—J˜Jšœí™íšŸ œœœœœœ œ œœ˜…Jš œœœœœ˜Jš%œœœœœœœœœœœœœœœœœœœ™’Jšœ˜Jšœœ˜#Jšœœ˜:J˜Jšœœ(œ˜7J˜Jšœ œœ˜Jšœ%˜%šœ œœ˜Jšœ˜Jšœ'˜'Jšœ œD˜SJšœ œ7˜JJšœ˜J˜—šœ˜Jšœœ˜Jšœ œ˜Jšœ œ˜Jšœ>œ)˜iJšœ\˜\J˜—J˜Jšœœ˜8J˜Jšœ˜Jš œ œ œœœœ˜9J™Jšœ>˜>Jšœ>˜>J˜Jšœœœ œ˜2Jšœ ˜ J˜—J˜J™‚Jšœg™gJšœp™p™0Jšœ8™8JšœA™AJšœG™G—šŸœœ0œœ œœœ˜}Jšœœ˜5Jšœ$˜$Jšœ,˜,Jšœ6˜6Jšœ œ˜Jšœœœ˜J™J™iJšœ.˜5J™J™;Jšœ œ ˜J™˜WJ˜—J˜J™/Jšœ˜Jšœ˜J˜J™\Jšœi˜iJ˜Jšœi˜iJ˜Jšœ4™4J˜Jšœ œœ:˜LJš œœœœœœ˜Jšœœ œœœœœœ ˜Dšœœ˜Jšœ œ˜!Jšœ9œ!˜^J˜—J˜J˜J™;J˜Jšœ œœ5˜GJš œœœœœœ˜Jšœœ œœœœœœ ˜Dšœœ˜Jšœ}œ˜ƒJšœzœ˜€Jšœzœ˜€J˜—J˜J˜šœœœ˜J˜.J˜&J˜(J˜—šœ˜Jšœœ˜Jšœœ˜Jšœœ˜J˜—J™šœ˜ J˜J˜J™J˜/J˜J™9Jšœ œ>˜OJ™J™7Jšœ]˜]J˜Jšœœœœœ œœœH˜¶Jš œœ œœœE˜J™J™š œœœœ œ˜1Jšœ‰˜‰JšœZ˜ZJ˜—Jšœ!˜!Jšœ˜—J˜—J˜šŸœœ=œ œ˜kšœœ˜Jšœœ˜/šœ œ˜Jšœœ5˜OJšœœœ;˜`J˜—šœ œ˜Jšœœ@˜]Jšœœœ>˜fJ˜—Jšœ.œœ˜FJšœ˜—Jšœ œœ(˜;Jšœ ˜ J˜J˜—šœ1˜1Jšœ(™(Jš œœ œ œœ˜KJ˜—J˜šœ œ˜-Jšœ!˜!Jšœœœ˜Jšœœ˜Jšœœœ˜Jšœœ˜ Jšœœ%˜1Jšœ œ˜JšœLœ˜YJšœœœœ˜)šœœ˜J™.Jšœœœ<˜QJ™J™CJšœ(œ œœ˜KJšœ˜Jšœ˜—J˜JšœrœP˜ÇJšœœœ>˜fšœœ(œ ˜FJšœœœ˜C—Jšœœœ˜/Jšœœœœ>˜`J™$J˜ šœœ˜"Jšœœ˜"šœœ œ˜Jšœ˜JšœI˜OJ˜—J˜šœ œ˜J˜J˜ J˜—Jšœ˜—Jšœ˜JšœŒ˜ŒJ™J™WJšœ œ4˜CJšœ%œ"˜MJšœ˜Jšœ€˜€J˜JšœCœ7˜}J˜Jšœ˜J™J™¥Jšœ(œ œœ˜SJšœ˜J˜Jšœ;˜;J˜—J˜šÏb œœ˜-Jšœ!˜!Jšœœ˜Jšœœœ˜Jšœ œœ˜Jšœ œ˜Jšœœ˜ JšœHœ˜UJšœœœœ˜)šœœœ˜šœœ˜šœœœ˜&šœ˜Jšœœ˜Jšœœ˜Jšœ˜—Jšœ˜—Jšœ˜J˜—Jšœ˜Jšœ—˜—šœœœ˜Jšœ˜J˜J˜—Jšœœœ˜Jšœ˜—Jšœœ˜ J˜—J˜Jšœ„™„J˜šŸœœœœœœœœ˜hJš œœœ œœœ˜BJšœ œ˜Jšœœ œ˜!Jšœœ˜ Jšœœœ˜Jšœœœœ˜,J˜Jšœ1˜1Jšœ˜Jš œ œ<œ œœœ(˜»šœ˜ šœ˜šœ˜šœ˜Jšœ%˜%Jšœ)˜)Jšœ/˜/Jšœ+˜+Jšœ-˜-Jšœœ˜—Jšœ(˜(Jšœœ˜ Jšœ˜ Jšœ˜—šœ ˜ Jšœ˜JšœKœ ™YJšœ˜ Jšœ˜—J˜—Jšœœ˜Jšœ œœ ˜!šœ˜šœœœ˜!Jšœ˜šœœœ˜-šŸ œœœœ ˜(Jšœ9œ˜HJšœ œ œ˜.Jšœ œ&˜HJšœ*œ˜;šœ&œœœ ˜NJšœž+˜H—Jšœ˜Jšœ˜—š  œœœœœœœ˜\Jšœ=œœœ˜QJšœ˜—Jšœ1˜1šœ!˜#šœ<˜?Jšœ˜—Jšœœ˜——Jšœž2˜6Jšœ˜Jšœžœž˜,—J˜š œœœœ˜)šœ.œ˜:Jšœ!˜!Jšœœ˜ Jšœ˜ Jšœ˜——šœœœ˜Jšœ œ"œ˜JJšœ!˜!Jšœ˜J˜—šœœœ˜Jšœ'˜'š œœœ(œ˜Cš˜Jšœœœœ˜jJ˜Jšœ˜——Jšœœ˜*Jšœ˜—J˜—Jšœ œœœœœœ˜Mšœœœ˜Jšœœ˜!Jšœœ ˜(Jš œœœœ œ ˜:J˜—J˜—Jšœœœœ ˜šœ œœ˜Jšœ œœ5œ*˜‚J˜—Jšœ$˜(J˜—J˜šœœ˜6Jšœœœœ˜0Jšœ˜Jšœ œœ˜(šœ œ˜Jšœ˜Jšœ˜Jšœ˜J˜Jšœ˜—Jšœ˜J˜—J˜šœœ˜4Jšœœœœ˜Jš œ œœœœ˜!Jšœ˜JšœNœ ˜`Jšœœœœ˜)Jšœ˜šœ œ˜Jšœ]˜]Jšœœ'œœ@˜xJšœœœE˜nJšœ˜J˜Jšœ˜—Jšœ œ˜šœ+œ˜3šœœ˜Jš œœ$œœ%œ˜lJšœ˜J˜Jšœ˜—Jš œœœœ%œ ˜bJ˜—šœ˜Jšœ5˜5šœœ˜Jš œœ$œœ%œ˜lJšœ˜J˜Jšœ˜—J˜—Jšœ˜J˜—J˜šŸœœœ œ˜7Jš œ œœœœ˜"JšœO˜OJšœ+œœœ˜@Jšœ6˜6Jšœœœœ˜4Jšœ&œ˜2Jšœ ˜ J˜—J˜šŸœœ˜JšœPœ.˜‚Jšœ®˜®Jšœh˜hJšœ`˜`Jšœ¾œ˜ÃJšœ©˜©Jšœ†˜†JšœÆ˜ÆJš œœ œ œœœ˜^J˜—J˜Jšœ™J˜Jšœ˜J˜Jšœ˜J˜J˜6J˜@J˜,J˜=J˜2J˜DJ˜FJ˜KJ˜6J˜—…—}þÀÓ