[PATCH 1.5/8] Query parser testing framework and basic tests.

Austin Clements amdragon at MIT.EDU
Thu Jan 20 22:37:14 PST 2011


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 at 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 <amdragon at mit.edu>
+ */
+
+/* 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 (<alldocuments> 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: <expression> AND <expression>
+
+and x
+[lex]    AND "x"
+[parse]  "x"
+[gen]    x:(pos=1)
+[xapian] error Syntax: <expression> AND <expression>
+
+and
+[lex]    AND
+[parse]  <nil>
+[gen]    <alldocuments>
+[xapian] error Syntax: <expression> AND <expression>
+
+# 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]    (<alldocuments> AND_NOT (<alldocuments> AND_NOT x:(pos=1)))
+[xapian] error Syntax: <expression> NOT <expression>
+
+# 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: <expression> AND <expression>
+
+() and x
+[lex]    BRA KET AND "x"
+[parse]  "x"
+[gen]    x:(pos=1)
+[xapian] error Syntax: <expression> AND <expression>
+
+# NULL phrases
+# These are all Xapian-incompatible because they're syntax errors.
+
+and
+[lex]    AND
+[parse]  <nil>
+[gen]    <alldocuments>
+[xapian] error Syntax: <expression> AND <expression>
+
+@
+[lex]    "@"
+[parse]  "@"
+[gen]    <alldocuments>
+[xapian] 
+
+@ AND x
+[lex]    "@" AND "x"
+[parse]  (AND "@" "x")
+[gen]    x:(pos=1)
+[xapian] error Syntax: <expression> AND <expression>
+
+x AND @
+[lex]    "x" AND "@"
+[parse]  (AND "x" "@")
+[gen]    x:(pos=1)
+[xapian] error Syntax: <expression> AND <expression>
+
+@ AND NOT x
+[lex]    "@" AND NOT "x"
+[parse]  (AND "@" (NOT "x"))
+[gen]    (<alldocuments> AND_NOT x:(pos=1))
+[xapian] error Syntax: <expression> AND NOT <expression>
+
+x AND NOT @
+[lex]    "x" AND NOT "@"
+[parse]  (AND "x" (NOT "@"))
+[gen]    x:(pos=1)
+[xapian] error Syntax: <expression> AND NOT <expression>
+
+NOT @
+[lex]    NOT "@"
+[parse]  (NOT "@")
+[gen]    <alldocuments>
+[xapian] error Syntax: <expression> NOT <expression>
+
+@ OR x
+[lex]    "@" OR "x"
+[parse]  (OR "@" "x")
+[gen]    x:(pos=1)
+[xapian] error Syntax: <expression> OR <expression>
+
+x OR @
+[lex]    "x" OR "@"
+[parse]  (OR "x" "@")
+[gen]    x:(pos=1)
+[xapian] error Syntax: <expression> OR <expression>
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]    (<alldocuments> 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]  <nil>
+[gen]    <alldocuments>
+[xapian] 
+
+()
+[lex]    BRA KET
+[parse]  <nil>
+[gen]    <alldocuments>
+[xapian] 
+
+)
+[lex]    KET
+[parse]  <nil>
+[gen]    <alldocuments>
+[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



More information about the notmuch mailing list