<> <> <> <> <> DIRECTORY Commander USING [CommandProc, CommandProcHandle, CommandProcObject, Enumerate, Handle, Lookup], CommandTool, CommandToolLookup USING [FindMatchingFiles], Convert USING [Error, RopeFromLiteral], FileNames USING [ConvertToSlashFormat, CurrentWorkingDirectory, Directory, FileWithSearchRules, ResolveRelativePath], FS USING [Error], IO USING [CreateStream, CreateStreamProcs, GetInfo, PutChar, PutF1, PutRope, RopeFromROS, ROS, STREAM], IOUtils USING [closedStreamProcs], List USING [AList, Append, Assoc, DotCons, Memb, PutAssoc, Remove], Process USING [CheckForAbort], ProcessProps USING [AddPropList, GetProp], ReadEvalPrint USING [Handle], Rope USING [Cat, Concat, Equal, Fetch, Length, Match, Replace, ROPE, Substr], RopeFile USING [Create], ViewerClasses USING [Viewer]; CommandToolUtilitiesImpl: CEDAR PROGRAM IMPORTS Commander, CommandTool, CommandToolLookup, Convert, FileNames, FS, IO, IOUtils, List, Process, ProcessProps, Rope, RopeFile EXPORTS CommandTool SHARES IO = BEGIN ROPE: TYPE = Rope.ROPE; <<>> commandFileProcData: Commander.CommandProcHandle _ NEW[Commander.CommandProcObject _ [CommandTool.CommandFile]]; GetViewer: PUBLIC PROC [cmd: Commander.Handle] RETURNS [ViewerClasses.Viewer] = { rep: ReadEvalPrint.Handle _ NIL; WHILE rep = NIL DO rep _ NARROW[GetProp[cmd, $ReadEvalPrintHandle]]; IF rep # NIL AND rep.viewer # NIL THEN RETURN[rep.viewer] ELSE rep _ NIL; cmd _ NARROW[GetProp[cmd, $ParentCommander]]; IF cmd = NIL THEN EXIT; ENDLOOP; RETURN[NIL]; }; GetReadEvalPrint: PUBLIC PROC [cmd: Commander.Handle, topLevel: BOOL _ FALSE] RETURNS [ReadEvalPrint.Handle] = { rep: ReadEvalPrint.Handle _ NIL; <> WHILE rep = NIL DO rep _ NARROW[GetProp[cmd, $ReadEvalPrintHandle]]; IF rep # NIL AND (NOT topLevel OR rep.topLevel) THEN EXIT ELSE rep _ NIL; cmd _ NARROW[GetProp[cmd, $ParentCommander]]; IF cmd = NIL THEN EXIT; ENDLOOP; RETURN[rep]; }; GetProp: PUBLIC PROC [cmd: Commander.Handle, key: REF] RETURNS [REF] = { RETURN [List.Assoc[key, cmd.propertyList]]; }; CopyAList: PUBLIC PROC [old: List.AList] RETURNS [new: List.AList] = { <> tail: List.AList _ NIL; new _ NIL; UNTIL old = NIL DO newItem: List.AList _ LIST[List.DotCons[key: old.first.key, val: old.first.val]]; IF tail = NIL THEN new _ newItem ELSE tail.rest _ newItem; old _ old.rest; tail _ newItem; ENDLOOP; }; PutLocalProperty: PUBLIC PROC [key, val: REF ANY, aList: List.AList, origList: List.AList _ NIL] RETURNS [List.AList] = { <> newList: List.AList; FOR l: List.AList _ aList, l.rest WHILE l # NIL AND l # origList DO IF l.first.key = key THEN { l.first.val _ val; RETURN[aList]; }; ENDLOOP; newList _ LIST[List.DotCons[key: key, val: val]]; newList.rest _ aList; RETURN[newList]; }; Insulate: PUBLIC PROC [stream: IO.STREAM] RETURNS [safeStream: IO.STREAM] = { <> safeStream _ IO.CreateStream[streamProcs: IO.CreateStreamProcs[variety: stream.GetInfo[].variety, class: stream.GetInfo[].class, close: IOUtils.closedStreamProcs.close], streamData: NIL, backingStream: stream]; }; <> <<>> CallList: PUBLIC PROC [property: REF ANY, cmd: Commander.Handle, proc: PROC [result: REF, msg: ROPE] RETURNS [stop: BOOL]] = { <> result: REF; msg: ROPE; WITH GetProp[cmd, property] SELECT FROM list: LIST OF REF ANY => FOR l: LIST OF REF ANY _ list, l.rest WHILE l # NIL DO WITH l.first SELECT FROM cpc: Commander.CommandProcHandle => { [result: result, msg: msg] _ cpc.proc[cmd]; <> <> <> IF proc # NIL AND proc[result: result, msg: msg] THEN EXIT; }; ENDCASE; ENDLOOP; ENDCASE; }; AddProcToList: PUBLIC PROC [aList: List.AList, listKey: REF ANY, proc: Commander.CommandProcHandle, append: BOOL _ TRUE] RETURNS [List.AList] = { <> maybeList: REF ANY _ List.Assoc[key: listKey, aList: aList]; list: LIST OF REF ANY; alreadyThere: BOOL; IF maybeList # NIL AND ISTYPE[maybeList, LIST OF REF ANY] THEN list _ NARROW[maybeList]; alreadyThere _ list # NIL AND List.Memb[ref: proc, list: list]; IF NOT alreadyThere THEN { IF append THEN { ra: REF ANY _ proc; list _ List.Append[list, LIST[ra]]; } ELSE list _ CONS[proc, list]; aList _ List.PutAssoc[key: listKey, val: list, aList: aList]; }; RETURN[aList]; }; RemoveProcFromList: PUBLIC PROC [aList: List.AList, listKey: REF ANY, proc: Commander.CommandProcHandle] RETURNS [List.AList] = { <> maybeList: REF ANY _ List.Assoc[key: listKey, aList: aList]; list: LIST OF REF ANY; alreadyThere: BOOL; IF maybeList # NIL AND ISTYPE[maybeList, LIST OF REF ANY] THEN list _ NARROW[maybeList]; alreadyThere _ list # NIL AND List.Memb[ref: proc, list: list]; IF alreadyThere THEN aList _ List.PutAssoc[key: listKey, val: List.Remove[ref: proc, list: list], aList: aList]; RETURN[aList]; }; <<>> FileWithSearchRules: PUBLIC PROC [root: ROPE, defaultExtension: ROPE, cmd: Commander.Handle, tryStar: BOOL _ TRUE] RETURNS [fullPath: ROPE _ NIL] = { RETURN[FileNames.FileWithSearchRules[root: root, defaultExtension: defaultExtension, requireExact: NOT tryStar, searchRules: GetProp[cmd, $SearchRules]].fullPath]; }; <> <> ResolveRelativePath: PUBLIC PROC [path: ROPE] RETURNS [ROPE] = { RETURN[FileNames.ResolveRelativePath[path]]; }; ConvertToSlashFormat: PUBLIC PROC [path: ROPE] RETURNS [ROPE] = { RETURN[FileNames.ConvertToSlashFormat[path]]; }; CopyListOfRefAny: PUBLIC PROC [key: REF ANY, aList: List.AList] RETURNS [List.AList] = { <> ref: REF ANY _ List.Assoc[key: key, aList: aList]; IF ref = NIL THEN RETURN[aList]; WITH ref SELECT FROM lra: LIST OF REF ANY => aList _ List.PutAssoc[key: key, val: List.Append[lra], aList: aList]; ENDCASE; RETURN[aList]; }; AddSearchRule: PUBLIC PROC [cmd: Commander.Handle, dir: ROPE, append: BOOL _ TRUE] = { <> rules: LIST OF REF ANY; length: INT; first, last: CHAR; rules _ NARROW[GetProp[cmd, $SearchRules]]; IF dir = NIL THEN rules _ NIL ELSE { dir _ FileNames.ResolveRelativePath[dir]; length _ dir.Length[]; IF length < 3 THEN RETURN; dir _ FileNames.ConvertToSlashFormat[dir]; first _ dir.Fetch[0]; last _ dir.Fetch[length - 1]; IF first # '/ THEN RETURN; IF last # '/ THEN dir _ Rope.Concat[dir, "/"]; <> FOR r: LIST OF REF ANY _ rules, r.rest WHILE r # NIL DO IF Rope.Equal[NARROW[r.first, ROPE], dir, FALSE] THEN RETURN; ENDLOOP; IF append THEN { ra: REF ANY _ dir; rules _ List.Append[rules, LIST[ra]]; } ELSE rules _ CONS[dir, rules]; }; [] _ List.PutAssoc[key: $SearchRules, val: rules, aList: cmd.propertyList]; }; CurrentWorkingDirectory: PUBLIC PROC RETURNS [ROPE] = { RETURN[FileNames.CurrentWorkingDirectory[]]; }; <<>> LookupWithSearchRules: PUBLIC Commander.CommandProc = { root: ROPE _ cmd.command; wDir: ROPE _ NIL; wDirRoot: ROPE _ NIL; temp: ROPE _ NIL; ambiguous: BOOL _ FALSE; rules: LIST OF REF ANY; fullPath: BOOL; Try: PROC [name: ROPE] RETURNS [foundSomething: BOOL] = { cmd.procData _ Commander.Lookup[name]; IF cmd.procData # NIL AND cmd.procData.proc # NIL THEN { cmd.command _ name; RETURN[TRUE]; }; cmd.procData _ NIL; RETURN[FALSE]; }; UniqueMatch: PROC [name: ROPE] RETURNS [foundSomething: BOOL _ FALSE] = { lst: LIST OF ROPE _ NIL; p: PROC [key: ROPE, procData: Commander.CommandProcHandle] RETURNS [stop: BOOL _ FALSE] = { IF procData.proc # NIL AND Rope.Match[pattern: name, object: key, case: FALSE] THEN lst _ CONS[key, lst]; }; name _ Rope.Concat[name, "*"]; [] _ Commander.Enumerate[p]; IF lst = NIL THEN RETURN[FALSE]; IF lst.rest # NIL THEN { <> ros: IO.STREAM _ IO.ROS[]; ambiguous _ TRUE; result _ $Ambiguous; ros.PutRope[" . . . command ambiguous ( "]; WHILE lst # NIL DO ros.PutRope[lst.first]; ros.PutChar[' ]; lst _ lst.rest; ENDLOOP; ros.PutRope[")\n"]; msg _ IO.RopeFromROS[ros]; RETURN[TRUE]; }; <> cmd.command _ lst.first; cmd.procData _ Commander.Lookup[cmd.command]; RETURN[TRUE]; }; <<>> IF root.Length[] = 0 THEN GO TO notFound; root _ FileNames.ResolveRelativePath[root]; root _ FileNames.ConvertToSlashFormat[root]; <> IF Try[root] THEN GO TO found; <<>> fullPath _ root.Fetch[0] = '/; IF NOT fullPath THEN { <> wDir _ FileNames.CurrentWorkingDirectory[]; wDirRoot _ Rope.Concat[wDir, root]; IF Try[wDirRoot] THEN GO TO found; <<>> rules _ NARROW[GetProp[cmd, $SearchRules]]; <> <> FOR list: LIST OF REF ANY _ rules, list.rest WHILE list # NIL DO <> IF Try[Rope.Concat[NARROW[list.first], root]] THEN GO TO found; ENDLOOP; }; <<>> <> IF ambiguous OR UniqueMatch[root] THEN GO TO found; IF NOT fullPath THEN { <<>> <> IF ambiguous OR UniqueMatch[wDirRoot] THEN GO TO found; <<>> <> <> FOR list: LIST OF REF ANY _ rules, list.rest WHILE list # NIL DO IF ambiguous OR UniqueMatch[Rope.Concat[NARROW[list.first], root]] THEN GO TO found; ENDLOOP; }; GO TO notFound; EXITS found => IF result # $Ambiguous THEN result _ $Found; notFound => result _ $Failed; }; CommandFileWithSearchRules: PUBLIC Commander.CommandProc = { <> <> <> commandFileName: ROPE _ NIL; ambiguous: BOOL _ GetProp[cmd, $Result] = $Ambiguous; list: LIST OF ROPE _ CommandToolLookup.FindMatchingFiles[root: cmd.command, defaultExtension: ".cm", requireExact: ambiguous, searchRules: GetProp[cmd, $SearchRules]]; IF list = NIL THEN GO TO notFound; commandFileName _ list.first; IF list.rest # NIL THEN { err: IO.STREAM _ cmd.err; IO.PutRope[err, "[[Ambiguous command files:\n"]; FOR each: LIST OF ROPE _ list, each.rest WHILE each # NIL DO IO.PutF1[err, " %g\n", [rope[each.first]] ]; ENDLOOP; IO.PutRope[err, " ]]\n"]; GO TO ambig; }; cmd.commandLine _ Rope.Concat[commandFileName, " "]; cmd.command _ "Commander"; cmd.procData _ commandFileProcData; result _ $Found; EXITS ambig => result _ $Ambiguous; notFound => result _ $Failed; }; <<>> LoadAndRunWithSearchRules: PUBLIC Commander.CommandProc = { <> <> <> commandFileName: ROPE; sc: SavedCommand _ NIL; list: LIST OF ROPE _ CommandToolLookup.FindMatchingFiles[root: cmd.command, defaultExtension: ".load", requireExact: FALSE, searchRules: GetProp[cmd, $SearchRules]]; IF list = NIL THEN GO TO notFound; commandFileName _ list.first; IF list.rest # NIL THEN { err: IO.STREAM _ cmd.err; result _ $Ambiguous; IO.PutRope[err, "[[Ambiguous load files:\n"]; FOR each: LIST OF ROPE _ list, each.rest WHILE each # NIL DO IO.PutF1[err, " %g\n", [rope[each.first]] ]; ENDLOOP; IO.PutRope[err, " ]]\n"]; GO TO ambig; }; <> cmd.procData _ NEW[Commander.CommandProcObject _ [ ReallyLoadAndRun, "Command not yet loaded", NEW[SavedCommandObject _ [command: cmd.command, commandFileName: commandFileName]]]]; result _ $Found; EXITS ambig => result _ $Ambiguous; notFound => result _ $Failed; }; <> SavedCommand: TYPE = REF SavedCommandObject; SavedCommandObject: TYPE = RECORD [ command: ROPE _ NIL, commandFileName: ROPE _ NIL ]; ReallyLookupHandle: Commander.CommandProcHandle _ NEW[Commander.CommandProcObject _ [ReallyLoadAndRun, "ReallyLoadAndRun", "execute .load file and run command"]]; ReallyLoadAndRun: Commander.CommandProc = { <> sc: SavedCommand _ NARROW[cmd.procData.clientData]; commandFileName: ROPE _ sc.commandFileName; originalCommandLine: ROPE _ cmd.commandLine; oldOut: IO.STREAM; oldIn: IO.STREAM; loadFileDirectory: ROPE; inner: PROC = { ENABLE UNWIND => {cmd.out _ oldOut; cmd.in _ oldIn}; [result, msg] _ CommandTool.CommandFile[cmd]; cmd.out _ oldOut; cmd.in _ oldIn; }; commandFileName _ FileNames.ConvertToSlashFormat[commandFileName]; loadFileDirectory _ FileNames.Directory[path: commandFileName]; cmd.command _ "CommandTool"; cmd.commandLine _ commandFileName; cmd.procData _ commandFileProcData; oldOut _ cmd.out; cmd.out _ Insulate[cmd.err]; oldIn _ cmd.in; cmd.in _ Insulate[oldIn]; <> ProcessProps.AddPropList[ List.PutAssoc[key: $WorkingDirectory, val: loadFileDirectory, aList: NIL], inner]; IF result = $Failure THEN RETURN[result, msg]; cmd.command _ sc.command; cmd.commandLine _ originalCommandLine; [result, msg] _ LookupWithSearchRules[cmd]; IF cmd.procData # NIL AND cmd.procData.proc # NIL THEN { <> CommandTool.ExecuteCommand[cmd, FALSE]; result _ CommandTool.GetProp[cmd, $Result]; } ELSE RETURN[$Failure, ".load file failed to register command"]; }; AtSignLimit: NAT _ 20; AtSignFile: PROC [name: ROPE] RETURNS [msg: ROPE _ NIL] = { WITH ProcessProps.GetProp[$CommanderHandle] SELECT FROM cmd: Commander.Handle => { rules: REF _ CommandTool.GetProp[cmd, $SearchRules]; paths: LIST OF ROPE _ CommandToolLookup.FindMatchingFiles[name, ".cm", TRUE, rules]; IF paths # NIL THEN name _ paths.first; }; ENDCASE => { RETURN [RopeFile.Create[name: Rope.Concat[name, ".cm"], raw: FALSE ! FS.Error => CONTINUE]]; }; RETURN [RopeFile.Create[name: name, raw: FALSE ! FS.Error => { msg _ error.explanation; GO TO fail}]]; EXITS fail => ERROR CommandTool.Failed[msg]; }; <<>> Pass1: PUBLIC PROC [initial: ROPE, nameOnly: BOOL] RETURNS [first: ROPE _ NIL, rest: ROPE _ NIL, terminator: CHAR _ '\n, someExpansion: BOOL _ FALSE] = { <> { state: {outside, insideQuotes, insideAtName} _ outside; c: CHAR; i: INT _ 0; atPosition: INT; atSignDepth: NAT _ 0; startPosition: INT _ 0; pastLeadingWhiteSpace: BOOL _ FALSE; IF NOT nameOnly THEN pastLeadingWhiteSpace _ TRUE; WHILE i < initial.Length[] DO Process.CheckForAbort[]; { c _ initial.Fetch[i]; IF c = '\\ THEN { slashPosition: INT _ i; slashSequenceLength: INT _ 2; escapeRope: ROPE; i _ i + 1; -- pointing at char after the slash IF i >= initial.Length[] THEN ERROR CommandTool.Failed["Backslash at end of command"]; c _ initial.Fetch[i]; -- get the char after the slash IF c IN ['0..'9] THEN { IF (i + 2) >= initial.Length[] THEN ERROR CommandTool.Failed["Not enough characters for backslash convention"]; slashSequenceLength _ 4; }; escapeRope _ Convert.RopeFromLiteral[Rope.Cat["""", initial.Substr[start: slashPosition, len: slashSequenceLength], """"] ! Convert.Error => ERROR CommandTool.Failed["Backslash sequence error"]; ]; initial _ Rope.Replace[base: initial, start: slashPosition, len: slashSequenceLength, with: escapeRope]; i _ slashPosition; c _ initial.Fetch[i]; }; SELECT state FROM outside => { SELECT c FROM '" => { state _ insideQuotes; pastLeadingWhiteSpace _ TRUE; }; ';, '\n, '| => { terminator _ c; GOTO FoundTerminator; }; '@ => { state _ insideAtName; atPosition _ i; }; ' , '\t => { IF nameOnly THEN { IF pastLeadingWhiteSpace THEN { terminator _ ' ; GOTO FoundTerminator; } ELSE startPosition _ startPosition + 1; }; }; '& => { IF nameOnly THEN { -- end of name terminator _ c; GOTO FoundTerminator; }; IF initial.Length[] > (i + 1) THEN { SELECT initial.Fetch[i+1] FROM ' , ' , '\n, '; => { -- followed by whitespace terminator _ c; GOTO FoundTerminator; }; ENDCASE => NULL; } ELSE { -- end of line terminator _ c; GOTO FoundTerminator; } }; ENDCASE => pastLeadingWhiteSpace _ TRUE; }; insideQuotes => { IF c = '" THEN { IF initial.Length[] > (i + 1) AND initial.Fetch[i+1] = '" THEN i _ i + 1 ELSE state _ outside; }; }; insideAtName => { IF c = '@ OR c = ' OR c = '\n THEN { nameLength: INT _ i - atPosition - 1; atSignFileName: ROPE _ initial.Substr[start: atPosition + 1, len: nameLength]; initial _ Rope.Replace[base: initial, start: atPosition, len: IF c = '@ THEN nameLength + 2 ELSE nameLength + 1, with: AtSignFile[name: atSignFileName]]; i _ atPosition - 1; state _ outside; atSignDepth _ atSignDepth + 1; IF atSignDepth > AtSignLimit THEN ERROR CommandTool.Failed["Exceeded limit on expansion of @ command files"]; someExpansion _ TRUE; }; }; ENDCASE => ERROR; i _ i + 1; }; REPEAT FoundTerminator => { rest _ Rope.Substr[base: initial, start: i + 1]; initial _ Rope.Substr[base: initial, start: startPosition, len: i - startPosition]; IF NOT nameOnly THEN initial _ Rope.Concat[initial, "\n"]; }; ENDLOOP; IF state = insideQuotes THEN ERROR CommandTool.Failed["Mismatched quotes"]; IF state = insideAtName THEN ERROR CommandTool.Failed["Improper @-file specification"]; }; first _ initial; }; END. October 7, 1983 9:38 am, Stewart, Created December 13, 1983 4:14 pm, Stewart, fixed bug in CallList January 14, 1984 7:45 pm, Stewart, fixed bugs in ReallyLoadAndRun