From 2aef4f8884d96375a506f90fe163d90d6346b43c Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Wed, 28 Oct 2009 18:38:33 -0400 Subject: Render an expr if expr_t::text() has no string --- src/expr.cc | 10 ++++++++++ src/expr.h | 5 +---- 2 files changed, 11 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/expr.cc b/src/expr.cc index 89214d51..4f12a648 100644 --- a/src/expr.cc +++ b/src/expr.cc @@ -78,6 +78,16 @@ expr_t::ptr_op_t expr_t::get_op() throw() return ptr; } +string expr_t::text() +{ + if (str.empty()) { + std::ostringstream out; + ptr->print(out); + set_text(out.str()); + } + return str; +} + expr_t& expr_t::operator=(const expr_t& _expr) { if (this != &_expr) { diff --git a/src/expr.h b/src/expr.h index 24276cd1..b880fd79 100644 --- a/src/expr.h +++ b/src/expr.h @@ -116,10 +116,7 @@ public: } ptr_op_t get_op() throw(); - - string text() const throw() { - return str; - } + string text(); // This has special use in the textual parser void set_text(const string& txt) { -- cgit v1.2.3 From c26daee2cf05329e1d7ea6db4aad34210303a272 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Wed, 28 Oct 2009 18:39:09 -0400 Subject: Pushing null values into a sequence is legitimate --- src/interactive.cc | 36 ++++++++++++++++++++---------------- src/value.h | 20 ++++++-------------- 2 files changed, 26 insertions(+), 30 deletions(-) (limited to 'src') diff --git a/src/interactive.cc b/src/interactive.cc index 1ae976e7..d2d6256b 100644 --- a/src/interactive.cc +++ b/src/interactive.cc @@ -62,6 +62,8 @@ void interactive_t::verify_arguments() const for (; ! wrong_arg && ! exit_loop && *p && next_arg; p++) { DEBUG("interactive.verify", "Want " << *p << " got: " << next_arg->label()); + + wrong_arg = false; switch (*p) { case 'a': label = _("an amount"); @@ -86,24 +88,24 @@ void interactive_t::verify_arguments() const case 'i': case 'l': label = _("an integer"); - if (next_arg->is_long() || - (next_arg->is_amount() && - ! next_arg->as_amount().has_commodity())) { - wrong_arg = false; - } - else if (next_arg->is_string()) { - wrong_arg = false; - for (const char * q = next_arg->as_string().c_str(); *q; q++) { - if (! std::isdigit(*q) && *q != '-') { - wrong_arg = true; - break; - } + if (next_arg->is_long() || + (next_arg->is_amount() && + ! next_arg->as_amount().has_commodity())) { + wrong_arg = false; + } + else if (next_arg->is_string()) { + wrong_arg = false; + for (const char * q = next_arg->as_string().c_str(); *q; q++) { + if (! std::isdigit(*q) && *q != '-') { + wrong_arg = true; + break; } } - else { - wrong_arg = true; - } - break; + } + else { + wrong_arg = true; + } + break; case 'm': label = _("a regex"); wrong_arg = ! next_arg->is_mask(); @@ -134,6 +136,8 @@ void interactive_t::verify_arguments() const dont_skip = true; break; } + if (wrong_arg && optional && next_arg->is_null()) + wrong_arg = false; if (wrong_arg) vlabel = next_arg->label(); diff --git a/src/value.h b/src/value.h index 9aa3cb04..31850894 100644 --- a/src/value.h +++ b/src/value.h @@ -818,13 +818,11 @@ public: } void push_back(const value_t& val) { - if (! val.is_null()) { - if (is_null()) - *this = sequence_t(); - if (! is_sequence()) - in_place_cast(SEQUENCE); - as_sequence_lval().push_back(val); - } + if (is_null()) + *this = sequence_t(); + if (! is_sequence()) + in_place_cast(SEQUENCE); + as_sequence_lval().push_back(val); } void pop_back() { @@ -855,24 +853,18 @@ public: } sequence_t::iterator begin() { - VERIFY(is_sequence()); return as_sequence_lval().begin(); } sequence_t::iterator end() { - VERIFY(is_sequence()); - // This special hack is because we never used end() in a context which - // needs us to call _dup(). - return boost::get(storage->data)->end(); + return as_sequence_lval().end(); } sequence_t::const_iterator begin() const { - VERIFY(is_sequence()); return as_sequence().begin(); } sequence_t::const_iterator end() const { - VERIFY(is_sequence()); return as_sequence().end(); } -- cgit v1.2.3 From 9408f3cbae5027734fe9b22ba3855e209d192eb1 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Wed, 28 Oct 2009 18:39:17 -0400 Subject: Changed some debug text --- src/token.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/token.h b/src/token.h index a9b75dd4..50d2bf72 100644 --- a/src/token.h +++ b/src/token.h @@ -108,10 +108,10 @@ struct expr_t::token_t : public noncopyable std::size_t length; explicit token_t() : kind(UNKNOWN), length(0) { - TRACE_CTOR(token_t, ""); + TRACE_CTOR(expr_t::token_t, ""); } ~token_t() throw() { - TRACE_DTOR(token_t); + TRACE_DTOR(expr_t::token_t); } token_t& operator=(const token_t& other) { -- cgit v1.2.3 From 47df7dd60e9209db3be91a7b29a91911ee4a846b Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Wed, 28 Oct 2009 18:40:31 -0400 Subject: Rewrote the report query parser It is now a full parser that parses report queries directly into value expression trees. These then get rendered into text so that other options may extend the expression. --- src/precmd.cc | 4 +- src/predicate.cc | 449 +++++++++++++++++++++++++++++++++++----------------- src/predicate.h | 156 +++++++++++++++++- src/report.cc | 4 +- test/unit/t_expr.cc | 275 ++++++++++++++++++++++++++++++++ test/unit/t_expr.h | 32 ++++ 6 files changed, 767 insertions(+), 153 deletions(-) (limited to 'src') diff --git a/src/precmd.cc b/src/precmd.cc index 1160cc64..999261fa 100644 --- a/src/precmd.cc +++ b/src/precmd.cc @@ -226,7 +226,7 @@ value_t args_command(call_scope_t& args) args.value().dump(out); out << std::endl << std::endl; - string predicate = args_to_predicate_expr(begin, end); + string predicate = args_to_predicate(begin, end).text(); call_scope_t sub_args(static_cast(args)); sub_args.push_back(string_value(predicate)); @@ -237,7 +237,7 @@ value_t args_command(call_scope_t& args) out << std::endl << _("====== Display predicate ======") << std::endl << std::endl; - predicate = args_to_predicate_expr(begin, end); + predicate = args_to_predicate(begin, end).text(); call_scope_t disp_sub_args(static_cast(args)); disp_sub_args.push_back(string_value(predicate)); diff --git a/src/predicate.cc b/src/predicate.cc index ce71a180..4f712904 100644 --- a/src/predicate.cc +++ b/src/predicate.cc @@ -32,175 +32,330 @@ #include #include "predicate.h" +#include "op.h" namespace ledger { -string args_to_predicate_expr(value_t::sequence_t::const_iterator& begin, - value_t::sequence_t::const_iterator end) +query_lexer_t::token_t query_lexer_t::next_token() { - std::ostringstream expr; - - bool append_or = false; - bool only_parenthesis = false; - - while (begin != end) { - string arg = (*begin).as_string(); - string prefix; + if (token_cache.kind != token_t::UNKNOWN) { + token_t tok = token_cache; + token_cache = token_t(); + return tok; + } - if (arg == "show") { - ++begin; - break; + 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(); } + } - bool parse_argument = true; - bool only_closed_parenthesis = false;; + 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; - if (arg == "not" || arg == "NOT") { - if (append_or) - prefix = " | ! "; - else - prefix = " ! "; - parse_argument = false; - append_or = false; - } - else if (arg == "and" || arg == "AND") { - prefix = " & "; - parse_argument = false; - append_or = false; - } - else if (arg == "or" || arg == "OR") { - prefix = " | "; - parse_argument = false; - append_or = false; - } - else if (append_or) { - if (! only_parenthesis) - prefix = " | "; + 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; + } } - else { - append_or = true; + 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 == "account") + return token_t(token_t::TOK_ACCOUNT); + 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 == "code") + return token_t(token_t::TOK_CODE); + else if (ident == "note") + return token_t(token_t::TOK_NOT); + 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 == "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; + } + } - value_t::sequence_t::const_iterator next = begin; - if (++next != end) { - if (arg == "desc" || arg == "DESC" || - arg == "payee" || arg == "PAYEE") { - arg = string("@") + (*++begin).as_string(); - } - else if (arg == "code" || arg == "CODE") { - arg = string("#") + (*++begin).as_string(); - } - else if (arg == "note" || arg == "NOTE") { - arg = string("&") + (*++begin).as_string(); - } - else if (arg == "tag" || arg == "TAG" || - arg == "meta" || arg == "META" || - arg == "data" || arg == "DATA") { - arg = string("%") + (*++begin).as_string(); - } - else if (arg == "expr" || arg == "EXPR") { - arg = string("=") + (*++begin).as_string(); + return token_t(token_t::UNKNOWN); +} + +void query_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_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_parser_t::parse_query_term(query_lexer_t::token_t::kind_t tok_context) +{ + expr_t::ptr_op_t node; + + query_lexer_t::token_t tok = lexer.next_token(); + switch (tok.kind) { + case query_lexer_t::token_t::END_REACHED: + break; + + case query_lexer_t::token_t::TOK_ACCOUNT: + case query_lexer_t::token_t::TOK_PAYEE: + case query_lexer_t::token_t::TOK_CODE: + case query_lexer_t::token_t::TOK_NOTE: + case query_lexer_t::token_t::TOK_META: + case query_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 query_lexer_t::token_t::TERM: + assert(tok.value); + if (tok_context == query_lexer_t::token_t::TOK_META) { + assert(0); + } else { + node = new expr_t::op_t(expr_t::op_t::O_MATCH); + + expr_t::ptr_op_t ident; + ident = new expr_t::op_t(expr_t::op_t::IDENT); + switch (tok_context) { + case query_lexer_t::token_t::TOK_ACCOUNT: + ident->set_ident("account"); break; + case query_lexer_t::token_t::TOK_PAYEE: + ident->set_ident("payee"); break; + case query_lexer_t::token_t::TOK_CODE: + ident->set_ident("code"); break; + case query_lexer_t::token_t::TOK_NOTE: + ident->set_ident("note"); break; + default: + assert(0); break; } + + expr_t::ptr_op_t mask; + 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; - if (parse_argument) { - bool in_prefix = true; - bool found_specifier = false; - bool no_final_slash = false; - - only_parenthesis = true; - - std::ostringstream buf; - string parens; - - for (const char * c = arg.c_str(); *c != '\0'; c++) { - bool consumed = false; - - if (*c != '(' && *c != ')') - only_parenthesis = false; - - if (in_prefix) { - switch (*c) { - case ')': - if (only_parenthesis) - only_closed_parenthesis = true; - // fall through... - case '(': - parens += c; - consumed = true; - break; - case '@': - buf << "(payee =~ /"; - found_specifier = true; - consumed = true; - break; - case '#': - buf << "(code =~ /"; - found_specifier = true; - consumed = true; - break; - case '=': - buf << "("; - found_specifier = true; - no_final_slash = true; - consumed = true; - break; - case '&': - buf << "(note =~ /"; - found_specifier = true; - consumed = true; - break; - case '%': { - bool found_metadata = false; - for (const char *q = c; *q != '\0'; q++) - if (*q == '=') { - buf << "has_tag(/" - << string(c + 1, q - c - 1) << "/, /"; - found_metadata = true; - c = q; - break; - } - if (! found_metadata) { - buf << "has_tag(/"; - } - found_specifier = true; - consumed = true; - break; - } - default: - if (! found_specifier) { - buf << parens << "(account =~ /"; - parens.clear(); - found_specifier = true; - } - in_prefix = false; - break; - } - } - - if (! consumed) - buf << *c; - } + case query_lexer_t::token_t::LPAREN: + node = parse_query_expr(tok_context); + tok = lexer.next_token(); + if (tok.kind != query_lexer_t::token_t::RPAREN) + tok.expected(')'); + break; - if (! prefix.empty() && - ! (only_parenthesis && only_closed_parenthesis)) - expr << prefix; + default: + lexer.push_token(tok); + break; + } + + return node; +} - expr << parens << buf.str(); +expr_t::ptr_op_t +query_parser_t::parse_unary_expr(query_lexer_t::token_t::kind_t tok_context) +{ + expr_t::ptr_op_t node; - if (found_specifier) { - if (! no_final_slash) - expr << "/"; - expr << ")"; + query_lexer_t::token_t tok = lexer.next_token(); + switch (tok.kind) { + case query_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_parser_t::parse_and_expr(query_lexer_t::token_t::kind_t tok_context) +{ + if (expr_t::ptr_op_t node = parse_unary_expr(tok_context)) { + while (true) { + query_lexer_t::token_t tok = lexer.next_token(); + if (tok.kind == query_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_parser_t::parse_or_expr(query_lexer_t::token_t::kind_t tok_context) +{ + if (expr_t::ptr_op_t node = parse_and_expr(tok_context)) { + while (true) { + query_lexer_t::token_t tok = lexer.next_token(); + if (tok.kind == query_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; } - } else { - expr << prefix; } + return node; + } + return expr_t::ptr_op_t(); +} - begin++; +expr_t::ptr_op_t +query_parser_t::parse_query_expr(query_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(); +} - return std::string("(") + expr.str() + ")"; +expr_t::ptr_op_t query_parser_t::parse() +{ + return parse_query_expr(query_lexer_t::token_t::TOK_ACCOUNT); +} + +expr_t args_to_predicate(value_t::sequence_t::const_iterator& begin, + value_t::sequence_t::const_iterator end) +{ + query_parser_t parser(begin, end); + return expr_t(parser.parse()); } } // namespace ledger diff --git a/src/predicate.h b/src/predicate.h index 3e9fc6b1..e1048f83 100644 --- a/src/predicate.h +++ b/src/predicate.h @@ -96,8 +96,160 @@ public: } }; -string args_to_predicate_expr(value_t::sequence_t::const_iterator& begin, - value_t::sequence_t::const_iterator end); +class query_lexer_t +{ + value_t::sequence_t::const_iterator begin; + value_t::sequence_t::const_iterator end; + + string::const_iterator arg_i; + string::const_iterator arg_end; + + bool consume_whitespace; + +public: + struct token_t + { + enum kind_t { + UNKNOWN, + + LPAREN, + RPAREN, + + TOK_NOT, + TOK_AND, + TOK_OR, + TOK_EQ, + + TOK_ACCOUNT, + TOK_PAYEE, + TOK_CODE, + TOK_NOTE, + TOK_META, + TOK_EXPR, + + TERM, + + END_REACHED + + } kind; + + optional value; + + explicit token_t(kind_t _kind = UNKNOWN, + const optional& _value = none) + : kind(_kind), value(_value) { + TRACE_CTOR(query_lexer_t::token_t, ""); + } + token_t(const token_t& tok) + : kind(tok.kind), value(tok.value) { + TRACE_CTOR(query_lexer_t::token_t, "copy"); + } + ~token_t() throw() { + TRACE_DTOR(query_lexer_t::token_t); + } + + token_t& operator=(const token_t& tok) { + if (this != &tok) { + kind = tok.kind; + value = tok.value; + } + return *this; + } + + operator bool() const { + return kind != END_REACHED; + } + + string to_string() const { + switch (kind) { + case UNKNOWN: return "UNKNOWN"; + case LPAREN: return "LPAREN"; + case RPAREN: return "RPAREN"; + case TOK_NOT: return "TOK_NOT"; + case TOK_AND: return "TOK_AND"; + case TOK_OR: return "TOK_OR"; + case TOK_EQ: return "TOK_EQ"; + case TOK_ACCOUNT: return "TOK_ACCOUNT"; + case TOK_PAYEE: return "TOK_PAYEE"; + case TOK_CODE: return "TOK_CODE"; + case TOK_NOTE: return "TOK_NOTE"; + case TOK_META: return "TOK_META"; + case TOK_EXPR: return "TOK_EXPR"; + case TERM: return string("TERM(") + *value + ")"; + case END_REACHED: return "END_REACHED"; + } + } + + string symbol() const { + switch (kind) { + case LPAREN: return "("; + case RPAREN: return ")"; + case TOK_NOT: return "not"; + case TOK_AND: return "and"; + case TOK_OR: return "or"; + case TOK_EQ: return "="; + case TOK_ACCOUNT: return "account"; + case TOK_PAYEE: return "payee"; + case TOK_CODE: return "code"; + case TOK_NOTE: return "note"; + case TOK_META: return "meta"; + case TOK_EXPR: return "expr"; + + case END_REACHED: return ""; + + case TERM: + assert(0); + return ""; + + case UNKNOWN: + default: + assert(0); + return ""; + } + } + + void unexpected(); + void expected(char wanted, char c = '\0'); + }; + + token_t token_cache; + + query_lexer_t(value_t::sequence_t::const_iterator _begin, + value_t::sequence_t::const_iterator _end) + : begin(_begin), end(_end), consume_whitespace(false) + { + assert(begin != end); + arg_i = (*begin).as_string().begin(); + arg_end = (*begin).as_string().end(); + } + + token_t next_token(); + void push_token(token_t tok) { + assert(token_cache.kind == token_t::UNKNOWN); + token_cache = tok; + } +}; + +class query_parser_t +{ + query_lexer_t lexer; + + expr_t::ptr_op_t parse_query_term(query_lexer_t::token_t::kind_t tok_context); + expr_t::ptr_op_t parse_unary_expr(query_lexer_t::token_t::kind_t tok_context); + expr_t::ptr_op_t parse_and_expr(query_lexer_t::token_t::kind_t tok_context); + expr_t::ptr_op_t parse_or_expr(query_lexer_t::token_t::kind_t tok_context); + expr_t::ptr_op_t parse_query_expr(query_lexer_t::token_t::kind_t tok_context); + +public: + query_parser_t(value_t::sequence_t::const_iterator begin, + value_t::sequence_t::const_iterator end) + : lexer(begin, end) {} + + expr_t::ptr_op_t parse(); +}; + +expr_t args_to_predicate(value_t::sequence_t::const_iterator& begin, + value_t::sequence_t::const_iterator end); } // namespace ledger diff --git a/src/report.cc b/src/report.cc index 62b54ad1..febe43e3 100644 --- a/src/report.cc +++ b/src/report.cc @@ -380,7 +380,7 @@ namespace { value_t::sequence_t::const_iterator end = args.value().as_sequence().end(); - string limit = args_to_predicate_expr(begin, end); + string limit = args_to_predicate(begin, end).text(); if (! limit.empty()) report.HANDLER(limit_).on(whence, limit); @@ -390,7 +390,7 @@ namespace { string display; if (begin != end) - display = args_to_predicate_expr(begin, end); + display = args_to_predicate(begin, end).text(); if (! display.empty()) report.HANDLER(display_).on(whence, display); diff --git a/test/unit/t_expr.cc b/test/unit/t_expr.cc index 6c88a08a..970706a2 100644 --- a/test/unit/t_expr.cc +++ b/test/unit/t_expr.cc @@ -3,6 +3,7 @@ #include "t_expr.h" #include "expr.h" +#include "predicate.h" using namespace ledger; @@ -19,3 +20,277 @@ void ValueExprTestCase::tearDown() amount_t::shutdown(); times_shutdown(); } + +// 1. foo and bar +// 2. 'foo and bar' +// 3. (foo and bar) +// 4. ( foo and bar ) +// 5. '( foo and' bar) +// 6. =foo and bar +// 7. ='foo and bar' +// 8. 'expr foo and bar' +// 9. expr 'foo and bar' +// 10. expr foo and bar +// 11. foo and bar or baz +// 12. foo and bar | baz +// 13. foo and bar |baz +// 14. foo and bar| baz +// 15. foo and bar|baz +// 16. foo 'and bar|baz' + +void ValueExprTestCase::testPredicateTokenizer1() +{ + value_t args; + args.push_back(string_value("foo")); + args.push_back(string_value("and")); + args.push_back(string_value("bar")); + + query_lexer_t tokens(args.begin(), args.end()); + + assertEqual(query_lexer_t::token_t::TERM, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::TOK_AND, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::TERM, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::END_REACHED, tokens.next_token().kind); +} + +void ValueExprTestCase::testPredicateTokenizer2() +{ + value_t args; + args.push_back(string_value("foo and bar")); + + query_lexer_t tokens(args.begin(), args.end()); + + assertEqual(query_lexer_t::token_t::TERM, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::TOK_AND, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::TERM, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::END_REACHED, tokens.next_token().kind); +} + +void ValueExprTestCase::testPredicateTokenizer3() +{ + value_t args; + args.push_back(string_value("(foo")); + args.push_back(string_value("and")); + args.push_back(string_value("bar)")); + + query_lexer_t tokens(args.begin(), args.end()); + + assertEqual(query_lexer_t::token_t::LPAREN, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::TERM, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::TOK_AND, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::TERM, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::RPAREN, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::END_REACHED, tokens.next_token().kind); +} + +void ValueExprTestCase::testPredicateTokenizer4() +{ + value_t args; + args.push_back(string_value("(")); + args.push_back(string_value("foo")); + args.push_back(string_value("and")); + args.push_back(string_value("bar")); + args.push_back(string_value(")")); + + query_lexer_t tokens(args.begin(), args.end()); + + assertEqual(query_lexer_t::token_t::LPAREN, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::TERM, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::TOK_AND, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::TERM, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::RPAREN, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::END_REACHED, tokens.next_token().kind); +} + +void ValueExprTestCase::testPredicateTokenizer5() +{ + value_t args; + args.push_back(string_value("( foo and")); + args.push_back(string_value("bar)")); + + query_lexer_t tokens(args.begin(), args.end()); + + assertEqual(query_lexer_t::token_t::LPAREN, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::TERM, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::TOK_AND, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::TERM, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::RPAREN, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::END_REACHED, tokens.next_token().kind); +} + +void ValueExprTestCase::testPredicateTokenizer6() +{ + value_t args; + args.push_back(string_value("=foo")); + args.push_back(string_value("and")); + args.push_back(string_value("bar")); + + query_lexer_t tokens(args.begin(), args.end()); + + assertEqual(query_lexer_t::token_t::TOK_EQ, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::TERM, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::TOK_AND, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::TERM, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::END_REACHED, tokens.next_token().kind); +} + +void ValueExprTestCase::testPredicateTokenizer7() +{ + value_t args; + args.push_back(string_value("=foo and bar")); + + query_lexer_t tokens(args.begin(), args.end()); + + assertEqual(query_lexer_t::token_t::TOK_EQ, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::TERM, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::END_REACHED, tokens.next_token().kind); +} + +void ValueExprTestCase::testPredicateTokenizer8() +{ + value_t args; + args.push_back(string_value("expr foo and bar")); + + query_lexer_t tokens(args.begin(), args.end()); + + assertEqual(query_lexer_t::token_t::TOK_EXPR, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::TERM, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::END_REACHED, tokens.next_token().kind); +} + +void ValueExprTestCase::testPredicateTokenizer9() +{ + value_t args; + args.push_back(string_value("expr")); + args.push_back(string_value("foo and bar")); + + query_lexer_t tokens(args.begin(), args.end()); + + assertEqual(query_lexer_t::token_t::TOK_EXPR, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::TERM, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::END_REACHED, tokens.next_token().kind); +} + +void ValueExprTestCase::testPredicateTokenizer10() +{ + value_t args; + args.push_back(string_value("expr")); + args.push_back(string_value("foo")); + args.push_back(string_value("and")); + args.push_back(string_value("bar")); + + query_lexer_t tokens(args.begin(), args.end()); + + assertEqual(query_lexer_t::token_t::TOK_EXPR, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::TERM, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::TOK_AND, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::TERM, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::END_REACHED, tokens.next_token().kind); +} + +void ValueExprTestCase::testPredicateTokenizer11() +{ + value_t args; + args.push_back(string_value("foo")); + args.push_back(string_value("and")); + args.push_back(string_value("bar")); + args.push_back(string_value("or")); + args.push_back(string_value("baz")); + + query_lexer_t tokens(args.begin(), args.end()); + + assertEqual(query_lexer_t::token_t::TERM, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::TOK_AND, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::TERM, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::TOK_OR, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::TERM, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::END_REACHED, tokens.next_token().kind); +} + +void ValueExprTestCase::testPredicateTokenizer12() +{ + value_t args; + args.push_back(string_value("foo")); + args.push_back(string_value("and")); + args.push_back(string_value("bar")); + args.push_back(string_value("|")); + args.push_back(string_value("baz")); + + query_lexer_t tokens(args.begin(), args.end()); + + assertEqual(query_lexer_t::token_t::TERM, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::TOK_AND, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::TERM, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::TOK_OR, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::TERM, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::END_REACHED, tokens.next_token().kind); +} + +void ValueExprTestCase::testPredicateTokenizer13() +{ + value_t args; + args.push_back(string_value("foo")); + args.push_back(string_value("and")); + args.push_back(string_value("bar")); + args.push_back(string_value("|baz")); + + query_lexer_t tokens(args.begin(), args.end()); + + assertEqual(query_lexer_t::token_t::TERM, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::TOK_AND, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::TERM, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::TOK_OR, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::TERM, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::END_REACHED, tokens.next_token().kind); +} + +void ValueExprTestCase::testPredicateTokenizer14() +{ + value_t args; + args.push_back(string_value("foo")); + args.push_back(string_value("and")); + args.push_back(string_value("bar|")); + args.push_back(string_value("baz")); + + query_lexer_t tokens(args.begin(), args.end()); + + assertEqual(query_lexer_t::token_t::TERM, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::TOK_AND, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::TERM, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::TOK_OR, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::TERM, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::END_REACHED, tokens.next_token().kind); +} + +void ValueExprTestCase::testPredicateTokenizer15() +{ + value_t args; + args.push_back(string_value("foo")); + args.push_back(string_value("and")); + args.push_back(string_value("bar|baz")); + + query_lexer_t tokens(args.begin(), args.end()); + + assertEqual(query_lexer_t::token_t::TERM, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::TOK_AND, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::TERM, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::TOK_OR, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::TERM, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::END_REACHED, tokens.next_token().kind); +} + +void ValueExprTestCase::testPredicateTokenizer16() +{ + value_t args; + args.push_back(string_value("foo")); + args.push_back(string_value("and bar|baz")); + + query_lexer_t tokens(args.begin(), args.end()); + + assertEqual(query_lexer_t::token_t::TERM, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::TOK_AND, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::TERM, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::TOK_OR, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::TERM, tokens.next_token().kind); + assertEqual(query_lexer_t::token_t::END_REACHED, tokens.next_token().kind); +} diff --git a/test/unit/t_expr.h b/test/unit/t_expr.h index f1289143..2f22db3d 100644 --- a/test/unit/t_expr.h +++ b/test/unit/t_expr.h @@ -8,6 +8,22 @@ class ValueExprTestCase : public CPPUNIT_NS::TestCase CPPUNIT_TEST_SUITE(ValueExprTestCase); //CPPUNIT_TEST(testConstructors); + CPPUNIT_TEST(testPredicateTokenizer1); + CPPUNIT_TEST(testPredicateTokenizer2); + CPPUNIT_TEST(testPredicateTokenizer3); + CPPUNIT_TEST(testPredicateTokenizer4); + CPPUNIT_TEST(testPredicateTokenizer5); + CPPUNIT_TEST(testPredicateTokenizer6); + CPPUNIT_TEST(testPredicateTokenizer7); + CPPUNIT_TEST(testPredicateTokenizer8); + CPPUNIT_TEST(testPredicateTokenizer9); + CPPUNIT_TEST(testPredicateTokenizer10); + CPPUNIT_TEST(testPredicateTokenizer11); + CPPUNIT_TEST(testPredicateTokenizer12); + CPPUNIT_TEST(testPredicateTokenizer13); + CPPUNIT_TEST(testPredicateTokenizer14); + CPPUNIT_TEST(testPredicateTokenizer15); + CPPUNIT_TEST(testPredicateTokenizer16); CPPUNIT_TEST_SUITE_END(); @@ -19,6 +35,22 @@ public: virtual void tearDown(); //void testConstructors(); + void testPredicateTokenizer1(); + void testPredicateTokenizer2(); + void testPredicateTokenizer3(); + void testPredicateTokenizer4(); + void testPredicateTokenizer5(); + void testPredicateTokenizer6(); + void testPredicateTokenizer7(); + void testPredicateTokenizer8(); + void testPredicateTokenizer9(); + void testPredicateTokenizer10(); + void testPredicateTokenizer11(); + void testPredicateTokenizer12(); + void testPredicateTokenizer13(); + void testPredicateTokenizer14(); + void testPredicateTokenizer15(); + void testPredicateTokenizer16(); private: ValueExprTestCase(const ValueExprTestCase ©); -- cgit v1.2.3 From 218a333e8394aac053b7d7acce2a95deb56709a4 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Wed, 28 Oct 2009 23:07:03 -0400 Subject: Fixes to the new query expression parser --- src/precmd.cc | 7 +++++-- src/predicate.cc | 15 ++++++++++++--- src/predicate.h | 14 ++++++++++++-- src/report.cc | 7 +++++-- 4 files changed, 34 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/precmd.cc b/src/precmd.cc index 999261fa..d6ff1ba5 100644 --- a/src/precmd.cc +++ b/src/precmd.cc @@ -226,7 +226,10 @@ value_t args_command(call_scope_t& args) args.value().dump(out); out << std::endl << std::endl; - string predicate = args_to_predicate(begin, end).text(); + std::pair + info = args_to_predicate(begin, end); + begin = info.first; + string predicate = info.second.text(); call_scope_t sub_args(static_cast(args)); sub_args.push_back(string_value(predicate)); @@ -237,7 +240,7 @@ value_t args_command(call_scope_t& args) out << std::endl << _("====== Display predicate ======") << std::endl << std::endl; - predicate = args_to_predicate(begin, end).text(); + predicate = args_to_predicate(begin, end).second.text(); call_scope_t disp_sub_args(static_cast(args)); disp_sub_args.push_back(string_value(predicate)); diff --git a/src/predicate.cc b/src/predicate.cc index 4f712904..a39aece0 100644 --- a/src/predicate.cc +++ b/src/predicate.cc @@ -140,6 +140,12 @@ query_lexer_t::token_t query_lexer_t::next_token() 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. + ++begin; + return token_t(token_t::END_REACHED); + } else if (ident == "expr") { // The expr keyword takes the whole of the next string as its // argument. @@ -351,11 +357,14 @@ expr_t::ptr_op_t query_parser_t::parse() return parse_query_expr(query_lexer_t::token_t::TOK_ACCOUNT); } -expr_t args_to_predicate(value_t::sequence_t::const_iterator& begin, - value_t::sequence_t::const_iterator end) +std::pair +args_to_predicate(value_t::sequence_t::const_iterator begin, + value_t::sequence_t::const_iterator end) { query_parser_t parser(begin, end); - return expr_t(parser.parse()); + expr_t expr(parser.parse()); + return std::pair + (parser.begin(), expr); } } // namespace ledger diff --git a/src/predicate.h b/src/predicate.h index e1048f83..dc39f2f2 100644 --- a/src/predicate.h +++ b/src/predicate.h @@ -98,6 +98,8 @@ public: class query_lexer_t { + friend class query_parser_t; + value_t::sequence_t::const_iterator begin; value_t::sequence_t::const_iterator end; @@ -246,10 +248,18 @@ public: : lexer(begin, end) {} expr_t::ptr_op_t parse(); + + value_t::sequence_t::const_iterator begin() const { + return lexer.begin; + } + value_t::sequence_t::const_iterator end() const { + return lexer.end; + } }; -expr_t args_to_predicate(value_t::sequence_t::const_iterator& begin, - value_t::sequence_t::const_iterator end); +std::pair +args_to_predicate(value_t::sequence_t::const_iterator begin, + value_t::sequence_t::const_iterator end); } // namespace ledger diff --git a/src/report.cc b/src/report.cc index febe43e3..7755c911 100644 --- a/src/report.cc +++ b/src/report.cc @@ -380,8 +380,11 @@ namespace { value_t::sequence_t::const_iterator end = args.value().as_sequence().end(); - string limit = args_to_predicate(begin, end).text(); + std::pair + info = args_to_predicate(begin, end); + begin = info.first; + string limit = info.second.text(); if (! limit.empty()) report.HANDLER(limit_).on(whence, limit); @@ -390,7 +393,7 @@ namespace { string display; if (begin != end) - display = args_to_predicate(begin, end).text(); + display = args_to_predicate(begin, end).second.text(); if (! display.empty()) report.HANDLER(display_).on(whence, display); -- cgit v1.2.3 From 52433e56e562976887325ce48a6271abb82a89aa Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Wed, 28 Oct 2009 23:41:51 -0400 Subject: Fixed "show" keywords, and added // syntax --- src/precmd.cc | 14 +++++--------- src/predicate.cc | 36 ++++++++++++++++++++++++++++++++---- src/predicate.h | 44 ++++++++++++++++++++++++++++++++++++-------- src/report.cc | 20 +++++++++----------- 4 files changed, 82 insertions(+), 32 deletions(-) (limited to 'src') diff --git a/src/precmd.cc b/src/precmd.cc index d6ff1ba5..590d2553 100644 --- a/src/precmd.cc +++ b/src/precmd.cc @@ -226,24 +226,20 @@ value_t args_command(call_scope_t& args) args.value().dump(out); out << std::endl << std::endl; - std::pair - info = args_to_predicate(begin, end); - begin = info.first; - string predicate = info.second.text(); + std::pair info = args_to_predicate(begin, end); call_scope_t sub_args(static_cast(args)); - sub_args.push_back(string_value(predicate)); + sub_args.push_back(string_value(info.first.text())); parse_command(sub_args); - if (begin != end) { + if (info.second.tokens_remaining()) { out << std::endl << _("====== Display predicate ======") << std::endl << std::endl; - predicate = args_to_predicate(begin, end).second.text(); - call_scope_t disp_sub_args(static_cast(args)); - disp_sub_args.push_back(string_value(predicate)); + disp_sub_args.push_back + (string_value(args_to_predicate(info.second).first.text())); parse_command(disp_sub_args); } diff --git a/src/predicate.cc b/src/predicate.cc index a39aece0..b3926116 100644 --- a/src/predicate.cc +++ b/src/predicate.cc @@ -64,6 +64,29 @@ query_lexer_t::token_t query_lexer_t::next_token() 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); @@ -143,7 +166,7 @@ query_lexer_t::token_t query_lexer_t::next_token() else if (ident == "show") { // The "show" keyword is special, and separates a limiting predicate // from a display predicate. - ++begin; + DEBUG("pred.show", "string = " << (*begin).as_string()); return token_t(token_t::END_REACHED); } else if (ident == "expr") { @@ -357,14 +380,19 @@ expr_t::ptr_op_t query_parser_t::parse() return parse_query_expr(query_lexer_t::token_t::TOK_ACCOUNT); } -std::pair +std::pair args_to_predicate(value_t::sequence_t::const_iterator begin, value_t::sequence_t::const_iterator end) { query_parser_t parser(begin, end); expr_t expr(parser.parse()); - return std::pair - (parser.begin(), expr); + return std::pair(expr, parser); +} + +std::pair args_to_predicate(query_parser_t parser) +{ + expr_t expr(parser.parse()); + return std::pair(expr, parser); } } // namespace ledger diff --git a/src/predicate.h b/src/predicate.h index dc39f2f2..555fac05 100644 --- a/src/predicate.h +++ b/src/predicate.h @@ -106,7 +106,7 @@ class query_lexer_t string::const_iterator arg_i; string::const_iterator arg_end; - bool consume_whitespace; + bool consume_whitespace; public: struct token_t @@ -220,16 +220,33 @@ public: value_t::sequence_t::const_iterator _end) : begin(_begin), end(_end), consume_whitespace(false) { + TRACE_CTOR(query_lexer_t, ""); assert(begin != end); arg_i = (*begin).as_string().begin(); arg_end = (*begin).as_string().end(); } + query_lexer_t(const query_lexer_t& lexer) + : begin(lexer.begin), end(lexer.end), + arg_i(lexer.arg_i), arg_end(lexer.arg_end), + consume_whitespace(lexer.consume_whitespace), + token_cache(lexer.token_cache) + { + TRACE_CTOR(query_lexer_t, "copy"); + } + ~query_lexer_t() throw() { + TRACE_DTOR(query_lexer_t); + } token_t next_token(); void push_token(token_t tok) { assert(token_cache.kind == token_t::UNKNOWN); token_cache = tok; } + token_t peek_token() { + if (token_cache.kind == token_t::UNKNOWN) + token_cache = next_token(); + return token_cache; + } }; class query_parser_t @@ -245,22 +262,33 @@ class query_parser_t public: query_parser_t(value_t::sequence_t::const_iterator begin, value_t::sequence_t::const_iterator end) - : lexer(begin, end) {} + : lexer(begin, end) { + TRACE_CTOR(query_parser_t, ""); + } + query_parser_t(const query_parser_t& parser) + : lexer(parser.lexer) { + TRACE_CTOR(query_parser_t, "copy"); + } + ~query_parser_t() throw() { + TRACE_DTOR(query_parser_t); + } expr_t::ptr_op_t parse(); - value_t::sequence_t::const_iterator begin() const { - return lexer.begin; - } - value_t::sequence_t::const_iterator end() const { - return lexer.end; + bool tokens_remaining() { + query_lexer_t::token_t tok = lexer.peek_token(); + assert(tok.kind != query_lexer_t::token_t::UNKNOWN); + return tok.kind != query_lexer_t::token_t::END_REACHED; } }; -std::pair +std::pair args_to_predicate(value_t::sequence_t::const_iterator begin, value_t::sequence_t::const_iterator end); +std::pair +args_to_predicate(query_parser_t parser); + } // namespace ledger #endif // _PREDICATE_H diff --git a/src/report.cc b/src/report.cc index 7755c911..281057d3 100644 --- a/src/report.cc +++ b/src/report.cc @@ -380,26 +380,24 @@ namespace { value_t::sequence_t::const_iterator end = args.value().as_sequence().end(); - std::pair - info = args_to_predicate(begin, end); - begin = info.first; + std::pair info = args_to_predicate(begin, end); - string limit = info.second.text(); + string limit = info.first.text(); if (! limit.empty()) report.HANDLER(limit_).on(whence, limit); DEBUG("report.predicate", "Predicate = " << report.HANDLER(limit_).str()); - string display; - if (begin != end) - display = args_to_predicate(begin, end).second.text(); + if (info.second.tokens_remaining()) { + string display = args_to_predicate(info.second).first.text(); - if (! display.empty()) - report.HANDLER(display_).on(whence, display); + if (! display.empty()) + report.HANDLER(display_).on(whence, display); - DEBUG("report.predicate", - "Display predicate = " << report.HANDLER(display_).str()); + DEBUG("report.predicate", + "Display predicate = " << report.HANDLER(display_).str()); + } } (report.*report_method)(handler_ptr(handler)); -- cgit v1.2.3