home *** CD-ROM | disk | FTP | other *** search
- {$P128}
- {$V-}
- {$C-}
-
- PROGRAM tfind(Output);
-
- {.F-}
- {
- searches a group of files for a text string.
- type TFIND alone to get help screens.
-
- requires Turbo Pascal version 3.0 to compile.
- heap/stack size sets limit on depth of subdirectory searching allowed.
- compile with max heap= A000.
- program does not check for heap-stack collision, but
- Turbo run-time library will complain if that happens.
- there is a tradeoff between number of files in a given directory
- vs. the maximum depth of subdirectory checking.
- currently each level of subdirectory eats about 16K of RAM, on the heap.
- written 8/29/85, Kim Kokkonen, 408-378-3672.
- revised 9/24/85 to use 8088 fast string instructions. runs 5x faster!
-
- FEATURES:
- assembler-aided search algorithm, almost as fast as DOS FIND,
- and does more.
- match pattern up to 255 characters may include any ASCII character
- except #13, #10 and #26 (which will never produce a match).
- 8192 character input lines with segmentation (not truncation)
- for longer lines.
- automatic but selectable exclusion of binary files.
- quiet and verbose modes.
- built-in WordStar filter and Uppercasing.
- general purpose recursive file finder (up to 256 files per directory).
- optionally specify a list of files to search.
- redirectable output.
- }
- {.F+}
-
- CONST
- copyright:string[80]='TFIND - Text Finder. Copyright (c) 1986, TurboPower Software';
- version:string[4]='1.00';
- maxfiles = 256; {max number of files searched in a given directory}
- maxavoids = 32; {max number of file extensions to avoid}
- optionchar = '-'; {character which prefixes options on command line}
- endstr = #0;
- esc = '\';
- lspace = 's';
- ltab = 't';
- lbackspace = 'b';
- ascii = '#';
- BufLen = 4608; {buffer size for input reads -
- keep buflen>=2*max line length desired}
-
- TYPE
- drivename = STRING[2];
- filename = STRING[12];
- pathname = STRING[64];
- longstring = STRING[255];
- darray = RECORD
- num : Integer;
- arr : ARRAY[1..maxfiles] OF filename;
- END;
- farray = RECORD
- num : Integer;
- arr : ARRAY[1..maxfiles] OF pathname;
- END;
- register = RECORD
- CASE Integer OF
- 1 : (ax, bx, cx, dx, bp, si, di, ds, es, flags : Integer);
- 2 : (al, ah, bl, bh, cl, ch, dl, dh : Byte);
- END;
- dtarec = RECORD
- dosnext : ARRAY[1..21] OF Byte;
- attr : Byte;
- ftime, fdate, flsize, fhsize : Integer;
- fullname : ARRAY[1..13] OF Char;
- END;
- avoidset = ARRAY[1..maxavoids] OF STRING[3];
- buffer = ARRAY[1..BufLen] OF Byte;
- pathstring = STRING[64];
-
- VAR
- reg : register;
- listfilename, startpath : pathname;
- matchpattern : longstring;
- matchlen : Byte ABSOLUTE matchpattern;
- avoidextension : avoidset;
- avoidnum : Integer;
- lenmatch : Byte;
- readexclude, recursive, verbose, lineoutput, uselistfile, avoid,
- wordstar, casesens, printheader : Boolean;
- dta : dtarec;
- tstart, tstop, grandbytes, grandtotal, grandmatch : Real;
- buf, cleanbuf : buffer;
- err : Text[128]; {non-redirectable status output written here}
- files : farray;
-
- PROCEDURE dohalt(exitcode : Integer);
- {-halt}
- BEGIN
- Halt(exitcode);
- END; {dohalt}
-
- FUNCTION fileexists(s : pathname; attr : Integer) : Boolean;
- {-determine whether a file exists with the specified attribute}
- BEGIN
- reg.ah := $4E;
- s[Length(s)+1] := #0;
- reg.ds := Seg(s);
- reg.dx := Ofs(s[1]);
- reg.cx := attr;
- MsDos(reg);
- fileexists := ((reg.flags AND 1) = 0) AND ((dta.attr AND 31) = attr);
- END; {fileexists}
-
- PROCEDURE parsepath(VAR start : pathname;
- VAR dname : drivename;
- VAR pname : pathname;
- VAR fname : filename);
- {-parse a full (perhaps incomplete) pathname into component parts}
- VAR
- i : Integer;
- BEGIN
- {get drive name}
- i := Pos(':', start);
- IF i = 0 THEN BEGIN
- dname := '';
- pname := start;
- END ELSE BEGIN
- dname := Copy(start, 1, i);
- IF i = Length(start) THEN pname := '\'
- ELSE pname := Copy(start, i+1, 64);
- END;
-
- {see if wildcard specified}
- i := Pos('*', start)+Pos('?', start);
-
- {separate out filename and pathname}
- IF (i = 0) AND (fileexists(start, 16) OR (pname = '\')) THEN BEGIN
- {start specifies a subdirectory}
- IF pname <> '\' THEN pname := pname+'\';
- fname := '*.*';
- END ELSE BEGIN
- {parse out filename on end}
- i := Length(pname);
- WHILE (i > 0) AND NOT(pname[i] IN [':', '\', '/']) DO i := i-1;
- fname := Copy(pname, i+1, 63);
- pname := Copy(pname, 1, i);
- END;
- END; {parsepath}
-
- FUNCTION stupcase(s : longstring) : longstring;
- {-return the uppercase of a string}
- VAR
- i : Byte;
- BEGIN
- FOR i := 1 TO Length(s) DO s[i] := UpCase(s[i]);
- stupcase := s;
- END; {stupcase}
-
- FUNCTION breakpressed : Boolean;
- {-true if Break key has been pressed}
- {-note that keypressed function executes int 23 if ^C has been pressed}
- VAR
- c : Char;
- breakdown : Boolean;
- BEGIN
- {check current state}
- breakdown := False;
- WHILE KeyPressed AND NOT(breakdown) DO BEGIN
- Read(Kbd, c);
- IF c = ^c THEN breakdown := True;
- END;
- breakpressed := breakdown;
- END; {breakpressed}
-
- PROCEDURE breakhalt;
- {-executed when break is detected}
- {-exit with return code 1}
- BEGIN
- INLINE(
- {exit with a return code of 1}
- $B8/$01/$4C/ {mov ax,4c01}
- $CD/$21 {int 21}
- );
- END; {breakhalt}
-
- PROCEDURE setbreak;
- {-set the ctrl-break address to a process exit handler}
- BEGIN
- reg.ax := $2523;
- reg.ds := CSeg;
- reg.dx := Ofs(breakhalt);
- MsDos(reg);
- END; {setbreak}
-
- PROCEDURE setoptions;
- {-read command line and set up options and defaults}
- VAR
- i : Integer;
- c : Char;
- haltsoon, gotmatch : Boolean;
- param : longstring;
-
- PROCEDURE writehelp;
- VAR
- ch : Char;
- BEGIN
- writeln;
- writeln;
- writeln(copyright);
- writeln('All Rights Reserved. Version ',version);
- WriteLn;
- writeln;
- WriteLn('Usage: TFIND [options] MatchString [output redirection]');
- WriteLn;
- WriteLn('TFIND is used to search a set of files for a specified text string. It is');
- WriteLn('designed specifically for use with text files but will work half-heartedly');
- WriteLn('with binary files. Each <CR><LF> delimited text line is limited to 2048');
- WriteLn('characters. Lines longer than this will be broken into multiple segments, but');
- WriteLn('no text will be lost. Any given match must occur within a single line.');
- WriteLn;
- WriteLn('The MatchString contains the text which TFIND tries to find. It must always');
- WriteLn('be specified on the command line. It may not contain blank space or carriage');
- WriteLn('returns. Use the special symbols below to insert unprintable characters into');
- WriteLn('MatchString. The search string is limited to 255 characters, although in');
- WriteLn('practice it is limited by the DOS command line to ~120. By default, TFIND');
- WriteLn('does all matching in uppercase (it is not case-sensitive), and it applies');
- WriteLn('a high-bit filter so that it works with WordStar files. These may be');
- WriteLn('changed via options below.');
- WriteLn;
- writeln;
- Write('press any key to continue (Q to quit)....'); Read(Kbd, ch);
- IF UpCase(ch) = 'Q' THEN dohalt(2);
- WriteLn;
- WriteLn;
- WriteLn;
- WriteLn;
- WriteLn('If no options are specified, the search will cover all files in the current');
- WriteLn('directory and drive. Options below allow you to specify selected files, start');
- WriteLn('in any directory or drive, or search all subdirectories of the start');
- WriteLn('directory. TFIND is limited to 256 files per directory.');
- WriteLn;
- WriteLn('Because TFIND is not designed for binary files, it can automatically exclude');
- WriteLn('certain file types from its search. You can exclude extensions by using an');
- WriteLn('optional "exclude" file named TFIND.EXC. TFIND automatically looks in the');
- WriteLn('current directory and the root directory of the default drive for this file.');
- WriteLn('If it finds one of these, it will read it. Each line of TFIND.EXC should');
- WriteLn('hold one extension to be excluded from searching. The normal TFIND.EXC file');
- WriteLn('will exclude files with extensions of COM, EXE, OBJ, WKS, and BIN.');
- WriteLn;
- WriteLn('For each string that TFIND matches, it will write out the filename and line');
- WriteLn('number of the match, followed by the matching line. TFIND writes to the');
- WriteLn('standard output, so these results can be redirected to a file, to the printer');
- WriteLn('or through a MORE filter as desired.');
- WriteLn;
- WriteLn;
- WriteLn;
- WriteLn;
- Write('press any key to continue (Q to quit)....'); Read(Kbd, ch);
- IF UpCase(ch) = 'Q' THEN dohalt(2);
- WriteLn;
- WriteLn('Options:');
- WriteLn;
- WriteLn(' -S Pathname Start search on specified drive and path, or with specified');
- WriteLn(' files matched. Full DOS wildcards and shorthand supported.');
- WriteLn(' -R Recursive search. Search down all subdirectories found.');
- WriteLn(' -F ListFile Search only those files named in ListFile. Files should');
- WriteLn(' be listed one per line, and may include pathname.');
- WriteLn(' -V Print info about lines that do NOT match.');
- WriteLn(' -G Search for Graphics characters (turn off WordStar Filter).');
- WriteLn(' -C Make Case significant in searching.');
- WriteLn(' -H Do NOT print Header lines (line # and file name) with match.');
- WriteLn(' -T Do NOT print matched Text lines, only then matched file name.');
- WriteLn(' -Q Quiet output mode. Normally TFIND keeps a status line');
- WriteLn(' running during its search. This status line shows the current');
- WriteLn(' file and line number as well as the number of matches found.');
- WriteLn(' This status always goes to the screen and is never');
- WriteLn(' redirected. -Q mode turns this status line off.');
- WriteLn(' -N No exclusions are made, that is, the file TFIND.EXC is not');
- WriteLn(' read.');
- WriteLn;
- WriteLn('Special Characters:');
- WriteLn(' \s space \t tab \\ backslash \b backspace');
- WriteLn(' #nnn any ASCII character nnn (3 digits or terminated with non-numeral)');
- WriteLn;
- Write('press any key to continue (Q to quit)....'); Read(Kbd, ch);
- IF UpCase(ch) = 'Q' THEN dohalt(2);
- WriteLn;
- WriteLn;
- WriteLn;
- WriteLn('Examples:');
- WriteLn;
- WriteLn(' TFIND stupcase');
- WriteLn(' will search all non-excluded files in the current directory for the');
- WriteLn(' string "stupcase". Case will not be significant.');
- WriteLn;
- WriteLn(' TFIND -R stupcase >stupcase.dat');
- WriteLn(' will search all non-excluded files in the current directory and all of');
- WriteLn(' its subdirectories for "stupcase". Results are written to STUPCASE.DAT.');
- WriteLn;
- WriteLn(' TFIND -S c:\pascal\*.pas -R stupcase');
- WriteLn(' looks at all files with extension .PAS starting in the \pascal directory');
- WriteLn(' and working down.');
- WriteLn;
- WriteLn(' TFIND -S a:graph.p -C GraphBackground');
- WriteLn(' looks only at one file, graph.p, for the word "GraphBackground". Case is');
- WriteLn(' significant in making the match.');
- WriteLn;
- WriteLn(' TFIND -S \*.DOC -R Now\sis\sthe\stime\sfor\sall >lpt1');
- WriteLn(' looks at all DOC files on the current drive for the string "Now is the');
- WriteLn(' time for all", and sends its results to LPT1.');
- dohalt(2);
- END; {writehelp}
-
- PROCEDURE doerror(message : longstring);
- {-display an error message}
- BEGIN
- WriteLn(err, message);
- haltsoon := True;
- END; {doerror}
-
- FUNCTION getccl(arg : longstring; i : Integer; VAR s : longstring) : Boolean;
- {-expand a character class starting at position i of arg into a string s}
- {return true if successful}
-
- PROCEDURE dodash(delim : Char; VAR arg : longstring; VAR i : Integer; VAR s : longstring);
- {-expand the innards of the character class, including dashes}
- {stop when endc is found}
- {return a string s with the expansion}
- VAR
- c : Char;
- j : Integer;
-
- PROCEDURE addstr(c : Char; VAR j : Integer; VAR s : longstring);
- {-append a character c onto string s and increment position}
- BEGIN
- j := j+1;
- s[j] := c;
- END; {addstr}
-
- PROCEDURE addcode(VAR arg : longstring; VAR i, j : Integer; VAR s : longstring);
- {-interpret an ASCII character code}
- VAR
- npos, code, cval : Integer;
- numstring : STRING[3];
- BEGIN
- npos := 0; numstring := '';
- WHILE (arg[i+npos] IN ['0'..'9']) AND (npos <= 2) DO BEGIN
- numstring := numstring+arg[i+npos];
- npos := npos+1;
- END;
- Val(numstring, cval, code);
- IF (code = 0) AND (cval >= 0) AND (cval < 254) THEN BEGIN
- {add the char to the set string}
- addstr(Chr(cval), j, s);
- i := i+npos-1;
- END ELSE BEGIN
- {illegal character, just interpret literally}
- WriteLn('WARNING: ASCII character ', Copy(arg, i-1, npos+1), ' not interpreted');
- addstr(ascii, j, s);
- i := i-1;
- END;
- END; {addcode}
-
- BEGIN {dodash}
- j := 0;
- WHILE (arg[i] <> delim) AND (arg[i] <> endstr) DO BEGIN
- c := arg[i];
- IF (c = esc) THEN BEGIN
- IF (arg[i+1] <> endstr) THEN BEGIN
- i := i+1;
- c := arg[i];
- {replace the special characters}
- IF (c = lspace) THEN addstr(#32, j, s)
- ELSE IF (c = ltab) THEN addstr(#9, j, s)
- ELSE IF (c = lbackspace) THEN addstr(#8, j, s)
- ELSE
- {add escaped character}
- addstr(c, j, s);
- END ELSE
- {escape must be the character}
- addstr(esc, j, s);
- END ELSE IF c = ascii THEN BEGIN
- IF (arg[i+1] <> endstr) AND (arg[i+1] IN ['0'..'9']) THEN BEGIN
- i := i+1;
- addcode(arg, i, j, s);
- END ELSE
- {ascii must be the character}
- addstr(c, j, s);
- END ELSE
- {literal character}
- addstr(c, j, s);
- i := i+1;
- END;
- s[0] := Chr(j);
- END; {dodash}
-
- BEGIN {getccl}
- {expand the potentially symbolic matchpattern}
- dodash(endstr, arg, i, s);
- getccl := (arg[i] = endstr);
- END; {getccl}
-
- PROCEDURE getexcludes;
- {-find and read the TFIND.EXC file}
- CONST
- {files searched for excludes, add more if you like}
- name1 : pathname = 'TFIND.EXC';
- name2 : pathname = '\TFIND.EXC';
-
- PROCEDURE readexclude(name : pathname);
- {-open and read the exclude file}
- VAR
- f : Text;
- l : STRING[4];
- BEGIN
- IF verbose THEN
- Write(err, 'reading exclude file ', name);
- Assign(f, name);
- Reset(f);
- WHILE NOT(EoF(f)) DO BEGIN
- ReadLn(f, l);
- IF l <> '' THEN BEGIN
- IF l[1] = '.' THEN
- l := stupcase(Copy(l, 2, 3))
- ELSE
- l := stupcase(Copy(l, 1, 3));
- avoidnum := avoidnum+1;
- avoidextension[avoidnum] := l;
- END;
- END;
- Close(f);
- END; {readexclude}
-
- BEGIN
- IF fileexists(name1, 0) THEN readexclude(name1)
- ELSE IF fileexists(name2, 0) THEN readexclude(name2)
- ELSE IF verbose THEN BEGIN
- WriteLn(err, 'WARNING: exclude file not found....');
- WriteLn(Con);
- END;
- END; {getexcludes}
-
- BEGIN
- {get options}
- IF ParamCount = 0 THEN
- writehelp
- ELSE BEGIN
- WriteLn(Con);
- {get default directory and disk}
- GetDir(0, startpath);
- {set default flags}
- verbose := True;
- wordstar := True;
- casesens := False;
- avoid := False;
- readexclude := True;
- uselistfile := False;
- avoidnum := 0;
- haltsoon := False;
- gotmatch := False;
- recursive := False;
- printheader := True;
- lineoutput := True;
- grandtotal := 0.0;
- grandmatch := 0.0;
- grandbytes := 0.0;
- i := 1;
- WHILE i <= ParamCount DO BEGIN
- {analyze options}
- param := ParamStr(i);
- IF param[1] = optionchar THEN BEGIN
- {an option}
- IF Length(param) = 2 THEN BEGIN
- c := UpCase(param[2]);
- CASE c OF
- 'V' : avoid := True;
- 'R' : recursive := True;
- 'Q' : verbose := False;
- 'G' : wordstar := False;
- 'C' : casesens := True;
- 'N' : readexclude := False;
- 'H' : printheader := False;
- 'T' : lineoutput := False;
- 'S' : BEGIN {new start path follows}
- i := i+1;
- IF i <= ParamCount THEN
- startpath := stupcase(ParamStr(i))
- ELSE
- doerror('New start path not found....');
- END;
- 'F' : BEGIN
- uselistfile := True;
- i := i+1;
- IF i <= ParamCount THEN
- listfilename := stupcase(ParamStr(i))
- ELSE
- doerror('ListFile name not found....');
- END;
- END;
- END ELSE
- doerror('Unrecognized command option....'+ParamStr(i));
- END ELSE BEGIN
- {the match pattern}
- IF NOT(gotmatch) THEN BEGIN
- param := ParamStr(i)+endstr;
- IF NOT(getccl(param, 1, matchpattern)) THEN
- doerror('Error during expansion of match string....');
- gotmatch := True;
- lenmatch := Length(matchpattern)-1;
- END ELSE
- doerror('More than one match pattern specified....'+ParamStr(i));
- END;
- i := i+1;
- END;
- IF NOT(gotmatch) THEN
- doerror('No match pattern found....');
- IF NOT(casesens) THEN
- matchpattern := stupcase(matchpattern);
- IF haltsoon THEN BEGIN
- WriteLn(err, 'Type TFIND for help....');
- dohalt(2);
- END;
- IF uselistfile THEN BEGIN
- recursive := False;
- readexclude := False;
- END;
- IF readexclude THEN getexcludes;
- END;
- END; {setoptions}
-
- PROCEDURE setdta(VAR dta : dtarec);
- {-set new DTA address}
- BEGIN
- reg.ah := $1A;
- reg.ds := Seg(dta);
- reg.dx := Ofs(dta);
- MsDos(reg);
- END; {setdta}
-
- PROCEDURE scantext(startpath : pathname);
- {-get all files in pathname, scan them and output matches}
- {-called recursively in recursive mode}
- VAR
- dirs : darray;
- dname : drivename;
- pname, usepath : pathname;
- fname : filename;
- filnum : Integer;
-
- PROCEDURE parsedta(VAR name, ext : filename);
- {-return a name and extension from a DTA}
- VAR
- i : Byte;
- tempname : filename;
- BEGIN
- i := 1;
- WHILE dta.fullname[i] <> #0 DO i := i+1;
- Move(dta.fullname, tempname[1], i-1);
- tempname[0] := Chr(i-1);
- i := Pos('.', tempname);
- IF i = 0 THEN BEGIN
- name := tempname;
- ext := '';
- END ELSE BEGIN
- name := Copy(tempname, 1, i);
- ext := Copy(tempname, i+1, 3);
- END;
- END; {parsedta}
-
- FUNCTION getfirst(attr : Integer; VAR startpath : pathname;
- VAR name, ext : filename;
- VAR rightdirattr : Boolean) : Boolean;
- {-return true and a name if first file is found}
- VAR
- foundone : Boolean;
- BEGIN
- reg.ah := $4E;
- reg.ds := Seg(startpath);
- reg.dx := Ofs(startpath[1]);
- reg.cx := attr;
- MsDos(reg);
- foundone := ((reg.flags AND 1) = 0);
- rightdirattr := (dta.attr AND 16) = (attr AND 16);
- IF foundone THEN
- {scan the DTA for the file name and extension}
- parsedta(name, ext);
- getfirst := foundone;
- END; {getfirst}
-
- FUNCTION getnext(attr : Integer; VAR name, ext : filename;
- VAR rightdirattr : Boolean) : Boolean;
- {-return true and a name if another file is found}
- VAR
- foundone : Boolean;
- BEGIN
- reg.ah := $4F;
- reg.ds := Seg(dta);
- reg.dx := Ofs(dta);
- MsDos(reg);
- foundone := ((reg.flags AND 1) = 0);
- rightdirattr := (dta.attr AND 16) = (attr AND 16);
- IF foundone THEN
- {scan the DTA for the file name and extension}
- parsedta(name, ext);
- getnext := foundone;
- END; {getnext}
-
- PROCEDURE getfiles(attr : Integer; takeall : Boolean;
- VAR files : farray;
- VAR startpath : pathname);
- {-return the files in the files array}
- VAR
- tempname, tempext : filename;
- rightdir : Boolean;
-
- FUNCTION goodext(VAR ext : filename) : Boolean;
- {-return true if ext is not one to avoid}
- LABEL 1;
- VAR
- i : Integer;
- BEGIN
- goodext := True;
- FOR i := 1 TO avoidnum DO
- IF ext = avoidextension[i] THEN BEGIN
- goodext := False;
- GOTO 1;
- END;
- 1:
- END; {goodext}
-
- PROCEDURE getlistoffiles;
- {-open and read the listfile, check for file existence}
- VAR
- lfile : Text;
- l : pathname;
- BEGIN
- IF fileexists(listfilename, 0) THEN BEGIN
- Assign(lfile, listfilename);
- Reset(lfile);
- WITH files DO BEGIN
- num := 0;
- WHILE NOT(EoF(lfile)) DO BEGIN
- ReadLn(lfile, l);
- IF fileexists(l, 0) THEN BEGIN
- IF num < maxfiles THEN BEGIN
- num := num+1;
- arr[num] := l;
- END ELSE
- WriteLn(err, 'Warning: exceeded file capacity. ignoring ', l);
- END ELSE
- WriteLn(err, 'Warning: file ', l, ' not found. proceeding....');
- END;
- END;
- Close(lfile);
- END ELSE BEGIN
- WriteLn(err, 'ListFile ', listfilename, ' not found....');
- Halt(2);
- END;
- END; {getlistoffiles}
-
- BEGIN
- IF uselistfile THEN
- getlistoffiles
- ELSE WITH files DO BEGIN
- startpath[Length(startpath)+1] := #0;
- num := 0;
- IF getfirst(attr, startpath, tempname, tempext, rightdir) THEN
- REPEAT
- IF rightdir AND (tempname <> '.') AND
- (takeall OR goodext(tempext)) THEN BEGIN
- num := num+1;
- arr[num] := tempname+tempext;
- END;
- UNTIL (num = maxfiles) OR NOT(getnext(attr, tempname, tempext, rightdir));
- END;
- END; {getfiles}
-
- PROCEDURE getdirs(attr : Integer; takeall : Boolean;
- VAR files : darray;
- VAR startpath : pathname);
- {-return the directory names in the dirs array}
- VAR
- tempname, tempext : filename;
- rightdir : Boolean;
- BEGIN
- WITH files DO BEGIN
- startpath[Length(startpath)+1] := #0;
- num := 0;
- IF getfirst(attr, startpath, tempname, tempext, rightdir) THEN
- REPEAT
- IF rightdir AND (tempname <> '.') THEN BEGIN
- num := num+1;
- arr[num] := tempname+tempext;
- END;
- UNTIL (num = maxfiles) OR NOT(getnext(attr, tempname, tempext, rightdir));
- END;
- END; {getdirs}
-
- PROCEDURE openfile(fname : pathstring; access : Integer; VAR handle, errcode : Integer);
- {-open a file for reading and return the handle}
- BEGIN
- fname := fname+#0;
- reg.ds := Seg(fname[1]); reg.dx := Ofs(fname[1]);
- reg.ax := $3D00 OR access;
- MsDos(reg);
- IF (reg.flags AND 1) = 1 THEN
- errcode := reg.ax
- ELSE BEGIN
- handle := reg.ax;
- errcode := 0;
- END;
- END; {openfile}
-
- PROCEDURE closefile(handle : Integer);
- {-close a file opened by openfile}
- BEGIN
- reg.bx := handle;
- reg.ax := $3E00;
- MsDos(reg);
- IF (reg.flags AND 1) = 1 THEN BEGIN
- WriteLn('problem closing file....');
- dohalt(2);
- END;
- END; {closefile}
-
- PROCEDURE matchup(VAR fname : pathname);
- {-scan the file fname looking for the matchpattern}
- VAR
- usepath : pathname;
- handle : Integer;
- lpos, bufofs, bufpos, eolpos, searcheol,
- firstmatch, ccount, lcount, mcount : Integer;
- outline : longstring;
- errcode : Integer;
- matched, endoffile : Boolean;
-
- PROCEDURE BlockRead(inhandle : Integer; VAR b : buffer;
- bufofs : Integer; VAR count : Integer);
- {-read a chunk of characters from the specified handle}
- BEGIN
- reg.bx := inhandle;
- reg.cx := BufLen-bufofs;
- reg.ds := Seg(b[1]);
- reg.dx := Ofs(b[1])+bufofs;
- reg.ax := $3F00;
- MsDos(reg);
- count := reg.ax+bufofs;
- END; {blockread}
-
- PROCEDURE putl(VAR l : longstring);
- {-send a short line to the standard output}
- VAR
- len : Byte ABSOLUTE l;
- tlen : Byte;
- BEGIN
- tlen := len+2; l[len+1] := #13; l[tlen] := #10;
- reg.bx := 1;
- reg.cx := tlen;
- reg.ds := Seg(l[1]);
- reg.dx := Ofs(l[1]);
- reg.ah := $40;
- MsDos(reg);
- IF (reg.flags AND 1) = 1 THEN BEGIN
- WriteLn('ERROR: cannot write to output device....');
- dohalt(2);
- END;
- IF reg.ax <> tlen THEN BEGIN
- WriteLn('ERROR: disk full....');
- dohalt(2);
- END;
- END; {putl}
-
- PROCEDURE writeline(handle, lstart, lstop : Integer);
- {-send a buffer section to the specified file or device}
- BEGIN
- reg.bx := handle;
- reg.cx := Succ(lstop-lstart);
- reg.ds := Seg(cleanbuf);
- reg.dx := Pred(Ofs(cleanbuf)+lstart);
- reg.ax := $4000;
- MsDos(reg);
- IF (reg.flags AND 1) = 1 THEN BEGIN
- WriteLn(Con);
- WriteLn(err, 'ERROR during write....');
- dohalt(2);
- END;
- END; {writeline}
-
- PROCEDURE writeoutput(lcount : Integer; VAR usepath : pathname;
- lstart, lstop : Integer);
- {-write output for selected lines}
- VAR
- lstring : filename;
- BEGIN
- IF verbose THEN BEGIN
- Write(err, ^M); ClrEol;
- END;
- IF printheader THEN
- if lineoutput then begin
- Str(lcount:7, lstring);
- outline := '['+lstring+'] '+usepath;
- putl(outline);
- end else
- putl(usepath);
- IF lineoutput THEN BEGIN
- writeline(1, lstart, lstop);
- outline := '';
- putl(outline);
- putl(outline);
- END;
- mcount := Succ(mcount);
- IF verbose THEN Write(err, lcount:8, mcount:8, ' ', usepath);
- END; {writeoutput}
-
- PROCEDURE checkeof(VAR b : buffer; VAR Count : Integer);
- {-adjust count if #1A found in buffer}
- {count does not include the ^Z}
- {fast way}
- BEGIN
- INLINE(
- $C4/$7E/$04/ {LES DI,[BP+04]}
- $26/ {ES: }
- $8B/$0D/ {MOV CX,[DI]}
- $51/ {PUSH CX}
- $C4/$7E/$08/ {LES DI,[BP+08]}
- $B0/$1A/ {MOV AL,1A}
- $FC/ {CLD }
- $F2/ {REPNZ }
- $AE/ {SCASB }
- $58/ {POP AX}
- $75/$09/ {JNZ 011A}
- $29/$C8/ {SUB AX,CX}
- $48/ {DEC AX}
- $C4/$7E/$04/ {LES DI,[BP+04]}
- $26/ {ES: }
- $89/$05 {MOV [DI],AX}
- );
- {011A:}
- END; {checkeof}
-
- PROCEDURE filterbuf(VAR b : buffer; VAR count : Integer);
- {-do wordstar filtering and uppercasing}
- {fast way}
- BEGIN
- IF wordstar THEN BEGIN
- {clear high bits and transform soft chars to hard ones}
- INLINE(
- $C4/$7E/$04/ {LES DI,[BP+04]}
- $26/ {ES: }
- $8B/$0D/ {MOV CX,[DI]}
- $C4/$7E/$08/ {LES DI,[BP+08]}
- $8B/$F7/ {MOV SI,DI}
- $FC/ {CLD }
- {010B:}
- $AC/ {LODSB }
- $24/$7F/ {AND AL,7F}
- $3C/$1E/ {CMP AL,1E}
- $75/$04/ {JNZ 0116}
- $B0/$20/ {MOV AL,20}
- $EB/$06/ {JMP 011C}
- $3C/$1F/ {CMP AL,1F}
- $75/$02/ {JNZ 011C}
- $B0/$2D/ {MOV AL,2D}
- $AA/ {STOSB }
- $E2/$EC {LOOP 010B}
- );
- END;
-
- {keep a cased copy of buf for output}
- Move(b, cleanbuf, count);
-
- IF NOT(casesens) THEN BEGIN
- {uppercase the buffer}
- INLINE(
- $C4/$7E/$04/ {LES DI,[BP+04]}
- $26/ {ES: }
- $8B/$0D/ {MOV CX,[DI]}
- $C4/$7E/$08/ {LES DI,[BP+08]}
- $8B/$F7/ {MOV SI,DI}
- $FC/ {CLD }
- $AC/ {LODSB }
- $8A/$D8/ {MOV BL,AL}
- $80/$E3/$7F/ {AND BL,7F}
- $80/$FB/$61/ {CMP BL,61}
- $7C/$07/ {JL 011D}
- $80/$FB/$7A/ {CMP BL,7A}
- $7F/$02/ {JG 011D}
- $24/$DF/ {AND AL,DF}
- $AA/ {STOSB }
- $E2/$EB {LOOP 010B}
- );
- END;
- END; {filterbuf}
-
- PROCEDURE findeol(VAR b : buffer; Count, bufpos : Integer;
- VAR eolpos : Integer);
- {-return eolpos as the next <LF> in buffer, or count+1 if not found}
- {fast way}
- BEGIN
- INLINE(
- $C4/$7E/$0C/ {LES DI,[BP+0C]}
- $8B/$5E/$08/ {MOV BX,[BP+08]}
- $4B/ {DEC BX}
- $01/$DF/ {ADD DI,BX}
- $8B/$4E/$0A/ {MOV CX,[BP+0A]}
- $51/ {PUSH CX}
- $29/$D9/ {SUB CX,BX}
- $B0/$0A/ {MOV AL,0A}
- $FC/ {CLD }
- $F2/ {REPNZ }
- $AE/ {SCASB }
- $58/ {POP AX}
- $75/$04/ {JNZ 011A}
- $29/$C8/ {SUB AX,CX}
- $EB/$01/ {JMP 011B}
- $40/ {INC AX}
- $C4/$7E/$04/ {LES DI,[BP+04]}
- $26/ {ES: }
- $89/$05 {MOV [DI],AX}
- );
- END; {findeol}
-
- PROCEDURE firstchar(VAR buf : buffer; lpos, searcheol : Integer;
- matchchar : Char; VAR matchpos : Integer);
- {-return matchpos pointing to lead character of match, or as searcheol+1}
- {fast way}
- BEGIN
- INLINE(
- $C4/$7E/$0E/ {LES DI,[BP+0E]}
- $8B/$5E/$0C/ {MOV BX,[BP+0C]}
- $01/$DF/ {ADD DI,BX}
- $8B/$4E/$0A/ {MOV CX,[BP+0A]}
- $51/ {PUSH CX}
- $29/$D9/ {SUB CX,BX}
- $8A/$46/$08/ {MOV AL,[BP+08]}
- $FC/ {CLD }
- $F2/ {REPNZ }
- $AE/ {SCASB }
- $58/ {POP AX}
- $75/$04/ {JNZ 011A}
- $29/$C8/ {SUB AX,CX}
- $EB/$04/ {JMP 011E}
- $8B/$46/$0A/ {MOV AX,[BP+0A]}
- $40/ {INC AX}
- $C4/$7E/$04/ {LES DI,[BP+04]}
- $26/ {ES: }
- $89/$05 {MOV [DI],AX}
- );
- END; {firstchar}
-
- PROCEDURE matchstr(VAR b : buffer; firstmatch : Integer;
- VAR matchpattern : longstring; VAR matched : Boolean);
- {-return matched true if a match}
- {fast way}
- BEGIN
- INLINE(
- $C4/$7E/$0E/ {LES DI,[BP+0E]}
- $8B/$5E/$0C/ {MOV BX,[BP+0C]}
- $01/$DF/ {ADD DI,BX}
- $8B/$76/$08/ {MOV SI,[BP+08]}
- $31/$C9/ {XOR CX,CX}
- $8A/$0C/ {MOV CL,[SI]}
- { $FE/$C9/} {DEC CL}
- $46/ {INC SI}
- $46/ {INC SI}
- $FC/ {CLD }
- $F3/ {REPZ }
- $A6/ {CMPSB }
- $B0/$01/ {MOV AL,01}
- $E3/$02/ {JCXZ 011C}
- $B0/$00/ {MOV AL,00}
- $C4/$7E/$04/ {LES DI,[BP+04]}
- $26/ {ES: }
- $88/$05 {MOV [DI],AL}
- );
- END; {matchstr}
-
- BEGIN
- IF uselistfile THEN
- usepath := fname+#0
- ELSE
- usepath := dname+pname+fname+#0;
- openfile(usepath, 0, handle, errcode);
-
- IF errcode = 0 THEN BEGIN
- {ready to read and match file}
- lcount := 0; mcount := 0; bufofs := 0;
- IF verbose THEN
- Write(err, lcount:8, mcount:8, ' ', usepath);
- REPEAT
- BlockRead(handle, buf, bufofs, ccount);
- {adjust ccount for first ^Z found in buffer}
- checkeof(buf, ccount);
- endoffile := (ccount <= 0);
- IF NOT(endoffile) THEN BEGIN
- IF breakpressed THEN BEGIN
- closefile(handle);
- dohalt(1);
- END;
- grandbytes := grandbytes+ccount-bufofs;
- {do wordstar filtering and uppercasing}
- filterbuf(buf, ccount);
- bufofs := 0;
- bufpos := 1;
- REPEAT
- {find next EOL (<LF> used)}
- findeol(buf, ccount, bufpos, eolpos);
- IF (eolpos > BufLen) AND (bufpos > (BufLen SHR 1)) THEN BEGIN
- {eol not found in buffer, continue line into next buffer}
- Move(buf[bufpos], buf[1], eolpos-bufpos);
- bufofs := eolpos-bufpos;
- bufpos := eolpos;
- END ELSE BEGIN
- {end of line found or linebreak forced}
- lcount := Succ(lcount);
- IF verbose AND ((lcount AND 63) = 0) THEN
- Write(err, ^M, lcount:8);
- IF eolpos > ccount THEN
- {line was broken without finding a <CR><LF>}
- searcheol := Pred(eolpos)
- ELSE
- {don't look at <CR><LF> while searching}
- searcheol := eolpos-2;
- IF searcheol >= bufpos THEN BEGIN
- {nonempty line}
- lpos := pred(bufpos);
- REPEAT
- {see if line has at least the first char of matchpattern}
- firstchar(buf, lpos, searcheol, matchpattern[1], firstmatch);
- IF firstmatch+lenmatch <= searcheol THEN BEGIN
- {found at least the first char at position firstmatch
- and have a shot at matching the rest}
- matchstr(buf, firstmatch, matchpattern, matched);
- IF matched THEN
- {skip rest of line}
- lpos := Succ(searcheol)
- ELSE
- {try again after firstmatch}
- lpos := Succ(firstmatch);
- END ELSE BEGIN
- {this line doesn't match}
- lpos := Succ(searcheol);
- matched := False;
- END;
- UNTIL lpos > searcheol;
- END ELSE
- matched := False;
- IF (matched AND NOT(avoid)) OR (NOT(matched) AND avoid) THEN begin
- writeoutput(lcount, usepath, bufpos, searcheol);
- if not(lineoutput) then begin
- {show only the first match in a file - force exit here}
- eolpos:=ccount;
- endoffile:=true;
- end;
- end;
- END;
- bufpos := Succ(eolpos);
- UNTIL bufpos > ccount;
- END;
- UNTIL endoffile;
- grandtotal := grandtotal+lcount;
- grandmatch := grandmatch+mcount;
- closefile(handle);
- IF verbose THEN BEGIN
- Write(err, ^M); ClrEol;
- END;
- END ELSE BEGIN
- WriteLn(Con);
- WriteLn(err, 'PROGRAM ERROR in Matchup: file ', fname, ' not found');
- END;
- END; {matchup}
-
- BEGIN
- {get a list of all normal, readonly, hidden matching files in startpath}
- parsepath(startpath, dname, pname, fname);
- usepath := dname+pname+fname;
- IF verbose AND NOT(uselistfile) THEN BEGIN
- Write(err, ^M); ClrEol;
- Write(err, 'Reading directory of ', usepath);
- END;
- getfiles(3, False, files, usepath);
- IF verbose THEN BEGIN
- Write(err, ^M); ClrEol;
- END;
-
- {check each file for match pattern}
- FOR filnum := 1 TO files.num DO matchup(files.arr[filnum]);
-
- {look at subdirectories}
- IF recursive THEN BEGIN
- {get all subdirectories}
- usepath := dname+pname+'*.*';
- getdirs(19, True, dirs, usepath);
- {look in the subdirectories}
- FOR filnum := 1 TO dirs.num DO BEGIN
- {build a pathname to the subdirectory}
- usepath := dname+pname+dirs.arr[filnum]+'\'+fname;
- {call recursively}
- scantext(usepath);
- END;
- END;
- END; {scantext}
-
- PROCEDURE time(VAR sec : Real);
- {-return time of day in seconds since midnight}
- BEGIN
- reg.ah := $2C;
- MsDos(reg);
- sec := 1.0*(reg.dh+60.0*(reg.cl+60.0*reg.ch)+reg.dl/100.0);
- END; {time}
-
- PROCEDURE writeresults;
- BEGIN
- WriteLn(err, 'lines: ', grandtotal:7:0);
- WriteLn(err, 'matches: ', grandmatch:7:0);
- WriteLn(err, 'bytes: ', grandbytes:7:0);
- IF tstop-tstart > 0 THEN BEGIN
- WriteLn(err, 'line rate: ', (grandtotal/(tstop-tstart)):6:1, ' lines/sec');
- WriteLn(err, 'byte rate: ', (grandbytes/(tstop-tstart)):6:0, ' bytes/sec');
- END;
- END; {writeresults}
-
- BEGIN
- setdta(dta);
- Assign(err, 'ERR:');
- Rewrite(err);
- setoptions;
- setbreak;
- time(tstart);
- scantext(startpath);
- time(tstop);
- if verbose then writeresults;
- END.