TransImpl.mesa
Last Edited by: Nix, April 5, 1984 4:40:03 pm PST
Eric Nickell, June 23, 1986 1:54:59 pm PDT
Swinehart, May 17, 1985 11:46:19 am PDT
DIRECTORY
Commander USING [CommandProc, Register, Handle, CommandObject],
Convert USING [CardFromRope],
Basics USING [LowHalf],
FS USING [StreamOpen, Error, NameProc, EnumerateForNames, ComponentPositions, ExpandName],
IO USING [STREAM, rope, int, PutF, 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],
CommandTool USING [ParseToList];
TransImpl: CEDAR PROGRAM
IMPORTS Commander, FS, IO, CommandTool, 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: BOOLTRUE;
outFile: STREAMNIL;
UsageMessage: PROC [] = {
IO.PutF[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];
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.PutF[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.PutF["%g changes\n", IO.int[cnt]];
}
ELSE {
IF verbose THEN
IO.PutF[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: ROPENIL] = {
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: BOOLTRUE, 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.PutF["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: BOOLTRUE, 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.PutF["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: BOOLTRUE, cmdLine: LIST OF ROPE, appendFile: ROPENIL] = {
filePatternName: ROPE;
present: BOOL;
cmdLine ← CommandTool.ParseToList[handle, FALSE, 040C].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 ← NEW[Commander.CommandObject]; -- Hacque.
in: STREAMFS.StreamOpen[filePatternName ! FS.Error => {
stderr.PutF["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.PutF["Missing pattern and replacement in \"%g\"\n", IO.rope[handle.commandLine]];
GOTO SyntaxError;
}
ELSE IF transOp # delete AND cmdLine.rest = NIL THEN {
stderr.PutF["Missing replacement expression in \"%g\"\n", IO.rope[handle.commandLine]];
GOTO SyntaxError;
}
ELSE IF transOp = delete AND cmdLine.rest # NIL THEN {
stderr.PutF["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.PutF["File names are not allowed in pattern files: \"%g\"\n", IO.rope[handle.commandLine]];
GOTO SyntaxError;
};
EXITS
SyntaxError => ok ← FALSE;
};
ok: BOOL;
appendFile: ROPENIL;
[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: BOOLFALSE, newCmdLine: LIST OF ROPE, previous: LIST OF ROPENIL] = {
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: BOOLFALSE, arg: ROPE, newCmdLine: LIST OF ROPE, previous: LIST OF ROPENIL] = {
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: BOOLFALSE] = {
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.PutF[stderr, "Invalid switch: \"%g\"\n", IO.rope[l.first]];
switchesLeft ← TRUE;
};
ENDLOOP;
};
Prefix: PROC [r1, r2: ROPE, length: INT, ignoreCase: BOOLTRUE] 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];
};
Commander.Register[
 key: "Trans", proc: Trans, doc: "Transforms files using regular expressions."];
}.