Re: [PATCH] .dir-locals.el: changed one-char comment prefix '; ' to two; '; ; '
[notmuch-archives.git] / 1a / 9eaf5817d93ce93a03cb215dfc10ae17a50b74
1 Return-Path: <amdragon@mit.edu>\r
2 X-Original-To: notmuch@notmuchmail.org\r
3 Delivered-To: notmuch@notmuchmail.org\r
4 Received: from localhost (localhost [127.0.0.1])\r
5         by olra.theworths.org (Postfix) with ESMTP id 002E9431FD0\r
6         for <notmuch@notmuchmail.org>; Thu, 20 Jan 2011 22:37:27 -0800 (PST)\r
7 X-Virus-Scanned: Debian amavisd-new at olra.theworths.org\r
8 X-Spam-Flag: NO\r
9 X-Spam-Score: 0\r
10 X-Spam-Level: \r
11 X-Spam-Status: No, score=0 tagged_above=-999 required=5\r
12         tests=[RCVD_IN_DNSWL_NONE=-0.0001] autolearn=disabled\r
13 Received: from olra.theworths.org ([127.0.0.1])\r
14         by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024)\r
15         with ESMTP id jFYJbVpBDHqI for <notmuch@notmuchmail.org>;\r
16         Thu, 20 Jan 2011 22:37:19 -0800 (PST)\r
17 Received: from dmz-mailsec-scanner-8.mit.edu (DMZ-MAILSEC-SCANNER-8.MIT.EDU\r
18         [18.7.68.37])\r
19         by olra.theworths.org (Postfix) with ESMTP id 5CEE7431FB6\r
20         for <notmuch@notmuchmail.org>; Thu, 20 Jan 2011 22:37:18 -0800 (PST)\r
21 X-AuditID: 12074425-b7c98ae000000a04-6c-4d39299c563f\r
22 Received: from mailhub-auth-4.mit.edu ( [18.7.62.39])\r
23         by dmz-mailsec-scanner-8.mit.edu (Symantec Brightmail Gateway) with\r
24         SMTP id 9F.51.02564.C99293D4; Fri, 21 Jan 2011 01:37:17 -0500 (EST)\r
25 Received: from outgoing.mit.edu (OUTGOING-AUTH.MIT.EDU [18.7.22.103])\r
26         by mailhub-auth-4.mit.edu (8.13.8/8.9.2) with ESMTP id p0L6bGVc020511; \r
27         Fri, 21 Jan 2011 01:37:16 -0500\r
28 Received: from awakening.csail.mit.edu (awakening.csail.mit.edu [18.26.4.91])\r
29         (authenticated bits=0)\r
30         (User authenticated as amdragon@ATHENA.MIT.EDU)\r
31         by outgoing.mit.edu (8.13.6/8.12.4) with ESMTP id p0L6bE4s004633\r
32         (version=TLSv1/SSLv3 cipher=AES256-SHA bits=256 verify=NOT);\r
33         Fri, 21 Jan 2011 01:37:15 -0500 (EST)\r
34 Received: from amthrax by awakening.csail.mit.edu with local (Exim 4.72)\r
35         (envelope-from <amdragon@mit.edu>)\r
36         id 1PgAcU-0000yr-8j; Fri, 21 Jan 2011 01:37:14 -0500\r
37 Date: Fri, 21 Jan 2011 01:37:14 -0500\r
38 From: Austin Clements <amdragon@MIT.EDU>\r
39 To: notmuch@notmuchmail.org\r
40 Subject: [PATCH 1.5/8] Query parser testing framework and basic tests.\r
41 Message-ID: <20110121063714.GI13226@mit.edu>\r
42 References: <1295165458-9573-1-git-send-email-amdragon@mit.edu>\r
43         <1295165458-9573-2-git-send-email-amdragon@mit.edu>\r
44 MIME-Version: 1.0\r
45 Content-Type: text/plain; charset=iso-8859-1\r
46 Content-Disposition: inline\r
47 Content-Transfer-Encoding: 8bit\r
48 In-Reply-To: <1295165458-9573-2-git-send-email-amdragon@mit.edu>\r
49 User-Agent: Mutt/1.5.20 (2009-06-14)\r
50 X-Brightmail-Tracker: AAAAAhcymoEXM1Bb\r
51 Cc: amdragon@mit.edu\r
52 X-BeenThere: notmuch@notmuchmail.org\r
53 X-Mailman-Version: 2.1.13\r
54 Precedence: list\r
55 List-Id: "Use and development of the notmuch mail system."\r
56         <notmuch.notmuchmail.org>\r
57 List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,\r
58         <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>\r
59 List-Archive: <http://notmuchmail.org/pipermail/notmuch>\r
60 List-Post: <mailto:notmuch@notmuchmail.org>\r
61 List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>\r
62 List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,\r
63         <mailto:notmuch-request@notmuchmail.org?subject=subscribe>\r
64 X-List-Received-Date: Fri, 21 Jan 2011 06:37:29 -0000\r
65 \r
66 The query parser test is implemented as a separate binary that calls\r
67 directly in to the lexer, parser, and generator to make it easy to\r
68 isolate test failures.\r
69 ---\r
70 \r
71 Sorry for the patch ordering.  This is intended to be applied after\r
72 patch 1/8 in this series,\r
73 id:1295165458-9573-2-git-send-email-amdragon@mit.edu\r
74 \r
75  test/Makefile.local                         |    7 +-\r
76  test/basic                                  |    2 +-\r
77  test/notmuch-test                           |    2 +-\r
78  test/qparser                                |   32 ++++++\r
79  test/qparser-test.cc                        |  153 +++++++++++++++++++++++++++\r
80  test/qparser.expected-output/operators      |  145 +++++++++++++++++++++++++\r
81  test/qparser.expected-output/prefixes       |   33 ++++++\r
82  test/qparser.expected-output/probs          |   46 ++++++++\r
83  test/qparser.expected-output/quoted-phrases |   29 +++++\r
84  test/qparser.expected-output/terms          |  136 ++++++++++++++++++++++++\r
85  10 files changed, 581 insertions(+), 4 deletions(-)\r
86  create mode 100755 test/qparser\r
87  create mode 100644 test/qparser-test.cc\r
88  create mode 100644 test/qparser.expected-output/operators\r
89  create mode 100644 test/qparser.expected-output/prefixes\r
90  create mode 100644 test/qparser.expected-output/probs\r
91  create mode 100644 test/qparser.expected-output/quoted-phrases\r
92  create mode 100644 test/qparser.expected-output/terms\r
93 \r
94 diff --git a/test/Makefile.local b/test/Makefile.local\r
95 index 7b602bc..302482b 100644\r
96 --- a/test/Makefile.local\r
97 +++ b/test/Makefile.local\r
98 @@ -5,10 +5,13 @@ dir := test\r
99  $(dir)/smtp-dummy: $(dir)/smtp-dummy.c\r
100         $(call quiet,CC) $^ -o $@\r
101  \r
102 +$(dir)/qparser-test: $(dir)/qparser-test.o notmuch-config.o query-string.o lib/libnotmuch.a\r
103 +       $(call quiet,CXX $(CXXFLAGS)) $^ $(FINAL_LIBNOTMUCH_LDFLAGS) -o $@\r
104 +\r
105  .PHONY: test check\r
106 -test:  all $(dir)/smtp-dummy\r
107 +test:  all $(dir)/smtp-dummy $(dir)/qparser-test\r
108         @${dir}/notmuch-test $(OPTIONS)\r
109  \r
110  check: test\r
111  \r
112 -CLEAN := $(CLEAN) $(dir)/smtp-dummy\r
113 +CLEAN := $(CLEAN) $(dir)/smtp-dummy $(dir)/qparser-test.o $(dir)/qparser-test\r
114 diff --git a/test/basic b/test/basic\r
115 index b4410f2..3191bcc 100755\r
116 --- a/test/basic\r
117 +++ b/test/basic\r
118 @@ -52,7 +52,7 @@ test_expect_code 2 'failure to clean up causes the test to fail' '\r
119  # Ensure that all tests are being run\r
120  test_begin_subtest 'Ensure that all available tests will be run by notmuch-test'\r
121  tests_in_suite=$(grep TESTS= ../notmuch-test | sed -e "s/TESTS=\"\(.*\)\"/\1/" | tr " " "\n" | sort)\r
122 -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)\r
123 +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)\r
124  test_expect_equal "$tests_in_suite" "$available"\r
125  \r
126  EXPECTED=../test.expected-output\r
127 diff --git a/test/notmuch-test b/test/notmuch-test\r
128 index 4889e49..1e331b3 100755\r
129 --- a/test/notmuch-test\r
130 +++ b/test/notmuch-test\r
131 @@ -16,7 +16,7 @@ fi\r
132  \r
133  cd $(dirname "$0")\r
134  \r
135 -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"\r
136 +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"\r
137  \r
138  # Clean up any results from a previous run\r
139  rm -r test-results >/dev/null 2>/dev/null\r
140 diff --git a/test/qparser b/test/qparser\r
141 new file mode 100755\r
142 index 0000000..0e7b022\r
143 --- /dev/null\r
144 +++ b/test/qparser\r
145 @@ -0,0 +1,32 @@\r
146 +#!/bin/bash\r
147 +test_description="query parser"\r
148 +. ./test-lib.sh\r
149 +\r
150 +EXPECTED=../qparser.expected-output\r
151 +\r
152 +test_begin_subtest "Quoted phrases"\r
153 +output=$(../qparser-test < $EXPECTED/quoted-phrases)\r
154 +expected=$(cat $EXPECTED/quoted-phrases)\r
155 +test_expect_equal "$output" "$expected"\r
156 +\r
157 +test_begin_subtest "Prefixes"\r
158 +output=$(../qparser-test < $EXPECTED/prefixes)\r
159 +expected=$(cat $EXPECTED/prefixes)\r
160 +test_expect_equal "$output" "$expected"\r
161 +\r
162 +test_begin_subtest "Terms"\r
163 +output=$(../qparser-test < $EXPECTED/terms)\r
164 +expected=$(cat $EXPECTED/terms)\r
165 +test_expect_equal "$output" "$expected"\r
166 +\r
167 +test_begin_subtest "Operators"\r
168 +output=$(../qparser-test < $EXPECTED/operators)\r
169 +expected=$(cat $EXPECTED/operators)\r
170 +test_expect_equal "$output" "$expected"\r
171 +\r
172 +test_begin_subtest "Probs"\r
173 +output=$(../qparser-test < $EXPECTED/probs)\r
174 +expected=$(cat $EXPECTED/probs)\r
175 +test_expect_equal "$output" "$expected"\r
176 +\r
177 +test_done\r
178 diff --git a/test/qparser-test.cc b/test/qparser-test.cc\r
179 new file mode 100644\r
180 index 0000000..01d6bae\r
181 --- /dev/null\r
182 +++ b/test/qparser-test.cc\r
183 @@ -0,0 +1,153 @@\r
184 +/* qparser-test - Display the lex, parse, and query tree for a query\r
185 + *\r
186 + * Copyright Â© 2011 Austin Clements\r
187 + *\r
188 + * This program is free software: you can redistribute it and/or modify\r
189 + * it under the terms of the GNU General Public License as published by\r
190 + * the Free Software Foundation, either version 3 of the License, or\r
191 + * (at your option) any later version.\r
192 + *\r
193 + * This program is distributed in the hope that it will be useful,\r
194 + * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
195 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
196 + * GNU General Public License for more details.\r
197 + *\r
198 + * You should have received a copy of the GNU General Public License\r
199 + * along with this program.  If not, see http://www.gnu.org/licenses/ .\r
200 + *\r
201 + * Authors: Austin Clements <amdragon@mit.edu>\r
202 + */\r
203 +\r
204 +/* If command-line arguments are given, they are used as the query\r
205 + * string.  Otherwise, qparser-test enters "echo mode", in which it\r
206 + * accepts queries from stdin.  In echo mode, lines beginning with '['\r
207 + * are ignored and lines consisting of whitespace or comments are\r
208 + * echoed back to stdout.  All other lines are treated as queries and\r
209 + * are echoed back, followed by the results of parsing the query.\r
210 + * This allows the output of qparser-test to be fed back in as input.\r
211 + *\r
212 + * For each, qparser-test displays the lex list of that query, the\r
213 + * parse tree of that query, and the generated query tree.  Finally,\r
214 + * if the generated query tree differs from that generated by Xapian's\r
215 + * query parser, it also displays what Xapian's query parser\r
216 + * generated.\r
217 + */\r
218 +\r
219 +#include "../lib/notmuch-private.h"\r
220 +#include "../lib/database-private.h"\r
221 +\r
222 +extern "C" {\r
223 +/* notmuch-client.h also defines INTERNAL_ERROR */\r
224 +#undef INTERNAL_ERROR\r
225 +#include "../notmuch-client.h"\r
226 +}\r
227 +\r
228 +static _notmuch_qparser_t *qparser;\r
229 +static Xapian::QueryParser xqparser;\r
230 +\r
231 +static char *\r
232 +query_desc (void *ctx, Xapian::Query q)\r
233 +{\r
234 +    char *desc = talloc_strdup (ctx, q.get_description ().c_str ());\r
235 +    desc += strlen ("Xapian::Query(");\r
236 +    desc[strlen(desc) - 1] = 0;\r
237 +    return desc;\r
238 +}\r
239 +\r
240 +static void\r
241 +test_one (void *ctx, const char *query_str)\r
242 +{\r
243 +    void *local = talloc_new (ctx);\r
244 +    Xapian::Query q;\r
245 +    _notmuch_token_t *toks, *root;\r
246 +    char *error, *qparser_desc, *xqparser_desc;\r
247 +\r
248 +    toks = _notmuch_qparser_lex (local, qparser, query_str);\r
249 +    printf("[lex]    %s\n", _notmuch_token_show_list (local, toks));\r
250 +\r
251 +    root = _notmuch_qparser_parse (local, qparser, query_str);\r
252 +    printf("[parse]  %s\n", _notmuch_token_show_tree (local, root));\r
253 +\r
254 +    root = _notmuch_qparser_transform (qparser, root);\r
255 +    q = _notmuch_qparser_generate (local, qparser, root, &error);\r
256 +    if (error)\r
257 +       printf("[gen]    error %s\n", error);\r
258 +    else {\r
259 +       qparser_desc = query_desc (local, q);\r
260 +       printf("[gen]    %s\n", qparser_desc);\r
261 +    }\r
262 +\r
263 +    try {\r
264 +       unsigned int flags = (Xapian::QueryParser::FLAG_BOOLEAN |\r
265 +                             Xapian::QueryParser::FLAG_PHRASE |\r
266 +                             Xapian::QueryParser::FLAG_LOVEHATE |\r
267 +                             Xapian::QueryParser::FLAG_BOOLEAN_ANY_CASE |\r
268 +                             Xapian::QueryParser::FLAG_WILDCARD |\r
269 +                             Xapian::QueryParser::FLAG_PURE_NOT);\r
270 +       q = xqparser.parse_query (query_str, flags);\r
271 +       xqparser_desc = query_desc (local, q);\r
272 +       if (strcmp (qparser_desc, xqparser_desc) != 0)\r
273 +           printf("[xapian] %s\n", xqparser_desc);\r
274 +    } catch (const Xapian::QueryParserError & e) {\r
275 +       printf("[xapian] error %s\n", e.get_msg ().c_str ());\r
276 +    }\r
277 +\r
278 +    talloc_free (local);\r
279 +}\r
280 +\r
281 +static _notmuch_qparser_t *\r
282 +create_qparser (void *ctx)\r
283 +{\r
284 +    _notmuch_qparser_t *qparser = _notmuch_qparser_create (ctx, NULL);\r
285 +    _notmuch_qparser_add_db_prefix (qparser, "prob", "P", FALSE);\r
286 +    _notmuch_qparser_add_db_prefix (qparser, "lit", "L", TRUE);\r
287 +    _notmuch_qparser_add_db_prefix (qparser, "tag", "K", TRUE);\r
288 +    return qparser;\r
289 +}\r
290 +\r
291 +static Xapian::QueryParser\r
292 +create_xapian_qparser (void)\r
293 +{\r
294 +    Xapian::QueryParser xq;\r
295 +    xq.set_default_op (Xapian::Query::OP_AND);\r
296 +    xq.add_prefix ("prob", "P");\r
297 +    xq.add_boolean_prefix ("lit", "L");\r
298 +    xq.add_boolean_prefix ("tag", "K");\r
299 +    return xq;\r
300 +}\r
301 +\r
302 +int\r
303 +main (int argc, char **argv)\r
304 +{\r
305 +    void *ctx;\r
306 +\r
307 +    ctx = talloc_new (NULL);\r
308 +\r
309 +    qparser = create_qparser (ctx);\r
310 +    xqparser = create_xapian_qparser ();\r
311 +\r
312 +    if (argc > 1) {\r
313 +       char *query_str;\r
314 +       query_str = query_string_from_args (ctx, argc - 1, argv + 1);\r
315 +       test_one (ctx, query_str);\r
316 +    } else {\r
317 +       /* Echo mode */\r
318 +       char line[512];\r
319 +       while (fgets (line, sizeof (line), stdin)) {\r
320 +           if (line[0] == '\n' || line[0] == '#') {\r
321 +               /* Comment or whitespace.  Echo it */\r
322 +               printf("%s", line);\r
323 +           } else if (line[0] == '[') {\r
324 +               /* Ignore line */\r
325 +           } else {\r
326 +               /* Query */\r
327 +               if (line[strlen (line) - 1] == '\n')\r
328 +                   line[strlen (line) - 1] = 0;\r
329 +               printf("%s\n", line);\r
330 +               test_one (ctx, line);\r
331 +           }\r
332 +       }\r
333 +    }\r
334 +\r
335 +    return 0;\r
336 +}\r
337 diff --git a/test/qparser.expected-output/operators b/test/qparser.expected-output/operators\r
338 new file mode 100644\r
339 index 0000000..788f007\r
340 --- /dev/null\r
341 +++ b/test/qparser.expected-output/operators\r
342 @@ -0,0 +1,145 @@\r
343 +# Boolean operators\r
344 +\r
345 +x and y\r
346 +[lex]    "x" AND "y"\r
347 +[parse]  (AND "x" "y")\r
348 +[gen]    (x:(pos=1) AND y:(pos=2))\r
349 +\r
350 +x or y\r
351 +[lex]    "x" OR "y"\r
352 +[parse]  (OR "x" "y")\r
353 +[gen]    (x:(pos=1) OR y:(pos=2))\r
354 +\r
355 +x xor y\r
356 +[lex]    "x" XOR "y"\r
357 +[parse]  (XOR "x" "y")\r
358 +[gen]    (x:(pos=1) XOR y:(pos=2))\r
359 +\r
360 +x and y or x and w\r
361 +[lex]    "x" AND "y" OR "x" AND "w"\r
362 +[parse]  (OR (AND "x" "y") (AND "x" "w"))\r
363 +[gen]    ((x:(pos=1) AND y:(pos=2)) OR (x:(pos=3) AND w:(pos=4)))\r
364 +\r
365 +x and -y\r
366 +[lex]    "x" AND HATE "y"\r
367 +[parse]  (AND "x" (NOT "y"))\r
368 +[gen]    (x:(pos=1) AND_NOT y:(pos=2))\r
369 +\r
370 +x or not y\r
371 +[lex]    "x" OR NOT "y"\r
372 +[parse]  (OR "x" (NOT "y"))\r
373 +[gen]    (x:(pos=1) OR (<alldocuments> AND_NOT y:(pos=2)))\r
374 +\r
375 +# The following three are Xapian-incompatible because they're syntax errors.\r
376 +x and\r
377 +[lex]    "x" AND\r
378 +[parse]  "x"\r
379 +[gen]    x:(pos=1)\r
380 +[xapian] error Syntax: <expression> AND <expression>\r
381 +\r
382 +and x\r
383 +[lex]    AND "x"\r
384 +[parse]  "x"\r
385 +[gen]    x:(pos=1)\r
386 +[xapian] error Syntax: <expression> AND <expression>\r
387 +\r
388 +and\r
389 +[lex]    AND\r
390 +[parse]  <nil>\r
391 +[gen]    <alldocuments>\r
392 +[xapian] error Syntax: <expression> AND <expression>\r
393 +\r
394 +# Unary NOT\r
395 +\r
396 +x not y\r
397 +[lex]    "x" NOT "y"\r
398 +[parse]  (AND "x" (NOT "y"))\r
399 +[gen]    (x:(pos=1) AND_NOT y:(pos=2))\r
400 +\r
401 +x not y or z\r
402 +[lex]    "x" NOT "y" OR "z"\r
403 +[parse]  (OR (AND "x" (NOT "y")) "z")\r
404 +[gen]    ((x:(pos=1) AND_NOT y:(pos=2)) OR z:(pos=3))\r
405 +\r
406 +x not y and z\r
407 +[lex]    "x" NOT "y" AND "z"\r
408 +[parse]  (AND (AND "x" (NOT "y")) "z")\r
409 +[gen]    ((x:(pos=1) AND_NOT y:(pos=2)) AND z:(pos=3))\r
410 +\r
411 +not not x\r
412 +[lex]    NOT NOT "x"\r
413 +[parse]  (NOT (NOT "x"))\r
414 +[gen]    (<alldocuments> AND_NOT (<alldocuments> AND_NOT x:(pos=1)))\r
415 +[xapian] error Syntax: <expression> NOT <expression>\r
416 +\r
417 +# Empty subexpressions\r
418 +# These are all Xapian-incompatible because they're syntax errors.\r
419 +\r
420 +x and ()\r
421 +[lex]    "x" AND BRA KET\r
422 +[parse]  "x"\r
423 +[gen]    x:(pos=1)\r
424 +[xapian] error Syntax: <expression> AND <expression>\r
425 +\r
426 +() and x\r
427 +[lex]    BRA KET AND "x"\r
428 +[parse]  "x"\r
429 +[gen]    x:(pos=1)\r
430 +[xapian] error Syntax: <expression> AND <expression>\r
431 +\r
432 +# NULL phrases\r
433 +# These are all Xapian-incompatible because they're syntax errors.\r
434 +\r
435 +and\r
436 +[lex]    AND\r
437 +[parse]  <nil>\r
438 +[gen]    <alldocuments>\r
439 +[xapian] error Syntax: <expression> AND <expression>\r
440 +\r
441 +@\r
442 +[lex]    "@"\r
443 +[parse]  "@"\r
444 +[gen]    <alldocuments>\r
445 +[xapian] \r
446 +\r
447 +@ AND x\r
448 +[lex]    "@" AND "x"\r
449 +[parse]  (AND "@" "x")\r
450 +[gen]    x:(pos=1)\r
451 +[xapian] error Syntax: <expression> AND <expression>\r
452 +\r
453 +x AND @\r
454 +[lex]    "x" AND "@"\r
455 +[parse]  (AND "x" "@")\r
456 +[gen]    x:(pos=1)\r
457 +[xapian] error Syntax: <expression> AND <expression>\r
458 +\r
459 +@ AND NOT x\r
460 +[lex]    "@" AND NOT "x"\r
461 +[parse]  (AND "@" (NOT "x"))\r
462 +[gen]    (<alldocuments> AND_NOT x:(pos=1))\r
463 +[xapian] error Syntax: <expression> AND NOT <expression>\r
464 +\r
465 +x AND NOT @\r
466 +[lex]    "x" AND NOT "@"\r
467 +[parse]  (AND "x" (NOT "@"))\r
468 +[gen]    x:(pos=1)\r
469 +[xapian] error Syntax: <expression> AND NOT <expression>\r
470 +\r
471 +NOT @\r
472 +[lex]    NOT "@"\r
473 +[parse]  (NOT "@")\r
474 +[gen]    <alldocuments>\r
475 +[xapian] error Syntax: <expression> NOT <expression>\r
476 +\r
477 +@ OR x\r
478 +[lex]    "@" OR "x"\r
479 +[parse]  (OR "@" "x")\r
480 +[gen]    x:(pos=1)\r
481 +[xapian] error Syntax: <expression> OR <expression>\r
482 +\r
483 +x OR @\r
484 +[lex]    "x" OR "@"\r
485 +[parse]  (OR "x" "@")\r
486 +[gen]    x:(pos=1)\r
487 +[xapian] error Syntax: <expression> OR <expression>\r
488 diff --git a/test/qparser.expected-output/prefixes b/test/qparser.expected-output/prefixes\r
489 new file mode 100644\r
490 index 0000000..04c4f90\r
491 --- /dev/null\r
492 +++ b/test/qparser.expected-output/prefixes\r
493 @@ -0,0 +1,33 @@\r
494 +prob:x lit:y none:z\r
495 +[lex]    PREFIX/prob "x" PREFIX/lit "y" "none:z"\r
496 +[parse]  (AND (AND (PREFIX/prob "x") "none:z") (FILTER (PREFIX/lit 'y')))\r
497 +[gen]    ((Px:(pos=1) AND (none:(pos=2) PHRASE 2 z:(pos=3))) FILTER Ly)\r
498 +\r
499 +prob:"x y" lit:"x y" none:"x y"\r
500 +[lex]    PREFIX/prob "x y" PREFIX/lit "x y" "none:" "x y"\r
501 +[parse]  (AND (AND (AND (PREFIX/prob "x y") "none:") "x y") (FILTER (PREFIX/lit 'x y')))\r
502 +[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)\r
503 +\r
504 +# Incompatible; Xapian bails and re-parses everything with no flags\r
505 +prob:(x y) lit:(x y) none:(x y)\r
506 +[lex]    PREFIX/prob BRA "x" "y" KET PREFIX/lit "(x" "y" KET "none:" BRA "x" "y" KET\r
507 +[parse]  (AND (AND (AND (AND (PREFIX/prob (AND "x" "y")) "y") "none:") (AND "x" "y")) (FILTER (PREFIX/lit '(x')))\r
508 +[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)\r
509 +[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)\r
510 +\r
511 +# This is Xapian-compatible, but seems ridiculous\r
512 +lit:(x)\r
513 +[lex]    PREFIX/lit "(x" KET\r
514 +[parse]  (FILTER (PREFIX/lit '(x'))\r
515 +[gen]    0 * L(x\r
516 +\r
517 +# Test characters accepted after the prefix colon\r
518 +lit:#\r
519 +[lex]    PREFIX/lit "#"\r
520 +[parse]  (FILTER (PREFIX/lit '#'))\r
521 +[gen]    0 * L#\r
522 +\r
523 +prob:#\r
524 +[lex]    "prob:#"\r
525 +[parse]  "prob:#"\r
526 +[gen]    prob:(pos=1)\r
527 diff --git a/test/qparser.expected-output/probs b/test/qparser.expected-output/probs\r
528 new file mode 100644\r
529 index 0000000..3c166f7\r
530 --- /dev/null\r
531 +++ b/test/qparser.expected-output/probs\r
532 @@ -0,0 +1,46 @@\r
533 +(x OR y) AND z\r
534 +[lex]    BRA "x" OR "y" KET AND "z"\r
535 +[parse]  (AND (OR "x" "y") "z")\r
536 +[gen]    ((x:(pos=1) OR y:(pos=2)) AND z:(pos=3))\r
537 +\r
538 +# Incompatible; Xapian bails on the syntax error, we forge ahead.\r
539 +(x OR y)) AND z\r
540 +[lex]    BRA "x" OR "y" KET KET AND "z"\r
541 +[parse]  (AND (OR "x" "y") "z")\r
542 +[gen]    ((x:(pos=1) OR y:(pos=2)) AND z:(pos=3))\r
543 +[xapian] (x:(pos=1) AND or:(pos=2) AND y:(pos=3) AND and:(pos=4) AND z:(pos=5))\r
544 +\r
545 +# Empty subexpression after prefix\r
546 +# Incompatible; Xapian treats as a syntax error.\r
547 +prob:() AND x\r
548 +[lex]    PREFIX/prob BRA KET AND "x"\r
549 +[parse]  "x"\r
550 +[gen]    x:(pos=1)\r
551 +[xapian] (prob:(pos=1) AND and:(pos=2) AND x:(pos=3))\r
552 +\r
553 +# Subqueries with same boolean prefix\r
554 +lit:x lit:y\r
555 +[lex]    PREFIX/lit "x" PREFIX/lit "y"\r
556 +[parse]  (FILTER (OR (PREFIX/lit 'x') (PREFIX/lit 'y')))\r
557 +[gen]    0 * (Lx OR Ly)\r
558 +\r
559 +# Combining prob components\r
560 +x -y lit:z\r
561 +[lex]    "x" HATE "y" PREFIX/lit "z"\r
562 +[parse]  (AND (AND "x" (FILTER (PREFIX/lit 'z'))) (NOT "y"))\r
563 +[gen]    ((x:(pos=1) FILTER Lz) AND_NOT y:(pos=2))\r
564 +\r
565 +x lit:z\r
566 +[lex]    "x" PREFIX/lit "z"\r
567 +[parse]  (AND "x" (FILTER (PREFIX/lit 'z')))\r
568 +[gen]    (x:(pos=1) FILTER Lz)\r
569 +\r
570 +-y lit:z\r
571 +[lex]    HATE "y" PREFIX/lit "z"\r
572 +[parse]  (AND (FILTER (PREFIX/lit 'z')) (NOT "y"))\r
573 +[gen]    (0 * Lz AND_NOT y:(pos=1))\r
574 +\r
575 +x -y\r
576 +[lex]    "x" HATE "y"\r
577 +[parse]  (AND "x" (NOT "y"))\r
578 +[gen]    (x:(pos=1) AND_NOT y:(pos=2))\r
579 diff --git a/test/qparser.expected-output/quoted-phrases b/test/qparser.expected-output/quoted-phrases\r
580 new file mode 100644\r
581 index 0000000..e223366\r
582 --- /dev/null\r
583 +++ b/test/qparser.expected-output/quoted-phrases\r
584 @@ -0,0 +1,29 @@\r
585 +x "y z" w\r
586 +[lex]    "x" "y z" "w"\r
587 +[parse]  (AND (AND "x" "y z") "w")\r
588 +[gen]    (x:(pos=1) AND (y:(pos=2) PHRASE 2 z:(pos=3)) AND w:(pos=4))\r
589 +\r
590 +x "y z\r
591 +[lex]    "x" "y z"\r
592 +[parse]  (AND "x" "y z")\r
593 +[gen]    (x:(pos=1) AND (y:(pos=2) PHRASE 2 z:(pos=3)))\r
594 +\r
595 +x "" y\r
596 +[lex]    "x" "" "y"\r
597 +[parse]  (AND (AND "x" "") "y")\r
598 +[gen]    (x:(pos=1) AND y:(pos=2))\r
599 +\r
600 +x "  " y\r
601 +[lex]    "x" "  " "y"\r
602 +[parse]  (AND (AND "x" "  ") "y")\r
603 +[gen]    (x:(pos=1) AND y:(pos=2))\r
604 +\r
605 +lit:" x y"\r
606 +[lex]    PREFIX/lit " x y"\r
607 +[parse]  (FILTER (PREFIX/lit ' x y'))\r
608 +[gen]    0 * L x y\r
609 +\r
610 +lit:"x""y"\r
611 +[lex]    PREFIX/lit "x"y"\r
612 +[parse]  (FILTER (PREFIX/lit 'x"y'))\r
613 +[gen]    0 * Lx"y\r
614 diff --git a/test/qparser.expected-output/terms b/test/qparser.expected-output/terms\r
615 new file mode 100644\r
616 index 0000000..9316c54\r
617 --- /dev/null\r
618 +++ b/test/qparser.expected-output/terms\r
619 @@ -0,0 +1,136 @@\r
620 +# Term lexing\r
621 +\r
622 +x y z\r
623 +[lex]    "x" "y" "z"\r
624 +[parse]  (AND (AND "x" "y") "z")\r
625 +[gen]    (x:(pos=1) AND y:(pos=2) AND z:(pos=3))\r
626 +\r
627 +x"y z"w\r
628 +[lex]    "x" "y z" "w"\r
629 +[parse]  (AND (AND "x" "y z") "w")\r
630 +[gen]    (x:(pos=1) AND (y:(pos=2) PHRASE 2 z:(pos=3)) AND w:(pos=4))\r
631 +\r
632 +x(y z)w\r
633 +[lex]    "x" BRA "y" "z" KET "w"\r
634 +[parse]  (AND (AND "x" (AND "y" "z")) "w")\r
635 +[gen]    (x:(pos=1) AND y:(pos=2) AND z:(pos=3) AND w:(pos=4))\r
636 +\r
637 +# The first query below is Xapian-compatible, while the second one\r
638 +# isn't.  We use much simpler term lexing rules than Xapian.\r
639 +x/y\r
640 +[lex]    "x/y"\r
641 +[parse]  "x/y"\r
642 +[gen]    (x:(pos=1) PHRASE 2 y:(pos=2))\r
643 +\r
644 +x!y\r
645 +[lex]    "x!y"\r
646 +[parse]  "x!y"\r
647 +[gen]    (x:(pos=1) PHRASE 2 y:(pos=2))\r
648 +[xapian] (x:(pos=1) AND y:(pos=2))\r
649 +\r
650 +# Incompatible; our simpler term parsing sees ! as a term\r
651 +x -! y\r
652 +[lex]    "x" HATE "!" "y"\r
653 +[parse]  (AND (AND "x" "y") (NOT "!"))\r
654 +[gen]    (x:(pos=1) AND y:(pos=2))\r
655 +[xapian] (x:(pos=1) AND_NOT y:(pos=2))\r
656 +\r
657 +# Term parsing\r
658 +\r
659 +x -\r
660 +[lex]    "x" HATE\r
661 +[parse]  "x"\r
662 +[gen]    x:(pos=1)\r
663 +\r
664 +x +\r
665 +[lex]    "x" LOVE\r
666 +[parse]  "x"\r
667 +[gen]    x:(pos=1)\r
668 +\r
669 +(x)\r
670 +[lex]    BRA "x" KET\r
671 +[parse]  "x"\r
672 +[gen]    x:(pos=1)\r
673 +\r
674 +# Prefixed operators get demoted to terms\r
675 +prob:AND\r
676 +[lex]    PREFIX/prob AND\r
677 +[parse]  (PREFIX/prob "AND")\r
678 +[gen]    Pand:(pos=1)\r
679 +\r
680 +# The first query below is Xapian-compatible, but the second isn't\r
681 +# because Xapian handles hate very differently from love.\r
682 ++AND\r
683 +[lex]    LOVE AND\r
684 +[parse]  "AND"\r
685 +[gen]    and:(pos=1)\r
686 +\r
687 +-AND\r
688 +[lex]    HATE AND\r
689 +[parse]  (NOT "AND")\r
690 +[gen]    (<alldocuments> AND_NOT and:(pos=1))\r
691 +[xapian] and:(pos=1)\r
692 +\r
693 +# Incompatible; Xapian sees this as prob:"prob:x"\r
694 +prob:prob:x\r
695 +[lex]    PREFIX/prob PREFIX/prob "x"\r
696 +[parse]  (PREFIX/prob "x")\r
697 +[gen]    Px:(pos=1)\r
698 +[xapian] (Pprob:(pos=1) PHRASE 2 Px:(pos=2))\r
699 +\r
700 +# The rest are Xapian-incompatible because they're all considered\r
701 +# syntax errors\r
702 +(\r
703 +[lex]    BRA\r
704 +[parse]  <nil>\r
705 +[gen]    <alldocuments>\r
706 +[xapian] \r
707 +\r
708 +()\r
709 +[lex]    BRA KET\r
710 +[parse]  <nil>\r
711 +[gen]    <alldocuments>\r
712 +[xapian] \r
713 +\r
714 +)\r
715 +[lex]    KET\r
716 +[parse]  <nil>\r
717 +[gen]    <alldocuments>\r
718 +[xapian] \r
719 +\r
720 +(x)) OR y\r
721 +[lex]    BRA "x" KET KET OR "y"\r
722 +[parse]  (OR "x" "y")\r
723 +[gen]    (x:(pos=1) OR y:(pos=2))\r
724 +[xapian] (x:(pos=1) AND or:(pos=2) AND y:(pos=3))\r
725 +\r
726 +# This one's only Xapian-compatible by chance.\r
727 +(x))\r
728 +[lex]    BRA "x" KET KET\r
729 +[parse]  "x"\r
730 +[gen]    x:(pos=1)\r
731 +\r
732 +# Term generating\r
733 +\r
734 +c++ x\r
735 +[lex]    "c++" "x"\r
736 +[parse]  (AND "c++" "x")\r
737 +[gen]    (c++:(pos=1) AND x:(pos=2))\r
738 +\r
739 +# Incompatible; + is not a "phrase generator" in Xapian.\r
740 +c+x\r
741 +[lex]    "c+x"\r
742 +[parse]  "c+x"\r
743 +[gen]    (c:(pos=1) PHRASE 2 x:(pos=2))\r
744 +[xapian] (c:(pos=1) AND x:(pos=2))\r
745 +\r
746 +c-x\r
747 +[lex]    "c-x"\r
748 +[parse]  "c-x"\r
749 +[gen]    (c:(pos=1) PHRASE 2 x:(pos=2))\r
750 +\r
751 +w "x y z"\r
752 +[lex]    "w" "x y z"\r
753 +[parse]  (AND "w" "x y z")\r
754 +[gen]    (w:(pos=1) AND (x:(pos=2) PHRASE 3 y:(pos=3) PHRASE 3 z:(pos=4)))\r
755 +\r
756 -- \r
757 1.7.2.3\r
758 \r