]> git.cworth.org Git - notmuch/blob - lib/parse-sexp.cc
emacs: Add new option notmuch-search-hide-excluded
[notmuch] / lib / parse-sexp.cc
1 #include "database-private.h"
2
3 #if HAVE_SFSEXP
4 #include "sexp.h"
5 #include "unicode-util.h"
6
7 /* _sexp is used for file scope symbols to avoid clashing with
8  * definitions from sexp.h */
9
10 /* sexp_binding structs attach name to a sexp and a defining
11  * context. The latter allows lazy evaluation of parameters whose
12  * definition contains other parameters.  Lazy evaluation is needed
13  * because a primary goal of macros is to change the parent field for
14  * a sexp.
15  */
16
17 typedef struct sexp_binding {
18     const char *name;
19     const sexp_t *sx;
20     const struct sexp_binding *context;
21     const struct sexp_binding *next;
22 } _sexp_binding_t;
23
24 typedef enum {
25     SEXP_FLAG_NONE      = 0,
26     SEXP_FLAG_FIELD     = 1 << 0,
27     SEXP_FLAG_BOOLEAN   = 1 << 1,
28     SEXP_FLAG_SINGLE    = 1 << 2,
29     SEXP_FLAG_WILDCARD  = 1 << 3,
30     SEXP_FLAG_REGEX     = 1 << 4,
31     SEXP_FLAG_DO_REGEX  = 1 << 5,
32     SEXP_FLAG_EXPAND    = 1 << 6,
33     SEXP_FLAG_DO_EXPAND = 1 << 7,
34     SEXP_FLAG_ORPHAN    = 1 << 8,
35     SEXP_FLAG_RANGE     = 1 << 9,
36     SEXP_FLAG_PATHNAME  = 1 << 10,
37 } _sexp_flag_t;
38
39 /*
40  * define bitwise operators to hide casts */
41
42 inline _sexp_flag_t
43 operator| (_sexp_flag_t a, _sexp_flag_t b)
44 {
45     return static_cast<_sexp_flag_t>(
46         static_cast<unsigned>(a) | static_cast<unsigned>(b));
47 }
48
49 inline _sexp_flag_t
50 operator& (_sexp_flag_t a, _sexp_flag_t b)
51 {
52     return static_cast<_sexp_flag_t>(
53         static_cast<unsigned>(a) & static_cast<unsigned>(b));
54 }
55
56 typedef struct  {
57     const char *name;
58     Xapian::Query::op xapian_op;
59     Xapian::Query initial;
60     _sexp_flag_t flags;
61 } _sexp_prefix_t;
62
63 static _sexp_prefix_t prefixes[] =
64 {
65     { "and",            Xapian::Query::OP_AND,          Xapian::Query::MatchAll,
66       SEXP_FLAG_NONE },
67     { "attachment",     Xapian::Query::OP_AND,          Xapian::Query::MatchAll,
68       SEXP_FLAG_FIELD | SEXP_FLAG_WILDCARD | SEXP_FLAG_EXPAND },
69     { "body",           Xapian::Query::OP_AND,          Xapian::Query::MatchAll,
70       SEXP_FLAG_FIELD },
71     { "date",           Xapian::Query::OP_INVALID,      Xapian::Query::MatchAll,
72       SEXP_FLAG_RANGE },
73     { "from",           Xapian::Query::OP_AND,          Xapian::Query::MatchAll,
74       SEXP_FLAG_FIELD | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX | SEXP_FLAG_EXPAND },
75     { "folder",         Xapian::Query::OP_OR,           Xapian::Query::MatchNothing,
76       SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX | SEXP_FLAG_EXPAND |
77       SEXP_FLAG_PATHNAME },
78     { "id",             Xapian::Query::OP_OR,           Xapian::Query::MatchNothing,
79       SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX },
80     { "infix",          Xapian::Query::OP_INVALID,      Xapian::Query::MatchAll,
81       SEXP_FLAG_SINGLE | SEXP_FLAG_ORPHAN },
82     { "is",             Xapian::Query::OP_AND,          Xapian::Query::MatchAll,
83       SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX | SEXP_FLAG_EXPAND },
84     { "lastmod",           Xapian::Query::OP_INVALID,      Xapian::Query::MatchAll,
85       SEXP_FLAG_RANGE },
86     { "matching",       Xapian::Query::OP_AND,          Xapian::Query::MatchAll,
87       SEXP_FLAG_DO_EXPAND },
88     { "mid",            Xapian::Query::OP_OR,           Xapian::Query::MatchNothing,
89       SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX },
90     { "mimetype",       Xapian::Query::OP_AND,          Xapian::Query::MatchAll,
91       SEXP_FLAG_FIELD | SEXP_FLAG_WILDCARD | SEXP_FLAG_EXPAND },
92     { "not",            Xapian::Query::OP_AND_NOT,      Xapian::Query::MatchAll,
93       SEXP_FLAG_NONE },
94     { "of",             Xapian::Query::OP_AND,          Xapian::Query::MatchAll,
95       SEXP_FLAG_DO_EXPAND },
96     { "or",             Xapian::Query::OP_OR,           Xapian::Query::MatchNothing,
97       SEXP_FLAG_NONE },
98     { "path",           Xapian::Query::OP_OR,           Xapian::Query::MatchNothing,
99       SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX |
100       SEXP_FLAG_PATHNAME },
101     { "property",       Xapian::Query::OP_AND,          Xapian::Query::MatchAll,
102       SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX | SEXP_FLAG_EXPAND },
103     { "query",          Xapian::Query::OP_INVALID,      Xapian::Query::MatchNothing,
104       SEXP_FLAG_SINGLE | SEXP_FLAG_ORPHAN },
105     { "regex",          Xapian::Query::OP_INVALID,      Xapian::Query::MatchAll,
106       SEXP_FLAG_SINGLE | SEXP_FLAG_DO_REGEX },
107     { "rx",             Xapian::Query::OP_INVALID,      Xapian::Query::MatchAll,
108       SEXP_FLAG_SINGLE | SEXP_FLAG_DO_REGEX },
109     { "starts-with",    Xapian::Query::OP_WILDCARD,     Xapian::Query::MatchAll,
110       SEXP_FLAG_SINGLE },
111     { "subject",        Xapian::Query::OP_AND,          Xapian::Query::MatchAll,
112       SEXP_FLAG_FIELD | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX | SEXP_FLAG_EXPAND },
113     { "tag",            Xapian::Query::OP_AND,          Xapian::Query::MatchAll,
114       SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX | SEXP_FLAG_EXPAND },
115     { "thread",         Xapian::Query::OP_OR,           Xapian::Query::MatchNothing,
116       SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX | SEXP_FLAG_EXPAND },
117     { "to",             Xapian::Query::OP_AND,          Xapian::Query::MatchAll,
118       SEXP_FLAG_FIELD | SEXP_FLAG_WILDCARD | SEXP_FLAG_EXPAND },
119     { }
120 };
121
122 static notmuch_status_t _sexp_to_xapian_query (notmuch_database_t *notmuch,
123                                                const _sexp_prefix_t *parent,
124                                                const _sexp_binding_t *env,
125                                                const sexp_t *sx,
126                                                Xapian::Query &output);
127
128 static notmuch_status_t
129 _sexp_combine_query (notmuch_database_t *notmuch,
130                      const _sexp_prefix_t *parent,
131                      const _sexp_binding_t *env,
132                      Xapian::Query::op operation,
133                      Xapian::Query left,
134                      const sexp_t *sx,
135                      Xapian::Query &output)
136 {
137     Xapian::Query subquery;
138
139     notmuch_status_t status;
140
141     /* if we run out elements, return accumulator */
142
143     if (! sx) {
144         output = left;
145         return NOTMUCH_STATUS_SUCCESS;
146     }
147
148     status = _sexp_to_xapian_query (notmuch, parent, env, sx, subquery);
149     if (status)
150         return status;
151
152     return _sexp_combine_query (notmuch,
153                                 parent,
154                                 env,
155                                 operation,
156                                 Xapian::Query (operation, left, subquery),
157                                 sx->next, output);
158 }
159
160 static notmuch_status_t
161 _sexp_parse_phrase (std::string term_prefix, const char *phrase, Xapian::Query &output)
162 {
163     Xapian::Utf8Iterator p (phrase);
164     Xapian::Utf8Iterator end;
165     std::vector<std::string> terms;
166
167     while (p != end) {
168         Xapian::Utf8Iterator start;
169         while (p != end && ! Xapian::Unicode::is_wordchar (*p))
170             p++;
171
172         if (p == end)
173             break;
174
175         start = p;
176
177         while (p != end && Xapian::Unicode::is_wordchar (*p))
178             p++;
179
180         if (p != start) {
181             std::string word (start, p);
182             word = Xapian::Unicode::tolower (word);
183             terms.push_back (term_prefix + word);
184         }
185     }
186     output = Xapian::Query (Xapian::Query::OP_PHRASE, terms.begin (), terms.end ());
187     return NOTMUCH_STATUS_SUCCESS;
188 }
189
190 static notmuch_status_t
191 _sexp_parse_wildcard (notmuch_database_t *notmuch,
192                       const _sexp_prefix_t *parent,
193                       unused(const _sexp_binding_t *env),
194                       std::string match,
195                       Xapian::Query &output)
196 {
197
198     std::string term_prefix = parent ? _notmuch_database_prefix (notmuch, parent->name) : "";
199
200     if (parent && ! (parent->flags & SEXP_FLAG_WILDCARD)) {
201         _notmuch_database_log (notmuch, "'%s' does not support wildcard queries\n", parent->name);
202         return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
203     }
204
205     output = Xapian::Query (Xapian::Query::OP_WILDCARD,
206                             term_prefix + Xapian::Unicode::tolower (match));
207     return NOTMUCH_STATUS_SUCCESS;
208 }
209
210 static notmuch_status_t
211 _sexp_parse_one_term (notmuch_database_t *notmuch, std::string term_prefix, const sexp_t *sx,
212                       Xapian::Query &output)
213 {
214     Xapian::Stem stem = *(notmuch->stemmer);
215
216     if (sx->aty == SEXP_BASIC && unicode_word_utf8 (sx->val)) {
217         std::string term = Xapian::Unicode::tolower (sx->val);
218
219         output = Xapian::Query ("Z" + term_prefix + stem (term));
220         return NOTMUCH_STATUS_SUCCESS;
221     } else {
222         return _sexp_parse_phrase (term_prefix, sx->val, output);
223     }
224
225 }
226
227 notmuch_status_t
228 _sexp_parse_regex (notmuch_database_t *notmuch,
229                    const _sexp_prefix_t *prefix, const _sexp_prefix_t *parent,
230                    unused(const _sexp_binding_t *env),
231                    std::string val, Xapian::Query &output)
232 {
233     if (! parent) {
234         _notmuch_database_log (notmuch, "illegal '%s' outside field\n",
235                                prefix->name);
236         return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
237     }
238
239     if (! (parent->flags & SEXP_FLAG_REGEX)) {
240         _notmuch_database_log (notmuch, "'%s' not supported in field '%s'\n",
241                                prefix->name, parent->name);
242         return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
243     }
244
245     std::string msg; /* ignored */
246
247     return _notmuch_regexp_to_query (notmuch, Xapian::BAD_VALUENO, parent->name,
248                                      val, output, msg);
249 }
250
251
252 static notmuch_status_t
253 _sexp_expand_query (notmuch_database_t *notmuch,
254                     const _sexp_prefix_t *prefix, const _sexp_prefix_t *parent,
255                     unused(const _sexp_binding_t *env), const sexp_t *sx, Xapian::Query &output)
256 {
257     Xapian::Query subquery;
258     notmuch_status_t status;
259     std::string msg;
260
261     if (! (parent->flags & SEXP_FLAG_EXPAND)) {
262         _notmuch_database_log (notmuch, "'%s' unsupported inside '%s'\n", prefix->name, parent->name);
263         return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
264     }
265
266     status = _sexp_combine_query (notmuch, NULL, NULL, prefix->xapian_op, prefix->initial, sx,
267                                   subquery);
268     if (status)
269         return status;
270
271     status = _notmuch_query_expand (notmuch, parent->name, subquery, output, msg);
272     if (status) {
273         _notmuch_database_log (notmuch, "error expanding query %s\n", msg.c_str ());
274     }
275     return status;
276 }
277
278 static notmuch_status_t
279 _sexp_parse_infix (notmuch_database_t *notmuch, const sexp_t *sx, Xapian::Query &output)
280 {
281     try {
282         output = notmuch->query_parser->parse_query (sx->val, NOTMUCH_QUERY_PARSER_FLAGS);
283     } catch (const Xapian::QueryParserError &error) {
284         _notmuch_database_log (notmuch, "Syntax error in infix query: %s\n", sx->val);
285         return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
286     } catch (const Xapian::Error &error) {
287         if (! notmuch->exception_reported) {
288             _notmuch_database_log (notmuch,
289                                    "A Xapian exception occurred parsing query: %s\n",
290                                    error.get_msg ().c_str ());
291             _notmuch_database_log_append (notmuch,
292                                           "Query string was: %s\n",
293                                           sx->val);
294             notmuch->exception_reported = true;
295             return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
296         }
297     }
298     return NOTMUCH_STATUS_SUCCESS;
299 }
300
301 static notmuch_status_t
302 _sexp_parse_header (notmuch_database_t *notmuch, const _sexp_prefix_t *parent,
303                     const _sexp_binding_t *env, const sexp_t *sx, Xapian::Query &output)
304 {
305     _sexp_prefix_t user_prefix;
306
307     user_prefix.name = sx->list->val;
308     user_prefix.flags = SEXP_FLAG_FIELD | SEXP_FLAG_WILDCARD;
309
310     if (parent) {
311         _notmuch_database_log (notmuch, "nested field: '%s' inside '%s'\n",
312                                sx->list->val, parent->name);
313         return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
314     }
315
316     parent = &user_prefix;
317
318     return _sexp_combine_query (notmuch, parent, env, Xapian::Query::OP_AND, Xapian::Query::MatchAll,
319                                 sx->list->next, output);
320 }
321
322 static _sexp_binding_t *
323 _sexp_bind (void *ctx, const _sexp_binding_t *env, const char *name, const sexp_t *sx, const
324             _sexp_binding_t *context)
325 {
326     _sexp_binding_t *binding = talloc (ctx, _sexp_binding_t);
327
328     binding->name = talloc_strdup (ctx, name);
329     binding->sx = sx;
330     binding->context = context;
331     binding->next = env;
332     return binding;
333 }
334
335 static notmuch_status_t
336 maybe_apply_macro (notmuch_database_t *notmuch, const _sexp_prefix_t *parent,
337                    const _sexp_binding_t *env, const sexp_t *sx, const sexp_t *args,
338                    Xapian::Query &output)
339 {
340     const sexp_t *params, *param, *arg, *body;
341     void *local = talloc_new (notmuch);
342     _sexp_binding_t *new_env = NULL;
343     notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
344
345     if (sx->list->ty != SEXP_VALUE || strcmp (sx->list->val, "macro") != 0) {
346         status = NOTMUCH_STATUS_IGNORED;
347         goto DONE;
348     }
349
350     params = sx->list->next;
351
352     if (! params || (params->ty != SEXP_LIST)) {
353         _notmuch_database_log (notmuch, "missing (possibly empty) list of arguments to macro\n");
354         return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
355     }
356
357     body = params->next;
358
359     if (! body) {
360         _notmuch_database_log (notmuch, "missing body of macro\n");
361         status = NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
362         goto DONE;
363     }
364
365     for (param = params->list, arg = args;
366          param && arg;
367          param = param->next, arg = arg->next) {
368         if (param->ty != SEXP_VALUE || param->aty != SEXP_BASIC) {
369             _notmuch_database_log (notmuch, "macro parameters must be unquoted atoms\n");
370             status = NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
371             goto DONE;
372         }
373         new_env = _sexp_bind (local, new_env, param->val, arg, env);
374     }
375
376     if (param && ! arg) {
377         _notmuch_database_log (notmuch, "too few arguments to macro\n");
378         status = NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
379         goto DONE;
380     }
381
382     if (! param && arg) {
383         _notmuch_database_log (notmuch, "too many arguments to macro\n");
384         status = NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
385         goto DONE;
386     }
387
388     status = _sexp_to_xapian_query (notmuch, parent, new_env, body, output);
389
390   DONE:
391     if (local)
392         talloc_free (local);
393
394     return status;
395 }
396
397 static notmuch_status_t
398 maybe_saved_squery (notmuch_database_t *notmuch, const _sexp_prefix_t *parent,
399                     const _sexp_binding_t *env, const sexp_t *sx, Xapian::Query &output)
400 {
401     char *key;
402     char *expansion = NULL;
403     notmuch_status_t status;
404     sexp_t *saved_sexp;
405     void *local = talloc_new (notmuch);
406     char *buf;
407
408     key = talloc_asprintf (local, "squery.%s", sx->list->val);
409     if (! key) {
410         status = NOTMUCH_STATUS_OUT_OF_MEMORY;
411         goto DONE;
412     }
413
414     status = notmuch_database_get_config (notmuch, key, &expansion);
415     if (status)
416         goto DONE;
417     if (EMPTY_STRING (expansion)) {
418         status = NOTMUCH_STATUS_IGNORED;
419         goto DONE;
420     }
421
422     buf = talloc_strdup (local, expansion);
423     /* XXX TODO: free this memory */
424     saved_sexp = parse_sexp (buf, strlen (expansion));
425     if (! saved_sexp) {
426         _notmuch_database_log (notmuch, "invalid saved s-expression query: '%s'\n", expansion);
427         status = NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
428         goto DONE;
429     }
430
431     status = maybe_apply_macro (notmuch, parent, env, saved_sexp, sx->list->next, output);
432     if (status == NOTMUCH_STATUS_IGNORED)
433         status =  _sexp_to_xapian_query (notmuch, parent, env, saved_sexp, output);
434
435   DONE:
436     if (local)
437         talloc_free (local);
438
439     return status;
440 }
441
442 static notmuch_status_t
443 _sexp_expand_param (notmuch_database_t *notmuch, const _sexp_prefix_t *parent,
444                     const _sexp_binding_t *env, const char *name,
445                     Xapian::Query &output)
446 {
447     for (; env; env = env->next) {
448         if (strcmp (name, env->name) == 0) {
449             return _sexp_to_xapian_query (notmuch, parent, env->context, env->sx,
450                                           output);
451         }
452     }
453     _notmuch_database_log (notmuch, "undefined parameter %s\n", name);
454     return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
455 }
456
457 static notmuch_status_t
458 _sexp_parse_range (notmuch_database_t *notmuch,  const _sexp_prefix_t *prefix,
459                    const sexp_t *sx, Xapian::Query &output)
460 {
461     const char *from, *to;
462     std::string msg;
463
464     /* empty range matches everything */
465     if (! sx) {
466         output = Xapian::Query::MatchAll;
467         return NOTMUCH_STATUS_SUCCESS;
468     }
469
470     if (sx->ty == SEXP_LIST) {
471         _notmuch_database_log (notmuch, "expected atom as first argument of '%s'\n", prefix->name);
472         return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
473     }
474
475     from = sx->val;
476     to = from;
477
478     if (sx->next) {
479         if (sx->next->ty == SEXP_LIST) {
480             _notmuch_database_log (notmuch, "expected atom as second argument of '%s'\n",
481                                    prefix->name);
482             return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
483         }
484
485         if (sx->next->next) {
486             _notmuch_database_log (notmuch, "'%s' expects maximum of two arguments\n", prefix->name);
487             return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
488         }
489
490         to = sx->next->val;
491     }
492
493     if (strcmp (prefix->name, "date") == 0) {
494         notmuch_status_t status;
495         status = _notmuch_date_strings_to_query (NOTMUCH_VALUE_TIMESTAMP, from, to, output, msg);
496         if (status) {
497             if (! msg.empty ())
498                 _notmuch_database_log (notmuch, "%s\n", msg.c_str ());
499         }
500         return status;
501     }
502
503     if (strcmp (prefix->name, "lastmod") == 0) {
504         long from_idx, to_idx;
505
506         try {
507             from_idx = std::stol (from);
508         } catch (std::logic_error &e) {
509             _notmuch_database_log (notmuch, "bad 'from' revision: '%s'\n", from);
510             return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
511         }
512
513         try {
514             to_idx = std::stol (to);
515         } catch (std::logic_error &e) {
516             _notmuch_database_log (notmuch, "bad 'to' revision: '%s'\n", to);
517             return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
518         }
519
520         output = Xapian::Query (Xapian::Query::OP_VALUE_RANGE, NOTMUCH_VALUE_LAST_MOD,
521                                 Xapian::sortable_serialise (from_idx),
522                                 Xapian::sortable_serialise (to_idx));
523         return NOTMUCH_STATUS_SUCCESS;
524     }
525
526     _notmuch_database_log (notmuch, "unimplimented range prefix: '%s'\n", prefix->name);
527     return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
528 }
529
530 /* Here we expect the s-expression to be a proper list, with first
531  * element defining and operation, or as a special case the empty
532  * list */
533
534 static notmuch_status_t
535 _sexp_to_xapian_query (notmuch_database_t *notmuch, const _sexp_prefix_t *parent,
536                        const _sexp_binding_t *env, const sexp_t *sx, Xapian::Query &output)
537 {
538     notmuch_status_t status;
539
540     if (sx->ty == SEXP_VALUE && sx->aty == SEXP_BASIC && sx->val[0] == ',') {
541         return _sexp_expand_param (notmuch, parent, env, sx->val + 1, output);
542     }
543
544     if (sx->ty == SEXP_VALUE) {
545         std::string term_prefix = parent ? _notmuch_database_prefix (notmuch, parent->name) : "";
546
547         if (sx->aty == SEXP_BASIC && strcmp (sx->val, "*") == 0) {
548             return _sexp_parse_wildcard (notmuch, parent, env, "", output);
549         }
550
551         char *atom = sx->val;
552
553         if (parent && parent->flags & SEXP_FLAG_PATHNAME)
554             strip_trailing (atom, '/');
555
556         if (parent && (parent->flags & SEXP_FLAG_BOOLEAN)) {
557             output = Xapian::Query (term_prefix + atom);
558             return NOTMUCH_STATUS_SUCCESS;
559         }
560
561         if (parent) {
562             return _sexp_parse_one_term (notmuch, term_prefix, sx, output);
563         } else {
564             Xapian::Query accumulator;
565             for (_sexp_prefix_t *prefix = prefixes; prefix->name; prefix++) {
566                 if (prefix->flags & SEXP_FLAG_FIELD) {
567                     Xapian::Query subquery;
568                     term_prefix = _notmuch_database_prefix (notmuch, prefix->name);
569                     status = _sexp_parse_one_term (notmuch, term_prefix, sx, subquery);
570                     if (status)
571                         return status;
572                     accumulator = Xapian::Query (Xapian::Query::OP_OR, accumulator, subquery);
573                 }
574             }
575             output = accumulator;
576             return NOTMUCH_STATUS_SUCCESS;
577         }
578     }
579
580     /* Empty list */
581     if (! sx->list) {
582         output = Xapian::Query::MatchAll;
583         return NOTMUCH_STATUS_SUCCESS;
584     }
585
586     if (sx->list->ty == SEXP_LIST) {
587         _notmuch_database_log (notmuch, "unexpected list in field/operation position\n",
588                                sx->list->val);
589         return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
590     }
591
592     status = maybe_saved_squery (notmuch, parent, env, sx, output);
593     if (status != NOTMUCH_STATUS_IGNORED)
594         return status;
595
596     /* Check for user defined field */
597     if (_notmuch_string_map_get (notmuch->user_prefix, sx->list->val)) {
598         return _sexp_parse_header (notmuch, parent, env, sx, output);
599     }
600
601     if (strcmp (sx->list->val, "macro") == 0) {
602         _notmuch_database_log (notmuch, "macro definition not permitted here\n");
603         return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
604     }
605
606     for (_sexp_prefix_t *prefix = prefixes; prefix && prefix->name; prefix++) {
607         if (strcmp (prefix->name, sx->list->val) == 0) {
608             if (prefix->flags & (SEXP_FLAG_FIELD | SEXP_FLAG_RANGE)) {
609                 if (parent) {
610                     _notmuch_database_log (notmuch, "nested field: '%s' inside '%s'\n",
611                                            prefix->name, parent->name);
612                     return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
613                 }
614                 parent = prefix;
615             }
616
617             if (parent && (prefix->flags & SEXP_FLAG_ORPHAN)) {
618                 _notmuch_database_log (notmuch, "'%s' not supported inside '%s'\n",
619                                        prefix->name, parent->name);
620                 return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
621             }
622
623             if ((prefix->flags & SEXP_FLAG_SINGLE) &&
624                 (! sx->list->next || sx->list->next->next || sx->list->next->ty != SEXP_VALUE)) {
625                 _notmuch_database_log (notmuch, "'%s' expects single atom as argument\n",
626                                        prefix->name);
627                 return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
628             }
629
630             if (prefix->flags & SEXP_FLAG_RANGE)
631                 return _sexp_parse_range (notmuch, prefix, sx->list->next, output);
632
633             if (strcmp (prefix->name, "infix") == 0) {
634                 return _sexp_parse_infix (notmuch, sx->list->next, output);
635             }
636
637             if (strcmp (prefix->name, "query") == 0) {
638                 return _notmuch_query_name_to_query (notmuch, sx->list->next->val, output);
639             }
640
641             if (prefix->xapian_op == Xapian::Query::OP_WILDCARD)
642                 return _sexp_parse_wildcard (notmuch, parent, env, sx->list->next->val, output);
643
644             if (prefix->flags & SEXP_FLAG_DO_REGEX) {
645                 return _sexp_parse_regex (notmuch, prefix, parent, env, sx->list->next->val, output);
646             }
647
648             if (prefix->flags & SEXP_FLAG_DO_EXPAND) {
649                 return _sexp_expand_query (notmuch, prefix, parent, env, sx->list->next, output);
650             }
651
652             return _sexp_combine_query (notmuch, parent, env, prefix->xapian_op, prefix->initial,
653                                         sx->list->next, output);
654         }
655     }
656
657     _notmuch_database_log (notmuch, "unknown prefix '%s'\n", sx->list->val);
658     return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
659 }
660
661 notmuch_status_t
662 _notmuch_sexp_string_to_xapian_query (notmuch_database_t *notmuch, const char *querystr,
663                                       Xapian::Query &output)
664 {
665     const sexp_t *sx = NULL;
666     char *buf = talloc_strdup (notmuch, querystr);
667
668     sx = parse_sexp (buf, strlen (querystr));
669     if (! sx) {
670         _notmuch_database_log (notmuch, "invalid s-expression: '%s'\n", querystr);
671         return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
672     }
673
674     return _sexp_to_xapian_query (notmuch, NULL, NULL, sx, output);
675 }
676 #endif