TransImpl.mesa
Copyright Ó 1987, 1993 by Xerox Corporation. All rights reserved.
Nix, April 5, 1984 4:40:03 pm PST
Eric Nickell, June 23, 1986 1:54:59 pm PDT
Swinehart, March 18, 1991 11:48 pm PST
Bertrand Serlet April 2, 1987 10:20:13 pm PST
Willie-s, January 18, 1993 1:16 pm PST
DIRECTORY
Commander USING [CommandProc, Register, Handle],
CommanderOps USING [CreateFromStreams, ParseToList],
Convert USING [CardFromRope],
Basics USING [LowHalf],
FS USING [StreamOpen, Error, NameProc, EnumerateForNames, ComponentPositions, ExpandName],
IO USING [STREAM, rope, int, PutF, PutF1, EndOfStream, Close, PutChar, PutRope, GetLineRope],
List USING [DReverse],
RegularExpression USING [CreateFromRope, SearchRope, MalformedPattern, Finder, NameLoc],
Rope USING [ROPE, Size, Fetch, Concat, Find, Translate, Substr, Replace, FromChar],
RopeFile USING [FromStream],
Ascii USING [Upper, Lower]
;
TransImpl: CEDAR PROGRAM
IMPORTS Commander, CommanderOps, FS, IO, List, RegularExpression, Ascii, Rope, RopeFile, Convert, Basics = {
ROPE: TYPE = Rope.ROPE;
STREAM: TYPE = IO.STREAM;
ReplacementRec: TYPE = RECORD [kind: {field, fieldLower, fieldUpper, text} ¬ text, text: ROPE];
Replacement: TYPE = LIST OF REF ReplacementRec;
TransOp: TYPE = {delete, select, replace};
TransProgramContent: TYPE = RECORD [
pattern: RegularExpression.Finder,
replacement: Replacement,
patternText: ROPE,
replacementText: ROPE,
literal: BOOL,
word: BOOL,
ignoreCase: BOOL,
transOp: TransOp
];
TransProgram: TYPE = REF TransProgramContent;
Trans: Commander.CommandProc -- [cmd: Commander.Handle] -- = {
Callable from the command interpreter. Expects a list of file names as arguments, and counts the number of words in the indicated files.
stdout: STREAM ¬ cmd.out;
stdin: STREAM ¬ cmd.in;
stderr: STREAM ¬ cmd.err;
cmdLine: LIST OF ROPE;
whoCares: LIST OF ROPE;
transPrograms: LIST OF TransProgram ¬ NIL;
verbose: BOOL ¬ TRUE;
outFile: STREAM ¬ NIL;
UsageMessage: PROC [] = {
IO.PutRope[stderr, "Usage: trans [switches] [<pattern> <replacement>] <fileNames>\n"];
};
TransFile: FS.NameProc = {
inFile: STREAM;
cnt: INT;
newR: ROPE;
x: FS.ComponentPositions ¬ FS.ExpandName[fullFName].cp;
continue ¬ TRUE;
fullFName ← Rope.Substr[fullFName, 0, x.ver.start-1];
System now only works with UX views.
inFile ¬ FS.StreamOpen[fullFName, read ! FS.Error => {
IO.PutF[stderr, "Could not open \"%g\" -- %g\n", IO.rope[fullFName], IO.rope[error.explanation]];
GOTO openFailed;
}];
IF verbose THEN
IO.PutF1[stderr, "Transforming \"%g\"...", IO.rope[fullFName]];
[newR, cnt] ¬ TransStream[inFile];
IF cnt > 0 OR appendFile # NIL THEN {
IF appendFile = NIL THEN
outFile ¬ FS.StreamOpen[fileName: fullFName, accessOptions: create, keep: 2 ! FS.Error => {
IO.PutF[stderr, "Could not open \"%g\" for write -- %g\n", IO.rope[fullFName], IO.rope[error.explanation]];
GOTO openFailed;
}];
outFile.PutRope[newR];
IF appendFile = NIL THEN
outFile.Close[];
stderr.PutF1["%g changes\n", IO.int[cnt]];
}
ELSE {
IF verbose THEN
IO.PutRope[stderr, "no changes, file not rewritten.\n"];
};
inFile.Close[];
EXITS
openFailed => NULL;
};
TransStream: PROC [inFile: STREAM] RETURNS [newR: ROPE, cnt: INT] = {
r: ROPE ¬ RopeFile.FromStream[inFile];
[newR, cnt] ¬ TransRope[r, transPrograms];
};
TransRope: PROC [r: ROPE, p: LIST OF TransProgram] RETURNS [newR: ROPE, cnt: INT ¬ 0] = {
found: BOOL;
ReplacementRope: PROC [r: ROPE, p: TransProgram] RETURNS [rep: ROPE ¬ NIL] = {
FOR l: Replacement ¬ p.replacement, l.rest UNTIL l = NIL DO
IF l.first.kind # text THEN {
at, atEnd: INT;
t: ROPE;
[at, atEnd] ¬ RegularExpression.NameLoc[p.pattern, l.first.text];
t ¬ r.Substr[at, atEnd-at];
SELECT l.first.kind FROM
fieldUpper => t ¬ Upper[t];
fieldLower => t ¬ Lower[t];
ENDCASE;
rep ¬ rep.Concat[t];
}
ELSE
rep ¬ rep.Concat[l.first.text];
ENDLOOP;
};
newR ¬ r;
FOR l: LIST OF TransProgram ¬ p, l.rest UNTIL l = NIL DO
lastLoc, start, atEnd, before, after: INT ¬ 0;
DO
replRope: ROPE;
[found, start, atEnd, before, after] ¬
RegularExpression.SearchRope[l.first.pattern, newR, start];
IF ~found THEN EXIT;
IF atEnd=start THEN ERROR;
replRope ¬ ReplacementRope[newR, l.first];
newR ¬ newR.Replace[start, atEnd-start, replRope];
IF l.first.transOp = select THEN {
IF lastLoc < start THEN
newR ¬ newR.Replace[lastLoc, start-lastLoc];
start ¬ start + Rope.Size[replRope]-(start-lastLoc);
lastLoc ¬ start;
}
ELSE
start ¬ start + Rope.Size[replRope];
cnt ¬ cnt + 1;
ENDLOOP;
IF l.first.transOp = select THEN
newR ¬ newR.Replace[lastLoc, newR.Size[]-lastLoc];
IF verbose THEN stderr.PutChar['.];
ENDLOOP;
};
MakeTransProgram: PROC [p, r: ROPE, literal, word, ignoreCase: BOOL, transOp: TransOp] RETURNS [ok: BOOL ¬ TRUE, t: TransProgram ¬ NEW[TransProgramContent]] = {
t.patternText ¬ p;
t.replacementText ¬ r;
t.literal ¬ literal;
t.word ¬ word;
t.ignoreCase ¬ ignoreCase;
t.transOp ¬ transOp;
t.pattern ¬ RegularExpression.CreateFromRope[pattern: p, literal: literal, word: word, ignoreCase: ignoreCase ! RegularExpression.MalformedPattern => {
stderr.PutF1["Syntax error in pattern \"%g\"\n", IO.rope[p]];
GOTO prematureExit;}];
[ok, t.replacement] ¬ MakeReplacement[r, literal];
EXITS
prematureExit => ok ¬ FALSE;
};
MakeReplacement: PROC [r: ROPE, literal: BOOL] RETURNS [ok: BOOL ¬ TRUE, x: Replacement ¬ NIL] = {
startPoint, endPoint: INT ¬ 0;
len: INT ¬ r.Size[];
IF literal THEN {
IF len > 0 THEN
x ¬ CONS[NEW[ReplacementRec ¬ [text, r]], NIL];
RETURN;
};
WHILE startPoint < len DO
frag: REF ReplacementRec ¬ NEW[ReplacementRec];
endPoint ¬ startPoint;
SELECT r.Fetch[startPoint] FROM
'< => {
colonPoint: INT ¬ startPoint;
endPoint ¬ startPoint + 1;
DO
IF endPoint >= len THEN GOTO SyntaxError;
SELECT r.Fetch[endPoint] FROM
'' => {
r ¬ r.Replace[endPoint, 1];
len ¬ len - 1;
};
'> => {
endPoint ¬ endPoint + 1;
EXIT;
};
': => colonPoint ¬ endPoint;
ENDCASE => NULL;
endPoint ¬ endPoint + 1;
ENDLOOP;
IF colonPoint > startPoint THEN {
frag.text ¬ r.Substr[startPoint+1, colonPoint-startPoint-1];
SELECT Ascii.Lower[r.Fetch[colonPoint+1]] FROM
'u => frag.kind ¬ fieldUpper;
'l => frag.kind ¬ fieldLower;
ENDCASE => GOTO SyntaxError;
}
ELSE {
frag.kind ¬ field;
frag.text ¬ r.Substr[startPoint+1, endPoint-startPoint-2];
};
};
ENDCASE => {
endPoint ¬ startPoint;
DO
IF endPoint > len THEN
GOTO SyntaxError;
IF endPoint = len THEN EXIT;
SELECT r.Fetch[endPoint] FROM
'' => {
IF r.Fetch[endPoint+1] = '0 THEN {
r ¬ r.Replace[endPoint, 5, Rope.FromChar[VAL[Basics.LowHalf[Convert.CardFromRope[r.Substr[endPoint+1, 4], 8]]]]];
len ¬ len - 4;
}
ELSE {
r ¬ r.Replace[endPoint, 1];
len ¬ len - 1;
};
};
'\\ => {
SELECT Ascii.Lower[r.Fetch[endPoint+1]] FROM
'n => {
r ¬ r.Replace[endPoint, 2, "\n"];
len ¬ len - 1;
};
't => {
r ¬ r.Replace[endPoint, 2, "\t"];
len ¬ len - 1;
};
's => {
r ¬ r.Replace[endPoint, 2, " "];
len ¬ len - 1;
};
'\\ => {
r ¬ r.Replace[endPoint, 2, "\\"];
len ¬ len - 1;
};
ENDCASE => ERROR;
};
'< => EXIT;
ENDCASE => NULL;
endPoint ¬ endPoint + 1;
ENDLOOP;
frag.kind ¬ text;
frag.text ¬ r.Substr[startPoint, endPoint-startPoint];
};
x ¬ CONS[frag, x];
startPoint ¬ endPoint;
ENDLOOP;
TRUSTED {x ¬ LOOPHOLE[List.DReverse[LOOPHOLE[x]]];};
EXITS
SyntaxError => {
stderr.PutF1["Syntax error in replacement expression: \"%g\"\n", IO.rope[r]];
ok ¬ FALSE;
};
};
ParseCmdSwitches: PROC [handle: Commander.Handle, literal, ignoreCase, word: BOOL, transOp: TransOp, fileNamesOK: BOOL] RETURNS [ok: BOOL ¬ TRUE, cmdLine: LIST OF ROPE, appendFile: ROPE ¬ NIL] = {
filePatternName: ROPE;
present: BOOL;
cmdLine ¬ CommanderOps.ParseToList[handle].list;
[present, cmdLine, whoCares] ¬ GetSwitch["-pattern", cmdLine];
IF present THEN literal ¬ FALSE;
[present, cmdLine, whoCares] ¬ GetSwitch["-literal", cmdLine];
IF present THEN literal ¬ TRUE;
[present, cmdLine, whoCares] ¬ GetSwitch["-caseSensitive", cmdLine];
IF present THEN ignoreCase ¬ FALSE;
[present, cmdLine, whoCares] ¬ GetSwitch["-ignoreCase", cmdLine];
IF present THEN ignoreCase ¬ TRUE;
[present, cmdLine, whoCares] ¬ GetSwitch["-wordMatch", cmdLine];
IF present THEN word ¬ FALSE;
[present, cmdLine, whoCares] ¬ GetSwitch["-matchAnywhere", cmdLine];
IF present THEN word ¬ TRUE;
[present, cmdLine, whoCares] ¬ GetSwitch["-deleteMatched", cmdLine];
IF present THEN transOp ¬ delete;
[present, cmdLine, whoCares] ¬ GetSwitch["-selectMatched", cmdLine];
IF present THEN transOp ¬ select;
[present, cmdLine, whoCares] ¬ GetSwitch["-replaceMatched", cmdLine];
IF present THEN transOp ¬ replace;
[present, appendFile, cmdLine, whoCares] ¬ GetSwitchWithArg["-appendFile", cmdLine];
[present, filePatternName, cmdLine, whoCares] ¬ GetSwitchWithArg["-filePatterns", cmdLine];
IF SwitchesLeft[cmdLine, stderr] THEN GOTO SyntaxError;
IF present THEN {
h: Commander.Handle ¬
CommanderOps.CreateFromStreams[parentCommander: handle]; -- Hacque.
in: STREAM ¬ FS.StreamOpen[filePatternName ! FS.Error => {
stderr.PutF1["Could not open \"%g\"\n", IO.rope[filePatternName]];
GOTO SyntaxError}];
DO
nc: LIST OF ROPE;
h.commandLine ¬ in.GetLineRope[ ! IO.EndOfStream => EXIT];
IF h.commandLine.Size[] > 0 THEN
[ok, nc, appendFile] ¬ ParseCmdSwitches[h, literal, ignoreCase, word, transOp, FALSE];
IF ~ok THEN GOTO SyntaxError;
ENDLOOP;
in.Close[];
}
ELSE IF cmdLine = NIL THEN {
stderr.PutF1["Missing pattern and replacement in \"%g\"\n", IO.rope[handle.commandLine]];
GOTO SyntaxError;
}
ELSE IF transOp # delete AND cmdLine.rest = NIL THEN {
stderr.PutF1["Missing replacement expression in \"%g\"\n", IO.rope[handle.commandLine]];
GOTO SyntaxError;
}
ELSE IF transOp = delete AND cmdLine.rest # NIL THEN {
stderr.PutF1["Delete mode should not have replacement expression in \"%g\"\n", IO.rope[handle.commandLine]];
GOTO SyntaxError;
}
ELSE IF transOp = delete THEN {
ok: BOOL;
program: TransProgram;
[ok, program] ¬ MakeTransProgram[cmdLine.first, "", literal, word, ignoreCase, transOp];
IF ~ok THEN
GOTO SyntaxError;
transPrograms ¬ CONS[program, transPrograms];
cmdLine ¬ cmdLine.rest;
}
ELSE {
ok: BOOL;
program: TransProgram;
IF cmdLine.rest = NIL THEN GOTO SyntaxError;
[ok, program] ¬ MakeTransProgram[cmdLine.first, cmdLine.rest.first, literal, word, ignoreCase, transOp];
IF ~ok THEN
GOTO SyntaxError;
transPrograms ¬ CONS[program, transPrograms];
cmdLine ¬ cmdLine.rest.rest;
};
IF cmdLine # NIL AND ~fileNamesOK THEN {
stderr.PutF1["File names are not allowed in pattern files: \"%g\"\n", IO.rope[handle.commandLine]];
GOTO SyntaxError;
};
EXITS
SyntaxError => ok ¬ FALSE;
};
ok: BOOL;
appendFile: ROPE ¬ NIL;
[ok, cmdLine, appendFile] ¬ ParseCmdSwitches[cmd, FALSE, TRUE, FALSE, replace, TRUE];
IF appendFile # NIL THEN
outFile ¬ FS.StreamOpen[fileName: appendFile, accessOptions: create, keep: 2 ! FS.Error => {
IO.PutF[stderr, "Could not open \"%g\" for write -- %g\n", IO.rope[appendFile], IO.rope[error.explanation]];
GOTO openFailed;
}];
IF ~ok THEN RETURN [result: $Failure];
TRUSTED {transPrograms ¬ LOOPHOLE[List.DReverse[LOOPHOLE[transPrograms]]]};
IF cmdLine = NIL THEN {
stdout.PutRope[TransStream[stdin].newR];
}
ELSE
FOR l: LIST OF ROPE ¬ cmdLine, l.rest UNTIL l = NIL DO
FS.EnumerateForNames[DefaultToHighestGeneration[l.first], TransFile];
ENDLOOP;
IF appendFile # NIL THEN outFile.Close[];
EXITS openFailed => NULL;
};
GetSwitch: PROC [switch: ROPE, cmdLine: LIST OF ROPE, prefixLen: INT ¬ 2] RETURNS [present: BOOL ¬ FALSE, newCmdLine: LIST OF ROPE, previous: LIST OF ROPE ¬ NIL] = {
IF cmdLine = NIL THEN RETURN;
IF Prefix[switch, cmdLine.first, prefixLen] THEN {
present ¬ TRUE;
newCmdLine ¬ cmdLine.rest;
}
ELSE {
newCmdLine ¬ cmdLine;
FOR l: LIST OF ROPE ¬ cmdLine, l.rest UNTIL l.rest = NIL DO
IF Prefix[switch, l.rest.first, prefixLen] THEN {
l.rest ¬ l.rest.rest;
present ¬ TRUE;
previous ¬ l;
RETURN;
};
ENDLOOP;
};
};
GetSwitchWithArg: PROC [switch: ROPE, cmdLine: LIST OF ROPE, prefixLen: INT ¬ 2] RETURNS [present: BOOL ¬ FALSE, arg: ROPE, newCmdLine: LIST OF ROPE, previous: LIST OF ROPE ¬ NIL] = {
IF cmdLine = NIL THEN RETURN;
IF Prefix[switch, cmdLine.first, prefixLen] THEN {
present ¬ TRUE;
arg ¬ cmdLine.rest.first;
newCmdLine ¬ cmdLine.rest.rest;
}
ELSE {
newCmdLine ¬ cmdLine;
FOR l: LIST OF ROPE ¬ cmdLine, l.rest UNTIL l.rest = NIL DO
IF Prefix[switch, l.rest.first, prefixLen] THEN {
arg ¬ l.rest.rest.first;
l.rest ¬ l.rest.rest.rest;
present ¬ TRUE;
previous ¬ l;
RETURN;
};
ENDLOOP;
};
};
SwitchesLeft: PROC [cmdLine: LIST OF ROPE, stderr: STREAM, switchChar: CHAR ¬ '-] RETURNS [switchesLeft: BOOL ¬ FALSE] = {
FOR l: LIST OF ROPE ¬ cmdLine, l.rest UNTIL l = NIL DO
IF Rope.Size[l.first] >= 1 THEN
IF Rope.Fetch[l.first] = switchChar THEN {
IO.PutF1[stderr, "Invalid switch: \"%g\"\n", IO.rope[l.first]];
switchesLeft ¬ TRUE;
};
ENDLOOP;
};
Prefix: PROC [r1, r2: ROPE, length: INT, ignoreCase: BOOL ¬ TRUE] RETURNS [BOOL] = {
r2Len: INT ¬ r2.Size[];
r1Len: INT ¬ r1.Size[];
IF r1Len < length THEN ERROR;
IF r2Len > r1Len OR r2Len < length THEN RETURN[FALSE];
IF ~ignoreCase THEN
FOR i: INT IN [0..r2Len) DO
IF r1.Fetch[i] # r2.Fetch[i] THEN RETURN[FALSE];
ENDLOOP
ELSE
FOR i: INT IN [0..r2Len) DO
IF Ascii.Upper[r1.Fetch[i]] # Ascii.Upper[r2.Fetch[i]] THEN RETURN[FALSE];
ENDLOOP;
RETURN[TRUE];
};
Upper: PROC [r: ROPE] RETURNS [ROPE] = {
UpIt: PROC[c: CHAR] RETURNS [CHAR] = {
RETURN[Ascii.Upper[c]];
};
RETURN[Rope.Translate[base: r, translator: UpIt]];
};
Lower: PROC [r: ROPE] RETURNS [ROPE] = {
LowerIt: PROC[c: CHAR] RETURNS [CHAR] = {
RETURN[Ascii.Lower[c]];
};
RETURN[Rope.Translate[base: r, translator: LowerIt]];
};
DefaultToHighestGeneration: PROC [filePattern: ROPE] RETURNS [ROPE] = {
IF Rope.Find[filePattern, "!"] = -1 THEN
RETURN[Rope.Concat[filePattern,"!H"]]
ELSE
RETURN[filePattern];
};
GetArg: Commander.CommandProc~{
msg ¬ CommanderOps.ParseToList[cmd].list.first;
};
Commander.Register[
 key: "Trans", proc: Trans, doc: "Transforms files using regular expressions."];
Commander.Register[
 key: "GetArg", proc: GetArg, doc: "Tests complicated rope arguments."];
}.