<> <> <> <> DIRECTORY Atom USING [MakeAtom], Commander USING [Handle, GetProperty], CommandTool, Convert USING [Error, IntFromRope], FileNames USING [Directory, ResolveRelativePath], FS USING [AccessOptions, EnumerateForNames, Error, NameProc, StreamOpen], IO USING [PutChar, RopeFromROS, ROS, STREAM], List USING [PutAssoc], ProcessProps USING [GetPropList], Rope USING [Cat, Concat, Equal, Fetch, Find, Flatten, IsEmpty, Length, Replace, ROPE, Size, SkipTo, Substr]; CommandToolParseImpl: CEDAR PROGRAM IMPORTS Atom, Commander, CommandTool, Convert, FileNames, FS, IO, List, ProcessProps, Rope EXPORTS CommandTool = { OPEN CommandTool; Handle: TYPE = Commander.Handle; ROPE: TYPE = Rope.ROPE; STREAM: TYPE = IO.STREAM; Failed: PUBLIC ERROR [errorMsg: ROPE] = CODE; ParseRecord: TYPE = RECORD [ argumentVector: CommandTool.ArgumentVector _ NIL, <> argumentPointer: INT _ -1, <> commandLine: ROPE <> ]; GetParseRecord: PROC [cmd: Handle] RETURNS [pr: REF ParseRecord] = { pr _ NARROW[CommandTool.GetProp[cmd, $ArgumentVector]]; IF pr = NIL OR (pr.argumentVector.argc > 0 AND pr.commandLine # cmd.commandLine) THEN { pr _ NEW[ParseRecord]; pr.argumentVector _ Parse[cmd ! Failed => CONTINUE]; IF pr.argumentVector = NIL THEN pr.argumentVector _ NEW[CommandTool.ArgHandleObject[0]]; pr.argumentPointer _ 0; pr.commandLine _ cmd.commandLine; cmd.propertyList _ List.PutAssoc[key: $ArgumentVector, val: pr, aList: cmd.propertyList]; }; }; NumArgs: PUBLIC PROC [cmd: Handle] RETURNS [INT] = { pp: REF ParseRecord _ GetParseRecord[cmd]; RETURN[pp.argumentVector.argc]; }; NextArgument: PUBLIC PROC [cmd: Handle] RETURNS [ROPE] = { pp: REF ParseRecord _ GetParseRecord[cmd]; n: NAT _ pp.argumentPointer + 1; IF pp = NIL OR n >= pp.argumentVector.argc THEN RETURN[NIL]; pp.argumentPointer _ n; RETURN[pp.argumentVector[n]]; }; ArgN: PUBLIC PROC [cmd: Handle, n: INT] RETURNS [ROPE] = { pp: REF ParseRecord _ GetParseRecord[cmd]; IF pp = NIL OR n < 0 OR n >= pp.argumentVector.argc THEN RETURN[NIL]; pp.argumentPointer _ n; RETURN[pp.argumentVector[n]]; }; RegistrationDirectory: PUBLIC PROC [cmd: Handle] RETURNS [dir: ROPE] = { dir _ FileNames.Directory[cmd.command]; IF dir.IsEmpty[] THEN RETURN["///"]; }; Parse: PUBLIC PROC [cmd: Handle, starExpand: BOOL _ FALSE, switchChar: CHAR _ '-] RETURNS [argv: ArgumentVector] = { IF starExpand THEN StarExpansion[cmd]; argv _ InnerParse[cmd, switchChar]; IF argv # NIL THEN argv[0] _ cmd.command; }; AmpersandSubstitution: PUBLIC PROC [cmd: Handle] = { AmpersandProc: PROC [thingName: ROPE] RETURNS [ROPE] = { arg: ATOM _ Atom.MakeAtom[thingName]; value: REF ANY; value _ Commander.GetProperty[key: arg, aList: ProcessProps.GetPropList[]]; IF value = NIL OR NOT ISTYPE[value, ROPE] THEN value _ Commander.GetProperty[key: arg, aList: cmd.propertyList]; IF NOT ISTYPE[value, ROPE] THEN RETURN[Rope.Concat["&", thingName]]; RETURN[NARROW[value, ROPE]]; }; Letters: PROC [c: CHAR] RETURNS [keep: BOOL] = { RETURN [c IN ['a..'z] OR c IN ['A..'Z] OR c IN ['0..'9]]; }; cmd.commandLine _ LookForThings[line: cmd.commandLine, thingChar: '&, charPredicate: Letters, proc: AmpersandProc]; }; DollarSubstitution: PUBLIC PROC [cmd: Handle] = { Letters: PROC [c: CHAR] RETURNS [keep: BOOL] = { RETURN [c IN ['a..'z] OR c IN ['A..'Z] OR c IN ['0..'9]]; }; WITH CommandTool.GetProp[cmd, $CommandFileArguments] SELECT FROM commandersArguments: ArgumentVector => { highest: INT _ 0; DollarProc: PROC [thingName: ROPE] RETURNS [value: ROPE _ NIL] = { IF Rope.Equal[thingName, "Rest", FALSE] THEN { FOR i: INT IN (highest..commandersArguments.argc) DO value _ Rope.Cat[value, " ", commandersArguments[i]]; ENDLOOP; RETURN[value]; } ELSE { argument: INT _ Convert.IntFromRope[thingName ! Convert.Error => GO TO nasty ]+1; IF argument < commandersArguments.argc THEN { IF argument > highest THEN highest _ argument; RETURN [commandersArguments[argument]]; }; EXITS nasty => RETURN [Rope.Concat["$", thingName]]; }; }; cmd.commandLine _ LookForThings[line: cmd.commandLine, thingChar: '$, charPredicate: Letters, proc: DollarProc]; }; ENDCASE; }; StarExpansion: PUBLIC PROC [cmd: Handle] = { token: ROPE; listOfTokens: LIST OF ROPE _ NIL; uecpArgv: ArgumentVector; IF cmd.commandLine.Find["*"] = -1 THEN RETURN; uecpArgv _ InnerParse[cmd]; FOR i: NAT IN [1..uecpArgv.argc) DO err: ROPE _ NIL; token _ uecpArgv[i]; IF Rope.Find[token, "*"] # -1 THEN { ENABLE FS.Error => IF error.group # bug THEN {err _ error.explanation; GO TO oops}; ConsProc: FS.NameProc = { listOfTokens _ CONS[fullFName, listOfTokens]; -- on front of list RETURN[TRUE]; }; FS.EnumerateForNames[pattern: FileNames.ResolveRelativePath[token], proc: ConsProc]; EXITS oops => ERROR CommandTool.Failed[err]; } ELSE listOfTokens _ CONS[token, listOfTokens]; -- on front of list ENDLOOP; <> cmd.commandLine _ "\n"; WHILE listOfTokens # NIL DO cmd.commandLine _ Rope.Cat[" ", listOfTokens.first, cmd.commandLine]; listOfTokens _ listOfTokens.rest; ENDLOOP; cmd.commandLine _ Rope.Flatten[cmd.commandLine]; }; IORedirection: PUBLIC PROC [cmd: Handle] RETURNS [inRedirected: BOOL _ FALSE, outRedirected: BOOL _ FALSE] = { token: ROPE; listOfTokens: LIST OF ROPE _ NIL; uecpArgv: ArgumentVector; i: NAT; { IF cmd.commandLine.SkipTo[pos: 0, skip: "<>"] = cmd.commandLine.Length[] THEN RETURN; uecpArgv _ InnerParse[cmd]; IF uecpArgv = NIL THEN ERROR Failed["Command line parse failure"]; cmd.commandLine _ NIL; i _ 1; DO IF i = uecpArgv.argc THEN EXIT; token _ uecpArgv[i]; i _ i + 1; SELECT TRUE FROM Rope.Equal[token, "<"] => { IF i = uecpArgv.argc THEN GO TO FailSyntax; IF inRedirected THEN GO TO FailDouble; cmd.in _ OpenRedirect[uecpArgv[i], $read]; i _ i + 1; inRedirected _ TRUE; LOOP; }; Rope.Equal[token, ">"] => { IF i = uecpArgv.argc THEN GO TO FailSyntax; IF outRedirected THEN GO TO FailDouble; cmd.out _ OpenRedirect[uecpArgv[i], $create]; i _ i + 1; outRedirected _ TRUE; LOOP; }; Rope.Equal[token, ">>"] => { IF i = uecpArgv.argc THEN GO TO FailSyntax; IF outRedirected THEN GO TO FailDouble; cmd.out _ OpenRedirect[uecpArgv[i], $append]; i _ i + 1; outRedirected _ TRUE; LOOP; }; ENDCASE => cmd.commandLine _ Rope.Cat[cmd.commandLine, " ", token]; ENDLOOP; cmd.commandLine _ Rope.Flatten[Rope.Concat[cmd.commandLine, "\n"]]; EXITS FailDouble => ERROR Failed["I/O redirected twice"]; FailSyntax => ERROR Failed["redirection without filename"]; }; }; ParseToList: PUBLIC PROC [cmd: Handle, starExpand: BOOL _ FALSE, switchChar: CHAR _ '-] RETURNS [list: LIST OF ROPE, length: NAT _ 0] = { IF starExpand THEN StarExpansion[cmd]; [list, length] _ InnerParseToList[cmd, switchChar]; length _ length - 1; list _ list.rest; }; InnerParseToList: PROC [cmd: Handle, switchChar: CHAR _ '-] RETURNS [list: LIST OF ROPE, length: NAT _ 1] = { cpd: REF CPData _ NEW[CPData]; tail: LIST OF ROPE _ list _ LIST[cmd.command]; cpd.swChar _ switchChar; cpd.rope _ cmd.commandLine; DO r: ROPE _ IGet[cpd ! UnmatchedQuote => GOTO Lose]; IF r = NIL THEN EXIT; length _ length + 1; tail _ tail.rest _ LIST[r]; ENDLOOP; EXITS Lose => Failed["Misatched quotes"]; }; LookForThings: PROC [line: ROPE, thingChar: CHAR, charPredicate: PROC [c: CHAR] RETURNS [keep: BOOL], proc: PROC [thingName: ROPE] RETURNS [ROPE]] RETURNS [ROPE] = { state: {outside, insideQuotes, insideThing} _ outside; c: CHAR; i: INT _ 0; thingPosition: INT; WHILE i < line.Length[] DO c _ line.Fetch[i]; SELECT state FROM outside => { SELECT c FROM '" => { state _ insideQuotes; }; thingChar => { state _ insideThing; thingPosition _ i; }; ENDCASE; }; insideQuotes => { IF c = '" THEN { IF line.Length[] > (i + 1) AND line.Fetch[i+1] = '" THEN i _ i + 1 ELSE state _ outside; }; }; insideThing => { IF NOT charPredicate[c] THEN { thingName: ROPE _ Rope.Substr[base: line, start: thingPosition + 1, len: i - thingPosition - 1]; thingValue: ROPE _ proc[thingName: thingName]; line _ line.Replace[start: thingPosition, len: IF c = thingChar THEN thingName.Length[] + 2 ELSE thingName.Length[] + 1, with: thingValue]; i _ thingPosition + thingValue.Length[]; state _ outside; }; }; ENDCASE => ERROR; i _ i + 1; ENDLOOP; IF state = insideQuotes THEN ERROR Failed["Mismatched quotes"]; RETURN[line]; }; OpenRedirect: PROC [fileName: ROPE, accessOptions: FS.AccessOptions] RETURNS [stream: STREAM _ NIL] = { errorMsg: ROPE; fileName _ FileNames.ResolveRelativePath[fileName]; stream _ FS.StreamOpen[fileName: fileName, accessOptions: accessOptions, keep: 2 ! FS.Error => IF error.group = user THEN { errorMsg _ error.explanation; CONTINUE }]; IF stream = NIL THEN ERROR Failed[Rope.Concat["Can't redirect I/O: ", errorMsg]]; }; UnmatchedQuote: ERROR = CODE; CPData: TYPE = RECORD [ ch: CHAR _ ' , -- first char of next token; initially blank swChar: CHAR, -- switch character ropeIndex: LONG INTEGER _ 0, -- index into Rope command line rope: ROPE _ NIL, -- command line passed from client done: BOOL _ FALSE -- used up all of command line ]; InnerParse: PROC [cmd: Handle, switchChar: CHAR _ '-] RETURNS [argv: ArgumentVector _ NIL] = { list: LIST OF ROPE _ NIL; nArgs: NAT _ 0; [list, nArgs] _ InnerParseToList[cmd: cmd, switchChar: switchChar]; argv _ NEW[ArgHandleObject[nArgs]]; FOR i: NAT IN [0..nArgs) DO argv[i] _ list.first; list _ list.rest; ENDLOOP; }; IGet: PROC [cpd: REF CPData] RETURNS [result: ROPE] = { <> <<(1) ignores leading blanks,>> <<(2) returns a quoted token intact (excluding the quotes),>> <<(3) returns an unquoted token delimited by a blank (not included) or switchchar (included in next token), the default switchchar is '->> <<(4) returns NIL at CR or end of file.>> <<(5) if the switchchar is '- then it must be preceded by a blank>> workstr: STREAM _ NIL; chlook: CHAR; <> <> <> WHILE (cpd.ch = ' OR cpd.ch = ' ) AND GetCh[cpd] DO ENDLOOP; IF cpd.done OR cpd.ch = '\n THEN RETURN[NIL]; chlook _ ' ; IF cpd.ch = '" THEN { [] _ GetCh[cpd]; chlook _ '"; }; DO IF cpd.done OR (chlook # '" AND cpd.ch = '\n) THEN GOTO EOL; SELECT chlook FROM ' , '\t => SELECT cpd.ch FROM ' , '" => IF workstr # NIL THEN EXIT; cpd.swChar => IF cpd.ch#'- AND workstr#NIL THEN EXIT; ENDCASE; '" => SELECT cpd.ch FROM '" => IF (NOT GetCh[cpd]) OR (cpd.ch # '") THEN EXIT; ENDCASE; ENDCASE => ERROR; <> IF workstr = NIL THEN workstr _ IO.ROS[]; IO.PutChar[workstr, cpd.ch]; IF NOT GetCh[cpd] THEN GOTO EOL; REPEAT EOL => IF chlook = '" THEN ERROR UnmatchedQuote; ENDLOOP; IF workstr = NIL THEN RETURN [NIL]; RETURN [IO.RopeFromROS[workstr]]; }; GetCh: PROC [cpd: REF CPData] RETURNS [BOOL] = { IF cpd.ropeIndex >= Rope.Size[cpd.rope] THEN GOTO EOS; cpd.ch _ Rope.Fetch[cpd.rope, cpd.ropeIndex]; cpd.ropeIndex _ SUCC[cpd.ropeIndex]; RETURN [TRUE]; EXITS EOS => { cpd.done _ TRUE; cpd.ch _ ' ; RETURN [FALSE]; }; }; }.