]> git.notmuchmail.org Git - notmuch/blob - lib/parse-sexp.cc
26b7e5f119cbe6f55e40675f8ff3acc8219db9f7
[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 typedef enum {
11     SEXP_FLAG_NONE      = 0,
12     SEXP_FLAG_FIELD     = 1 << 0,
13     SEXP_FLAG_BOOLEAN   = 1 << 1,
14 } _sexp_flag_t;
15
16 /*
17  * define bitwise operators to hide casts */
18
19 inline _sexp_flag_t
20 operator| (_sexp_flag_t a, _sexp_flag_t b)
21 {
22     return static_cast<_sexp_flag_t>(
23         static_cast<unsigned>(a) | static_cast<unsigned>(b));
24 }
25
26 inline _sexp_flag_t
27 operator& (_sexp_flag_t a, _sexp_flag_t b)
28 {
29     return static_cast<_sexp_flag_t>(
30         static_cast<unsigned>(a) & static_cast<unsigned>(b));
31 }
32
33 typedef struct  {
34     const char *name;
35     Xapian::Query::op xapian_op;
36     Xapian::Query initial;
37     _sexp_flag_t flags;
38 } _sexp_prefix_t;
39
40 static _sexp_prefix_t prefixes[] =
41 {
42     { "and",            Xapian::Query::OP_AND,          Xapian::Query::MatchAll,
43       SEXP_FLAG_NONE },
44     { "attachment",     Xapian::Query::OP_AND,          Xapian::Query::MatchAll,
45       SEXP_FLAG_FIELD },
46     { "body",           Xapian::Query::OP_AND,          Xapian::Query::MatchAll,
47       SEXP_FLAG_FIELD },
48     { "from",           Xapian::Query::OP_AND,          Xapian::Query::MatchAll,
49       SEXP_FLAG_FIELD },
50     { "folder",         Xapian::Query::OP_OR,           Xapian::Query::MatchNothing,
51       SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN },
52     { "id",             Xapian::Query::OP_OR,           Xapian::Query::MatchNothing,
53       SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN },
54     { "is",             Xapian::Query::OP_AND,          Xapian::Query::MatchAll,
55       SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN },
56     { "mid",            Xapian::Query::OP_OR,           Xapian::Query::MatchNothing,
57       SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN },
58     { "mimetype",       Xapian::Query::OP_AND,          Xapian::Query::MatchAll,
59       SEXP_FLAG_FIELD },
60     { "not",            Xapian::Query::OP_AND_NOT,      Xapian::Query::MatchAll,
61       SEXP_FLAG_NONE },
62     { "or",             Xapian::Query::OP_OR,           Xapian::Query::MatchNothing,
63       SEXP_FLAG_NONE },
64     { "path",           Xapian::Query::OP_OR,           Xapian::Query::MatchNothing,
65       SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN },
66     { "property",       Xapian::Query::OP_AND,          Xapian::Query::MatchAll,
67       SEXP_FLAG_FIELD
68       | SEXP_FLAG_BOOLEAN },
69     { "subject",        Xapian::Query::OP_AND,          Xapian::Query::MatchAll,
70       SEXP_FLAG_FIELD },
71     { "tag",            Xapian::Query::OP_AND,          Xapian::Query::MatchAll,
72       SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN },
73     { "thread",         Xapian::Query::OP_OR,           Xapian::Query::MatchNothing,
74       SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN },
75     { "to",             Xapian::Query::OP_AND,          Xapian::Query::MatchAll,
76       SEXP_FLAG_FIELD },
77     { }
78 };
79
80 static notmuch_status_t _sexp_to_xapian_query (notmuch_database_t *notmuch,
81                                                const _sexp_prefix_t *parent,
82                                                const sexp_t *sx,
83                                                Xapian::Query &output);
84
85 static notmuch_status_t
86 _sexp_combine_query (notmuch_database_t *notmuch,
87                      const _sexp_prefix_t *parent,
88                      Xapian::Query::op operation,
89                      Xapian::Query left,
90                      const sexp_t *sx,
91                      Xapian::Query &output)
92 {
93     Xapian::Query subquery;
94
95     notmuch_status_t status;
96
97     /* if we run out elements, return accumulator */
98
99     if (! sx) {
100         output = left;
101         return NOTMUCH_STATUS_SUCCESS;
102     }
103
104     status = _sexp_to_xapian_query (notmuch, parent, sx, subquery);
105     if (status)
106         return status;
107
108     return _sexp_combine_query (notmuch,
109                                 parent,
110                                 operation,
111                                 Xapian::Query (operation, left, subquery),
112                                 sx->next, output);
113 }
114
115 static notmuch_status_t
116 _sexp_parse_phrase (std::string term_prefix, const char *phrase, Xapian::Query &output)
117 {
118     Xapian::Utf8Iterator p (phrase);
119     Xapian::Utf8Iterator end;
120     std::vector<std::string> terms;
121
122     while (p != end) {
123         Xapian::Utf8Iterator start;
124         while (p != end && ! Xapian::Unicode::is_wordchar (*p))
125             p++;
126
127         if (p == end)
128             break;
129
130         start = p;
131
132         while (p != end && Xapian::Unicode::is_wordchar (*p))
133             p++;
134
135         if (p != start) {
136             std::string word (start, p);
137             word = Xapian::Unicode::tolower (word);
138             terms.push_back (term_prefix + word);
139         }
140     }
141     output = Xapian::Query (Xapian::Query::OP_PHRASE, terms.begin (), terms.end ());
142     return NOTMUCH_STATUS_SUCCESS;
143 }
144
145 /* Here we expect the s-expression to be a proper list, with first
146  * element defining and operation, or as a special case the empty
147  * list */
148
149 static notmuch_status_t
150 _sexp_to_xapian_query (notmuch_database_t *notmuch, const _sexp_prefix_t *parent, const sexp_t *sx,
151                        Xapian::Query &output)
152 {
153
154     if (sx->ty == SEXP_VALUE) {
155         std::string term = Xapian::Unicode::tolower (sx->val);
156         Xapian::Stem stem = *(notmuch->stemmer);
157         std::string term_prefix = parent ? _find_prefix (parent->name) : "";
158         if (parent && (parent->flags & SEXP_FLAG_BOOLEAN)) {
159             output = Xapian::Query (term_prefix + sx->val);
160             return NOTMUCH_STATUS_SUCCESS;
161         }
162         if (sx->aty == SEXP_BASIC && unicode_word_utf8 (sx->val)) {
163             output = Xapian::Query ("Z" + term_prefix + stem (term));
164             return NOTMUCH_STATUS_SUCCESS;
165         } else {
166             return _sexp_parse_phrase (term_prefix, sx->val, output);
167         }
168     }
169
170     /* Empty list */
171     if (! sx->list) {
172         output = Xapian::Query::MatchAll;
173         return NOTMUCH_STATUS_SUCCESS;
174     }
175
176     if (sx->list->ty == SEXP_LIST) {
177         _notmuch_database_log (notmuch, "unexpected list in field/operation position\n",
178                                sx->list->val);
179         return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
180     }
181
182     for (_sexp_prefix_t *prefix = prefixes; prefix && prefix->name; prefix++) {
183         if (strcmp (prefix->name, sx->list->val) == 0) {
184             if (prefix->flags & SEXP_FLAG_FIELD) {
185                 if (parent) {
186                     _notmuch_database_log (notmuch, "nested field: '%s' inside '%s'\n",
187                                            prefix->name, parent->name);
188                     return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
189                 }
190                 parent = prefix;
191             }
192
193             return _sexp_combine_query (notmuch, parent, prefix->xapian_op, prefix->initial,
194                                         sx->list->next, output);
195         }
196     }
197
198     _notmuch_database_log (notmuch, "unknown prefix '%s'\n", sx->list->val);
199
200     return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
201 }
202
203 notmuch_status_t
204 _notmuch_sexp_string_to_xapian_query (notmuch_database_t *notmuch, const char *querystr,
205                                       Xapian::Query &output)
206 {
207     const sexp_t *sx = NULL;
208     char *buf = talloc_strdup (notmuch, querystr);
209
210     sx = parse_sexp (buf, strlen (querystr));
211     if (! sx) {
212         _notmuch_database_log (notmuch, "invalid s-expression: '%s'\n", querystr);
213         return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
214     }
215
216     return _sexp_to_xapian_query (notmuch, NULL, sx, output);
217 }
218 #endif