Annotation of src/usr.bin/mandoc/apropos_db.c, Revision 1.6
1.6 ! schwarze 1: /* $Id: apropos_db.c,v 1.5 2011/11/17 14:52:32 schwarze Exp $ */
1.1 schwarze 2: /*
3: * Copyright (c) 2011 Kristaps Dzonsons <kristaps@bsd.lv>
1.3 schwarze 4: * Copyright (c) 2011 Ingo Schwarze <schwarze@openbsd.org>
1.1 schwarze 5: *
6: * Permission to use, copy, modify, and distribute this software for any
7: * purpose with or without fee is hereby granted, provided that the above
8: * copyright notice and this permission notice appear in all copies.
9: *
10: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11: * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12: * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13: * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14: * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15: * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16: * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17: */
18: #include <assert.h>
19: #include <fcntl.h>
20: #include <regex.h>
21: #include <stdarg.h>
1.6 ! schwarze 22: #include <stdint.h>
1.1 schwarze 23: #include <stdlib.h>
24: #include <string.h>
1.5 schwarze 25: #include <unistd.h>
1.1 schwarze 26:
27: #ifdef __linux__
28: # include <db_185.h>
29: #else
30: # include <db.h>
31: #endif
32:
1.2 schwarze 33: #include "mandocdb.h"
1.1 schwarze 34: #include "apropos_db.h"
35: #include "mandoc.h"
36:
1.5 schwarze 37: struct rectree {
38: struct rec *node;
39: int len;
40: };
41:
1.1 schwarze 42: struct expr {
1.3 schwarze 43: int regex;
1.4 schwarze 44: int index;
1.6 ! schwarze 45: uint64_t mask;
1.4 schwarze 46: int and;
1.1 schwarze 47: char *v;
48: regex_t re;
1.4 schwarze 49: struct expr *next;
1.1 schwarze 50: };
51:
52: struct type {
1.6 ! schwarze 53: uint64_t mask;
1.1 schwarze 54: const char *name;
55: };
56:
57: static const struct type types[] = {
1.2 schwarze 58: { TYPE_An, "An" },
1.6 ! schwarze 59: { TYPE_Ar, "Ar" },
! 60: { TYPE_At, "At" },
! 61: { TYPE_Bsx, "Bsx" },
! 62: { TYPE_Bx, "Bx" },
1.2 schwarze 63: { TYPE_Cd, "Cd" },
1.6 ! schwarze 64: { TYPE_Cm, "Cm" },
! 65: { TYPE_Dv, "Dv" },
! 66: { TYPE_Dx, "Dx" },
! 67: { TYPE_Em, "Em" },
1.2 schwarze 68: { TYPE_Er, "Er" },
69: { TYPE_Ev, "Ev" },
1.6 ! schwarze 70: { TYPE_Fa, "Fa" },
! 71: { TYPE_Fl, "Fl" },
1.2 schwarze 72: { TYPE_Fn, "Fn" },
73: { TYPE_Fn, "Fo" },
1.6 ! schwarze 74: { TYPE_Ft, "Ft" },
! 75: { TYPE_Fx, "Fx" },
! 76: { TYPE_Ic, "Ic" },
1.2 schwarze 77: { TYPE_In, "In" },
1.6 ! schwarze 78: { TYPE_Lb, "Lb" },
! 79: { TYPE_Li, "Li" },
! 80: { TYPE_Lk, "Lk" },
! 81: { TYPE_Ms, "Ms" },
! 82: { TYPE_Mt, "Mt" },
1.2 schwarze 83: { TYPE_Nd, "Nd" },
84: { TYPE_Nm, "Nm" },
1.6 ! schwarze 85: { TYPE_Nx, "Nx" },
! 86: { TYPE_Ox, "Ox" },
1.2 schwarze 87: { TYPE_Pa, "Pa" },
1.6 ! schwarze 88: { TYPE_Rs, "Rs" },
! 89: { TYPE_Sh, "Sh" },
! 90: { TYPE_Ss, "Ss" },
1.2 schwarze 91: { TYPE_St, "St" },
1.6 ! schwarze 92: { TYPE_Sy, "Sy" },
! 93: { TYPE_Tn, "Tn" },
1.2 schwarze 94: { TYPE_Va, "Va" },
95: { TYPE_Va, "Vt" },
96: { TYPE_Xr, "Xr" },
97: { INT_MAX, "any" },
1.1 schwarze 98: { 0, NULL }
99: };
100:
101: static DB *btree_open(void);
102: static int btree_read(const DBT *, const struct mchars *, char **);
1.6 ! schwarze 103: static int exprexecpre(const struct expr *, const char *, uint64_t);
1.4 schwarze 104: static void exprexecpost(const struct expr *,
1.6 ! schwarze 105: const char *, uint64_t, int *, size_t);
1.4 schwarze 106: static struct expr *exprterm(char *, int, int);
1.1 schwarze 107: static DB *index_open(void);
108: static int index_read(const DBT *, const DBT *,
109: const struct mchars *, struct rec *);
110: static void norm_string(const char *,
111: const struct mchars *, char **);
112: static size_t norm_utf8(unsigned int, char[7]);
1.4 schwarze 113: static void recfree(struct rec *);
1.5 schwarze 114: static void single_search(struct rectree *, const struct opts *,
115: const struct expr *, size_t terms,
116: struct mchars *);
1.1 schwarze 117:
118: /*
119: * Open the keyword mandoc-db database.
120: */
121: static DB *
122: btree_open(void)
123: {
124: BTREEINFO info;
125: DB *db;
126:
127: memset(&info, 0, sizeof(BTREEINFO));
128: info.flags = R_DUP;
129:
1.2 schwarze 130: db = dbopen(MANDOC_DB, O_RDONLY, 0, DB_BTREE, &info);
1.1 schwarze 131: if (NULL != db)
132: return(db);
133:
134: return(NULL);
135: }
136:
137: /*
138: * Read a keyword from the database and normalise it.
139: * Return 0 if the database is insane, else 1.
140: */
141: static int
142: btree_read(const DBT *v, const struct mchars *mc, char **buf)
143: {
144:
145: /* Sanity: are we nil-terminated? */
146:
147: assert(v->size > 0);
148: if ('\0' != ((char *)v->data)[(int)v->size - 1])
149: return(0);
150:
151: norm_string((char *)v->data, mc, buf);
152: return(1);
153: }
154:
155: /*
156: * Take a Unicode codepoint and produce its UTF-8 encoding.
157: * This isn't the best way to do this, but it works.
158: * The magic numbers are from the UTF-8 packaging.
159: * They're not as scary as they seem: read the UTF-8 spec for details.
160: */
161: static size_t
162: norm_utf8(unsigned int cp, char out[7])
163: {
164: size_t rc;
165:
166: rc = 0;
167:
168: if (cp <= 0x0000007F) {
169: rc = 1;
170: out[0] = (char)cp;
171: } else if (cp <= 0x000007FF) {
172: rc = 2;
173: out[0] = (cp >> 6 & 31) | 192;
174: out[1] = (cp & 63) | 128;
175: } else if (cp <= 0x0000FFFF) {
176: rc = 3;
177: out[0] = (cp >> 12 & 15) | 224;
178: out[1] = (cp >> 6 & 63) | 128;
179: out[2] = (cp & 63) | 128;
180: } else if (cp <= 0x001FFFFF) {
181: rc = 4;
182: out[0] = (cp >> 18 & 7) | 240;
183: out[1] = (cp >> 12 & 63) | 128;
184: out[2] = (cp >> 6 & 63) | 128;
185: out[3] = (cp & 63) | 128;
186: } else if (cp <= 0x03FFFFFF) {
187: rc = 5;
188: out[0] = (cp >> 24 & 3) | 248;
189: out[1] = (cp >> 18 & 63) | 128;
190: out[2] = (cp >> 12 & 63) | 128;
191: out[3] = (cp >> 6 & 63) | 128;
192: out[4] = (cp & 63) | 128;
193: } else if (cp <= 0x7FFFFFFF) {
194: rc = 6;
195: out[0] = (cp >> 30 & 1) | 252;
196: out[1] = (cp >> 24 & 63) | 128;
197: out[2] = (cp >> 18 & 63) | 128;
198: out[3] = (cp >> 12 & 63) | 128;
199: out[4] = (cp >> 6 & 63) | 128;
200: out[5] = (cp & 63) | 128;
201: } else
202: return(0);
203:
204: out[rc] = '\0';
205: return(rc);
206: }
207:
208: /*
209: * Normalise strings from the index and database.
210: * These strings are escaped as defined by mandoc_char(7) along with
211: * other goop in mandoc.h (e.g., soft hyphens).
212: * This function normalises these into a nice UTF-8 string.
213: * Returns 0 if the database is fucked.
214: */
215: static void
216: norm_string(const char *val, const struct mchars *mc, char **buf)
217: {
218: size_t sz, bsz;
219: char utfbuf[7];
220: const char *seq, *cpp;
221: int len, u, pos;
222: enum mandoc_esc esc;
223: static const char res[] = { '\\', '\t',
224: ASCII_NBRSP, ASCII_HYPH, '\0' };
225:
226: /* Pre-allocate by the length of the input */
227:
228: bsz = strlen(val) + 1;
229: *buf = mandoc_realloc(*buf, bsz);
230: pos = 0;
231:
232: while ('\0' != *val) {
233: /*
234: * Halt on the first escape sequence.
235: * This also halts on the end of string, in which case
236: * we just copy, fallthrough, and exit the loop.
237: */
238: if ((sz = strcspn(val, res)) > 0) {
239: memcpy(&(*buf)[pos], val, sz);
240: pos += (int)sz;
241: val += (int)sz;
242: }
243:
244: if (ASCII_HYPH == *val) {
245: (*buf)[pos++] = '-';
246: val++;
247: continue;
248: } else if ('\t' == *val || ASCII_NBRSP == *val) {
249: (*buf)[pos++] = ' ';
250: val++;
251: continue;
252: } else if ('\\' != *val)
253: break;
254:
255: /* Read past the slash. */
256:
257: val++;
258: u = 0;
259:
260: /*
261: * Parse the escape sequence and see if it's a
262: * predefined character or special character.
263: */
264:
265: esc = mandoc_escape(&val, &seq, &len);
266: if (ESCAPE_ERROR == esc)
267: break;
268:
269: /*
270: * XXX - this just does UTF-8, but we need to know
271: * beforehand whether we should do text substitution.
272: */
273:
274: switch (esc) {
275: case (ESCAPE_SPECIAL):
276: if (0 != (u = mchars_spec2cp(mc, seq, len)))
277: break;
278: /* FALLTHROUGH */
279: default:
280: continue;
281: }
282:
283: /*
284: * If we have a Unicode codepoint, try to convert that
285: * to a UTF-8 byte string.
286: */
287:
288: cpp = utfbuf;
289: if (0 == (sz = norm_utf8(u, utfbuf)))
290: continue;
291:
292: /* Copy the rendered glyph into the stream. */
293:
294: sz = strlen(cpp);
295: bsz += sz;
296:
297: *buf = mandoc_realloc(*buf, bsz);
298:
299: memcpy(&(*buf)[pos], cpp, sz);
300: pos += (int)sz;
301: }
302:
303: (*buf)[pos] = '\0';
304: }
305:
306: /*
307: * Open the filename-index mandoc-db database.
308: * Returns NULL if opening failed.
309: */
310: static DB *
311: index_open(void)
312: {
313: DB *db;
314:
1.2 schwarze 315: db = dbopen(MANDOC_IDX, O_RDONLY, 0, DB_RECNO, NULL);
1.1 schwarze 316: if (NULL != db)
317: return(db);
318:
319: return(NULL);
320: }
321:
322: /*
323: * Safely unpack from an index file record into the structure.
324: * Returns 1 if an entry was unpacked, 0 if the database is insane.
325: */
326: static int
327: index_read(const DBT *key, const DBT *val,
328: const struct mchars *mc, struct rec *rec)
329: {
330: size_t left;
331: char *np, *cp;
332:
333: #define INDEX_BREAD(_dst) \
334: do { \
335: if (NULL == (np = memchr(cp, '\0', left))) \
336: return(0); \
337: norm_string(cp, mc, &(_dst)); \
338: left -= (np - cp) + 1; \
339: cp = np + 1; \
340: } while (/* CONSTCOND */ 0)
341:
342: left = val->size;
343: cp = (char *)val->data;
344:
345: rec->rec = *(recno_t *)key->data;
346:
347: INDEX_BREAD(rec->file);
348: INDEX_BREAD(rec->cat);
349: INDEX_BREAD(rec->title);
350: INDEX_BREAD(rec->arch);
351: INDEX_BREAD(rec->desc);
352: return(1);
353: }
354:
355: /*
356: * Search the mandocdb database for the expression "expr".
357: * Filter out by "opts".
358: * Call "res" with the results, which may be zero.
359: */
360: void
1.5 schwarze 361: apropos_search(int argc, char *argv[], const struct opts *opts,
362: const struct expr *expr, size_t terms, void *arg,
1.4 schwarze 363: void (*res)(struct rec *, size_t, void *))
1.1 schwarze 364: {
1.5 schwarze 365: struct rectree tree;
366: struct mchars *mc;
367: struct rec *recs;
368: int i, mlen;
369:
370: memset(&tree, 0, sizeof(struct rectree));
371:
372: /* XXX: error out with bad regexp? */
373:
374: mc = mchars_alloc();
375:
376: for (i = 0; i < argc; i++) {
377: if (chdir(argv[i]))
378: continue;
379: single_search(&tree, opts, expr, terms, mc);
380: }
381:
382: /*
383: * Count the matching files
384: * and feed them to the output handler.
385: */
386:
387: for (mlen = i = 0; i < tree.len; i++)
388: if (tree.node[i].matches[0])
389: mlen++;
390: recs = mandoc_malloc(mlen * sizeof(struct rec));
391: for (mlen = i = 0; i < tree.len; i++)
392: if (tree.node[i].matches[0])
393: memcpy(&recs[mlen++], &tree.node[i],
394: sizeof(struct rec));
395: (*res)(recs, mlen, arg);
396: free(recs);
397:
398: for (i = 0; i < tree.len; i++)
399: recfree(&tree.node[i]);
400:
401: if (mc)
402: mchars_free(mc);
403: }
404:
405: static void
406: single_search(struct rectree *tree, const struct opts *opts,
407: const struct expr *expr, size_t terms,
408: struct mchars *mc)
409: {
1.6 ! schwarze 410: int root, leaf;
1.1 schwarze 411: DBT key, val;
412: DB *btree, *idx;
413: int ch;
414: char *buf;
415: recno_t rec;
1.5 schwarze 416: struct rec *recs;
1.1 schwarze 417: struct rec srec;
1.6 ! schwarze 418: struct db_val *vbuf;
1.1 schwarze 419:
420: root = -1;
421: leaf = -1;
422: btree = NULL;
423: idx = NULL;
424: buf = NULL;
1.5 schwarze 425: recs = tree->node;
1.1 schwarze 426:
427: memset(&srec, 0, sizeof(struct rec));
428:
429: /* XXX: return fact that we've errored? */
430:
431: if (NULL == (btree = btree_open()))
432: goto out;
433: if (NULL == (idx = index_open()))
434: goto out;
435:
436: while (0 == (ch = (*btree->seq)(btree, &key, &val, R_NEXT))) {
437: /*
438: * Low-water mark for key and value.
439: * The key must have something in it, and the value must
440: * have the correct tags/recno mix.
441: */
1.6 ! schwarze 442: if (key.size < 2 || sizeof(struct db_val) != val.size)
1.1 schwarze 443: break;
444: if ( ! btree_read(&key, mc, &buf))
445: break;
446:
1.4 schwarze 447: /*
448: * See if this keyword record matches any of the
449: * expressions we have stored.
450: */
1.6 ! schwarze 451: vbuf = val.data;
! 452: if ( ! exprexecpre(expr, buf, vbuf->mask))
1.1 schwarze 453: continue;
1.6 ! schwarze 454: rec = vbuf->rec;
1.1 schwarze 455:
456: /*
457: * O(log n) scan for prior records. Since a record
458: * number is unbounded, this has decent performance over
459: * a complex hash function.
460: */
461:
462: for (leaf = root; leaf >= 0; )
463: if (rec > recs[leaf].rec && recs[leaf].rhs >= 0)
464: leaf = recs[leaf].rhs;
465: else if (rec < recs[leaf].rec && recs[leaf].lhs >= 0)
466: leaf = recs[leaf].lhs;
467: else
468: break;
469:
1.4 schwarze 470: if (leaf >= 0 && recs[leaf].rec == rec) {
471: if (0 == recs[leaf].matches[0])
472: exprexecpost
1.6 ! schwarze 473: (expr, buf, vbuf->mask,
1.4 schwarze 474: recs[leaf].matches, terms);
1.1 schwarze 475: continue;
1.4 schwarze 476: }
1.1 schwarze 477:
478: /*
479: * Now we actually extract the manpage's metadata from
480: * the index database.
481: */
482:
483: key.data = &rec;
484: key.size = sizeof(recno_t);
485:
486: if (0 != (*idx->get)(idx, &key, &val, 0))
487: break;
488:
489: srec.lhs = srec.rhs = -1;
490: if ( ! index_read(&key, &val, mc, &srec))
491: break;
492:
493: if (opts->cat && strcasecmp(opts->cat, srec.cat))
494: continue;
495: if (opts->arch && strcasecmp(opts->arch, srec.arch))
496: continue;
497:
1.5 schwarze 498: tree->node = recs = mandoc_realloc
499: (recs, (tree->len + 1) * sizeof(struct rec));
1.1 schwarze 500:
1.5 schwarze 501: memcpy(&recs[tree->len], &srec, sizeof(struct rec));
502: recs[tree->len].matches =
1.4 schwarze 503: mandoc_calloc(terms + 1, sizeof(int));
504:
505: exprexecpost
1.6 ! schwarze 506: (expr, buf, vbuf->mask,
1.5 schwarze 507: recs[tree->len].matches, terms);
1.1 schwarze 508:
509: /* Append to our tree. */
510:
511: if (leaf >= 0) {
512: if (rec > recs[leaf].rec)
1.5 schwarze 513: recs[leaf].rhs = tree->len;
1.1 schwarze 514: else
1.5 schwarze 515: recs[leaf].lhs = tree->len;
1.1 schwarze 516: } else
1.5 schwarze 517: root = tree->len;
1.1 schwarze 518:
519: memset(&srec, 0, sizeof(struct rec));
1.5 schwarze 520: tree->len++;
1.1 schwarze 521: }
522:
1.5 schwarze 523: /* XXX handle database errors? */
1.1 schwarze 524:
525: out:
1.4 schwarze 526: recfree(&srec);
1.1 schwarze 527:
528: if (btree)
529: (*btree->close)(btree);
530: if (idx)
531: (*idx->close)(idx);
532:
533: free(buf);
534: }
535:
1.4 schwarze 536: static void
537: recfree(struct rec *rec)
538: {
539:
540: free(rec->file);
541: free(rec->matches);
542: free(rec->cat);
543: free(rec->title);
544: free(rec->arch);
545: free(rec->desc);
546: }
547:
1.1 schwarze 548: struct expr *
1.4 schwarze 549: exprcomp(int argc, char *argv[], size_t *tt)
550: {
551: struct expr *e, *first, *next;
552: int pos, log;
553:
554: first = next = NULL;
555: (*tt) = 0;
556:
557: for (pos = 0; pos < argc; pos++) {
558: e = next;
559: log = 0;
560:
561: if (0 == strcmp("-a", argv[pos]))
562: log = 1;
563: else if (0 == strcmp("-o", argv[pos]))
564: log = 2;
565:
566: if (log > 0 && ++pos >= argc)
567: goto err;
568:
569: if (0 == strcmp("-i", argv[pos])) {
570: if (++pos >= argc)
571: goto err;
572: next = exprterm(argv[pos], 1, log == 1);
573: } else
574: next = exprterm(argv[pos], 0, log == 1);
575:
576: if (NULL == next)
577: goto err;
578:
579: next->index = (int)(*tt)++;
580:
581: if (NULL == first) {
582: assert(NULL == e);
583: first = next;
584: } else {
585: assert(NULL != e);
586: e->next = next;
587: }
588: }
589:
590: return(first);
591: err:
592: exprfree(first);
593: return(NULL);
594: }
595:
596: static struct expr *
597: exprterm(char *buf, int cs, int and)
1.1 schwarze 598: {
1.4 schwarze 599: struct expr e;
1.1 schwarze 600: struct expr *p;
1.3 schwarze 601: char *key;
1.4 schwarze 602: int i;
1.1 schwarze 603:
1.4 schwarze 604: memset(&e, 0, sizeof(struct expr));
1.1 schwarze 605:
1.4 schwarze 606: e.and = and;
607:
608: /*
609: * Choose regex or substring match.
1.3 schwarze 610: */
611:
1.4 schwarze 612: if (NULL == (e.v = strpbrk(buf, "=~"))) {
1.3 schwarze 613: e.regex = 0;
1.4 schwarze 614: e.v = buf;
1.3 schwarze 615: } else {
616: e.regex = '~' == *e.v;
617: *e.v++ = '\0';
618: }
1.1 schwarze 619:
1.3 schwarze 620: /*
621: * Determine the record types to search for.
622: */
623:
624: e.mask = 0;
1.4 schwarze 625: if (buf < e.v) {
626: while (NULL != (key = strsep(&buf, ","))) {
1.3 schwarze 627: i = 0;
628: while (types[i].mask &&
629: strcmp(types[i].name, key))
630: i++;
631: e.mask |= types[i].mask;
632: }
633: }
634: if (0 == e.mask)
635: e.mask = TYPE_Nm | TYPE_Nd;
1.1 schwarze 636:
1.4 schwarze 637: if (e.regex) {
638: i = REG_EXTENDED | REG_NOSUB | cs ? REG_ICASE : 0;
639: if (regcomp(&e.re, e.v, i))
640: return(NULL);
641: }
1.1 schwarze 642:
1.3 schwarze 643: e.v = mandoc_strdup(e.v);
1.1 schwarze 644:
645: p = mandoc_calloc(1, sizeof(struct expr));
646: memcpy(p, &e, sizeof(struct expr));
647: return(p);
648: }
649:
650: void
651: exprfree(struct expr *p)
652: {
1.4 schwarze 653: struct expr *pp;
654:
655: while (NULL != p) {
656: if (p->regex)
657: regfree(&p->re);
658: free(p->v);
659: pp = p->next;
660: free(p);
661: p = pp;
662: }
663: }
1.1 schwarze 664:
1.4 schwarze 665: /*
666: * See if this expression evaluates to true for any terms.
667: * Return 1 if any expression evaluates to true, else 0.
668: */
669: static int
1.6 ! schwarze 670: exprexecpre(const struct expr *p, const char *cp, uint64_t mask)
1.4 schwarze 671: {
1.1 schwarze 672:
1.4 schwarze 673: for ( ; NULL != p; p = p->next) {
674: if ( ! (mask & p->mask))
675: continue;
676: if (p->regex) {
677: if (0 == regexec(&p->re, cp, 0, NULL, 0))
678: return(1);
679: } else if (NULL != strcasestr(cp, p->v))
680: return(1);
681: }
682: return(0);
1.1 schwarze 683: }
684:
1.4 schwarze 685: /*
686: * First, update the array of terms for which this expression evaluates
687: * to true.
688: * Second, logically evaluate all terms over the updated array of truth
689: * values.
690: * If this evaluates to true, mark the expression as satisfied.
691: */
692: static void
693: exprexecpost(const struct expr *e, const char *cp,
1.6 ! schwarze 694: uint64_t mask, int *matches, size_t matchsz)
1.1 schwarze 695: {
1.4 schwarze 696: const struct expr *p;
697: int match;
1.1 schwarze 698:
1.4 schwarze 699: assert(0 == matches[0]);
700:
701: for (p = e; p; p = p->next) {
702: if ( ! (mask & p->mask))
703: continue;
704: if (p->regex) {
705: if (regexec(&p->re, cp, 0, NULL, 0))
706: continue;
707: } else if (NULL == strcasestr(cp, p->v))
708: continue;
709:
710: matches[p->index + 1] = 1;
711: }
712:
713: for (match = 0, p = e; p && ! match; p = p->next) {
714: match = matches[p->index + 1];
715: for ( ; p->next && p->next->and; p = p->next)
716: match = match && matches[p->next->index + 1];
717: }
1.1 schwarze 718:
1.4 schwarze 719: matches[0] = match;
1.1 schwarze 720: }