home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Shareware 1 2 the Maxx
/
sw_1.zip
/
sw_1
/
WINDOWS
/
UTILS
/
MEWIN10S.ZIP
/
SEARCH.C
< prev
next >
Wrap
C/C++ Source or Header
|
1992-03-11
|
43KB
|
1,804 lines
/*
* The functions in this file implement commands that search in the forward
* and backward directions.
*
* (History comments formerly here have been moved to history.c)
*
*/
#include <stdio.h>
#include "estruct.h"
#include "eproto.h"
#include "edef.h"
#include "elang.h"
static int patlenadd;
static int lastchfjump, lastchbjump;
static int deltaf[HICHAR], deltab[HICHAR];
static int replen; /* length of replacement string */
#if MAGIC
static int group_count;
static int group_len[MAXGROUPS];
static REGION group_reg[MAXGROUPS];
#endif
/*
* forwsearch -- Search forward. Get a search string from the user, and
* search for the string. If found, reset the "." to be just after
* the match string, and (perhaps) repaint the display.
*/
int PASCAL NEAR forwsearch(f, n)
int f, n; /* default flag / numeric argument */
{
register int status;
/* If n is negative, search backwards.
* Otherwise proceed by asking for the search string.
*/
if (n < 0)
return (backsearch(f, -n));
/* Ask the user for the text of a pattern. If the response is
* TRUE (responses other than FALSE are possible), search for
* pattern for up to n times, as long as the pattern is there
* to be found.
*/
if ((status = readpattern(TEXT78, &pat[0], TRUE)) == TRUE)
status = forwhunt(f, n);
/* "Search" */
return (status);
}
/*
* forwhunt -- Search forward for a previously acquired search string.
* If found, reset the "." to be just after the match string,
* and (perhaps) repaint the display.
*/
int PASCAL NEAR forwhunt(f, n)
int f, n; /* default flag / numeric argument */
{
register int spoint = PTEND;
register int status;
if (n < 0) /* search backwards */
return (backhunt(f, -n));
/* Make sure a pattern exists, or that we didn't switch
* into MAGIC mode after we entered the pattern.
*/
if (pat[0] == '\0') {
mlwrite(TEXT80);
/* "No pattern set" */
return FALSE;
}
#if MAGIC
if ((curwp->w_bufp->b_mode & MDMAGIC) && mcpat[0].mc_type == MCNIL) {
if (!mcstr())
return FALSE;
}
#endif
/*
* Do one extra search to get us past our current
* match, if the search type has us at the start
* of a match, instead of after a match.
*/
if (searchtype == SRBEGIN) {
spoint = PTBEG;
if (lastflag & CFSRCH)
n = (n > 2)? (n + 1): 2;
}
#if MAGIC
if (magical && (curwp->w_bufp->b_mode & MDMAGIC))
status = mcscanner(FORWARD, spoint, n);
else
#endif
status = scanner(FORWARD, spoint, n);
/* Complain if not there.
*/
if (status == FALSE)
mlwrite(TEXT79);
/* "Not found" */
thisflag |= CFSRCH;
return (status);
}
/*
* backsearch -- Reverse search. Get a search string from the user, and
* search, starting at "." and proceeding toward the front of the buffer.
* If found "." is left pointing at the first character of the pattern
* (the last character that was matched).
*/
int PASCAL NEAR backsearch(f, n)
int f, n; /* default flag / numeric argument */
{
register int status;
/* If n is negative, search forwards.
* Otherwise proceed by asking for the search string.
*/
if (n < 0)
return (forwsearch(f, -n));
/* Ask the user for the text of a pattern. If the response is
* TRUE (responses other than FALSE are possible), search for
* pattern for up to n times, as long as the pattern is there
* to be found.
*/
if ((status = readpattern(TEXT81, &pat[0], TRUE)) == TRUE)
status = backhunt(f, n);
/* "Reverse search" */
return (status);
}
/*
* backhunt -- Reverse search for a previously acquired search string,
* starting at "." and proceeding toward the front of the buffer.
* If found "." is left pointing at the first character of the pattern
* (the last character that was matched).
*/
int PASCAL NEAR backhunt(f, n)
int f, n; /* default flag / numeric argument */
{
register int spoint = PTBEG;
register int status;
if (n < 0)
return (forwhunt(f, -n));
/* Make sure a pattern exists, or that we didn't switch
* into MAGIC mode after we entered the pattern.
*/
if (tap[0] == '\0') {
mlwrite(TEXT80);
/* "No pattern set" */
return FALSE;
}
#if MAGIC
if ((curwp->w_bufp->b_mode & MDMAGIC) && tapcm[0].mc_type == MCNIL) {
if (!mcstr())
return FALSE;
}
#endif
/*
* Do one extra search to get us past our current
* match, if the search type has us at the start
* of a match, instead of after a match.
*/
if (searchtype == SREND) {
spoint = PTEND;
if (lastflag & CFSRCH)
n = (n > 2)? (n + 1): 2;
}
#if MAGIC
if (magical && (curwp->w_bufp->b_mode & MDMAGIC))
status = mcscanner(REVERSE, spoint, n);
else
#endif
status = scanner(REVERSE, spoint, n);
/* Complain if not there.
*/
if (status == FALSE)
mlwrite(TEXT79);
/* "Not found" */
thisflag |= CFSRCH;
return (status);
}
#if MAGIC
/*
* mcscanner -- Search for a meta-pattern in either direction. If found,
* reset the "." to be at the start or just after the match string,
* and (perhaps) repaint the display.
*/
int PASCAL NEAR mcscanner(direct, beg_or_end, repeats)
int direct; /* which way to go.*/
int beg_or_end; /* put point at beginning or end of pattern.*/
int repeats; /* search repetitions.*/
{
MC *mcpatrn; /* pointer into pattern */
LINE *curline; /* current line during scan */
int curoff; /* position within current line */
/* If we are going in reverse, then the 'end' is actually
* the beginning of the pattern. Toggle it.
*/
beg_or_end ^= direct;
/* Another directional problem: if we are searching forwards,
* use the pattern in mcpat[]. Otherwise, use the reversed
* pattern in tapcm[].
*/
mcpatrn = (direct == FORWARD)? &mcpat[0]: &tapcm[0];
/* Setup local scan pointers to global ".".
*/
curline = curwp->w_dotp;
curoff = curwp->w_doto;
/* Scan each character until we hit the head link record.
*/
while (!boundry(curline, curoff, direct)) {
/* Save the current position in case we need to
* restore it on a match, and initialize matchlen to
* zero in case we are doing a search for replacement.
*/
matchline = curline;
matchoff = curoff;
matchlen = 0;
#if WINDOW_MSWIN
{
static int o = 0;
if (--o < 0) {
longop(TRUE);
o = 100; /* to lower overhead, only 1/100
calls to longop */
}
}
#endif
if (amatch(mcpatrn, direct, &curline, &curoff)) {
/* A SUCCESSFULL MATCH!!!
* Flag that we have moved,
* reset the global "." pointers.
*/
curwp->w_flag |= WFMOVE;
if (beg_or_end == PTEND) /* at end of string */
{
curwp->w_dotp = curline;
curwp->w_doto = curoff;
}
else /* at beginning of string */
{
curwp->w_dotp = matchline;
curwp->w_doto = matchoff;
}
/* If we're heading in reverse, set the "match"
* pointers to the start of the string, for savematch().
*/
if (direct == REVERSE) {
matchline = curline;
matchoff = curoff;
}
if (savematch() == ABORT)
return ABORT;
/* Continue scanning if we haven't found
* the nth match.
*/
if (--repeats <= 0)
return TRUE;
}
/* Advance the cursor.
*/
nextch(&curline, &curoff, direct);
}
return FALSE; /* We could not find a match.*/
}
/*
* amatch -- Search for a meta-pattern in either direction. Based on the
* recursive routine amatch() (for "anchored match") in
* Kernighan & Plauger's "Software Tools".
*/
int PASCAL NEAR amatch(mcptr, direct, pcwline, pcwoff)
register MC *mcptr; /* pattern to scan for */
int direct; /* which way to go.*/
LINE **pcwline; /* current line during scan */
int *pcwoff; /* position within current line */
{
register int c; /* character at current position */
LINE *curline; /* current line during scan */
int curoff; /* position within current line */
int n_chars; /* number of chars matched in a closure.*/
int cl_min; /* minimum number of chars matched in closure.*/
int cl_type; /* Which closure type?.*/
/* Set up local scan pointers to ".".
*/
curline = *pcwline;
curoff = *pcwoff;
/*
* Loop through the meta-pattern, laughing all the way.
*/
while (mcptr->mc_type != MCNIL) {
/* Is the current meta-character modified
* by a closure?
*/
if (cl_type = (mcptr->mc_type & ALLCLOS)) {
c = nextch(&curline, &curoff, direct);
n_chars = 0;
if (cl_type == ZEROONE) {
cl_min = 0;
if (mceq(c, mcptr)) {
c = nextch(&curline, &curoff, direct);
n_chars++;
}
}
else
{
/* Minimum number of characters that may
* match is 0 or 1.
*/
cl_min = (cl_type == CLOSURE_1);
/* Match as many characters as possible
* against the current meta-character.
*/
while (mceq(c, mcptr)) {
c = nextch(&curline, &curoff, direct);
n_chars++;
}
}
/* We are now at the character that made us
* fail. Try to match the rest of the pattern.
* Shrink the closure by one for each failure.
*/
mcptr++;
matchlen += n_chars;
for (;;) {
if (n_chars < cl_min) {
matchlen -= n_chars;
return FALSE;
}
nextch(&curline, &curoff, direct ^ REVERSE);
if (amatch(mcptr, direct, &curline, &curoff))
goto success;
matchlen--;
n_chars--;
}
}
else if (mcptr->mc_type == GRPBEG) {
group_reg[mcptr->u.group_no].r_offset = curoff;
group_reg[mcptr->u.group_no].r_linep = curline;
group_reg[mcptr->u.group_no].r_size = (direct == FORWARD)? -matchlen: matchlen;
}
else if (mcptr->mc_type == GRPEND) {
group_len[mcptr->u.group_no] = (direct == FORWARD)? matchlen: -matchlen;
}
else if (mcptr->mc_type == BOL) {
if (curoff != 0)
return FALSE;
}
else if (mcptr->mc_type == EOL) {
if (curoff != llength(curline))
return FALSE;
}
else
{
/* A character to compare against
* the meta-character equal function.
* If we still match, increment the length.
*/
if (!mceq(nextch(&curline, &curoff, direct), mcptr))
return FALSE;
matchlen++;
}
mcptr++;
} /* End of mcptr loop.*/
/* A SUCCESSFULL MATCH!!!
* Reset the "." pointers.
*/
success:
*pcwline = curline;
*pcwoff = curoff;
return (TRUE);
}
#endif
/*
* scanner -- Search for a pattern in either direction. If found,
* reset the "." to be at the start or just after the match string,
* and (perhaps) repaint the display.
* Fast version using simplified version of Boyer and Moore
* Software-Practice and Experience, vol 10, 501-506 (1980)
*/
int PASCAL NEAR scanner(direct, beg_or_end, repeats)
int direct; /* which way to go.*/
int beg_or_end; /* put point at beginning or end of pattern.*/
int repeats; /* search repetitions.*/
{
register int c; /* character at current position */
register char *patptr; /* pointer into pattern */
char *patrn; /* string to scan for */
LINE *curline; /* current line during scan */
int curoff; /* position within current line */
LINE *scanline; /* current line during scanning */
int scanoff; /* position in scanned line */
int jump; /* next offset */
/* If we are going in reverse, then the 'end' is actually
* the beginning of the pattern. Toggle it.
*/
beg_or_end ^= direct;
/* Another directional problem: if we are searching
* forwards, use the pattern in pat[], and the jump
* size in lastchfjump. Otherwise, use the reversed
* pattern in tap[], and the jump size of lastchbjump.
*/
if (direct == FORWARD) {
patrn = &pat[0];
jump = lastchfjump;
}
else
{
patrn = &tap[0];
jump = lastchbjump;
}
/* Set up local pointers to global ".".
*/
curline = curwp->w_dotp;
curoff = curwp->w_doto;
/* Scan each character until we hit the head link record.
* Get the character resolving newlines, offset
* by the pattern length, i.e. the last character of the
* potential match.
*/
if (!fbound(patlenadd, &curline, &curoff, direct)) {
do
{
/* Set up the scanning pointers, and save
* the current position in case we match
* the search string at this point.
*/
scanline = matchline = curline;
scanoff = matchoff = curoff;
patptr = patrn;
/* Scan through the pattern for a match.
*/
while ((c = *patptr++) != '\0')
if (!eq(c, nextch(&scanline, &scanoff, direct)))
goto fail;
/* A SUCCESSFULL MATCH!!!
* Flag that we have moved and reset the
* global "." pointers.
*/
curwp->w_flag |= WFMOVE;
if (beg_or_end == PTEND) /* at end of string */
{
curwp->w_dotp = scanline;
curwp->w_doto = scanoff;
}
else /* at beginning of string */
{
curwp->w_dotp = matchline;
curwp->w_doto = matchoff;
}
/* If we're heading in reverse, set the "match"
* pointers to the start of the string, for savematch().
*/
if (direct == REVERSE) {
matchline = scanline;
matchoff = scanoff;
}
if (savematch() == ABORT)
return ABORT;
/* Continue scanning if we haven't found
* the nth match.
*/
if (--repeats <= 0)
return (TRUE);
else
{
curline = scanline;
curoff = scanoff;
}
fail:; /* continue to search */
} while (!fbound(jump, &curline, &curoff, direct));
}
return FALSE; /* We could not find a match */
}
/*
* fbound -- Return information depending on whether we have hit a boundry
* (and may therefore search no further) or if a trailing character
* of the search string has been found. See boundry() for search
* restrictions.
*/
int PASCAL NEAR fbound(jump, pcurline, pcuroff, dir)
LINE **pcurline;
int *pcuroff, dir, jump;
{
register int spare, curoff;
register LINE *curline;
curline = *pcurline;
curoff = *pcuroff;
if (dir == FORWARD)
{
while (jump != 0) {
#if WINDOW_MSWIN
{
static int o = 0;
if (--o < 0) {
longop(TRUE);
o = 100; /* to lower overhead, only 1/100
calls to longop */
}
}
#endif
curoff += jump;
spare = curoff - llength(curline);
if (curline == curbp->b_linep)
return (TRUE); /* hit end of buffer */
while (spare > 0) {
curline = lforw(curline);/* skip to next line */
curoff = spare - 1;
spare = curoff - llength(curline);
if (curline == curbp->b_linep)
return (TRUE); /* hit end of buffer */
}
if (spare == 0)
jump = deltaf[(int) '\r'];
else
jump = deltaf[(int) lgetc(curline, curoff)];
}
/* The last character matches, so back up to start
* of possible match.
*/
curoff -= patlenadd;
while (curoff < 0) {
curline = lback(curline); /* skip back a line */
curoff += llength(curline) + 1;
}
}
else /* Reverse.*/
{
jump++; /* allow for offset in reverse */
while (jump != 0) {
#if WINDOW_MSWIN
{
static int o = 0;
if (--o < 0) {
longop(TRUE);
o = 100; /* to lower overhead, only 1/100
calls to longop */
}
}
#endif
curoff -= jump;
while (curoff < 0) {
curline = lback(curline); /* skip back a line */
curoff += llength(curline) + 1;
if (curline == curbp->b_linep)
return (TRUE); /* hit end of buffer */
}
if (curoff == llength(curline))
jump = deltab[(int) '\r'];
else
jump = deltab[(int) lgetc(curline, curoff)];
}
/* The last character matches, so back up to start
* of possible match.
*/
curoff += patlenadd + 1;
spare = curoff - llength(curline);
while (spare > 0) {
curline = lforw(curline); /* skip back a line */
curoff = spare - 1;
spare = curoff - llength(curline);
}
}
*pcurline = curline;
*pcuroff = curoff;
return FALSE;
}
/*
* setjtable -- Settting up search delta forward and delta backward
* tables. The reverse search string and string lengths are
* set here, for table initialization and for substitution
* purposes. The default for any character to jump is the
* pattern length.
*/
VOID PASCAL NEAR setjtable()
{
int i;
rvstrcpy(tap, pat);
patlenadd = (matchlen = strlen(pat)) - 1;
for (i = 0; i < HICHAR; i++) {
deltaf[i] = matchlen;
deltab[i] = matchlen;
}
/* Now put in the characters contained
* in the pattern, duplicating the CASE.
*/
for (i = 0; i < patlenadd; i++) {
if (isletter(pat[i]))
deltaf[(unsigned int) chcase((unsigned int) pat[i])]
= patlenadd - i;
deltaf[(unsigned int) pat[i]] = patlenadd - i;
if (isletter(tap[i]))
deltab[(unsigned int) chcase((unsigned int) tap[i])]
= patlenadd - i;
deltab[(unsigned int) tap[i]] = patlenadd - i;
}
/* The last character will have the pattern length
* unless there are duplicates of it. Get the number to
* jump from the arrays delta, and overwrite with zeroes
* in delta duplicating the CASE.
*
* The last character is, of course, the first character
* of the reversed string.
*/
lastchfjump = patlenadd + deltaf[(unsigned int) tap[0]];
lastchbjump = patlenadd + deltab[(unsigned int) pat[0]];
if (isletter(tap[0]))
deltaf[(unsigned int) chcase(tap[0])] = 0;
deltaf[(unsigned int) tap[0]] = 0;
if (isletter(pat[0]))
deltab[(unsigned int) chcase(pat[0])] = 0;
deltab[(unsigned int) pat[0]] = 0;
}
/*
* rvstrcpy -- Reverse string copy.
*/
VOID PASCAL NEAR rvstrcpy(rvstr, str)
register char *rvstr, *str;
{
register int i;
str += (i = strlen(str));
while (i-- > 0)
*rvstr++ = *--str;
*rvstr = '\0';
}
/*
* eq -- Compare two characters. The "bc" comes from the buffer, "pc"
* from the pattern. If we are not in EXACT mode, fold out the case.
*/
int PASCAL NEAR eq(bc, pc)
register int bc;
register int pc;
{
if ((curwp->w_bufp->b_mode & MDEXACT) == 0) {
if (islower(bc))
bc = chcase(bc);
if (islower(pc))
pc = chcase(pc);
}
return (bc == pc);
}
/*
* readpattern -- Read a pattern. Stash it in apat. If it is the
* search string (which means that the global variable pat[]
* has been passed in), create the reverse pattern and the magic
* pattern, assuming we are in MAGIC mode (and #defined that way).
*
* Apat is not updated if the user types in an empty line. If
* the user typed an empty line, and there is no old pattern, it is
* an error. Display the old pattern, in the style of Jeff Lomicka.
* There is some do-it-yourself control expansion. Change to using
* <META> to delimit the end-of-pattern to allow <NL>s in the search
* string.
*/
int PASCAL NEAR readpattern(prompt, apat, srch)
char *prompt;
char apat[];
int srch;
{
register int status;
char tpat[NPAT+20];
strcpy(tpat, prompt); /* copy prompt to output string */
strcat(tpat, " [");
expandp(&apat[0], &tpat[strlen(tpat)], NPAT/2); /* add old pattern */
strcat(tpat, "]");
/* display the proper current search terminator character */
switch (sterm) {
case CTRL | '[': strcat(tpat, "<META>"); break;
case CTRL | 'M': strcat(tpat, "<NL>"); break;
default: strcat(tpat, "<");
cmdstr(sterm, &(tpat[strlen(tpat)]));
strcat(tpat, ">");
}
strcat(tpat, ": ");
/* Read a pattern. Either we get one,
* or we just get the META charater, and use the previous pattern.
* Then, if it's the search string, create the delta tables.
* *Then*, make the meta-pattern, if we are defined that way.
*/
if ((status = mltreply(tpat, tpat, NPAT, sterm)) == TRUE) {
lastflag &= ~CFSRCH;
strcpy(apat, tpat);
if (srch)
setjtable();
}
else if (status == FALSE && apat[0] != 0) /* Old one */
status = TRUE;
#if MAGIC
/* Only make the meta-pattern if in magic mode, since the
* pattern in question might have an invalid meta combination.
*/
if (status == TRUE)
if ((curwp->w_bufp->b_mode & MDMAGIC) == 0) {
mcclear();
rmcclear();
}
else
status = srch? mcstr(): rmcstr();
#endif
return (status);
}
/*
* savematch -- We found the pattern? Let's save it away.
*/
int PASCAL NEAR savematch()
{
register int j;
REGION tmpreg;
if (patmatch != NULL)
free(patmatch);
if ((patmatch = malloc(matchlen + 1)) == NULL) {
mlabort(TEXT94);
/* "%%Out of memory" */
return ABORT;
}
tmpreg.r_offset = matchoff;
tmpreg.r_linep = matchline;
tmpreg.r_size = matchlen;
#if MAGIC == 0
regtostr(patmatch, &tmpreg);
#else
/*
* Save the groups.
*/
grpmatch[0] = regtostr(patmatch, &tmpreg);
for (j = 1; j <= group_count; j++)
{
group_reg[j].r_size += group_len[j];
if (grpmatch[j] != NULL)
free(grpmatch[j]);
if ((grpmatch[j] = malloc(group_reg[j].r_size + 1)) != NULL)
regtostr(grpmatch[j], &group_reg[j]);
else {
mlabort(TEXT94);
/* "%%Out of memory" */
return ABORT;
}
}
#endif
return TRUE;
}
/*
* sreplace -- Search and replace.
*/
int PASCAL NEAR sreplace(f, n)
int f; /* default flag */
int n; /* # of repetitions wanted */
{
return (replaces(FALSE, f, n));
}
/*
* qreplace -- search and replace with query.
*/
int PASCAL NEAR qreplace(f, n)
int f; /* default flag */
int n; /* # of repetitions wanted */
{
return (replaces(TRUE, f, n));
}
/*
* replaces -- Search for a string and replace it with another
* string. Query might be enabled (according to kind).
*/
int PASCAL NEAR replaces(kind, f, n)
int kind; /* Query enabled flag */
int f; /* default flag */
int n; /* # of repetitions wanted */
{
register int status; /* success flag on pattern inputs */
register int nummatch; /* number of found matches */
int numsub; /* number of substitutions */
int nlflag; /* last char of search string a <NL>? */
int nlrepl; /* was a replace done on the last line? */
char c; /* input char for query */
char tpat[NPAT]; /* temporary to hold search pattern */
LINE *origline; /* original "." position */
int origoff; /* and offset (for . query option) */
LINE *lastline; /* position of last replace and */
int lastoff; /* offset (for 'u' query option) */
int oldmatchlen; /* Closure may alter the match length.*/
static char *oldpatmatch = NULL; /* allocated memory for un-do.*/
if (curbp->b_mode & MDVIEW) /* don't allow this command if */
return (rdonly()); /* we are in read only mode */
/* Check for negative repetitions.
*/
if (f && n < 0)
return FALSE;
/* Ask the user for the text of a pattern.
*/
if ((status = readpattern(
(kind == FALSE ? TEXT84 : TEXT85), &pat[0], TRUE)) != TRUE)
/* "Replace" */
/* "Query replace" */
return (status);
/* Ask for the replacement string, and get its length.
*/
if ((status = readpattern(TEXT86, &rpat[0], FALSE)) == ABORT)
/* "with" */
return (status);
/* Set up flags so we can make sure not to do a recursive
* replace on the last line.
*/
nlflag = (pat[matchlen - 1] == '\r');
nlrepl = FALSE;
if (kind) {
/* Build query replace question string.
*/
strcpy(tpat, TEXT87);
/* "Replace '" */
expandp(&pat[0], &tpat[strlen(tpat)], NPAT/3);
strcat(tpat, TEXT88);
/* "' with '" */
expandp(&rpat[0], &tpat[strlen(tpat)], NPAT/3);
strcat(tpat, "'? ");
/* Initialize last replaced pointers.
*/
lastline = NULL;
lastoff = 0;
}
/* Save original . position, reset the number of matches and
* substitutions, and scan through the file.
*/
origline = curwp->w_dotp;
origoff = curwp->w_doto;
numsub = 0;
nummatch = 0;
while ( (f == FALSE || n > nummatch) &&
(nlflag == FALSE || nlrepl == FALSE) ) {
/* Search for the pattern.
* If we search with a regular expression,
* matchlen is reset to the true length of
* the matched string.
*/
#if MAGIC
if (magical && (curwp->w_bufp->b_mode & MDMAGIC)) {
if (!mcscanner(FORWARD, PTBEG, 1))
break;
}
else
#endif
if (!scanner(FORWARD, PTBEG, 1))
break; /* all done */
++nummatch; /* Increment # of matches */
/* Check if we are on the last line.
*/
nlrepl = (lforw(curwp->w_dotp) == curwp->w_bufp->b_linep);
/* Check for query.
*/
if (kind) {
/* Get the query.
*/
pprompt: mlwrite(&tpat[0]);
qprompt:
update(TRUE); /* show the proposed place to change */
c = tgetc(); /* and input */
mlerase(); /* and clear it */
/* And respond appropriately.
*/
switch (c) {
#if FRENCH
case 'o': /* oui, substitute */
case 'O':
#endif
case 'y': /* yes, substitute */
case 'Y':
case ' ':
break;
case 'n': /* no, onward */
case 'N':
forwchar(FALSE, 1);
continue;
case '!': /* yes/stop asking */
kind = FALSE;
break;
case 'u': /* undo last and re-prompt */
case 'U':
/* Restore old position.
*/
if (lastline == NULL) {
/* There is nothing to undo.
*/
TTbeep();
goto pprompt;
}
curwp->w_dotp = lastline;
curwp->w_doto = lastoff;
lastline = NULL;
lastoff = 0;
/* Delete the new string,
* restore the old match.
*/
backchar(FALSE, replen);
status = delins(replen, oldpatmatch, FALSE);
if (status != TRUE)
return (status);
/* Record one less substitution,
* backup, save our place, and
* reprompt.
*/
--numsub;
backchar(FALSE, oldmatchlen);
matchlen = oldmatchlen;
matchline = curwp->w_dotp;
matchoff = curwp->w_doto;
continue;
case '.': /* abort! and return */
/* restore old position */
curwp->w_dotp = origline;
curwp->w_doto = origoff;
curwp->w_flag |= WFMOVE;
case BELL: /* abort! and stay */
mlwrite(TEXT89);
/* "Aborted!" */
return FALSE;
default: /* bitch and beep */
TTbeep();
case '?': /* help me */
mlwrite(TEXT90);
/*"(Y)es, (N)o, (!)Do rest, (U)ndo last, (^G)Abort, (.)Abort back, (?)Help: "*/
goto qprompt;
} /* end of switch */
} /* end of "if kind" */
/* if this is the point origin, flag so we a can reset it */
if (curwp->w_dotp == origline) {
origline = NULL;
lastline = lback(curwp->w_dotp);
}
/* Delete the sucker, and insert its
* replacement.
*/
#if MAGIC
status = delins(matchlen, &rpat[0], rmagical);
#else
status = delins(matchlen, &rpat[0], FALSE);
#endif
if (origline == NULL) {
origline = lforw(lastline);
origoff = 0;
}
if (status != TRUE)
return (status);
numsub++; /* increment # of substitutions */
/* Save our position, the match length, and the string
* matched if we are query-replacing, as we may undo
* the replacement. If we are not querying, check to
* make sure that we didn't replace an empty string
* (possible in MAGIC mode), because we'll infinite loop.
*/
if (kind) {
lastline = curwp->w_dotp;
lastoff = curwp->w_doto;
oldmatchlen = matchlen; /* Save the length for un-do.*/
if (oldpatmatch != NULL)
free(oldpatmatch);
if ((oldpatmatch = malloc(matchlen + 1)) == NULL) {
mlabort(TEXT94);
/* "%%Out of memory" */
return ABORT;
}
strcpy(oldpatmatch, patmatch);
}
else if (matchlen == 0) {
mlwrite(TEXT91);
/* "Empty string replaced, stopping." */
return FALSE;
}
}
/* And report the results.
*/
mlwrite(TEXT92, numsub);
/* "%d substitutions" */
return (TRUE);
}
/*
* delins -- Delete a specified length from the current point
* then either insert the string directly, or make use of
* replacement meta-array.
*/
int PASCAL NEAR delins(dlength, instr, use_rmc)
int dlength;
char *instr;
int use_rmc;
{
register int status;
register char *rstr;
#if MAGIC
register RMC *rmcptr;
#endif
replen = 0;
/* Zap what we gotta,
* and insert its replacement.
*/
if ((status = ldelete((long) dlength, FALSE)) != TRUE)
mlwrite(TEXT93);
/* "%%ERROR while deleting" */
else
#if MAGIC
if (use_rmc && (curwp->w_bufp->b_mode & MDMAGIC)) {
rmcptr = &rmcpat[0];
while (rmcptr->mc_type != MCNIL && status == TRUE) {
if (rmcptr->mc_type == LITCHAR)
status = linstr(rstr = rmcptr->u.rstr);
else if (rmcptr->mc_type == DITTO)
status = linstr(rstr = patmatch);
else
status = linstr(rstr = fixnull(grpmatch[rmcptr->u.group_no]));
replen += strlen(rstr);
rmcptr++;
}
}
else
#endif
{
status = linstr(instr);
replen = strlen(instr);
}
return (status);
}
/*
* expandp -- Expand control key sequences for output.
*/
int PASCAL NEAR expandp(srcstr, deststr, maxlength)
char *srcstr; /* string to expand */
char *deststr; /* destination of expanded string */
int maxlength; /* maximum chars in destination */
{
unsigned char c; /* current char to translate */
/* Scan through the string.
*/
while ((c = *srcstr++) != 0) {
if (c == '\r') /* it's a newline */
{
*deststr++ = '<';
*deststr++ = 'N';
*deststr++ = 'L';
*deststr++ = '>';
maxlength -= 4;
}
else if (c < 0x20 || c == 0x7f) /* control character */
{
*deststr++ = '^';
*deststr++ = c ^ 0x40;
maxlength -= 2;
}
else if (c == '%') {
*deststr++ = '%';
*deststr++ = '%';
maxlength -= 2;
}
else /* any other character */
{
*deststr++ = c;
maxlength--;
}
/* check for maxlength */
if (maxlength < 4) {
*deststr++ = '$';
*deststr = '\0';
return FALSE;
}
}
*deststr = '\0';
return (TRUE);
}
/*
* boundry -- Return information depending on whether we may search no
* further. Beginning of file and end of file are the obvious
* cases, but we may want to add further optional boundry restrictions
* in future, a' la VMS EDT. At the moment, just return (TRUE) or
* FALSE depending on if a boundry is hit (ouch).
*/
int PASCAL NEAR boundry(curline, curoff, dir)
LINE *curline;
int curoff, dir;
{
register int border;
if (dir == FORWARD) {
border = (curoff == llength(curline)) &&
(lforw(curline) == curbp->b_linep);
}
else
{
border = (curoff == 0) &&
(lback(curline) == curbp->b_linep);
}
return (border);
}
/*
* nextch -- retrieve the next/previous character in the buffer,
* and advance/retreat the point.
* The order in which this is done is significant, and depends
* upon the direction of the search. Forward searches look at
* the current character and move, reverse searches move and
* look at the character.
*/
int PASCAL NEAR nextch(pcurline, pcuroff, dir)
LINE **pcurline;
int *pcuroff;
int dir;
{
register LINE *curline;
register int curoff;
register int c;
curline = *pcurline;
curoff = *pcuroff;
if (dir == FORWARD) {
if (curoff == llength(curline)) /* if at EOL */
{
curline = lforw(curline); /* skip to next line */
curoff = 0;
c = '\r'; /* and return a <NL> */
}
else
c = lgetc(curline, curoff++); /* get the char */
}
else /* Reverse.*/
{
if (curoff == 0) {
curline = lback(curline);
curoff = llength(curline);
c = '\r';
}
else
c = lgetc(curline, --curoff);
}
*pcurline = curline;
*pcuroff = curoff;
return (c);
}
#if MAGIC
/*
* mcstr -- Set up the 'magic' array. The closure symbol is taken as
* a literal character when (1) it is the first character in the
* pattern, and (2) when preceded by a symbol that does not allow
* closure, such as beginning or end of line symbol, or another
* closure symbol.
*
* Coding comment (jmg): yes, i know i have gotos that are, strictly
* speaking, unnecessary. But right now we are so cramped for
* code space that i will grab what i can in order to remain
* within the 64K limit. C compilers actually do very little
* in the way of optimizing - they expect you to do that.
*/
int PASCAL NEAR mcstr()
{
MC *mcptr, *rtpcm;
char *patptr;
int pchr;
int status = TRUE;
int does_closure = FALSE;
int mj = 0;
int group_stack[MAXGROUPS];
int stacklevel = 0;
/* If we had metacharacters in the MC array previously,
* free up any bitmaps that may have been allocated, and
* reset magical.
*/
if (magical)
mcclear();
mcptr = &mcpat[0];
patptr = &pat[0];
while ((pchr = *patptr) && status) {
switch (pchr) {
case MC_CCL:
status = cclmake(&patptr, mcptr);
magical = TRUE;
does_closure = TRUE;
break;
case MC_BOL:
/* If the BOL character isn't the
* first in the pattern, we assume
* it's a literal instead.
*/
if (mj != 0)
goto litcase;
mcptr->mc_type = BOL;
magical = TRUE;
break;
case MC_EOL:
/* If the EOL character isn't the
* last in the pattern, we assume
* it's a literal instead.
*/
if (*(patptr + 1) != '\0')
goto litcase;
mcptr->mc_type = EOL;
magical = TRUE;
break;
case MC_ANY:
mcptr->mc_type = ANY;
magical = TRUE;
does_closure = TRUE;
break;
case MC_CLOSURE:
case MC_CLOSURE_1:
case MC_ZEROONE:
/* Does the closure symbol mean closure here?
* If so, back up to the previous element
* and indicate it is enclosed.
*/
if (does_closure == FALSE)
goto litcase;
mj--;
mcptr--;
if (pchr == MC_CLOSURE)
mcptr->mc_type |= CLOSURE;
else if (pchr == MC_CLOSURE_1)
mcptr->mc_type |= CLOSURE_1;
else
mcptr->mc_type |= ZEROONE;
magical = TRUE;
does_closure = FALSE;
break;
case MC_ESC:
/* No break between here and LITCHAR if
* the next character is to be taken literally.
*/
magical = TRUE;
pchr = *++patptr;
if (pchr == MC_GRPBEG) {
/* Start of a group. Indicate it, and
* set magical.
*/
if (++group_count < MAXGROUPS) {
mcptr->mc_type = GRPBEG;
mcptr->u.group_no = group_count;
group_stack[stacklevel++] = group_count;
does_closure = FALSE;
}
else
{
mlwrite(TEXT221);
/* "Too many groups" */
status = FALSE;
}
break;
}
else if (pchr == MC_GRPEND) {
/* If we've no groups to close, assume
* a literal character. Otherwise,
* indicate the end of a group.
*/
if (stacklevel > 0) {
mcptr->u.group_no = group_stack[--stacklevel];
mcptr->mc_type = GRPEND;
does_closure = FALSE;
break;
}
}
else if (pchr == '\0') {
pchr = '\\';
--patptr;
}
default:
litcase: mcptr->mc_type = LITCHAR;
mcptr->u.lchar = pchr;
does_closure = TRUE;
break;
} /* End of switch.*/
mcptr++;
patptr++;
mj++;
} /* End of while.*/
/* Close off the meta-string, then set up the reverse array,
* if the status is good.
*
* The beginning and end of groups are reversed by flipping
* the bits, if they are set.
*
* If the status is not good, nil out the meta-pattern.
* Even if the status is bad from the cclmake() routine,
* the bitmap for that member is guaranteed to be freed.
* So we stomp a MCNIL value there, and call mcclear()
* to free any other bitmaps.
*
* Please note the structure assignment - your compiler may
* not like that.
*/
mcptr->mc_type = MCNIL;
if (stacklevel) {
status = FALSE;
mlwrite(TEXT222);
/* "Group not ended" */
}
if (status) {
rtpcm = &tapcm[0];
while (--mj >= 0) {
#if LATTICE
movmem(--mcptr, rtpcm, sizeof (MC));
#else
*rtpcm = *--mcptr;
#endif
rtpcm++;
}
rtpcm->mc_type = MCNIL;
}
else
{
(--mcptr)->mc_type = MCNIL;
mcclear();
}
return (status);
}
/*
* rmcstr -- Set up the replacement 'magic' array. Note that if there
* are no meta-characters encountered in the replacement string,
* the array is never actually created - we will just use the
* character array rpat[] as the replacement string.
*/
int PASCAL NEAR rmcstr()
{
RMC *rmcptr;
char *patptr;
int pchr;
int status = TRUE;
int mj;
patptr = &rpat[0];
rmcptr = &rmcpat[0];
mj = 0;
rmagical = FALSE;
while (*patptr && status == TRUE) {
switch (*patptr) {
case MC_DITTO:
/* If there were non-magical characters in
* the string before reaching this character
* plunk it in the replacement array before
* processing the current meta-character.
*/
if (mj != 0) {
rmcptr->mc_type = LITCHAR;
if ((rmcptr->u.rstr = malloc(mj + 1)) == NULL) {
mlabort(TEXT94);
/* "%%Out of memory" */
status = FALSE;
break;
}
bytecopy(rmcptr->u.rstr, patptr - mj, mj);
rmcptr++;
mj = 0;
}
rmcptr->mc_type = DITTO;
rmcptr++;
rmagical = TRUE;
break;
case MC_ESC:
pchr = *(patptr + 1); /* peek at next char.*/
if (pchr <= '9' && pchr >= '1') {
if (mj != 0) {
rmcptr->mc_type = LITCHAR;
if ((rmcptr->u.rstr = malloc(mj + 1)) == NULL) {
mlabort(TEXT94);
/* "%%Out of memory" */
status = FALSE;
break;
}
bytecopy(rmcptr->u.rstr, patptr - mj, mj);
rmcptr++;
mj = 0;
}
rmcptr->mc_type = GROUP;
rmcptr->u.group_no = pchr - '0';
patptr++;
}
else
{
rmcptr->mc_type = LITCHAR;
/* We malloc mj plus two here, instead
* of one, because we have to count the
* current character.
*/
if ((rmcptr->u.rstr = malloc(mj + 2)) == NULL) {
mlabort(TEXT94);
/* "%%Out of memory" */
status = FALSE;
break;
}
bytecopy(rmcptr->u.rstr, patptr - mj, mj + 1);
/* If MC_ESC is not the last character
* in the string, find out what it is
* escaping, and overwrite the last
* character with it.
*/
if (pchr != '\0') {
*((rmcptr->u.rstr) + mj) = pchr;
patptr++;
}
mj = 0;
}
rmcptr++;
rmagical = TRUE;
break;
default:
mj++;
}
patptr++;
}
if (rmagical && mj > 0) {
rmcptr->mc_type = LITCHAR;
if ((rmcptr->u.rstr = malloc(mj + 1)) == NULL) {
mlabort(TEXT94);
/* "%%Out of memory" */
status = FALSE;
}
bytecopy(rmcptr->u.rstr, patptr - mj, mj);
rmcptr++;
}
rmcptr->mc_type = MCNIL;
return (status);
}
/*
* mcclear -- Free up any CCL bitmaps, and MCNIL the MC search arrays.
*/
VOID PASCAL NEAR mcclear()
{
register MC *mcptr;
register int j;
mcptr = &mcpat[0];
while (mcptr->mc_type != MCNIL) {
if ((mcptr->mc_type == CCL) || (mcptr->mc_type == NCCL))
free(mcptr->u.cclmap);
mcptr++;
}
mcpat[0].mc_type = tapcm[0].mc_type = MCNIL;
/*
* Remember that grpmatch[0] == patmatch.
*/
for (j = 0; j < MAXGROUPS; j++) {
if (grpmatch[j] != NULL) {
free(grpmatch[j]);
grpmatch[j] = NULL;
}
}
patmatch = NULL;
group_count = 0;
magical = FALSE;
}
/*
* rmcclear -- Free up any strings, and MCNIL the RMC array.
*/
VOID PASCAL NEAR rmcclear()
{
register RMC *rmcptr;
register int j;
rmcptr = &rmcpat[0];
while (rmcptr->mc_type != MCNIL) {
if (rmcptr->mc_type == LITCHAR)
free(rmcptr->u.rstr);
rmcptr++;
}
rmcpat[0].mc_type = MCNIL;
}
/*
* mceq -- meta-character equality with a character. In Kernighan & Plauger's
* Software Tools, this is the function omatch(), but i felt there were
* too many functions with the 'match' name already.
*/
int PASCAL NEAR mceq(bc, mt)
int bc;
MC *mt;
{
register int result;
switch (mt->mc_type & MASKCLO) {
case LITCHAR:
result = eq(bc, (int) mt->u.lchar);
break;
case ANY:
result = (bc != '\r');
break;
case CCL:
if (!(result = biteq(bc, mt->u.cclmap))) {
if ((curwp->w_bufp->b_mode & MDEXACT) == 0 &&
(isletter(bc))) {
result = biteq(chcase(bc), mt->u.cclmap);
}
}
break;
case NCCL:
result = !biteq(bc, mt->u.cclmap);
if ((curwp->w_bufp->b_mode & MDEXACT) == 0 &&
(isletter(bc))) {
result &= !biteq(chcase(bc), mt->u.cclmap);
}
break;
default:
mlwrite(TEXT95, mt->mc_type);
/* "%%mceq: what is %d?" */
result = FALSE;
break;
} /* End of switch.*/
return (result);
}
/*
* cclmake -- create the bitmap for the character class.
* ppatptr is left pointing to the end-of-character-class character,
* so that a loop may automatically increment with safety.
*/
int PASCAL NEAR cclmake(ppatptr, mcptr)
char **ppatptr;
MC *mcptr;
{
BITMAP bmap;
register char *patptr;
register int pchr, ochr;
#if OS2
if ((bmap = (BITMAP) malloc(HIBYTE_)) == NULL) {
mlwrite(TEXT94);
/* "%%Out of memory" */
return FALSE;
}
memset(bmap, 0, HIBYTE_);
#else
if ((bmap = (BITMAP) malloc(HIBYTE)) == NULL) {
mlabort(TEXT94);
/* "%%Out of memory" */
return FALSE;
}
memset(bmap, 0, HIBYTE);
#endif
mcptr->u.cclmap = bmap;
patptr = *ppatptr;
ochr = MC_CCL;
/*
* Test the initial character(s) in ccl for
* special cases - negate ccl, or an end ccl
* character as a first character. Anything
* else gets set in the bitmap.
*/
if (*++patptr == MC_NCCL) {
patptr++;
mcptr->mc_type = NCCL;
}
else
mcptr->mc_type = CCL;
if ((pchr = *patptr) == MC_ECCL) {
mlwrite(TEXT96);
/* "%%No characters in character class" */
free(bmap);
return FALSE;
}
while (pchr != MC_ECCL && pchr != '\0') {
switch (pchr) {
/* Range character loses its meaning if it is
* the first or last character in the class. We
* also treat it as un-ordinary if the order is
* wrong, e.g. "z-a".
*/
case MC_RCCL:
pchr = *(patptr + 1);
if (ochr == MC_CCL || pchr == MC_ECCL ||
ochr > pchr)
setbit(MC_RCCL, bmap);
else
{
do {
setbit(++ochr, bmap);
} while (ochr < pchr);
patptr++;
}
break;
/* Note: no break between case MC_ESC and the default.
*/
case MC_ESC:
pchr = *++patptr;
default:
setbit(pchr, bmap);
break;
}
ochr = pchr;
pchr = *++patptr;
}
*ppatptr = patptr;
if (pchr == '\0') {
mlwrite(TEXT97);
/* "%%Character class not ended" */
free(bmap);
return FALSE;
}
return TRUE;
}
/*
* biteq -- is the character in the bitmap?
*/
int PASCAL NEAR biteq(bc, cclmap)
int bc;
BITMAP cclmap;
{
if (bc >= HICHAR)
return FALSE;
return ( (*(cclmap + (bc >> 3)) & BIT(bc & 7))? TRUE: FALSE );
}
/*
* setbit -- Set a bit (ON only) in the bitmap.
*/
VOID PASCAL NEAR setbit(bc, cclmap)
int bc;
BITMAP cclmap;
{
if (bc < HICHAR)
*(cclmap + (bc >> 3)) |= BIT(bc & 7);
}
#endif