summaryrefslogtreecommitdiff
path: root/src/query.cc
diff options
context:
space:
mode:
authorJohn Wiegley <johnw@newartisans.com>2009-11-08 23:40:42 -0500
committerJohn Wiegley <johnw@newartisans.com>2009-11-09 02:06:06 -0500
commitc3535d06c89732a0ba4c13274702b0f48198ae79 (patch)
tree5a8153d4c627cc3c7eff687b50a59a1cc9c3d04a /src/query.cc
parent523d4243e8c347cb7cbd1f68b03a5098ceb73b70 (diff)
downloadfork-ledger-c3535d06c89732a0ba4c13274702b0f48198ae79.tar.gz
fork-ledger-c3535d06c89732a0ba4c13274702b0f48198ae79.tar.bz2
fork-ledger-c3535d06c89732a0ba4c13274702b0f48198ae79.zip
Redesigned the expr_t, predicate_t, query_t classes
Diffstat (limited to 'src/query.cc')
-rw-r--r--src/query.cc456
1 files changed, 456 insertions, 0 deletions
diff --git a/src/query.cc b/src/query.cc
new file mode 100644
index 00000000..98a3de1d
--- /dev/null
+++ b/src/query.cc
@@ -0,0 +1,456 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "query.h"
+#include "op.h"
+
+namespace ledger {
+
+query_t::lexer_t::token_t query_t::lexer_t::next_token()
+{
+ if (token_cache.kind != token_t::UNKNOWN) {
+ token_t tok = token_cache;
+ token_cache = token_t();
+ return tok;
+ }
+
+ if (arg_i == arg_end) {
+ if (begin == end || ++begin == end) {
+ return token_t(token_t::END_REACHED);
+ } else {
+ arg_i = (*begin).as_string().begin();
+ arg_end = (*begin).as_string().end();
+ }
+ }
+
+ resume:
+ bool consume_next = false;
+ switch (*arg_i) {
+ case ' ':
+ case '\t':
+ case '\r':
+ case '\n':
+ if (++arg_i == arg_end)
+ return next_token();
+ goto resume;
+
+ case '/': {
+ string pat;
+ bool found_end_slash = false;
+ for (++arg_i; arg_i != arg_end; ++arg_i) {
+ if (*arg_i == '\\') {
+ if (++arg_i == arg_end)
+ throw_(parse_error, _("Unexpected '\\' at end of pattern"));
+ }
+ else if (*arg_i == '/') {
+ ++arg_i;
+ found_end_slash = true;
+ break;
+ }
+ pat.push_back(*arg_i);
+ }
+ if (! found_end_slash)
+ throw_(parse_error, _("Expected '/' at end of pattern"));
+ if (pat.empty())
+ throw_(parse_error, _("Match pattern is empty"));
+
+ return token_t(token_t::TERM, pat);
+ }
+
+ case '(': ++arg_i; return token_t(token_t::LPAREN);
+ case ')': ++arg_i; return token_t(token_t::RPAREN);
+ case '&': ++arg_i; return token_t(token_t::TOK_AND);
+ case '|': ++arg_i; return token_t(token_t::TOK_OR);
+ case '!': ++arg_i; return token_t(token_t::TOK_NOT);
+ case '@': ++arg_i; return token_t(token_t::TOK_PAYEE);
+ case '#': ++arg_i; return token_t(token_t::TOK_CODE);
+ case '%': ++arg_i; return token_t(token_t::TOK_META);
+ case '=':
+ // The '=' keyword at the beginning of a string causes the entire string
+ // to be taken as an expression.
+ if (arg_i == (*begin).as_string().begin())
+ consume_whitespace = true;
+ ++arg_i;
+ return token_t(token_t::TOK_EQ);
+
+ case '\\':
+ consume_next = true;
+ ++arg_i;
+ // fall through...
+ default: {
+ string ident;
+ string::const_iterator beg = arg_i;
+ for (; arg_i != arg_end; ++arg_i) {
+ switch (*arg_i) {
+ case ' ':
+ case '\t':
+ case '\n':
+ case '\r':
+ if (! consume_whitespace)
+ goto test_ident;
+ else
+ ident.push_back(*arg_i);
+ break;
+ case '(':
+ case ')':
+ case '&':
+ case '|':
+ case '!':
+ case '@':
+ case '#':
+ case '%':
+ case '=':
+ if (! consume_next)
+ goto test_ident;
+ // fall through...
+ default:
+ ident.push_back(*arg_i);
+ break;
+ }
+ }
+ consume_whitespace = false;
+
+ test_ident:
+ if (ident == "and")
+ return token_t(token_t::TOK_AND);
+ else if (ident == "or")
+ return token_t(token_t::TOK_OR);
+ else if (ident == "not")
+ return token_t(token_t::TOK_NOT);
+ else if (ident == "code")
+ return token_t(token_t::TOK_CODE);
+ else if (ident == "desc")
+ return token_t(token_t::TOK_PAYEE);
+ else if (ident == "payee")
+ return token_t(token_t::TOK_PAYEE);
+ else if (ident == "note")
+ return token_t(token_t::TOK_NOTE);
+ else if (ident == "account")
+ return token_t(token_t::TOK_ACCOUNT);
+ else if (ident == "tag")
+ return token_t(token_t::TOK_META);
+ else if (ident == "meta")
+ return token_t(token_t::TOK_META);
+ else if (ident == "data")
+ return token_t(token_t::TOK_META);
+ else if (ident == "show") {
+ // The "show" keyword is special, and separates a limiting predicate
+ // from a display predicate.
+ DEBUG("pred.show", "string = " << (*begin).as_string());
+ return token_t(token_t::END_REACHED);
+ }
+#if 0
+ // jww (2009-11-06): This is disabled for the time being.
+ else if (ident == "date") {
+ // The date keyword takes the whole of the next string as its argument.
+ consume_whitespace = true;
+ return token_t(token_t::TOK_DATE);
+ }
+#endif
+ else if (ident == "expr") {
+ // The expr keyword takes the whole of the next string as its argument.
+ consume_whitespace = true;
+ return token_t(token_t::TOK_EXPR);
+ }
+ else
+ return token_t(token_t::TERM, ident);
+ break;
+ }
+ }
+
+ return token_t(token_t::UNKNOWN);
+}
+
+void query_t::lexer_t::token_t::unexpected()
+{
+ kind_t prev_kind = kind;
+
+ kind = UNKNOWN;
+
+ switch (prev_kind) {
+ case END_REACHED:
+ throw_(parse_error, _("Unexpected end of expression"));
+ case TERM:
+ throw_(parse_error, _("Unexpected string '%1'") << *value);
+ default:
+ throw_(parse_error, _("Unexpected token '%1'") << symbol());
+ }
+}
+
+void query_t::lexer_t::token_t::expected(char wanted, char c)
+{
+ kind = UNKNOWN;
+
+ if (c == '\0' || c == -1) {
+ if (wanted == '\0' || wanted == -1)
+ throw_(parse_error, _("Unexpected end"));
+ else
+ throw_(parse_error, _("Missing '%1'") << wanted);
+ } else {
+ if (wanted == '\0' || wanted == -1)
+ throw_(parse_error, _("Invalid char '%1'") << c);
+ else
+ throw_(parse_error, _("Invalid char '%1' (wanted '%2')") << c << wanted);
+ }
+}
+
+expr_t::ptr_op_t
+query_t::parser_t::parse_query_term(query_t::lexer_t::token_t::kind_t tok_context)
+{
+ expr_t::ptr_op_t node;
+
+ lexer_t::token_t tok = lexer.next_token();
+ switch (tok.kind) {
+ case lexer_t::token_t::END_REACHED:
+ break;
+
+ case lexer_t::token_t::TOK_DATE:
+ case lexer_t::token_t::TOK_CODE:
+ case lexer_t::token_t::TOK_PAYEE:
+ case lexer_t::token_t::TOK_NOTE:
+ case lexer_t::token_t::TOK_ACCOUNT:
+ case lexer_t::token_t::TOK_META:
+ case lexer_t::token_t::TOK_EXPR:
+ node = parse_query_term(tok.kind);
+ if (! node)
+ throw_(parse_error,
+ _("%1 operator not followed by argument") << tok.symbol());
+ break;
+
+ case lexer_t::token_t::TERM:
+ assert(tok.value);
+ switch (tok_context) {
+ case lexer_t::token_t::TOK_DATE: {
+ expr_t::ptr_op_t ident = new expr_t::op_t(expr_t::op_t::IDENT);
+ ident->set_ident("date");
+
+ date_interval_t interval(*tok.value);
+
+ if (interval.start) {
+ node = new expr_t::op_t(expr_t::op_t::O_GTE);
+ node->set_left(ident);
+
+ expr_t::ptr_op_t arg1 = new expr_t::op_t(expr_t::op_t::VALUE);
+ arg1->set_value(*interval.start);
+ node->set_right(arg1);
+ }
+
+ if (interval.end) {
+ expr_t::ptr_op_t lt = new expr_t::op_t(expr_t::op_t::O_LT);
+ lt->set_left(ident);
+
+ expr_t::ptr_op_t arg1 = new expr_t::op_t(expr_t::op_t::VALUE);
+ arg1->set_value(*interval.end);
+ lt->set_right(arg1);
+
+ if (node) {
+ expr_t::ptr_op_t prev(node);
+ node = new expr_t::op_t(expr_t::op_t::O_AND);
+ node->set_left(prev);
+ node->set_right(lt);
+ } else {
+ node = lt;
+ }
+ }
+ break;
+ }
+
+ case lexer_t::token_t::TOK_EXPR:
+ node = expr_t(*tok.value).get_op();
+ break;
+
+ case lexer_t::token_t::TOK_META: {
+ node = new expr_t::op_t(expr_t::op_t::O_CALL);
+
+ expr_t::ptr_op_t ident = new expr_t::op_t(expr_t::op_t::IDENT);
+ ident->set_ident("has_tag");
+ node->set_left(ident);
+
+ expr_t::ptr_op_t arg1 = new expr_t::op_t(expr_t::op_t::VALUE);
+ arg1->set_value(mask_t(*tok.value));
+
+ tok = lexer.peek_token();
+ if (tok.kind == lexer_t::token_t::TOK_EQ) {
+ tok = lexer.next_token();
+ tok = lexer.next_token();
+ if (tok.kind != lexer_t::token_t::TERM)
+ throw_(parse_error,
+ _("Metadata equality operator not followed by term"));
+
+ expr_t::ptr_op_t cons = new expr_t::op_t(expr_t::op_t::O_CONS);
+
+ expr_t::ptr_op_t arg2 = new expr_t::op_t(expr_t::op_t::VALUE);
+ assert(tok.value);
+ arg2->set_value(mask_t(*tok.value));
+
+ cons->set_left(arg1);
+ cons->set_right(arg2);
+ node->set_right(cons);
+ } else {
+ node->set_right(arg1);
+ }
+ break;
+ }
+
+ default: {
+ node = new expr_t::op_t(expr_t::op_t::O_MATCH);
+
+ expr_t::ptr_op_t ident = new expr_t::op_t(expr_t::op_t::IDENT);
+ switch (tok_context) {
+ case lexer_t::token_t::TOK_ACCOUNT:
+ ident->set_ident("account"); break;
+ case lexer_t::token_t::TOK_PAYEE:
+ ident->set_ident("payee"); break;
+ case lexer_t::token_t::TOK_CODE:
+ ident->set_ident("code"); break;
+ case lexer_t::token_t::TOK_NOTE:
+ ident->set_ident("note"); break;
+ default:
+ assert(0); break;
+ }
+
+ expr_t::ptr_op_t mask = new expr_t::op_t(expr_t::op_t::VALUE);
+ mask->set_value(mask_t(*tok.value));
+
+ node->set_left(ident);
+ node->set_right(mask);
+ }
+ }
+ break;
+
+ case lexer_t::token_t::LPAREN:
+ node = parse_query_expr(tok_context);
+ tok = lexer.next_token();
+ if (tok.kind != lexer_t::token_t::RPAREN)
+ tok.expected(')');
+ break;
+
+ default:
+ lexer.push_token(tok);
+ break;
+ }
+
+ return node;
+}
+
+expr_t::ptr_op_t
+query_t::parser_t::parse_unary_expr(lexer_t::token_t::kind_t tok_context)
+{
+ expr_t::ptr_op_t node;
+
+ lexer_t::token_t tok = lexer.next_token();
+ switch (tok.kind) {
+ case lexer_t::token_t::TOK_NOT: {
+ expr_t::ptr_op_t term(parse_query_term(tok_context));
+ if (! term)
+ throw_(parse_error,
+ _("%1 operator not followed by argument") << tok.symbol());
+
+ node = new expr_t::op_t(expr_t::op_t::O_NOT);
+ node->set_left(term);
+ break;
+ }
+
+ default:
+ lexer.push_token(tok);
+ node = parse_query_term(tok_context);
+ break;
+ }
+
+ return node;
+}
+
+expr_t::ptr_op_t
+query_t::parser_t::parse_and_expr(lexer_t::token_t::kind_t tok_context)
+{
+ if (expr_t::ptr_op_t node = parse_unary_expr(tok_context)) {
+ while (true) {
+ lexer_t::token_t tok = lexer.next_token();
+ if (tok.kind == lexer_t::token_t::TOK_AND) {
+ expr_t::ptr_op_t prev(node);
+ node = new expr_t::op_t(expr_t::op_t::O_AND);
+ node->set_left(prev);
+ node->set_right(parse_unary_expr(tok_context));
+ if (! node->right())
+ throw_(parse_error,
+ _("%1 operator not followed by argument") << tok.symbol());
+ } else {
+ lexer.push_token(tok);
+ break;
+ }
+ }
+ return node;
+ }
+ return expr_t::ptr_op_t();
+}
+
+expr_t::ptr_op_t
+query_t::parser_t::parse_or_expr(lexer_t::token_t::kind_t tok_context)
+{
+ if (expr_t::ptr_op_t node = parse_and_expr(tok_context)) {
+ while (true) {
+ lexer_t::token_t tok = lexer.next_token();
+ if (tok.kind == lexer_t::token_t::TOK_OR) {
+ expr_t::ptr_op_t prev(node);
+ node = new expr_t::op_t(expr_t::op_t::O_OR);
+ node->set_left(prev);
+ node->set_right(parse_and_expr(tok_context));
+ if (! node->right())
+ throw_(parse_error,
+ _("%1 operator not followed by argument") << tok.symbol());
+ } else {
+ lexer.push_token(tok);
+ break;
+ }
+ }
+ return node;
+ }
+ return expr_t::ptr_op_t();
+}
+
+expr_t::ptr_op_t
+query_t::parser_t::parse_query_expr(lexer_t::token_t::kind_t tok_context)
+{
+ if (expr_t::ptr_op_t node = parse_or_expr(tok_context)) {
+ if (expr_t::ptr_op_t next = parse_query_expr(tok_context)) {
+ expr_t::ptr_op_t prev(node);
+ node = new expr_t::op_t(expr_t::op_t::O_OR);
+ node->set_left(prev);
+ node->set_right(next);
+ }
+ return node;
+ }
+ return expr_t::ptr_op_t();
+}
+
+} // namespace ledger