I'm done with client work for a bit and back hacking on Bricolage 2.0. I'm inordinately proud of finally getting this to work:
is_deeply [(NOT BETWEEN [2,4])->()], [ [KEYWORD => 'NOT'], [KEYWORD => 'BETWEEN'], [OP => '['], [VALUE => 2], [OP => ','], [VALUE => 4], [OP => ']'], ], '... even if it is negated';
The NOT BETWEEN [2,4] is a little code snippet of "helper" subs that one module exports on demand. Those helper subs automatically lex themselves into the same tokens that the regular lexer will convert "NOT BETWEEN [2,4]" into. This way, I use the same parser with both code and strings. Why is this important? I can't exactly export the helper subs to JavaScript, so AJAX calls will need to use the string version.
The upshot of all of this is that regardless of whether you are issuing searches from Javascript, Perl, or something else entirely, there's really only one interface to memorize and we get to reuse all of the search code.