[alt.sources] lq-text Full Text Retrieval Database Part 08/13

lee@sq.sq.com (Liam R. E. Quin) (03/04/91)

: cut here --- cut here --
: To unbundle, sh this file
#! /bin/sh
: part 08
echo x - lq-text/src/lqtext/lqword.c 1>&2
sed 's/^X//' >lq-text/src/lqtext/lqword.c <<'@@@End of lq-text/src/lqtext/lqword.c'
X/* lqword.c -- Copyright 1989 Liam R. Quin.  All Rights Reserved.
X * This code is NOT in the public domain.
X * See the file COPYRIGHT for full details.
X */
X
X/* lqword -- simple program to print information about individual words.
X *
X * $Id: lqword.c,v 2.8 90/10/06 00:51:00 lee Rel1-10 $
X */
X
X#include "globals.h" /* defines and declarations for database filenames */
X
X#include <stdio.h>
X#include <sys/types.h>
X#include <malloc.h>
X#include <fcntl.h> /* for fileinfo.h */
X#include <ctype.h>
X
X#ifdef BSD
X# define USI_MAX ((unsigned int) -1)
X#else
X# include <limits.h>
X  /* for USI_MAX, the largest unsigned integer.
X   * 4.3 BSD doesn't seem to have this.  I don't know how to get this
X   * on BSD systems.
X   */
X#endif
X
X#include "fileinfo.h"
X#include "wordinfo.h"
X#include "smalldb.h"
X#include "pblock.h"
X#include "wordrules.h"
X#include "emalloc.h"
X
X/*** Declarations: ***/
X/** System calls and library routines: **/
Xextern void exit();
X
X/** System calls: **/
X
X/** Unix Library Functions: **/
Xextern char *strncpy();
X#ifndef tolower
X extern int tolower();
X#endif
X
X/** lqtext library functions: **/
Xextern char *UnFlag();
Xextern t_WordInfo *WID2WordInfo();
Xextern int TooCommon();
Xextern void cleanupdb();
Xextern void SetDefaults();
Xextern void DefaultUsage();
Xextern void DeleteWord();
X
X/** functions defined within this file: */
Xvoid PrintWordInfo(), AllWordInfo();
Xvoid Display(), ShowWordList();
Xvoid dbmmarch();
X
X/** Macros and variable definitions **/
X
X#define DISPLAY_ALL 1
X#define DISPLAY_NAME 2
X    /* These are the possible DisplayMode values -- see main() */
X
Xchar *progname = 0;
X    /* Used for error messages */
X
Xint SilentMode = 0;
X    /* Set if we were invoked with the -s option.  In this mode, we behave
X     * like grep -s, and exit with a zero exit status if one or more of
X     * the words were found in the database.
X     */
X
Xint ListMode = 0;
X    /* Set if we are to provide a terser output format suitable for use
X     * with lqshow(1L).
X     */
X
Xint AsciiTrace = 0;
X    /* If this is non-zero, we provide debugging information.  The lqtext
X     * library also uses this variable.  Setting it to values greater
X     * than 1 or 2 will generally provide large amounts of debugging
X     * information.  If the library was compiled with -UASCIITRACE,
X     * however, there will be much less diagnostic output at higher
X     * levels.
X     */
X
Xstatic char *Revision = "lqword 2.2";
X
X/** end of declarations... **/
X
X
Xint
Xmain(argc, argv)
X    int argc;
X    char *argv[];
X{
X    extern int optind, getopt();  /* For getopt(3) */
X    extern char *optarg;	  /* For getopt(3) */
X    int ch;			  /* For getopt(3) */
X    int ErrorFlag = 0;		  /* For getopt(3) */
X    int DisplayMode = 0;
X	/* DisplayMode indicates what kind of information we are to
X	 * print in response to queries.  The values understood are
X	 * the DISPLAY_* constants.  Perhaps this should be an enum.
X	 */
X
X    progname = argv[0];
X	/* I see this as a library program, so I am leaving the full
X	 * path.  lqaddfile(1L) and lqphrase(1L) set progname to be
X	 * the filename of the command, rather than the full pathname.
X	 */
X
X    SetDefaults(argc, argv);
X	/* Deal with any arguments that are understood by all lqtext
X	 * programs.
X	 */
X
X    while ((ch = getopt(argc, argv, "aAD:lsVxZz:")) != EOF) {
X	switch (ch) {
X	case 'a':
X	    DisplayMode = DISPLAY_NAME;
X	    break;
X	case 'A':
X	    DisplayMode = DISPLAY_ALL;
X	    break;
X	case 'D':
X	    DeleteWord(optarg); /* MISFEATURE */
X		/* This actually removes all entries for the given word
X		 * from the database.  You need write permission, of
X		 * course.
X		 */
X	    break;
X	case 'l':
X	    ListMode = 1;
X	    break;
X	case 's':
X	    SilentMode = 1;
X	    break;
X	case 'V':
X	    fprintf(stderr, "%s version %s\n", progname, Revision);
X	    break;
X	case 'x':
X	    ErrorFlag++;
X	    break;
X	case '?':
X	    ErrorFlag++;
X	    break;
X	case 'z':
X	case 'Z':
X	    break; /* done by SetDefaults(); */
X	}
X    }
X
X    /* Normally put call to lrqError here to give a helpful message,
X     * but not yet ready to ship the error handling package, sorry
X     */
X    if (ErrorFlag) {
X	fprintf(stderr, "%s: options are:\n", progname);
X	fputs("\
X	-D Word -- delete the named word (DANGEROUS!)\n\
X	-l	-- list mode, for use with lqshow\n\
X	-s	-- silent mode (like grep -s)\n", stderr);
X	DefaultUsage();
X	    /* DefaultUsage() prints the list of the standard options. */
X	fputs("\n\
XIn addition, if no words are given, the following are understood:\n\
X	-a	-- print all words\n\
X	-A	-- print all matches to all words\n", stderr);
X	exit(1);
X    }
X
X    if (optind >= argc) {
X	if (SilentMode) exit(1);
X	    /* if there were no words given, none of them matched.
X	     * It could be argued that this case should be an error.
X	     */
X	if (DisplayMode) {
X	    AllWordInfo(DisplayMode);
X	} else {
X	    /* In this case, there were no command-line options and no
X	     * display-mode flags, so we do the default thing.
X	     * This happens to be to print every word in the database.
X	     * This is probably bogus behaviour -- there should be a better
X	     * way of finding words that match a given pattern than using
X	     * lqword | grep
X	     * which is what this allows.
X	     */
X	    dbmmarch();
X	}
X    } else {
X	if (!SilentMode && !ListMode) {
X	    /* Print some pretty headers */
X	    printf("       WID | Where   | Total   | Word\n");
X	    puts(
X"===========|=========|=========|============================================");
X	}
X
X	while (optind < argc) {
X	    PrintWordInfo(argv[optind++]);
X	}
X    }
X    cleanupdb();
X	/* close database files.  This is particularly important if we are
X	 * updating the database -- the horrible -D option -- but should
X	 * probably be done by liblqtext itself.
X	 */
X    exit(SilentMode); /* 0 or 1 (this is a little devious) */
X#ifdef lint
X    /*NOTREACHED*/
X    return 1;
X	/* this is for versions of lint and gcc that don't understand
X	 * that exit() doesn't return -- or, if it douse, that there is
X	 * nothing that can be done about it!
X	 */
X#endif
X}
X
Xvoid
XPrintWordInfo(Word)
X    char *Word;
X{
X    extern t_WordInfo *FindWordInfoFromIndex();
X    extern long atol();
X    extern t_WID Word2WID();
X    extern char *WordRoot();
X
X    register char *p;
X    t_WordInfo *WordInfo;
X    t_WID WID;
X    t_WordInfo Root;
X
X    Root.WordPlace.Flags = 0;
X
X    /** Find the canonical form of the word, with plurals reduced to the
X     ** singular and letters folded into lower case.
X     **/
X
X    /* First, remember if the word originally started with an upper case
X     * letter:
X     */
X    if (isupper(*Word)) {
X	Root.WordPlace.Flags |= WPF_UPPERCASE;
X    }
X
X    /* now convert to lower case and measure its length at the same time: */
X    for (p = Word; *p; p++) {
X	if (isupper(*p)) *p = tolower(*p);
X    }
X
X    Root.Length = p - Word;
X    Root.Word = Word;
X
X    /* Now call WordRoot() to find the canonical form: */
X    Word = WordRoot(&Root);
X
X    /** Now see if the canonical word is too common to list: **/
X
X    if (TooCommon(&Root)) {
X	/* It is listed in the common word list, so don't bother looking
X	 * it up at all
X	 */
X	if (!SilentMode) {
X	    fprintf(stderr, "No index information for: %s (too common)\n",
X								    Word);
X	}
X	return;
X    }
X
X    /** It is not too common, so look it up: **/
X
X    if (((WID = Word2WID(Word, Root.Length)) == (t_WID) 0) ||
X	(WordInfo = WID2WordInfo(WID)) == (t_WordInfo *) 0) {
X	if (!SilentMode) {
X	    if (WID) {
X		/* In this case the word is in the database (since it has
X		 * a non-zero WID), but not in the word index.  This might
X		 * happen if the word is being deleted (or added) by someone
X		 * else at this very moment, or if the database is corrupt.
X		 */
X		fprintf(stderr, "No index information for: %s (WID %lu)\n",
X								Word, WID);
X	    } else {
X		/* In this case the word is neither listed as common nor
X		 * found in the database.  Either it was spelt differently
X		 * there or it isn't there at all.
X		 */
X		fprintf(stderr, "No index information for: %s\n", Word);
X	    }
X	}
X	return;
X    }
X    if (SilentMode && WordInfo->NumberOfWordPlaces > 0) {
X	/* We found something, so there is no point looking further --
X	 * we already know enough to exit.  If a lot of words are given,
X	 * this could be a big efficiency win.
X	 */
X	exit(0);
X    }
X
X    /** Now we have the database entry for the word, so let's print it!
X     **/
X    Display(WordInfo, DISPLAY_ALL);
X
X    /** Now return the storage used...
X     **/
X    if (WordInfo) {
X	SlayWordInfo(WordInfo);
X    }
X
X    /** All done for this word.
X     **/
X}
X
X/* Display() -- print information about a single word */
Xvoid
XDisplay(WordInfo, Verbose)
X    t_WordInfo *WordInfo;
X    int Verbose;
X{
X    char *Buf = emalloc(WordInfo->Length + 1);
X
X    /* Words in a t_WordInfo might not be null terminated, since the
X     * storage overhead and the work of putting the nulls there might
X     * be significant...
X     */
X    (void) strncpy(Buf, WordInfo->Word, WordInfo->Length);
X    Buf[WordInfo->Length] = '\0';
X
X    if (!ListMode) {
X	/* Print a little header for the word, unless we were asked not to */
X	printf("%10lu | %7lu | %7lu | %s\n", WordInfo->WID,
X					     WordInfo->Offset,
X					     WordInfo->NumberOfWordPlaces,
X					     WordInfo->Word
X	);
X			    
X    }
X    if ((ListMode || Verbose == DISPLAY_ALL) && WordInfo->NumberOfWordPlaces) {
X	/* If  there are occurrences in the database (there might not be if
X	 *     the word has been deleted, or has only just been added),
X	 * and  we want all the matches,
X	 * then  print the list of matches in the appropriate format:
X	 */
X	ShowWordList(WordInfo);
X    }
X
X    (void) efree(Buf);
X    /* reclaim storage */
X}
X
Xvoid
XShowWordList(WordInfo)
X    t_WordInfo *WordInfo;
X{
X    extern t_pblock *Getpblock();
X    t_FileInfo *GetFileInfo();
X
X    t_FileInfo *FileInfo = (t_FileInfo *) 0;
X    t_pblock *pblock = (t_pblock *) 0;
X    t_WordPlace *PP = (t_WordPlace *) 0;
X    int Place;
X    char *LastRoot = "[internal error lqword.c 392]";
X	/* the message is in case I make a coding error!.  The number
X	 * was once the line number of the message, but it only needs to
X	 * be a distinct enough message to search for.
X	 */
X
X    if (WordInfo->WordPlacesInHere >= WordInfo->NumberOfWordPlaces) {
X	/* In this case, the match info all fits in the index, so it
X	 * does not matter if automatic pre-fetching from the overflow
X	 * file "data" happens or not (i.e. if we are using Lazy Evaluation,
X	 * it doesn't happen, but it makes no difference in this case).
X	 */
X	PP = WordInfo->WordPlaces;
X    } else if ((pblock = Getpblock(WordInfo)) != (t_pblock *) 0) {
X	PP = pblock->WordPlaces;
X	/* If Lazy Evaluation is enabled, liblqtext might not have fetched
X	 * all of the match information from the overflow database, in
X	 * which case we must do it now.
X	 */
X    }
X
X    if (PP) {
X	t_FID LastFID = USI_MAX;
X	    /* This is not a plausible FID (File IDentifier), so it
X	     * will force a call to GetFileInfo() in the loop below.
X	     */
X	unsigned int LastFlags = 256 * 2;
X	    /* Similarly, this is an impossible flag value, since the
X	     * flags are constrained to fit in a single byte.
X	     */
X
X	/* cycle through the Place... */
X	for (Place = 0; Place < WordInfo->NumberOfWordPlaces; Place++) {
X
X	    char BIF[100]; char WIB[100];
X	    register char *p;
X	    char *Bp, *Wp;
X	    long l;
X
X	    if (LastFlags != PP[Place].Flags) {
X		LastFlags = PP[Place].Flags;
X		LastRoot = UnFlag(WordInfo, LastFlags);
X		    /* UnFlag() takes a canonical (singular, lower-case)
X		     * word and a set of flags, and reverses the
X		     * transformations implied by the flags.  For example,
X		     * if WordInfo->Word is "boy" and flags contain the
X		     * Plural flag, you should get "boys" returned.
X		     * Since we don't remember whether a word was in all
X		     * caps or had only the first letter capitalised (at
X		     * the moment, anyway), the routine will return Boys
X		     * even if the input was BOYS or BoYs.
X		     * Possessives (the boy's books) may also be indicated.
X		     */
X	    }
X
X	    if (LastFID != PP[Place].FID || FileInfo == (t_FileInfo *) 0) {
X		/* The first part of the test means we don't call the
X		 * function to retrieve the file name lots of times if
X		 * there are multiple matches in the same data file. 
X		 * This turns out to be a common case.
X		 */
X
X		/* Reclaim storage */
X		if (FileInfo) {
X		    if (FileInfo->Name) {
X			(void) efree(FileInfo->Name);
X		    }
X		    (void) efree(FileInfo);
X		}
X
X		/* Find the file name from the FID.  This routine should
X		 * be called FID2FileName(), and may in fact be renamed
X		 * in the future.
X		 */
X		if ((FileInfo = GetFileInfo(LastFID = PP[Place].FID)) ==
X						(t_FileInfo *) 0) {
X		    /* No filename information available.  This sometimes
X		     * happens if you rin lqword diring an lqaddfile
X		     * session and match a word in one of the new files.
X		     * Note that if the output is for reuse, we don't
X		     * want to include references to files whose names
X		     * we don't have!
X		     */
X		    if (!ListMode) {
X			printf("%20s | %-.5lu/%-.3lu | [FID %d]\n",
X			    LastRoot,
X			    PP[Place].BlockInFile,
X			    PP[Place].WordInBlock,
X			    PP[Place].FID);
X		    }
X		    continue;
X		}
X	    }
X
X	    /* This is an inline printf, because otherwise this call
X	     * to printf takes over 20% of the execution time, and nearly
X	     * 40% for a frequent word (e.g. over 1000 places) !!
X	     */
X	    p = &BIF[sizeof(BIF) - 1];
X	    *p = '\0';
X	    if (PP[Place].BlockInFile == 0) {
X		*--p = '0';
X	    } else for (l = PP[Place].BlockInFile; l; l /= 10) {
X		*--p = "0123456789"[l % 10];
X	    }
X	    Bp = p;
X
X	    p = &WIB[sizeof(WIB) - 1];
X	    *p = '\0';
X	    {
X		register int i = PP[Place].WordInBlock;
X		if (i == 0) {
X		    *--p = '0';
X		} else for (; i; i /= 10) {
X		    *--p = "0123456789"[i % 10];
X		}
X		Wp = p;
X	    }
X
X  	    if (ListMode) {
X		while (*Bp) {
X		    putchar(*Bp);
X		    Bp++;
X		}
X		putchar(' ');
X		while (*Wp) {
X		    putchar(*Wp);
X		    Wp++;
X		}
X		putchar(' ');
X		puts(FileInfo->Name);
X  	    } else {
X		/* Well, if we are not reusing the output, maybe the speed
X		 * is not quite so critical...
X		 */
X  		printf("%20s | %5lu/%3lu F=%3u S=%3u | %s\n",
X		    LastRoot,
X		    PP[Place].BlockInFile,
X		    PP[Place].WordInBlock,
X		    PP[Place].Flags, /* XXX */
X		    PP[Place].StuffBefore,
X		    FileInfo->Name);
X	    }
X	}
X    }
X
X    if (pblock) {
X	/* If we had to go and get the matches ourselves, we had better
X	 * release the storage.
X	 * Actually we should also be freeing the FileInfo and possibly
X	 * the WordInfo as well, but the pblock is the biggest... and I
X	 * am only adding comments today, not fixing code (I hope)...
X	 * NOTDONE FIXME
X	 */
X	(void) efree(pblock);
X    }
X}
X
Xvoid
XAllWordInfo(Verbose)
X    int Verbose;
X{
X    extern char *WID2Word();
X    extern t_WID GetMaxWID();
X
X    t_WID i;
X    t_WID MaxWid = GetMaxWID();
X    t_WordInfo *WordInfo;
X    char *Name;
X
X    /* Loop over all possible WID numbers and print information
X     * for each of them.
X     */
X    for (i = (t_WID) 1; i <= MaxWid; i++) {
X	if ((Name = WID2Word(i)) != (char *) 0) {
X
X	    /* If Name is zero, that WID is unused.  There might be gaps
X	     * if a word was deleted.
X	     */
X
X	    if ((WordInfo = WID2WordInfo(i)) != (t_WordInfo *) 0) {
X		Display(WordInfo, Verbose);
X		SlayWordInfo(WordInfo);
X	    } else {
X		/* In this case the word is known, but there is no further
X		 * information about it.  In the current inplementation,
X		 * this cannot happen unless someone else is updating the
X		 * database and replacing a WID whose word had been deleted.
X		 */
X		if (!ListMode) {
X		    /* If we are in list mode, it is probably because the
X		     * output is wanted by another prpgram, so we had
X		     * better not print out this (useless) entry.
X		     */
X		    printf("%10lu | %7lu |           | ?? %s\n",
X			i, 0L, Name);
X		}
X	    }
X
X	    /* Reclaim the storage used... */
X	    (void) efree(Name);
X	} /* end if */
X    } /* for each WID */
X
X    if (!ListMode) {
X	printf("Maximum WID is %lu\n", MaxWid);
X    }
X}
X
X/* dbmmarch -- print every value in a dbm database.  This might go
X * wrong (omitting some values) if the database is being concurrently
X * updated.
X */
Xvoid
Xdbmmarch()
X{
X    DBM *db;
X    datum d;
X
X    if ((db = startdb(WordIndex)) == (DBM *) 0) {
X	/* WordIndex is the list of words, defined in "globals.h".
X	 * If we didn't open it, the user probably has not set
X	 * $LQTEXTDIR, or didn't use the -d database-dir option that
X	 * is handled bu SetDefaults() called from main().
X	 */
X	fprintf(stderr, "Can't open database file \"%s\"\n", WordIndex);
X	exit(1);
X    }
X
X    /* The word database contains WID-->word matches, that look like
X     * (key = "Word", content = WID)
X     */
X    for (d = dbm_firstkey(db); d.dsize != 0; d = dbm_nextkey(db)) {
X	register char *s;
X
X	/* IMPORTANT NOTE:
X	 * The words are not nul-terminated in the database.  It is
X	 * therefore not safe to use printf() or puts() unless we make
X	 * a copy or are careful...
X	 */
X	for (s = d.dptr; s - d.dptr < d.dsize; s++) {
X	    putchar(*s);
X	}
X	putchar('\n');
X    }
X    enddb(db);
X}
X
X/*
X * $Log:	lqword.c,v $
X * Revision 2.8  90/10/06  00:51:00  lee
X * Prepared for first beta release.
X * 
X * Revision 2.7  90/08/29  21:45:37  lee
X * Alpha release
X * 
X * Revision 2.6  90/08/08  22:22:53  lee
X * Added heavy comments.  Cleaned up dbmmarch() and made some other
X * minor fixes.
X * 
X * Revision 2.5  90/08/08  21:06:21  lee
X * Added -x option; removed rude message about getpts bugs.
X * 
X * Revision 2.4  90/04/21  18:50:38  lee
X * fixed a serious bug in the -l mode -- now prints the entire match!
X * 
X * Revision 2.3  90/03/27  13:20:57  lee
X * now passes gcc -Wall
X * 
X * Revision 2.2  89/10/08  20:47:23  lee
X * Working version of nx-text engine.  Addfile and wordinfo work OK.
X * 
X * Revision 2.1  89/10/02  01:16:10  lee
X * New index format, with Block/WordInBlock/Flags/BytesSkipped info.
X * 
X * Revision 1.3  89/09/17  23:04:42  lee
X * Various fixes; NumberInBlock now a short...
X * 
X * Revision 1.2  89/09/16  21:18:50  lee
X * First demonstratable version.
X * 
X * Revision 1.1  89/09/07  21:06:14  lee
X * Initial revision
X * 
X */
@@@End of lq-text/src/lqtext/lqword.c
echo x - lq-text/src/lqtext/matchword.sh 1>&2
sed 's/^X//' >lq-text/src/lqtext/matchword.sh <<'@@@End of lq-text/src/lqtext/matchword.sh'
X:
X# matchword pattern [...] -- grep for words in the database
X#
X# matchword -- Copyright 1990 Liam R. Quin.  All Rights Reserved.
X# This code is NOT in the public domain.
X# See the file ../COPYRIGHT for full details.
X#
X# $Id: matchword.sh,v 1.2 90/10/06 00:51:02 lee Rel1-10 $
X#
X
X# "echo" portability test:
XN=; C='\c'; if [ x"`echo -n hello`" = x"hello" ]; then N=-n;C=; fi
Xexport N C
X
Xans=no
Xwhile [ x"$ans" != x"q" ]
Xdo
X    echo $N "Enter a word or pattern: $C"
X    read pattern
X    if [ x"$pattern" = x"q" ]
X    then
X	break
X    fi
X    WORDS=`lqword | grep "^${pattern}\$"`
X    if [ "$WORDS" = "" ]
X    then echo "(no match in the database for ${pattern})"
X    else echo `echo "$WORDS" | wc -l` words found:
X	 echo "$WORDS" | sort -d | rs | ${PAGER-more}
X	 # If you don't have rs, you could use cat instead.
X	 # PAGER could also be "pg -nse", or "less -q".
X    fi
Xdone
X
@@@End of lq-text/src/lqtext/matchword.sh
echo x - lq-text/src/lqtext/sizes.c 1>&2
sed 's/^X//' >lq-text/src/lqtext/sizes.c <<'@@@End of lq-text/src/lqtext/sizes.c'
X/* sizes.c -- Copyright 1990 Liam R. Quin.  All Rights Reserved.
X * This code is NOT in the public domain.
X * See the file COPYRIGHT for full details.
X */
X
X#ifndef lint
X static char *Rcs = "$Id: sizes.c,v 1.3 90/10/06 00:51:03 lee Rel1-10 $";
X#endif
X
X#include "globals.h" /* defines and declarations for database filenames */
X
X#include <stdio.h>
X#include <sys/types.h>
X#include "fileinfo.h"
X#include "wordinfo.h"
X#include "pblock.h"
X#include "wordrules.h"
X#include "wordindex.h"
X
Xmain()
X{
X    printf("FileInfo  %u bytes\n", sizeof(t_FileInfo));
X    printf("WordInfo  %u bytes\n", sizeof(t_WordInfo));
X    printf("WordPlace %u bytes\n", sizeof(t_WordPlace));
X    printf("pblock    %u bytes\n", sizeof(t_pblock));
X}
@@@End of lq-text/src/lqtext/sizes.c
echo x - lq-text/src/lqtext/wordtable.c 1>&2
sed 's/^X//' >lq-text/src/lqtext/wordtable.c <<'@@@End of lq-text/src/lqtext/wordtable.c'
X/* wordtable.c -- Copyright 1989, 1990 Liam R. Quin.  All Rights Reserved.
X * This code is NOT in the public domain.
X * See the file ../COPYRIGHT for full details.
X */
X
X/* Symbol Table Interface to text retrieval database.
X * Handles both the internal and external indexes.
X *
X * This originally used a linked list.  Converting to a hash table reduced
X * the time to index comp.os.vms from nearly an hour to one and a half
X * minutes...
X *
X * Liam Quin, 1989
X */
X
X/* 
X * $Id: wordtable.c,v 2.11 91/02/20 19:07:37 lee Rel1-10 $
X */
X
X#ifndef lint
X static char *Rcs = "$Id: wordtable.c,v 2.11 91/02/20 19:07:37 lee Rel1-10 $";
X#endif
X
X#include "globals.h" /* defines and declarations for database filenames */
X
X#ifdef SYSV
Xextern int _filbuf();
X#endif
X#include <stdio.h>
X#include <malloc.h>
X#include <ctype.h>
X#include <sys/types.h>
X#include <fcntl.h> /* for O_RDWR wtc */
X#include "smalldb.h"
X#include "fileinfo.h"
X#include "wordinfo.h"
X#include "pblock.h"
X#include "wordrules.h"
X#include "emalloc.h"
X
X#define HASHSIZ 32768 /* MUST be a power of two */
X
X#ifndef MAXWORDSINCACHE
X# define MAXWORDSINCACHE  (HASHSIZ * 10)
X#endif
Xint MaxWordsInCache = MAXWORDSINCACHE;
X
Xextern int AsciiTrace;
X
X/* useful macros */
X#define NumberOfElements(array, type) (sizeof(array)/sizeof(type))
X#define STRCMP(a,b) ((*(a) > *(b)) ? 1 : ((*(a) < *(b)) ? -1 : strcmp(a,b)) )
X/* #define Hash(WordInfo) \
X *	(dbm_hash(WordInfo->Word, WordInfo->Length) % HashSize)
X */
X
X/** System calls and library functions used in this file: **/
X
X/** Lqtext calls */
Xextern unsigned int Putpblock();
Xextern void DeleteWordPlaces();
X
X/** System calls: */
X
X/** Library Functions: */
Xextern char *strncpy();
Xextern int strcmp();
Xextern void perror();
Xextern void exit();
X/**/
X
X#define enew(var, type) (var = (type *) emalloc(sizeof (type)))
X
Xextern char *progname;
Xstatic int HashSize = HASHSIZ; /* MUST be a power of two */
X
X#ifdef NEWSYM
X
X#define NPLACES 7
X/* THis is small to optimise the common case -- by far the majority of
X * words are used less than 10 times.  In the cases where we've gone
X * wrong, well, there'll be a few thousand.
X */
X
Xtypedef struct s_HashEl {
X    char *Word;
X    t_WID WID;
X    int PlacesUsed;
X    t_WordPlace Places[NPLACES];
X    struct s_HashEl *Next;
X} t_HashEl;
X
Xstatic t_HashEl *SymbolTable;
Xstatic t_HashEl *LastEl;
Xstatic int WordsInCache = 0;
X
XStartHash()
X{
X    if (MaxWordsInCache) HashSize = MaxWordsInCache / 16;
X    SymbolTable = (t_HashEl *) emalloc(sizeof(t_HashEl) * HashSize);
X    /* Note that we only need to initialise the Word pointers... */
X    for (LastEl = SymbolTable; LastEl != &SymbolTable[HashSize]; LastEl++) {
X	LastEl->Word = (char *) 0;
X    }
X    /* ASSERT: LastEl == &SymbolTable[HashSize] */
X    MaxWordsInCache = HashSize;
X}
X
XSetElEmpty(El)	/* Initialisation function for Hash Elements */
X    t_HashEl *El;
X{
X    El->Word = (char *) 0;
X    El->WID = (t_WID) -1;
X		/* NOT zero, so we can distinguish between unknown and
X		 * "haven't looked"
X		 */
X    El->PlacesUsed = 0;
X    El->Next = (t_HashEl *) 0;
X}
X
Xvoid DumpCache();
X
Xvoid
XAddWord(WordInfo)
X    t_WordInfo *WordInfo;
X{
X    register t_HashEl *HashEl;
X    int Slot;
X    t_HashEl *FirstEl;
X
X    if (!WordInfo || !WordInfo->Word || !WordInfo->Word[0]) {
X	(void) fprintf(stderr, "%s: warning: Null Word in AddWord\n", progname);
X	return;
X    }
X
X    if (!LastEl) {
X	StartHash();
X    } else if (MaxWordsInCache && ++WordsInCache > MaxWordsInCache) {
X	DumpCache(1);
X    }
X
X    if (WordInfo->Word[0] == 'q') {
X	register char *xp;
X
X	for (xp = &WordInfo->Word[1]; *xp && *xp == 'x'; xp++) {
X	    /*NULLBODY*/
X	}
X	if (!*xp) {
X	    if (AsciiTrace >= 10) {
X		(void) fprintf(stderr, "Discard %d\n", WordInfo->Word);
X	    }
X	    return;
X	}
X    }
X
X    Slot = Hash(WordInfo);
X    FirstEl = HashEl = &SymbolTable[Slot];
X
X
X    for (;;) {
X	if (!HashEl->Word) {
X	    extern char *strcpy();
X	    extern t_WID Word2WID();
X
X	    if (AsciiTrace > 9) {
X		(void) fprintf(stderr, "New ", WordInfo->Word);
X	    }
X	    /* make a new element */
X	    SetElEmpty(HashEl);
X	    HashEl->Word = emalloc(WordInfo->Length + 1);
X	    (void) strcpy(HashEl->Word, WordInfo->Word);
X	    /**
X	    HashEl->WID = (t_WID) -1;
X	    **/
X	    HashEl->WID = Word2WID(HashEl->Word, WordInfo->Length);
X	    /** **/
X	    break;
X	} else if (STREQ(HashEl->Word, WordInfo->Word)) {
X	    break;
X	}
X
X	if (++HashEl == LastEl) HashEl = SymbolTable;
X
X	if (HashEl == FirstEl) {
X	    /* We need to dump the cache and start again */
X	    DumpCache(1);
X	    AddWord(WordInfo);
X	    return;
X	}
X    }
X    /* If we get here, all we need to do is add the WordPlace */
X    if (AsciiTrace > 9) {
X	(void) fprintf(stderr, "AddWord %s\n", WordInfo->Word);
X    }
X    FirstEl = HashEl;
X
X    while (HashEl->PlacesUsed >= NPLACES && HashEl->Next != (t_HashEl *) 0) {
X	HashEl = HashEl->Next;
X    }
X
X    if (HashEl->PlacesUsed >= NPLACES) {
X	t_HashEl *New;
X
X	New = (t_HashEl *) malloc(sizeof(t_HashEl));
X	SetElEmpty(New);
X
X	New->Next = FirstEl->Next;
X	FirstEl->Next = HashEl = New;
X    }
X    HashEl->Places[HashEl->PlacesUsed] = WordInfo->WordPlace; /* structure copy */
X    HashEl->PlacesUsed++;
X    return;
X}
X
Xvoid
XDumpCache(CallFree)
X    int CallFree;
X{
X    register t_HashEl *HashEl, *MeNext;
X    int Progress = 0;
X
X    for (HashEl = SymbolTable; HashEl != LastEl; HashEl++) {
X	if (HashEl->Word) {
X	    extern t_WordInfo *MakeWordInfo();
X	    unsigned len;
X	    t_WordInfo *WP;
X
X	    /* We are going to make a new index entry for the word.
X	     * There are two cases -- depending on whether the word
X	     * is already indexed or not.
X	     * In the former case we must merge the new information.
X	     * In the latter case we don't have to read the old info,
X	     * but we must make a new entry in the WID Index.
X	     */
X
X	    len = strlen(HashEl->Word);
X	    if (HashEl->WID == (t_WID) -1) {
X		HashEl->WID = Word2WID(HashEl->Word, len);
X	    }
X	    WP = MakeWordInfo(HashEl->WID, len, HashEl->Word);
X
X	    if (HashEl->WID == (t_WID) 0) {
X		NewEntry(HashEl, WP);
X	    } else {
X		UpdateEntry(HashEl, WP);
X	    }
X	    /* Reclaim storage */
X	    if (CallFree) {
X		extern void SlayWordInfo();
X		register t_HashEl *FreeMe = HashEl;
X
X		(void) SlayWordInfo(WP);
X
X		efree(HashEl->Word);
X		FreeMe->Word = (char *) 0;
X		FreeMe = FreeMe->Next; /* don't do the first one */
X		while (FreeMe) {
X		    MeNext = FreeMe->Next;
X		    (void) efree((char *) FreeMe);
X		    FreeMe = MeNext;
X		}
X	    }
X	}
X	if (AsciiTrace > 1) {
X	    if (HashEl - SymbolTable >= Progress * (HashSize / 16)) {
X		fputc(" 01234567890ABCDEFGHIJKL"[Progress], stderr);
X		++Progress;
X	    }
X	}
X    }
X    WordsInCache = 0;
X}
X
XNewEntry(HashEl, WP)
X    t_HashEl *HashEl;
X    t_WordInfo *WP;
X{
X    extern t_WID GetNextWID();
X    t_pblock *pblock;
X    long MatchCount;
X    t_HashEl *Ep;
X
X    /** Assign a new WID */
X    WP->WID = GetNextWID();
X
X    /** make a WIDIndex entry and mark it as invalid (NOTDONE) */
X
X    /* In order to do this, we must make a "pblock", a structure that
X     * reflects the physical database.  This is fairly low-level stuff
X     * for efficiency's sake...
X     */
X
X    /* count the total number of entries we're adding: */
X    for (Ep = HashEl, MatchCount = 0; Ep; Ep = Ep->Next) {
X	MatchCount += Ep->PlacesUsed;
X    }
X
X    /* allocate a pblock structure.  These are rather devious things, a
X     * structure with an array tacked onto the end.
X     */
X    pblock = (t_pblock *) emalloc(sizeof(t_pblock) +
X				MatchCount * sizeof(t_WordPlace));
X    
X    pblock->WID = WP->WID;
X    pblock->ChainStart = 0L; /* address on disk -- not there yet, so 0! */
X    pblock->NumberOfWordPlaces = WP->NumberOfWordPlaces = MatchCount;
X
X    /* fill in the WordPlaces */
X    for (Ep = HashEl, MatchCount = 0; Ep; Ep = Ep->Next) {
X	register int i;
X
X	for (i = 0; i < Ep->PlacesUsed; i++) {
X	    pblock->WordPlaces[MatchCount++] = Ep->Places[i]; /* struct copy */
X	}
X    }
X
X    /* Now fill in enough of WP to let us use the low-level routines: */
X    WP->FID = (t_FID) 0;
X    WP->Next = (t_WordInfo *) 0;
X    WP->DataBlock = (char *) 0;
X    WP->WordPlaceStart = (char *) 0;
X    WP->WordPlaces = (t_WordPlace *) 0;
X    WP->WordPlacesInHere = 0;
X    WP->WordPlace.FID = 0;
X    WP->WordPlace.Flags = 0;
X    WP->Offset = 0;
X
X    /* First, let's make an index entry: */
X#ifndef MaxWordPlacesInAWordBlock
X# define MaxWordPlacesInAWordBlock ((WIDBLOCKSIZE-(WP->Length+2)/3))
X#endif
X    if (pblock->NumberOfWordPlaces <= MaxWordPlacesInAWordBlock) {
X	(void) MkWIB(WP, pblock);
X    }
X
X    /** write out the new entry */
X    if (WP->WordPlacesInHere == pblock->NumberOfWordPlaces) {
X	/* In this case it all fits into the main index */
X	if (PutWordInfoIntoIndex(WP, (unsigned long) 0L) < 0) {
X	    extern int errno;
X	    int e = errno;
X	    fprintf(stderr, "%s: Couldn't insert word \"%s\" into the index",
X				progname, WP->Word);
X	    perror("");
X	    exit(1);
X	}
X    } else {
X	(void) Putpblock(WP, pblock);
X	if (PutWordInfoIntoIndex(WP, pblock->ChainStart) < 0) {
X	    extern int errno;
X	    int e = errno;
X	    fprintf(stderr, "%s: Couldn't re-insert word \"%s\" into the index",
X				progname, WP->Word);
X	    perror("");
X	    exit(1);
X	}
X    }
X
X    /** mark it as valid (NOTDONE) */
X
X    /** reclaim storage */
X    (void) efree((char *) pblock);
X    /* the caller *must* do SlayWordInfo(WP) */
X}
X
XUpdateEntry(HashEl, WP)
X    t_HashEl *HashEl;
X    t_WordInfo *WP;
X{
X    extern t_pblock *Getpblock();
X    extern t_WordInfo *WID2WordInfo();
X    t_pblock *pblock;
X    long MatchCount;
X    t_HashEl *Ep;
X    t_WordInfo *Wpp;
X
X    /** Mark the old entry as invalid (NOTDONE) */
X
X    /** get the old entry */
X    if ((Wpp = WID2WordInfo(WP->WID)) == (t_WordInfo *) 0) {
X	/* someone else has just deleted it! */
X	NewEntry(HashEl, WP);
X	return;
X    }
X    /* It would be best if we could append to the old entry... which is what
X     * I had in mind when I designed the disk storage stuff... but you can't.
X     */
X    pblock = Getpblock(Wpp);
X
X    /** merge the old and new entries */
X
X    /* count the total number of entries we're adding: */
X    for (Ep = HashEl, MatchCount = 0; Ep; Ep = Ep->Next) {
X	MatchCount += Ep->PlacesUsed;
X    }
X
X    pblock = (t_pblock *) erealloc((char *) pblock, sizeof(t_pblock) +
X	     (Wpp->NumberOfWordPlaces + MatchCount) * sizeof(t_WordPlace));
X
X    /* delete the old entry from disk */
X    if (Wpp->Offset) {
X	DeleteWordPlaces(Wpp->Offset, Wpp->WID);
X    }
X
X    /* fill in the WordPlaces */
X    for (Ep = HashEl, MatchCount = 0; Ep; Ep = Ep->Next) {
X	register int i;
X
X	for (i = 0; i < Ep->PlacesUsed; i++) {
X	    pblock->WordPlaces[pblock->NumberOfWordPlaces++] =
X					Ep->Places[i]; /* struct copy */
X	}
X    }
X
X    Wpp->Offset = 0L; /* it's invalid now... */
X    Wpp->WordPlacesInHere = 0;
X
X    /* First, let's make an index entry: */
X    if (pblock->NumberOfWordPlaces <= MaxWordPlacesInAWordBlock) {
X	(void) MkWIB(WP, pblock);
X    }
X
X    /** write out the new entry */
X    if (Wpp->WordPlacesInHere == pblock->NumberOfWordPlaces) {
X	/* In this case it all fits into the main index */
X	if (PutWordInfoIntoIndex(Wpp, (unsigned long) 0L) < 0) {
X	    extern int errno;
X	    int e = errno;
X	    fprintf(stderr, "%s: Couldn't insert word \"%s\" into the index",
X				progname, Wpp->Word);
X	    perror("");
X	    exit(1);
X	}
X    } else {
X	(void) Putpblock(Wpp, pblock);
X	if (PutWordInfoIntoIndex(Wpp, pblock->ChainStart) < 0) {
X	    extern int errno;
X	    int e = errno;
X	    fprintf(stderr, "%s: Couldn't re-insert word \"%s\" into the index",
X				progname, Wpp->Word);
X	    perror("");
X	    exit(1);
X	}
X    }
X
X    /** mark it as valid (NOTDONE) */
X
X    /** reclaim storage */
X    (void) efree((char *)pblock);
X    /* the caller *must* do SlayWordInfo(WP) */
X    (void) SlayWordInfo(Wpp);
X}
X
X#else /* NEWSYM */
Xstatic t_WordPlaceList *SymbolTable[HASHSIZ]; /* static --> initialised to 0 */
X#endif /* NEWSYM */
X
X#ifdef __GNU__
Xinline
X#endif
X#ifndef Hash
Xint
XHash(WordInfo)
X    t_WordInfo *WordInfo;
X{
X    register unsigned long n = 0;
X    register int len = WordInfo->Length;
X    register char *str = WordInfo->Word;
X
X#ifdef DUFF /* clever stuff for speedup... dmr-approved!... */
X
X#define HASHC	n = *str++ + 65599 * n
X
X    if (len > 0) {
X	register int loop = (len + 8 - 1) >> 3;
X
X	switch(len & (8 - 1)) {
X	case 0:	do {
X		HASHC;	case 7:	HASHC;
X	case 6:	HASHC;	case 5:	HASHC;
X	case 4:	HASHC;	case 3:	HASHC;
X	case 2:	HASHC;	case 1:	HASHC;
X		} while (--loop);
X	}
X
X    }
X#else /* DUFF */
X    while (len--)
X	n = *str++ + 65599 * n;
X#endif /* DUFF */
X    /**
X    return n & (HashSize - 1);
X    **/
X    return n % HashSize;
X}
X#endif
X
Xstatic int HashOK = 0;
X
Xvoid
XInitHash()
X{
X    HashOK = 1;
X}
X
X#ifndef NEWSYM
Xstatic int WordsInCache = 0;
X
X/* FIXME: this ought to taks a WordInfo and a WordPlaceList instead.
X * Using a hash table means that we can end up with really pathalogical
X * paging pehaviour.  Nearly all of lqaddfile is resident when running
X * on a Sun.  Hence, I shall be replacing this code entirely soon with
X * something that has less memory fragmentation, perhaps by coalescing
X * list members or with a tree.
X * For now, MaxWordsInCache is a parameter that you can set to zero if
X * you want.
X *
X * Also, the cache structure should be cleaver enough to avoid writing
X * out the more common words if it can, so as to minimise the number
X * of data _fetches_ that have to be done.
X * You could also argue that it should be more efficient to add new data,
X * of course.  I couldn't disagree.
X *
X * Next change required is to make AddWord do a little more of the work --
X * in particular, to call Word2WID for each new word, in an attempt to
X * make cache dumping faster.
X */
X
XAddWord(WordInfo) /* old version */
X    t_WordInfo *WordInfo;
X{
X    int Slot;
X    int GreaterOrLess = 1;
X    t_WordPlaceList *SaveOldNext;
X    t_WordPlaceList **WPL;
X
X    if (!HashOK) InitHash();
X
X    /* The following are all awfully serious internal errors.
X     * They will only happen if I make a huge coding error, whereupon
X     * they tend to happen for every word in the input...
X     */
X    if (!WordInfo) {
X	fprintf(stderr, "AddWord(0)\n");
X	return;
X    } else if (!WordInfo->Word) {
X	fprintf(stderr, "AddWord(Word=0)\n");
X	return;
X    } else if (!WordInfo->Word[0]) {
X	fprintf(stderr, "AddWord(Word[0]=0)\n");
X	return;
X#ifdef ASCIITRACE
X    } else if (AsciiTrace > 20) {
X	fprintf(stderr, "[%s.len %d]\n", WordInfo->Word, WordInfo->Length);
X#endif
X    }
X
X    Slot = Hash(WordInfo);
X
X#ifdef ASCIITRACE
X    if (AsciiTrace > 10) {
X	fprintf(stderr, "H %d %s\n", Slot, WordInfo->Word);
X    }
X#endif
X
X    if (WordInfo->Word[0] == 'q') {
X	register char *p = WordInfo->Word;
X
X	/* Words of the form qxxxxx* are not indexed.  This is so the filters
X	 * can preprocess the files without upsetting the word counts.
X	 * If you can think of a better way to do this, well, tell me!
X	 * Lee
X	 */
X	
X	for (++p; p - WordInfo->Word < WordInfo->Length; p++) {
X	    if (*p != 'x') break;
X	}
X
X	if (p - WordInfo->Word == WordInfo->Length) {
X#ifdef ASCIITRACE
X	    if (AsciiTrace > 10) {
X		(void) fprintf(stderr, "rejected %s (too boring)\n",
X					WordInfo->Word);
X	    }
X#endif
X	    return;
X	}
X    }
X
X    for (WPL = &SymbolTable[Slot]; *WPL; WPL = &((*WPL)->Next)) {
X	if ((GreaterOrLess = STRCMP((*WPL)->Word, WordInfo->Word)) <= 0) {
X	    break;
X	}
X    }
X
X    /* Insert the new word at the head of the Word Chain,
X     * i.e. at the start of the group of similar words
X     */
X    SaveOldNext = *WPL;
X
X    enew(*WPL, t_WordPlaceList);
X    (*WPL)->WordPlace = WordInfo->WordPlace; /* structure copy */
X    (*WPL)->WordPlace.FID = WordInfo->WordPlace.FID;
X    (*WPL)->Next = SaveOldNext;
X
X    if (GreaterOrLess || !SaveOldNext) {
X	(*WPL)->Word = emalloc(WordInfo->Length + 1);
X	(void) strncpy((*WPL)->Word, WordInfo->Word, (int) WordInfo->Length);
X	(*WPL)->Word[WordInfo->Length] = '\0';
X    } else {
X	/* The word is already saved, so we only need to link to it */
X	(*WPL)->Word = SaveOldNext->Word;
X    }
X    if (MaxWordsInCache && ++WordsInCache > MaxWordsInCache) {
X	void DumpCache();
X
X	DumpCache(1);
X	WordsInCache = 0;
X    }
X}
X
Xvoid
XDumpCache(CallFree)
X    int CallFree; /* call efree() if non-zero */
X{
X    extern int WriteWordChain();
X
X    register int Slot;
X    register t_WordPlaceList *WordPlaceList;
X    int WordsLeft = WordsInCache;
X    int EmptySlots = 0, UsedSlots = 0;
X    int Progress = 0;
X
X    if (WordsInCache == 0) return; /* save some work maybe */
X
X    if (AsciiTrace) {
X	fprintf(stderr, "Writing%s%d words\n",
X			(CallFree) ? " and freeing " : " ", WordsInCache);
X    }
X
X    for (Slot = 0; WordsLeft > 0 && Slot < HASHSIZ; Slot++) {
X
X	if (AsciiTrace > 1) {
X	    if (Slot >= Progress * (HASHSIZ / 16)) {
X		fputc(" 01234567890ABCDEFGHIJKL"[Progress], stderr);
X		++Progress;
X	    }
X	}
X	if (SymbolTable[Slot] == (t_WordPlaceList *) 0) {
X	    ++EmptySlots;
X	    continue;
X	} else {
X	    char *LastFreed = (char *) 0;
X
X	    ++UsedSlots;
X	    WordPlaceList = SymbolTable[Slot];
X	    WordsLeft -= WriteWordChain(WordPlaceList);
X
X	    if (CallFree) {
X		while (WordPlaceList) {
X		    register t_WordPlaceList *SavePointer;
X
X		    if (WordPlaceList->Word &&
X					WordPlaceList->Word != LastFreed) {
X			efree(WordPlaceList->Word);
X			LastFreed = WordPlaceList->Word;
X		    }
X
X		    SavePointer = WordPlaceList->Next;
X		    efree((char *) WordPlaceList);
X		    WordPlaceList = SavePointer;
X		}
X		SymbolTable[Slot] = (t_WordPlaceList *) 0;
X	    }
X	}
X    }
X
X    if (AsciiTrace) {
X	double d = UsedSlots;
X	d /= (EmptySlots + UsedSlots);
X	d *= 100.0;
X
X	fprintf(stderr, "%4.3f%% cache used -- %d out of (%d <= %d)\n",
X			d, UsedSlots, UsedSlots + EmptySlots, HASHSIZ);
X#ifdef MALLOCTRACE
X	mallocmap();
X#endif
X    }
X
X    if (WordsInCache != 0 && CallFree) {
X	WordsInCache = 0;
X    }
X}
X
X#endif /*!NEWSYM*/
X
X/*
X * $Log:	wordtable.c,v $
X * Revision 2.11  91/02/20  19:07:37  lee
X * The qxxx fix only worked if ASCIITRACE was defined!
X * 
X * Revision 2.10  90/10/06  00:51:05  lee
X * Prepared for first beta release.
X * 
X * Revision 2.9  90/10/05  23:44:30  lee
X * Major experimentation with new symbol table failed...
X * 
X * Revision 2.8  90/09/26  19:45:02  lee
X * Added call to mallocmap() in ifdef MALLTRACE.
X * 
X * Revision 2.7  90/09/20  18:58:25  lee
X * Added some comments, and deleted a needless test.  Reorderered a loop
X * in the (probably vain) hope of a speed-up in the face of paging...
X * 
X * Revision 2.6  90/09/19  20:25:44  lee
X * Don't index "qxxxxxxxx" words (this is a hook for filters...)
X * 
X * Revision 2.5  90/08/29  21:46:11  lee
X * Alpha release
X * 
X * Revision 2.4  90/08/09  19:17:37  lee
X * BSD lint and Saber
X * 
X * Revision 2.3  90/03/21  17:32:31  lee
X * new hashing function, masses, masses better -- the old one only ever
X * used abuot 6% of the available values!
X * 
X * Revision 2.2  89/10/08  20:47:47  lee
X * Working version of nx-text engine.  Addfile and wordinfo work OK.
X * 
X * Revision 2.1  89/10/02  01:16:22  lee
X * New index format, with Block/WordInBlock/Flags/BytesSkipped info.
X * 
X * Revision 1.3  89/09/17  23:05:15  lee
X * Various fixes; NumberInBlock now a short...
X * 
X * Revision 1.2  89/09/16  21:18:55  lee
X * First demonstratable version.
X * 
X * Revision 1.1  89/09/07  21:06:20  lee
X * Initial revision
X * 
X */
@@@End of lq-text/src/lqtext/wordtable.c
echo x - lq-text/src/menu/Makefile 1>&2
sed 's/^X//' >lq-text/src/menu/Makefile <<'@@@End of lq-text/src/menu/Makefile'
X# Makefile for simple curses-based menu interface.
X#
X# $Id: Makefile,v 1.3 90/10/06 01:28:02 lee Rel1-10 $
X
XPWD=menu
X
X# PERFORMANCE makes curses go faster
XEXTRA=-I../h -DPERFORMANCE
XOPT=-O -g
XDEFS= -DASCIITRACE -UBSD -DSYSV
XWHICHDBM=sdbm
X# change the next three lines to be the same as the lq-text definitions.
XDBMLIBS=$(LIBDIR)/libsdbm.a
XBCOPY=bcopy.o
X# DBMLIBS=-lndbm -linet # 386/ix with hbtcpip provides a good bcopy()
X
XCFLAGS= $(OPT) $(DEFS) -UBSD -DSYSV $(GCCF) -D$(WHICHDBM) $(EXTRA)
XCC=gcc
XTERMCAP=-lcurses
XRANLIB=ranlib
X
XTEXT=lqtext
XPROG=m # a simple example of using the library...
XPROGOBJS=example.o
XPROGSRC=example.c
XLIBDIR=../lib
XBINDIR=../bin
XLIAMLIB=$(LIBDIR)/liblq.a
XLQTEXTLIB=$(LIBDIR)/liblqtext.a
XMENULIB=liblqmenu.a
X
XPROGS=$(PROG) $(TEXT) # removed by make clean
X
XOBJS=menu.o error.o stringbox.o OldCurses.o
XSRCS=menu.c error.c stringbox.c OldCurses.c
X
Xall: $(TEXT) m
X
Xinstall: $(MENULIB) $(TEXT)
X	cp $(MENULIB) $(LIBDIR)/$(MENULIB)
X	cp $(TEXT) $(BINDIR)/$(TEXT)
X	strip $(BINDIR)/$(TEXT)
X
X$(TEXT): text.o $(LQTEXTLIB) $(LIAMLIB) $(MENULIB) $(BCOPY)
X	$(CC) $(CFLAGS) -o $(TEXT) text.o $(BCOPY) \
X	$(MENULIB) $(LQTEXTLIB) $(DBMLIBS) $(LIAMLIB) $(TERMCAP)
X
X$(MENULIB): $(OBJS)
X	rm -f $(MENULIB)
X	ar rv $(MENULIB) $(OBJS)
X	$(RANLIB) $(MENULIB)
X
X$(PROG): $(PROGOBJS)
X	$(CC) $(CFLAGS) -o $(PROG) $(OBJS) $(PROGOBJS) $(BCOPY) $(TERMCAP)
X
Xlint$(PROG): $(OBJS) $(SRCS) $(PROGSRCS)
X	lint $(CFLAGS) $(SRCS) $(TERMCAP) 2>&1 | tee lint$(PROG)
X
X# Tidy should leave the final executables, but otherwise remove all
X# generated files
Xtidy:
X	/bin/rm -f *.o core make.log .mk m.log
X
X# Clean should revert to a distribution state as far as possible
Xclean:
X	/bin/rm -f *.o core *.a $(PROGS) $(CHARGEN) make.log .mk m.log
X
Xtext.o: text.c
X	$(CC) $(CFLAGS) $(TEXTINC) -c text.c
X
X
Xdepend:
X	mkdep $(CFLAGS) *.c
X
X#
X# $Log:	Makefile,v $
X# Revision 1.3  90/10/06  01:28:02  lee
X# deleted mkdep output.
X# 
X# Revision 1.2  90/10/01  20:33:09  lee
X# Added BSD compatibility hooks and improved "make clean".
X# 
X# Revision 1.1  90/08/29  21:48:48  lee
X# Initial revision
X# 
X# Revision 2.1  89/08/07  13:52:22  lee
X# First fully working release; this is the basis for all
X# future development.
X# 
X# Revision 1.2  89/08/04  17:59:23  lee
X# Fully working with Basic Functionality.
X# Scrolling menubar, scrolling menus, moveable Info windows.
X# 
X# Revision 1.1  89/07/27  11:41:39  lee
X# Initial revision
X#
X 
X# DO NOT DELETE THIS LINE -- mkdep uses it.
X# DO NOT PUT ANYTHING AFTER THIS LINE, IT WILL GO AWAY.
X
X# IF YOU PUT ANYTHING HERE IT WILL GO AWAY
@@@End of lq-text/src/menu/Makefile
echo x - lq-text/src/menu/README 1>&2
sed 's/^X//' >lq-text/src/menu/README <<'@@@End of lq-text/src/menu/README'
XThis directory contains as much as necessary of my curses menu library
Xto demonstrate lq-text.  (and I just saw some more files I could remove...)
XThis is a fairly simple curses-based front end to the lq-text text retrieval
Xsoftware.
X
XIt has only been tested on System V Release 3.2, and almost certainly will
Xnot work on anything else without at least a little effort.
X
XSee the notes on porting below if you want to try...
X
XPlease do not ask me for the rest of the package.  If I get the time and the
Xnecessary facilities, I will make it available.  I am hoping to have a
Xversion which will run with no source changes under X windows as well as
Xunder Curses, but it is not trivial...  What you see here is a hacked version,
Xin order to minimise what I post.  Sorry.
X
X
XTo install:
X
X(1) You need to have lq-text already.
X    Make it and test that it works, for example by indexing the unix man pages.
X    In particular, check that lqphrase works with two-word phrases.  If it
X    doesn't, there is little point in proceeding.
X
X(2) You will need to edit Makefile in this directory to point to the directory
X    containing the lq-text source:
X    Change the defintion of $(NX) as appropriate -- for example,
X	NX=../../../src/lq-text/src
X    or something.
X
X(3) make text
X
X(4) ln text lqtext (if you want)
X
X(5) try it.  If you don't have working function keys, you can use ESC
X    followed by a digit (e.g. ESC 1 is the same as F1), and when you are
X    entering phrases, ^D at the start of the line will take you back to the
X    main menu just as F1 does.
X
X    **>>> You will need to have lqshow in your path for this to work. <<<**
X    **>>> You will need want to set $LQTEXTDIR, or use the -d option.
X    see the man page for lq-text for command-line options to "text".
X
X    Try
X	from the File menu, select "new words"
X	(you can type 'x' for an explanation at any point in the menus)
X
X	type some words or phrases
X
X	select "match all" from the "All Words" menu
X
X	the numbers by the phrases indicate the number of matches;
X	you can then do "browse all" from the "All Words" menu.
X
X    To exit, press "q" from the main menu, or select "Finish" from the
X    Main Menu.
X
X    When you have typed 'x', a box will appear containing an explanation.
X    You can type 'x' at this point for an explanation of what to do with
X    the box... for example, you can move the explain-box around the sceen
X    or resize it if you want.  I have no idea why you would want to do
X    this, but it can be a little fun for people who are bored... and is
X    a facility that came for free from my curses/menu package...
X
X(6) You might also like to try making "m", and running examples/vsh, which
X    is a simple shell-script.  It is not meant to be a useful shell -- just
X    a tiny demo I wrote at home in some spare time...
X
X
X(7) Now investigate internal.h and menu.h if you want to change things.
X    If you change these, go back to step (3)
X
XLee
Xsq.com
XThu Dec 14 21:06:34 EST 1989
@@@End of lq-text/src/menu/README
echo x - lq-text/src/menu/bcopy.c 1>&2
sed 's/^X//' >lq-text/src/menu/bcopy.c <<'@@@End of lq-text/src/menu/bcopy.c'
X#ifdef BCOPYTEST
X# include <stdio.h>
X#endif
X
X/* this is a simple replacement for bcopy() where the native bcopy()
X * does not handle overlapping blocks.
X * do
X *	cc -DBCOPYTEST -o bcopy bcopy.c
X * and run "./bcopy" for a simple test.  You should get three
X * identical lines of output.
X */
X
Xbcopy(src, dest, nbytes)
X    char *dest;
X    char *src;
X    int nbytes;
X{
X    /* We have to be clever about this...
X     * If src < dest then we copy from the top down
X     * otherwise, copy from the bottom up...
X     */
X    
X    register char *p, *q;
X
X    if (src < dest) {
X	for (p = &src[nbytes - 1], q = &dest[nbytes - 1]; nbytes--; q--, p--) {
X	    *q = *p;
X	}
X    } else {
X	for (p = src, q = dest; nbytes--; p++, q++) {
X	    *q = *p;
X	}
X    }
X}
X
X#ifdef BCOPYTEST
Xmain()
X{
X    char buffer[4096];
X    char *s = "The naked children hugged each other";
X
X    puts(s); /* first line */
X    (void) sprintf(&buffer[12], "%s", s);
X    bcopy(&buffer[12], buffer, strlen(s) + 1);
X    printf("[%s]\n", buffer); /* 2nd line */
X    bcopy(buffer, &buffer[12], strlen(s) + 1);
X    printf("[%s]\n", &buffer[12]); /* 3rd line */
X}
X#endif
@@@End of lq-text/src/menu/bcopy.c
echo x - lq-text/src/menu/OldCurses.c 1>&2
sed 's/^X//' >lq-text/src/menu/OldCurses.c <<'@@@End of lq-text/src/menu/OldCurses.c'
X/* Compatibility routines for older versions of curses...
X * $Id: OldCurses.c,v 1.2 90/10/04 16:27:58 lee Rel1-10 $
X *
X */
X
X#include <curses.h>
X#include <ctype.h>
X
X#ifndef A_STANDOUT
X#include "oldcurses.h"
X
X#undef CONTROL
X#define CONTROL(c) (c ^ 64)
X
X#undef wgetch
X
Xchtype
XLqwgetch(w)
X    WINDOW *w;
X{
X    int ch = wgetch(w);
X
X    if (isprint(ch)) return ch;
X
X    switch (ch) {
X    case CONTROL('^'): return KEY_HOME;
X    case CONTROL('P'): return KEY_UP;
X    case CONTROL('N'): return KEY_DOWN;
X    case CONTROL('B'): return KEY_LEFT;
X    case CONTROL('F'): return KEY_RIGHT;
X    case CONTROL('X'): return KEY_HELP; /* Xplain.... (groan) */
X    case '\033': /* Escape */
X	(void) fprintf(stderr, "ESC\007");
X	(void) fflush(stderr);
X
X	switch (ch = wgetch(w)) {
X	case 0:		return KEY_F0;
X	case 1:		return KEY_F(1);
X	case 2:		return KEY_F(2);
X	case 3:		return KEY_F(3);
X	case 4:		return KEY_F(4);
X	case 5:		return KEY_F(5);
X	case 6:		return KEY_F(6);
X	case 7:		return KEY_F(7);
X	case 8:		return KEY_F(8);
X	case 9:		return KEY_F(9);
X	case 'a': case 'A':	return KEY_F(10);
X	case 'b': case 'B':	return KEY_F(11);
X	case 'c': case 'C':	return KEY_F(12);
X	case 'd': case 'D':	return KEY_F(13);
X	case 'e': case 'E':	return KEY_F(14);
X	case 'f': case 'F':	return KEY_F(15);
X	case 'h':	return KEY_HELP;
X	}
X	break;
X    }
X    return ch;
X}
X
Xvoid
Xbeep()
X{
X    (void) putc('\b', stderr);
X    (void) fflush(stderr);
X}
X
Xvoid
Xbox(win, vert, hor)
X    WINDOW *win;
X    int vert;
X    int hor;
X{
X#undef box
X    if (hor == 0) hor = ACS_HLINE;
X    if (vert == 0) vert = ACS_VLINE;
X    box(win, vert, hor);
X}
X
Xvoid
XLqattrset(win, attr)
X    WINDOW *win;
X    int attr;
X{
X    if (attr) {
X	wstandout(win);
X    } else {
X	wstandend(win);
X    }
X}
X
Xwnoutrefresh(win)
X    WINDOW *win;
X{
X    touchwin(win);
X}
X#endif
X
X/* $Log:	OldCurses.c,v $
X * Revision 1.2  90/10/04  16:27:58  lee
X * SysV compat improved.
X * 
X * Revision 1.1  90/10/03  21:54:04  lee
X * Initial revision
X * 
X *
X */
@@@End of lq-text/src/menu/OldCurses.c
echo x - lq-text/src/menu/oldcurses.h 1>&2
sed 's/^X//' >lq-text/src/menu/oldcurses.h <<'@@@End of lq-text/src/menu/oldcurses.h'
X/* oldcurses.h -- compatibility with pre-System V.3 curses...
X * $Id: oldcurses.h,v 1.2 90/10/04 16:28:31 lee Rel1-10 $
X */
X
Xtypedef int chtype;
X
X#define ACS_LARROW	'>'
X#define ACS_RARROW	'<'
X#define ACS_HLINE	'='
X#define ACS_VLINE	'|'
X#define ACS_LRCORNER	'+'
X#define ACS_LLCORNER	'+'
X
X/* Line drawing: */
X#define ACS_BSSS	'+'	/* T-piece */
X#define ACS_SBSS	'+'	/* -| */
X#define ACS_SSBS	'+'	/* inverted T-piece */
X#define ACS_SSSB	'+'	/* |- */
X#define ACS_BBSS	'+'	/* top right corner */
X#define ACS_BSSB	'+'	/* bottom left corner */
X
X#define KEY_DOWN	257
X#define KEY_UP  	258
X#define KEY_LEFT	259
X#define KEY_RIGHT	260
X#define KEY_HELP	261
X#define KEY_HOME	262
X#define KEY_F0		300
X#define KEY_F(n)	(KEY_F0+n)
X
X#undef getch
X#define getch()	Lqwgetch(stdscr)
X#define wgetch	Lqwgetch
X
X#undef box
X#define box LqBox
X
X#define A_STANDOUT 1
X#undef standout
X#undef standend
X#define wattrset Lqattrset
X#define attrset(a) Lqattrset(stdscr, a)
X#define keypad(win, bool)	1 /* ignore this one please */
X
X/* $Log:	oldcurses.h,v $
X * Revision 1.2  90/10/04  16:28:31  lee
X * SysV compat improved.
X * 
X * Revision 1.1  90/10/03  21:56:32  lee
X * Initial revision
X * 
X *
X */
@@@End of lq-text/src/menu/oldcurses.h
echo end of part 08
-- 
Liam R. E. Quin,  lee@sq.com, SoftQuad Inc., Toronto, +1 (416) 963-8337