Import mdocml-1.12.3
[dragonfly.git] / contrib / mdocml / apropos_db.c
CommitLineData
7888c61d 1/* $Id: apropos_db.c,v 1.32.2.3 2013/10/10 23:43:04 schwarze Exp $ */
36342e81
SW
2/*
3 * Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv>
4 * Copyright (c) 2011 Ingo Schwarze <schwarze@openbsd.org>
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#ifdef HAVE_CONFIG_H
19#include "config.h"
20#endif
21
f88b6c16
FF
22#include <sys/param.h>
23
36342e81
SW
24#include <assert.h>
25#include <fcntl.h>
26#include <regex.h>
27#include <stdarg.h>
28#include <stdint.h>
29#include <stdlib.h>
30#include <string.h>
31#include <unistd.h>
32
7888c61d 33#if defined(__APPLE__)
36342e81 34# include <libkern/OSByteOrder.h>
7888c61d
FF
35#elif defined(__linux__)
36# include <endian.h>
37#elif defined(__sun)
38# include <sys/byteorder.h>
36342e81 39#else
f88b6c16 40# include <sys/endian.h>
7888c61d
FF
41#endif
42
43#if defined(__linux__) || defined(__sun)
44# include <db_185.h>
45#else
36342e81
SW
46# include <db.h>
47#endif
48
49#include "mandocdb.h"
50#include "apropos_db.h"
51#include "mandoc.h"
52
53#define RESFREE(_x) \
54 do { \
55 free((_x)->file); \
56 free((_x)->cat); \
57 free((_x)->title); \
58 free((_x)->arch); \
59 free((_x)->desc); \
60 free((_x)->matches); \
61 } while (/*CONSTCOND*/0)
62
63struct expr {
64 int regex; /* is regex? */
65 int index; /* index in match array */
66 uint64_t mask; /* type-mask */
67 int and; /* is rhs of logical AND? */
68 char *v; /* search value */
69 regex_t re; /* compiled re, if regex */
70 struct expr *next; /* next in sequence */
71 struct expr *subexpr;
72};
73
74struct type {
75 uint64_t mask;
76 const char *name;
77};
78
79struct rectree {
80 struct res *node; /* record array for dir tree */
81 int len; /* length of record array */
82};
83
84static const struct type types[] = {
85 { TYPE_An, "An" },
86 { TYPE_Ar, "Ar" },
87 { TYPE_At, "At" },
88 { TYPE_Bsx, "Bsx" },
89 { TYPE_Bx, "Bx" },
90 { TYPE_Cd, "Cd" },
91 { TYPE_Cm, "Cm" },
92 { TYPE_Dv, "Dv" },
93 { TYPE_Dx, "Dx" },
94 { TYPE_Em, "Em" },
95 { TYPE_Er, "Er" },
96 { TYPE_Ev, "Ev" },
97 { TYPE_Fa, "Fa" },
98 { TYPE_Fl, "Fl" },
99 { TYPE_Fn, "Fn" },
100 { TYPE_Fn, "Fo" },
101 { TYPE_Ft, "Ft" },
102 { TYPE_Fx, "Fx" },
103 { TYPE_Ic, "Ic" },
104 { TYPE_In, "In" },
105 { TYPE_Lb, "Lb" },
106 { TYPE_Li, "Li" },
107 { TYPE_Lk, "Lk" },
108 { TYPE_Ms, "Ms" },
109 { TYPE_Mt, "Mt" },
110 { TYPE_Nd, "Nd" },
111 { TYPE_Nm, "Nm" },
112 { TYPE_Nx, "Nx" },
113 { TYPE_Ox, "Ox" },
114 { TYPE_Pa, "Pa" },
115 { TYPE_Rs, "Rs" },
116 { TYPE_Sh, "Sh" },
117 { TYPE_Ss, "Ss" },
118 { TYPE_St, "St" },
119 { TYPE_Sy, "Sy" },
120 { TYPE_Tn, "Tn" },
121 { TYPE_Va, "Va" },
122 { TYPE_Va, "Vt" },
123 { TYPE_Xr, "Xr" },
124 { UINT64_MAX, "any" },
125 { 0, NULL }
126};
127
128static DB *btree_open(void);
129static int btree_read(const DBT *, const DBT *,
130 const struct mchars *,
131 uint64_t *, recno_t *, char **);
132static int expreval(const struct expr *, int *);
133static void exprexec(const struct expr *,
134 const char *, uint64_t, struct res *);
135static int exprmark(const struct expr *,
136 const char *, uint64_t, int *);
137static struct expr *exprexpr(int, char *[], int *, int *, size_t *);
138static struct expr *exprterm(char *, int);
139static DB *index_open(void);
140static int index_read(const DBT *, const DBT *, int,
141 const struct mchars *, struct res *);
142static void norm_string(const char *,
143 const struct mchars *, char **);
144static size_t norm_utf8(unsigned int, char[7]);
145static int single_search(struct rectree *, const struct opts *,
146 const struct expr *, size_t terms,
147 struct mchars *, int);
148
149/*
150 * Open the keyword mandoc-db database.
151 */
152static DB *
153btree_open(void)
154{
155 BTREEINFO info;
156 DB *db;
157
158 memset(&info, 0, sizeof(BTREEINFO));
159 info.lorder = 4321;
160 info.flags = R_DUP;
161
162 db = dbopen(MANDOC_DB, O_RDONLY, 0, DB_BTREE, &info);
163 if (NULL != db)
164 return(db);
165
166 return(NULL);
167}
168
169/*
170 * Read a keyword from the database and normalise it.
171 * Return 0 if the database is insane, else 1.
172 */
173static int
174btree_read(const DBT *k, const DBT *v, const struct mchars *mc,
175 uint64_t *mask, recno_t *rec, char **buf)
176{
177 uint64_t vbuf[2];
178
179 /* Are our sizes sane? */
180 if (k->size < 2 || sizeof(vbuf) != v->size)
181 return(0);
182
183 /* Is our string nil-terminated? */
184 if ('\0' != ((const char *)k->data)[(int)k->size - 1])
185 return(0);
186
187 norm_string((const char *)k->data, mc, buf);
188 memcpy(vbuf, v->data, v->size);
189 *mask = betoh64(vbuf[0]);
190 *rec = betoh64(vbuf[1]);
191 return(1);
192}
193
194/*
195 * Take a Unicode codepoint and produce its UTF-8 encoding.
196 * This isn't the best way to do this, but it works.
197 * The magic numbers are from the UTF-8 packaging.
198 * They're not as scary as they seem: read the UTF-8 spec for details.
199 */
200static size_t
201norm_utf8(unsigned int cp, char out[7])
202{
203 int rc;
204
205 rc = 0;
206
207 if (cp <= 0x0000007F) {
208 rc = 1;
209 out[0] = (char)cp;
210 } else if (cp <= 0x000007FF) {
211 rc = 2;
212 out[0] = (cp >> 6 & 31) | 192;
213 out[1] = (cp & 63) | 128;
214 } else if (cp <= 0x0000FFFF) {
215 rc = 3;
216 out[0] = (cp >> 12 & 15) | 224;
217 out[1] = (cp >> 6 & 63) | 128;
218 out[2] = (cp & 63) | 128;
219 } else if (cp <= 0x001FFFFF) {
220 rc = 4;
221 out[0] = (cp >> 18 & 7) | 240;
222 out[1] = (cp >> 12 & 63) | 128;
223 out[2] = (cp >> 6 & 63) | 128;
224 out[3] = (cp & 63) | 128;
225 } else if (cp <= 0x03FFFFFF) {
226 rc = 5;
227 out[0] = (cp >> 24 & 3) | 248;
228 out[1] = (cp >> 18 & 63) | 128;
229 out[2] = (cp >> 12 & 63) | 128;
230 out[3] = (cp >> 6 & 63) | 128;
231 out[4] = (cp & 63) | 128;
232 } else if (cp <= 0x7FFFFFFF) {
233 rc = 6;
234 out[0] = (cp >> 30 & 1) | 252;
235 out[1] = (cp >> 24 & 63) | 128;
236 out[2] = (cp >> 18 & 63) | 128;
237 out[3] = (cp >> 12 & 63) | 128;
238 out[4] = (cp >> 6 & 63) | 128;
239 out[5] = (cp & 63) | 128;
240 } else
241 return(0);
242
243 out[rc] = '\0';
244 return((size_t)rc);
245}
246
247/*
248 * Normalise strings from the index and database.
249 * These strings are escaped as defined by mandoc_char(7) along with
250 * other goop in mandoc.h (e.g., soft hyphens).
251 * This function normalises these into a nice UTF-8 string.
252 * Returns 0 if the database is fucked.
253 */
254static void
255norm_string(const char *val, const struct mchars *mc, char **buf)
256{
257 size_t sz, bsz;
258 char utfbuf[7];
259 const char *seq, *cpp;
260 int len, u, pos;
261 enum mandoc_esc esc;
262 static const char res[] = { '\\', '\t',
263 ASCII_NBRSP, ASCII_HYPH, '\0' };
264
265 /* Pre-allocate by the length of the input */
266
267 bsz = strlen(val) + 1;
268 *buf = mandoc_realloc(*buf, bsz);
269 pos = 0;
270
271 while ('\0' != *val) {
272 /*
273 * Halt on the first escape sequence.
274 * This also halts on the end of string, in which case
275 * we just copy, fallthrough, and exit the loop.
276 */
277 if ((sz = strcspn(val, res)) > 0) {
278 memcpy(&(*buf)[pos], val, sz);
279 pos += (int)sz;
280 val += (int)sz;
281 }
282
283 if (ASCII_HYPH == *val) {
284 (*buf)[pos++] = '-';
285 val++;
286 continue;
287 } else if ('\t' == *val || ASCII_NBRSP == *val) {
288 (*buf)[pos++] = ' ';
289 val++;
290 continue;
291 } else if ('\\' != *val)
292 break;
293
294 /* Read past the slash. */
295
296 val++;
297 u = 0;
298
299 /*
300 * Parse the escape sequence and see if it's a
301 * predefined character or special character.
302 */
303
304 esc = mandoc_escape(&val, &seq, &len);
305 if (ESCAPE_ERROR == esc)
306 break;
307
308 /*
309 * XXX - this just does UTF-8, but we need to know
310 * beforehand whether we should do text substitution.
311 */
312
313 switch (esc) {
314 case (ESCAPE_SPECIAL):
315 if (0 != (u = mchars_spec2cp(mc, seq, len)))
316 break;
317 /* FALLTHROUGH */
318 default:
319 continue;
320 }
321
322 /*
323 * If we have a Unicode codepoint, try to convert that
324 * to a UTF-8 byte string.
325 */
326
327 cpp = utfbuf;
328 if (0 == (sz = norm_utf8(u, utfbuf)))
329 continue;
330
331 /* Copy the rendered glyph into the stream. */
332
333 sz = strlen(cpp);
334 bsz += sz;
335
336 *buf = mandoc_realloc(*buf, bsz);
337
338 memcpy(&(*buf)[pos], cpp, sz);
339 pos += (int)sz;
340 }
341
342 (*buf)[pos] = '\0';
343}
344
345/*
346 * Open the filename-index mandoc-db database.
347 * Returns NULL if opening failed.
348 */
349static DB *
350index_open(void)
351{
352 DB *db;
353
354 db = dbopen(MANDOC_IDX, O_RDONLY, 0, DB_RECNO, NULL);
355 if (NULL != db)
356 return(db);
357
358 return(NULL);
359}
360
361/*
362 * Safely unpack from an index file record into the structure.
363 * Returns 1 if an entry was unpacked, 0 if the database is insane.
364 */
365static int
366index_read(const DBT *key, const DBT *val, int index,
367 const struct mchars *mc, struct res *rec)
368{
369 size_t left;
370 char *np, *cp;
371 char type;
372
373#define INDEX_BREAD(_dst) \
374 do { \
375 if (NULL == (np = memchr(cp, '\0', left))) \
376 return(0); \
377 norm_string(cp, mc, &(_dst)); \
378 left -= (np - cp) + 1; \
379 cp = np + 1; \
380 } while (/* CONSTCOND */ 0)
381
382 if (0 == (left = val->size))
383 return(0);
384
385 cp = val->data;
386 assert(sizeof(recno_t) == key->size);
387 memcpy(&rec->rec, key->data, key->size);
388 rec->volume = index;
389
390 if ('d' == (type = *cp++))
391 rec->type = RESTYPE_MDOC;
392 else if ('a' == type)
393 rec->type = RESTYPE_MAN;
394 else if ('c' == type)
395 rec->type = RESTYPE_CAT;
396 else
397 return(0);
398
399 left--;
400 INDEX_BREAD(rec->file);
401 INDEX_BREAD(rec->cat);
402 INDEX_BREAD(rec->title);
403 INDEX_BREAD(rec->arch);
404 INDEX_BREAD(rec->desc);
405 return(1);
406}
407
408/*
409 * Search mandocdb databases in paths for expression "expr".
410 * Filter out by "opts".
411 * Call "res" with the results, which may be zero.
412 * Return 0 if there was a database error, else return 1.
413 */
414int
415apropos_search(int pathsz, char **paths, const struct opts *opts,
416 const struct expr *expr, size_t terms, void *arg,
417 size_t *sz, struct res **resp,
418 void (*res)(struct res *, size_t, void *))
419{
420 struct rectree tree;
421 struct mchars *mc;
7888c61d 422 int i;
36342e81
SW
423
424 memset(&tree, 0, sizeof(struct rectree));
425
36342e81
SW
426 mc = mchars_alloc();
427 *sz = 0;
428 *resp = NULL;
429
430 /*
431 * Main loop. Change into the directory containing manpage
432 * databases. Run our expession over each database in the set.
433 */
434
435 for (i = 0; i < pathsz; i++) {
f88b6c16 436 assert('/' == paths[i][0]);
36342e81
SW
437 if (chdir(paths[i]))
438 continue;
439 if (single_search(&tree, opts, expr, terms, mc, i))
440 continue;
441
442 resfree(tree.node, tree.len);
443 mchars_free(mc);
444 return(0);
445 }
446
447 (*res)(tree.node, tree.len, arg);
448 *sz = tree.len;
449 *resp = tree.node;
450 mchars_free(mc);
451 return(1);
452}
453
454static int
455single_search(struct rectree *tree, const struct opts *opts,
456 const struct expr *expr, size_t terms,
457 struct mchars *mc, int vol)
458{
459 int root, leaf, ch;
460 DBT key, val;
461 DB *btree, *idx;
462 char *buf;
463 struct res *rs;
464 struct res r;
465 uint64_t mask;
466 recno_t rec;
467
468 root = -1;
469 leaf = -1;
470 btree = NULL;
471 idx = NULL;
472 buf = NULL;
473 rs = tree->node;
474
475 memset(&r, 0, sizeof(struct res));
476
477 if (NULL == (btree = btree_open()))
478 return(1);
479
480 if (NULL == (idx = index_open())) {
481 (*btree->close)(btree);
482 return(1);
483 }
484
485 while (0 == (ch = (*btree->seq)(btree, &key, &val, R_NEXT))) {
486 if ( ! btree_read(&key, &val, mc, &mask, &rec, &buf))
487 break;
488
489 /*
490 * See if this keyword record matches any of the
491 * expressions we have stored.
492 */
493 if ( ! exprmark(expr, buf, mask, NULL))
494 continue;
495
496 /*
497 * O(log n) scan for prior records. Since a record
498 * number is unbounded, this has decent performance over
499 * a complex hash function.
500 */
501
502 for (leaf = root; leaf >= 0; )
503 if (rec > rs[leaf].rec &&
504 rs[leaf].rhs >= 0)
505 leaf = rs[leaf].rhs;
506 else if (rec < rs[leaf].rec &&
507 rs[leaf].lhs >= 0)
508 leaf = rs[leaf].lhs;
509 else
510 break;
511
512 /*
513 * If we find a record, see if it has already evaluated
514 * to true. If it has, great, just keep going. If not,
515 * try to evaluate it now and continue anyway.
516 */
517
518 if (leaf >= 0 && rs[leaf].rec == rec) {
519 if (0 == rs[leaf].matched)
520 exprexec(expr, buf, mask, &rs[leaf]);
521 continue;
522 }
523
524 /*
525 * We have a new file to examine.
526 * Extract the manpage's metadata from the index
527 * database, then begin partial evaluation.
528 */
529
530 key.data = &rec;
531 key.size = sizeof(recno_t);
532
533 if (0 != (*idx->get)(idx, &key, &val, 0))
534 break;
535
536 r.lhs = r.rhs = -1;
537 if ( ! index_read(&key, &val, vol, mc, &r))
538 break;
539
540 /* XXX: this should be elsewhere, I guess? */
541
542 if (opts->cat && strcasecmp(opts->cat, r.cat))
543 continue;
544
545 if (opts->arch && *r.arch)
546 if (strcasecmp(opts->arch, r.arch))
547 continue;
548
549 tree->node = rs = mandoc_realloc
550 (rs, (tree->len + 1) * sizeof(struct res));
551
552 memcpy(&rs[tree->len], &r, sizeof(struct res));
553 memset(&r, 0, sizeof(struct res));
554 rs[tree->len].matches =
555 mandoc_calloc(terms, sizeof(int));
556
557 exprexec(expr, buf, mask, &rs[tree->len]);
558
559 /* Append to our tree. */
560
561 if (leaf >= 0) {
562 if (rec > rs[leaf].rec)
563 rs[leaf].rhs = tree->len;
564 else
565 rs[leaf].lhs = tree->len;
566 } else
567 root = tree->len;
568
569 tree->len++;
570 }
571
572 (*btree->close)(btree);
573 (*idx->close)(idx);
574
575 free(buf);
576 RESFREE(&r);
577 return(1 == ch);
578}
579
580void
581resfree(struct res *rec, size_t sz)
582{
583 size_t i;
584
585 for (i = 0; i < sz; i++)
586 RESFREE(&rec[i]);
587 free(rec);
588}
589
590/*
591 * Compile a list of straight-up terms.
592 * The arguments are re-written into ~[[:<:]]term[[:>:]], or "term"
593 * surrounded by word boundaries, then pumped through exprterm().
594 * Terms are case-insensitive.
595 * This emulates whatis(1) behaviour.
596 */
597struct expr *
598termcomp(int argc, char *argv[], size_t *tt)
599{
600 char *buf;
601 int pos;
602 struct expr *e, *next;
603 size_t sz;
604
605 buf = NULL;
606 e = NULL;
607 *tt = 0;
608
609 for (pos = argc - 1; pos >= 0; pos--) {
610 sz = strlen(argv[pos]) + 18;
611 buf = mandoc_realloc(buf, sz);
612 strlcpy(buf, "Nm~[[:<:]]", sz);
613 strlcat(buf, argv[pos], sz);
614 strlcat(buf, "[[:>:]]", sz);
615 if (NULL == (next = exprterm(buf, 0))) {
616 free(buf);
617 exprfree(e);
618 return(NULL);
619 }
620 next->next = e;
621 e = next;
622 (*tt)++;
623 }
624
625 free(buf);
626 return(e);
627}
628
629/*
630 * Compile a sequence of logical expressions.
631 * See apropos.1 for a grammar of this sequence.
632 */
633struct expr *
634exprcomp(int argc, char *argv[], size_t *tt)
635{
636 int pos, lvl;
637 struct expr *e;
638
639 pos = lvl = 0;
640 *tt = 0;
641
642 e = exprexpr(argc, argv, &pos, &lvl, tt);
643
644 if (0 == lvl && pos >= argc)
645 return(e);
646
647 exprfree(e);
648 return(NULL);
649}
650
651/*
652 * Compile an array of tokens into an expression.
653 * An informal expression grammar is defined in apropos(1).
654 * Return NULL if we fail doing so. All memory will be cleaned up.
655 * Return the root of the expression sequence if alright.
656 */
657static struct expr *
658exprexpr(int argc, char *argv[], int *pos, int *lvl, size_t *tt)
659{
660 struct expr *e, *first, *next;
661 int log;
662
663 first = next = NULL;
664
665 for ( ; *pos < argc; (*pos)++) {
666 e = next;
667
668 /*
669 * Close out a subexpression.
670 */
671
672 if (NULL != e && 0 == strcmp(")", argv[*pos])) {
673 if (--(*lvl) < 0)
674 goto err;
675 break;
676 }
677
678 /*
679 * Small note: if we're just starting, don't let "-a"
680 * and "-o" be considered logical operators: they're
681 * just tokens unless pairwise joining, in which case we
682 * record their existence (or assume "OR").
683 */
684 log = 0;
685
686 if (NULL != e && 0 == strcmp("-a", argv[*pos]))
687 log = 1;
688 else if (NULL != e && 0 == strcmp("-o", argv[*pos]))
689 log = 2;
690
691 if (log > 0 && ++(*pos) >= argc)
692 goto err;
693
694 /*
695 * Now we parse the term part. This can begin with
696 * "-i", in which case the expression is case
697 * insensitive.
698 */
699
700 if (0 == strcmp("(", argv[*pos])) {
701 ++(*pos);
702 ++(*lvl);
703 next = mandoc_calloc(1, sizeof(struct expr));
704 next->subexpr = exprexpr(argc, argv, pos, lvl, tt);
705 if (NULL == next->subexpr) {
706 free(next);
707 next = NULL;
708 }
709 } else if (0 == strcmp("-i", argv[*pos])) {
710 if (++(*pos) >= argc)
711 goto err;
712 next = exprterm(argv[*pos], 0);
713 } else
714 next = exprterm(argv[*pos], 1);
715
716 if (NULL == next)
717 goto err;
718
719 next->and = log == 1;
720 next->index = (int)(*tt)++;
721
722 /* Append to our chain of expressions. */
723
724 if (NULL == first) {
725 assert(NULL == e);
726 first = next;
727 } else {
728 assert(NULL != e);
729 e->next = next;
730 }
731 }
732
733 return(first);
734err:
735 exprfree(first);
736 return(NULL);
737}
738
739/*
740 * Parse a terminal expression with the grammar as defined in
741 * apropos(1).
742 * Return NULL if we fail the parse.
743 */
744static struct expr *
745exprterm(char *buf, int cs)
746{
747 struct expr e;
748 struct expr *p;
749 char *key;
750 int i;
751
752 memset(&e, 0, sizeof(struct expr));
753
754 /* Choose regex or substring match. */
755
756 if (NULL == (e.v = strpbrk(buf, "=~"))) {
757 e.regex = 0;
758 e.v = buf;
759 } else {
760 e.regex = '~' == *e.v;
761 *e.v++ = '\0';
762 }
763
764 /* Determine the record types to search for. */
765
766 e.mask = 0;
767 if (buf < e.v) {
768 while (NULL != (key = strsep(&buf, ","))) {
769 i = 0;
770 while (types[i].mask &&
771 strcmp(types[i].name, key))
772 i++;
773 e.mask |= types[i].mask;
774 }
775 }
776 if (0 == e.mask)
777 e.mask = TYPE_Nm | TYPE_Nd;
778
779 if (e.regex) {
780 i = REG_EXTENDED | REG_NOSUB | (cs ? 0 : REG_ICASE);
781 if (regcomp(&e.re, e.v, i))
782 return(NULL);
783 }
784
785 e.v = mandoc_strdup(e.v);
786
787 p = mandoc_calloc(1, sizeof(struct expr));
788 memcpy(p, &e, sizeof(struct expr));
789 return(p);
790}
791
792void
793exprfree(struct expr *p)
794{
795 struct expr *pp;
796
797 while (NULL != p) {
798 if (p->subexpr)
799 exprfree(p->subexpr);
800 if (p->regex)
801 regfree(&p->re);
802 free(p->v);
803 pp = p->next;
804 free(p);
805 p = pp;
806 }
807}
808
809static int
810exprmark(const struct expr *p, const char *cp,
811 uint64_t mask, int *ms)
812{
813
814 for ( ; p; p = p->next) {
815 if (p->subexpr) {
816 if (exprmark(p->subexpr, cp, mask, ms))
817 return(1);
818 continue;
819 } else if ( ! (mask & p->mask))
820 continue;
821
822 if (p->regex) {
823 if (regexec(&p->re, cp, 0, NULL, 0))
824 continue;
825 } else if (NULL == strcasestr(cp, p->v))
826 continue;
827
828 if (NULL == ms)
829 return(1);
830 else
831 ms[p->index] = 1;
832 }
833
834 return(0);
835}
836
837static int
838expreval(const struct expr *p, int *ms)
839{
840 int match;
841
842 /*
843 * AND has precedence over OR. Analysis is left-right, though
844 * it doesn't matter because there are no side-effects.
845 * Thus, step through pairwise ANDs and accumulate their Boolean
846 * evaluation. If we encounter a single true AND collection or
847 * standalone term, the whole expression is true (by definition
848 * of OR).
849 */
850
851 for (match = 0; p && ! match; p = p->next) {
852 /* Evaluate a subexpression, if applicable. */
853 if (p->subexpr && ! ms[p->index])
854 ms[p->index] = expreval(p->subexpr, ms);
855
856 match = ms[p->index];
857 for ( ; p->next && p->next->and; p = p->next) {
858 /* Evaluate a subexpression, if applicable. */
859 if (p->next->subexpr && ! ms[p->next->index])
860 ms[p->next->index] =
861 expreval(p->next->subexpr, ms);
862 match = match && ms[p->next->index];
863 }
864 }
865
866 return(match);
867}
868
869/*
870 * First, update the array of terms for which this expression evaluates
871 * to true.
872 * Second, logically evaluate all terms over the updated array of truth
873 * values.
874 * If this evaluates to true, mark the expression as satisfied.
875 */
876static void
877exprexec(const struct expr *e, const char *cp,
878 uint64_t mask, struct res *r)
879{
880
881 assert(0 == r->matched);
882 exprmark(e, cp, mask, r->matches);
883 r->matched = expreval(e, r->matches);
884}