]> git.notmuchmail.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 resolve_binding (notmuch_database_t *notmuch, const _sexp_binding_t *env, const char *name,
192                  const _sexp_binding_t **out)
193 {
194     for (; env; env = env->next) {
195         if (strcmp (name, env->name) == 0) {
196             *out = env;
197             return NOTMUCH_STATUS_SUCCESS;
198         }
199     }
200
201     _notmuch_database_log (notmuch, "undefined parameter '%s'\n", name);
202     return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
203 }
204
205 static notmuch_status_t
206 _sexp_expand_term (notmuch_database_t *notmuch,
207                    const _sexp_prefix_t *prefix,
208                    const _sexp_binding_t *env,
209                    const sexp_t *sx,
210                    const char **out)
211 {
212     notmuch_status_t status;
213
214     if (! out)
215         return NOTMUCH_STATUS_NULL_POINTER;
216
217     while (sx->ty == SEXP_VALUE && sx->aty == SEXP_BASIC && sx->val[0] == ',') {
218         const char *name = sx->val + 1;
219         const _sexp_binding_t *binding;
220
221         status = resolve_binding (notmuch, env, name, &binding);
222         if (status)
223             return status;
224
225         sx = binding->sx;
226         env = binding->context;
227     }
228
229     if (sx->ty != SEXP_VALUE) {
230         _notmuch_database_log (notmuch, "'%s' expects single atom as argument\n",
231                                prefix->name);
232         return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
233     }
234
235     *out = sx->val;
236     return NOTMUCH_STATUS_SUCCESS;
237 }
238
239 static notmuch_status_t
240 _sexp_parse_wildcard (notmuch_database_t *notmuch,
241                       const _sexp_prefix_t *parent,
242                       unused(const _sexp_binding_t *env),
243                       std::string match,
244                       Xapian::Query &output)
245 {
246
247     std::string term_prefix = parent ? _notmuch_database_prefix (notmuch, parent->name) : "";
248
249     if (parent && ! (parent->flags & SEXP_FLAG_WILDCARD)) {
250         _notmuch_database_log (notmuch, "'%s' does not support wildcard queries\n", parent->name);
251         return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
252     }
253
254     output = Xapian::Query (Xapian::Query::OP_WILDCARD,
255                             term_prefix + Xapian::Unicode::tolower (match));
256     return NOTMUCH_STATUS_SUCCESS;
257 }
258
259 static notmuch_status_t
260 _sexp_parse_one_term (notmuch_database_t *notmuch, std::string term_prefix, const sexp_t *sx,
261                       Xapian::Query &output)
262 {
263     Xapian::Stem stem = *(notmuch->stemmer);
264
265     if (sx->aty == SEXP_BASIC && unicode_word_utf8 (sx->val)) {
266         std::string term = Xapian::Unicode::tolower (sx->val);
267
268         output = Xapian::Query ("Z" + term_prefix + stem (term));
269         return NOTMUCH_STATUS_SUCCESS;
270     } else {
271         return _sexp_parse_phrase (term_prefix, sx->val, output);
272     }
273
274 }
275
276 notmuch_status_t
277 _sexp_parse_regex (notmuch_database_t *notmuch,
278                    const _sexp_prefix_t *prefix, const _sexp_prefix_t *parent,
279                    const _sexp_binding_t *env,
280                    const sexp_t *term, Xapian::Query &output)
281 {
282     if (! parent) {
283         _notmuch_database_log (notmuch, "illegal '%s' outside field\n",
284                                prefix->name);
285         return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
286     }
287
288     if (! (parent->flags & SEXP_FLAG_REGEX)) {
289         _notmuch_database_log (notmuch, "'%s' not supported in field '%s'\n",
290                                prefix->name, parent->name);
291         return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
292     }
293
294     std::string msg; /* ignored */
295     const char *str;
296     notmuch_status_t status;
297
298     status = _sexp_expand_term (notmuch, prefix, env, term, &str);
299     if (status)
300         return status;
301
302     return _notmuch_regexp_to_query (notmuch, Xapian::BAD_VALUENO, parent->name,
303                                      str, output, msg);
304 }
305
306
307 static notmuch_status_t
308 _sexp_expand_query (notmuch_database_t *notmuch,
309                     const _sexp_prefix_t *prefix, const _sexp_prefix_t *parent,
310                     unused(const _sexp_binding_t *env), const sexp_t *sx, Xapian::Query &output)
311 {
312     Xapian::Query subquery;
313     notmuch_status_t status;
314     std::string msg;
315
316     if (! (parent->flags & SEXP_FLAG_EXPAND)) {
317         _notmuch_database_log (notmuch, "'%s' unsupported inside '%s'\n", prefix->name, parent->name);
318         return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
319     }
320
321     status = _sexp_combine_query (notmuch, NULL, NULL, prefix->xapian_op, prefix->initial, sx,
322                                   subquery);
323     if (status)
324         return status;
325
326     status = _notmuch_query_expand (notmuch, parent->name, subquery, output, msg);
327     if (status) {
328         _notmuch_database_log (notmuch, "error expanding query %s\n", msg.c_str ());
329     }
330     return status;
331 }
332
333 static notmuch_status_t
334 _sexp_parse_infix (notmuch_database_t *notmuch, const sexp_t *sx, Xapian::Query &output)
335 {
336     try {
337         output = notmuch->query_parser->parse_query (sx->val, NOTMUCH_QUERY_PARSER_FLAGS);
338     } catch (const Xapian::QueryParserError &error) {
339         _notmuch_database_log (notmuch, "Syntax error in infix query: %s\n", sx->val);
340         return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
341     } catch (const Xapian::Error &error) {
342         if (! notmuch->exception_reported) {
343             _notmuch_database_log (notmuch,
344                                    "A Xapian exception occurred parsing query: %s\n",
345                                    error.get_msg ().c_str ());
346             _notmuch_database_log_append (notmuch,
347                                           "Query string was: %s\n",
348                                           sx->val);
349             notmuch->exception_reported = true;
350             return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
351         }
352     }
353     return NOTMUCH_STATUS_SUCCESS;
354 }
355
356 static notmuch_status_t
357 _sexp_parse_header (notmuch_database_t *notmuch, const _sexp_prefix_t *parent,
358                     const _sexp_binding_t *env, const sexp_t *sx, Xapian::Query &output)
359 {
360     _sexp_prefix_t user_prefix;
361
362     user_prefix.name = sx->list->val;
363     user_prefix.flags = SEXP_FLAG_FIELD | SEXP_FLAG_WILDCARD;
364
365     if (parent) {
366         _notmuch_database_log (notmuch, "nested field: '%s' inside '%s'\n",
367                                sx->list->val, parent->name);
368         return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
369     }
370
371     parent = &user_prefix;
372
373     return _sexp_combine_query (notmuch, parent, env, Xapian::Query::OP_AND, Xapian::Query::MatchAll,
374                                 sx->list->next, output);
375 }
376
377 static _sexp_binding_t *
378 _sexp_bind (void *ctx, const _sexp_binding_t *env, const char *name, const sexp_t *sx, const
379             _sexp_binding_t *context)
380 {
381     _sexp_binding_t *binding = talloc (ctx, _sexp_binding_t);
382
383     binding->name = talloc_strdup (ctx, name);
384     binding->sx = sx;
385     binding->context = context;
386     binding->next = env;
387     return binding;
388 }
389
390 static notmuch_status_t
391 maybe_apply_macro (notmuch_database_t *notmuch, const _sexp_prefix_t *parent,
392                    const _sexp_binding_t *env, const sexp_t *sx, const sexp_t *args,
393                    Xapian::Query &output)
394 {
395     const sexp_t *params, *param, *arg, *body;
396     void *local = talloc_new (notmuch);
397     _sexp_binding_t *new_env = NULL;
398     notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
399
400     if (sx->list->ty != SEXP_VALUE || strcmp (sx->list->val, "macro") != 0) {
401         status = NOTMUCH_STATUS_IGNORED;
402         goto DONE;
403     }
404
405     params = sx->list->next;
406
407     if (! params || (params->ty != SEXP_LIST)) {
408         _notmuch_database_log (notmuch, "missing (possibly empty) list of arguments to macro\n");
409         return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
410     }
411
412     body = params->next;
413
414     if (! body) {
415         _notmuch_database_log (notmuch, "missing body of macro\n");
416         status = NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
417         goto DONE;
418     }
419
420     for (param = params->list, arg = args;
421          param && arg;
422          param = param->next, arg = arg->next) {
423         if (param->ty != SEXP_VALUE || param->aty != SEXP_BASIC) {
424             _notmuch_database_log (notmuch, "macro parameters must be unquoted atoms\n");
425             status = NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
426             goto DONE;
427         }
428         new_env = _sexp_bind (local, new_env, param->val, arg, env);
429     }
430
431     if (param && ! arg) {
432         _notmuch_database_log (notmuch, "too few arguments to macro\n");
433         status = NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
434         goto DONE;
435     }
436
437     if (! param && arg) {
438         _notmuch_database_log (notmuch, "too many arguments to macro\n");
439         status = NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
440         goto DONE;
441     }
442
443     status = _sexp_to_xapian_query (notmuch, parent, new_env, body, output);
444
445   DONE:
446     if (local)
447         talloc_free (local);
448
449     return status;
450 }
451
452 static notmuch_status_t
453 maybe_saved_squery (notmuch_database_t *notmuch, const _sexp_prefix_t *parent,
454                     const _sexp_binding_t *env, const sexp_t *sx, Xapian::Query &output)
455 {
456     char *key;
457     char *expansion = NULL;
458     notmuch_status_t status;
459     sexp_t *saved_sexp;
460     void *local = talloc_new (notmuch);
461     char *buf;
462
463     key = talloc_asprintf (local, "squery.%s", sx->list->val);
464     if (! key) {
465         status = NOTMUCH_STATUS_OUT_OF_MEMORY;
466         goto DONE;
467     }
468
469     status = notmuch_database_get_config (notmuch, key, &expansion);
470     if (status)
471         goto DONE;
472     if (EMPTY_STRING (expansion)) {
473         status = NOTMUCH_STATUS_IGNORED;
474         goto DONE;
475     }
476
477     buf = talloc_strdup (local, expansion);
478     /* XXX TODO: free this memory */
479     saved_sexp = parse_sexp (buf, strlen (expansion));
480     if (! saved_sexp) {
481         _notmuch_database_log (notmuch, "invalid saved s-expression query: '%s'\n", expansion);
482         status = NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
483         goto DONE;
484     }
485
486     status = maybe_apply_macro (notmuch, parent, env, saved_sexp, sx->list->next, output);
487     if (status == NOTMUCH_STATUS_IGNORED)
488         status =  _sexp_to_xapian_query (notmuch, parent, env, saved_sexp, output);
489
490   DONE:
491     if (local)
492         talloc_free (local);
493
494     return status;
495 }
496
497 static notmuch_status_t
498 _sexp_expand_param (notmuch_database_t *notmuch, const _sexp_prefix_t *parent,
499                     const _sexp_binding_t *env, const char *name,
500                     Xapian::Query &output)
501 {
502     notmuch_status_t status;
503
504     const _sexp_binding_t *binding;
505
506     status = resolve_binding (notmuch, env, name, &binding);
507     if (status)
508         return status;
509
510     return _sexp_to_xapian_query (notmuch, parent, binding->context, binding->sx,
511                                   output);
512 }
513
514 static notmuch_status_t
515 _sexp_parse_range (notmuch_database_t *notmuch,  const _sexp_prefix_t *prefix,
516                    const sexp_t *sx, Xapian::Query &output)
517 {
518     const char *from, *to;
519     std::string msg;
520
521     /* empty range matches everything */
522     if (! sx) {
523         output = Xapian::Query::MatchAll;
524         return NOTMUCH_STATUS_SUCCESS;
525     }
526
527     if (sx->ty == SEXP_LIST) {
528         _notmuch_database_log (notmuch, "expected atom as first argument of '%s'\n", prefix->name);
529         return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
530     }
531
532     from = sx->val;
533     if (strcmp (from, "*") == 0)
534         from = "";
535
536     to = from;
537
538     if (sx->next) {
539         if (sx->next->ty == SEXP_LIST) {
540             _notmuch_database_log (notmuch, "expected atom as second argument of '%s'\n",
541                                    prefix->name);
542             return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
543         }
544
545         if (sx->next->next) {
546             _notmuch_database_log (notmuch, "'%s' expects maximum of two arguments\n", prefix->name);
547             return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
548         }
549
550         to = sx->next->val;
551         if (strcmp (to, "*") == 0)
552             to = "";
553     }
554
555     if (strcmp (prefix->name, "date") == 0) {
556         notmuch_status_t status;
557         status = _notmuch_date_strings_to_query (NOTMUCH_VALUE_TIMESTAMP, from, to, output, msg);
558         if (status) {
559             if (! msg.empty ())
560                 _notmuch_database_log (notmuch, "%s\n", msg.c_str ());
561         }
562         return status;
563     }
564
565     if (strcmp (prefix->name, "lastmod") == 0) {
566         notmuch_status_t status;
567         status = _notmuch_lastmod_strings_to_query (notmuch, from, to, output, msg);
568         if (status) {
569             if (! msg.empty ())
570                 _notmuch_database_log (notmuch, "%s\n", msg.c_str ());
571         }
572         return status;
573     }
574
575     _notmuch_database_log (notmuch, "unimplimented range prefix: '%s'\n", prefix->name);
576     return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
577 }
578
579 /* Here we expect the s-expression to be a proper list, with first
580  * element defining and operation, or as a special case the empty
581  * list */
582
583 static notmuch_status_t
584 _sexp_to_xapian_query (notmuch_database_t *notmuch, const _sexp_prefix_t *parent,
585                        const _sexp_binding_t *env, const sexp_t *sx, Xapian::Query &output)
586 {
587     notmuch_status_t status;
588
589     if (sx->ty == SEXP_VALUE && sx->aty == SEXP_BASIC && sx->val[0] == ',') {
590         return _sexp_expand_param (notmuch, parent, env, sx->val + 1, output);
591     }
592
593     if (sx->ty == SEXP_VALUE) {
594         std::string term_prefix = parent ? _notmuch_database_prefix (notmuch, parent->name) : "";
595
596         if (sx->aty == SEXP_BASIC && strcmp (sx->val, "*") == 0) {
597             return _sexp_parse_wildcard (notmuch, parent, env, "", output);
598         }
599
600         char *atom = sx->val;
601
602         if (parent && parent->flags & SEXP_FLAG_PATHNAME)
603             strip_trailing (atom, '/');
604
605         if (parent && (parent->flags & SEXP_FLAG_BOOLEAN)) {
606             output = Xapian::Query (term_prefix + atom);
607             return NOTMUCH_STATUS_SUCCESS;
608         }
609
610         if (parent) {
611             return _sexp_parse_one_term (notmuch, term_prefix, sx, output);
612         } else {
613             Xapian::Query accumulator;
614             for (_sexp_prefix_t *prefix = prefixes; prefix->name; prefix++) {
615                 if (prefix->flags & SEXP_FLAG_FIELD) {
616                     Xapian::Query subquery;
617                     term_prefix = _notmuch_database_prefix (notmuch, prefix->name);
618                     status = _sexp_parse_one_term (notmuch, term_prefix, sx, subquery);
619                     if (status)
620                         return status;
621                     accumulator = Xapian::Query (Xapian::Query::OP_OR, accumulator, subquery);
622                 }
623             }
624             output = accumulator;
625             return NOTMUCH_STATUS_SUCCESS;
626         }
627     }
628
629     /* Empty list */
630     if (! sx->list) {
631         output = Xapian::Query::MatchAll;
632         return NOTMUCH_STATUS_SUCCESS;
633     }
634
635     if (sx->list->ty == SEXP_LIST) {
636         _notmuch_database_log (notmuch, "unexpected list in field/operation position\n",
637                                sx->list->val);
638         return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
639     }
640
641     status = maybe_saved_squery (notmuch, parent, env, sx, output);
642     if (status != NOTMUCH_STATUS_IGNORED)
643         return status;
644
645     /* Check for user defined field */
646     if (_notmuch_string_map_get (notmuch->user_prefix, sx->list->val)) {
647         return _sexp_parse_header (notmuch, parent, env, sx, output);
648     }
649
650     if (strcmp (sx->list->val, "macro") == 0) {
651         _notmuch_database_log (notmuch, "macro definition not permitted here\n");
652         return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
653     }
654
655     for (_sexp_prefix_t *prefix = prefixes; prefix && prefix->name; prefix++) {
656         if (strcmp (prefix->name, sx->list->val) == 0) {
657             if (prefix->flags & (SEXP_FLAG_FIELD | SEXP_FLAG_RANGE)) {
658                 if (parent) {
659                     _notmuch_database_log (notmuch, "nested field: '%s' inside '%s'\n",
660                                            prefix->name, parent->name);
661                     return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
662                 }
663                 parent = prefix;
664             }
665
666             if (parent && (prefix->flags & SEXP_FLAG_ORPHAN)) {
667                 _notmuch_database_log (notmuch, "'%s' not supported inside '%s'\n",
668                                        prefix->name, parent->name);
669                 return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
670             }
671
672             if ((prefix->flags & SEXP_FLAG_SINGLE) &&
673                 (! sx->list->next || sx->list->next->next || sx->list->next->ty != SEXP_VALUE)) {
674                 _notmuch_database_log (notmuch, "'%s' expects single atom as argument\n",
675                                        prefix->name);
676                 return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
677             }
678
679             if (prefix->flags & SEXP_FLAG_RANGE)
680                 return _sexp_parse_range (notmuch, prefix, sx->list->next, output);
681
682             if (strcmp (prefix->name, "infix") == 0) {
683                 return _sexp_parse_infix (notmuch, sx->list->next, output);
684             }
685
686             if (strcmp (prefix->name, "query") == 0) {
687                 return _notmuch_query_name_to_query (notmuch, sx->list->next->val, output);
688             }
689
690             if (prefix->xapian_op == Xapian::Query::OP_WILDCARD) {
691                 const char *str;
692                 status = _sexp_expand_term (notmuch, prefix, env, sx->list->next, &str);
693                 if (status)
694                     return status;
695
696                 return _sexp_parse_wildcard (notmuch, parent, env, str, output);
697             }
698
699             if (prefix->flags & SEXP_FLAG_DO_REGEX) {
700                 return _sexp_parse_regex (notmuch, prefix, parent, env, sx->list->next, output);
701             }
702
703             if (prefix->flags & SEXP_FLAG_DO_EXPAND) {
704                 return _sexp_expand_query (notmuch, prefix, parent, env, sx->list->next, output);
705             }
706
707             return _sexp_combine_query (notmuch, parent, env, prefix->xapian_op, prefix->initial,
708                                         sx->list->next, output);
709         }
710     }
711
712     _notmuch_database_log (notmuch, "unknown prefix '%s'\n", sx->list->val);
713     return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
714 }
715
716 notmuch_status_t
717 _notmuch_sexp_string_to_xapian_query (notmuch_database_t *notmuch, const char *querystr,
718                                       Xapian::Query &output)
719 {
720     const sexp_t *sx = NULL;
721     char *buf = talloc_strdup (notmuch, querystr);
722
723     sx = parse_sexp (buf, strlen (querystr));
724     if (! sx) {
725         _notmuch_database_log (notmuch, "invalid s-expression: '%s'\n", querystr);
726         return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
727     }
728
729     return _sexp_to_xapian_query (notmuch, NULL, NULL, sx, output);
730 }
731 #endif