GrepImpl.mesa
DIRECTORY
TextFind USING [CreateFromRope, SearchRope, MalformedPattern, Finder],
Commander USING [CommandProc, Register],
FS USING [StreamOpen, Error, NameProc, EnumerateForNames, StreamOptions],
IO USING [STREAM, rope, int, text, PutF, GetLine, EndOfStream, Close, PutText, PutChar, PutRope],
Rope USING [ROPE, Size, Fetch, Concat, Find],
RefText USING [TrustTextAsRope],
Ascii USING [Upper],
CommandTool USING [ParseToList];
GrepImpl: CEDAR PROGRAM
IMPORTS Commander, FS, IO, CommandTool, RefText, TextFind, Ascii, Rope = {
ROPE: TYPE = Rope.ROPE;
STREAM: TYPE = IO.STREAM;
binaryFileExtensions: LIST OF ROPELIST[".bcd", ".press", ".symbols", ".tipc", ".boot", ".versionmap", ".bittable"];
Grep: 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;
fileCount: INT ← 0;
totalNumberOfHits, totalFileHits, totalFilesExamined: INT ← 0;
pattern: TextFind.Finder;
interrupt: REF BOOLNEW[BOOLFALSE];
openOptions: FS.StreamOptions;
stopOnFirstError: BOOL;
ignoreTiogaFormatting: BOOL;
binaryFilesToo: BOOL;
literal, word, ignoreCase: BOOL; -- Interesting pattern options
oneHitPerLine, fileNamesOnly, textOnly, positionsOnly, regularOutput, verbose: BOOL; -- Display options.
line: REF TEXTNEW[TEXT[200]];
UsageMessage: PROC [] = {
IO.PutF[stderr, "Usage: GREP [switches] <pattern> <fileNames>\n"];
};
GrepFile: FS.NameProc = {
file: STREAM;
continue ← TRUE;
IF ~binaryFilesToo THEN
FOR l: LIST OF ROPE ← binaryFileExtensions, l.rest UNTIL l = NIL DO
IF Rope.Find[fullFName, l.first, 0, FALSE] # -1 THEN RETURN;
ENDLOOP;
file ← FS.StreamOpen[fullFName, read, openOptions ! 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, "Searching \"%g\"...", IO.rope[fullFName]];
GrepStream[file, fullFName];
IO.Close[file];
continue ← ~interrupt^;
EXITS
openFailed => {
interrupt^ ← interrupt^ OR stopOnFirstError;
continue ← ~interrupt^;
};
};
GrepStream: PROC [file: STREAM, fullFName: ROPE] = {
found: BOOL;
at, atEnd, before, after: INT;
numberOfHits: INT ← 0;
position: INT ← 0;
start: INT;
DO -- Match the pattern against the next line
line ← IO.GetLine[file, line ! IO.EndOfStream => EXIT];
start ← 0;
DO -- Keep matching the pattern until it fails.
[found, at, atEnd, before, after] ←
TextFind.SearchRope[pattern, RefText.TrustTextAsRope[line], start, line.length, interrupt];
IF ~found THEN EXIT;
IF verbose AND numberOfHits = 0 THEN
IO.PutF[stdout, "\n"];
numberOfHits ← numberOfHits + 1;
SELECT TRUE FROM
fileNamesOnly => {
IO.PutF[stdout, "%g\n", IO.rope[fullFName]];
GOTO prematureExit;
};
textOnly => IO.PutF[stdout, "%g\n", IO.text[line]];
positionsOnly => IO.PutF[stdout, "%g (%g)\n", IO.rope[fullFName], IO.int[position+at]];
regularOutput => {
IF numberOfHits = 1 THEN
IO.PutF[stdout, "%g\n", IO.rope[fullFName]];
IO.PutF[stdout, " (%g): ", IO.int[position+at]];
IF line.length > 50 THEN {
start: INTMAX[0, at-20];
end: INTMIN[line.length, start+50];
len: INT ← end-start;
dots: BOOL ← end < line.length;
IF start > 0 THEN IO.PutRope[stdout, "..."];
FOR i: INT IN [0..len) DO
line[i] ← line[i+start];
ENDLOOP;
line.length ← len;
IO.PutText[stdout, line];
IF dots THEN IO.PutRope[stdout, "..."];
}
ELSE
IO.PutText[stdout, line];
IO.PutChar[stdout, '\n];
};
verbose => IO.PutF[stdout, "%g (%g): %g\n", IO.rope[fullFName], IO.int[position+at], IO.text[line]];
ENDCASE => ERROR;
start ← atEnd;
IF oneHitPerLine THEN EXIT;
ENDLOOP;
position ← position + line.length + 1;
REPEAT
prematureExit => NULL;
ENDLOOP;
IF numberOfHits > 0 THEN {
totalNumberOfHits ← totalNumberOfHits + numberOfHits;
totalFileHits ← totalFileHits + 1;
};
IF verbose THEN
IF numberOfHits = 0 THEN
IO.PutF[stdout, "no matches found.\n"]
ELSE
IO.PutF[stdout, "Matched %g times.\n", IO.int[numberOfHits]];
totalFilesExamined ← totalFilesExamined + 1;
};
cmdLine ← CommandTool.ParseToList[cmd].list;
[literal, cmdLine, whoCares] ← GetSwitch["-pattern", cmdLine];
literal ← ~literal;
[ignoreCase, cmdLine, whoCares] ← GetSwitch["-caseSensitive", cmdLine];
ignoreCase ← ~ignoreCase;
[word, cmdLine, whoCares] ← GetSwitch["-word", cmdLine];
[oneHitPerLine, cmdLine, whoCares] ← GetSwitch["-oncePerLine", cmdLine];
[fileNamesOnly, cmdLine, whoCares] ← GetSwitch["-fileNamesOnly", cmdLine];
[textOnly, cmdLine, whoCares] ← GetSwitch["-textOnly", cmdLine];
[positionsOnly, cmdLine, whoCares] ← GetSwitch["-positionsOnly", cmdLine];
[verbose, cmdLine, whoCares] ← GetSwitch["-verbose", cmdLine];
regularOutput ← ~(fileNamesOnly OR textOnly OR positionsOnly OR verbose);
[stopOnFirstError, cmdLine, whoCares] ← GetSwitch["-stopOnFirstError", cmdLine];
[binaryFilesToo, cmdLine, whoCares] ← GetSwitch["-binaryFilesToo", cmdLine];
[ignoreTiogaFormatting, cmdLine, whoCares] ← GetSwitch["-ignoreTiogaFormatting", cmdLine];
IF SwitchesLeft[cmdLine, stderr] THEN GOTO prematureExit;
IF cmdLine = NIL THEN {
UsageMessage[];
GOTO prematureExit;
};
pattern ← TextFind.CreateFromRope[pattern: cmdLine.first, literal: literal, word: word, ignoreCase: ignoreCase ! TextFind.MalformedPattern => {
IO.PutF[stderr, "Syntax error in pattern \"%g\"\n", IO.rope[cmdLine.first]];
GOTO prematureExit;}];
cmdLine ← cmdLine.rest; -- Get rid of the pattern.
openOptions[tiogaRead] ← ignoreTiogaFormatting;
IF cmdLine = NIL THEN
GrepStream[stdin, "Standard input"]
ELSE
FOR l: LIST OF ROPE ← cmdLine, l.rest UNTIL l = NIL DO
FS.EnumerateForNames[DefaultToHighestGeneration[l.first], GrepFile];
IF interrupt^ THEN GOTO prematureExit;
ENDLOOP;
IF regularOutput OR verbose THEN
IO.PutF[stderr, "Files examined: %g, files matched: %g, number of matches: %g\n",
IO.int[totalFilesExamined], IO.int[totalFileHits], IO.int[totalNumberOfHits]];
EXITS
prematureExit => 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;
};
};
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];
};
DefaultToHighestGeneration: PROC [filePattern: ROPE] RETURNS [ROPE] = {
IF Rope.Find[filePattern, "!"] = -1 THEN
RETURN[Rope.Concat[filePattern,"!H"]]
ELSE
RETURN[filePattern];
};
Commander.Register[
 key: "Grep", proc: Grep, doc: "Searches files for lines that match a pattern."];
}.