home *** CD-ROM | disk | FTP | other *** search
/ Source Code 1992 March / Source_Code_CD-ROM_Walnut_Creek_March_1992.iso / usenet / altsrcs / 2 / 2952 < prev    next >
Internet Message Format  |  1991-03-03  |  60KB

  1. From: lee@sq.sq.com (Liam R. E. Quin)
  2. Newsgroups: alt.sources
  3. Subject: lq-text Full Text Retrieval Database Part 04/13
  4. Message-ID: <1991Mar4.020316.16307@sq.sq.com>
  5. Date: 4 Mar 91 02:03:16 GMT
  6.  
  7. : cut here --- cut here --
  8. : To unbundle, sh this file
  9. #! /bin/sh
  10. : part 04
  11. echo x - lq-text/src/liblqtext/Phrase.c 1>&2
  12. sed 's/^X//' >lq-text/src/liblqtext/Phrase.c <<'@@@End of lq-text/src/liblqtext/Phrase.c'
  13. X/* Phrase.c -- Copyright 1989 Liam R. Quin.  All Rights Reserved.
  14. X * This code is NOT in the public domain.
  15. X * See the file COPYRIGHT for full details.
  16. X */
  17. X
  18. X/*
  19. X * Deal with (WID, FID, Offfset) triples
  20. X * Liam Quin, September 1989
  21. X *
  22. X * $Id: Phrase.c,v 1.11 91/02/22 18:22:59 lee Rel1-10 $
  23. X *
  24. X * $Log:    Phrase.c,v $
  25. X * Revision 1.11  91/02/22  18:22:59  lee
  26. X * Improved a trace message.
  27. X * 
  28. X * Revision 1.10  90/10/06  00:11:57  lee
  29. X * Prepared for first beta release.
  30. X * 
  31. X * Revision 1.9  90/10/04  17:10:43  lee
  32. X * Case matching now works on one-word phrases.
  33. X * 
  34. X * Revision 1.8  90/10/03  20:47:06  lee
  35. X * changed an int to an unsigned long
  36. X * 
  37. X * Revision 1.7  90/08/29  21:46:39  lee
  38. X * Alpha release.
  39. X * 
  40. X * Revision 1.6  90/03/29  19:59:05  lee
  41. X * Now passes gcc -Wall
  42. X * 
  43. X * Revision 1.5  90/03/19  00:02:23  lee
  44. X * Simplified phrase matching greatly by adding new routine, and also
  45. X * improved checking of word flags and generation of ModifiedString,
  46. X * the canonical phrase, in String2Phrase.
  47. X * 
  48. X * Revision 1.4  90/03/16  22:43:15  lee
  49. X * After Richard's consummate help...
  50. X * fixed two severe bugs in matching, and started to simplify
  51. X * MakeMatches().
  52. X * 
  53. X * Revision 1.3  90/03/14  21:02:14  lee
  54. X * Before Richard...
  55. X * 
  56. X * Revision 1.2  90/03/12  22:39:30  lee
  57. X * prepared for general release.
  58. X * 
  59. X * Revision 1.1  89/09/17  23:01:34  lee
  60. X * Initial revision
  61. X * 
  62. X *
  63. X */
  64. X
  65. X/** Unix system calls that need to be declared: **/
  66. Xextern void exit();
  67. X/** Unix/C Library Functions: **/
  68. Xextern unsigned int sleep();
  69. X#ifndef tolower
  70. X extern int tolower();
  71. X#endif
  72. Xextern int strlen();
  73. Xextern char *strcpy();
  74. X/** lqtext functions: **/
  75. Xextern int TooCommon();
  76. Xextern char *UnFlag();
  77. X/** **/
  78. X
  79. X#include "globals.h" /* defines and declarations for database filenames */
  80. X
  81. X#include <stdio.h> /* stderr, also for fileinfo.h */
  82. X#include <fcntl.h>
  83. X#include <malloc.h>
  84. X#include <sys/types.h>
  85. X#include <ctype.h>
  86. X
  87. X#include "fileinfo.h" /* for wordinfo.h */
  88. X#include "wordinfo.h"
  89. X#include "pblock.h"
  90. X#include "phrase.h"
  91. X#include "wordrules.h"
  92. X
  93. X#include "emalloc.h"
  94. X
  95. X#ifndef STREQ
  96. X# define STREQ(boy,girl) ((*(boy) == *(girl)) && (!strcmp((boy),(girl))))
  97. X#endif
  98. X
  99. X#ifndef new
  100. X# define new(type) ((type *) emalloc(sizeof (type)))
  101. X#endif
  102. X
  103. X#ifndef MAXPHRASELEN
  104. X# define MAXPHRASELEN 2000
  105. X#endif
  106. X
  107. Xextern int AsciiTrace;
  108. X
  109. Xt_Phrase *
  110. XString2Phrase(String)
  111. X    char *String;
  112. X{
  113. X    extern t_WordInfo *WID2WordInfo();
  114. X    extern t_WID Word2WID();
  115. X    extern char *WordRoot();
  116. X
  117. X    t_Phrase *Result;
  118. X    t_PhraseItem **ThisWord;
  119. X    /* (* 3 because in the worst case, "a a a" expands to "[a] [a] [a]") */
  120. X    register char *p;
  121. X    register char *q;
  122. X    char *LastStart = 0;
  123. X    char *PrevLastEnd = (char *) 0;
  124. X    int InWord = 0;
  125. X    int Flags = 0;
  126. X    int FoundLetters = 0;
  127. X
  128. X    if (AsciiTrace > 50) {
  129. X    fprintf(stderr, "String2Phrase(%s)\n", String);
  130. X    }
  131. X    Result = (t_Phrase *) emalloc(sizeof(t_Phrase));
  132. X    Result->Next = (t_Phrase *) 0;
  133. X    p = Result->ModifiedString = emalloc(strlen(String) * 3 + 1);
  134. X
  135. X    Result->HasUnknownWords = 0;
  136. X
  137. X    *(ThisWord = &Result->Words) = (t_PhraseItem *) 0;
  138. X
  139. X    /* March along the supplied phrase, looking for keywords.
  140. X     * surround unindexed or short words with [brackets].
  141. X     * Also converts to lower case and strips plurals.
  142. X     */
  143. X    for (q = String; /*LOTSOFTIMES*/; q++) {
  144. X
  145. X    if (AsciiTrace > 50) {
  146. X        fputc(*q, stderr);
  147. X    }
  148. X
  149. X    if (!InWord && !StartsWord(*q)) {
  150. X        if (!*q) {
  151. X        break;
  152. X        } else {
  153. X        if (!LastStart) continue;
  154. X        }
  155. X    }
  156. X
  157. X    if (!InWord) {
  158. X        LastStart = q;
  159. X        if (StartsWord(*q)) {
  160. X        InWord = 1;
  161. X        }
  162. X        continue;
  163. X    } else if (isalpha(*q)) {
  164. X        /* in a word and found letters, so remember in case we skip
  165. X         * this word...
  166. X         */
  167. X        FoundLetters = 1;
  168. X    }
  169. X
  170. X    /* ASSERT: inword == 1 */
  171. X
  172. X    if (*q == '\'') {
  173. X        if (!WithinWord(q[1])) {
  174. X        InWord = 0;
  175. X        }
  176. X    }
  177. X
  178. X    if (!*q || !WithinWord(*q)) {
  179. X        InWord = 0;
  180. X    }
  181. X
  182. X
  183. X    if (LastStart && !InWord) {
  184. X        int Length = q - LastStart;
  185. X        int UsedABracket = 0;
  186. X
  187. X        if (p > Result->ModifiedString) *p++ = ' ';
  188. X
  189. X        /* we have reached the end of a word, is it long enough? */
  190. X        if (!FoundLetters) {
  191. X        *p++ = '[';
  192. X        UsedABracket = 1;
  193. X        } else if (Length < MinWordLength) {
  194. X        *p++ = '[';
  195. X        UsedABracket = 1;
  196. X        if (FoundLetters) {
  197. X            Flags |= WPF_LASTHADLETTERS;
  198. X            FoundLetters = 0;
  199. X        }
  200. X        } else {
  201. X        t_WID WID;
  202. X        t_WordInfo *W;
  203. X        char SaveEnd = (*q);
  204. X        t_WordInfo TryRoot;
  205. X        register char *p2;
  206. X        char RootBuffer[MaxWordLength + 1];
  207. X        char *R = RootBuffer;
  208. X
  209. X        /* Add the word to the chain, too: */
  210. X        *q = '\0';
  211. X
  212. X        FoundLetters = 0; /* unnecessary now (?) */
  213. X        TryRoot.Length = Length;
  214. X        TryRoot.WordPlace.Flags = Flags;
  215. X        if (isupper(*LastStart)) {
  216. X            TryRoot.WordPlace.Flags |= WPF_UPPERCASE;
  217. X        }
  218. X        Flags = 0;
  219. X
  220. X        for (p2 = LastStart; *p2; p2++) {
  221. X            *R++ = isupper(*p2) ? tolower(*p2) : *p2;
  222. X        }
  223. X        *R = '\0';
  224. X        TryRoot.Word = RootBuffer;
  225. X
  226. X        R = WordRoot(&TryRoot);
  227. X
  228. X        if (TooCommon(&TryRoot)) {
  229. X            *p++ = '[';
  230. X            *p++ = '*';
  231. X            UsedABracket = 1;
  232. X            Flags |= WPF_LASTWASCOMMON;
  233. X            if (AsciiTrace > 10) {
  234. X            fprintf(stderr, " Common(%s) ", TryRoot.Word);
  235. X            }
  236. X        } else if (!(WID = Word2WID(TryRoot.Word, TryRoot.Length)) ||
  237. X                (W = WID2WordInfo(WID)) == (t_WordInfo *) 0) {
  238. X            *p++ = '[';
  239. X            *p++ = WID ? '@' : '?';
  240. X            UsedABracket = 1;
  241. X            if (AsciiTrace > 10) {
  242. X            fprintf(stderr, " Unknown(%s) ", TryRoot.Word);
  243. X            }
  244. X            Result->HasUnknownWords++;
  245. X        } else {
  246. X            if ((*ThisWord = new(t_PhraseItem)) == (t_PhraseItem *) 0) {
  247. X            fprintf(stderr,
  248. X            "Not enough memory for PHRASE \"%s\"", String);
  249. X            return (t_Phrase *) 0;
  250. X            }
  251. X            W->WordPlace.Flags |= TryRoot.WordPlace.Flags;
  252. X            if (PrevLastEnd == (char *) 0) {
  253. X            W->WordPlace.StuffBefore = 0;
  254. X            } else {
  255. X            W->WordPlace.StuffBefore = LastStart - PrevLastEnd;
  256. X            }
  257. X            PrevLastEnd = &q[1];
  258. X
  259. X            if (AsciiTrace) {
  260. X            fprintf(stderr, "Word %s --> %s, %lu matches\n",
  261. X                LastStart,
  262. X                UnFlag(W, W->WordPlace.Flags),
  263. X                W->NumberOfWordPlaces);
  264. X            }
  265. X            /* point to the new space */
  266. X            (*ThisWord)->Word = W;
  267. X            (*ThisWord)->WordStart = LastStart;
  268. X            (*ThisWord)->Next = (t_PhraseItem *) 0;
  269. X            (*ThisWord)->SearchIndex = 0L;
  270. X            ThisWord = &(*ThisWord)->Next;
  271. X
  272. X            /** (void) strcpy(p, R); **/
  273. X            /** p += TryRoot.Length; **/
  274. X
  275. X            (void) strcpy(p, LastStart);
  276. X            p += q - LastStart; /* q points one beyond the end */
  277. X
  278. X            LastStart = q;
  279. X
  280. X        }
  281. X        *q = SaveEnd;
  282. X        }
  283. X        while (LastStart < q) {
  284. X        *p++ = *LastStart++;
  285. X        }
  286. X        if (UsedABracket) {
  287. X        *p++ = ']';
  288. X        }
  289. X        LastStart = 0;
  290. X    } /* if */
  291. X    if (!*q) break;
  292. X    } /* for */
  293. X    *p= '\0';
  294. X
  295. X    if (ThisWord == &Result->Words) {
  296. X    /* There were no words in the phrase! */
  297. X    return (t_Phrase *) 0;
  298. X    }
  299. X
  300. X    Result->OriginalString = emalloc(q - String + 2);
  301. X    (void) strcpy(Result->OriginalString, String);
  302. X
  303. X    Result->NumberOfMatches = 0;
  304. X    Result->Matches = (t_MatchList *) 0;
  305. X    if (AsciiTrace > 1) {
  306. X    fprintf(stderr, "phrase \"%s\",\n", Result->OriginalString, String);
  307. X    fprintf(stderr, "Canonical form \"%s\"\n", Result->ModifiedString);
  308. X    }
  309. X    return Result;
  310. X}
  311. X
  312. X#define MaxDistance 20
  313. X
  314. Xt_Answer *
  315. XGetFiles(Phrase)
  316. X    t_Phrase *Phrase;
  317. X{
  318. X    char *MakeOneDescription();
  319. X
  320. X    t_Answer *Result = 0;
  321. X    t_Answer **RP = &Result;
  322. X    t_MatchList *MP;
  323. X    t_FID LastFID;
  324. X    unsigned long ThisFIDNumberOfMatches = 0L;
  325. X
  326. X    if (!Phrase || !Phrase->Matches) {
  327. X    return Result;
  328. X    }
  329. X
  330. X    LastFID = Phrase->Matches->Match->Where->FID;
  331. X
  332. X    for (MP = Phrase->Matches; MP; MP = MP->Next) {
  333. X    if (MP->Match->Where->FID != LastFID) {
  334. X        char *p;
  335. X        
  336. X        p = MakeOneDescription(LastFID, ThisFIDNumberOfMatches);
  337. X
  338. X        if ((*RP = new(t_Answer)) == (t_Answer *) 0) {
  339. X        return Result;
  340. X        }
  341. X        (*RP)->Answer = p;
  342. X        RP = &(*RP)->Next;
  343. X        *RP = (t_Answer *) 0;
  344. X        ThisFIDNumberOfMatches = 0L;
  345. X    } else {
  346. X        ++ThisFIDNumberOfMatches;
  347. X    }
  348. X
  349. X    LastFID = MP->Match->Where->FID;
  350. X    }
  351. X
  352. X    if (ThisFIDNumberOfMatches) {
  353. X    char *p = MakeOneDescription(LastFID, ThisFIDNumberOfMatches);
  354. X
  355. X    if ((*RP = new(t_Answer)) == (t_Answer *) 0) {
  356. X        return Result;
  357. X    }
  358. X    (*RP)->Answer = p;
  359. X    RP = &(*RP)->Next;
  360. X    *RP = (t_Answer *) 0;
  361. X    ThisFIDNumberOfMatches = 0L;
  362. X    }
  363. X
  364. X    return Result;
  365. X}
  366. X
  367. Xchar *
  368. XMakeOneDescription(FID, NumberOfMatches)
  369. X    t_FID FID;
  370. X    unsigned long NumberOfMatches;
  371. X{
  372. X    extern char *ctime();
  373. X    extern t_FileInfo *GetFileInfo();
  374. X
  375. X    char *Date;
  376. X    char *p;
  377. X    t_FileInfo *FileInfo;
  378. X    char NumBuf[20];
  379. X
  380. X    if (!FID) return (char *) 0;
  381. X
  382. X    if (!(FileInfo = GetFileInfo(FID))) return (char *) 0;
  383. X
  384. X    Date = ctime(&(FileInfo->Date));
  385. X    /** Tue Oct  3 00:57:11 BST 1989 **/
  386. X    Date[10] = '\0';
  387. X
  388. X    (void) sprintf(NumBuf, "%lu", NumberOfMatches);
  389. X
  390. X    p = emalloc((unsigned) (strlen(FileInfo->Name) + strlen(NumBuf) + 11));
  391. X    (void) sprintf(p, "%-.5s %s %s", NumBuf, Date, FileInfo->Name);
  392. X    efree(FileInfo->Name);
  393. X    efree(FileInfo);
  394. X    return p;
  395. X}
  396. X
  397. Xvoid
  398. XResetPhraseMatch(Phrase)
  399. X    t_Phrase *Phrase;
  400. X{
  401. X    t_PhraseItem *Word;
  402. X
  403. X    if (!Phrase || !Phrase->Words) return;
  404. X
  405. X    for (Word = Phrase->Words; Word; Word = Word->Next) {
  406. X    Word->SearchIndex = 0;
  407. X    }
  408. X    Phrase->NumberOfMatches = 0;
  409. X}
  410. X
  411. X/* Default is to check case, etc. only if given in the input phrase.
  412. X * This is an enum from phrase.h, and only used in MakeMatches().
  413. X */
  414. X
  415. Xextern t_PhraseCaseMatch PhraseMatchLevel;
  416. X
  417. Xlong
  418. XMakeMatches(Phrase)
  419. X    t_Phrase *Phrase;
  420. X{
  421. X    /* Each word has a pointer (SearchIndex) to the last Word Place
  422. X     * that was examined.  This enables an O(NumberOfWords) search instead
  423. X     * of O(NumberOfWords * NumberOfWords) search.
  424. X     */
  425. X    static int ContinuesMatch();
  426. X
  427. X    unsigned long PIFB; /* PlaceInFirstBlock */
  428. X    t_MatchList **MLPP = &(Phrase->Matches);
  429. X    t_Match **MPP;
  430. X    t_Match **OldMatch;
  431. X    t_WordPlace *pp;
  432. X    t_PhraseItem *Word;
  433. X    long Result = 0L;
  434. X    long LastResult = (-1L); /* to detect new matches */
  435. X    t_PhraseItem *LeastWord;
  436. X    int HowGood;
  437. X
  438. X    if (!Phrase) {
  439. X    return 0L;
  440. X    }
  441. X
  442. X    ResetPhraseMatch(Phrase);
  443. X    /* Each iteration over this list either produces a match or rejects a
  444. X     * possible phrase starting place.
  445. X     */
  446. X
  447. X    if (AsciiTrace > 1) {
  448. X    fprintf(stderr, "Match(%s)\n", Phrase->ModifiedString);
  449. X    }
  450. X
  451. X    /* A phrase with garbage words can't match anything */
  452. X    if (Phrase->HasUnknownWords && PhraseMatchLevel != PCM_AnyCase) {
  453. X    return 0L;
  454. X    }
  455. X
  456. X    /* Ensure that the matches for the first word have been read */
  457. X    if (Phrase->Words->Word->WordPlacesInHere <
  458. X            Phrase->Words->Word->NumberOfWordPlaces) {
  459. X    extern t_WordPlace *GetWordPlaces();
  460. X    t_WordInfo *W = Phrase->Words->Word; /* less indirection! */
  461. X
  462. X    if (W->WordPlaces) {
  463. X        (void) efree(W->WordPlaces);
  464. X    }
  465. X
  466. X    W->WordPlaces = GetWordPlaces(
  467. X        W->WID,
  468. X        W->WordPlaceStart,
  469. X        (unsigned) WIDBLOCKSIZE - (W->WordPlaceStart - W->DataBlock),
  470. X        W->Offset,
  471. X        W->NumberOfWordPlaces
  472. X    );
  473. X    W->WordPlacesInHere = W->NumberOfWordPlaces;
  474. X    }
  475. X
  476. X    /* Find the word in the phrase with least matches: */
  477. X    LeastWord = Phrase->Words;
  478. X    for (Word = Phrase->Words; Word; Word = Word->Next) {
  479. X    if (Word->Word->NumberOfWordPlaces <
  480. X                    LeastWord->Word->NumberOfWordPlaces) {
  481. X        LeastWord = Word;
  482. X    }
  483. X    }
  484. X
  485. X    /* For each match in the first word in the phrase: */
  486. X    for (PIFB = 0; PIFB < Phrase->Words->Word->NumberOfWordPlaces; PIFB++) {
  487. X    t_WordPlace *LastFOP = (t_WordPlace *) 0;
  488. X
  489. X    /* The idea is that the next two loops are are likely to reduce
  490. X     * considerably the number of places we have to consider in the
  491. X     * case that the first word in the phrase has a lot of matches
  492. X     * and there is a subsequent word with relatively few matches.
  493. X     * Experiments suggest that this is fairly common.
  494. X     *
  495. X     * This is still a nearly (i.e. slightly-better-than) linear
  496. X     * algorithm w.r.t the total number of matches in all of the
  497. X     * words added up.  Note that I alter LeastWord->SearchIndex in
  498. X     * one of the two loops that follow, so when WordPlaces from that
  499. X     * word are considered, we don't have to look at any twice.
  500. X     *
  501. X     * In order to do better, one would have to be able to avoid
  502. X     * looking at some or (better!) most of the WordPlaces.
  503. X     *
  504. X     * For example, not fetching so many from disk:
  505. X     * if we didn't do the fetches until we needed to, and we gave
  506. X     * GetWordPlaces a minimum FID to look for, we might be able
  507. X     * to reduce things by (say) 15%.
  508. X     * If all of the FIDS were stored separately, we would not
  509. X     * have to look at the (Block, Word, Flags, StuffBefore) stuff at
  510. X     * all, and that would be much faster.  One way to do that might be
  511. X     * to store the list of FIDs with the word (as now), and perhaps
  512. X     * some flags and the count of words/fid, and to store the rest
  513. X     * in a per-file data structure.
  514. X     *
  515. X     * That would be a major, major hack...
  516. X     *                     ... sigh.o
  517. X     *
  518. X     */
  519. X
  520. X    while (LeastWord->Word->WordPlaces[LeastWord->SearchIndex].FID <
  521. X            Phrase->Words->Word->WordPlaces[PIFB].FID) {
  522. X        if (++(LeastWord->SearchIndex) >=
  523. X                    LeastWord->Word->NumberOfWordPlaces) {
  524. X        goto GiveUp;
  525. X        }
  526. X    }
  527. X
  528. X    while (Phrase->Words->Word->WordPlaces[PIFB].FID <
  529. X        LeastWord->Word->WordPlaces[LeastWord->SearchIndex].FID) {
  530. X        if (++PIFB >= Phrase->Words->Word->NumberOfWordPlaces) {
  531. X        goto GiveUp;
  532. X        }
  533. X    }
  534. X
  535. X    /* The following comment tells Sabre_C not to moan about "if (0)" */
  536. X    /*SUPPRESS558*/
  537. X    if (0) {
  538. XGiveUp:
  539. X        break;
  540. X    }
  541. X    /* end of attempted speed improvement */
  542. X
  543. X    /* Optimistically allocate a new match: */
  544. X    if (1 || Result != LastResult) {
  545. X        *MLPP = (t_MatchList *) emalloc(sizeof(t_MatchList));
  546. X        (*MLPP)->Match = (t_Match *) 0;
  547. X        OldMatch = MPP = &((*MLPP)->Match);
  548. X        MLPP = &(*MLPP)->Next;
  549. X        *MLPP = (t_MatchList *) 0;
  550. X    }
  551. X    LastResult = Result;
  552. X
  553. X    pp = &Phrase->Words->Word->WordPlaces[Phrase->Words->SearchIndex = PIFB];
  554. X    /* When we have a partially completed match,
  555. X     * FOP (declared below) will point to the WordPlace currently
  556. X     * being considered to see if it extends the partial match;
  557. X     * LastFOP points to the previous WordPlace in the match.
  558. X     */
  559. X
  560. X    /* For each word in the phrase: */
  561. X    for (Word = Phrase->Words; Word; Word = Word->Next) {
  562. X        int GotOne = 0;
  563. X
  564. X        /* Ensure that the matches word have been read */
  565. X        if (Word->Word->WordPlacesInHere <
  566. X                Word->Word->NumberOfWordPlaces) {
  567. X        extern t_WordPlace *GetWordPlaces();
  568. X        t_WordInfo *W = Word->Word; /* less indirection! */
  569. X
  570. X        if (W->WordPlaces) {
  571. X            (void) efree(W->WordPlaces);
  572. X        }
  573. X        W->WordPlaces = GetWordPlaces(
  574. X            W->WID,
  575. X            W->WordPlaceStart,
  576. X            (unsigned) WIDBLOCKSIZE - (W->WordPlaceStart - W->DataBlock),
  577. X            W->Offset,
  578. X            W->NumberOfWordPlaces
  579. X        );
  580. X        W->WordPlacesInHere = W->NumberOfWordPlaces;
  581. X        }
  582. X
  583. X        /* For each occurrence of that word: */
  584. X        for (; Word->SearchIndex < Word->Word->NumberOfWordPlaces;
  585. X                            ++Word->SearchIndex) {
  586. X        register t_WordPlace *FOP =
  587. X            &Word->Word->WordPlaces[Word->SearchIndex];
  588. X        
  589. X#if 0
  590. X        /* Speedup -- binary search to find next candidate...
  591. X         * this is commented out because it actually seems to
  592. X         * make things run slower!
  593. X         */
  594. X        {
  595. X            int low = Word->SearchIndex;
  596. X            int high = Word->Word->NumberOfWordPlaces - 1;
  597. X            t_WordPlace *Places = Word->Word->WordPlaces;
  598. X            int guess = (high + low) / 2;
  599. X
  600. X            while (low < high) {
  601. X            if (Places[guess].FID < pp->FID) {
  602. X                /* not gone far enough */
  603. X                low = guess + 1;
  604. X            } else {
  605. X                high = guess;
  606. X            }
  607. X            guess = (high + low) / 2;
  608. X            }
  609. X            if (guess != Word->SearchIndex) {
  610. X            Word->SearchIndex = guess;
  611. X            FOP = &Word->Word->WordPlaces[Word->SearchIndex];
  612. X            }
  613. X        }
  614. X#endif
  615. X
  616. X        if (!LastFOP) {
  617. X            LastFOP = FOP;
  618. X        }
  619. X
  620. X        /** So:
  621. X         ** | int PIFB = each match in the first word in the phrase
  622. X         ** | t_WordPlace *pp = each match in the phrase
  623. X         **    | t_PhraseItem *Word = each word in the phrase
  624. X         **       | unsigned SearchIndex = each match of that word
  625. X         **       | t_WordPlace *FOP = each occurrence of that word
  626. X         **
  627. X         ** Hence, we are comparing pp and FOP, hoping that each time
  628. X         ** round the (Word) loop we will advance FOP.
  629. X         ** Once we have decided that FOP and pp relate to the
  630. X         ** same file and that FOP is no earlier than pp in the
  631. X         ** file, we must then check that FOP is advancing the
  632. X         ** chain by comparing it to the previous element in the
  633. X         ** list (LastFOP).
  634. X         **
  635. X         ** When we break from this inner list, we must either have
  636. X         ** eliminated this particular (PIFB) as starting a match-
  637. X         ** chain, or have decided that we have extended the
  638. X         ** current match chain (by setting GotOne).
  639. X         **/
  640. X
  641. X
  642. X        if (LastFOP == FOP) {
  643. X            HowGood = CheckFlags(Word->Word, FOP);
  644. X        } else {
  645. X            HowGood = ContinuesMatch(Word->Word, pp, LastFOP, FOP);
  646. X        }
  647. X
  648. X        switch (HowGood) {
  649. X        case 0:
  650. X            /* G O T C H A !!!! */
  651. X            /* extend the HitList, since it's OK so far. */
  652. X
  653. X            *MPP = (t_Match *) emalloc(sizeof(t_Match));
  654. X            (*MPP)->WID = Word->Word->WID;
  655. X            (*MPP)->Where = FOP;
  656. X            (*MPP)->Next = (t_Match *) 0;
  657. X            MPP = &(*MPP)->Next;
  658. X            GotOne++;
  659. X            break;
  660. X        case 1: /* gone too far */
  661. X            if (AsciiTrace > 10) {
  662. X            t_WordInfo WW;
  663. X            
  664. X            WW = *(Word->Word);
  665. X
  666. X            if (LastFOP == FOP) {
  667. X                /* UnFlag() returns a pointer to a static buffer,
  668. X                 * so I have to use two printf() calls here.
  669. X                 */
  670. X                fprintf(stderr, "Reject(%s (%d) != ",
  671. X                    UnFlag(&WW, WW.WordPlace.Flags),
  672. X                    WW.WordPlace.Flags);
  673. X                fprintf(stderr, "%s (%d)) [flags]\n",
  674. X                    UnFlag(&WW, FOP->Flags), FOP->Flags);
  675. X            } else {
  676. X                fprintf(stderr, "Reject(%s) -- too far\n",
  677. X                UnFlag(&WW, WW.WordPlace.Flags));
  678. X            }
  679. X            }
  680. X            break;
  681. X        case -1:
  682. X            continue; /* not there yet */
  683. X        default:
  684. X            fprintf(stderr, "\n\rInternal Error %s: %d\n", __FILE__,
  685. X                            __LINE__ - 1);
  686. X            (void) sleep(4); /* for curses stuff... */
  687. X            exit(1);
  688. X        }
  689. X
  690. X        /* Remember where we got up to... so that we can extend
  691. X         * the list when we start looking at the next word.
  692. X         */
  693. X        LastFOP = FOP;
  694. X
  695. X        if (AsciiTrace >= 4) {
  696. X            t_WordInfo WW;
  697. X            
  698. X            WW = *(Word->Word);
  699. X            /* UnFlag() returns a pointer to a static buffer */
  700. X            fprintf(stderr, "Partial match %s",
  701. X                UnFlag(&WW, Word->Word->WordPlace.Flags));
  702. X            fprintf(stderr, "(Word (%s,%lu,%u) in file %lu)\n",
  703. X                    UnFlag(&WW, FOP->Flags),
  704. X                    FOP->BlockInFile, FOP->WordInBlock,
  705. X                    FOP->FID
  706. X            );
  707. X        }
  708. X        /* If we got to here, we extended the list, which is fine;
  709. X         * otherwise, if we hit a continue, we try to carry on
  710. X         * looking at matches of this word, and if we hit a break
  711. X         * before we set "GotOne", we give up on this match
  712. X         * altogether.
  713. X         */
  714. X        break;
  715. X        } /* For each occurrence of that word: */
  716. X
  717. X        if (!GotOne) {
  718. X        t_Match *MP;
  719. X        /* This word isn't here, so neither is the phrase found
  720. X         * in this file starting here.
  721. X         */
  722. X
  723. X        for (MP = (*OldMatch); MP != (t_Match *) 0; /*void*/) {
  724. X            t_Match *Next = MP->Next;
  725. X
  726. X            efree((char *) MP);
  727. X            MP = Next;
  728. X        }
  729. X
  730. X        *OldMatch = (t_Match *) 0;
  731. X        break;
  732. X        } else {
  733. X        /* If we've reached the end of the phrase, i.e. if
  734. X         * Word->Next is zero, we have successfully added a new
  735. X         * phrase!
  736. X         */
  737. X        if (Word->Next == (t_PhraseItem *) 0) {
  738. X            if (AsciiTrace > 10) {
  739. X            fprintf(stderr, "Result now %d\n", Result + 1);
  740. X            }
  741. X            Result++;
  742. X        }
  743. X        }
  744. X
  745. X    } /* end for (each word in the phrase) */
  746. X    } /* end (for each FID/Offset pair in the first word */
  747. X    return Phrase->NumberOfMatches = Result;
  748. X}
  749. X
  750. X
  751. Xstatic int
  752. XContinuesMatch(QueryWord, First, Prev, New)
  753. X    t_WordInfo *QueryWord;
  754. X    t_WordPlace *First;
  755. X    t_WordPlace *Prev;
  756. X    t_WordPlace *New;
  757. X{
  758. X    /* Return Value is
  759. X     *  -1 --- if New occurs before Prev (and thus isn't part of the match)
  760. X     *  0  --- if it's the next word in the match
  761. X     *  +1 --- if we've gone past it
  762. X     * Note: you can use these values in a switch() if you want.
  763. X     */
  764. X
  765. X    /* First check we are looking at the right file:
  766. X     * Have we gone far enough?
  767. X     */
  768. X    if (New->FID < First->FID) {
  769. X    return -1; /* not far enough */
  770. X    } else if (New->FID > First->FID) {
  771. X    return 1; /* too far */
  772. X    } else if (Prev == New) {
  773. X    return 0;
  774. X    }
  775. X
  776. X    /* Hey everybody, they're the same!
  777. X     * That means that this might be a candidate for a MATCH!!!!
  778. X     */
  779. X    
  780. X    /* if (SimplyAnywhereWillDo) { OK; break; } */
  781. X
  782. X    /* Clearly later words in the phrase can't be in earlier
  783. X     * blocks...
  784. X     */
  785. X    if (New->BlockInFile < First->BlockInFile) {
  786. X    /* Although we are in the right file, we have not
  787. X     * yet reached the correct offset.
  788. X     */
  789. X    return -1;
  790. X    } 
  791. X
  792. X    /* If we get to here,
  793. X     * . we are in the right file
  794. X     * . we are at least as far into the file as the start
  795. X     *   of the phrase
  796. X     */
  797. X
  798. X    /* Now check that we are a reasonable distance past
  799. X     * the preceding word (checking that we are not on the first
  800. X     * match in the list, of course):
  801. X     */
  802. X    if (New->BlockInFile < Prev->BlockInFile) {
  803. X    /* not gone far enough */
  804. X    return -1;
  805. X    }
  806. X    if (New->BlockInFile > Prev->BlockInFile + 1) {
  807. X    /* If they are more than one block apart, I
  808. X     * don't believe them to be part of a phrase!
  809. X     */
  810. X    return 1;
  811. X    }
  812. X    if (New->BlockInFile == Prev->BlockInFile) {
  813. X    /* If they are in the same block, one must be
  814. X     * exactly one word beyond the other.  I don't
  815. X     * think they can ever be the same, unless there
  816. X     * is a serious bug somewhere!
  817. X     */
  818. X    if (New->WordInBlock <= Prev->WordInBlock) {
  819. X        /* too early in the block */
  820. X        return -1;
  821. X    }
  822. X    switch (PhraseMatchLevel) {
  823. X    case PCM_AnyCase:
  824. X        if (New->WordInBlock > Prev->WordInBlock + 4) {
  825. X        return 1; /* gone too far */
  826. X        /* We allow a few words slop in this case, though... */
  827. X        }
  828. X        break; /* clearly OK */
  829. X    case PCM_SameCase:
  830. X    case PCM_HalfCase:
  831. X    default:
  832. X        if (New->WordInBlock > Prev->WordInBlock + 1) {
  833. X        return 1; /* gone too far */
  834. X        }
  835. X    }
  836. X    } else {
  837. X    /* they are in adjacent blocks */
  838. X    if (New->WordInBlock > 0 ||
  839. X            !(Prev->Flags & WPF_LASTINBLOCK)) {
  840. X        /* there is another word between them, so
  841. X         * we have gone too far.
  842. X         * I went to a lot of effort in addfile.c to
  843. X         * mantain that flag, just for this!
  844. X         */
  845. X        return 1;
  846. X    }
  847. X    }
  848. X    /* So they are adjacent words.
  849. X     * Now, I wonder if they are plausible distances
  850. X     * apart, and whether the common words skipped are
  851. X     * the same?
  852. X     * Also, what about other flag details?
  853. X     */
  854. X    
  855. X    /* NOTDONE */
  856. X    
  857. X    /* Now we check that the word matches the given word
  858. X     * -- in other words, that possessive/plural/case
  859. X     *    is correct if required.  Do this later as it is
  860. X     *    relatively expensive I expect, and we will not
  861. X     *    usually care about case.
  862. X     *
  863. X     * Since the word is in the right place, if it fails here there
  864. X     * is no point in looking at the next word in this block!
  865. X     */
  866. X
  867. X    return CheckFlags(QueryWord, New);
  868. X}
  869. X
  870. XCheckFlags(QueryWord, New)
  871. X    t_WordInfo *QueryWord;
  872. X    t_WordPlace *New;
  873. X{
  874. X    /* First check case */
  875. X    switch (PhraseMatchLevel) {
  876. X
  877. X    default: /* defensive! */
  878. X    fprintf(stderr, "\n\rinternal error %d %s\n", __LINE__, __FILE__);
  879. X    (void) sleep(4);
  880. X    break;
  881. X    case PCM_AnyCase:
  882. X    break; /* clearly OK */
  883. X    case PCM_SameCase:
  884. X    if ((QueryWord->WordPlace.Flags & (WPF_UPPERCASE|WPF_POSSESSIVE)) != 
  885. X            (New->Flags & (WPF_UPPERCASE|WPF_POSSESSIVE))) {
  886. X        /* The cases are different, no good */
  887. X        return 1; /* give up on this match */
  888. X    }
  889. X    if (QueryWord->WordPlace.StuffBefore > 0) {
  890. X        int Difference;
  891. X
  892. X        Difference = QueryWord->WordPlace.StuffBefore - New->StuffBefore;
  893. X        if (Difference < -2 || Difference > 4) {
  894. X        return 1; /* give up on this match */
  895. X        }
  896. X    }
  897. X    /* Now, what about skipped common words? */
  898. X    if ((New->Flags & WPF_LASTWASCOMMON) !=
  899. X            (QueryWord->WordPlace.Flags & WPF_LASTWASCOMMON)) {
  900. X        return 1; /* give up on this match */
  901. X    }
  902. X
  903. X    /* plurals: this should be separate */
  904. X    if ((QueryWord->WordPlace.Flags & WPF_WASPLURAL) &&
  905. X                !(New->Flags & WPF_WASPLURAL)) {
  906. X        return 1; /* give up on this match */
  907. X    }
  908. X
  909. X    /* Only do this test if we are being awfully strict.
  910. X     * Remember also that the first word in the phrase will
  911. X     * not usually have this set.
  912. X     */
  913. X    if ((QueryWord->WordPlace.Flags & WPF_LASTHADLETTERS) &&
  914. X                !(New->Flags & WPF_LASTHADLETTERS)) {
  915. X        return 1; /* give up on this match */
  916. X    }
  917. X    break;
  918. X    case PCM_HalfCase:
  919. X    /* In this case, we are lax about things, but if the
  920. X     * user typed  plural/possessive/capital, we only
  921. X     * match one with the same attribute.
  922. X     */
  923. X    if ((QueryWord->WordPlace.Flags & WPF_UPPERCASE) &&
  924. X                    !(New->Flags & WPF_UPPERCASE)) {
  925. X        if (AsciiTrace > 4) {
  926. X        fprintf(stderr, "Reject [uppercase]\n");
  927. X        }
  928. X        return 1; /* give up on this match */
  929. X    }
  930. X
  931. X    /* plurals: this should be separate */
  932. X    if ((New->Flags & WPF_WASPLURAL) &&
  933. X          !(QueryWord->WordPlace.Flags & WPF_WASPLURAL)) {
  934. X        if (AsciiTrace > 4) {
  935. X        fprintf(stderr, "Reject [plural]\n");
  936. X        }
  937. X        return 1; /* give up on this match */
  938. X    }
  939. X
  940. X    /* Now, what about skipped common words? */
  941. X    if ((QueryWord->WordPlace.Flags & WPF_LASTWASCOMMON) &&
  942. X                !(New->Flags & WPF_LASTWASCOMMON)) {
  943. X        if (AsciiTrace > 4) {
  944. X        fprintf(stderr, "Reject [last was common]\n");
  945. X        }
  946. X        return 1; /* give up on this match */
  947. X    }
  948. X    /* Stuff before, if given, must be present: */
  949. X    if (QueryWord->WordPlace.StuffBefore > 1) {
  950. X        if (New->StuffBefore < QueryWord->WordPlace.StuffBefore - 1) {
  951. X        if (AsciiTrace > 4) {
  952. X            fprintf(stderr, "Reject [Stuff Before %d != Q%d]\n",
  953. X                QueryWord->WordPlace.StuffBefore,
  954. X                New->StuffBefore);
  955. X        }
  956. X        return 1;
  957. X        } /* don't care if there is too much there, though */
  958. X    }
  959. X    if ((QueryWord->WordPlace.Flags & WPF_POSSESSIVE) &&
  960. X                   !(New->Flags & WPF_POSSESSIVE)) {
  961. X        if (AsciiTrace > 4) {
  962. X        fprintf(stderr, "Reject [user flag]\n");
  963. X        }
  964. X        return 1; /* give up on this match */
  965. X    }
  966. X    break;
  967. X    }
  968. X
  969. X    /* If we got here...
  970. X     *
  971. X     */
  972. X    
  973. X    return 0; /* It's all OK! */
  974. X}
  975. @@@End of lq-text/src/liblqtext/Phrase.c
  976. echo x - lq-text/src/liblqtext/Root.c 1>&2
  977. sed 's/^X//' >lq-text/src/liblqtext/Root.c <<'@@@End of lq-text/src/liblqtext/Root.c'
  978. X/* Root.c -- Copyright 1989 Liam R. Quin.  All Rights Reserved.
  979. X * This code is NOT in the public domain.
  980. X * See the file COPYRIGHT for full details.
  981. X */
  982. X
  983. X/*
  984. X * $Id: Root.c,v 2.7 91/03/03 00:13:36 lee Rel1-10 $
  985. X *
  986. X * $Log:    Root.c,v $
  987. X * Revision 2.7  91/03/03  00:13:36  lee
  988. X * cosmetic changes.
  989. X * 
  990. X * Revision 2.6  90/10/06  00:11:59  lee
  991. X * Prepared for first beta release.
  992. X * 
  993. X * Revision 2.5  90/08/29  21:46:42  lee
  994. X * Alpha release.
  995. X * 
  996. X * Revision 2.4  90/08/09  19:16:29  lee
  997. X * BSD lint and fixes...
  998. X * 
  999. X * Revision 2.3  90/03/29  23:00:04  lee
  1000. X * Now passes gcc -Wall
  1001. X * 
  1002. X * Revision 2.2  89/10/08  20:44:56  lee
  1003. X * Working version of nx-text engine.  Addfile and wordinfo work OK.
  1004. X * 
  1005. X * Revision 2.1  89/10/02  01:13:07  lee
  1006. X * New index format, with Block/WordInBlock/Flags/BytesSkipped info.
  1007. X * 
  1008. X *
  1009. X */
  1010. X
  1011. X#include "globals.h" /* defines and declarations for database filenames */
  1012. X
  1013. X#include <sys/types.h>
  1014. X#include <fcntl.h> /* for my header files, sorry */
  1015. X#include <stdio.h>
  1016. X#include <malloc.h>
  1017. X#include <ctype.h>
  1018. X
  1019. X#include "fileinfo.h"
  1020. X#include "wordinfo.h"
  1021. X#include "wordrules.h"
  1022. X#include "emalloc.h"
  1023. X
  1024. X/** Unix system calls that need to be declared: **/
  1025. X    /* (none) */
  1026. X/** C Library functions that nees to be declared: **/
  1027. Xextern void perror();
  1028. Xextern int strcmp();
  1029. Xextern int strlen();
  1030. Xextern char *strcpy();
  1031. Xextern char *strcat();
  1032. X#ifndef tolower
  1033. X extern int toupper();
  1034. X#endif
  1035. X
  1036. X/** lqtext functions that need to be declared: **/
  1037. X/** Functions from this file that need to be declared: **/
  1038. Xvoid InsertCommonWord();
  1039. X/** **/
  1040. X
  1041. X/** Useful macros **/
  1042. X#define new(type) ((type *) emalloc(sizeof(type)))
  1043. X    /* so you can say
  1044. X     * struct foo *x = enew(struct foo)
  1045. X     */
  1046. X
  1047. X#define STRCMP(s1,s2) ((*(s1) == *(s2)) ? strcmp(s1, s2) : *(s1) - *(s2))
  1048. X    /* faster then strcmp in the (common) case where the
  1049. X     * strings differ at the first character.
  1050. X     * From an idea by Henry Spencer (utzoo!henry)
  1051. X     */
  1052. X
  1053. X/** **/
  1054. X
  1055. Xextern int AsciiTrace;
  1056. X
  1057. X/* This routine is only sensible for English (although it could be
  1058. X * modified...), but that does not matter.
  1059. X */
  1060. Xchar *
  1061. XWordRoot(WordInfo)
  1062. X    t_WordInfo *WordInfo;
  1063. X{
  1064. X    char *Word;
  1065. X
  1066. X    if (!WordInfo) return "@#!!";
  1067. X
  1068. X    Word = WordInfo->Word;
  1069. X
  1070. X    if (!Word) {
  1071. X    return "oh dear";
  1072. X    }
  1073. X
  1074. X    if (!*Word) {
  1075. X    return Word;
  1076. X    }
  1077. X
  1078. X    /** delete trailing <'s> and mark posessive */
  1079. X    while (WordInfo->Length >= 3 && Word[WordInfo->Length - 1] == 's' &&
  1080. X                    Word[WordInfo->Length - 2] == '\'') {
  1081. X    WordInfo->Length -= 2;
  1082. X    Word[WordInfo->Length] = '\0';
  1083. X    WordInfo->WordPlace.Flags |= WPF_POSSESSIVE;
  1084. X    }
  1085. X
  1086. X    /** delete trailing plural suffix and mark plural */
  1087. X
  1088. X    /* It's important to realise that the purpose of this routine is not
  1089. X     * in any way to reduce a word to an etymological root.  In other words,
  1090. X     * no attempt is made to differentiate between plurals and present
  1091. X     * participles, or words that simply happen to end in `s'.
  1092. X     * Hence, elephants, blunderbus, hostess, runs and tomatoes are all
  1093. X     * candidates.  Of course, one would like to do as well as one can!
  1094. X     * Again, the object isn't to derive the correct singular, but instead
  1095. X     * to be fairly fast, and, above all, to ensure that any transformations
  1096. X     * are reversible!
  1097. X     *
  1098. X     * The result is that I can store dog and dogs in the same Wordinfo
  1099. X     * chain.  In the case that either word is unusual, there is a space
  1100. X     * saving of (on average) 30 or so bytes.  More usefully, if you ask
  1101. X     * for `Window', I will automatically find `Windows' as well.
  1102. X     *
  1103. X     * so...
  1104. X     * XXXo, XXXss, XXXsh, XXXch, XXXx --> +es
  1105. X     *     except: pianos, dynamos, photos
  1106. X     * XXCy --> XXCies [ C consonant]
  1107. X     * XXVy --> XXVys [ V vowel ]
  1108. X     * f or fe --> ves (12 cases only)
  1109. X     * vowel change:
  1110. X     * foot/feet (why bother with these? -- use a thesaurus!)
  1111. X     * need to keep penny/pence separate
  1112. X     * See Thomson & Martinet, section 8ff (I think)
  1113. X     */
  1114. X    if (WordInfo->Length > 2 && Word[WordInfo->Length - 1] == 's') {
  1115. X    WordInfo->WordPlace.Flags |= WPF_WASPLURAL; /* WRONG */
  1116. X    switch (Word[WordInfo->Length - 2]) {
  1117. X    case 'e':
  1118. X        if (WordInfo->Length >= 3) switch (Word[WordInfo->Length - 3]) {
  1119. X        case 'i': /* xxcies --> xxxy */
  1120. X        if (WordInfo->Length > 3) {
  1121. X            Word[WordInfo->Length - 3] = 'y';
  1122. X            WordInfo->Length -= 2;
  1123. X        } else { /* ies not a plural, but lies is :-) */
  1124. X            WordInfo->Length--; /* just the s */
  1125. X        }
  1126. X        break;
  1127. X        case 's':
  1128. X        case 'h':
  1129. X        case 'x':
  1130. X        case 'o': /* xxxoes --> xxx */
  1131. X        WordInfo->Length -= 2;
  1132. X        break;
  1133. X        default: /* xxxes -> xxxe */
  1134. X        WordInfo->Length -= 1;
  1135. X        break;
  1136. X        } else { /* too short */
  1137. X        WordInfo->WordPlace.Flags &=
  1138. X            (unsigned short)~(unsigned short)WPF_WASPLURAL;
  1139. X        }
  1140. X        break;
  1141. X    case 'y': /* xxxvys --> xxxvy */
  1142. X        switch (Word[WordInfo->Length - 2]) { /* e.g. holidays */
  1143. X        case 'a': /* flays */
  1144. X        case 'e': /* beys */
  1145. X        case 'i': /* ??iys?? */
  1146. X        case 'o': /* boys */
  1147. X        case 'u': /* guys */
  1148. X        WordInfo->Length--; /* just remove the s */
  1149. X        break;
  1150. X        default: /*not a plural, e.g. Unixsys, happy */
  1151. X        WordInfo->WordPlace.Flags &=
  1152. X            (unsigned short)~(unsigned short)WPF_WASPLURAL;
  1153. X        break;
  1154. X        }
  1155. X        break;
  1156. X    case 's': /* trailing ss doesn't mark a plural! */
  1157. X        WordInfo->WordPlace.Flags &=
  1158. X                (unsigned short)~(unsigned short)WPF_WASPLURAL;
  1159. X        break;
  1160. X    case 'u':
  1161. X        /* ONE bus, thus, omnibus; TWO gnus, TWO emus */
  1162. X        /* So it doesn't work for gnus and emus right now! */
  1163. X        WordInfo->WordPlace.Flags &=
  1164. X            (unsigned short)~(unsigned short)WPF_WASPLURAL;
  1165. X        break;
  1166. X    case 'i': /* not a plural.. this, his, fleur-de-lis */
  1167. X        WordInfo->WordPlace.Flags &=
  1168. X            (unsigned short)~(unsigned short)WPF_WASPLURAL;
  1169. X        break;
  1170. X    case 'a': /* has */
  1171. X    case 'o': /* cos */
  1172. X        if (WordInfo->Length < 4) {
  1173. X        WordInfo->WordPlace.Flags &=
  1174. X            (unsigned short)~(unsigned short)WPF_WASPLURAL;
  1175. X        break;
  1176. X        }
  1177. X        /* else fall through */
  1178. X    default: /* just plain s */
  1179. X        WordInfo->Length -= 1;
  1180. X        break;
  1181. X    }
  1182. X    Word[WordInfo->Length] = '\0';
  1183. X    } 
  1184. X    /* Should check for ii --> ius here, but that would increase the length
  1185. X     * of the word and therefore will break lots of things.
  1186. X     */
  1187. X    return WordInfo->Word;
  1188. X}
  1189. X
  1190. Xchar *
  1191. XUnFlag(WordInfo, Flags)
  1192. X    t_WordInfo *WordInfo;
  1193. X    unsigned int Flags;
  1194. X{
  1195. X    static char Buffer[MaxWordLength + 5]; /* 's + es + \0 */
  1196. X    register char *p, *q;
  1197. X    int Length;
  1198. X
  1199. X    if (!WordInfo) return "(null word info)";
  1200. X    if (!WordInfo->Word) return "(null word)";
  1201. X    if (!WordInfo->Word[0]) return "(empty word)";
  1202. X
  1203. X    p = Buffer;
  1204. X    q = WordInfo->Word;
  1205. X    while (*p++ = *q++)
  1206. X    ;
  1207. X    *p = '\0';
  1208. X    
  1209. X    if ((Length = p - Buffer) != WordInfo->Length) {
  1210. X    /* Well, maybe I can't count */
  1211. X    WordInfo->Length = Length = strlen(Buffer);
  1212. X    }
  1213. X
  1214. X    if (Flags & WPF_WASPLURAL) {
  1215. X    if (Length >= 2) switch (Buffer[Length - 1]) {
  1216. X    case 'y':
  1217. X        if (Length > 2) switch (Buffer[Length - 2]) {
  1218. X        case 'a':
  1219. X        case 'e':
  1220. X        case 'i':
  1221. X        case 'o':
  1222. X        case 'u':
  1223. X        Buffer[Length++] = 's'; /* e.g. days */
  1224. X        break;
  1225. X        default:
  1226. X        strcpy(&Buffer[Length - 1], "ies"); /* ladies */
  1227. X        Length += 2;
  1228. X        }
  1229. X        break;
  1230. X    case 's':
  1231. X        if (Length > 2) if (Buffer[Length - 2] == 'u') {
  1232. X        strcpy(&Buffer[Length - 1], "ii"); /* Genii */
  1233. X        break;
  1234. X        } /* else fall through... */
  1235. X    case 'o':
  1236. X    case 'h':
  1237. X    case 'x':
  1238. X        strcat(Buffer, "es");
  1239. X        Length += 2;
  1240. X        break;
  1241. X    default:
  1242. X        Buffer[Length++] = 's';
  1243. X    }
  1244. X    Buffer[Length] = '\0';
  1245. X    }
  1246. X
  1247. X    if (Flags & WPF_POSSESSIVE) {
  1248. X    Buffer[Length++] = '\'';
  1249. X    Buffer[Length++] = 's';
  1250. X    Buffer[Length] = '\0';
  1251. X    }
  1252. X
  1253. X    if (Flags & WPF_UPPERCASE) {
  1254. X    Buffer[0] = toupper(Buffer[0]);
  1255. X    }
  1256. X
  1257. X    return Buffer;
  1258. X}
  1259. X
  1260. Xtypedef struct s_WordList {
  1261. X    char *Word;
  1262. X    unsigned short Flags;
  1263. X    struct s_WordList *Next;
  1264. X} t_WordList;
  1265. X
  1266. Xstatic t_WordList *CommonWords = 0;
  1267. X
  1268. Xint
  1269. XTooCommon(WordInfo)
  1270. X    t_WordInfo *WordInfo;
  1271. X{
  1272. X    register char *Word = WordInfo->Word;
  1273. X    register t_WordList **WP;
  1274. X
  1275. X    for (WP = &CommonWords; *WP; WP = &(*WP)->Next) {
  1276. X    int i = STRCMP((*WP)->Word, Word);
  1277. X
  1278. X    if (i == 0) return 1; /* yes, it's common */
  1279. X    else if (i > 0) return 0;
  1280. X    }
  1281. X    return 0;
  1282. X}
  1283. X
  1284. Xstatic char *FileName = "Internal Error";
  1285. X/* should be set before being printed! */
  1286. X
  1287. Xint
  1288. XReadCommonWords(CommonFile)
  1289. X    char *CommonFile;
  1290. X{
  1291. X    extern char *fgets();
  1292. X    extern int AsciiTrace;
  1293. X
  1294. X    FILE *fd;
  1295. X    extern int errno;
  1296. X    char Buffer[200];
  1297. X    t_WordInfo W;
  1298. X    char *Root;
  1299. X    t_WordList *WP;
  1300. X
  1301. X    errno = 0;
  1302. X
  1303. X    if ((fd = fopen(CommonFile, "r")) == (FILE *) 0) {
  1304. X    int e = errno;
  1305. X
  1306. X    fprintf(stderr, "Can't open common word list ");
  1307. X    errno = e;
  1308. X    perror(CommonFile);
  1309. X    return -1;
  1310. X    }
  1311. X
  1312. X    FileName = CommonFile;
  1313. X
  1314. X    while (fgets(Buffer, sizeof(Buffer), fd) != (char *) 0) {
  1315. X    register char *p;
  1316. X    char *Start;
  1317. X
  1318. X    for (p = Buffer; *p; p++) {
  1319. X        if (*p == '#') break;
  1320. X        if (StartsWord(*p)) break;
  1321. X    }
  1322. X
  1323. X    if (*p == '#' || !*p) {
  1324. X        continue;
  1325. X    }
  1326. X
  1327. X    Start = p;
  1328. X
  1329. X    for (; *p; p++) {
  1330. X        if (!WithinWord(*p)) break;
  1331. X        if (*p == '\'' && !WithinWord(p[1])) break;
  1332. X    }
  1333. X
  1334. X    if (p - Start + 1 < MinWordLength) continue;
  1335. X
  1336. X    *p = '\0'; /* delete trailing \n or whatever */
  1337. X    W.WordPlace.Flags = 0;
  1338. X    W.Word = Start;
  1339. X    W.Length = p - Start; /* length excludes the \0 */
  1340. X
  1341. X    Root = WordRoot(&W);
  1342. X    InsertCommonWord(Root, W.WordPlace.Flags);
  1343. X    }
  1344. X    (void) fclose(fd);
  1345. X
  1346. X#if 0
  1347. X    if (!CommonWords) {
  1348. X    fprintf(stderr, "No common words found in file \"%s\"\n", FileName);
  1349. X    exit(1);
  1350. X    }
  1351. X#endif
  1352. X
  1353. X    if (AsciiTrace > 1) {
  1354. X    for (WP = CommonWords; WP; WP = WP->Next) {
  1355. X        fprintf(stderr, "Ignore: \"%s\"\n", WP->Word);
  1356. X    }
  1357. X    }
  1358. X    FileName = "Internal Error";
  1359. X    return 0;
  1360. X}
  1361. X
  1362. Xvoid
  1363. XInsertCommonWord(Root, Flags)
  1364. X    char *Root;
  1365. X    unsigned int Flags;
  1366. X{
  1367. X    register t_WordList **WP;
  1368. X    t_WordList *W;
  1369. X
  1370. X    for (WP = &CommonWords; *WP; WP = &(*WP)->Next) {
  1371. X    int i = STRCMP((*WP)->Word, Root);
  1372. X
  1373. X    if (i == 0) return;
  1374. X    else if (i > 0) break;
  1375. X    }
  1376. X    /* insert it before this one! */
  1377. X    W = (*WP);
  1378. X    (*WP) = (t_WordList *) emalloc(sizeof(t_WordList));
  1379. X    (*WP)->Word = emalloc(strlen(Root) + 1);
  1380. X    (void) strcpy((*WP)->Word, Root);
  1381. X    (*WP)->Flags = Flags;
  1382. X    (*WP)->Next = W;
  1383. X    return;
  1384. X}
  1385. @@@End of lq-text/src/liblqtext/Root.c
  1386. echo x - lq-text/src/liblqtext/WordInfo.c 1>&2
  1387. sed 's/^X//' >lq-text/src/liblqtext/WordInfo.c <<'@@@End of lq-text/src/liblqtext/WordInfo.c'
  1388. X/* WordInfo.c -- Copyright 1989 Liam R. Quin.  All Rights Reserved.
  1389. X * This code is NOT in the public domain.
  1390. X * See the file COPYRIGHT for full details.
  1391. X */
  1392. X
  1393. X/* WordInfo.c -- handle the database of words for NX-Text.
  1394. X * 
  1395. X * NX-Text keeps a master list of all of the words that have ever been
  1396. X * seen.  Currently, this is in dbm format (sdbm or ndbm).
  1397. X * For each word, there's an associated WID (a unique number), an offset
  1398. X * into the master database (see pblock.c), and possibly thesaurus info.
  1399. X *
  1400. X * $Id: WordInfo.c,v 2.11 90/10/13 03:10:07 lee Rel1-10 $
  1401. X *
  1402. X * $Log:    WordInfo.c,v $
  1403. X * Revision 2.11  90/10/13  03:10:07  lee
  1404. X * Type error -- efree() needs a char *.
  1405. X * 
  1406. X * Revision 2.10  90/10/06  00:12:01  lee
  1407. X * Prepared for first beta release.
  1408. X * 
  1409. X * Revision 2.9  90/09/29  23:47:30  lee
  1410. X * Reduced the size of a buffer, and plugged yet another memory leak!
  1411. X * 
  1412. X * Revision 2.8  90/09/10  13:38:50  lee
  1413. X * deleted declaration of sleep()
  1414. X * 
  1415. X * Revision 2.7  90/08/29  21:46:48  lee
  1416. X * Alpha release.
  1417. X * 
  1418. X * Revision 2.6  90/08/12  17:33:38  lee
  1419. X * malloc changes; added SlayWordInfo() and MakeWordInfo().
  1420. X * 
  1421. X * Revision 2.5  90/08/09  19:16:35  lee
  1422. X * BSD lint and fixes...
  1423. X * 
  1424. X * Revision 2.4  90/03/22  14:23:19  lee
  1425. X * new calls to efree();
  1426. X * Offset now stored as a block number, not a byte offset
  1427. X * 
  1428. X * Revision 2.3  90/03/21  14:59:13  lee
  1429. X * Numerous changes.  WID2WordInfo() no longer calles GetWordPlaces().
  1430. X * 
  1431. X * Revision 2.2  89/10/08  20:45:05  lee
  1432. X * Working version of nx-text engine.  Addfile and wordinfo work OK.
  1433. X * 
  1434. X * Revision 2.1  89/10/02  01:13:56  lee
  1435. X * New index format, with Block/WordInBlock/Flags/BytesSkipped info.
  1436. X * 
  1437. X * Revision 1.4  89/09/17  23:01:53  lee
  1438. X * Various fixes; NumberInBlock now a short...
  1439. X * 
  1440. X * Revision 1.3  89/09/16  21:16:07  lee
  1441. X * First demonstratable version.
  1442. X * 
  1443. X * Revision 1.2  89/09/11  00:35:03  lee
  1444. X * Some speedups, but WID not working properly...
  1445. X * 
  1446. X * Revision 1.1  89/09/07  21:05:51  lee
  1447. X * Initial revision
  1448. X * 
  1449. X *
  1450. X */
  1451. X
  1452. X#include "globals.h" /* defines and declarations for database filenames */
  1453. X
  1454. X#include <errno.h>
  1455. X#include <fcntl.h>
  1456. X#include <malloc.h>
  1457. X#include <signal.h>
  1458. X#include <stdio.h>
  1459. X#include <string.h>
  1460. X#include <ctype.h>
  1461. X#include <sys/types.h>
  1462. X#include <sys/stat.h>
  1463. X#include <unistd.h>
  1464. X
  1465. X#include "fileinfo.h"
  1466. X#include "smalldb.h"
  1467. X#include "wordindex.h"
  1468. X#include "wordinfo.h"
  1469. X#include "numbers.h"
  1470. X
  1471. X#include "emalloc.h"
  1472. X
  1473. X#include "wordrules.h" /* max word length */
  1474. X
  1475. X#include "pblock.h"
  1476. X
  1477. X/** declarations: **/
  1478. X/** Unix system calls that need to be declared: **/
  1479. Xextern int open(), close(); /* (these are not the stdio fopen and fclose) */
  1480. Xextern int creat();
  1481. Xextern void exit();
  1482. Xextern long lseek(); /* watch out for this on 16 bit (286, PDP11) systems! */
  1483. Xextern int read(), write();
  1484. Xextern int stat();
  1485. Xextern unsigned alarm(/* unsigned */);
  1486. X
  1487. X/** Unix Library Calls that need to be declared: **/
  1488. X#ifndef tolower /* e.g. on SunOS */
  1489. Xextern int tolower();
  1490. X#endif
  1491. Xextern void perror();
  1492. X/* extern int sleep(); -- this is unsigned on some systems */
  1493. Xextern int lockf();
  1494. X/** lqtext Library calls that need to be declared: **/
  1495. Xextern void Deletepblock();
  1496. X
  1497. X/** Functions within this file that need to be declared: **/
  1498. Xt_WordInfo *MakeWordInfo();
  1499. Xvoid SlayWordInfo();
  1500. X
  1501. X/** **/
  1502. X
  1503. Xextern int AsciiTrace;
  1504. Xextern char *progname;
  1505. X
  1506. X#define new(type) ( ((type) *) emalloc(sizeof(type)) )
  1507. X
  1508. X/* Format when using ndbm: */
  1509. X
  1510. Xtypedef struct {
  1511. X    t_WID WID;
  1512. X    unsigned long Offset; /* position in the database */
  1513. X    unsigned long NumberOfWordPlaces;
  1514. X    char Word[1]; /* Cheat here */
  1515. X} t_WordIndexEntry;
  1516. X
  1517. X/* Replacement fomat, intended to be faster and to use much less disk space.
  1518. X * Still use dbm for Word2WID, but not for the reverse mapping.
  1519. X * Also, cache the most recently-used WordInfo entry...
  1520. X * I have not measured how much of a win this is, but a lot of the code
  1521. X * calls Word2Wid() and then WID2WordInfo().
  1522. X */
  1523. X
  1524. Xstatic int Widfd = (-1);
  1525. X
  1526. Xt_WordInfo *
  1527. XWID2WordInfo(WID)
  1528. X    t_WID WID;
  1529. X{
  1530. X    extern t_WordPlace *GetWordPlaces(); /* pblock.c */
  1531. X
  1532. X    char Buffer[WIDBLOCKSIZE + 5]; /* the +5 allows for overrun... */
  1533. X    char *q = Buffer;
  1534. X    t_WordInfo *WP;
  1535. X
  1536. X    /* The above calculation is derived like this:
  1537. X     *
  1538. X     * The entry contains the total number of pairs (>= 1 byte),
  1539. X     * the length of the string (>= 1 byte), and the string (>= 3 bytes)
  1540. X     * (actually >= MinWordLength bytes, but setting this less than 3
  1541. X     * would be a major disaster!)
  1542. X     * Hence, there are WIDBLOCKSIZE - (2 + length) bytes left for pairs.
  1543. X     * Now, each pair is at least 2 bytes, so halve the remainder.  I add
  1544. X     * one coz WIDBLOCKSIZE - 3 is odd, so I would otherwise lose one!
  1545. X     */
  1546. X
  1547. X    if (Widfd < 0) {
  1548. X    if ((Widfd = open(WidIndexFile, O_RDWR|O_CREAT, 0766)) < 0) {
  1549. X        fprintf(stderr, "Can't open WID file \"%s\"\n", WidIndexFile);
  1550. X        exit(1);
  1551. X    }
  1552. X    }
  1553. X
  1554. X    if (lseek(Widfd, (long) (WID * WIDBLOCKSIZE), 0) < 0) {
  1555. X    return (t_WordInfo *) 0;
  1556. X    }
  1557. X
  1558. X    if (read(Widfd, Buffer, WIDBLOCKSIZE) != WIDBLOCKSIZE) {
  1559. X    return (t_WordInfo *) 0;
  1560. X    }
  1561. X
  1562. X    {
  1563. X    unsigned short L;
  1564. X
  1565. X    if ((L = sReadNumber(&q)) == 0) {
  1566. X        (void) fprintf(stderr,
  1567. X            "%s: Database corrupt, WID %lu has length zero\n",
  1568. X            progname, WID);
  1569. X        return (t_WordInfo *) 0;
  1570. X    }
  1571. X    WP = MakeWordInfo(WID, (int) L, q);
  1572. X    q += L;
  1573. X    }
  1574. X
  1575. X    WP->Offset = (sReadNumber(&q) >> 1) * BLOCKSIZE;
  1576. X    WP->NumberOfWordPlaces = sReadNumber(&q);
  1577. X
  1578. X    /* Now, maybe read some WordPlace tuplets: */
  1579. X#if 1
  1580. X    if (q - Buffer < WIDBLOCKSIZE) {
  1581. X    WP->WordPlaces = GetWordPlaces(
  1582. X        WP->WID,
  1583. X        q,
  1584. X        WIDBLOCKSIZE - (q - Buffer),
  1585. X        WP->Offset,
  1586. X        WP->NumberOfWordPlaces
  1587. X    );
  1588. X    WP->WordPlacesInHere = WP->NumberOfWordPlaces;
  1589. X    } else {
  1590. X    fprintf(stderr, "%s: Internal error, block too small for %ld (%s)\n",
  1591. X        progname, WP->WID, WP->Word);
  1592. X    exit(1);
  1593. X    }
  1594. X
  1595. X#else
  1596. X    WP->WordPlaces = (t_WordPlace *) 0;
  1597. X    if (q - Buffer < WIDBLOCKSIZE) {
  1598. X    WP->DataBlock = emalloc(WIDBLOCKSIZE + 5);
  1599. X    (void) memcpy(WP->DataBlock, Buffer, WIDBLOCKSIZE);
  1600. X    WP->WordPlaceStart = &(WP->DataBlock[q - Buffer]);
  1601. X    }
  1602. X#endif
  1603. X
  1604. X    /* done! */
  1605. X    return WP;
  1606. X}
  1607. X
  1608. Xstatic char PairBuffer[WIDBLOCKSIZE + 5]; /* the +5 allows for overrun... */
  1609. X
  1610. X/* Make WordInfo Block Header... */
  1611. Xvoid
  1612. XMkWIBH(WordInfo, pblock)
  1613. X    t_WordInfo *WordInfo;
  1614. X    t_pblock *pblock;
  1615. X{
  1616. X    char *q = PairBuffer;
  1617. X
  1618. X#ifdef ASCIITRACE
  1619. X    if (AsciiTrace > 15) {
  1620. X    fprintf(stderr, "\tMake info block header for %s, Offset %lu==%lu\n",
  1621. X    WordInfo->Word, pblock->ChainStart, WordInfo->Offset);
  1622. X    }
  1623. X#endif
  1624. X
  1625. X    sWriteNumber(&q, WordInfo->Length);
  1626. X    (void) strncpy(q, WordInfo->Word, WordInfo->Length);
  1627. X    q += WordInfo->Length;
  1628. X    if (pblock) sWriteNumber(&q, (pblock->ChainStart / BLOCKSIZE) << 1);
  1629. X    else sWriteNumber(&q, 0L);
  1630. X    sWriteNumber(&q, WordInfo->NumberOfWordPlaces);
  1631. X
  1632. X    WordInfo->WordPlaceStart = q;
  1633. X    WordInfo->DataBlock = PairBuffer;
  1634. X}
  1635. X
  1636. X/* Make WordInfo Block ... */
  1637. Xint
  1638. XMkWIB(WordInfo, pblock)
  1639. X    t_WordInfo *WordInfo;
  1640. X    t_pblock *pblock;
  1641. X{
  1642. X    extern unsigned short PutWordPlaces();
  1643. X
  1644. X    /* See how many pairs from the given pblock fit into WordInfo,
  1645. X     * and leave them in PairBuffer...
  1646. X     */
  1647. X
  1648. X    if (AsciiTrace > 3) {
  1649. X    fprintf(stderr, "Make info block for %s\n", WordInfo->Word);
  1650. X    }
  1651. X
  1652. X    MkWIBH(WordInfo, pblock);
  1653. X
  1654. X    if (pblock == (t_pblock *) 0) {
  1655. X    /* No WordPlaces to put in! */
  1656. X    WordInfo->WordPlacesInHere = 0;
  1657. X    return 0;
  1658. X    }
  1659. X
  1660. X    return WordInfo->WordPlacesInHere = PutWordPlaces(
  1661. X        pblock->WordPlaces,
  1662. X        WordInfo->WID,
  1663. X        WordInfo->WordPlaceStart,
  1664. X        WIDBLOCKSIZE - (WordInfo->WordPlaceStart - PairBuffer),
  1665. X        pblock->ChainStart,
  1666. X        pblock->NumberOfWordPlaces);
  1667. X}
  1668. X
  1669. Xchar *
  1670. XString2SixBitString(String)
  1671. X    unsigned char *String;
  1672. X{
  1673. X    static unsigned char Buffer[MaxWordLength + 1];
  1674. X    register unsigned char *p;
  1675. X    register unsigned char *Bufp = Buffer;
  1676. X    unsigned short Val;
  1677. X    int BitsLeft = 0;
  1678. X
  1679. X    /* BUG: we lose word-processing accents, etc. and 8-bitness if
  1680. X     * we do this.  Also, it slows things down very, very slightly.
  1681. X     */
  1682. X
  1683. X    /* Some ascii character equivalents:
  1684. X     * '0' 48 060 0x30
  1685. X     * 'A' 65 0101 0x41
  1686. X     * '_' 95 0137 0x5f
  1687. X     * 'a' 97 0141 0x61
  1688. X     */
  1689. X    for (p = String; *p; p++) {
  1690. X    if (!isalnum(*p) && *p != '\'' && *p != '_') {
  1691. X        return (char *) 0;
  1692. X    }
  1693. X    if (isupper(*p)) *p = tolower(*p);
  1694. X    /* Store as
  1695. X     * 0-9 --> 0-9 (easy!)
  1696. X     * a-z --> 10...35
  1697. X     * _/' --> 36/37
  1698. X     * hence, I need 6 bits per character.  This also leaves rather
  1699. X     * a lot of bits spare (38..64, 27 or so spaces).  As I fold case,
  1700. X     * and don't have controls, I don't know what to do there.  I
  1701. X     * could store digrams.  There are 38*38 = 1444 of these, but
  1702. X     * some of them don't happen.  Not worth the effort.
  1703. X     */
  1704. X    if (isdigit(*p)) {
  1705. X        Val = (*p) - '0';
  1706. X    } else if (isalpha(*p)) {
  1707. X        Val = (*p) - 'a' + ('9' - '0');
  1708. X    } else if (*p == '\'') {
  1709. X        Val = ('9' - '0') + ('z' - 'a') + 1;
  1710. X    } else if (*p == '_') {
  1711. X        Val = ('9' - '0') + ('z' - 'a') + 2;
  1712. X    } else {
  1713. X#define NEXTISEIGHT ('9' - '0') + ('z' - 'a') + 3
  1714. X        Val = NEXTISEIGHT;
  1715. X    }
  1716. X    /* Write the first half */
  1717. X    if (!(BitsLeft & 07)) { /* i.e. it's 0 or 8 */
  1718. X        *Bufp = (Val << 2);
  1719. X        BitsLeft = 2;
  1720. X    } else {
  1721. X        /* top BITSLEFT bits */
  1722. X        *Bufp++ |= (Val >> (6 - BitsLeft));
  1723. X        *Bufp = (unsigned) (Val << (2 + BitsLeft)); /* lose some bits */
  1724. X        if ((BitsLeft -= 6) < 0) BitsLeft += 8;
  1725. X    }
  1726. X    }
  1727. X    if (BitsLeft) {
  1728. X    Bufp++;
  1729. X    }
  1730. X    *Bufp = 0;
  1731. X    return (char *) Buffer;
  1732. X}
  1733. X
  1734. Xunsigned char *
  1735. XSixBitString2String(SixBitString)
  1736. X    char *SixBitString;
  1737. X{
  1738. X    static unsigned char Buffer[MaxWordLength + 2];
  1739. X    register unsigned char *p = (unsigned char *) SixBitString;
  1740. X    int BitsLeft = 0;
  1741. X    unsigned char *Bufp = Buffer;
  1742. X
  1743. X    while (*p) {
  1744. X    if (!(BitsLeft & 07)) { /* i.e. it's 0 or 8 */
  1745. X        *Bufp++ = (*p) >> 2;
  1746. X        BitsLeft = 2;
  1747. X    } else {
  1748. X        /* W R O N G */
  1749. X        /* bottom BITSLEFT bits */
  1750. X        *Bufp = ((*p) << (6 - BitsLeft));
  1751. X        /* Rest */
  1752. X        *Bufp = (unsigned) ((*p) << (2 + BitsLeft)); /* lose some bits */
  1753. X        if ((BitsLeft -= 6) < 0) BitsLeft += 8;
  1754. X    }
  1755. X    }
  1756. X    return (unsigned char *) "notdone'fixme"; /* NOTDONE FIXME */
  1757. X}
  1758. X
  1759. X#ifdef TESTSIX
  1760. Xchar *progname= "testsix";
  1761. Xmain(argc, argv)
  1762. X    int argc;
  1763. X    char *argv[];
  1764. X{
  1765. X    extern char *gets();
  1766. X
  1767. X    char Line[4096];
  1768. X    int Encode = 1;
  1769. X
  1770. X    if (argc != 3) {
  1771. X    fprintf(stderr, "bad arg count; usage: %s -[de]\n", progname);
  1772. X    }
  1773. X    if (STREQ(argv[1], "-e")) Encode = 1;
  1774. X    else if (STREQ(argv[1], "-d")) Decode = 1;
  1775. X    else {
  1776. X    fprintf(stderr, "usage: %s -[d|e]\n", progname);
  1777. X    exit(1);
  1778. X    }
  1779. X    while (gets(Line) != (char *) 0) {
  1780. X    char *Result;
  1781. X
  1782. X    if (Encode) {
  1783. X        Result = String2SixBitString(Line);
  1784. X    } else {
  1785. X        if (STREQ(Line, "(cannot be encoded)")) {
  1786. X        Result = "(this line was not saved)";
  1787. X        } else {
  1788. X        Result = SixBitString2String(line);
  1789. X        }
  1790. X    }
  1791. X    if (Result) {
  1792. X        printf("%s\n", Result);
  1793. X    } else {
  1794. X        printf("(cannot be encoded)\n");
  1795. X    }
  1796. X    }
  1797. X}
  1798. X
  1799. X#endif /*TESTSIX*/
  1800. X
  1801. X
  1802. Xt_WID
  1803. XWord2WID(Word, Length)
  1804. X    char *Word;
  1805. X    unsigned int Length;
  1806. X{
  1807. X    DBM *db;
  1808. X    datum key, data;
  1809. X    char *q;
  1810. X    t_WID WID;
  1811. X    char Buffer[8];
  1812. X    /* enough for the binary representation of a number -- see numbers.c;
  1813. X     * this is _not_ sizeof(long).  It's probably 5, in fact, although
  1814. X     * for small numbers it's less.
  1815. X     */
  1816. X
  1817. X    if (Length > MaxWordLength) {
  1818. X    Length = MaxWordLength; /* NOTE: no trailing \0 required. */
  1819. X    }
  1820. X
  1821. X    /* contact database server */
  1822. X    if ((db = startdb(WordIndex)) == (DBM *) 0) {
  1823. X    fprintf(stderr, "dbmopen(%s) failed\n", WordIndex);
  1824. X    exit(2);
  1825. X    }
  1826. X
  1827. X    key.dptr = Word;
  1828. X    key.dsize = Length;
  1829. X
  1830. X    data = dbm_fetch(db, key);
  1831. X
  1832. X    enddb(db);
  1833. X
  1834. X    if (data.dsize == 0) {
  1835. X    return (t_WID) 0;
  1836. X    }
  1837. X
  1838. X    /* do this because ReadNumber will leave q pointing beyond Buffer: */
  1839. X    (void) memcpy(Buffer, data.dptr, data.dsize);
  1840. X    q = Buffer;
  1841. X    WID = sReadNumber(&q);
  1842. X    if (q - Buffer != data.dsize) {
  1843. X    fprintf(stderr, "Word2Wid failed... got %lu\n");
  1844. X    return (t_WID) 0;
  1845. X    }
  1846. X    return WID;
  1847. X}
  1848. X    
  1849. Xchar *
  1850. XWID2Word(WID)
  1851. X    t_WID WID;
  1852. X{
  1853. X    t_WordInfo *W;
  1854. X    char *Word;
  1855. X
  1856. X    if (WID == (t_WID) 0) {
  1857. X    return (char *) 0;
  1858. X    }
  1859. X
  1860. X    if ((W = WID2WordInfo(WID)) == (t_WordInfo *) 0) {
  1861. X    return (char *) 0;
  1862. X    }
  1863. X    Word = W->Word;
  1864. X    W->Word = (char *) 0;
  1865. X    SlayWordInfo(W);
  1866. X    return Word;
  1867. X}
  1868. X
  1869. Xint
  1870. XPutWordInfoIntoIndex(WordInfo, Offset)
  1871. X    t_WordInfo *WordInfo;
  1872. X    unsigned long Offset;
  1873. X{
  1874. X    DBM *db;
  1875. X    char NumBuf[sizeof(t_WID) + 1];
  1876. X    char *q = NumBuf;
  1877. X    datum key, data;
  1878. X    int RetVal;
  1879. X
  1880. X    /** First, write the WID itself, so we can go from Word to WID */
  1881. X
  1882. X    key.dptr = WordInfo->Word;
  1883. X    key.dsize = WordInfo->Length;
  1884. X
  1885. X    sWriteNumber(&q, WordInfo->WID);
  1886. X
  1887. X    data.dptr = NumBuf;
  1888. X    data.dsize = q - NumBuf;
  1889. X
  1890. X    /* contact database server */
  1891. X    if ((db = startdb(WordIndex)) == (DBM *) 0) {
  1892. X    fprintf(stderr, "dbmopen(%s) failed\n", WordIndex);
  1893. X    exit(2);
  1894. X    }
  1895. X
  1896. X    RetVal = dbm_store(db, key, data, DBM_REPLACE);
  1897. X
  1898. X    enddb(db);
  1899. X
  1900. X    /** Now, ensure that we have a physical block for WordInfo.  If
  1901. X     ** we don't, there is something very wrong in pblock.c, our only
  1902. X     ** possible caller.
  1903. X     **/
  1904. X
  1905. X    if (WordInfo->DataBlock == (char *) 0) {
  1906. X    if (Offset) {
  1907. X        fprintf(stderr, "WARNING: WordInfo corrupt for \"%s\"\n",
  1908. X                WordInfo->Word);
  1909. X    }
  1910. X    (void) MkWIB(WordInfo, (t_pblock *) 0);
  1911. X    }
  1912. X
  1913. X    /** Now write the physical entry... */
  1914. X
  1915. X    if (Widfd < 0) {
  1916. X    if ((Widfd = open(WidIndexFile, O_RDWR|O_CREAT, 0766)) < 0) {
  1917. X        fprintf(stderr, "Can't open WID file \"%s\"\n", WidIndexFile);
  1918. X        exit(1);
  1919. X    }
  1920. X    }
  1921. X
  1922. X    if (lseek(Widfd, (long) (WordInfo->WID * WIDBLOCKSIZE), 0) < 0) {
  1923. X    perror("lseek");
  1924. X    exit(1);
  1925. X    }
  1926. X
  1927. X    if (write(Widfd, WordInfo->DataBlock, WIDBLOCKSIZE) != WIDBLOCKSIZE) {
  1928. X    perror("write");
  1929. X    exit(1);
  1930. X    }
  1931. X
  1932. X    return RetVal;
  1933. X}
  1934. X
  1935. Xint
  1936. XWID_sig()
  1937. X{
  1938. X    return 0;
  1939. X}
  1940. X
  1941. Xt_WID
  1942. XGetMaxWID()
  1943. X{
  1944. X    extern int errno;
  1945. X    extern long atol();
  1946. X
  1947. X    int fd;
  1948. X    char Buffer[20]; /* large enough to sprintf() a WID */
  1949. X    struct stat StatBuf;
  1950. X#if 0
  1951. X    int FileKey; /* what one gets from a lock... */
  1952. X    int NumberOfTriesLeft = 5;
  1953. X#endif
  1954. X
  1955. X    /* ensure that the file is there */
  1956. X    if (stat(WidFile, &StatBuf) == -1) {
  1957. X    return 0;
  1958. X    }
  1959. X
  1960. X    if ((fd = open(WidFile, O_RDWR, 0)) < 0) {
  1961. X    fprintf(stderr, "Warning: Can't open WID file");
  1962. X    return 0;
  1963. X    }
  1964. X
  1965. X#if 0
  1966. X    errno = 0;
  1967. X
  1968. X    /** Lock the file **/
  1969. X
  1970. X    do {
  1971. X    /* Set a timeout of 2 seconds */
  1972. X    signal(SIGALRM, WID_sig);
  1973. X    (void) alarm(3);
  1974. X    if ((FileKey = lockf(fd, F_LOCK, 0L)) < 0) {
  1975. X        switch (errno) {
  1976. X        case EACCES: /*[sic]*/ /* another process has the lock */
  1977. X        fprintf(stderr, "Please wait...\n");
  1978. X        /* shouldn't happen */
  1979. X        break;
  1980. X        case EDEADLK:
  1981. X        fprintf(stderr, "Warning: can't lock \"%s\" -- EDEADLK\n", WidFile);
  1982. X        FileKey = 1;
  1983. X        break;
  1984. X        case EINTR:
  1985. X        fprintf(stderr, "Please Wait... someone has the key...\n");
  1986. X        sleep(1);
  1987. X        break;
  1988. X        }
  1989. X    }
  1990. X    if (--NumberOfTriesLeft <= 0) {
  1991. X        fprintf(stderr, "Warning: can't lock ");
  1992. X        perror(WidFile);
  1993. X        (void) close(fd);
  1994. X        return 0;
  1995. X    }
  1996. X    } while (FileKey < 0);
  1997. X    (void) alarm(0);
  1998. X
  1999. X    if (stat(WidFile, &StatBuf) == -1) {
  2000. X    fprintf(stderr, "It went away!\n");
  2001. X    return 0;
  2002. X    }
  2003. X#endif
  2004. X
  2005. X    /* Read the file */
  2006. X    if (read(fd, Buffer, (unsigned int) StatBuf.st_size) < 0) {
  2007. X    fprintf(stderr, "Can't read from \"%s\"\n", WidFile);
  2008. X    exit(1);
  2009. X    }
  2010. X
  2011. X#if 0
  2012. X    /** Unlock the file **/
  2013. X    if (lockf(fd, F_ULOCK, 0L) < 0 && FileKey == 0) {
  2014. X    fprintf(stderr, "Warning: might not have unlocked \"%s\"\n",
  2015. X                WidFile);
  2016. X    }
  2017. X#endif
  2018. X    (void) close(fd);
  2019. X
  2020. X    Buffer[StatBuf.st_size] = '\0';
  2021. X
  2022. X    return atol(Buffer);
  2023. X}
  2024. X
  2025. X
  2026. Xt_WID LastNextWIDVal = (t_WID) 0;
  2027. X
  2028. X#undef GetNextWID
  2029. X
  2030. Xt_WID GetNextWID(), GetMaxWID();
  2031. X
  2032. XINLINE
  2033. Xt_WID
  2034. XSpoofGetNextWID()
  2035. X{
  2036. X    static int SinceLastUpdate = 0;
  2037. X
  2038. X    /* Call the real function sometimes, so that the database does
  2039. X     * get updated in case of a crash or for other users.
  2040. X     */
  2041. X    if (++SinceLastUpdate > 500) {
  2042. X    SinceLastUpdate = 0;
  2043. X    return GetNextWID(1);
  2044. X    }
  2045. X
  2046. X    if (LastNextWIDVal == (t_WID) 0) {
  2047. X    SinceLastUpdate = 0;
  2048. X    LastNextWIDVal = GetMaxWID();
  2049. X    }
  2050. X    return ++LastNextWIDVal;
  2051. X}
  2052. X
  2053. Xvoid
  2054. XWriteCurrentMaxWID()
  2055. X{
  2056. X    (void) GetNextWID(1);
  2057. X}
  2058. X
  2059. Xt_WID
  2060. XGetNextWID(WriteCurrent)
  2061. X    int WriteCurrent; /* simply write the current MaxWID if true */
  2062. X{
  2063. X    extern int errno;
  2064. X    extern long atol();
  2065. X
  2066. X    int fd;
  2067. X    char Buffer[20];
  2068. X    struct stat StatBuf;
  2069. X#if 0
  2070. X    int FileKey; /* what one gets from a lock... */
  2071. X    int NumberOfTriesLeft = 5;
  2072. X#endif
  2073. X    t_WID Result;
  2074. X
  2075. X    /** Alter the file, so other programs can see the new words...
  2076. X     **/
  2077. X
  2078. X    /* ensure that the file is there */
  2079. X    if (stat(WidFile, &StatBuf) == -1) {
  2080. X    fprintf(stderr, "Creating WID file \"%s\"\n", WidFile);
  2081. X    if ((fd = creat(WidFile, 02666)) < 0) {
  2082. X        fprintf(stderr, "Can't create WID file \"%s\"\n", WidFile);
  2083. X        exit(1);
  2084. X    }
  2085. X    (void) close(fd);
  2086. X    return GetNextWID(WriteCurrent);
  2087. X
  2088. X    /*NOTREACHED*/
  2089. X    }
  2090. X
  2091. X    if ((fd = open(WidFile, O_RDWR, 0)) < 0) {
  2092. X    fprintf(stderr, "Can't open WID file");
  2093. X    perror(WidFile);
  2094. X    exit(1);
  2095. X    }
  2096. X
  2097. X#if 0
  2098. X    errno = 0;
  2099. X
  2100. X    /** Lock the file **/
  2101. X
  2102. X    do {
  2103. X    /* Set a timeout of 2 seconds */
  2104. X    signal(SIGALRM, WID_sig);
  2105. X    (void) alarm(3);
  2106. X    if ((FileKey = lockf(fd, F_LOCK, 0L)) < 0) {
  2107. X        switch (errno) {
  2108. X        case EACCES: /*[sic]*/ /* another process has the lock */
  2109. X        fprintf(stderr, "Please wait...\n");
  2110. X        /* shouldn't happen */
  2111. X        break;
  2112. X        case EDEADLK:
  2113. X        fprintf(stderr, "Warning: can't lock \"%s\" -- EDEADLK\n", WidFile);
  2114. X        FileKey = 1;
  2115. X        break;
  2116. X        case EINTR:
  2117. X        fprintf(stderr, "Please Wait... someone has the key...\n");
  2118. X        sleep(1);
  2119. X        break;
  2120. X        }
  2121. X    }
  2122. X    if (--NumberOfTriesLeft <= 0) {
  2123. X        fprintf(stderr, "Warning: can't lock the file \"%s\"\n", WidFile);
  2124. X    }
  2125. X    } while (FileKey < 0);
  2126. X    (void) alarm(0);
  2127. X
  2128. X    if (stat(WidFile, &StatBuf) == -1) {
  2129. X    fprintf(stderr, "It went away!\n");
  2130. X    exit(1);
  2131. X    }
  2132. X#endif
  2133. X
  2134. X    /* Read the file */
  2135. X
  2136. X    if (read(fd, Buffer, (unsigned int) StatBuf.st_size) < 0) {
  2137. X    fprintf(stderr, "Can't read from \"%s\"\n", WidFile);
  2138. X    exit(1);
  2139. X    }
  2140. X
  2141. X    Buffer[StatBuf.st_size] = '\0';
  2142. X
  2143. X    Result = atol(Buffer);
  2144. X
  2145. X    /* if WriteCurrent is set, we should not increment anything */
  2146. X    if (!WriteCurrent) {
  2147. X    ++LastNextWIDVal;
  2148. X    ++Result;
  2149. X    }
  2150. X
  2151. X    if (Result < LastNextWIDVal) {
  2152. X    Result = LastNextWIDVal;
  2153. X    }
  2154. X
  2155. X    (void) sprintf(Buffer, "%lu\n", Result);
  2156. X
  2157. X    /* Move to the start of the file and write the now value.
  2158. X     * No need to truncate the file, because it didn't shrink!
  2159. X     */
  2160. X    (void) lseek(fd, 0, 0L);
  2161. X    (void) write(fd, Buffer, (unsigned int) strlen(Buffer));
  2162. X
  2163. X#if 0
  2164. X    /** Unlock the file **/
  2165. X
  2166. X    if (lockf(fd, F_ULOCK, 0L) < 0 && FileKey == 0) {
  2167. X    fprintf(stderr, "Warning: might not have unlocked \"%s\"\n",
  2168. X                WidFile);
  2169. X    }
  2170. X#endif
  2171. X    (void) close(fd);
  2172. X
  2173. X    return Result;
  2174. X}
  2175. X
  2176. Xint
  2177. XDeleteWord(Word)
  2178. X    char *Word;
  2179. X{
  2180. X    extern t_pblock *Getpblock();
  2181. X
  2182. X    t_WID WID;
  2183. X    t_WordInfo *WordInfo;
  2184. X    t_pblock *tmp;
  2185. X
  2186. X    if ((WID = Word2WID(Word, strlen(Word))) == (t_WID) 0) {
  2187. X    return -1; /* not there */
  2188. X    }
  2189. X
  2190. X    /* get info from the list */
  2191. X    if ((WordInfo = WID2WordInfo(WID)) == (t_WordInfo *) 0) {
  2192. X    return -1;
  2193. X    }
  2194. X
  2195. X    if ((tmp = Getpblock(WordInfo)) != (t_pblock *) NULL) {
  2196. X    Deletepblock(tmp);
  2197. X    (void) efree(tmp);
  2198. X    }
  2199. X
  2200. X    /* delete the offset from the database, but retain the WID: */
  2201. X    WordInfo->Offset = 0L;
  2202. X    WordInfo->NumberOfWordPlaces = 0L;
  2203. X    WordInfo->WordPlacesInHere = 0;
  2204. X    PutWordInfoIntoIndex(WordInfo, 0L);
  2205. X    SlayWordInfo(WordInfo);
  2206. X
  2207. X    return 0;
  2208. X}
  2209. X
  2210. X/* Routines to create and destroy WordInfo structures */
  2211. XINLINE t_WordInfo *
  2212. XMakeWordInfo(WID, Length, Word)
  2213. X    t_WID WID;
  2214. X    int Length;
  2215. X    char *Word; /* the word, which might not be nul-terminated */
  2216. X{
  2217. X    register t_WordInfo *WP;
  2218. X    WP = (t_WordInfo *) emalloc(sizeof(t_WordInfo));
  2219. X
  2220. X    WP->WID = WID;
  2221. X    WP->FID = (t_FID) 0;
  2222. X    WP->Next = (t_WordInfo *) 0;
  2223. X    WP->NumberOfWordPlaces = 0;
  2224. X    WP->DataBlock = (char *) 0;
  2225. X    WP->WordPlaceStart = (char *) 0;
  2226. X    WP->WordPlaces = (t_WordPlace *) 0;
  2227. X    WP->WordPlacesInHere = 0;
  2228. X    WP->WordPlace.FID = 0; /* mark as invalid */
  2229. X    WP->WordPlace.Flags = 0; /* this gets used anyway, so set it to zero! */
  2230. X
  2231. X    WP->Word = emalloc(Length + 1);
  2232. X
  2233. X    (void) strncpy(WP->Word, Word, Length);
  2234. X    WP->Length = Length;
  2235. X    WP->Word[Length] = '\0'; /* strncpy does not add a null */
  2236. X    WP->Offset = 0;
  2237. X
  2238. X    return WP;
  2239. X}
  2240. X
  2241. Xvoid
  2242. XSlayWordInfo(WP)
  2243. X    t_WordInfo *WP;
  2244. X{
  2245. X    if (!WP) return;
  2246. X    if (WP->Word) efree(WP->Word);
  2247. X    if (WP->WordPlaces) efree((char *)WP-> WordPlaces);
  2248. X
  2249. X    WP->Next = (t_WordInfo *) 0;
  2250. X    /* The above line is to force a run-time error in the common
  2251. X     * (but wrong) case
  2252. X     * for (w = WordList; w; w = w->Next) SlayWordInfo(w);
  2253. X     */
  2254. X    efree((char *) WP);
  2255. X}
  2256. @@@End of lq-text/src/liblqtext/WordInfo.c
  2257. echo end of part 04
  2258. -- 
  2259. Liam R. E. Quin,  lee@sq.com, SoftQuad Inc., Toronto, +1 (416) 963-8337
  2260.