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, EndOps USING [Register], 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, noInputStream, noWhereStream, PutChar, PutF, PutRope, Reset, RIS, RopeFromROS, ROS, SetIndex, STREAM], IOClasses USING [CreatePipe], List USING [AList, Assoc, PutAssoc], Loader USING [Error, Instantiate, IRItem, Start], LoadState USING [local, Acquire, Release, ConfigInfo, EnumerateConfigs, nullConfig, ConfigID], PrincOps USING [ControlModule, NullControl], 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, Concat, Equal, Fetch, Find, FromChar, Index, IsEmpty, Length, Match, ROPE, Substr], RopeList USING [CopyTopList, DAppend, Memb], RuntimeError USING [UNCAUGHT], UserCredentials USING [Get], UserProfile USING [Line], ViewerIO USING [CreateViewerStreams], VM USING [Interval, Allocate, AddressForPageNumber, Free]; CommandToolImpl: CEDAR MONITOR IMPORTS AMFiles, Buttons, Commander, CommandExtras, CommandTool, EndOps, FileNames, FS, IO, IOClasses, List, Loader, LoadState, Process, ProcessExtras, ProcessProps, ReadEvalPrint, Rope, RopeList, RuntimeError, UserCredentials, UserProfile, ViewerIO, VM EXPORTS CommandTool = BEGIN ROPE: TYPE = Rope.ROPE; STREAM: TYPE = IO.STREAM; defaultPrompt: ROPE _ "%l%% %l"; 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: STREAM _ NIL; remainingCommands: ROPE; someExpansion: BOOL; expansion: BOOL _ FALSE; cth: Commander.Handle; pipePullStream: STREAM _ NIL; lastControls: Controls _ []; controls: Controls _ []; { -- solely for exits clauses 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 result _ NIL; IF remainingCommands.Length[] <= 1 THEN { IF pipePullStream # NIL THEN result _ "[[Last command fed a pipe, output discarded]]"; GOTO PipeClose; }; 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 errorMsg _ Rope.Concat[ errorMsg, "\n Previous command fed a pipe, output discarded."]; GOTO PipeClose; }]; expansion _ expansion OR someExpansion; cmd.procData _ NIL; IF cmd.command.IsEmpty[] THEN { controls.uninterpreted _ FALSE; controls.starExpand _ FALSE; } ELSE { 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 errorMsg _ Rope.Concat[errorMsg, "\n Previous command fed a pipe, output discarded."]; result _ errorMsg; GOTO PipeClose; }]; expansion _ expansion OR someExpansion; }; IF cmd.procData = NIL THEN { IF pipePullStream # NIL THEN { result _ "[[Last command fed a pipe, output discarded.]]"; GOTO PipeClose; }; lastControls _ controls; ProcessExtras.CheckForAbort[]; LOOP; }; IF controls.uninterpreted THEN { IF pipePullStream # NIL THEN { result _ "[[Cannot pipe into an uninterpreted command]]"; GOTO PipeClose; }; } ELSE { SELECT controls.terminator FROM '\n => NULL; '; => controls.quitOnFailure _ TRUE; '& => controls.background _ controls.createViewer _ TRUE; '| => controls.background _ controls.pipe _ TRUE; ENDCASE => { result _ Rope.Cat["[[Unknown terminating character: ", Rope.FromChar[controls.terminator], "]]"]; GOTO PipeClose; }; { 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 { result _ Rope.Cat["[[", result, "]]"]; GOTO PipeClose; -- discards remaining commands }; IF controls.pipe AND controls.outRedirected THEN { result _ "[[Cannot redirect output and have a pipe]]"; GOTO PipeClose; -- discards remaining commands on line }; IF lastControls.pipe AND controls.inRedirected THEN { result _ "[[Previous command was a pipe, cannot redirect input]]"; GOTO PipeClose; -- 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: STREAM; viewerIn: STREAM; [in: viewerIn, out: viewerOut] _ ViewerIO.CreateViewerStreams[ name: Rope.Cat[cmd.command, " ", cmd.commandLine]]; 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 { pipePushStream: STREAM; [push: pipePushStream, pull: pipePullStream] _ IOClasses.CreatePipe[]; cmd.out _ pipePushStream; pipePushStream _ NIL; }; }; -- 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.PutF["[[%g%g]]\n", [rope[cmd.command]], [rope[Rope.Substr[cmd.commandLine, 0, cmd.commandLine.Length[] - 1]]]]; { savedOutStream: 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 => 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 PipeClose => { IF pipePullStream # NIL THEN { pipePullStream.Close[! IO.Error => CONTINUE; ]; pipePullStream _ NIL; }; }; }; }; LookupCommand: PUBLIC PROC [cmd: Commander.Handle] = { abmsg: ROPE _ NIL; ambigMsg: ROPE _ NIL; printedAMessage: BOOL _ FALSE; previousAmbiguity: BOOL _ FALSE; foundProcData: Commander.CommandProcHandle _ NIL; foundCommand: ROPE _ NIL; foundCommandLine: ROPE _ NIL; origCommand: ROPE _ cmd.command; origCommandLine: ROPE _ cmd.commandLine; savedResult: REF ANY; err: STREAM = cmd.err; TestLookupDone: PROC [result: REF, msg: ROPE] RETURNS [stop: BOOL] = { ambiguous: BOOL _ result = $Ambiguous; stop _ FALSE; IF NOT msg.IsEmpty[] THEN { -- print any message except one about ambiguitites IF ambiguous THEN ambigMsg _ Rope.Concat[ambigMsg, msg] ELSE { printedAMessage _ TRUE; err.PutRope[msg]; IF msg.Fetch[msg.Length[] - 1] # '\n THEN 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 { err.PutRope[abmsg ! RuntimeError.UNCAUGHT => CONTINUE]; cmd.propertyList _ List.PutAssoc[key: $Result, val: $Failure, aList: cmd.propertyList]; cmd.commandLine _ NIL; cmd.command _ origCommand; cmd.procData _ NIL; IO.Flush[err]; IF cmd.out # NIL THEN IO.Flush[cmd.out]; IF cmd.in # NIL THEN IO.Reset[cmd.in]; RETURN; }; IF foundProcData = NIL THEN { IF ambigMsg.IsEmpty[] AND NOT printedAMessage THEN err.PutF["[[%g . . . not found]]\n", [rope[cmd.command]] ] ELSE err.PutRope[ambigMsg]; RETURN; }; cmd.procData _ foundProcData; cmd.command _ foundCommand; cmd.commandLine _ foundCommandLine; }; 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; 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 { ENABLE IO.Error => { IF ec = StreamClosed AND (stream = cmd.in OR stream = cmd.out) THEN GO TO closed; }; IF list # NIL THEN ProcessProps.PushPropList[list, innerExecute] ELSE innerExecute[]; EXITS closed => {}; }; 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, parent: Commander.Handle] RETURNS [result: REF ANY] = { rep: ReadEvalPrint.Handle; oldREP: ReadEvalPrint.Handle; cll: INT _ commandLine.Length[]; msg: 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]; parent.propertyList _ List.PutAssoc[ key: $SearchRules, val: ConsRope[Rope.Cat["///Users/", UserCredentials.Get[].name, "/"], ConsRope["///Commands/", ConsRope["///"]]], 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, [rope["b"]], [rope["B"]] ]; 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]; }; ConsRope: PROC [r: ROPE, list: LIST OF REF ANY _ NIL] RETURNS [LIST OF REF ANY] = { RETURN [CONS[r, list]]; }; DoCommandRope: PUBLIC PROC [commandLine, in: ROPE _ NIL, parent: Commander.Handle] RETURNS [out: ROPE, result: REF ANY] = { outS: 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; rep.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; errorInputStream: STREAM; props: List.AList _ NIL; TRUSTED{Process.SetPriority[Process.priorityNormal]}; fork _ fork OR newViewer; copyProps _ copyProps OR fork; IF parentCommander # NIL THEN { props _ parentCommander.propertyList; IF copyProps THEN { props _ CommandTool.CopyAList[props]; props _ CommandTool.CopyListOfRefAny[key: $Before, aList: props]; props _ CommandTool.CopyListOfRefAny[key: $After, aList: props]; props _ CommandTool.CopyListOfRefAny[key: $Lookup, aList: props]; props _ CommandTool.CopyListOfRefAny[key: $SearchRules, aList: props]; props _ List.PutAssoc[key: $ParentCommander, val: parentCommander, aList: props]; cmd.propertyList _ props; }; }; prompt _ NARROW[List.Assoc[key: $Prompt, aList: props]]; IF prompt.Length[] = 0 THEN { props _ List.PutAssoc[key: $Prompt, val: defaultPrompt, aList: props]; prompt _ defaultPrompt; }; IF NOT copyProps THEN originalReadEvalPrint _ NARROW[List.Assoc[key: $ReadEvalPrintHandle, aList: props]]; 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: props]]; }; cmd.in _ readEvalPrint.in; cmd.out _ readEvalPrint.out; props _ List.PutAssoc[key: $ErrorInputStream, val: errorInputStream, aList: props]; props _ List.PutAssoc[key: $ReadEvalPrintHandle, val: readEvalPrint, aList: props]; WITH List.Assoc[key: $SearchRules, aList: props] SELECT FROM list: LIST OF REF ANY => { }; ENDCASE => { rope: ROPE _ "///Commands/"; props _ List.PutAssoc[key: $SearchRules, val: LIST[rope], aList: props]; }; WITH List.Assoc[key: $Lookup, aList: props] SELECT FROM list: LIST OF REF ANY => { }; ENDCASE => { props _ CommandTool.AddProcToList[aList: props, listKey: $Lookup, proc: MyCommandLookupHandle, append: TRUE]; props _ CommandTool.AddProcToList[aList: props, listKey: $Lookup, proc: MyLoadLookupHandle, append: TRUE]; props _ CommandTool.AddProcToList[aList: props, 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; }; cmd.propertyList _ props; 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 _ UserProfile.Line[key: "CommandTool.BootCommands"]; IF NOT initCommandLine.IsEmpty[] THEN [] _ DoCommand[commandLine: initCommandLine, parent: cmd]; }; IF readInit THEN { startupCommandLine: 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: STREAM; commandFileName: ROPE; source: BOOL _ cmd.procData.clientData = $Source; prompt: ROPE; argv _ CommandTool.Parse[cmd: cmd ! CommandTool.Failed => { msg _ errorMsg; GO TO oops; }]; IF argv.argc = 1 THEN { IF source THEN {msg _ "Usage: Source commandFileName {argument list}"; GO TO oops}; 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 { msg _ Rope.Concat["Cannot open command file ", argv[1]]; GO TO oops; }; commandStream _ FS.StreamOpen[fileName: commandFileName ! FS.Error => IF error.group # bug THEN {msg _ error.explanation; GO TO oops; }]; WHILE NOT commandStream.EndOf[] DO c: CHAR _ commandStream.GetChar[]; IF c = 0C OR c >= 200C THEN { commandStream.Close[]; msg _ Rope.Concat[commandFileName, " appears to be a binary file"]; GO TO oops; }; ProcessExtras.CheckForAbort[]; 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 _ "%l%l> "; 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]; EXITS oops => result _ $Failure; }; RunCommand: Commander.CommandProc = TRUSTED { startIfUnbound: BOOL _ TRUE; runAgain: BOOL _ FALSE; loadOnly: BOOL _ FALSE; argv: CommandTool.ArgumentVector _ CommandTool.Parse[cmd ! CommandTool.Failed => { msg _ errorMsg; GO TO oops; }]; IF argv = NIL THEN RETURN[$Failure, msg]; FOR i: NAT IN [1..argv.argc) DO error: BOOL _ FALSE; errMsg: ROPE; bcdName: ROPE; cm: PrincOps.ControlModule _ PrincOps.NullControl; IF argv[i].Fetch[0] = '- THEN { FOR j: INT IN [1..argv[i].Length[]) DO SELECT argv[i].Fetch[j] FROM 'd => startIfUnbound _ FALSE; 'a => runAgain _ TRUE; 'l => loadOnly _ TRUE; ENDCASE; ENDLOOP; LOOP; }; bcdName _ argv[i]; [errMsg: errMsg, error: error] _ RunInternal[bcdName: FileNames.ResolveRelativePath[argv[i]], runEvenIfAlreadyRun: runAgain, runEvenIfUnbound: startIfUnbound, loadOnly: loadOnly]; IF NOT errMsg.IsEmpty[] THEN { cmd.out.PutRope[errMsg]; cmd.out.PutChar['\n]; }; IF error THEN {result _ $Failure; RETURN}; ENDLOOP; EXITS oops => result _ $Failure; }; Run: PUBLIC PROC [bcdName: ROPE, runEvenIfAlreadyRun, runEvenIfUnbound: BOOL _ FALSE] RETURNS [errMsg: ROPE _ NIL, error: BOOL _ FALSE] = { [errMsg: errMsg, error: error] _ RunInternal[bcdName, runEvenIfAlreadyRun, runEvenIfUnbound, FALSE]; }; 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] ! FS.Error => GO TO nope]; IF bcd.versionIdent = BcdDefs.VersionID AND NOT bcd.definitions AND bcd.spare1 THEN version _ bcd.version; -- else error, which will be reported later EXITS nope => {}; }; VM.Free[bcdSpace]; }; RunInternal: PROC [bcdName: ROPE, runEvenIfAlreadyRun, runEvenIfUnbound, loadOnly: BOOL _ FALSE] RETURNS [errMsg: ROPE _ NIL, error: BOOL _ FALSE] = { version: BcdDefs.VersionStamp _ BcdDefs.NullVersion; cm: PrincOps.ControlModule _ PrincOps.NullControl; f: FS.OpenFile _ FS.nullOpenFile; ros: STREAM = IO.ROS[]; put1: PROC [r1: ROPE _ NIL] = {IO.PutRope[ros, r1]}; put2: PROC [r1,r2: ROPE _ NIL] = {IO.PutRope[ros, r1]; IO.PutRope[ros, r2]}; shortName: ROPE _ bcdName _ FileNames.ResolveRelativePath[bcdName]; SELECT TRUE FROM Rope.Match["*.bcd", bcdName, FALSE] => {}; Rope.Match["*!*", bcdName, TRUE] => {}; ENDCASE => bcdName _ Rope.Concat[bcdName, ".bcd"]; {-- start scope for exits unboundImports: LIST OF Loader.IRItem _ NIL; name, fullFName, attachedTo: ROPE _ NIL; f _ FS.Open[bcdName ! FS.Error => {IO.PutRope[ros, error.explanation]; GO TO failed} ]; [fullFName, attachedTo] _ FS.GetName[f]; name _ IF attachedTo # NIL THEN attachedTo ELSE fullFName; IF NOT runEvenIfAlreadyRun THEN TRUSTED { duplicateFound: BOOL _ FALSE; LoadState.local.Acquire[]; { ENABLE UNWIND => LoadState.local.Release[]; 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; }; LoadState.local.Release[]; IF duplicateFound THEN { put2["Previously loaded and run: ", name]; GO TO done; }; }; TRUSTED { ENABLE { Loader.Error => { SELECT type FROM invalidBcd => put1["InvalidBcd["]; fileNotFound => put1["FileNotFound["]; versionMismatch => put1["VersionMismatch["]; loadStateFull => put1["LoadStateFull["]; insufficientVM => put1["InsufficientVM["]; ENDCASE => ERROR; put2[message, "]"]; GO TO failed; }; ABORTED => { put2["Loading aborted in ", name]; GO TO failed; }; }; [cm, unboundImports] _ Loader.Instantiate[f]; }; AddNewUniqueDebugRule[ FileNames.Directory[FileNames.ConvertToSlashFormat[fullFName]]]; IF unboundImports # NIL THEN { put1["\n-- Unbound imports { "]; FOR l: LIST OF Loader.IRItem _ unboundImports, l.rest UNTIL l = NIL DO IO.PutF[ros, "[%g,%d] ", [rope[l.first.interfaceName]], [integer[l.first.index]] ]; ENDLOOP; put1["}"]; IF NOT runEvenIfUnbound THEN GO TO failed; }; ProcessExtras.CheckForAbort[]; IF NOT loadOnly THEN { Loader.Start[cm]; put2["Loaded and ran: ", name]; cm _ PrincOps.NullControl; } ELSE { put2["Loaded: ", shortName]; put2[", from: ", name]; AddControlModule[cm, shortName]; }; EXITS failed => { error _ TRUE; }; done => { }; }; IF f # FS.nullOpenFile THEN FS.Close[f]; put1["\n"]; errMsg _ IO.RopeFromROS[ros]; }; AddControlModule: ENTRY PROC [cm: PrincOps.ControlModule, name: ROPE] = { IF cm # PrincOps.NullControl THEN unstartedList _ CONS[ [cm, name], unstartedList]; }; RemControlModule: ENTRY PROC [name: ROPE] RETURNS [cm: PrincOps.ControlModule _ PrincOps.NullControl] = { lag: UnstartedList _ NIL; FOR each: UnstartedList _ unstartedList, each.rest WHILE each # NIL DO IF Rope.Equal[each.first.name, name, FALSE] THEN { IF lag = NIL THEN unstartedList _ each.rest ELSE lag.rest _ each.rest; RETURN [each.first.cm]; }; lag _ each; ENDLOOP; }; unstartedList: UnstartedList _ NIL; UnstartedList: TYPE = LIST OF UnstartedEntry; UnstartedEntry: TYPE = RECORD [ cm: PrincOps.ControlModule, name: ROPE]; StartCommand: Commander.CommandProc = { argv: CommandTool.ArgumentVector _ CommandTool.Parse[cmd ! CommandTool.Failed => { msg _ errorMsg; GO TO oops; }]; FOR i: NAT IN [1..argv.argc) DO name: ROPE _ argv[1]; cm: PrincOps.ControlModule _ RemControlModule[name]; IF cm = PrincOps.NullControl THEN { msg _ "Error; no such unstarted module.\n"; GO TO oops}; TRUSTED {Loader.Start[cm]}; IO.PutF[cmd.out, "Started %g\n", [rope[name]] ]; ENDLOOP; EXITS oops => result _ $Failure; }; LoadCommand: Commander.CommandProc = { cmd.commandLine _ Rope.Concat["-l ", cmd.commandLine]; [result, msg] _ RunCommand[cmd]; }; PrintDebugSearchRules: ENTRY Commander.CommandProc = { rules: LIST OF ROPE _ AMFiles.pathPrefixes; out: STREAM = cmd.out; IO.PutRope[out, "( "]; IF rules = NIL THEN IO.PutChar[out, ' ]; WHILE rules # NIL DO IO.PutRope[out, rules.first]; rules _ rules.rest; IO.PutChar[out, ' ]; ProcessExtras.CheckForAbort[]; ENDLOOP; IO.PutRope[out, ")\n"]; }; SetDebugSearchRules: ENTRY Commander.CommandProc = { args: LIST OF ROPE; newList: LIST OF 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] = { newRules: LIST OF 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 -d -a {file}* - Load and Start one or more .bcds. -d => don't START if unbound, -a => ignore previously loaded, -l => load only", clientData: NIL]; Commander.Register[key: "///Commands/Load", proc: LoadCommand, doc: "Load -a {file}* - Load one or more .bcds. -a => ignore previously loaded", clientData: NIL]; Commander.Register[key: "///Commands/Start", proc: StartCommand, doc: "Start a previously loaded bcd", 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]; EndOps.Register[CreateFirst]; }; CreateFirst: PROC = { 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 January 14, 1984 7:44 pm, Stewart, Run defaults now start even if unbound, bugs fixed May 23, 1984 2:40:31 pm PDT, Spreitzer, Made initial create last thing done, via EndOps. ´CommandToolImpl.mesa Copyright c 1984 by Xerox Corporation. All rights reserved. Larry Stewart, January 17, 1984 11:05 am Russ Atkinson, March 6, 1985 3:28:15 pm PST Mike Schroeder, November 18, 1983 1:04 pm Paul Rovner, November 30, 1983 5:07 pm Spreitzer, May 23, 1984 2:41:20 pm PDT 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 Now set up the remaining streams according to lastControls and controls First check for the error cases. 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 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. LookupCommand fills in procData & commandLine, and alters command. 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! suppress any previous messages about ambiguitites! ambigMsg _ NIL; 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 Perform the requested command. If there is a property list, then use the one given, otherwise inherit the process properties from the current process. If we ever get an IO.Error due to the input or output being closed, just terminate the command. 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. 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. 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. There is a $SearchRules list, so do nothing When there is no SearchRules list, add our own SearchRules procs to the list There is a $Lookup list, so do nothing When 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. Check to see if this version has already been run. If so, we do not run this bcd again, but we have not really failed, either. At this point we really can start the bcd, no longer under protection of the ENABLE Loader.Error (the idea is to allow errors from user programs to be properly reported). Starts the last named module loaded by Load or Run that was loaded but NOT started. Can only start things listed here, since we otherwise do not know what the control list is. Loads a module but does NOT start it. main program for Commander Ê&Ϙcodešœ™Kšœ Ïmœ1™Kšœžœ˜#Kšœ žœV˜eKšœžœ˜&Kšœ ˜ Kšœžœ ˜Kšœ žœt˜ƒKšžœžœI˜QKš žœžœdžœžœ žœ˜•Kšœ žœ˜Kšœžœ˜$Kšœžœ%˜1Kšœ žœO˜^Kšœ žœ˜,Kšœžœ>˜KKšœžœ˜$Kšœ žœ˜/KšœžœW˜jKšœžœLžœ ˜dKšœ žœ˜,Kšœ žœžœ˜Kšœžœ˜Kšœ žœ˜Kšœ žœ˜%Kšžœžœ2˜:K˜—šœžœž˜KšžœMžœžœ¡ž˜ýKšžœ ˜Kšœž˜K˜Kšžœžœžœ˜Kšžœžœžœžœ˜K˜Kšœžœ ˜ —˜šœ žœžœ˜Kšœ žœ˜Kšœžœžœ˜Kšœžœžœ˜Kšœ žœžœ˜Kšœžœžœ˜Kšœžœžœ˜Kšœžœžœ˜Kšœžœžœ˜Kšœ žœž˜K˜—K˜Kšœ5žœu˜­Kšœ2žœƒ˜¸Kšœ2žœ~˜³K˜—šœ žœ˜0K™¯Kšœ˜Kšœžœ˜Kšœ žœžœ˜Kšœžœ˜Kšœžœ˜Kšœ žœžœ˜Kšœ˜Kšœžœžœ˜K˜Kšœ˜Kšœ˜šœÏc˜K˜Kšœžœ˜Kš žœžœžœžœŸ˜/K˜K™BKšžœ žœžœ˜.K˜Kšœ ˜ Kšœ˜K™K™cKšœ˜šž˜Kšœ žœ˜ Kšœ/™/šžœ!žœ˜)Kšžœžœžœ:˜VKšžœ ˜Kšœ˜—K™K™YKšœžœ˜(Kšœ˜K˜ K˜K˜K™(šœl˜lšœ:ž˜>šœ˜šžœžœž˜šœ˜Kšœ?˜?——Kšžœ ˜Kšœ˜———Kšœžœ˜'K™Kšœžœ˜šžœ˜šžœ˜Kšœžœ˜Kšœžœ˜K˜—šžœ˜Kšœ˜šžœž˜Kšžœž˜"šžœ˜K™4KšœE˜EK˜K™.Kšœžœ˜K˜——K˜——K˜Kšœe˜eK˜šžœ˜šžœ˜Kšœžœ>žœ˜MKšœO˜OKšœI˜IKšœ˜K˜—šžœ˜K™&šœ«žœ˜ÊKšžœžœžœW˜sKšœ˜Kšžœ ˜Kšœ˜—Kšœžœ˜'K˜——K˜K™&šžœžœžœ˜šžœžœžœ˜Kšœ:˜:Kšžœ ˜K˜—Kšœ˜Kšœ˜Kšžœ˜K˜—K˜K™Tšžœ˜šžœ˜šžœžœžœ˜Kšœ9˜9Kšžœ ˜K˜—K˜—šžœ˜K˜K™*K˜K™!šžœž˜Kšœžœ˜ Kšœžœ˜$Kšœ4žœ˜9Kšœ,žœ˜1šžœ˜ Kšœa˜aKšžœ ˜K˜——K˜K˜šžœ˜Kšœ˜Kšžœ˜ K˜—Kšœ'˜'Kšœ$˜$Kšžœžœ ˜;Kšœn˜nK˜šžœ žœžœ˜Kšœ&˜&Kšžœ Ÿ˜/K˜—KšœG™GK™K™ šžœžœžœ˜2Kšœ6˜6Kšžœ Ÿ&˜7K˜—šžœžœžœ˜5KšœB˜BKšžœ Ÿ&˜7K˜—K˜K™™Kš œ1žœžœžœžœžœ˜ K˜K™šžœžœ˜Kšœ žœ˜Kšœ žœ˜K™2šœ>˜>Kšœ3˜3—Kšœ˜Kšœp™pKšœŒ˜ŒKšžœ žœžœ˜'Kšžœ žœžœ˜*K˜—™(K™3Kšœg™g—K˜K™šžœžœ˜K˜KšœžœŸ"˜9K˜—šžœžœ˜Kšœžœ˜K˜FK˜Kšœžœ˜K˜—KšœŸ˜!——K™Kšœžœ/™@Kšžœ žœžœ'˜;Kšžœ žœžœ)˜>K˜K™6šžœ ž˜Kšœu˜u—K™˜Kšœžœ ˜!Kšœ˜K˜Kšœ8žœ˜=K˜Kšžœžœžœ!˜EKšœ:˜:Kšœ˜Kšœ6žœ˜;K˜—K˜K™Gšžœ/žœž˜>Kšœžœ˜Kšžœ˜$—K˜Kšœ˜K™K˜K™.šžœžœ>žœ˜aKš žœžœžœžœŸ˜5Kšžœ˜K˜—K˜KšžœŸS˜\—šž˜˜šžœžœžœ˜Kšœžœ žœ˜/Kšœžœ˜K˜—K˜——K˜—K˜—K˜šÏn œžœžœ˜6Kšœ­™­Kšœžœžœ˜Kšœ žœžœ˜Kšœžœžœ˜Kšœžœžœ˜ Kšœ-žœ˜1Kšœžœžœ˜Kšœžœžœ˜Kšœ žœ˜ Kšœžœ˜(Kšœ žœžœ˜Kšœžœ ˜š  œžœ žœžœžœžœ˜FKšœ žœ˜&Kšœžœ˜ šžœžœžœŸ2˜OKšžœ žœ&˜7šžœ˜Kšœžœ˜Kšœ˜Kšžœ#žœ˜;K˜—K˜—šžœžœžœŸ˜.šžœ ˜ Kšžœž˜šžœ˜K™%KšžœžœZ˜sK˜——KšœK™KKšžœžœ˜K˜—Kšœ'™'šžœžœžœ˜!Kšœžœ˜K™zKšžœžœ˜K˜—Kšœ3™3šžœžœžœŸ#˜DKšœŸ ˜)Kšœ˜Kšœ#˜#K™mšžœ žœŸ+˜?Kšœžœ˜Kšœžœ˜Kšœ˜Kšœ"˜"KšžœžœŸ,˜BK˜—KšžœžœŸ˜.K˜—šžœŸ˜#š žœžœ žœžœžœŸG˜{Kšœ˜Kšœ˜Kšœ#˜#K˜—šžœ žœ˜Kšœp™pKšœžœ˜Kšœ˜Kšœ"˜"Kšžœžœ˜K˜—Kšœd™dK™2Kšœ™Kšžœžœ˜K˜—K˜—šžœ5žœžœ˜AKšœ}žœ˜ƒKšœzžœ˜€Kšœzžœ˜€K˜—Kšœ@˜@šœH˜HKšžœ!žœ˜2Kšžœ!žœ˜1Kšœ˜—KšœZ˜Zšžœ žœžœ˜Kšœ!žœžœ˜7KšœW˜WKšœžœ˜Kšœ˜Kšœžœ˜Kšžœ ˜Kšžœ žœžœžœ˜(Kšžœ žœžœžœ˜&Kšžœ˜K˜—šžœžœžœ˜šžœžœžœ˜-Kšžœ;˜?Kšžœ˜—Kšžœ˜Kšœ˜—Kšœ˜Kšœ˜Kšœ#˜#K˜—K˜š œžœ5žœ˜Hšœžœ˜šœžœ˜ šžœ˜ K™ÃKš žœ žœžœžœ žœ˜:šžœ žœžœ˜Kšœžœ žœ˜&Kšœžœ žœ˜&K˜—šžœ žœžœ˜Kšœžœ žœ˜&K˜—K™Kšœ˜—K˜—Kšœžœžœ˜Kšœžœ˜ Kšœ4˜4Kšœ9˜9šžœžœžœ˜Kšœ˜Kšžœ#žœ˜?K˜—Kšžœžœ žœZ˜nKšœŸ˜KšœŸ˜—K˜Kšœ™˜Kšœø™øšžœžœ ˜šžœžœžœž˜CKšžœžœ˜ —K˜—šžœž˜ Kšžœ.˜2Kšžœ˜—Kšžœ˜K˜—K™ÃKš žœ žœžœžœ žœ˜:šžœ žœžœ˜Kšœžœ žœ˜&Kšœžœ žœ˜&K˜—šžœ žœžœ˜Kšœžœ žœ˜&K˜—K™K˜—K˜š œžœžœ%žœ˜IK™!Kšœ™šžœ ˜ šžœ˜K™9KšœR˜RKšœ"™"Kšœ]˜]KšžœžœC˜`K˜—šžœ˜Kšœ"™"KšœW˜WKšœ žœ"˜0K˜——K˜—K˜š  œžœžœžœžœ žœžœ˜bKšœÉ™ÉKšœ˜Kšœ˜Kšœžœ˜ Kšœžœ˜ K˜Kšœžœ(žœ˜7Kšžœ žœžœžœ˜Kšžœ"žœ.˜VK˜šžœ žœžœ˜Kšœ žœ˜+Kšœ žœ˜Kšœ žœ˜Kšœ žœ˜šœ$˜$Kšœžœ,˜K—šœ$˜$Kšœ>˜>—šœ$˜$Kšœ˜šœE˜EKšœ+˜+—Kšœ˜—K˜—K˜Kšœ žœ7˜JKšœ žœD˜SK˜Kšžœžœ˜8K˜Kšœ˜Kšœ˜Kš œ žœ žœžœžœžœ˜9šžœžœžœ˜Kšœ7˜7Kšœ ˜ K˜—Kšœ0˜0Kšœ˜Kšžœžœ#žœ˜XKšœ>˜>K˜K˜š œžœžœžœžœžœžœžœžœžœžœžœžœ˜SKšžœžœ ˜K˜——K˜š  œžœžœžœžœžœžœ žœžœ˜{Kšœí™íKšœžœžœžœ˜Kšœ˜Kšœžœ˜#Kšœžœ˜:K˜Kšœžœ(žœ˜7K˜Kšœ žœžœ˜Kšœ%˜%šžœ žœžœ˜Kšœ˜Kšœ'˜'Kšœ žœD˜SKšœ žœ7˜JKšœ˜K˜—šžœ˜Kšœžœ˜Kšœ žœ˜Kšœ žœ˜Kšœ>žœ)˜iKšœ\˜\K˜—K˜Kšžœžœ˜8K˜Kšœ˜Kš œ žœ žœžœžœžœ˜9K™Kšœ;˜;Kšœ>˜>K˜Kšœžœžœ žœ˜2Kšœ ˜ K˜—K˜š œžœ0žœžœ žœžœžœ˜}K™‚Kšœg™gKšœp™p™0Kšœ8™8KšœA™AKšœG™G—Kšœžœ˜5Kšœ$˜$Kšœ,˜,Kšœ6˜6Kšœžœ˜ Kšœžœ˜Kšœžœ˜K™K™iKšžœ.˜5K™K™;Kšœ žœ ˜K™˜OK™K™7Kšœ]˜]K˜šžœ˜Kšžœžœžœžœ žœžœžœH˜®Kš žœžœ žœžœžœE˜—K™K™š žœžœžœžœ žœ˜1Kšœ‰˜‰KšœZ˜ZK˜—Kšœ!˜!Kšœ˜—K˜—K˜š œžœ=žœ žœ˜kšœžœ˜Kšœžœ˜/šžœ žœ˜Kšœžœ5˜JKšžœžœžœ;˜`K˜—šžœ žœ˜Kšœžœ@˜XKšžœžœžœ>˜fK˜—Kšœ.žœžœ˜FKšœ˜—Kšžœ žœžœ)žœ ˜IK˜K˜—šœ1˜1Kšœ(™(Kš œžœ žœ žœžœ˜KK˜—K˜šœ žœ˜-Kšœ!˜!Kšœžœ˜Kšœžœ˜Kšœžœ%˜1Kšœžœ˜ šœ!˜!Kšœ*žœžœ ˜9—šžœžœ˜K™.Kšžœžœ9žœžœ˜SK™K™CKšœ(žœ žœžœ˜KKšžœ˜Kšœ˜—K˜KšœržœP˜Çšžœžœ˜#Kšœ8˜8Kšžœžœ˜ K˜—šœžœ%˜7Kš œžœ žœžœžœ ˜Q—K™$šžœžœž˜"Kšœžœ˜"šžœžœ žœ˜Kšœ˜KšœC˜CKšžœžœ˜ K˜—K˜Kšžœ˜—Kšœ˜KšœŒ˜ŒK™K™WKšœ žœ4˜Cšžœ$˜&Kšžœ"˜&Kšžœ˜—Kšœ€˜€K˜KšœCžœ7˜}K˜Kšœ˜K™K™¥Kšœ(žœ žœžœ˜SKšœ˜K˜Kšœ;˜;Kšžœ˜ K˜—K˜šÏb œžœ˜-Kšœžœžœ˜Kšœ žœžœ˜Kšœ žœžœ˜šœ8˜8Kšœ+žœžœ ˜:—Kšžœžœžœžœ˜)šžœžœžœž˜Kšœžœžœ˜Kšœžœ˜ Kšœ žœ˜Kšœ2˜2šžœžœ˜šžœžœžœž˜&šžœž˜Kšœžœ˜Kšœžœ˜Kšœžœ˜Kšžœ˜—Kšžœ˜—Kšžœ˜K˜—Kšœ˜Kšœ³˜³šžœžœžœ˜Kšœ˜K˜K˜—Kšžœžœžœ˜*Kšžœ˜—Kšžœ˜ K˜K˜—š œžœžœ žœ)žœžœžœ žœžœ žœžœ˜‹Kšœ„™„Kšœ]žœ˜dK˜K˜—š   œžœžœžœ žœ9žœ˜sKšœ žœ žœ˜.Kšœžœžœ&˜H˜Kš žœ*žœžœžœ žœžœ˜Tš žœ&žœžœžœ ž˜SKšœŸ+˜C—Kšžœ ˜K˜—Kšžœ˜Kšœ˜K˜—š  œžœ žœ3žœžœžœ žœžœ žœžœ˜–Kšœ4˜4Kšœ2˜2Kšœžœ žœ˜!Kšœžœžœžœ˜Kš œžœžœžœžœ˜4Kš œžœ žœžœžœžœ˜LKšœ žœ4˜Cšžœžœž˜Kšœžœ˜*Kšœžœ˜'Kšžœ+˜2—šœŸ˜Kšœžœžœžœ˜,Kšœžœžœ˜(šœžœ ˜Kš œžœ žœ"žœžœ˜@Kšœ˜—Kšœžœ ˜(Kš œžœžœžœ žœ ˜:K˜šžœžœžœžœ˜)Kšœ™Kšœžœžœ˜Kšœ˜šœžœžœ˜-š¡ œžœž˜Kš œžœžœžœžœ˜DKšžœ=žœžœžœ˜QKšœ˜—Kšœ1˜1šžœ!˜#šžœ<˜?Kšœ˜—Kšžœžœ˜—Kšœ˜—Kšœ˜šžœžœ˜Kšœ*˜*Kšžœžœ˜ K˜—Kšœ˜—K˜šžœ˜ šžœ˜šœ˜šžœž˜Kšœ"˜"Kšœ&˜&Kšœ,˜,Kšœ(˜(Kšœ*˜*Kšžœžœ˜—Kšœ˜Kšžœžœ˜ Kšœ˜—šžœ˜ Kšœ"˜"Kšžœžœ˜ K˜—K˜—Kšœ-˜-K˜K˜—šœ˜Kšœ@˜@—šžœžœžœ˜Kšœ ˜ š žœžœžœ(žœžœž˜FKšžœQ˜SKšžœ˜—Kšœ ˜ Kš žœžœžœžœžœ˜*Kšœ˜K˜—Kšœª™ªK˜šžœžœ ˜šžœ˜Kšœ˜Kšœ˜Kšœ˜K˜—šžœ˜Kšœ˜Kšœ˜Kšœ ˜ K˜——K˜šž˜šœ ˜ Kšœžœ˜ K˜—šœ ˜ K˜——K˜—Kšžœžœžœžœ ˜(K˜Kšœ ˜ Kšœ žœ˜K˜K˜—š œžœžœ$žœ˜Išžœž˜!Kšœžœ˜1—K˜K˜—š  œžœžœžœžœ8˜iK˜šžœ0žœžœž˜Fšžœ#žœžœ˜2Kšžœžœžœžœ˜FKšžœ˜K˜—K˜ Kšžœ˜—K˜K˜—Kšœžœ˜#K˜Kšœžœžœžœ˜-šœžœžœ˜Kšœ˜Kšœžœ˜ K˜—šœ'˜'KšœGžœf™°šœ8˜8Kšœ+žœžœ ˜:—šžœžœžœž˜Kšœžœ ˜Kšœ4˜4šžœžœ˜#Kšœ+˜+Kšžœžœ˜ —Kšžœ˜Kšžœ.˜0Kšžœ˜—Kšžœ˜ K˜K˜—šœ&˜&Kšœ%™%Kšœ6˜6Kšœ ˜ K˜K˜—šœžœ˜6Kšœžœžœžœ˜+Kšœžœ ˜Kšžœ˜Kšžœ žœžœžœ˜(šžœ žœž˜Kšžœ˜Kšœ˜Kšžœ˜K˜Kšžœ˜—Kšžœ˜K˜—K˜šœžœ˜4Kšœžœžœžœ˜Kš œ žœžœžœžœ˜Kšœ˜KšœNžœ ˜`Kšžœžœžœžœ˜)Kšœ˜šžœ žœž˜Kšœ]˜]Kšžœžœ'žœžœ@˜xKšžœžœžœE˜nKšœ˜K˜Kšžœ˜—Kšœ žœ˜šžœ*˜,šžœ˜šžœžœž˜šžœžœ$žœž˜5Kšœ$žœ˜6—Kšœ˜K˜Kšžœ˜—šžœžœžœž˜0Kšœ$žœ ˜1—K˜—šžœ˜Kšœ5˜5šžœžœž˜šžœžœ$žœž˜5Kšœ$žœ˜6—Kšœ˜K˜Kšžœ˜—K˜——Kšœ˜K˜—K˜š œžœžœžœ˜2Kš œ žœžœžœžœ˜KšœO˜OKšžœ+žœžœžœ˜@Kšœ6˜6Kšžœžœžœžœ˜4Kšœ&žœ˜2Kšœ ˜ K˜K˜—š œžœ˜KšœPžœ.˜‚Kšœ®˜®Kšœh˜hKšœ`˜`KšœÕžœ˜ÚKšœœžœ˜¡Kšœsžœ˜xKšœ©˜©Kšœ†˜†KšœÆ˜ÆK˜K˜—K˜š  œžœ˜Kš œžœ žœ žœžœžœ˜^Kšœ˜—K˜Kšœ™K˜Kšœ˜K˜Kšžœ˜K˜K˜6K˜@K˜,K˜=K˜2K˜DK˜FK˜KK˜6K˜UKšœžœ!Ïeœ˜XK˜—…—…Ë—