<<>> <> <> <> <> <> <> <> 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] -- = { <> 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] [ ] \n"]; }; TransFile: FS.NameProc = { inFile: STREAM; cnt: INT; newR: ROPE; x: FS.ComponentPositions ¬ FS.ExpandName[fullFName].cp; continue ¬ TRUE; <> <> 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."]; }.