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] = { 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]; }; }; }. 8CommandToolParseImpl.mesa Copyright c 1984, 1985, 1986 by Xerox Corporation. All rights reserved. Swinehart, June 25, 1984 2:48:53 pm PDT Russ Atkinson (RRA) April 2, 1986 4:35:33 pm PST The arguments to the command. argumentVector.argc is the number of arguments, argumentVector[0] is the command position of last fetched argument (initially none fetched) used to check if the command line has been parsed already for the current command Now the tokens must be glued back together again, but the listOfTokens is in reverse order Returns next token. This scanner (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 delimiter for token: either blank, meaning blank or switchchar (usually '-), or '", or '", meaning '" not followed by another '". ch now contains a character to be included in the token ΚΌ˜codešœ™Kšœ Οmœ=™HK™'K™0K˜šΟk ˜ Kšœžœ ˜Kšœ žœ˜&Kšœ ˜ Kšœžœ˜#Kšœ žœ"˜1KšžœžœA˜IKšžœžœžœžœ˜-Kšœžœ ˜Kšœ žœ˜!KšœžœFžœ˜l——headšœžœž˜#Kšžœ3žœžœ˜ZKšžœ˜Kšžœ ˜K˜Kšœžœ˜ Kšžœžœžœ˜Kšžœžœžœžœ˜K˜Kš Οnœžœžœ žœžœ˜-K˜šœ žœžœ˜šœ-žœ˜1Kšœo™o—šœžœ˜Kšœ:™:—šœ ž˜KšœQ™Q—K˜K˜—šŸœžœžœžœ˜DKšœžœ,˜7š žœžœžœžœ#žœ˜WKšœžœ˜Kšœ*žœ˜4Kšžœžœžœžœ!˜XK˜Kšœ!˜!KšœY˜YK˜—K˜K˜—š Ÿœžœžœžœžœ˜4Kšœžœ#˜*Kšžœ˜K˜K˜—š Ÿ œžœžœžœžœ˜:Kšœžœ#˜*Kšœžœ˜ Kš žœžœžœžœžœžœ˜