version 1.5, 2011/11/18 07:02:19 |
version 1.32, 2012/03/25 00:48:47 |
|
|
/* $Id$ */ |
/* $Id$ */ |
/* |
/* |
* Copyright (c) 2011 Kristaps Dzonsons <kristaps@bsd.lv> |
* Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv> |
* Copyright (c) 2011 Ingo Schwarze <schwarze@openbsd.org> |
* Copyright (c) 2011 Ingo Schwarze <schwarze@openbsd.org> |
* |
* |
* Permission to use, copy, modify, and distribute this software for any |
* Permission to use, copy, modify, and distribute this software for any |
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
*/ |
*/ |
|
#ifdef HAVE_CONFIG_H |
|
#include "config.h" |
|
#endif |
|
|
|
#include <sys/param.h> |
|
|
#include <assert.h> |
#include <assert.h> |
#include <fcntl.h> |
#include <fcntl.h> |
#include <regex.h> |
#include <regex.h> |
#include <stdarg.h> |
#include <stdarg.h> |
|
#include <stdint.h> |
#include <stdlib.h> |
#include <stdlib.h> |
#include <string.h> |
#include <string.h> |
|
#include <unistd.h> |
|
|
#ifdef __linux__ |
#if defined(__linux__) |
|
# include <endian.h> |
# include <db_185.h> |
# include <db_185.h> |
|
#elif defined(__APPLE__) |
|
# include <libkern/OSByteOrder.h> |
|
# include <db.h> |
#else |
#else |
# include <db.h> |
# include <db.h> |
#endif |
#endif |
|
|
#include "apropos_db.h" |
#include "apropos_db.h" |
#include "mandoc.h" |
#include "mandoc.h" |
|
|
struct rec { |
#define RESFREE(_x) \ |
struct res res; /* resulting record info */ |
do { \ |
/* |
free((_x)->file); \ |
* Maintain a binary tree for checking the uniqueness of `rec' |
free((_x)->cat); \ |
* when adding elements to the results array. |
free((_x)->title); \ |
* Since the results array is dynamic, use offset in the array |
free((_x)->arch); \ |
* instead of a pointer to the structure. |
free((_x)->desc); \ |
*/ |
free((_x)->matches); \ |
int lhs; |
} while (/*CONSTCOND*/0) |
int rhs; |
|
int matched; /* expression is true */ |
|
int *matches; /* partial truth evaluations */ |
|
}; |
|
|
|
struct expr { |
struct expr { |
int regex; /* is regex? */ |
int regex; /* is regex? */ |
int index; /* index in match array */ |
int index; /* index in match array */ |
int mask; /* type-mask */ |
uint64_t mask; /* type-mask */ |
int cs; /* is case-sensitive? */ |
|
int and; /* is rhs of logical AND? */ |
int and; /* is rhs of logical AND? */ |
char *v; /* search value */ |
char *v; /* search value */ |
regex_t re; /* compiled re, if regex */ |
regex_t re; /* compiled re, if regex */ |
|
|
}; |
}; |
|
|
struct type { |
struct type { |
int mask; |
uint64_t mask; |
const char *name; |
const char *name; |
}; |
}; |
|
|
|
struct rectree { |
|
struct res *node; /* record array for dir tree */ |
|
int len; /* length of record array */ |
|
}; |
|
|
static const struct type types[] = { |
static const struct type types[] = { |
{ TYPE_An, "An" }, |
{ TYPE_An, "An" }, |
|
{ TYPE_Ar, "Ar" }, |
|
{ TYPE_At, "At" }, |
|
{ TYPE_Bsx, "Bsx" }, |
|
{ TYPE_Bx, "Bx" }, |
{ TYPE_Cd, "Cd" }, |
{ TYPE_Cd, "Cd" }, |
|
{ TYPE_Cm, "Cm" }, |
|
{ TYPE_Dv, "Dv" }, |
|
{ TYPE_Dx, "Dx" }, |
|
{ TYPE_Em, "Em" }, |
{ TYPE_Er, "Er" }, |
{ TYPE_Er, "Er" }, |
{ TYPE_Ev, "Ev" }, |
{ TYPE_Ev, "Ev" }, |
|
{ TYPE_Fa, "Fa" }, |
|
{ TYPE_Fl, "Fl" }, |
{ TYPE_Fn, "Fn" }, |
{ TYPE_Fn, "Fn" }, |
{ TYPE_Fn, "Fo" }, |
{ TYPE_Fn, "Fo" }, |
|
{ TYPE_Ft, "Ft" }, |
|
{ TYPE_Fx, "Fx" }, |
|
{ TYPE_Ic, "Ic" }, |
{ TYPE_In, "In" }, |
{ TYPE_In, "In" }, |
|
{ TYPE_Lb, "Lb" }, |
|
{ TYPE_Li, "Li" }, |
|
{ TYPE_Lk, "Lk" }, |
|
{ TYPE_Ms, "Ms" }, |
|
{ TYPE_Mt, "Mt" }, |
{ TYPE_Nd, "Nd" }, |
{ TYPE_Nd, "Nd" }, |
{ TYPE_Nm, "Nm" }, |
{ TYPE_Nm, "Nm" }, |
|
{ TYPE_Nx, "Nx" }, |
|
{ TYPE_Ox, "Ox" }, |
{ TYPE_Pa, "Pa" }, |
{ TYPE_Pa, "Pa" }, |
|
{ TYPE_Rs, "Rs" }, |
|
{ TYPE_Sh, "Sh" }, |
|
{ TYPE_Ss, "Ss" }, |
{ TYPE_St, "St" }, |
{ TYPE_St, "St" }, |
|
{ TYPE_Sy, "Sy" }, |
|
{ TYPE_Tn, "Tn" }, |
{ TYPE_Va, "Va" }, |
{ TYPE_Va, "Va" }, |
{ TYPE_Va, "Vt" }, |
{ TYPE_Va, "Vt" }, |
{ TYPE_Xr, "Xr" }, |
{ TYPE_Xr, "Xr" }, |
{ INT_MAX, "any" }, |
{ UINT64_MAX, "any" }, |
{ 0, NULL } |
{ 0, NULL } |
}; |
}; |
|
|
static DB *btree_open(void); |
static DB *btree_open(void); |
static int btree_read(const DBT *, |
static int btree_read(const DBT *, const DBT *, |
const struct mchars *, char **); |
const struct mchars *, |
|
uint64_t *, recno_t *, char **); |
static int expreval(const struct expr *, int *); |
static int expreval(const struct expr *, int *); |
static void exprexec(const struct expr *, |
static void exprexec(const struct expr *, |
const char *, int, struct rec *); |
const char *, uint64_t, struct res *); |
static int exprmark(const struct expr *, |
static int exprmark(const struct expr *, |
const char *, int, int *); |
const char *, uint64_t, int *); |
static struct expr *exprexpr(int, char *[], int *, int *, size_t *); |
static struct expr *exprexpr(int, char *[], int *, int *, size_t *); |
static struct expr *exprterm(char *, int); |
static struct expr *exprterm(char *, int); |
static DB *index_open(void); |
static DB *index_open(void); |
static int index_read(const DBT *, const DBT *, |
static int index_read(const DBT *, const DBT *, int, |
const struct mchars *, struct rec *); |
const struct mchars *, struct res *); |
static void norm_string(const char *, |
static void norm_string(const char *, |
const struct mchars *, char **); |
const struct mchars *, char **); |
static size_t norm_utf8(unsigned int, char[7]); |
static size_t norm_utf8(unsigned int, char[7]); |
static void recfree(struct rec *); |
static int single_search(struct rectree *, const struct opts *, |
|
const struct expr *, size_t terms, |
|
struct mchars *, int); |
|
|
/* |
/* |
* Open the keyword mandoc-db database. |
* Open the keyword mandoc-db database. |
Line 110 btree_open(void) |
|
Line 150 btree_open(void) |
|
DB *db; |
DB *db; |
|
|
memset(&info, 0, sizeof(BTREEINFO)); |
memset(&info, 0, sizeof(BTREEINFO)); |
|
info.lorder = 4321; |
info.flags = R_DUP; |
info.flags = R_DUP; |
|
|
db = dbopen(MANDOC_DB, O_RDONLY, 0, DB_BTREE, &info); |
db = dbopen(MANDOC_DB, O_RDONLY, 0, DB_BTREE, &info); |
if (NULL != db) |
if (NULL != db) |
return(db); |
return(db); |
|
|
return(NULL); |
return(NULL); |
Line 124 btree_open(void) |
|
Line 165 btree_open(void) |
|
* Return 0 if the database is insane, else 1. |
* Return 0 if the database is insane, else 1. |
*/ |
*/ |
static int |
static int |
btree_read(const DBT *v, const struct mchars *mc, char **buf) |
btree_read(const DBT *k, const DBT *v, const struct mchars *mc, |
|
uint64_t *mask, recno_t *rec, char **buf) |
{ |
{ |
|
uint64_t vbuf[2]; |
|
|
/* Sanity: are we nil-terminated? */ |
/* Are our sizes sane? */ |
|
if (k->size < 2 || sizeof(vbuf) != v->size) |
|
return(0); |
|
|
assert(v->size > 0); |
/* Is our string nil-terminated? */ |
if ('\0' != ((char *)v->data)[(int)v->size - 1]) |
if ('\0' != ((const char *)k->data)[(int)k->size - 1]) |
return(0); |
return(0); |
|
|
norm_string((char *)v->data, mc, buf); |
norm_string((const char *)k->data, mc, buf); |
|
memcpy(vbuf, v->data, v->size); |
|
*mask = betoh64(vbuf[0]); |
|
*rec = betoh64(vbuf[1]); |
return(1); |
return(1); |
} |
} |
|
|
/* |
/* |
* Take a Unicode codepoint and produce its UTF-8 encoding. |
* Take a Unicode codepoint and produce its UTF-8 encoding. |
* This isn't the best way to do this, but it works. |
* This isn't the best way to do this, but it works. |
* The magic numbers are from the UTF-8 packaging. |
* The magic numbers are from the UTF-8 packaging. |
* They're not as scary as they seem: read the UTF-8 spec for details. |
* They're not as scary as they seem: read the UTF-8 spec for details. |
*/ |
*/ |
static size_t |
static size_t |
norm_utf8(unsigned int cp, char out[7]) |
norm_utf8(unsigned int cp, char out[7]) |
{ |
{ |
size_t rc; |
int rc; |
|
|
rc = 0; |
rc = 0; |
|
|
Line 187 norm_utf8(unsigned int cp, char out[7]) |
|
Line 235 norm_utf8(unsigned int cp, char out[7]) |
|
return(0); |
return(0); |
|
|
out[rc] = '\0'; |
out[rc] = '\0'; |
return(rc); |
return((size_t)rc); |
} |
} |
|
|
/* |
/* |
Line 205 norm_string(const char *val, const struct mchars *mc, |
|
Line 253 norm_string(const char *val, const struct mchars *mc, |
|
const char *seq, *cpp; |
const char *seq, *cpp; |
int len, u, pos; |
int len, u, pos; |
enum mandoc_esc esc; |
enum mandoc_esc esc; |
static const char res[] = { '\\', '\t', |
static const char res[] = { '\\', '\t', |
ASCII_NBRSP, ASCII_HYPH, '\0' }; |
ASCII_NBRSP, ASCII_HYPH, '\0' }; |
|
|
/* Pre-allocate by the length of the input */ |
/* Pre-allocate by the length of the input */ |
Line 251 norm_string(const char *val, const struct mchars *mc, |
|
Line 299 norm_string(const char *val, const struct mchars *mc, |
|
if (ESCAPE_ERROR == esc) |
if (ESCAPE_ERROR == esc) |
break; |
break; |
|
|
/* |
/* |
* XXX - this just does UTF-8, but we need to know |
* XXX - this just does UTF-8, but we need to know |
* beforehand whether we should do text substitution. |
* beforehand whether we should do text substitution. |
*/ |
*/ |
Line 309 index_open(void) |
|
Line 357 index_open(void) |
|
* Returns 1 if an entry was unpacked, 0 if the database is insane. |
* Returns 1 if an entry was unpacked, 0 if the database is insane. |
*/ |
*/ |
static int |
static int |
index_read(const DBT *key, const DBT *val, |
index_read(const DBT *key, const DBT *val, int index, |
const struct mchars *mc, struct rec *rec) |
const struct mchars *mc, struct res *rec) |
{ |
{ |
size_t left; |
size_t left; |
char *np, *cp; |
char *np, *cp; |
|
char type; |
|
|
#define INDEX_BREAD(_dst) \ |
#define INDEX_BREAD(_dst) \ |
do { \ |
do { \ |
Line 324 index_read(const DBT *key, const DBT *val, |
|
Line 373 index_read(const DBT *key, const DBT *val, |
|
cp = np + 1; \ |
cp = np + 1; \ |
} while (/* CONSTCOND */ 0) |
} while (/* CONSTCOND */ 0) |
|
|
left = val->size; |
if (0 == (left = val->size)) |
cp = (char *)val->data; |
return(0); |
|
|
rec->res.rec = *(recno_t *)key->data; |
cp = val->data; |
|
assert(sizeof(recno_t) == key->size); |
|
memcpy(&rec->rec, key->data, key->size); |
|
rec->volume = index; |
|
|
INDEX_BREAD(rec->res.file); |
if ('d' == (type = *cp++)) |
INDEX_BREAD(rec->res.cat); |
rec->type = RESTYPE_MDOC; |
INDEX_BREAD(rec->res.title); |
else if ('a' == type) |
INDEX_BREAD(rec->res.arch); |
rec->type = RESTYPE_MAN; |
INDEX_BREAD(rec->res.desc); |
else if ('c' == type) |
|
rec->type = RESTYPE_CAT; |
|
else |
|
return(0); |
|
|
|
left--; |
|
INDEX_BREAD(rec->file); |
|
INDEX_BREAD(rec->cat); |
|
INDEX_BREAD(rec->title); |
|
INDEX_BREAD(rec->arch); |
|
INDEX_BREAD(rec->desc); |
return(1); |
return(1); |
} |
} |
|
|
/* |
/* |
* Search the mandocdb database for the expression "expr". |
* Search mandocdb databases in paths for expression "expr". |
* Filter out by "opts". |
* Filter out by "opts". |
* Call "res" with the results, which may be zero. |
* Call "res" with the results, which may be zero. |
* Return 0 if there was a database error, else return 1. |
* Return 0 if there was a database error, else return 1. |
*/ |
*/ |
int |
int |
apropos_search(const struct opts *opts, const struct expr *expr, |
apropos_search(int pathsz, char **paths, const struct opts *opts, |
size_t terms, void *arg, |
const struct expr *expr, size_t terms, void *arg, |
|
size_t *sz, struct res **resp, |
void (*res)(struct res *, size_t, void *)) |
void (*res)(struct res *, size_t, void *)) |
{ |
{ |
int i, rsz, root, leaf, mask, mlen, rc, ch; |
struct rectree tree; |
|
struct mchars *mc; |
|
int i, rc; |
|
|
|
memset(&tree, 0, sizeof(struct rectree)); |
|
|
|
rc = 0; |
|
mc = mchars_alloc(); |
|
*sz = 0; |
|
*resp = NULL; |
|
|
|
/* |
|
* Main loop. Change into the directory containing manpage |
|
* databases. Run our expession over each database in the set. |
|
*/ |
|
|
|
for (i = 0; i < pathsz; i++) { |
|
assert('/' == paths[i][0]); |
|
if (chdir(paths[i])) |
|
continue; |
|
if (single_search(&tree, opts, expr, terms, mc, i)) |
|
continue; |
|
|
|
resfree(tree.node, tree.len); |
|
mchars_free(mc); |
|
return(0); |
|
} |
|
|
|
(*res)(tree.node, tree.len, arg); |
|
*sz = tree.len; |
|
*resp = tree.node; |
|
mchars_free(mc); |
|
return(1); |
|
} |
|
|
|
static int |
|
single_search(struct rectree *tree, const struct opts *opts, |
|
const struct expr *expr, size_t terms, |
|
struct mchars *mc, int vol) |
|
{ |
|
int root, leaf, ch; |
DBT key, val; |
DBT key, val; |
DB *btree, *idx; |
DB *btree, *idx; |
struct mchars *mc; |
|
char *buf; |
char *buf; |
|
struct res *rs; |
|
struct res r; |
|
uint64_t mask; |
recno_t rec; |
recno_t rec; |
struct rec *rs; |
|
struct res *ress; |
|
struct rec r; |
|
|
|
rc = 0; |
|
root = -1; |
root = -1; |
leaf = -1; |
leaf = -1; |
btree = NULL; |
btree = NULL; |
idx = NULL; |
idx = NULL; |
mc = NULL; |
|
buf = NULL; |
buf = NULL; |
rs = NULL; |
rs = tree->node; |
rsz = 0; |
|
|
|
memset(&r, 0, sizeof(struct rec)); |
memset(&r, 0, sizeof(struct res)); |
|
|
mc = mchars_alloc(); |
if (NULL == (btree = btree_open())) |
|
return(1); |
|
|
if (NULL == (btree = btree_open())) |
if (NULL == (idx = index_open())) { |
goto out; |
(*btree->close)(btree); |
if (NULL == (idx = index_open())) |
return(1); |
goto out; |
} |
|
|
while (0 == (ch = (*btree->seq)(btree, &key, &val, R_NEXT))) { |
while (0 == (ch = (*btree->seq)(btree, &key, &val, R_NEXT))) { |
/* |
if ( ! btree_read(&key, &val, mc, &mask, &rec, &buf)) |
* Low-water mark for key and value. |
|
* The key must have something in it, and the value must |
|
* have the correct tags/recno mix. |
|
*/ |
|
if (key.size < 2 || 8 != val.size) |
|
break; |
break; |
if ( ! btree_read(&key, mc, &buf)) |
|
break; |
|
|
|
mask = *(int *)val.data; |
|
|
|
/* |
/* |
* See if this keyword record matches any of the |
* See if this keyword record matches any of the |
* expressions we have stored. |
* expressions we have stored. |
Line 397 apropos_search(const struct opts *opts, const struct e |
|
Line 488 apropos_search(const struct opts *opts, const struct e |
|
if ( ! exprmark(expr, buf, mask, NULL)) |
if ( ! exprmark(expr, buf, mask, NULL)) |
continue; |
continue; |
|
|
memcpy(&rec, val.data + 4, sizeof(recno_t)); |
|
|
|
/* |
/* |
* O(log n) scan for prior records. Since a record |
* O(log n) scan for prior records. Since a record |
* number is unbounded, this has decent performance over |
* number is unbounded, this has decent performance over |
Line 406 apropos_search(const struct opts *opts, const struct e |
|
Line 495 apropos_search(const struct opts *opts, const struct e |
|
*/ |
*/ |
|
|
for (leaf = root; leaf >= 0; ) |
for (leaf = root; leaf >= 0; ) |
if (rec > rs[leaf].res.rec && |
if (rec > rs[leaf].rec && |
rs[leaf].rhs >= 0) |
rs[leaf].rhs >= 0) |
leaf = rs[leaf].rhs; |
leaf = rs[leaf].rhs; |
else if (rec < rs[leaf].res.rec && |
else if (rec < rs[leaf].rec && |
rs[leaf].lhs >= 0) |
rs[leaf].lhs >= 0) |
leaf = rs[leaf].lhs; |
leaf = rs[leaf].lhs; |
else |
else |
break; |
break; |
|
|
/* |
/* |
Line 421 apropos_search(const struct opts *opts, const struct e |
|
Line 510 apropos_search(const struct opts *opts, const struct e |
|
* try to evaluate it now and continue anyway. |
* try to evaluate it now and continue anyway. |
*/ |
*/ |
|
|
if (leaf >= 0 && rs[leaf].res.rec == rec) { |
if (leaf >= 0 && rs[leaf].rec == rec) { |
if (0 == rs[leaf].matched) |
if (0 == rs[leaf].matched) |
exprexec(expr, buf, mask, &rs[leaf]); |
exprexec(expr, buf, mask, &rs[leaf]); |
continue; |
continue; |
Line 440 apropos_search(const struct opts *opts, const struct e |
|
Line 529 apropos_search(const struct opts *opts, const struct e |
|
break; |
break; |
|
|
r.lhs = r.rhs = -1; |
r.lhs = r.rhs = -1; |
if ( ! index_read(&key, &val, mc, &r)) |
if ( ! index_read(&key, &val, vol, mc, &r)) |
break; |
break; |
|
|
/* XXX: this should be elsewhere, I guess? */ |
/* XXX: this should be elsewhere, I guess? */ |
|
|
if (opts->cat && strcasecmp(opts->cat, r.res.cat)) |
if (opts->cat && strcasecmp(opts->cat, r.cat)) |
continue; |
continue; |
if (opts->arch && strcasecmp(opts->arch, r.res.arch)) |
|
continue; |
|
|
|
rs = mandoc_realloc |
if (opts->arch && *r.arch) |
(rs, (rsz + 1) * sizeof(struct rec)); |
if (strcasecmp(opts->arch, r.arch)) |
|
continue; |
|
|
memcpy(&rs[rsz], &r, sizeof(struct rec)); |
tree->node = rs = mandoc_realloc |
rs[rsz].matches = mandoc_calloc(terms, sizeof(int)); |
(rs, (tree->len + 1) * sizeof(struct res)); |
|
|
exprexec(expr, buf, mask, &rs[rsz]); |
memcpy(&rs[tree->len], &r, sizeof(struct res)); |
|
memset(&r, 0, sizeof(struct res)); |
|
rs[tree->len].matches = |
|
mandoc_calloc(terms, sizeof(int)); |
|
|
|
exprexec(expr, buf, mask, &rs[tree->len]); |
|
|
/* Append to our tree. */ |
/* Append to our tree. */ |
|
|
if (leaf >= 0) { |
if (leaf >= 0) { |
if (rec > rs[leaf].res.rec) |
if (rec > rs[leaf].rec) |
rs[leaf].rhs = rsz; |
rs[leaf].rhs = tree->len; |
else |
else |
rs[leaf].lhs = rsz; |
rs[leaf].lhs = tree->len; |
} else |
} else |
root = rsz; |
root = tree->len; |
|
|
memset(&r, 0, sizeof(struct rec)); |
|
rsz++; |
|
} |
|
|
|
/* |
|
* If we haven't encountered any database errors, then construct |
|
* an array of results and push them to the caller. |
|
*/ |
|
|
|
if (1 == ch) { |
tree->len++; |
for (mlen = i = 0; i < rsz; i++) |
|
if (rs[i].matched) |
|
mlen++; |
|
ress = mandoc_malloc(mlen * sizeof(struct res)); |
|
for (mlen = i = 0; i < rsz; i++) |
|
if (rs[i].matched) |
|
memcpy(&ress[mlen++], &rs[i].res, |
|
sizeof(struct res)); |
|
(*res)(ress, mlen, arg); |
|
free(ress); |
|
rc = 1; |
|
} |
} |
|
|
out: |
(*btree->close)(btree); |
for (i = 0; i < rsz; i++) |
(*idx->close)(idx); |
recfree(&rs[i]); |
|
|
|
recfree(&r); |
free(buf); |
|
RESFREE(&r); |
|
return(1 == ch); |
|
} |
|
|
if (mc) |
void |
mchars_free(mc); |
resfree(struct res *rec, size_t sz) |
if (btree) |
{ |
(*btree->close)(btree); |
size_t i; |
if (idx) |
|
(*idx->close)(idx); |
|
|
|
free(buf); |
for (i = 0; i < sz; i++) |
free(rs); |
RESFREE(&rec[i]); |
return(rc); |
free(rec); |
} |
} |
|
|
static void |
/* |
recfree(struct rec *rec) |
* Compile a list of straight-up terms. |
|
* The arguments are re-written into ~[[:<:]]term[[:>:]], or "term" |
|
* surrounded by word boundaries, then pumped through exprterm(). |
|
* Terms are case-insensitive. |
|
* This emulates whatis(1) behaviour. |
|
*/ |
|
struct expr * |
|
termcomp(int argc, char *argv[], size_t *tt) |
{ |
{ |
|
char *buf; |
|
int pos; |
|
struct expr *e, *next; |
|
size_t sz; |
|
|
free(rec->res.file); |
buf = NULL; |
free(rec->res.cat); |
e = NULL; |
free(rec->res.title); |
*tt = 0; |
free(rec->res.arch); |
|
free(rec->res.desc); |
|
|
|
free(rec->matches); |
for (pos = argc - 1; pos >= 0; pos--) { |
|
sz = strlen(argv[pos]) + 18; |
|
buf = mandoc_realloc(buf, sz); |
|
strlcpy(buf, "Nm~[[:<:]]", sz); |
|
strlcat(buf, argv[pos], sz); |
|
strlcat(buf, "[[:>:]]", sz); |
|
if (NULL == (next = exprterm(buf, 0))) { |
|
free(buf); |
|
exprfree(e); |
|
return(NULL); |
|
} |
|
next->next = e; |
|
e = next; |
|
(*tt)++; |
|
} |
|
|
|
free(buf); |
|
return(e); |
} |
} |
|
|
|
/* |
|
* Compile a sequence of logical expressions. |
|
* See apropos.1 for a grammar of this sequence. |
|
*/ |
struct expr * |
struct expr * |
exprcomp(int argc, char *argv[], size_t *tt) |
exprcomp(int argc, char *argv[], size_t *tt) |
{ |
{ |
Line 575 exprexpr(int argc, char *argv[], int *pos, int *lvl, s |
|
Line 679 exprexpr(int argc, char *argv[], int *pos, int *lvl, s |
|
log = 0; |
log = 0; |
|
|
if (NULL != e && 0 == strcmp("-a", argv[*pos])) |
if (NULL != e && 0 == strcmp("-a", argv[*pos])) |
log = 1; |
log = 1; |
else if (NULL != e && 0 == strcmp("-o", argv[*pos])) |
else if (NULL != e && 0 == strcmp("-o", argv[*pos])) |
log = 2; |
log = 2; |
|
|
Line 592 exprexpr(int argc, char *argv[], int *pos, int *lvl, s |
|
Line 696 exprexpr(int argc, char *argv[], int *pos, int *lvl, s |
|
++(*pos); |
++(*pos); |
++(*lvl); |
++(*lvl); |
next = mandoc_calloc(1, sizeof(struct expr)); |
next = mandoc_calloc(1, sizeof(struct expr)); |
next->cs = 1; |
|
next->subexpr = exprexpr(argc, argv, pos, lvl, tt); |
next->subexpr = exprexpr(argc, argv, pos, lvl, tt); |
if (NULL == next->subexpr) { |
if (NULL == next->subexpr) { |
free(next); |
free(next); |
Line 643 exprterm(char *buf, int cs) |
|
Line 746 exprterm(char *buf, int cs) |
|
|
|
memset(&e, 0, sizeof(struct expr)); |
memset(&e, 0, sizeof(struct expr)); |
|
|
e.cs = cs; |
|
|
|
/* Choose regex or substring match. */ |
/* Choose regex or substring match. */ |
|
|
if (NULL == (e.v = strpbrk(buf, "=~"))) { |
if (NULL == (e.v = strpbrk(buf, "=~"))) { |
Line 671 exprterm(char *buf, int cs) |
|
Line 772 exprterm(char *buf, int cs) |
|
e.mask = TYPE_Nm | TYPE_Nd; |
e.mask = TYPE_Nm | TYPE_Nd; |
|
|
if (e.regex) { |
if (e.regex) { |
i = REG_EXTENDED | REG_NOSUB | cs ? 0 : REG_ICASE; |
i = REG_EXTENDED | REG_NOSUB | (cs ? 0 : REG_ICASE); |
if (regcomp(&e.re, e.v, i)) |
if (regcomp(&e.re, e.v, i)) |
return(NULL); |
return(NULL); |
} |
} |
|
|
exprfree(struct expr *p) |
exprfree(struct expr *p) |
{ |
{ |
struct expr *pp; |
struct expr *pp; |
|
|
while (NULL != p) { |
while (NULL != p) { |
if (p->subexpr) |
if (p->subexpr) |
exprfree(p->subexpr); |
exprfree(p->subexpr); |
Line 701 exprfree(struct expr *p) |
|
Line 802 exprfree(struct expr *p) |
|
} |
} |
|
|
static int |
static int |
exprmark(const struct expr *p, const char *cp, int mask, int *ms) |
exprmark(const struct expr *p, const char *cp, |
|
uint64_t mask, int *ms) |
{ |
{ |
|
|
for ( ; p; p = p->next) { |
for ( ; p; p = p->next) { |
Line 715 exprmark(const struct expr *p, const char *cp, int mas |
|
Line 817 exprmark(const struct expr *p, const char *cp, int mas |
|
if (p->regex) { |
if (p->regex) { |
if (regexec(&p->re, cp, 0, NULL, 0)) |
if (regexec(&p->re, cp, 0, NULL, 0)) |
continue; |
continue; |
} else if (p->cs) { |
} else if (NULL == strcasestr(cp, p->v)) |
if (NULL == strstr(cp, p->v)) |
continue; |
continue; |
|
} else { |
|
if (NULL == strcasestr(cp, p->v)) |
|
continue; |
|
} |
|
|
|
if (NULL == ms) |
if (NULL == ms) |
return(1); |
return(1); |
Line 755 expreval(const struct expr *p, int *ms) |
|
Line 852 expreval(const struct expr *p, int *ms) |
|
for ( ; p->next && p->next->and; p = p->next) { |
for ( ; p->next && p->next->and; p = p->next) { |
/* Evaluate a subexpression, if applicable. */ |
/* Evaluate a subexpression, if applicable. */ |
if (p->next->subexpr && ! ms[p->next->index]) |
if (p->next->subexpr && ! ms[p->next->index]) |
ms[p->next->index] = |
ms[p->next->index] = |
expreval(p->next->subexpr, ms); |
expreval(p->next->subexpr, ms); |
match = match && ms[p->next->index]; |
match = match && ms[p->next->index]; |
} |
} |
Line 772 expreval(const struct expr *p, int *ms) |
|
Line 869 expreval(const struct expr *p, int *ms) |
|
* If this evaluates to true, mark the expression as satisfied. |
* If this evaluates to true, mark the expression as satisfied. |
*/ |
*/ |
static void |
static void |
exprexec(const struct expr *p, const char *cp, int mask, struct rec *r) |
exprexec(const struct expr *e, const char *cp, |
|
uint64_t mask, struct res *r) |
{ |
{ |
|
|
assert(0 == r->matched); |
assert(0 == r->matched); |
exprmark(p, cp, mask, r->matches); |
exprmark(e, cp, mask, r->matches); |
r->matched = expreval(p, r->matches); |
r->matched = expreval(e, r->matches); |
} |
} |