diff options
-rw-r--r-- | doc/sample.dat | 4 | ||||
-rw-r--r-- | src/item.cc | 23 | ||||
-rw-r--r-- | src/query.cc | 18 | ||||
-rw-r--r-- | src/query.h | 6 | ||||
-rw-r--r-- | src/textual.cc | 150 | ||||
-rw-r--r-- | test/unit/t_expr.cc | 2 | ||||
-rwxr-xr-x | tools/proof | 1 | ||||
-rwxr-xr-x | tools/push | 2 |
8 files changed, 116 insertions, 90 deletions
diff --git a/doc/sample.dat b/doc/sample.dat index e773d6df..10a970db 100644 --- a/doc/sample.dat +++ b/doc/sample.dat @@ -27,12 +27,16 @@ N $ Русский язык:Активы:Русский язык:Русский язык $1000.00 Income:Salary +tag foo + 2004/05/27 Book Store Expenses:Books $20.00 Expenses:Cards $40.00 Expenses:Docs $30.00 Liabilities:MasterCard +end tag + 2004/05/27 (100) Credit card company ; This is an xact note! ; Sample: Value diff --git a/src/item.cc b/src/item.cc index 43274cfd..da6429ed 100644 --- a/src/item.cc +++ b/src/item.cc @@ -118,17 +118,20 @@ void item_t::set_tag(const string& tag, void item_t::parse_tags(const char * p, optional<date_t::year_type> current_year) { if (const char * b = std::strchr(p, '[')) { - if (const char * e = std::strchr(p, ']')) { - char buf[256]; - std::strncpy(buf, b + 1, e - b - 1); - buf[e - b - 1] = '\0'; - - if (char * p = std::strchr(buf, '=')) { - *p++ = '\0'; - _date_eff = parse_date(p, current_year); + if (*(b + 1) != '\0' && + (std::isdigit(*(b + 1)) || *(b + 1) == '=')) { + if (const char * e = std::strchr(p, ']')) { + char buf[256]; + std::strncpy(buf, b + 1, e - b - 1); + buf[e - b - 1] = '\0'; + + if (char * p = std::strchr(buf, '=')) { + *p++ = '\0'; + _date_eff = parse_date(p, current_year); + } + if (buf[0]) + _date = parse_date(buf, current_year); } - if (buf[0]) - _date = parse_date(buf, current_year); } } diff --git a/src/query.cc b/src/query.cc index 21304f92..bf4eb62a 100644 --- a/src/query.cc +++ b/src/query.cc @@ -53,6 +53,12 @@ query_t::lexer_t::token_t query_t::lexer_t::next_token() } } + if (consume_next_arg) { + consume_next_arg = false; + arg_i = arg_end; + return token_t(token_t::TERM, (*begin).as_string()); + } + resume: bool consume_next = false; switch (*arg_i) { @@ -95,13 +101,7 @@ query_t::lexer_t::token_t query_t::lexer_t::next_token() 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 '=': ++arg_i; return token_t(token_t::TOK_EQ); case '\\': consume_next = true; @@ -140,7 +140,7 @@ query_t::lexer_t::token_t query_t::lexer_t::next_token() } consume_whitespace = false; - test_ident: +test_ident: if (ident == "and") return token_t(token_t::TOK_AND); else if (ident == "or") @@ -177,7 +177,7 @@ query_t::lexer_t::token_t query_t::lexer_t::next_token() #endif else if (ident == "expr") { // The expr keyword takes the whole of the next string as its argument. - consume_whitespace = true; + consume_next_arg = true; return token_t(token_t::TOK_EXPR); } else diff --git a/src/query.h b/src/query.h index 73639263..43db60a6 100644 --- a/src/query.h +++ b/src/query.h @@ -61,6 +61,7 @@ public: string::const_iterator arg_end; bool consume_whitespace; + bool consume_next_arg; public: struct token_t @@ -175,7 +176,9 @@ public: lexer_t(value_t::sequence_t::const_iterator _begin, value_t::sequence_t::const_iterator _end) - : begin(_begin), end(_end), consume_whitespace(false) + : begin(_begin), end(_end), + consume_whitespace(false), + consume_next_arg(false) { TRACE_CTOR(lexer_t, ""); assert(begin != end); @@ -186,6 +189,7 @@ public: : begin(lexer.begin), end(lexer.end), arg_i(lexer.arg_i), arg_end(lexer.arg_end), consume_whitespace(lexer.consume_whitespace), + consume_next_arg(lexer.consume_next_arg), token_cache(lexer.token_cache) { TRACE_CTOR(lexer_t, "copy"); diff --git a/src/textual.cc b/src/textual.cc index 27ea13b8..aec7dbda 100644 --- a/src/textual.cc +++ b/src/textual.cc @@ -54,12 +54,13 @@ namespace { static const std::size_t MAX_LINE = 1024; public: - std::list<account_t *>& account_stack; - std::list<string>& tag_stack; + typedef variant<account_t *, string> state_t; + + std::list<state_t>& state_stack; + #if defined(TIMELOG_SUPPORT) - time_log_t& timelog; + time_log_t& timelog; #endif - instance_t * parent; std::istream& in; scope_t& scope; @@ -68,7 +69,6 @@ namespace { const path * original_file; accounts_map account_aliases; bool strict; - path pathname; char linebuf[MAX_LINE + 1]; std::size_t linenum; @@ -79,27 +79,41 @@ namespace { optional<date_t::year_type> current_year; - instance_t(std::list<account_t *>& _account_stack, - std::list<string>& _tag_stack, + instance_t(std::list<state_t>& _state_stack, #if defined(TIMELOG_SUPPORT) - time_log_t& _timelog, + time_log_t& _timelog, #endif - std::istream& _in, - scope_t& _scope, - journal_t& _journal, - account_t * _master = NULL, - const path * _original_file = NULL, - bool _strict = false, - instance_t * _parent = NULL); + std::istream& _in, + scope_t& _scope, + journal_t& _journal, + account_t * _master = NULL, + const path * _original_file = NULL, + bool _strict = false, + instance_t * _parent = NULL); ~instance_t(); + bool front_is_account() { + return state_stack.front().type() == typeid(account_t *); + } + bool front_is_string() { + return state_stack.front().type() == typeid(string); + } + + account_t * top_account() { + foreach (state_t& state, state_stack) + if (state.type() == typeid(account_t *)) + return boost::get<account_t *>(state); + return NULL; + } + void parse(); std::streamsize read_line(char *& line); bool peek_whitespace_line() { return (in.good() && ! in.eof() && (in.peek() == ' ' || in.peek() == '\t')); } + void read_next_directive(); #if defined(TIMELOG_SUPPORT) @@ -122,7 +136,6 @@ namespace { void end_directive(char * line); void alias_directive(char * line); void tag_directive(char * line); - void pop_directive(char * line); void define_directive(char * line); bool general_directive(char * line); @@ -188,19 +201,18 @@ namespace { } } -instance_t::instance_t(std::list<account_t *>& _account_stack, - std::list<string>& _tag_stack, +instance_t::instance_t(std::list<state_t>& _state_stack, #if defined(TIMELOG_SUPPORT) - time_log_t& _timelog, + time_log_t& _timelog, #endif - std::istream& _in, - scope_t& _scope, - journal_t& _journal, - account_t * _master, - const path * _original_file, - bool _strict, - instance_t * _parent) - : account_stack(_account_stack), tag_stack(_tag_stack), + std::istream& _in, + scope_t& _scope, + journal_t& _journal, + account_t * _master, + const path * _original_file, + bool _strict, + instance_t * _parent) + : state_stack(_state_stack), #if defined(TIMELOG_SUPPORT) timelog(_timelog), #endif @@ -212,7 +224,7 @@ instance_t::instance_t(std::list<account_t *>& _account_stack, if (! master) master = journal.master; - account_stack.push_front(master); + state_stack.push_front(master); if (_original_file) pathname = *_original_file; @@ -224,7 +236,8 @@ instance_t::~instance_t() { TRACE_DTOR(instance_t); - account_stack.pop_front(); + assert(! state_stack.empty()); + state_stack.pop_front(); } void instance_t::parse() @@ -434,7 +447,7 @@ void instance_t::clock_in_directive(char * line, position.end_line = linenum; time_xact_t event(position, parse_datetime(datetime, current_year), - p ? account_stack.front()->find_account(p) : NULL, + p ? top_account()->find_account(p) : NULL, n ? n : "", end ? end : ""); @@ -463,7 +476,7 @@ void instance_t::clock_out_directive(char * line, position.end_line = linenum; time_xact_t event(position, parse_datetime(datetime, current_year), - p ? account_stack.front()->find_account(p) : NULL, + p ? top_account()->find_account(p) : NULL, n ? n : "", end ? end : ""); @@ -483,7 +496,7 @@ void instance_t::default_commodity_directive(char * line) void instance_t::default_account_directive(char * line) { - journal.bucket = account_stack.front()->find_account(skip_ws(line + 1)); + journal.bucket = top_account()->find_account(skip_ws(line + 1)); journal.bucket->add_flags(ACCOUNT_KNOWN); } @@ -547,7 +560,7 @@ void instance_t::automated_xact_directive(char * line) reveal_context = false; - if (parse_posts(account_stack.front(), *ae.get(), true)) { + if (parse_posts(top_account(), *ae.get(), true)) { reveal_context = true; journal.auto_xacts.push_back(ae.get()); @@ -586,7 +599,7 @@ void instance_t::period_xact_directive(char * line) reveal_context = false; - if (parse_posts(account_stack.front(), *pe.get())) { + if (parse_posts(top_account(), *pe.get())) { reveal_context = true; pe->journal = &journal; @@ -622,7 +635,7 @@ void instance_t::xact_directive(char * line, std::streamsize len) { TRACE_START(xacts, 1, "Time spent handling transactions:"); - if (xact_t * xact = parse_xact(line, len, account_stack.front())) { + if (xact_t * xact = parse_xact(line, len, top_account())) { std::auto_ptr<xact_t> manager(xact); if (journal.add_xact(xact)) { @@ -664,7 +677,7 @@ void instance_t::include_directive(char * line) ifstream stream(filename); - instance_t instance(account_stack, tag_stack, + instance_t instance(state_stack, #if defined(TIMELOG_SUPPORT) timelog, #endif @@ -678,19 +691,28 @@ void instance_t::include_directive(char * line) void instance_t::master_account_directive(char * line) { - if (account_t * acct = account_stack.front()->find_account(line)) - account_stack.push_front(acct); + if (account_t * acct = top_account()->find_account(line)) + state_stack.push_front(acct); else assert(! "Failed to create account"); } -void instance_t::end_directive(char *) +void instance_t::end_directive(char * kind) { - if (account_stack.size() <= 1) + string name(kind); + + if ((name.empty() || name == "account") && ! front_is_account()) + throw_(std::runtime_error, + _("'end account' directive does not match open tag directive")); + else if (name == "tag" && ! front_is_string()) throw_(std::runtime_error, - _("'end' directive found, but no master account currently active")); + _("'end tag' directive does not match open account directive")); + + if (state_stack.size() <= 1) + throw_(std::runtime_error, + _("'end' found, but no enclosing tag or account directive")); else - account_stack.pop_back(); + state_stack.pop_front(); } void instance_t::alias_directive(char * line) @@ -707,7 +729,7 @@ void instance_t::alias_directive(char * line) // name (e), add a reference to the account in the // `account_aliases' map, which is used by the post // parser to resolve alias references. - account_t * acct = account_stack.front()->find_account(e); + account_t * acct = top_account()->find_account(e); std::pair<accounts_map::iterator, bool> result = account_aliases.insert(accounts_map::value_type(b, acct)); assert(result.second); @@ -716,16 +738,12 @@ void instance_t::alias_directive(char * line) void instance_t::tag_directive(char * line) { - tag_stack.push_back(trim_ws(line)); -} + string tag(trim_ws(line)); -void instance_t::pop_directive(char *) -{ - if (tag_stack.empty()) - throw_(std::runtime_error, - _("'pop' directive found, but no tags currently active")); - else - tag_stack.pop_back(); + if (tag.find(':') == string::npos) + tag = string(":") + tag + ":"; + + state_stack.push_front(tag); } void instance_t::define_directive(char * line) @@ -786,13 +804,6 @@ bool instance_t::general_directive(char * line) } break; - case 'p': - if (std::strcmp(p, "pop") == 0) { - pop_directive(arg); - return true; - } - break; - case 't': if (std::strcmp(p, "tag") == 0) { tag_directive(arg); @@ -1102,9 +1113,10 @@ post_t * instance_t::parse_post(char * line, post->pos->end_pos = curr_pos; post->pos->end_line = linenum; - if (! tag_stack.empty()) { - foreach (const string& tag, tag_stack) - post->parse_tags(tag.c_str()); + if (! state_stack.empty()) { + foreach (const state_t& state, state_stack) + if (state.type() == typeid(string)) + post->parse_tags(boost::get<string>(state).c_str()); } TRACE_STOP(post_details, 1); @@ -1266,9 +1278,10 @@ xact_t * instance_t::parse_xact(char * line, xact->pos->end_pos = curr_pos; xact->pos->end_line = linenum; - if (! tag_stack.empty()) { - foreach (const string& tag, tag_stack) - xact->parse_tags(tag.c_str()); + if (! state_stack.empty()) { + foreach (const state_t& state, state_stack) + if (state.type() == typeid(string)) + xact->parse_tags(boost::get<string>(state).c_str()); } TRACE_STOP(xact_details, 1); @@ -1300,13 +1313,12 @@ std::size_t journal_t::parse(std::istream& in, { TRACE_START(parsing_total, 1, "Total time spent parsing text:"); - std::list<account_t *> account_stack; - std::list<string> tag_stack; + std::list<instance_t::state_t> state_stack; #if defined(TIMELOG_SUPPORT) - time_log_t timelog(*this); + time_log_t timelog(*this); #endif - instance_t parsing_instance(account_stack, tag_stack, + instance_t parsing_instance(state_stack, #if defined(TIMELOG_SUPPORT) timelog, #endif diff --git a/test/unit/t_expr.cc b/test/unit/t_expr.cc index 5e5d44fb..b5865948 100644 --- a/test/unit/t_expr.cc +++ b/test/unit/t_expr.cc @@ -158,6 +158,8 @@ void ValueExprTestCase::testPredicateTokenizer7() assertEqual(query_t::lexer_t::token_t::TOK_EQ, tokens.next_token().kind); assertEqual(query_t::lexer_t::token_t::TERM, tokens.next_token().kind); + assertEqual(query_t::lexer_t::token_t::TOK_AND, tokens.next_token().kind); + assertEqual(query_t::lexer_t::token_t::TERM, tokens.next_token().kind); assertEqual(query_t::lexer_t::token_t::END_REACHED, tokens.next_token().kind); #endif } diff --git a/tools/proof b/tools/proof index 7c5a16f2..c41745b6 100755 --- a/tools/proof +++ b/tools/proof @@ -26,6 +26,7 @@ if egrep -q '(ERROR|CRITICAL)' ~/Desktop/proof.log; then else echo "Ledger proof build succeeded" echo $VERSION > ~/Products/last-proofed + mv ~/Desktop/proof.log /tmp fi exit 0 @@ -10,6 +10,6 @@ git checkout next git rebase master git push git checkout master -./acprep upload +./acprep opt upload mv *.dmg* build git checkout next |