Return-Path: X-Original-To: notmuch@notmuchmail.org Delivered-To: notmuch@notmuchmail.org Received: from localhost (localhost [127.0.0.1]) by olra.theworths.org (Postfix) with ESMTP id 002E9431FD0 for ; Thu, 20 Jan 2011 22:37:27 -0800 (PST) X-Virus-Scanned: Debian amavisd-new at olra.theworths.org X-Spam-Flag: NO X-Spam-Score: 0 X-Spam-Level: X-Spam-Status: No, score=0 tagged_above=-999 required=5 tests=[RCVD_IN_DNSWL_NONE=-0.0001] autolearn=disabled Received: from olra.theworths.org ([127.0.0.1]) by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id jFYJbVpBDHqI for ; Thu, 20 Jan 2011 22:37:19 -0800 (PST) Received: from dmz-mailsec-scanner-8.mit.edu (DMZ-MAILSEC-SCANNER-8.MIT.EDU [18.7.68.37]) by olra.theworths.org (Postfix) with ESMTP id 5CEE7431FB6 for ; Thu, 20 Jan 2011 22:37:18 -0800 (PST) X-AuditID: 12074425-b7c98ae000000a04-6c-4d39299c563f Received: from mailhub-auth-4.mit.edu ( [18.7.62.39]) by dmz-mailsec-scanner-8.mit.edu (Symantec Brightmail Gateway) with SMTP id 9F.51.02564.C99293D4; Fri, 21 Jan 2011 01:37:17 -0500 (EST) Received: from outgoing.mit.edu (OUTGOING-AUTH.MIT.EDU [18.7.22.103]) by mailhub-auth-4.mit.edu (8.13.8/8.9.2) with ESMTP id p0L6bGVc020511; Fri, 21 Jan 2011 01:37:16 -0500 Received: from awakening.csail.mit.edu (awakening.csail.mit.edu [18.26.4.91]) (authenticated bits=0) (User authenticated as amdragon@ATHENA.MIT.EDU) by outgoing.mit.edu (8.13.6/8.12.4) with ESMTP id p0L6bE4s004633 (version=TLSv1/SSLv3 cipher=AES256-SHA bits=256 verify=NOT); Fri, 21 Jan 2011 01:37:15 -0500 (EST) Received: from amthrax by awakening.csail.mit.edu with local (Exim 4.72) (envelope-from ) id 1PgAcU-0000yr-8j; Fri, 21 Jan 2011 01:37:14 -0500 Date: Fri, 21 Jan 2011 01:37:14 -0500 From: Austin Clements To: notmuch@notmuchmail.org Subject: [PATCH 1.5/8] Query parser testing framework and basic tests. Message-ID: <20110121063714.GI13226@mit.edu> References: <1295165458-9573-1-git-send-email-amdragon@mit.edu> <1295165458-9573-2-git-send-email-amdragon@mit.edu> MIME-Version: 1.0 Content-Type: text/plain; charset=iso-8859-1 Content-Disposition: inline Content-Transfer-Encoding: 8bit In-Reply-To: <1295165458-9573-2-git-send-email-amdragon@mit.edu> User-Agent: Mutt/1.5.20 (2009-06-14) X-Brightmail-Tracker: AAAAAhcymoEXM1Bb Cc: amdragon@mit.edu X-BeenThere: notmuch@notmuchmail.org X-Mailman-Version: 2.1.13 Precedence: list List-Id: "Use and development of the notmuch mail system." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Fri, 21 Jan 2011 06:37:29 -0000 The query parser test is implemented as a separate binary that calls directly in to the lexer, parser, and generator to make it easy to isolate test failures. --- Sorry for the patch ordering. This is intended to be applied after patch 1/8 in this series, id:1295165458-9573-2-git-send-email-amdragon@mit.edu test/Makefile.local | 7 +- test/basic | 2 +- test/notmuch-test | 2 +- test/qparser | 32 ++++++ test/qparser-test.cc | 153 +++++++++++++++++++++++++++ test/qparser.expected-output/operators | 145 +++++++++++++++++++++++++ test/qparser.expected-output/prefixes | 33 ++++++ test/qparser.expected-output/probs | 46 ++++++++ test/qparser.expected-output/quoted-phrases | 29 +++++ test/qparser.expected-output/terms | 136 ++++++++++++++++++++++++ 10 files changed, 581 insertions(+), 4 deletions(-) create mode 100755 test/qparser create mode 100644 test/qparser-test.cc create mode 100644 test/qparser.expected-output/operators create mode 100644 test/qparser.expected-output/prefixes create mode 100644 test/qparser.expected-output/probs create mode 100644 test/qparser.expected-output/quoted-phrases create mode 100644 test/qparser.expected-output/terms diff --git a/test/Makefile.local b/test/Makefile.local index 7b602bc..302482b 100644 --- a/test/Makefile.local +++ b/test/Makefile.local @@ -5,10 +5,13 @@ dir := test $(dir)/smtp-dummy: $(dir)/smtp-dummy.c $(call quiet,CC) $^ -o $@ +$(dir)/qparser-test: $(dir)/qparser-test.o notmuch-config.o query-string.o lib/libnotmuch.a + $(call quiet,CXX $(CXXFLAGS)) $^ $(FINAL_LIBNOTMUCH_LDFLAGS) -o $@ + .PHONY: test check -test: all $(dir)/smtp-dummy +test: all $(dir)/smtp-dummy $(dir)/qparser-test @${dir}/notmuch-test $(OPTIONS) check: test -CLEAN := $(CLEAN) $(dir)/smtp-dummy +CLEAN := $(CLEAN) $(dir)/smtp-dummy $(dir)/qparser-test.o $(dir)/qparser-test diff --git a/test/basic b/test/basic index b4410f2..3191bcc 100755 --- a/test/basic +++ b/test/basic @@ -52,7 +52,7 @@ test_expect_code 2 'failure to clean up causes the test to fail' ' # Ensure that all tests are being run test_begin_subtest 'Ensure that all available tests will be run by notmuch-test' tests_in_suite=$(grep TESTS= ../notmuch-test | sed -e "s/TESTS=\"\(.*\)\"/\1/" | tr " " "\n" | sort) -available=$(ls -1 ../ | grep -v -E "^(aggregate-results.sh|Makefile|Makefile.local|notmuch-test|README|test-lib.sh|test-results|tmp.*|valgrind|corpus*|emacs.expected-output|smtp-dummy|smtp-dummy.c|test-verbose|test.expected-output)" | sort) +available=$(ls -1 ../ | grep -v -E "^(aggregate-results.sh|Makefile|Makefile.local|notmuch-test|README|test-lib.sh|test-results|tmp.*|valgrind|corpus*|emacs.expected-output|smtp-dummy|smtp-dummy.c|test-verbose|test.expected-output|qparser-test.*|qparser-test|qparser.expected-output)" | sort) test_expect_equal "$tests_in_suite" "$available" EXPECTED=../test.expected-output diff --git a/test/notmuch-test b/test/notmuch-test index 4889e49..1e331b3 100755 --- a/test/notmuch-test +++ b/test/notmuch-test @@ -16,7 +16,7 @@ fi cd $(dirname "$0") -TESTS="basic new search search-output json thread-naming raw reply dump-restore uuencode thread-order author-order from-guessing long-id encoding emacs maildir-sync" +TESTS="basic new qparser search search-output json thread-naming raw reply dump-restore uuencode thread-order author-order from-guessing long-id encoding emacs maildir-sync" # Clean up any results from a previous run rm -r test-results >/dev/null 2>/dev/null diff --git a/test/qparser b/test/qparser new file mode 100755 index 0000000..0e7b022 --- /dev/null +++ b/test/qparser @@ -0,0 +1,32 @@ +#!/bin/bash +test_description="query parser" +. ./test-lib.sh + +EXPECTED=../qparser.expected-output + +test_begin_subtest "Quoted phrases" +output=$(../qparser-test < $EXPECTED/quoted-phrases) +expected=$(cat $EXPECTED/quoted-phrases) +test_expect_equal "$output" "$expected" + +test_begin_subtest "Prefixes" +output=$(../qparser-test < $EXPECTED/prefixes) +expected=$(cat $EXPECTED/prefixes) +test_expect_equal "$output" "$expected" + +test_begin_subtest "Terms" +output=$(../qparser-test < $EXPECTED/terms) +expected=$(cat $EXPECTED/terms) +test_expect_equal "$output" "$expected" + +test_begin_subtest "Operators" +output=$(../qparser-test < $EXPECTED/operators) +expected=$(cat $EXPECTED/operators) +test_expect_equal "$output" "$expected" + +test_begin_subtest "Probs" +output=$(../qparser-test < $EXPECTED/probs) +expected=$(cat $EXPECTED/probs) +test_expect_equal "$output" "$expected" + +test_done diff --git a/test/qparser-test.cc b/test/qparser-test.cc new file mode 100644 index 0000000..01d6bae --- /dev/null +++ b/test/qparser-test.cc @@ -0,0 +1,153 @@ +/* qparser-test - Display the lex, parse, and query tree for a query + * + * Copyright © 2011 Austin Clements + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/ . + * + * Authors: Austin Clements + */ + +/* If command-line arguments are given, they are used as the query + * string. Otherwise, qparser-test enters "echo mode", in which it + * accepts queries from stdin. In echo mode, lines beginning with '[' + * are ignored and lines consisting of whitespace or comments are + * echoed back to stdout. All other lines are treated as queries and + * are echoed back, followed by the results of parsing the query. + * This allows the output of qparser-test to be fed back in as input. + * + * For each, qparser-test displays the lex list of that query, the + * parse tree of that query, and the generated query tree. Finally, + * if the generated query tree differs from that generated by Xapian's + * query parser, it also displays what Xapian's query parser + * generated. + */ + +#include "../lib/notmuch-private.h" +#include "../lib/database-private.h" + +extern "C" { +/* notmuch-client.h also defines INTERNAL_ERROR */ +#undef INTERNAL_ERROR +#include "../notmuch-client.h" +} + +static _notmuch_qparser_t *qparser; +static Xapian::QueryParser xqparser; + +static char * +query_desc (void *ctx, Xapian::Query q) +{ + char *desc = talloc_strdup (ctx, q.get_description ().c_str ()); + desc += strlen ("Xapian::Query("); + desc[strlen(desc) - 1] = 0; + return desc; +} + +static void +test_one (void *ctx, const char *query_str) +{ + void *local = talloc_new (ctx); + Xapian::Query q; + _notmuch_token_t *toks, *root; + char *error, *qparser_desc, *xqparser_desc; + + toks = _notmuch_qparser_lex (local, qparser, query_str); + printf("[lex] %s\n", _notmuch_token_show_list (local, toks)); + + root = _notmuch_qparser_parse (local, qparser, query_str); + printf("[parse] %s\n", _notmuch_token_show_tree (local, root)); + + root = _notmuch_qparser_transform (qparser, root); + q = _notmuch_qparser_generate (local, qparser, root, &error); + if (error) + printf("[gen] error %s\n", error); + else { + qparser_desc = query_desc (local, q); + printf("[gen] %s\n", qparser_desc); + } + + try { + unsigned int flags = (Xapian::QueryParser::FLAG_BOOLEAN | + Xapian::QueryParser::FLAG_PHRASE | + Xapian::QueryParser::FLAG_LOVEHATE | + Xapian::QueryParser::FLAG_BOOLEAN_ANY_CASE | + Xapian::QueryParser::FLAG_WILDCARD | + Xapian::QueryParser::FLAG_PURE_NOT); + q = xqparser.parse_query (query_str, flags); + xqparser_desc = query_desc (local, q); + if (strcmp (qparser_desc, xqparser_desc) != 0) + printf("[xapian] %s\n", xqparser_desc); + } catch (const Xapian::QueryParserError & e) { + printf("[xapian] error %s\n", e.get_msg ().c_str ()); + } + + talloc_free (local); +} + +static _notmuch_qparser_t * +create_qparser (void *ctx) +{ + _notmuch_qparser_t *qparser = _notmuch_qparser_create (ctx, NULL); + _notmuch_qparser_add_db_prefix (qparser, "prob", "P", FALSE); + _notmuch_qparser_add_db_prefix (qparser, "lit", "L", TRUE); + _notmuch_qparser_add_db_prefix (qparser, "tag", "K", TRUE); + return qparser; +} + +static Xapian::QueryParser +create_xapian_qparser (void) +{ + Xapian::QueryParser xq; + xq.set_default_op (Xapian::Query::OP_AND); + xq.add_prefix ("prob", "P"); + xq.add_boolean_prefix ("lit", "L"); + xq.add_boolean_prefix ("tag", "K"); + return xq; +} + +int +main (int argc, char **argv) +{ + void *ctx; + + ctx = talloc_new (NULL); + + qparser = create_qparser (ctx); + xqparser = create_xapian_qparser (); + + if (argc > 1) { + char *query_str; + query_str = query_string_from_args (ctx, argc - 1, argv + 1); + test_one (ctx, query_str); + } else { + /* Echo mode */ + char line[512]; + while (fgets (line, sizeof (line), stdin)) { + if (line[0] == '\n' || line[0] == '#') { + /* Comment or whitespace. Echo it */ + printf("%s", line); + } else if (line[0] == '[') { + /* Ignore line */ + } else { + /* Query */ + if (line[strlen (line) - 1] == '\n') + line[strlen (line) - 1] = 0; + printf("%s\n", line); + test_one (ctx, line); + } + } + } + + return 0; +} diff --git a/test/qparser.expected-output/operators b/test/qparser.expected-output/operators new file mode 100644 index 0000000..788f007 --- /dev/null +++ b/test/qparser.expected-output/operators @@ -0,0 +1,145 @@ +# Boolean operators + +x and y +[lex] "x" AND "y" +[parse] (AND "x" "y") +[gen] (x:(pos=1) AND y:(pos=2)) + +x or y +[lex] "x" OR "y" +[parse] (OR "x" "y") +[gen] (x:(pos=1) OR y:(pos=2)) + +x xor y +[lex] "x" XOR "y" +[parse] (XOR "x" "y") +[gen] (x:(pos=1) XOR y:(pos=2)) + +x and y or x and w +[lex] "x" AND "y" OR "x" AND "w" +[parse] (OR (AND "x" "y") (AND "x" "w")) +[gen] ((x:(pos=1) AND y:(pos=2)) OR (x:(pos=3) AND w:(pos=4))) + +x and -y +[lex] "x" AND HATE "y" +[parse] (AND "x" (NOT "y")) +[gen] (x:(pos=1) AND_NOT y:(pos=2)) + +x or not y +[lex] "x" OR NOT "y" +[parse] (OR "x" (NOT "y")) +[gen] (x:(pos=1) OR ( AND_NOT y:(pos=2))) + +# The following three are Xapian-incompatible because they're syntax errors. +x and +[lex] "x" AND +[parse] "x" +[gen] x:(pos=1) +[xapian] error Syntax: AND + +and x +[lex] AND "x" +[parse] "x" +[gen] x:(pos=1) +[xapian] error Syntax: AND + +and +[lex] AND +[parse] +[gen] +[xapian] error Syntax: AND + +# Unary NOT + +x not y +[lex] "x" NOT "y" +[parse] (AND "x" (NOT "y")) +[gen] (x:(pos=1) AND_NOT y:(pos=2)) + +x not y or z +[lex] "x" NOT "y" OR "z" +[parse] (OR (AND "x" (NOT "y")) "z") +[gen] ((x:(pos=1) AND_NOT y:(pos=2)) OR z:(pos=3)) + +x not y and z +[lex] "x" NOT "y" AND "z" +[parse] (AND (AND "x" (NOT "y")) "z") +[gen] ((x:(pos=1) AND_NOT y:(pos=2)) AND z:(pos=3)) + +not not x +[lex] NOT NOT "x" +[parse] (NOT (NOT "x")) +[gen] ( AND_NOT ( AND_NOT x:(pos=1))) +[xapian] error Syntax: NOT + +# Empty subexpressions +# These are all Xapian-incompatible because they're syntax errors. + +x and () +[lex] "x" AND BRA KET +[parse] "x" +[gen] x:(pos=1) +[xapian] error Syntax: AND + +() and x +[lex] BRA KET AND "x" +[parse] "x" +[gen] x:(pos=1) +[xapian] error Syntax: AND + +# NULL phrases +# These are all Xapian-incompatible because they're syntax errors. + +and +[lex] AND +[parse] +[gen] +[xapian] error Syntax: AND + +@ +[lex] "@" +[parse] "@" +[gen] +[xapian] + +@ AND x +[lex] "@" AND "x" +[parse] (AND "@" "x") +[gen] x:(pos=1) +[xapian] error Syntax: AND + +x AND @ +[lex] "x" AND "@" +[parse] (AND "x" "@") +[gen] x:(pos=1) +[xapian] error Syntax: AND + +@ AND NOT x +[lex] "@" AND NOT "x" +[parse] (AND "@" (NOT "x")) +[gen] ( AND_NOT x:(pos=1)) +[xapian] error Syntax: AND NOT + +x AND NOT @ +[lex] "x" AND NOT "@" +[parse] (AND "x" (NOT "@")) +[gen] x:(pos=1) +[xapian] error Syntax: AND NOT + +NOT @ +[lex] NOT "@" +[parse] (NOT "@") +[gen] +[xapian] error Syntax: NOT + +@ OR x +[lex] "@" OR "x" +[parse] (OR "@" "x") +[gen] x:(pos=1) +[xapian] error Syntax: OR + +x OR @ +[lex] "x" OR "@" +[parse] (OR "x" "@") +[gen] x:(pos=1) +[xapian] error Syntax: OR diff --git a/test/qparser.expected-output/prefixes b/test/qparser.expected-output/prefixes new file mode 100644 index 0000000..04c4f90 --- /dev/null +++ b/test/qparser.expected-output/prefixes @@ -0,0 +1,33 @@ +prob:x lit:y none:z +[lex] PREFIX/prob "x" PREFIX/lit "y" "none:z" +[parse] (AND (AND (PREFIX/prob "x") "none:z") (FILTER (PREFIX/lit 'y'))) +[gen] ((Px:(pos=1) AND (none:(pos=2) PHRASE 2 z:(pos=3))) FILTER Ly) + +prob:"x y" lit:"x y" none:"x y" +[lex] PREFIX/prob "x y" PREFIX/lit "x y" "none:" "x y" +[parse] (AND (AND (AND (PREFIX/prob "x y") "none:") "x y") (FILTER (PREFIX/lit 'x y'))) +[gen] (((Px:(pos=1) PHRASE 2 Py:(pos=2)) AND none:(pos=3) AND (x:(pos=4) PHRASE 2 y:(pos=5))) FILTER Lx y) + +# Incompatible; Xapian bails and re-parses everything with no flags +prob:(x y) lit:(x y) none:(x y) +[lex] PREFIX/prob BRA "x" "y" KET PREFIX/lit "(x" "y" KET "none:" BRA "x" "y" KET +[parse] (AND (AND (AND (AND (PREFIX/prob (AND "x" "y")) "y") "none:") (AND "x" "y")) (FILTER (PREFIX/lit '(x'))) +[gen] ((Px:(pos=1) AND Py:(pos=2) AND y:(pos=3) AND none:(pos=4) AND x:(pos=5) AND y:(pos=6)) FILTER L(x) +[xapian] ((prob:(pos=1) AND x:(pos=2) AND y:(pos=3) AND y:(pos=4) AND none:(pos=5) AND x:(pos=6) AND y:(pos=7)) FILTER L(x) + +# This is Xapian-compatible, but seems ridiculous +lit:(x) +[lex] PREFIX/lit "(x" KET +[parse] (FILTER (PREFIX/lit '(x')) +[gen] 0 * L(x + +# Test characters accepted after the prefix colon +lit:# +[lex] PREFIX/lit "#" +[parse] (FILTER (PREFIX/lit '#')) +[gen] 0 * L# + +prob:# +[lex] "prob:#" +[parse] "prob:#" +[gen] prob:(pos=1) diff --git a/test/qparser.expected-output/probs b/test/qparser.expected-output/probs new file mode 100644 index 0000000..3c166f7 --- /dev/null +++ b/test/qparser.expected-output/probs @@ -0,0 +1,46 @@ +(x OR y) AND z +[lex] BRA "x" OR "y" KET AND "z" +[parse] (AND (OR "x" "y") "z") +[gen] ((x:(pos=1) OR y:(pos=2)) AND z:(pos=3)) + +# Incompatible; Xapian bails on the syntax error, we forge ahead. +(x OR y)) AND z +[lex] BRA "x" OR "y" KET KET AND "z" +[parse] (AND (OR "x" "y") "z") +[gen] ((x:(pos=1) OR y:(pos=2)) AND z:(pos=3)) +[xapian] (x:(pos=1) AND or:(pos=2) AND y:(pos=3) AND and:(pos=4) AND z:(pos=5)) + +# Empty subexpression after prefix +# Incompatible; Xapian treats as a syntax error. +prob:() AND x +[lex] PREFIX/prob BRA KET AND "x" +[parse] "x" +[gen] x:(pos=1) +[xapian] (prob:(pos=1) AND and:(pos=2) AND x:(pos=3)) + +# Subqueries with same boolean prefix +lit:x lit:y +[lex] PREFIX/lit "x" PREFIX/lit "y" +[parse] (FILTER (OR (PREFIX/lit 'x') (PREFIX/lit 'y'))) +[gen] 0 * (Lx OR Ly) + +# Combining prob components +x -y lit:z +[lex] "x" HATE "y" PREFIX/lit "z" +[parse] (AND (AND "x" (FILTER (PREFIX/lit 'z'))) (NOT "y")) +[gen] ((x:(pos=1) FILTER Lz) AND_NOT y:(pos=2)) + +x lit:z +[lex] "x" PREFIX/lit "z" +[parse] (AND "x" (FILTER (PREFIX/lit 'z'))) +[gen] (x:(pos=1) FILTER Lz) + +-y lit:z +[lex] HATE "y" PREFIX/lit "z" +[parse] (AND (FILTER (PREFIX/lit 'z')) (NOT "y")) +[gen] (0 * Lz AND_NOT y:(pos=1)) + +x -y +[lex] "x" HATE "y" +[parse] (AND "x" (NOT "y")) +[gen] (x:(pos=1) AND_NOT y:(pos=2)) diff --git a/test/qparser.expected-output/quoted-phrases b/test/qparser.expected-output/quoted-phrases new file mode 100644 index 0000000..e223366 --- /dev/null +++ b/test/qparser.expected-output/quoted-phrases @@ -0,0 +1,29 @@ +x "y z" w +[lex] "x" "y z" "w" +[parse] (AND (AND "x" "y z") "w") +[gen] (x:(pos=1) AND (y:(pos=2) PHRASE 2 z:(pos=3)) AND w:(pos=4)) + +x "y z +[lex] "x" "y z" +[parse] (AND "x" "y z") +[gen] (x:(pos=1) AND (y:(pos=2) PHRASE 2 z:(pos=3))) + +x "" y +[lex] "x" "" "y" +[parse] (AND (AND "x" "") "y") +[gen] (x:(pos=1) AND y:(pos=2)) + +x " " y +[lex] "x" " " "y" +[parse] (AND (AND "x" " ") "y") +[gen] (x:(pos=1) AND y:(pos=2)) + +lit:" x y" +[lex] PREFIX/lit " x y" +[parse] (FILTER (PREFIX/lit ' x y')) +[gen] 0 * L x y + +lit:"x""y" +[lex] PREFIX/lit "x"y" +[parse] (FILTER (PREFIX/lit 'x"y')) +[gen] 0 * Lx"y diff --git a/test/qparser.expected-output/terms b/test/qparser.expected-output/terms new file mode 100644 index 0000000..9316c54 --- /dev/null +++ b/test/qparser.expected-output/terms @@ -0,0 +1,136 @@ +# Term lexing + +x y z +[lex] "x" "y" "z" +[parse] (AND (AND "x" "y") "z") +[gen] (x:(pos=1) AND y:(pos=2) AND z:(pos=3)) + +x"y z"w +[lex] "x" "y z" "w" +[parse] (AND (AND "x" "y z") "w") +[gen] (x:(pos=1) AND (y:(pos=2) PHRASE 2 z:(pos=3)) AND w:(pos=4)) + +x(y z)w +[lex] "x" BRA "y" "z" KET "w" +[parse] (AND (AND "x" (AND "y" "z")) "w") +[gen] (x:(pos=1) AND y:(pos=2) AND z:(pos=3) AND w:(pos=4)) + +# The first query below is Xapian-compatible, while the second one +# isn't. We use much simpler term lexing rules than Xapian. +x/y +[lex] "x/y" +[parse] "x/y" +[gen] (x:(pos=1) PHRASE 2 y:(pos=2)) + +x!y +[lex] "x!y" +[parse] "x!y" +[gen] (x:(pos=1) PHRASE 2 y:(pos=2)) +[xapian] (x:(pos=1) AND y:(pos=2)) + +# Incompatible; our simpler term parsing sees ! as a term +x -! y +[lex] "x" HATE "!" "y" +[parse] (AND (AND "x" "y") (NOT "!")) +[gen] (x:(pos=1) AND y:(pos=2)) +[xapian] (x:(pos=1) AND_NOT y:(pos=2)) + +# Term parsing + +x - +[lex] "x" HATE +[parse] "x" +[gen] x:(pos=1) + +x + +[lex] "x" LOVE +[parse] "x" +[gen] x:(pos=1) + +(x) +[lex] BRA "x" KET +[parse] "x" +[gen] x:(pos=1) + +# Prefixed operators get demoted to terms +prob:AND +[lex] PREFIX/prob AND +[parse] (PREFIX/prob "AND") +[gen] Pand:(pos=1) + +# The first query below is Xapian-compatible, but the second isn't +# because Xapian handles hate very differently from love. ++AND +[lex] LOVE AND +[parse] "AND" +[gen] and:(pos=1) + +-AND +[lex] HATE AND +[parse] (NOT "AND") +[gen] ( AND_NOT and:(pos=1)) +[xapian] and:(pos=1) + +# Incompatible; Xapian sees this as prob:"prob:x" +prob:prob:x +[lex] PREFIX/prob PREFIX/prob "x" +[parse] (PREFIX/prob "x") +[gen] Px:(pos=1) +[xapian] (Pprob:(pos=1) PHRASE 2 Px:(pos=2)) + +# The rest are Xapian-incompatible because they're all considered +# syntax errors +( +[lex] BRA +[parse] +[gen] +[xapian] + +() +[lex] BRA KET +[parse] +[gen] +[xapian] + +) +[lex] KET +[parse] +[gen] +[xapian] + +(x)) OR y +[lex] BRA "x" KET KET OR "y" +[parse] (OR "x" "y") +[gen] (x:(pos=1) OR y:(pos=2)) +[xapian] (x:(pos=1) AND or:(pos=2) AND y:(pos=3)) + +# This one's only Xapian-compatible by chance. +(x)) +[lex] BRA "x" KET KET +[parse] "x" +[gen] x:(pos=1) + +# Term generating + +c++ x +[lex] "c++" "x" +[parse] (AND "c++" "x") +[gen] (c++:(pos=1) AND x:(pos=2)) + +# Incompatible; + is not a "phrase generator" in Xapian. +c+x +[lex] "c+x" +[parse] "c+x" +[gen] (c:(pos=1) PHRASE 2 x:(pos=2)) +[xapian] (c:(pos=1) AND x:(pos=2)) + +c-x +[lex] "c-x" +[parse] "c-x" +[gen] (c:(pos=1) PHRASE 2 x:(pos=2)) + +w "x y z" +[lex] "w" "x y z" +[parse] (AND "w" "x y z") +[gen] (w:(pos=1) AND (x:(pos=2) PHRASE 3 y:(pos=3) PHRASE 3 z:(pos=4))) + -- 1.7.2.3