/* 
 * mxSearch.c --
 *
 *	This file provides searching primitives that work in
 *	conjunction with files stored by mxFile.c.  The procedures
 *	here provide facilities like pattern search, parenthesis
 *	matching, and tag lookup.
 *
 * Copyright (C) 1986, 1987, 1988 Regents of the University of California
 * Permission to use, copy, modify, and distribute this
 * software and its documentation for any purpose and without
 * fee is hereby granted, provided that the above copyright
 * notice appear in all copies.  The University of California
 * makes no representations about the suitability of this
 * software for any purpose.  It is provided "as is" without
 * express or implied warranty.
 */

#ifndef lint
static char rcsid[] = "$Header: /sprite/src/lib/mx/RCS/mxSearch.c,v 1.13 89/10/16 17:28:24 shirriff Exp $ SPRITE (Berkeley)";
#endif not lint

#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "mxInt.h"
#include "regex.h"

#define REGEXPBUFSIZE 100

static struct re_pattern_buffer *regExpBufPtr = NULL;
static struct re_registers	regs;
char				*regOldLine = NULL;


/*
 *----------------------------------------------------------------------
 *
 * Mx_SearchPattern --
 *
 *	Search for a given pattern in a given file.
 *
 * Results:
 *	1 is returned if there is a range of characters in file
 *	that matches pattern, and if the first character in that range
 *	lies between start and stop, inclusive.  In this case, *firstPtr
 *	and *lastPtr are filled in with the location of the matching
 *	range that is closest to start.  If no matching range could be
 *	found then 0 is returned and *firstPtr and *lastPtr are
 *	unmodified.
 *
 * Matching:
 *	Right now, only exact matches are permitted:  no wild-cards.
 *	Furthermore, the match must be within a single line.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

int
Mx_SearchPattern(file, start, stop, pattern, firstPtr, lastPtr)
    Mx_File file;		/* File in which to search. */
    Mx_Position start;		/* First position to check for match. */
    Mx_Position stop;		/* Last position to check for match. */
    char *pattern;		/* String giving pattern to match against. */
    Mx_Position *firstPtr;	/* Filled in with position of first character
				 * in range that matched pattern. */
    Mx_Position *lastPtr;	/* Filled in with position of last character
				 * in range that matched pattern. */
{
    char *line;
    register char firstChar, curChar, *p;
    int stringLength, lineLength;

    firstChar = *pattern;
    stringLength = strlen(pattern);
    if (MX_POS_LEQ(start, stop)) {

	/*
	 * Work forwards through the file one line at a time.  For
	 * each line, search for characters matching the first character
	 * of the pattern;  if that matches, then check the rest of
	 * the pattern.
	 */

	while (1) {
	    line = Mx_GetLine(file, start.lineIndex, &lineLength);
	    if ((line == NULL) || (start.lineIndex > stop.lineIndex)) {
		return 0;
	    }
	    for (p = &line[start.charIndex], curChar = *p;
		    curChar != 0; p++, curChar = *p, start.charIndex++) {
		if ((curChar == firstChar)
			&& (strncmp(&line[start.charIndex], pattern,
				stringLength) == 0)) {
		    if (MX_POS_LEQ(start, stop)) {
			goto gotMatch;
		    } else {
			return 0;
		    }
		}
	    }
	    start.charIndex = 0;
	    start.lineIndex++;
	}
    } else {

	/*
	 * Work backwards through the file one line at a time.  For
	 * each line, search for characters matching the first character
	 * of the pattern;  if that matches, then check the rest of
	 * the pattern.
	 */

	line = Mx_GetLine(file, start.lineIndex, &lineLength);
	while (1) {
	    if ((line == NULL) || (start.lineIndex < stop.lineIndex)) {
		return 0;
	    }
	    for (p = &line[start.charIndex], curChar = *p;
		    p >= line; p--, curChar = *p, start.charIndex--) {
		if ((curChar == firstChar)
			&& (strncmp(&line[start.charIndex], pattern,
				stringLength) == 0)) {
		    if (MX_POS_LEQ(stop, start)) {
			goto gotMatch;
		    } else {
			return 0;
		    }
		}
	    }
	    start.lineIndex--;
	    line = Mx_GetLine(file, start.lineIndex, &lineLength);
	    start.charIndex = lineLength - 1;
	}
    }

    gotMatch:
    *firstPtr = start;
    lastPtr->lineIndex = start.lineIndex;
    lastPtr->charIndex = start.charIndex + stringLength - 1;
    return 1;
}


/*
 *----------------------------------------------------------------------
 *
 * Mx_CompileRegExp --
 *
 *	Compile a regular expression into a search pattern.
 *
 * Results:
 *	Returns NULL, or an error string if there were an error.
 *
 * Side effects:
 *
 *	regExpBuf is initialized to hold the pattern.
 *
 *----------------------------------------------------------------------
 */

char *
Mx_CompileRegExp(pattern)
    char *pattern;		/* The regular expression pattern */
{
    char *errStr;

    if (regExpBufPtr==NULL) {
	/* allocate the regular expresion buffers */
	regExpBufPtr = (struct re_pattern_buffer *) 
	      malloc(sizeof(struct re_pattern_buffer));
	regExpBufPtr->allocated = REGEXPBUFSIZE;
	regExpBufPtr->buffer = (char *)malloc((unsigned)REGEXPBUFSIZE);
	regExpBufPtr->fastmap = (char *)malloc((unsigned)256);
	regExpBufPtr->translate = NULL;
    }

    errStr = re_compile_pattern(pattern,strlen(pattern),regExpBufPtr);
    re_compile_fastmap(regExpBufPtr);
    return errStr;
}

/*
 *----------------------------------------------------------------------
 *
 * Mx_SearchRegExp --
 *
 *	Search for a given regular expression in a given file.
 *	Before calling this routine, Mx_CompileRegExp must be called.
 *
 * Results:
 *	1 is returned if there is a range of characters in file
 *	that matches pattern, and if the first character in that range
 *	lies between start and stop, inclusive.  In this case, *firstPtr
 *	and *lastPtr are filled in with the location of the matching
 *	range that is closest to start.  If no matching range could be
 *	found then 0 is returned and *firstPtr and *lastPtr are
 *	unmodified.
 *
 * Matching:
 *	Vi-style regular expressions are used.
 *
 * Side effects:
 *	The variable regs keeps track of the pattern found.
 *
 *----------------------------------------------------------------------
 */

int
Mx_SearchRegExp(file, start, stop, firstPtr, lastPtr)
    Mx_File file;		/* File in which to search. */
    Mx_Position start;		/* First position to check for match. */
    Mx_Position stop;		/* Last position to check for match. */
    Mx_Position *firstPtr;	/* Filled in with position of first character
				 * in range that matched pattern. */
    Mx_Position *lastPtr;	/* Filled in with position of last character
				 * in range that matched pattern. */
{
    char *line;
    int lineLength, searchLength, searchVal;

    if (MX_POS_LEQ(start, stop)) {

	/*
	 * Work forwards through the file one line at a time.  For
	 * each line, search for characters matching the first character
	 * of the pattern;  if that matches, then check the rest of
	 * the pattern.
	 */

	while (1) {
	    line = Mx_GetLine(file, start.lineIndex, &lineLength);
	    lineLength--;
	    if ((line == NULL) || (start.lineIndex > stop.lineIndex)) {
		return 0;
	    }
	    /*
	     * We want to search from start.charIndex up to the end of
	     * the string, or to stop.charIndex if we're on that line
	     * and it's shorter.
	     */
	    searchLength = lineLength-start.charIndex-1;
	    if (start.lineIndex == stop.lineIndex &&
		    stop.charIndex<lineLength) {
		searchLength = stop.charIndex-start.charIndex;
		
	    }
	    if (searchLength<0) {
		searchLength = 0;
	    }
	    searchVal = re_search(regExpBufPtr, line, lineLength,
		    start.charIndex < 0 ? 0 : start.charIndex, searchLength,
		    &regs);

	    if (searchVal != -1) {
		goto gotMatch;
	    }

	    start.charIndex = 0;
	    start.lineIndex++;
	}
    } else {

	/*
	 * Work backwards through the file one line at a time.  For
	 * each line, search for characters matching the first character
	 * of the pattern;  if that matches, then check the rest of
	 * the pattern.
	 */

	line = Mx_GetLine(file, start.lineIndex, &lineLength);
	lineLength--;
	while (1) {
	    if ((line == NULL) || (start.lineIndex < stop.lineIndex)) {
		return 0;
	    }
	    /*
	     * We want to search from start.charIndex back to the
	     * beginning of the string, or to stop.charIndex if
	     * we're on that line and it's shorter.
	     */
	    if (start.lineIndex == stop.lineIndex) {
		if (stop.charIndex <= start.charIndex) {
		    searchLength = start.charIndex-stop.charIndex;
		} else {
		    return 0;
		}
	    } else {
		searchLength = start.charIndex;
	    }
	    searchVal = re_search(regExpBufPtr, line, lineLength,
		    start.charIndex < 0 ? 0 : start.charIndex, -searchLength,
		    &regs);

	    if (searchVal != -1) {
		goto gotMatch;
	    }

	    start.lineIndex--;
	    line = Mx_GetLine(file, start.lineIndex, &lineLength);
	    lineLength--;
	    start.charIndex = lineLength-1;
	}
    }

    gotMatch:
    if (searchVal == -2) {
	/*
	 * Error in searching
	 */
	return -1;
    }
    firstPtr->lineIndex = start.lineIndex;
    firstPtr->charIndex = regs.start[0];
    lastPtr->lineIndex = start.lineIndex;
    lastPtr->charIndex = regs.end[0]-1;
    if (regOldLine != NULL) {
	free(regOldLine);
    }
    regOldLine = (char *)malloc((unsigned)strlen(line)+1);
    strcpy(regOldLine,line);
    return 1;
}

/*
 * Macro to substitute length "len" of "str" into "file"
 * side effects: file is modified and pos is updated.
 */
#define INSERT(str,len) \
      (ch = (str)[len], (str)[len]='\0',\
       Mx_ReplaceBytes(mxwPtr->fileInfoPtr->file,linePos,linePos,(str)),\
       linePos = Mx_Offset(mxwPtr->fileInfoPtr->file,linePos,(len)),\
       (str)[len]=ch)

/*
 *----------------------------------------------------------------------
 *
 * Mx_ReplaceRegExp --
 *
 *	Do a regular expression replacement, handling metacharacters.
 *
 * Results:
 *	The new starting point for the next search, newStart,  is set
 *	the position just after the replaced text.
 *
 * Side effects:
 *	The file is modified.
 *	The pattern matching positions are stored in regs.
 *
 *----------------------------------------------------------------------
 */

void
Mx_ReplaceRegExp(mxwPtr,first,last,replace,newStart)
    MxWindow	*mxwPtr; 	/* Window in which to replace. */
    Mx_Position first;		/* First position for replace. */
    Mx_Position last;		/* Last position for replace. */
    char	*replace;	/* Replacement string. */
    Mx_Position	*newStart;	/* New starting search position. */
{
    char *repCur;                       /* Current position in replacement. */
    char *repNext;                      /* Next position in replacement. */
    char *repEnd;                       /* End of replacement. */
    char ch;                            /* Temporary character. */
    Mx_Position linePos;                /* Position in the line. */
    int n;                              /* Match buffer number. */

    if (!MX_POS_LESS(last,first)) {
	/*
	 * erase the substituted string
	 */
	Mx_ReplaceBytes(mxwPtr->fileInfoPtr->file, first,
		Mx_Offset(mxwPtr->fileInfoPtr->file, last, 1),
		"");
    }
    linePos = first;

    /*
     * The following regular expression replacement code
     * follows that in sed.c
     */

    repEnd = replace+strlen(replace);
    for (repNext=repCur=replace; repNext<repEnd; repNext++) {
	if (*repNext == '&') {
	    if (repNext != repCur) {
	       INSERT(repCur,repNext-repCur);
	    }
	   INSERT(regOldLine+regs.start[0],regs.end[0]-regs.start[0]);
	   repCur = repNext + 1;
	} else if (*repNext == '\\') {
	    if (repNext != repCur) {
		INSERT(repCur,repNext-repCur);
	    }
	    repNext++;
	    if (repNext != repEnd) {
		if (isdigit(*repNext)) {
		    n = *repNext - '0';
		    if (regs.start[n] >= 0) {
			INSERT(regOldLine+regs.start[n],
			      regs.end[n]-regs.start[n]);
		    }
		} else {
		    INSERT(repNext,1);
		}
	    }
	    repCur = repNext + 1;
	}
    }
    if (repNext != repCur) {
	INSERT(repCur,repNext-repCur);
    }
    if (regs.end[0] == regs.start[0]) {
	*newStart = Mx_Offset(mxwPtr->fileInfoPtr->file, linePos, 1);
    } else {
	*newStart = linePos;
    }
}


/*
 *----------------------------------------------------------------------
 *
 * Mx_SearchMask --
 *
 *	Find the farthest extent of a range of characters all of
 *	which are selected by a given mask.
 *
 * Results:
 *	If the character at start isn't in mask, then 0 is returned
 *	and *lastPtr isn't modified.  Otherwise, 1 is returned and
 *	*lastPtr is filled with the location of the character between
 *	start and stop (inclusive), but farthest from start, such that
 *	all the characters between it and start, inclusive, are selected
 *	by mask.  
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

int
Mx_SearchMask(file, start, stop, mask, lastPtr)
    Mx_File file;		/* File in which to search. */
    Mx_Position start;		/* Starting position for search. */
    Mx_Position stop;		/* Last character to consider in search.  May
				 * be less than start. */
    register char mask[16];	/* For each ASCII character i, bit (i&07) of
				 * mask[i>>3] indicates whether i is a
				 * desired character. */
    Mx_Position *lastPtr;	/* Filled in with end of range of characters
				 * in mask. */
{
    static char bit[8] = {0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80};
    char *line;
    int lineLength, increment;
    register char c;
    Mx_Position cur;

    /*
     * Search through the file one character at a time from start to stop.
     */
    
    cur = start;
    if (MX_POS_LEQ(start, stop)) {
	increment = 1;
    } else {
	increment = -1;
    }
    line = Mx_GetLine(file, cur.lineIndex, &lineLength);
    while (line != NULL) {
	c = line[cur.charIndex];
	if ((c & 0200) || (!(mask[c>>3] & bit[c&07]))) {
	    if (MX_POS_EQUAL(cur, start)) {
		return 0;
	    }
	    *lastPtr = Mx_Offset(file, cur, -increment);
	    return 1;
	}
	if (MX_POS_EQUAL(cur, stop)) {
	    *lastPtr = stop;
	    return 1;
	}
	cur.charIndex += increment;
	if (cur.charIndex >= lineLength) {
	    cur.charIndex = 0;
	    cur.lineIndex++;
	    line = Mx_GetLine(file, cur.lineIndex, &lineLength);
	} else if (cur.charIndex < 0) {
	    cur.lineIndex--;
	    line = Mx_GetLine(file, cur.lineIndex, &lineLength);
	    cur.charIndex = lineLength - 1;
	}
    }
    return 0;			/* Error: weird line. */
}

/*
 *----------------------------------------------------------------------
 *
 * Mx_SearchWord --
 *
 *	See if a particular position falls within a particular word.
 *	If so, return the total range of the word.
 *
 * Results:
 *	1 is returned if pos (in file) points within a range of
 *	characters that matches word exactly (the match must occur
 *	entirely on one line of the file).  In this case, *firstPtr
 *	and *lastPtr are filled in with the endpoints of the range.
 *	If pos doesn't point to a matching word then 0 is returned
 *	and *firstPtr and *lastPtr aren't modified.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

int
Mx_SearchWord(file, pos, word, firstPtr, lastPtr)
    Mx_File file;		/* File in which to search. */
    Mx_Position pos;		/* Position that is suspected to point to
				 * a word. */
    char *word;			/* Does pos point to this? */
    Mx_Position *firstPtr;	/* If pos does point to word, fill in this
				 * with beginning of word. */
    Mx_Position *lastPtr;	/* Ditto for end of word. */
{
    char *line, c, *occurrence;
    int dummyLength, wordLength, ix;

    /*
     * Get the line of the file, then iterate over all the occurrences
     * of the character at pos within word.  For each occurrence, check
     * for a match that would make pos correspond to that occurrence.
     */
    
    line = Mx_GetLine(file, pos.lineIndex, &dummyLength);
    if (line == NULL) {
	return 0;
    }
    c = line[pos.charIndex];
    occurrence = word;
    wordLength = strlen(word);
    while (1) {
	occurrence = strchr(occurrence, c);
	if (occurrence == NULL) {
	    return 0;
	}
	ix = pos.charIndex - (occurrence - word);
	if (ix < 0) {
	    return 0;
	}
	if (strncmp(word, &line[ix], wordLength) == 0) {
	    firstPtr->lineIndex = lastPtr->lineIndex = pos.lineIndex;
	    firstPtr->charIndex = ix;
	    lastPtr->charIndex = ix + wordLength - 1;
	    return 1;
	}
	occurrence++;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * Mx_SearchParen --
 *
 *	Search for matching parentheses.
 *
 * Results:
 *	Opens and closes indicate what constitute open and closing
 *	parentheses.  Each word in openParens, and the corresponding word
 *	in closeParens, constitute a pair of parentheses (the "parentheses"
 *	may be arbitrary words).  If pos points to a valid parenthesis,
 *	then 1 is returned and *open1Ptr, *open2Ptr, *close1Ptr, and
 *	*close2Ptr are filled in with the ranges of the parenthesis
 *	and its match (nesting is handled properly).  If no match could
 *	be found, then *open1Ptr and *close1Ptr will be equal, as will
 *	*open2Ptr and *close2Ptr.  If pos doesn't point to a parenthesis
 *	then 0 is returned and the positions aren't filled in.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

int
Mx_SearchParen(file, pos, start, stop, openParens, closeParens,
	open1Ptr, open2Ptr, close1Ptr, close2Ptr)
    Mx_File file;		/* File in which to search. */
    Mx_Position pos;		/* Position in file. */
    Mx_Position start, stop;	/* Range to search for matching parenthesis.
				 * Must bracket pos.  Only matching parens in
				 * this range will be returned. */
    char **openParens;		/* Array of strings giving opening words
				 * for "parentheses".  End of array is
				 * indicated by NULL pointer. */
    char **closeParens;		/* Array of strings giving closing words
				 * for "parentheses".  End of array is
				 * indicated by NULL pointer. */
    Mx_Position *open1Ptr;	/* Filled in with first character in range
				 * of open parenthesis. */
    Mx_Position *open2Ptr;	/* Filled in with last character in range
				 * of open parenthesis. */
    Mx_Position *close1Ptr;	/* Filled in with first character in range
				 * of close parenthesis. */
    Mx_Position *close2Ptr;	/* Filled in with last character in range
				 * of close parenthesis. */
{
    int i, lineLength, count;
    register char openChar, closeChar, curChar, *p;
    char *line;
    
    /*
     * Search through all the open and close strings, seeing if the
     * indicated position falls in a parenthesis.
     */
    
    for (i = 0; openParens[i] != NULL ; i++) {
	if (Mx_SearchWord(file, pos, openParens[i], open1Ptr, open2Ptr)) {

	    /*
	     * Got an open parenthesis.  Search forward through the
	     * file, one line at a time for the matching close parenthesis.
	     */
	    
	    count = 1;
	    pos.charIndex++;
	    openChar = *(openParens[i]);
	    closeChar = *(closeParens[i]);
	    while (1) {
		line = Mx_GetLine(file, pos.lineIndex, &lineLength);
		if ((line == NULL) || MX_POS_LESS(stop, pos)) {
		    noMatchingClose:
		    *close1Ptr = *open1Ptr;
		    *close2Ptr = *open2Ptr;
		    return 1;
		}
		for (p = &line[pos.charIndex], curChar = *p;
			curChar != 0; p++, curChar = *p, pos.charIndex++) {
		    if ((curChar == openChar) && Mx_SearchWord(file, pos,
			    openParens[i], close1Ptr, close2Ptr)) {
			count++;
		    } else if ((curChar == closeChar) && Mx_SearchWord(file,
			    pos, closeParens[i], close1Ptr, close2Ptr)) {
	    		count--;
			if (count == 0) {
			    if (MX_POS_LESS(stop, *close1Ptr)) {
				goto noMatchingClose;
			    }
			    return 1;
			}
		    }
		}
		pos.charIndex = 0;
		pos.lineIndex++;
	    }
	} else if (Mx_SearchWord(file, pos, closeParens[i],
		close1Ptr, close2Ptr)) {

	    /*
	     * Got a close parenthesis.  Search backward through the
	     * file, one line at a time, for the matching open parenthesis.
	     */

	    count = 1;
	    pos.charIndex--;
	    openChar = *(openParens[i]);
	    closeChar = *(closeParens[i]);
	    line = Mx_GetLine(file, pos.lineIndex, &lineLength);
	    while (1) {
		if ((line == NULL) || MX_POS_LESS(pos, start)) {
		    noMatchingOpen:
		    *open1Ptr = *close1Ptr;
		    *open2Ptr = *close2Ptr;
		    return 1;
		}
		for (p = &line[pos.charIndex], curChar = *p;
			p >= line; p--, curChar = *p, pos.charIndex--) {
		    if ((curChar == openChar) && Mx_SearchWord(file, pos,
			    openParens[i], open1Ptr, open2Ptr)) {
	    		count--;
			if (count == 0) {
			    if (MX_POS_LESS(*open1Ptr, start)) {
				goto noMatchingOpen;
			    }
			    return 1;
			}
		    } else if ((curChar == closeChar) && Mx_SearchWord(file,
			    pos, closeParens[i], open1Ptr, open2Ptr)) {
			count++;
		    }
		}
		pos.lineIndex--;
		line = Mx_GetLine(file, pos.lineIndex, &lineLength);
		pos.charIndex = lineLength - 1;
	    }
	}
    }
    return 0;
}

/*
 *----------------------------------------------------------------------
 *
 * Mx_GetTag --
 *
 *	Lookup a name in the tags file and return the tag information.
 *
 * Results:
 *	TCL_OK is returned if the lookup was successful.  If it wasn't
 *	successful, then TCL_ERROR is returned and a pointer to an error
 *	message is left in interp->result in the standard way.  If TCL_OK is
 *	returned, then the string pointers at *fileNamePtr and
 *	*searchStringPtr get filled in with pointers to relevant strings.
 *	These are statically-allocated strings, which will be modified on
 *	the next call to this procedure.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

int
Mx_GetTag(tag, tagFile, regExp, fileNamePtr, searchStringPtr, interp)
    char *tag;			/* Tag name for which information is wanted. */
    char *tagFile;		/* If non-empty, must consist of a list of
				 * tags files to check.  If NULL, then the
				 * tags file "tags" is checked. */
    int regExp;			/* 1 if pattern should be in regex format;
				   0 otherwise. */
    char **fileNamePtr;		/* Name of tag's file gets filled in here. */
    char **searchStringPtr;	/* Search string to use in location tag in
				 * file gets filled in here. */
    Tcl_Interp *interp;		/* Interpreter to use for error reporting. */
{
#define LINELENGTH 200
    static char line[LINELENGTH];
    static char fileName[135];
    register char *p, *p2;
    char *lastSlash, **argv;
    FILE *stream;
    int argc, result, i;

    if ((tagFile == NULL) || (*tagFile == 0)) {
	tagFile = "tags";
    }

    /*
     * Loop over the various tags files in the string.
     */

    result = Tcl_SplitList(interp, tagFile, &argc, &argv);
    if (result != TCL_OK) {
	return result;
    }
    for (i = 0; i < argc; i++) {

	/*
	 * Find the name of the next tags file to check.
	 */

	lastSlash = NULL;
	for (p = argv[i]; *p != 0; p++) {
	    if (*p == '/') {
		lastSlash = p;
	    }
	}
	stream = fopen(argv[i], "r");
	if (stream == NULL) {
	    sprintf(interp->result,
		    "couldn't read tags file \"%.50s\"", argv[i]);
	    goto error;
	}

	/*
	 * Search for a line whose first characters match the tag.
	 */
	
	while (1) {
	    if (fgets(line, LINELENGTH, stream) == NULL) {
		break;
	    }
	    for (p = line; !isspace(*p); p++) {
		if (*p == 0) {
		    goto formatError;
		}
	    }
	    *p = 0;
	    p++;
	    if (strcmp(line, tag) == 0) {
	    
		/*
		 * Found the tag.  Parse off the file name, and combine
		 * it with the name of the tags file's directory.
		 */
	
		fclose(stream);
		while (isspace(*p)) {
		    p++;
		}
		*fileNamePtr = p;
		while (!isspace(*p)) {
		    if (*p == 0) {
			goto formatError;
		    }
		    p++;
		}
		*p = 0;
		p++;
		if (lastSlash != NULL) {
		    *lastSlash = 0;
		    sprintf(fileName, "%.80s/%.50s", argv[i],
			    *fileNamePtr);
		    *fileNamePtr = fileName;
		}

		/* Parse off the search string.  The search string is in
		 * regexp format, so the code below strips off all the
		 * fancy control characters to turn it back into an
		 * ordinary exact-match string.  Typically the search
		 * string is "/^pattern$/", but there may also be other
		 * embedded backslash sequences.
		 */
	    
		while (isspace(*p)) {
		    p++;
		}
		*searchStringPtr = p;
		p2 = p;
		if (*p != '/') {
		    goto formatError;
		}
		p++;
		for ( ; *p != '/'; p++) {
		    if (!regExp) {
			if (*p == '$') {
			    *p = '\n';
			} else if (*p == '^') {
			    continue;
			} else if (*p == '\\') {
			    p++;
			}
		    }
		    if (*p == 0) {
			goto formatError;
		    }
		    *p2 = *p;
		    p2++;
		}
		*p2 = 0;
		free((char *) argv);
		return TCL_OK;
	    }
	}

	/*
	 * Nothing in this file.  Try next tags file.
	 */

	fclose(stream);
    }

    sprintf(interp->result, "no tag \"%.50s\" in tags file(s)", tag);
    goto error;

    formatError:
    sprintf(interp->result, "format error in tags file \"%.50s\"",
	    argv[i]);

    error:
    free((char *) argv);
    return TCL_ERROR;
}
