language-Modula2-0.1: examples/Modula-2_Libraries/andrea-m2/applications/eth-hamburg/text-tools/kwic.mod
MODULE Kwic;
(* A keyword-in-context program *)
(* V1.0, J.Andrea, July 85 *)
(* This code may be freely used and distributed, it may not be sold *)
(*
* find the input string in a text file and show the line on which
* it occured in the output file, with the line number --- many times too
*
* the line consists of a number of characters before and after the string
* -- this size is input by the user
*
* the line buffer is implemented with the use of a circular linked list
* with a pointer to the start, and a pointer to the position at which
* the string search is being performed
*
* the input string is converted to uppercase and the search is performed
* on uppercase only
*
*)
FROM Storage IMPORT ALLOCATE, DEALLOCATE;
FROM InOut IMPORT OpenInput, OpenOutput, CloseInput, CloseOutput,
Read, ReadLn, ReadCard,
WriteString, WriteLn, WriteCard,
in, out, EOL;
FROM FileSystem IMPORT Eof, Name;
CONST
null = 0C;
blank = ' ';
abs_max_string_size = 132 DIV 2;
abs_max_buffer = 132 - 2;
TYPE
String = ARRAY [0..abs_max_string_size] OF CHAR;
VAR
input_line_number : CARDINAL;
match_count : CARDINAL;
string : String;
string_size : CARDINAL;
buffer_size : CARDINAL;
input_file_name : String;
i : CARDINAL;
(* --------------------------------------------------------- *)
PROCEDURE UpperCase( c : CHAR ) : CHAR;
BEGIN (* *)
IF ( c >= 'a' ) & ( c <= 'z' ) THEN
RETURN CAP(c);
ELSE
RETURN c;
END; (* if *)
END UpperCase;
(* --------------------------------------------------------- *)
PROCEDURE InputString;
(*
* get the string to be search for and make sure that its not too big
*)
VAR
c : CHAR;
p : CARDINAL;
too_big : BOOLEAN;
BEGIN (* InputString *)
WriteString('Input the string to find ? ');
too_big := FALSE;
p := 0; Read(c);
WHILE ( c # EOL ) & ( NOT too_big ) DO
IF p = abs_max_string_size THEN
too_big := TRUE;
ELSE
string[p] := UpperCase(c);
Read(c); p := p + 1;
END; (* if *)
END; (* while *)
ReadLn;
IF too_big THEN
WriteLn;
WriteString('The program will only handle strings to a size of');
WriteCard(abs_max_string_size,10); WriteLn;
WriteString('so your string was truncated.'); WriteLn;
END; (* if *)
string[p] := null;
string_size := p;
END InputString;
(* --------------------------------------------------------- *)
PROCEDURE InputBufferSize;
(*
* get the number of characters to be output before and after the
* string once it is found
*)
BEGIN (* InputBufferSize *)
WriteString('What is the number of characters to be buffered');
WriteLn;
WriteString(' before and after the search string'); WriteLn;
(*
* make this suggestion about the size if its going on a terminal
* or a printer page
*)
WriteString('The suggested sizes are:'); WriteLn;
IF 80 > string_size THEN
WriteCard( (80-string_size) DIV 2, 10);
WriteString(' to fill up an 80 character line.'); WriteLn;
END; (* if *)
WriteCard( (132-string_size) DIV 2, 10);
WriteString(' to fill up a 132 character line.'); WriteLn;
WriteString(' 1 to'); WriteCard(abs_max_buffer,5);
WriteString(' ? ');
ReadCard(buffer_size); ReadLn;
WHILE ( buffer_size < 1 ) OR ( buffer_size > abs_max_buffer ) DO
WriteLn;
WriteString(' -- invalid number, try again --');
WriteLn;
WriteString('What is the number of characters to be buffered');
WriteLn;
WriteString(' before and after the search string');
WriteString(' 1 to'); WriteCard(abs_max_buffer,5);
WriteString(' ? ');
ReadCard(buffer_size); ReadLn;
END; (* while *)
END InputBufferSize;
(* --------------------------------------------------------- *)
PROCEDURE ScanText;
(*
* search through the input text looking for the input search string
* the search is done a character at a time every time a new character
* is input
* the beginning of the search position is always kept as a pointer to
* the position 'string_position'
* the top of the text is the pointer 'first', which moves one position
* forward in the ring every time a new character is input
* and characters are always input at the end position 'last'
*
* So the ring actually moves backward through the text, with characters
* being dropped off at the head 'first', but upon output the head
* of the ring is the first character on the line.
*)
TYPE
RingPtr = POINTER TO RingElement;
RingElement = RECORD
value : CHAR;
next : RingPtr;
END; (* record *)
VAR
first, last, string_position : RingPtr;
real_buffer_size : CARDINAL;
(* --------------------------------------------------------- *)
PROCEDURE InitalizeRing;
(*
* build the ring with the given number of characters on each side
* of the string using a linked list
*)
VAR
i : CARDINAL;
fiddle_dum, fiddle_de : RingPtr;
BEGIN (* InitalizeRing *)
(* the the actual size of the linked ring is *)
real_buffer_size := 2 * buffer_size + string_size;
(* build each element in the ring *)
(* each one points to the next element in the list *)
(* create the top of the list *)
NEW(first);
first^.value := null;
(* first is the part before the string *)
NEW(fiddle_dum);
first^.next := fiddle_dum;
FOR i := 1 TO buffer_size - 2 DO
NEW(fiddle_de);
fiddle_dum^.next := fiddle_de;
fiddle_dum^.value:= null;
fiddle_dum := fiddle_de;
END; (* for *)
(* then the string part *)
NEW(fiddle_de);
(* make the string point to this place *)
string_position := fiddle_dum;
(* and continue *)
fiddle_dum^.next := fiddle_de;
fiddle_dum^.value:= null;
fiddle_dum := fiddle_de;
FOR i := 1 TO string_size - 1 DO
NEW(fiddle_de);
fiddle_dum^.next := fiddle_de;
fiddle_dum^.value:= null;
fiddle_dum := fiddle_de;
END; (* for *)
(* and last is the part after the string *)
FOR i := 1 TO buffer_size DO
NEW(fiddle_de);
fiddle_dum^.next := fiddle_de;
fiddle_dum^.value:= null;
fiddle_dum := fiddle_de;
END; (* for *)
(* finally, burn the candle at both ends *)
fiddle_dum^.next := first;
fiddle_dum^.value:= null;
last := fiddle_dum;
(* now fill up the ring with the first charcters in the file *)
i := 0;
WHILE ( NOT Eof(in) ) & ( i < real_buffer_size ) DO
ReadNextChar; UpdateRingPointers;
i := i + 1;
END; (* while *)
END InitalizeRing;
(* --------------------------------------------------------- *)
PROCEDURE ReadNextChar;
(*
* get the next character in the file, control characters are blanked
*)
VAR
c : CHAR;
BEGIN (* ReadNextChar *)
IF NOT Eof(in) THEN
Read(c);
IF c = EOL THEN
(* replace the end of line with a blank *)
c := blank;
input_line_number := input_line_number + 1;
ELSIF c < ' ' THEN
(* skip control chars *)
c := blank;
END; (* if *)
last := last^.next;
last^.value := c;
ELSE
c := null;
END; (* if *)
END ReadNextChar;
(* --------------------------------------------------------- *)
PROCEDURE UpdateStringPointer;
(* move the position of the string ahead in the ring *)
BEGIN (* UpdateStringPointer *)
string_position := string_position^.next;
END UpdateStringPointer;
(* --------------------------------------------------------- *)
PROCEDURE UpdateRingPointers;
(* move the position of the first of the ring to the next character *)
BEGIN (* UpdateRingPointers *)
first := first^.next;
UpdateStringPointer;
END UpdateRingPointers;
(* --------------------------------------------------------- *)
PROCEDURE SearchForStringInFirstRing;
(* look for the string in the first 'buffer' characters' *)
VAR
save_position : RingPtr;
BEGIN (* SearchForStringInFirstRing *)
(* save the old position *)
save_position := string_position;
(* look for the string at the very beginning *)
string_position := first;
(*
* search until we come to the position of the real string pointer
* in the normal ring
*)
WHILE string_position # save_position DO
SearchForString;
UpdateStringPointer;
END; (* while *)
(*
* reset the old string place, to its normal position
* where it will stay for the rest of the program
*)
string_position := save_position;
END SearchForStringInFirstRing;
(* --------------------------------------------------------- *)
PROCEDURE SearchForString;
(*
* look for the search string at the current position
* if each successive character matches then move the search location
* along to the next character in the ring and test again ...
*)
VAR
i : CARDINAL;
search_value : RingPtr;
c : CHAR;
char_match : BOOLEAN;
BEGIN (* SearchForString *)
search_value := string_position;
i := 0; char_match := TRUE;
WHILE ( char_match ) & ( i < string_size ) DO
c := UpperCase(search_value^.value);
char_match := c = string[i];
i := i + 1;
search_value := search_value^.next;
END; (* while *)
IF char_match THEN
(* wow, it must have found the string *)
match_count := match_count + 1;
(* output the line number *)
WriteString('< line');
WriteCard(input_line_number,10);
WriteString(' >');WriteLn;
search_value := first;
(* output this line *)
(* do the first character in the ring *)
WriteString(search_value^.value);
search_value := search_value^.next;
(* then continue until we come back to the first again *)
WHILE search_value # first DO
WriteString(search_value^.value);
search_value := search_value^.next;
END; (* while *)
WriteLn; WriteLn;
END; (* if *)
END SearchForString;
BEGIN (* ScanText *)
match_count := 0; input_line_number := 1;
InitalizeRing;
SearchForStringInFirstRing;
WHILE NOT Eof(in) DO
SearchForString;
ReadNextChar;
UpdateRingPointers;
END; (* while *)
(* keep searching till the end of the current and last ring *)
FOR i := 1 TO buffer_size + string_size - 1 DO
UpdateStringPointer;
SearchForString;
END; (* for *)
END ScanText;
BEGIN (* Kwic *)
WriteLn;
InputString;
IF string_size > 0 THEN
WriteLn;
InputBufferSize;
WriteLn; OpenInput('.TXT'); WriteLn; OpenOutput('.OUT');
Name(in,input_file_name);
WriteString('KWIC searching for "'); WriteString(string);
WriteString('" in '); WriteString(input_file_name);
WriteLn;
WriteString('using a'); WriteCard(buffer_size,10);
WriteString(' character buffer on each side of the string.');
WriteLn; WriteLn;
ScanText;
CloseInput; CloseOutput;
WriteLn;
WriteCard(match_count,10); WriteString(' matches found in ');
WriteCard(input_line_number-1,10); WriteString(' input lines');
WriteLn;
ELSE
WriteString('null string is no good'); WriteLn;
END; (* if *)
END Kwic.