[BACK]Return to mansearch.c CVS log [TXT][DIR] Up to [cvsweb.bsd.lv] / mandoc

Annotation of mandoc/mansearch.c, Revision 1.66

1.66    ! schwarze    1: /*     $OpenBSD: mansearch.c,v 1.50 2016/07/09 15:23:36 schwarze Exp $ */
1.1       kristaps    2: /*
                      3:  * Copyright (c) 2012 Kristaps Dzonsons <kristaps@bsd.lv>
1.53      schwarze    4:  * Copyright (c) 2013, 2014, 2015 Ingo Schwarze <schwarze@openbsd.org>
1.1       kristaps    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:  *
1.56      schwarze   10:  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
1.1       kristaps   11:  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1.56      schwarze   12:  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
1.1       kristaps   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:
1.28      schwarze   19: #include <sys/mman.h>
1.43      schwarze   20: #include <sys/types.h>
                     21:
1.1       kristaps   22: #include <assert.h>
1.59      schwarze   23: #include <err.h>
1.52      schwarze   24: #include <errno.h>
1.1       kristaps   25: #include <fcntl.h>
1.54      schwarze   26: #include <glob.h>
1.6       schwarze   27: #include <limits.h>
1.8       schwarze   28: #include <regex.h>
1.1       kristaps   29: #include <stdio.h>
                     30: #include <stdint.h>
                     31: #include <stddef.h>
                     32: #include <stdlib.h>
                     33: #include <string.h>
                     34: #include <unistd.h>
                     35:
                     36: #include "mandoc.h"
1.23      schwarze   37: #include "mandoc_aux.h"
1.60      schwarze   38: #include "mandoc_ohash.h"
1.56      schwarze   39: #include "manconf.h"
1.1       kristaps   40: #include "mansearch.h"
1.66    ! schwarze   41: #include "dbm.h"
1.2       kristaps   42:
1.1       kristaps   43: struct expr {
1.66    ! schwarze   44:        /* Used for terms: */
        !            45:        struct dbm_match match;   /* Match type and expression. */
        !            46:        uint64_t         bits;    /* Type mask. */
        !            47:        /* Used for OR and AND groups: */
        !            48:        struct expr     *next;    /* Next child in the parent group. */
        !            49:        struct expr     *child;   /* First child in this group. */
        !            50:        enum { EXPR_TERM, EXPR_OR, EXPR_AND } type;
1.1       kristaps   51: };
                     52:
1.66    ! schwarze   53: const char *const mansearch_keynames[KEY_MAX] = {
        !            54:        "arch", "sec",  "Xr",   "Ar",   "Fa",   "Fl",   "Dv",   "Fn",
        !            55:        "Ic",   "Pa",   "Cm",   "Li",   "Em",   "Cd",   "Va",   "Ft",
        !            56:        "Tn",   "Er",   "Ev",   "Sy",   "Sh",   "In",   "Ss",   "Ox",
        !            57:        "An",   "Mt",   "St",   "Bx",   "At",   "Nx",   "Fx",   "Lk",
        !            58:        "Ms",   "Bsx",  "Dx",   "Rs",   "Vt",   "Lb",   "Nm",   "Nd"
1.1       kristaps   59: };
                     60:
1.66    ! schwarze   61:
        !            62: static struct ohash    *manmerge(struct expr *, struct ohash *);
        !            63: static struct ohash    *manmerge_term(struct expr *, struct ohash *);
        !            64: static struct ohash    *manmerge_or(struct expr *, struct ohash *);
        !            65: static struct ohash    *manmerge_and(struct expr *, struct ohash *);
        !            66: static char            *buildnames(const struct dbm_page *);
        !            67: static char            *buildoutput(size_t, int32_t);
        !            68: static size_t           lstlen(const char *);
        !            69: static void             lstcat(char *, size_t *, const char *);
        !            70: static int              lstmatch(const char *, const char *);
1.34      schwarze   71: static struct expr     *exprcomp(const struct mansearch *,
1.66    ! schwarze   72:                                int, char *[], int *);
        !            73: static struct expr     *expr_and(const struct mansearch *,
        !            74:                                int, char *[], int *);
        !            75: static struct expr     *exprterm(const struct mansearch *,
        !            76:                                int, char *[], int *);
1.1       kristaps   77: static void             exprfree(struct expr *);
1.39      schwarze   78: static int              manpage_compare(const void *, const void *);
1.28      schwarze   79:
1.1       kristaps   80:
                     81: int
1.5       kristaps   82: mansearch(const struct mansearch *search,
1.12      schwarze   83:                const struct manpaths *paths,
                     84:                int argc, char *argv[],
1.1       kristaps   85:                struct manpage **res, size_t *sz)
                     86: {
1.6       schwarze   87:        char             buf[PATH_MAX];
1.66    ! schwarze   88:        struct dbm_res  *rp;
        !            89:        struct expr     *e;
        !            90:        struct dbm_page *page;
1.10      schwarze   91:        struct manpage  *mpage;
1.66    ! schwarze   92:        struct ohash    *htab;
        !            93:        size_t           cur, i, maxres, outkey;
        !            94:        unsigned int     slot;
        !            95:        int              argi, chdir_status, getcwd_status, im;
1.57      schwarze   96:
1.66    ! schwarze   97:        argi = 0;
        !            98:        if ((e = exprcomp(search, argc, argv, &argi)) == NULL) {
1.57      schwarze   99:                *sz = 0;
1.58      schwarze  100:                return 0;
1.57      schwarze  101:        }
1.1       kristaps  102:
1.57      schwarze  103:        cur = maxres = 0;
1.1       kristaps  104:        *res = NULL;
                    105:
1.66    ! schwarze  106:        outkey = KEY_Nd;
        !           107:        if (search->outkey != NULL)
        !           108:                for (im = 0; im < KEY_MAX; im++)
1.45      schwarze  109:                        if (0 == strcasecmp(search->outkey,
1.66    ! schwarze  110:                            mansearch_keynames[im])) {
        !           111:                                outkey = im;
1.12      schwarze  112:                                break;
                    113:                        }
                    114:
1.1       kristaps  115:        /*
1.57      schwarze  116:         * Remember the original working directory, if possible.
                    117:         * This will be needed if the second or a later directory
                    118:         * is given as a relative path.
                    119:         * Do not error out if the current directory is not
                    120:         * searchable: Maybe it won't be needed after all.
1.1       kristaps  121:         */
                    122:
1.57      schwarze  123:        if (getcwd(buf, PATH_MAX) == NULL) {
                    124:                getcwd_status = 0;
                    125:                (void)strlcpy(buf, strerror(errno), sizeof(buf));
                    126:        } else
                    127:                getcwd_status = 1;
1.1       kristaps  128:
                    129:        /*
                    130:         * Loop over the directories (containing databases) for us to
                    131:         * search.
                    132:         * Don't let missing/bad databases/directories phase us.
                    133:         * In each, try to open the resident database and, if it opens,
                    134:         * scan it for our match expression.
                    135:         */
                    136:
1.57      schwarze  137:        chdir_status = 0;
1.1       kristaps  138:        for (i = 0; i < paths->sz; i++) {
1.57      schwarze  139:                if (chdir_status && paths->paths[i][0] != '/') {
                    140:                        if ( ! getcwd_status) {
1.59      schwarze  141:                                warnx("%s: getcwd: %s", paths->paths[i], buf);
1.57      schwarze  142:                                continue;
                    143:                        } else if (chdir(buf) == -1) {
1.64      schwarze  144:                                warn("%s", buf);
1.57      schwarze  145:                                continue;
                    146:                        }
                    147:                }
                    148:                if (chdir(paths->paths[i]) == -1) {
1.64      schwarze  149:                        warn("%s", paths->paths[i]);
1.1       kristaps  150:                        continue;
1.34      schwarze  151:                }
1.57      schwarze  152:                chdir_status = 1;
1.1       kristaps  153:
1.66    ! schwarze  154:                if (dbm_open(MANDOC_DB) == -1) {
1.59      schwarze  155:                        warn("%s/%s", paths->paths[i], MANDOC_DB);
1.1       kristaps  156:                        continue;
                    157:                }
                    158:
1.66    ! schwarze  159:                if ((htab = manmerge(e, NULL)) == NULL) {
        !           160:                        dbm_close();
        !           161:                        continue;
1.1       kristaps  162:                }
                    163:
1.66    ! schwarze  164:                for (rp = ohash_first(htab, &slot); rp != NULL;
        !           165:                    rp = ohash_next(htab, &slot)) {
        !           166:                        page = dbm_page_get(rp->page);
1.1       kristaps  167:
1.66    ! schwarze  168:                        if (lstmatch(search->sec, page->sect) == 0 ||
        !           169:                            lstmatch(search->arch, page->arch) == 0)
1.1       kristaps  170:                                continue;
                    171:
                    172:                        if (cur + 1 > maxres) {
                    173:                                maxres += 1024;
1.36      schwarze  174:                                *res = mandoc_reallocarray(*res,
1.66    ! schwarze  175:                                    maxres, sizeof(**res));
1.1       kristaps  176:                        }
1.10      schwarze  177:                        mpage = *res + cur;
1.66    ! schwarze  178:                        mandoc_asprintf(&mpage->file, "%s/%s",
        !           179:                            paths->paths[i], page->file + 1);
        !           180:                        mpage->names = buildnames(page);
        !           181:                        mpage->output = (int)outkey == KEY_Nd ?
        !           182:                            mandoc_strdup(page->desc) :
        !           183:                            buildoutput(outkey, page->addr);
1.47      schwarze  184:                        mpage->ipath = i;
1.66    ! schwarze  185:                        mpage->bits = rp->bits;
        !           186:                        mpage->sec = *page->sect - '0';
        !           187:                        if (mpage->sec < 0 || mpage->sec > 9)
        !           188:                                mpage->sec = 10;
        !           189:                        mpage->form = *page->file;
        !           190:                        free(rp);
        !           191:                        cur++;
        !           192:                }
        !           193:                ohash_delete(htab);
        !           194:                free(htab);
        !           195:                dbm_close();
1.49      schwarze  196:
                    197:                /*
                    198:                 * In man(1) mode, prefer matches in earlier trees
                    199:                 * over matches in later trees.
                    200:                 */
                    201:
                    202:                if (cur && search->firstmatch)
                    203:                        break;
1.1       kristaps  204:        }
1.39      schwarze  205:        qsort(*res, cur, sizeof(struct manpage), manpage_compare);
1.57      schwarze  206:        if (chdir_status && getcwd_status && chdir(buf) == -1)
1.64      schwarze  207:                warn("%s", buf);
1.1       kristaps  208:        exprfree(e);
                    209:        *sz = cur;
1.58      schwarze  210:        return 1;
1.11      schwarze  211: }
                    212:
1.66    ! schwarze  213: /*
        !           214:  * Merge the results for the expression tree rooted at e
        !           215:  * into the the result list htab.
        !           216:  */
        !           217: static struct ohash *
        !           218: manmerge(struct expr *e, struct ohash *htab)
1.45      schwarze  219: {
1.66    ! schwarze  220:        switch (e->type) {
        !           221:        case EXPR_TERM:
        !           222:                return manmerge_term(e, htab);
        !           223:        case EXPR_OR:
        !           224:                return manmerge_or(e->child, htab);
        !           225:        case EXPR_AND:
        !           226:                return manmerge_and(e->child, htab);
        !           227:        default:
        !           228:                abort();
1.45      schwarze  229:        }
                    230: }
                    231:
1.66    ! schwarze  232: static struct ohash *
        !           233: manmerge_term(struct expr *e, struct ohash *htab)
1.39      schwarze  234: {
1.66    ! schwarze  235:        struct dbm_res   res, *rp;
        !           236:        uint64_t         ib;
        !           237:        unsigned int     slot;
        !           238:        int              im;
        !           239:
        !           240:        if (htab == NULL) {
        !           241:                htab = mandoc_malloc(sizeof(*htab));
        !           242:                mandoc_ohash_init(htab, 4, offsetof(struct dbm_res, page));
        !           243:        }
        !           244:
        !           245:        for (im = 0, ib = 1; im < KEY_MAX; im++, ib <<= 1) {
        !           246:                if ((e->bits & ib) == 0)
        !           247:                        continue;
        !           248:
        !           249:                switch (ib) {
        !           250:                case TYPE_arch:
        !           251:                        dbm_page_byarch(&e->match);
        !           252:                        break;
        !           253:                case TYPE_sec:
        !           254:                        dbm_page_bysect(&e->match);
        !           255:                        break;
        !           256:                case TYPE_Nm:
        !           257:                        dbm_page_byname(&e->match);
        !           258:                        break;
        !           259:                case TYPE_Nd:
        !           260:                        dbm_page_bydesc(&e->match);
        !           261:                        break;
        !           262:                default:
        !           263:                        dbm_page_bymacro(im - 2, &e->match);
        !           264:                        break;
        !           265:                }
        !           266:
        !           267:                /*
        !           268:                 * When hashing for deduplication, use the unique
        !           269:                 * page ID itself instead of a hash function;
        !           270:                 * that is quite efficient.
        !           271:                 */
1.39      schwarze  272:
1.66    ! schwarze  273:                for (;;) {
        !           274:                        res = dbm_page_next();
        !           275:                        if (res.page == -1)
        !           276:                                break;
        !           277:                        slot = ohash_lookup_memory(htab,
        !           278:                            (char *)&res, sizeof(res.page), res.page);
        !           279:                        if ((rp = ohash_find(htab, slot)) != NULL) {
        !           280:                                rp->bits |= res.bits;
        !           281:                                continue;
        !           282:                        }
        !           283:                        rp = mandoc_malloc(sizeof(*rp));
        !           284:                        *rp = res;
        !           285:                        ohash_insert(htab, slot, rp);
        !           286:                }
        !           287:        }
        !           288:        return htab;
1.39      schwarze  289: }
                    290:
1.66    ! schwarze  291: static struct ohash *
        !           292: manmerge_or(struct expr *e, struct ohash *htab)
        !           293: {
        !           294:        while (e != NULL) {
        !           295:                htab = manmerge(e, htab);
        !           296:                e = e->next;
        !           297:        }
        !           298:        return htab;
        !           299: }
1.22      schwarze  300:
1.66    ! schwarze  301: static struct ohash *
        !           302: manmerge_and(struct expr *e, struct ohash *htab)
        !           303: {
        !           304:        struct ohash    *hand, *h1, *h2;
        !           305:        struct dbm_res  *res;
        !           306:        unsigned int     slot1, slot2;
1.22      schwarze  307:
1.66    ! schwarze  308:        /* Evaluate the first term of the AND clause. */
1.39      schwarze  309:
1.66    ! schwarze  310:        hand = manmerge(e, NULL);
1.39      schwarze  311:
1.66    ! schwarze  312:        while ((e = e->next) != NULL) {
1.22      schwarze  313:
1.66    ! schwarze  314:                /* Evaluate the next term and prepare for ANDing. */
1.22      schwarze  315:
1.66    ! schwarze  316:                h2 = manmerge(e, NULL);
        !           317:                if (ohash_entries(h2) < ohash_entries(hand)) {
        !           318:                        h1 = h2;
        !           319:                        h2 = hand;
        !           320:                } else
        !           321:                        h1 = hand;
        !           322:                hand = mandoc_malloc(sizeof(*hand));
        !           323:                mandoc_ohash_init(hand, 4, offsetof(struct dbm_res, page));
1.22      schwarze  324:
1.66    ! schwarze  325:                /* Keep all pages that are in both result sets. */
1.22      schwarze  326:
1.66    ! schwarze  327:                for (res = ohash_first(h1, &slot1); res != NULL;
        !           328:                    res = ohash_next(h1, &slot1)) {
        !           329:                        if (ohash_find(h2, ohash_lookup_memory(h2,
        !           330:                            (char *)res, sizeof(res->page),
        !           331:                            res->page)) == NULL)
        !           332:                                free(res);
        !           333:                        else
        !           334:                                ohash_insert(hand, ohash_lookup_memory(hand,
        !           335:                                    (char *)res, sizeof(res->page),
        !           336:                                    res->page), res);
1.22      schwarze  337:                }
                    338:
1.66    ! schwarze  339:                /* Discard the merged results. */
1.22      schwarze  340:
1.66    ! schwarze  341:                for (res = ohash_first(h2, &slot2); res != NULL;
        !           342:                    res = ohash_next(h2, &slot2))
        !           343:                        free(res);
        !           344:                ohash_delete(h2);
        !           345:                free(h2);
        !           346:                ohash_delete(h1);
        !           347:                free(h1);
        !           348:        }
1.17      schwarze  349:
1.66    ! schwarze  350:        /* Merge the result of the AND into htab. */
1.17      schwarze  351:
1.66    ! schwarze  352:        if (htab == NULL)
        !           353:                return hand;
        !           354:
        !           355:        for (res = ohash_first(hand, &slot1); res != NULL;
        !           356:            res = ohash_next(hand, &slot1)) {
        !           357:                slot2 = ohash_lookup_memory(htab,
        !           358:                    (char *)res, sizeof(res->page), res->page);
        !           359:                if (ohash_find(htab, slot2) == NULL)
        !           360:                        ohash_insert(htab, slot2, res);
        !           361:                else
        !           362:                        free(res);
        !           363:        }
        !           364:
        !           365:        /* Discard the merged result. */
1.17      schwarze  366:
1.66    ! schwarze  367:        ohash_delete(hand);
        !           368:        free(hand);
        !           369:        return htab;
        !           370: }
1.54      schwarze  371:
1.66    ! schwarze  372: void
        !           373: mansearch_free(struct manpage *res, size_t sz)
        !           374: {
        !           375:        size_t   i;
1.54      schwarze  376:
1.66    ! schwarze  377:        for (i = 0; i < sz; i++) {
        !           378:                free(res[i].file);
        !           379:                free(res[i].names);
        !           380:                free(res[i].output);
1.22      schwarze  381:        }
1.66    ! schwarze  382:        free(res);
        !           383: }
        !           384:
        !           385: static int
        !           386: manpage_compare(const void *vp1, const void *vp2)
        !           387: {
        !           388:        const struct manpage    *mp1, *mp2;
        !           389:        int                      diff;
        !           390:
        !           391:        mp1 = vp1;
        !           392:        mp2 = vp2;
        !           393:        return (diff = mp2->bits - mp1->bits) ? diff :
        !           394:            (diff = mp1->sec - mp2->sec) ? diff :
        !           395:            strcasecmp(mp1->names, mp2->names);
1.12      schwarze  396: }
                    397:
                    398: static char *
1.66    ! schwarze  399: buildnames(const struct dbm_page *page)
1.12      schwarze  400: {
1.66    ! schwarze  401:        char    *buf;
        !           402:        size_t   i, sz;
1.12      schwarze  403:
1.66    ! schwarze  404:        sz = lstlen(page->name) + 1 + lstlen(page->sect) +
        !           405:            (page->arch == NULL ? 0 : 1 + lstlen(page->arch)) + 2;
        !           406:        buf = mandoc_malloc(sz);
        !           407:        i = 0;
        !           408:        lstcat(buf, &i, page->name);
        !           409:        buf[i++] = '(';
        !           410:        lstcat(buf, &i, page->sect);
        !           411:        if (page->arch != NULL) {
        !           412:                buf[i++] = '/';
        !           413:                lstcat(buf, &i, page->arch);
        !           414:        }
        !           415:        buf[i++] = ')';
        !           416:        buf[i++] = '\0';
        !           417:        assert(i == sz);
        !           418:        return buf;
1.1       kristaps  419: }
                    420:
                    421: /*
1.66    ! schwarze  422:  * Count the buffer space needed to print the NUL-terminated
        !           423:  * list of NUL-terminated strings, when printing two separator
        !           424:  * characters between strings.
1.7       schwarze  425:  */
1.66    ! schwarze  426: static size_t
        !           427: lstlen(const char *cp)
1.7       schwarze  428: {
1.66    ! schwarze  429:        size_t   sz;
1.7       schwarze  430:
1.66    ! schwarze  431:        for (sz = 0;; sz++) {
        !           432:                if (cp[0] == '\0') {
        !           433:                        if (cp[1] == '\0')
        !           434:                                break;
        !           435:                        sz++;
        !           436:                } else if (cp[0] < ' ')
        !           437:                        sz--;
        !           438:                cp++;
        !           439:        }
        !           440:        return sz;
1.7       schwarze  441: }
                    442:
                    443: /*
1.66    ! schwarze  444:  * Print the NUL-terminated list of NUL-terminated strings
        !           445:  * into the buffer, seperating strings with a comma and a blank.
1.8       schwarze  446:  */
                    447: static void
1.66    ! schwarze  448: lstcat(char *buf, size_t *i, const char *cp)
1.8       schwarze  449: {
1.66    ! schwarze  450:        for (;;) {
        !           451:                if (cp[0] == '\0') {
        !           452:                        if (cp[1] == '\0')
        !           453:                                break;
        !           454:                        buf[(*i)++] = ',';
        !           455:                        buf[(*i)++] = ' ';
        !           456:                } else if (cp[0] >= ' ')
        !           457:                        buf[(*i)++] = cp[0];
        !           458:                cp++;
        !           459:        }
1.8       schwarze  460: }
                    461:
1.66    ! schwarze  462: /*
        !           463:  * Return 1 if the string *want occurs in any of the strings
        !           464:  * in the NUL-terminated string list *have, or 0 otherwise.
        !           465:  * If either argument is NULL or empty, assume no filtering
        !           466:  * is desired and return 1.
        !           467:  */
        !           468: static int
        !           469: lstmatch(const char *want, const char *have)
1.13      schwarze  470: {
1.66    ! schwarze  471:         if (want == NULL || have == NULL || *have == '\0')
        !           472:                 return 1;
        !           473:         while (*have != '\0') {
        !           474:                 if (strcasestr(have, want) != NULL)
        !           475:                         return 1;
        !           476:                 have = strchr(have, '\0') + 1;
        !           477:         }
        !           478:         return 0;
1.13      schwarze  479: }
                    480:
1.8       schwarze  481: /*
1.66    ! schwarze  482:  * Build a list of values taken by the macro im
        !           483:  * in the manual page with big-endian address addr.
1.1       kristaps  484:  */
                    485: static char *
1.66    ! schwarze  486: buildoutput(size_t im, int32_t addr)
1.1       kristaps  487: {
1.66    ! schwarze  488:        const char      *oldoutput, *sep;
        !           489:        char            *output, *newoutput, *value;
        !           490:
        !           491:        output = NULL;
        !           492:        dbm_macro_bypage(im - 2, addr);
        !           493:        while ((value = dbm_macro_next()) != NULL) {
        !           494:                if (output == NULL) {
        !           495:                        oldoutput = "";
        !           496:                        sep = "";
        !           497:                } else {
        !           498:                        oldoutput = output;
        !           499:                        sep = " # ";
        !           500:                }
        !           501:                mandoc_asprintf(&newoutput, "%s%s%s", oldoutput, sep, value);
        !           502:                free(output);
        !           503:                output = newoutput;
1.1       kristaps  504:        }
1.66    ! schwarze  505:        return output;
1.1       kristaps  506: }
                    507:
                    508: /*
                    509:  * Compile a set of string tokens into an expression.
                    510:  * Tokens in "argv" are assumed to be individual expression atoms (e.g.,
                    511:  * "(", "foo=bar", etc.).
                    512:  */
                    513: static struct expr *
1.66    ! schwarze  514: exprcomp(const struct mansearch *search, int argc, char *argv[], int *argi)
1.1       kristaps  515: {
1.66    ! schwarze  516:        struct expr     *parent, *child;
        !           517:        int              needterm, nested;
        !           518:
        !           519:        if ((nested = *argi) == argc)
        !           520:                return NULL;
        !           521:        needterm = 1;
        !           522:        parent = child = NULL;
        !           523:        while (*argi < argc) {
        !           524:                if (strcmp(")", argv[*argi]) == 0) {
        !           525:                        if (needterm)
        !           526:                                warnx("missing term "
        !           527:                                    "before closing parenthesis");
        !           528:                        needterm = 0;
        !           529:                        if (nested)
        !           530:                                break;
        !           531:                        warnx("ignoring unmatched right parenthesis");
        !           532:                        ++*argi;
1.13      schwarze  533:                        continue;
1.66    ! schwarze  534:                }
        !           535:                if (strcmp("-o", argv[*argi]) == 0) {
        !           536:                        if (needterm) {
        !           537:                                if (*argi > 0)
        !           538:                                        warnx("ignoring -o after %s",
        !           539:                                            argv[*argi - 1]);
        !           540:                                else
        !           541:                                        warnx("ignoring initial -o");
        !           542:                        }
        !           543:                        needterm = 1;
        !           544:                        ++*argi;
1.13      schwarze  545:                        continue;
1.66    ! schwarze  546:                }
        !           547:                needterm = 0;
        !           548:                if (child == NULL) {
        !           549:                        child = expr_and(search, argc, argv, argi);
1.13      schwarze  550:                        continue;
1.1       kristaps  551:                }
1.66    ! schwarze  552:                if (parent == NULL) {
        !           553:                        parent = mandoc_calloc(1, sizeof(*parent));
        !           554:                        parent->type = EXPR_OR;
        !           555:                        parent->next = NULL;
        !           556:                        parent->child = child;
        !           557:                }
        !           558:                child->next = expr_and(search, argc, argv, argi);
        !           559:                child = child->next;
        !           560:        }
        !           561:        if (needterm && *argi)
        !           562:                warnx("ignoring trailing %s", argv[*argi - 1]);
        !           563:        return parent == NULL ? child : parent;
        !           564: }
1.26      schwarze  565:
1.66    ! schwarze  566: static struct expr *
        !           567: expr_and(const struct mansearch *search, int argc, char *argv[], int *argi)
        !           568: {
        !           569:        struct expr     *parent, *child;
        !           570:        int              needterm;
1.26      schwarze  571:
1.66    ! schwarze  572:        needterm = 1;
        !           573:        parent = child = NULL;
        !           574:        while (*argi < argc) {
        !           575:                if (strcmp(")", argv[*argi]) == 0) {
        !           576:                        if (needterm)
        !           577:                                warnx("missing term "
        !           578:                                    "before closing parenthesis");
        !           579:                        needterm = 0;
        !           580:                        break;
        !           581:                }
        !           582:                if (strcmp("-o", argv[*argi]) == 0)
        !           583:                        break;
        !           584:                if (strcmp("-a", argv[*argi]) == 0) {
        !           585:                        if (needterm) {
        !           586:                                if (*argi > 0)
        !           587:                                        warnx("ignoring -a after %s",
        !           588:                                            argv[*argi - 1]);
        !           589:                                else
        !           590:                                        warnx("ignoring initial -a");
1.27      schwarze  591:                        }
1.66    ! schwarze  592:                        needterm = 1;
        !           593:                        ++*argi;
        !           594:                        continue;
1.27      schwarze  595:                }
1.66    ! schwarze  596:                if (needterm == 0)
        !           597:                        break;
        !           598:                if (child == NULL) {
        !           599:                        child = exprterm(search, argc, argv, argi);
        !           600:                        if (child != NULL)
        !           601:                                needterm = 0;
        !           602:                        continue;
        !           603:                }
        !           604:                needterm = 0;
        !           605:                if (parent == NULL) {
        !           606:                        parent = mandoc_calloc(1, sizeof(*parent));
        !           607:                        parent->type = EXPR_AND;
        !           608:                        parent->next = NULL;
        !           609:                        parent->child = child;
        !           610:                }
        !           611:                child->next = exprterm(search, argc, argv, argi);
        !           612:                if (child->next != NULL) {
        !           613:                        child = child->next;
        !           614:                        needterm = 0;
        !           615:                }
        !           616:        }
        !           617:        if (needterm && *argi)
        !           618:                warnx("ignoring trailing %s", argv[*argi - 1]);
        !           619:        return parent == NULL ? child : parent;
1.15      schwarze  620: }
                    621:
                    622: static struct expr *
1.66    ! schwarze  623: exprterm(const struct mansearch *search, int argc, char *argv[], int *argi)
1.1       kristaps  624: {
1.15      schwarze  625:        char             errbuf[BUFSIZ];
1.1       kristaps  626:        struct expr     *e;
1.38      schwarze  627:        char            *key, *val;
1.20      schwarze  628:        uint64_t         iterbit;
1.66    ! schwarze  629:        int              cs, i, irc;
1.1       kristaps  630:
1.66    ! schwarze  631:        if (strcmp("(", argv[*argi]) == 0) {
        !           632:                ++*argi;
        !           633:                e = exprcomp(search, argc, argv, argi);
        !           634:                if (*argi < argc) {
        !           635:                        assert(strcmp(")", argv[*argi]) == 0);
        !           636:                        ++*argi;
        !           637:                } else
        !           638:                        warnx("unclosed parenthesis");
        !           639:                return e;
        !           640:        }
1.1       kristaps  641:
1.66    ! schwarze  642:        e = mandoc_calloc(1, sizeof(*e));
        !           643:        e->type = EXPR_TERM;
        !           644:        e->bits = 0;
        !           645:        e->next = NULL;
        !           646:        e->child = NULL;
1.1       kristaps  647:
1.45      schwarze  648:        if (search->argmode == ARG_NAME) {
                    649:                e->bits = TYPE_Nm;
1.66    ! schwarze  650:                e->match.type = DBM_EXACT;
        !           651:                e->match.str = argv[(*argi)++];
1.58      schwarze  652:                return e;
1.5       kristaps  653:        }
                    654:
1.1       kristaps  655:        /*
1.45      schwarze  656:         * Separate macro keys from search string.
1.66    ! schwarze  657:         * If needed, request regular expression handling.
1.1       kristaps  658:         */
                    659:
1.45      schwarze  660:        if (search->argmode == ARG_WORD) {
                    661:                e->bits = TYPE_Nm;
1.66    ! schwarze  662:                e->match.type = DBM_REGEX;
1.61      schwarze  663: #if HAVE_REWB_BSD
1.66    ! schwarze  664:                mandoc_asprintf(&val, "[[:<:]]%s[[:>:]]", argv[*argi]);
1.61      schwarze  665: #elif HAVE_REWB_SYSV
1.66    ! schwarze  666:                mandoc_asprintf(&val, "\\<%s\\>", argv[*argi]);
1.61      schwarze  667: #else
                    668:                mandoc_asprintf(&val,
1.66    ! schwarze  669:                    "(^|[^a-zA-Z01-9_])%s([^a-zA-Z01-9_]|$)", argv[*argi]);
1.61      schwarze  670: #endif
1.46      schwarze  671:                cs = 0;
1.66    ! schwarze  672:        } else if ((val = strpbrk(argv[*argi], "=~")) == NULL) {
1.45      schwarze  673:                e->bits = TYPE_Nm | TYPE_Nd;
1.66    ! schwarze  674:                e->match.type = DBM_SUB;
        !           675:                e->match.str = argv[*argi];
1.38      schwarze  676:        } else {
1.66    ! schwarze  677:                if (val == argv[*argi])
1.45      schwarze  678:                        e->bits = TYPE_Nm | TYPE_Nd;
1.66    ! schwarze  679:                if (*val == '=') {
        !           680:                        e->match.type = DBM_SUB;
        !           681:                        e->match.str = val + 1;
        !           682:                } else
        !           683:                        e->match.type = DBM_REGEX;
1.38      schwarze  684:                *val++ = '\0';
1.66    ! schwarze  685:                if (strstr(argv[*argi], "arch") != NULL)
1.21      schwarze  686:                        cs = 0;
1.38      schwarze  687:        }
                    688:
                    689:        /* Compile regular expressions. */
                    690:
1.66    ! schwarze  691:        if (e->match.type == DBM_REGEX) {
        !           692:                e->match.re = mandoc_malloc(sizeof(*e->match.re));
        !           693:                irc = regcomp(e->match.re, val,
1.38      schwarze  694:                    REG_EXTENDED | REG_NOSUB | (cs ? 0 : REG_ICASE));
1.66    ! schwarze  695:                if (irc) {
        !           696:                        regerror(irc, e->match.re, errbuf, sizeof(errbuf));
        !           697:                        warnx("regcomp /%s/: %s", val, errbuf);
        !           698:                }
1.45      schwarze  699:                if (search->argmode == ARG_WORD)
1.38      schwarze  700:                        free(val);
                    701:                if (irc) {
1.66    ! schwarze  702:                        free(e->match.re);
1.8       schwarze  703:                        free(e);
1.66    ! schwarze  704:                        ++*argi;
1.58      schwarze  705:                        return NULL;
1.8       schwarze  706:                }
1.38      schwarze  707:        }
                    708:
1.66    ! schwarze  709:        if (e->bits) {
        !           710:                ++*argi;
1.58      schwarze  711:                return e;
1.66    ! schwarze  712:        }
1.1       kristaps  713:
                    714:        /*
                    715:         * Parse out all possible fields.
                    716:         * If the field doesn't resolve, bail.
                    717:         */
                    718:
1.66    ! schwarze  719:        while (NULL != (key = strsep(&argv[*argi], ","))) {
1.1       kristaps  720:                if ('\0' == *key)
                    721:                        continue;
1.66    ! schwarze  722:                for (i = 0, iterbit = 1; i < KEY_MAX; i++, iterbit <<= 1) {
        !           723:                        if (0 == strcasecmp(key, mansearch_keynames[i])) {
1.20      schwarze  724:                                e->bits |= iterbit;
                    725:                                break;
                    726:                        }
                    727:                }
1.66    ! schwarze  728:                if (i == KEY_MAX) {
        !           729:                        if (strcasecmp(key, "any"))
        !           730:                                warnx("treating unknown key "
        !           731:                                    "\"%s\" as \"any\"", key);
1.20      schwarze  732:                        e->bits |= ~0ULL;
1.1       kristaps  733:                }
                    734:        }
                    735:
1.66    ! schwarze  736:        ++*argi;
1.58      schwarze  737:        return e;
1.1       kristaps  738: }
                    739:
                    740: static void
1.66    ! schwarze  741: exprfree(struct expr *e)
1.1       kristaps  742: {
1.66    ! schwarze  743:        if (e->next != NULL)
        !           744:                exprfree(e->next);
        !           745:        if (e->child != NULL)
        !           746:                exprfree(e->child);
        !           747:        free(e);
1.1       kristaps  748: }

CVSweb