CommandToolParseImpl.mesa
Gifford on June 18, 1982 10:55 am
Paul Rovner on July 20, 1983 2:39 pm
Stewart on December 12, 1983 12:45 pm
DIRECTORY
Atom USING [MakeAtom],
Commander USING [Handle, GetProperty],
CommandTool,
Convert USING [Error, IntFromRope],
FileNames USING [ResolveRelativePath],
FS USING [AccessOptions, EnumerateForNames, Error, NameProc, StreamOpen],
IO USING [STREAM],
ProcessProps USING [GetPropList],
PrincOpsUtils USING [LongCOPY],
Rope USING [Cat, Concat, Equal, Fetch, Find, Flatten, FromRefText, Length, Replace, ROPE, Size, SkipTo, Substr];
CommandToolParseImpl: CEDAR PROGRAM
IMPORTS Atom, Commander, Convert, FileNames, FS, PrincOpsUtils, ProcessProps, Rope
EXPORTS CommandTool = {
OPEN CommandTool;
Failed: PUBLIC ERROR [errorMsg: Rope.ROPE] = CODE;
Parse: PUBLIC PROC [cmd: Commander.Handle, switchChar: CHAR ← '-] RETURNS [argv: ArgumentVector] = {
argv ← OldParse[commands: cmd.commandLine, switchchar: switchChar];
IF argv # NIL THEN argv[0] ← cmd.command;
};
AmpersandSubstitution: PUBLIC PROC [cmd: Commander.Handle] = {
AmpersandProc: PROC [thingName: Rope.ROPE] RETURNS [Rope.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.ROPE] THEN value ← Commander.GetProperty[key: arg, aList: cmd.propertyList];
IF NOT ISTYPE[value, Rope.ROPE] THEN RETURN[Rope.Concat["&", thingName]];
RETURN[NARROW[value, Rope.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: Commander.Handle] = {
commandersArguments: ArgumentVector ← NARROW[Commander.GetProperty[key: $CommandFileArguments, aList: cmd.propertyList], ArgumentVector];
DollarProc: PROC [thingName: Rope.ROPE] RETURNS [Rope.ROPE] = {
value: Rope.ROPE;
failed: BOOLFALSE;
argument: INT;
argument ← Convert.IntFromRope[thingName ! Convert.Error => {
value ← Rope.Concat["$", thingName];
failed ← TRUE;
CONTINUE;
}];
IF failed THEN RETURN[value];
IF commandersArguments = NIL OR commandersArguments.argc <= argument + 1 THEN RETURN [NIL];
RETURN[commandersArguments[argument + 1]];
};
Digits: PROC [c: CHAR] RETURNS [keep: BOOL] = {
RETURN [c IN ['0..'9]];
};
cmd.commandLine ← LookForThings[line: cmd.commandLine, thingChar: '$, charPredicate: Digits, proc: DollarProc];
};
StarExpansion: PUBLIC PROC [cmd: Commander.Handle] = {
token: Rope.ROPE;
listOfTokens: LIST OF Rope.ROPENIL;
uecpArgv: ArgumentVector;
IF cmd.commandLine.Find["*"] = -1 THEN RETURN;
uecpArgv ← OldParse[commands: cmd.commandLine];
FOR i: NAT IN [1..uecpArgv.argc) DO
token ← uecpArgv[i];
IF Rope.Find[token, "*"] # -1 THEN {
ConsProc: FS.NameProc = {
listOfTokens ← CONS[fullFName, listOfTokens]; -- on front of list
RETURN[TRUE];
};
FS.EnumerateForNames[pattern: FileNames.ResolveRelativePath[token], proc: ConsProc];
}
ELSE listOfTokens ← CONS[token, listOfTokens]; -- on front of list
ENDLOOP;
Now the tokens must be glued back together again, but the listOfTokens is in reverse order
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: Commander.Handle] RETURNS [inRedirected: BOOLFALSE, outRedirected: BOOLFALSE] = {
token: Rope.ROPE;
listOfTokens: LIST OF Rope.ROPENIL;
uecpArgv: ArgumentVector;
i: NAT;
{
IF cmd.commandLine.SkipTo[pos: 0, skip: "<>"] = cmd.commandLine.Length[] THEN RETURN;
uecpArgv ← OldParse[commands: cmd.commandLine];
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 GOTO FailSyntax;
IF inRedirected THEN ERROR Failed["input redirected twice"];
cmd.in ← OpenRedirect[uecpArgv[i], $read];
i ← i + 1;
inRedirected ← TRUE;
LOOP;
};
Rope.Equal[token, ">"] => {
IF i = uecpArgv.argc THEN GOTO FailSyntax;
IF outRedirected THEN ERROR Failed["output redirected twice"];
cmd.out ← OpenRedirect[uecpArgv[i], $create];
i ← i + 1;
outRedirected ← TRUE;
LOOP;
};
Rope.Equal[token, ">>"] => {
IF i = uecpArgv.argc THEN GOTO FailSyntax;
IF outRedirected THEN ERROR Failed["output redirected twice"];
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
FailSyntax => ERROR Failed["command line syntax error, redirection without filename"];
};
};
ParseToList: PUBLIC PROC [cmd: Commander.Handle, switchChar: CHAR ← '-] RETURNS [list: LIST OF Rope.ROPE, length: NAT ← 0] = {
uecpArgv: ArgumentVector ← OldParse[commands: cmd.commandLine, switchchar: switchChar];
IF uecpArgv = NIL THEN RETURN[NIL];
length ← 0;
FOR i: NAT DECREASING IN [1..uecpArgv.argc) DO
list ← CONS[uecpArgv[i], list]; -- on front of list
length ← length + 1;
ENDLOOP;
};
LookForThings: PROC [line: Rope.ROPE, thingChar: CHAR, charPredicate: PROC [c: CHAR] RETURNS [keep: BOOL], proc: PROC [thingName: Rope.ROPE] RETURNS [Rope.ROPE]] RETURNS [Rope.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 ← Rope.Substr[base: line, start: thingPosition + 1, len: i - thingPosition - 1];
thingValue: Rope.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.ROPE, accessOptions: FS.AccessOptions] RETURNS [stream: IO.STREAMNIL] = {
errorMsg: Rope.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 open file for IO redirection: ", errorMsg]];
};
UnmatchedQuote: ERROR = CODE;
CPData: TYPE = RECORD [
ch: CHARACTER ← ' , -- first char of next token; initially blank
swChar: CHARACTER, -- switch character
ropeIndex: LONG INTEGER ← 0, -- index into Rope command line
rope: Rope.ROPENIL, -- command line passed from client
done: BOOLEANFALSE, -- used up all of command line
argC: INT ← 0, -- number of tokens found
args: LIST OF Rope.ROPENIL -- list of tokens
];
OldParse: PROC [commands: Rope.ROPE, switchchar: CHARACTER ← '-] RETURNS [argv: ArgumentVector] = {
cpd: REF CPData ← NEW[CPData];
Fooey: PROC RETURNS [LIST OF Rope.ROPE, INT] = {
r: Rope.ROPE ← IGet[cpd];
IF r = NIL THEN RETURN [NIL, 1]
ELSE {
n: LIST OF Rope.ROPE;
c: INT;
[n, c] ← Fooey[];
RETURN [CONS[r, n], c+1];
};
};
cpd.swChar ← switchchar;
cpd.rope ← commands;
argv ← NIL;
[cpd.args, cpd.argC] ← Fooey[! UnmatchedQuote => GOTO Lose];
argv ← NEW[ArgHandleObject[cpd.argC]];
argv[0] ← "";
FOR i: NAT IN [1..argv.argc) DO
argv[i] ← cpd.args.first;
cpd.args ← cpd.args.rest;
ENDLOOP;
EXITS
Lose => Failed["Misatched quotes"];
};--Parse
IGet: PROC [cpd: REF CPData] RETURNS [result: Rope.ROPE] = {
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
chlook: CHARACTER;
workstr: REF TEXT;
delimiter for token: either blank, meaning blank
or switchchar (usually '/),
or '", or '", meaning '" not followed by another '".
RopeWP: PROC [c: CHARACTER] = {
IF workstr=NIL THEN workstr ← NEW[TEXT[40]];
IF workstr.length >= workstr.maxLength THEN TRUSTED {
old: REF TEXT ← workstr;
workstr ← NEW[TEXT[2*old.maxLength]];
PrincOpsUtils.LongCOPY[
from: LOOPHOLE[old, LONG POINTER] + 2, nwords: (old.maxLength + 1)/2,
to: LOOPHOLE[workstr, LONG POINTER] + 2];
workstr.length ← old.length;
};
workstr[workstr.length] ← c;
workstr.length ← SUCC[workstr.length];
};
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
' , '  => 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;
ch now contains a character to be included in the token
RopeWP[cpd.ch];
IF NOT GetCh[cpd] THEN GOTO EOL;
REPEAT
EOL => IF chlook = '" THEN ERROR UnmatchedQuote;
ENDLOOP;
IF workstr = NIL OR workstr.length=0 THEN RETURN [NIL];
result ← Rope.FromRefText[workstr];
};--IGet
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];
};
}; -- GetCh
}.-- UECPImpl
CHANGE LOG
Created by Stewart on 19-Mar-82 17:31
Rename Open to Parse by Gifford 23-Mar-82 14:46:08
CEDAR PROGRAM, Schroeder, September 17, 1982 3:20 pm
October 5, 1983 4:05 pm, Stewart
 renamed Parse to OldParse. Handles TAB now (interpreted like SP)
 added new Parse routine
December 2, 1983 10:22 pm, Stewart
 Fixed LookForThings to eat the trailing character if it is the same as the thingChar