mdocml: fix .Fo/.Fa output
[dragonfly.git] / contrib / mdocml / apropos_db.c
1 /*      $Id: apropos_db.c,v 1.32.2.3 2013/10/10 23:43:04 schwarze Exp $ */
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
22 #include <sys/param.h>
23
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
33 #if defined(__APPLE__)
34 # include <libkern/OSByteOrder.h>
35 #elif defined(__linux__)
36 # include <endian.h>
37 #elif defined(__sun)
38 # include <sys/byteorder.h>
39 #else
40 # include <sys/endian.h>
41 #endif
42
43 #if defined(__linux__) || defined(__sun)
44 # include <db_185.h>
45 #else
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
63 struct  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
74 struct  type {
75         uint64_t         mask;
76         const char      *name;
77 };
78
79 struct  rectree {
80         struct res      *node; /* record array for dir tree */
81         int              len; /* length of record array */
82 };
83
84 static  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
128 static  DB      *btree_open(void);
129 static  int      btree_read(const DBT *, const DBT *,
130                         const struct mchars *,
131                         uint64_t *, recno_t *, char **);
132 static  int      expreval(const struct expr *, int *);
133 static  void     exprexec(const struct expr *,
134                         const char *, uint64_t, struct res *);
135 static  int      exprmark(const struct expr *,
136                         const char *, uint64_t, int *);
137 static  struct expr *exprexpr(int, char *[], int *, int *, size_t *);
138 static  struct expr *exprterm(char *, int);
139 static  DB      *index_open(void);
140 static  int      index_read(const DBT *, const DBT *, int,
141                         const struct mchars *, struct res *);
142 static  void     norm_string(const char *,
143                         const struct mchars *, char **);
144 static  size_t   norm_utf8(unsigned int, char[7]);
145 static  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  */
152 static DB *
153 btree_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  */
173 static int
174 btree_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  */
200 static size_t
201 norm_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  */
254 static void
255 norm_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  */
349 static DB *
350 index_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  */
365 static int
366 index_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  */
414 int
415 apropos_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;
422         int              i;
423
424         memset(&tree, 0, sizeof(struct rectree));
425
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++) {
436                 assert('/' == paths[i][0]);
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
454 static int
455 single_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
580 void
581 resfree(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  */
597 struct expr *
598 termcomp(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  */
633 struct expr *
634 exprcomp(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  */
657 static struct expr *
658 exprexpr(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);
734 err:
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  */
744 static struct expr *
745 exprterm(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
792 void
793 exprfree(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
809 static int
810 exprmark(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
837 static int
838 expreval(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  */
876 static void
877 exprexec(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 }