+static notmuch_status_t
+_sexp_parse_one_term (notmuch_database_t *notmuch, std::string term_prefix, const sexp_t *sx,
+ Xapian::Query &output)
+{
+ Xapian::Stem stem = *(notmuch->stemmer);
+
+ if (sx->aty == SEXP_BASIC && unicode_word_utf8 (sx->val)) {
+ std::string term = Xapian::Unicode::tolower (sx->val);
+
+ output = Xapian::Query ("Z" + term_prefix + stem (term));
+ return NOTMUCH_STATUS_SUCCESS;
+ } else {
+ return _sexp_parse_phrase (term_prefix, sx->val, output);
+ }
+
+}
+
+notmuch_status_t
+_sexp_parse_regex (notmuch_database_t *notmuch,
+ const _sexp_prefix_t *prefix, const _sexp_prefix_t *parent,
+ unused(const _sexp_binding_t *env),
+ std::string val, Xapian::Query &output)
+{
+ if (! parent) {
+ _notmuch_database_log (notmuch, "illegal '%s' outside field\n",
+ prefix->name);
+ return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
+ }
+
+ if (! (parent->flags & SEXP_FLAG_REGEX)) {
+ _notmuch_database_log (notmuch, "'%s' not supported in field '%s'\n",
+ prefix->name, parent->name);
+ return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
+ }
+
+ std::string msg; /* ignored */
+
+ return _notmuch_regexp_to_query (notmuch, Xapian::BAD_VALUENO, parent->name,
+ val, output, msg);
+}
+
+
+static notmuch_status_t
+_sexp_expand_query (notmuch_database_t *notmuch,
+ const _sexp_prefix_t *prefix, const _sexp_prefix_t *parent,
+ unused(const _sexp_binding_t *env), const sexp_t *sx, Xapian::Query &output)
+{
+ Xapian::Query subquery;
+ notmuch_status_t status;
+ std::string msg;
+
+ if (! (parent->flags & SEXP_FLAG_EXPAND)) {
+ _notmuch_database_log (notmuch, "'%s' unsupported inside '%s'\n", prefix->name, parent->name);
+ return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
+ }
+
+ status = _sexp_combine_query (notmuch, NULL, NULL, prefix->xapian_op, prefix->initial, sx,
+ subquery);
+ if (status)
+ return status;
+
+ status = _notmuch_query_expand (notmuch, parent->name, subquery, output, msg);
+ if (status) {
+ _notmuch_database_log (notmuch, "error expanding query %s\n", msg.c_str ());
+ }
+ return status;
+}
+
+static notmuch_status_t
+_sexp_parse_infix (notmuch_database_t *notmuch, const sexp_t *sx, Xapian::Query &output)
+{
+ try {
+ output = notmuch->query_parser->parse_query (sx->val, NOTMUCH_QUERY_PARSER_FLAGS);
+ } catch (const Xapian::QueryParserError &error) {
+ _notmuch_database_log (notmuch, "Syntax error in infix query: %s\n", sx->val);
+ return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
+ } catch (const Xapian::Error &error) {
+ if (! notmuch->exception_reported) {
+ _notmuch_database_log (notmuch,
+ "A Xapian exception occurred parsing query: %s\n",
+ error.get_msg ().c_str ());
+ _notmuch_database_log_append (notmuch,
+ "Query string was: %s\n",
+ sx->val);
+ notmuch->exception_reported = true;
+ return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
+ }
+ }
+ return NOTMUCH_STATUS_SUCCESS;
+}
+
+static notmuch_status_t
+_sexp_parse_header (notmuch_database_t *notmuch, const _sexp_prefix_t *parent,
+ const _sexp_binding_t *env, const sexp_t *sx, Xapian::Query &output)
+{
+ _sexp_prefix_t user_prefix;
+
+ user_prefix.name = sx->list->val;
+ user_prefix.flags = SEXP_FLAG_FIELD | SEXP_FLAG_WILDCARD;
+
+ if (parent) {
+ _notmuch_database_log (notmuch, "nested field: '%s' inside '%s'\n",
+ sx->list->val, parent->name);
+ return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
+ }
+
+ parent = &user_prefix;
+
+ return _sexp_combine_query (notmuch, parent, env, Xapian::Query::OP_AND, Xapian::Query::MatchAll,
+ sx->list->next, output);
+}
+
+static _sexp_binding_t *
+_sexp_bind (void *ctx, const _sexp_binding_t *env, const char *name, const sexp_t *sx, const
+ _sexp_binding_t *context)
+{
+ _sexp_binding_t *binding = talloc (ctx, _sexp_binding_t);
+
+ binding->name = talloc_strdup (ctx, name);
+ binding->sx = sx;
+ binding->context = context;
+ binding->next = env;
+ return binding;
+}
+
+static notmuch_status_t
+maybe_apply_macro (notmuch_database_t *notmuch, const _sexp_prefix_t *parent,
+ const _sexp_binding_t *env, const sexp_t *sx, const sexp_t *args,
+ Xapian::Query &output)
+{
+ const sexp_t *params, *param, *arg, *body;
+ void *local = talloc_new (notmuch);
+ _sexp_binding_t *new_env = NULL;
+ notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
+
+ if (sx->list->ty != SEXP_VALUE || strcmp (sx->list->val, "macro") != 0) {
+ status = NOTMUCH_STATUS_IGNORED;
+ goto DONE;
+ }
+
+ params = sx->list->next;
+
+ if (! params || (params->ty != SEXP_LIST)) {
+ _notmuch_database_log (notmuch, "missing (possibly empty) list of arguments to macro\n");
+ return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
+ }
+
+ body = params->next;
+
+ if (! body) {
+ _notmuch_database_log (notmuch, "missing body of macro\n");
+ status = NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
+ goto DONE;
+ }
+
+ for (param = params->list, arg = args;
+ param && arg;
+ param = param->next, arg = arg->next) {
+ if (param->ty != SEXP_VALUE || param->aty != SEXP_BASIC) {
+ _notmuch_database_log (notmuch, "macro parameters must be unquoted atoms\n");
+ status = NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
+ goto DONE;
+ }
+ new_env = _sexp_bind (local, new_env, param->val, arg, env);
+ }
+
+ if (param && ! arg) {
+ _notmuch_database_log (notmuch, "too few arguments to macro\n");
+ status = NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
+ goto DONE;
+ }
+
+ if (! param && arg) {
+ _notmuch_database_log (notmuch, "too many arguments to macro\n");
+ status = NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
+ goto DONE;
+ }
+
+ status = _sexp_to_xapian_query (notmuch, parent, new_env, body, output);
+
+ DONE:
+ if (local)
+ talloc_free (local);
+
+ return status;
+}
+
+static notmuch_status_t
+maybe_saved_squery (notmuch_database_t *notmuch, const _sexp_prefix_t *parent,
+ const _sexp_binding_t *env, const sexp_t *sx, Xapian::Query &output)
+{
+ char *key;
+ char *expansion = NULL;
+ notmuch_status_t status;
+ sexp_t *saved_sexp;
+ void *local = talloc_new (notmuch);
+ char *buf;
+
+ key = talloc_asprintf (local, "squery.%s", sx->list->val);
+ if (! key) {
+ status = NOTMUCH_STATUS_OUT_OF_MEMORY;
+ goto DONE;
+ }
+
+ status = notmuch_database_get_config (notmuch, key, &expansion);
+ if (status)
+ goto DONE;
+ if (EMPTY_STRING (expansion)) {
+ status = NOTMUCH_STATUS_IGNORED;
+ goto DONE;
+ }
+
+ buf = talloc_strdup (local, expansion);
+ /* XXX TODO: free this memory */
+ saved_sexp = parse_sexp (buf, strlen (expansion));
+ if (! saved_sexp) {
+ _notmuch_database_log (notmuch, "invalid saved s-expression query: '%s'\n", expansion);
+ status = NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
+ goto DONE;
+ }
+
+ status = maybe_apply_macro (notmuch, parent, env, saved_sexp, sx->list->next, output);
+ if (status == NOTMUCH_STATUS_IGNORED)
+ status = _sexp_to_xapian_query (notmuch, parent, env, saved_sexp, output);
+
+ DONE:
+ if (local)
+ talloc_free (local);
+
+ return status;
+}
+
+static notmuch_status_t
+_sexp_expand_param (notmuch_database_t *notmuch, const _sexp_prefix_t *parent,
+ const _sexp_binding_t *env, const char *name,
+ Xapian::Query &output)
+{
+ for (; env; env = env->next) {
+ if (strcmp (name, env->name) == 0) {
+ return _sexp_to_xapian_query (notmuch, parent, env->context, env->sx,
+ output);
+ }
+ }
+ _notmuch_database_log (notmuch, "undefined parameter %s\n", name);
+ return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
+}
+
+static notmuch_status_t
+_sexp_parse_range (notmuch_database_t *notmuch, const _sexp_prefix_t *prefix,
+ const sexp_t *sx, Xapian::Query &output)
+{
+ const char *from, *to;
+ std::string msg;
+
+ /* empty range matches everything */
+ if (! sx) {
+ output = Xapian::Query::MatchAll;
+ return NOTMUCH_STATUS_SUCCESS;
+ }
+
+ if (sx->ty == SEXP_LIST) {
+ _notmuch_database_log (notmuch, "expected atom as first argument of '%s'\n", prefix->name);
+ return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
+ }
+
+ from = sx->val;
+ if (strcmp (from, "*") == 0)
+ from = "";
+
+ to = from;
+
+ if (sx->next) {
+ if (sx->next->ty == SEXP_LIST) {
+ _notmuch_database_log (notmuch, "expected atom as second argument of '%s'\n",
+ prefix->name);
+ return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
+ }
+
+ if (sx->next->next) {
+ _notmuch_database_log (notmuch, "'%s' expects maximum of two arguments\n", prefix->name);
+ return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
+ }
+
+ to = sx->next->val;
+ if (strcmp (to, "*") == 0)
+ to = "";
+ }
+
+ if (strcmp (prefix->name, "date") == 0) {
+ notmuch_status_t status;
+ status = _notmuch_date_strings_to_query (NOTMUCH_VALUE_TIMESTAMP, from, to, output, msg);
+ if (status) {
+ if (! msg.empty ())
+ _notmuch_database_log (notmuch, "%s\n", msg.c_str ());
+ }
+ return status;
+ }
+
+ if (strcmp (prefix->name, "lastmod") == 0) {
+ long from_idx, to_idx;
+
+ try {
+ if (EMPTY_STRING (from))
+ from_idx = 0L;
+ else
+ from_idx = std::stol (from);
+ } catch (std::logic_error &e) {
+ _notmuch_database_log (notmuch, "bad 'from' revision: '%s'\n", from);
+ return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
+ }
+
+ try {
+ if (EMPTY_STRING (to))
+ to_idx = LONG_MAX;
+ else
+ to_idx = std::stol (to);
+ } catch (std::logic_error &e) {
+ _notmuch_database_log (notmuch, "bad 'to' revision: '%s'\n", to);
+ return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
+ }
+
+ output = Xapian::Query (Xapian::Query::OP_VALUE_RANGE, NOTMUCH_VALUE_LAST_MOD,
+ Xapian::sortable_serialise (from_idx),
+ Xapian::sortable_serialise (to_idx));
+ return NOTMUCH_STATUS_SUCCESS;
+ }
+
+ _notmuch_database_log (notmuch, "unimplimented range prefix: '%s'\n", prefix->name);
+ return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
+}
+