]> git.cworth.org Git - notmuch/blob - lib/parse-sexp.cc
6282a4560983e40492d5b5625096a9b336df1feb
[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             if (EMPTY_STRING (from))
508                 from_idx = 0L;
509             else
510                 from_idx = std::stol (from);
511         } catch (std::logic_error &e) {
512             _notmuch_database_log (notmuch, "bad 'from' revision: '%s'\n", from);
513             return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
514         }
515
516         try {
517             if (EMPTY_STRING (to))
518                 to_idx = LONG_MAX;
519             else
520                 to_idx = std::stol (to);
521         } catch (std::logic_error &e) {
522             _notmuch_database_log (notmuch, "bad 'to' revision: '%s'\n", to);
523             return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
524         }
525
526         output = Xapian::Query (Xapian::Query::OP_VALUE_RANGE, NOTMUCH_VALUE_LAST_MOD,
527                                 Xapian::sortable_serialise (from_idx),
528                                 Xapian::sortable_serialise (to_idx));
529         return NOTMUCH_STATUS_SUCCESS;
530     }
531
532     _notmuch_database_log (notmuch, "unimplimented range prefix: '%s'\n", prefix->name);
533     return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
534 }
535
536 /* Here we expect the s-expression to be a proper list, with first
537  * element defining and operation, or as a special case the empty
538  * list */
539
540 static notmuch_status_t
541 _sexp_to_xapian_query (notmuch_database_t *notmuch, const _sexp_prefix_t *parent,
542                        const _sexp_binding_t *env, const sexp_t *sx, Xapian::Query &output)
543 {
544     notmuch_status_t status;
545
546     if (sx->ty == SEXP_VALUE && sx->aty == SEXP_BASIC && sx->val[0] == ',') {
547         return _sexp_expand_param (notmuch, parent, env, sx->val + 1, output);
548     }
549
550     if (sx->ty == SEXP_VALUE) {
551         std::string term_prefix = parent ? _notmuch_database_prefix (notmuch, parent->name) : "";
552
553         if (sx->aty == SEXP_BASIC && strcmp (sx->val, "*") == 0) {
554             return _sexp_parse_wildcard (notmuch, parent, env, "", output);
555         }
556
557         char *atom = sx->val;
558
559         if (parent && parent->flags & SEXP_FLAG_PATHNAME)
560             strip_trailing (atom, '/');
561
562         if (parent && (parent->flags & SEXP_FLAG_BOOLEAN)) {
563             output = Xapian::Query (term_prefix + atom);
564             return NOTMUCH_STATUS_SUCCESS;
565         }
566
567         if (parent) {
568             return _sexp_parse_one_term (notmuch, term_prefix, sx, output);
569         } else {
570             Xapian::Query accumulator;
571             for (_sexp_prefix_t *prefix = prefixes; prefix->name; prefix++) {
572                 if (prefix->flags & SEXP_FLAG_FIELD) {
573                     Xapian::Query subquery;
574                     term_prefix = _notmuch_database_prefix (notmuch, prefix->name);
575                     status = _sexp_parse_one_term (notmuch, term_prefix, sx, subquery);
576                     if (status)
577                         return status;
578                     accumulator = Xapian::Query (Xapian::Query::OP_OR, accumulator, subquery);
579                 }
580             }
581             output = accumulator;
582             return NOTMUCH_STATUS_SUCCESS;
583         }
584     }
585
586     /* Empty list */
587     if (! sx->list) {
588         output = Xapian::Query::MatchAll;
589         return NOTMUCH_STATUS_SUCCESS;
590     }
591
592     if (sx->list->ty == SEXP_LIST) {
593         _notmuch_database_log (notmuch, "unexpected list in field/operation position\n",
594                                sx->list->val);
595         return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
596     }
597
598     status = maybe_saved_squery (notmuch, parent, env, sx, output);
599     if (status != NOTMUCH_STATUS_IGNORED)
600         return status;
601
602     /* Check for user defined field */
603     if (_notmuch_string_map_get (notmuch->user_prefix, sx->list->val)) {
604         return _sexp_parse_header (notmuch, parent, env, sx, output);
605     }
606
607     if (strcmp (sx->list->val, "macro") == 0) {
608         _notmuch_database_log (notmuch, "macro definition not permitted here\n");
609         return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
610     }
611
612     for (_sexp_prefix_t *prefix = prefixes; prefix && prefix->name; prefix++) {
613         if (strcmp (prefix->name, sx->list->val) == 0) {
614             if (prefix->flags & (SEXP_FLAG_FIELD | SEXP_FLAG_RANGE)) {
615                 if (parent) {
616                     _notmuch_database_log (notmuch, "nested field: '%s' inside '%s'\n",
617                                            prefix->name, parent->name);
618                     return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
619                 }
620                 parent = prefix;
621             }
622
623             if (parent && (prefix->flags & SEXP_FLAG_ORPHAN)) {
624                 _notmuch_database_log (notmuch, "'%s' not supported inside '%s'\n",
625                                        prefix->name, parent->name);
626                 return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
627             }
628
629             if ((prefix->flags & SEXP_FLAG_SINGLE) &&
630                 (! sx->list->next || sx->list->next->next || sx->list->next->ty != SEXP_VALUE)) {
631                 _notmuch_database_log (notmuch, "'%s' expects single atom as argument\n",
632                                        prefix->name);
633                 return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
634             }
635
636             if (prefix->flags & SEXP_FLAG_RANGE)
637                 return _sexp_parse_range (notmuch, prefix, sx->list->next, output);
638
639             if (strcmp (prefix->name, "infix") == 0) {
640                 return _sexp_parse_infix (notmuch, sx->list->next, output);
641             }
642
643             if (strcmp (prefix->name, "query") == 0) {
644                 return _notmuch_query_name_to_query (notmuch, sx->list->next->val, output);
645             }
646
647             if (prefix->xapian_op == Xapian::Query::OP_WILDCARD)
648                 return _sexp_parse_wildcard (notmuch, parent, env, sx->list->next->val, output);
649
650             if (prefix->flags & SEXP_FLAG_DO_REGEX) {
651                 return _sexp_parse_regex (notmuch, prefix, parent, env, sx->list->next->val, output);
652             }
653
654             if (prefix->flags & SEXP_FLAG_DO_EXPAND) {
655                 return _sexp_expand_query (notmuch, prefix, parent, env, sx->list->next, output);
656             }
657
658             return _sexp_combine_query (notmuch, parent, env, prefix->xapian_op, prefix->initial,
659                                         sx->list->next, output);
660         }
661     }
662
663     _notmuch_database_log (notmuch, "unknown prefix '%s'\n", sx->list->val);
664     return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
665 }
666
667 notmuch_status_t
668 _notmuch_sexp_string_to_xapian_query (notmuch_database_t *notmuch, const char *querystr,
669                                       Xapian::Query &output)
670 {
671     const sexp_t *sx = NULL;
672     char *buf = talloc_strdup (notmuch, querystr);
673
674     sx = parse_sexp (buf, strlen (querystr));
675     if (! sx) {
676         _notmuch_database_log (notmuch, "invalid s-expression: '%s'\n", querystr);
677         return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
678     }
679
680     return _sexp_to_xapian_query (notmuch, NULL, NULL, sx, output);
681 }
682 #endif