diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/TODO | 19 | ||||
-rw-r--r-- | src/account.cc | 551 | ||||
-rw-r--r-- | src/account.h | 273 | ||||
-rw-r--r-- | src/accum.cc (renamed from src/data/jbuilder.cc) | 63 | ||||
-rw-r--r-- | src/accum.h | 86 | ||||
-rw-r--r-- | src/amount.cc | 1278 | ||||
-rw-r--r-- | src/amount.h | 761 | ||||
-rw-r--r-- | src/annotate.cc | 205 | ||||
-rw-r--r-- | src/annotate.h | 265 | ||||
-rw-r--r-- | src/archive.cc | 295 | ||||
-rw-r--r-- | src/archive.h (renamed from src/python/py_commodity.cc) | 75 | ||||
-rw-r--r-- | src/balance.cc (renamed from src/numerics/balance.cc) | 170 | ||||
-rw-r--r-- | src/balance.h (renamed from src/numerics/balance.h) | 263 | ||||
-rw-r--r-- | src/chain.cc | 241 | ||||
-rw-r--r-- | src/chain.h | 91 | ||||
-rw-r--r-- | src/commodity.cc | 705 | ||||
-rw-r--r-- | src/commodity.h | 431 | ||||
-rw-r--r-- | src/compare.cc | 114 | ||||
-rw-r--r-- | src/compare.h (renamed from src/data/jbuilder.h) | 91 | ||||
-rw-r--r-- | src/data/builder.h | 256 | ||||
-rw-r--r-- | src/data/compile.cc | 235 | ||||
-rw-r--r-- | src/data/compile.h | 206 | ||||
-rw-r--r-- | src/data/document.cc | 181 | ||||
-rw-r--r-- | src/data/document.h | 152 | ||||
-rw-r--r-- | src/data/journal.cc | 702 | ||||
-rw-r--r-- | src/data/journal.h | 439 | ||||
-rw-r--r-- | src/data/node.cc | 130 | ||||
-rw-r--r-- | src/data/node.h | 352 | ||||
-rw-r--r-- | src/data/parser.h | 138 | ||||
-rw-r--r-- | src/data/textual.cc | 480 | ||||
-rw-r--r-- | src/draft.cc | 527 | ||||
-rw-r--r-- | src/draft.h (renamed from src/utility/times.cc) | 109 | ||||
-rw-r--r-- | src/driver/fdstream.hpp | 215 | ||||
-rw-r--r-- | src/driver/main.cc | 481 | ||||
-rw-r--r-- | src/driver/option.cc | 222 | ||||
-rw-r--r-- | src/driver/report.cc | 224 | ||||
-rw-r--r-- | src/driver/report.h | 190 | ||||
-rw-r--r-- | src/driver/session.cc | 313 | ||||
-rw-r--r-- | src/driver/session.h | 196 | ||||
-rw-r--r-- | src/emacs.cc | 108 | ||||
-rw-r--r-- | src/emacs.h | 79 | ||||
-rw-r--r-- | src/error.cc | 118 | ||||
-rw-r--r-- | src/error.h | 104 | ||||
-rw-r--r-- | src/expr.cc | 143 | ||||
-rw-r--r-- | src/expr.h | 147 | ||||
-rw-r--r-- | src/exprbase.h | 253 | ||||
-rw-r--r-- | src/filters.cc | 1055 | ||||
-rw-r--r-- | src/filters.h | 721 | ||||
-rw-r--r-- | src/flags.h | 209 | ||||
-rw-r--r-- | src/format.cc | 488 | ||||
-rw-r--r-- | src/format.h | 161 | ||||
-rw-r--r-- | src/generate.cc | 385 | ||||
-rw-r--r-- | src/generate.h | 129 | ||||
-rw-r--r-- | src/global.cc | 480 | ||||
-rw-r--r-- | src/global.h | 163 | ||||
-rw-r--r-- | src/interactive.cc | 194 | ||||
-rw-r--r-- | src/interactive.h | 151 | ||||
-rw-r--r-- | src/item.cc | 467 | ||||
-rw-r--r-- | src/item.h | 215 | ||||
-rw-r--r-- | src/iterators.cc | 258 | ||||
-rw-r--r-- | src/iterators.h | 230 | ||||
-rw-r--r-- | src/journal.cc | 251 | ||||
-rw-r--r-- | src/journal.h | 197 | ||||
-rw-r--r-- | src/ledger.h | 56 | ||||
-rw-r--r-- | src/main.cc | 217 | ||||
-rw-r--r-- | src/mask.cc (renamed from src/utility/mask.cc) | 32 | ||||
-rw-r--r-- | src/mask.h | 157 | ||||
-rw-r--r-- | src/numerics/amount.cc | 1401 | ||||
-rw-r--r-- | src/numerics/amount.h | 713 | ||||
-rw-r--r-- | src/numerics/balpair.h | 367 | ||||
-rw-r--r-- | src/numerics/commodity.cc | 598 | ||||
-rw-r--r-- | src/numerics/commodity.h | 394 | ||||
-rw-r--r-- | src/numerics/value.cc | 1512 | ||||
-rw-r--r-- | src/op.cc | 732 | ||||
-rw-r--r-- | src/op.h | 343 | ||||
-rw-r--r-- | src/option.cc | 249 | ||||
-rw-r--r-- | src/option.h | 294 | ||||
-rw-r--r-- | src/output.cc | 254 | ||||
-rw-r--r-- | src/output.h | 110 | ||||
-rw-r--r-- | src/parser.cc | 517 | ||||
-rw-r--r-- | src/parser.h | 107 | ||||
-rw-r--r-- | src/pool.cc | 364 | ||||
-rw-r--r-- | src/pool.h | 156 | ||||
-rw-r--r-- | src/post.cc | 576 | ||||
-rw-r--r-- | src/post.h | 224 | ||||
-rw-r--r-- | src/precmd.cc | 211 | ||||
-rw-r--r-- | src/precmd.h (renamed from src/driver/option.h) | 36 | ||||
-rw-r--r-- | src/predicate.cc (renamed from src/data/textual.h) | 23 | ||||
-rw-r--r-- | src/predicate.h | 106 | ||||
-rw-r--r-- | src/pstream.h | 109 | ||||
-rw-r--r-- | src/py_account.cc | 235 | ||||
-rw-r--r-- | src/py_amount.cc | 314 | ||||
-rw-r--r-- | src/py_balance.cc | 238 | ||||
-rw-r--r-- | src/py_commodity.cc | 451 | ||||
-rw-r--r-- | src/py_expr.cc | 68 | ||||
-rw-r--r-- | src/py_format.cc | 64 | ||||
-rw-r--r-- | src/py_item.cc | 166 | ||||
-rw-r--r-- | src/py_journal.cc | 321 | ||||
-rw-r--r-- | src/py_post.cc | 184 | ||||
-rw-r--r-- | src/py_times.cc | 253 | ||||
-rw-r--r-- | src/py_utils.cc | 254 | ||||
-rw-r--r-- | src/py_value.cc | 380 | ||||
-rw-r--r-- | src/py_xact.cc | 153 | ||||
-rw-r--r-- | src/pyfstream.h (renamed from src/python/pyfstream.h) | 89 | ||||
-rw-r--r-- | src/pyinterp.cc | 505 | ||||
-rw-r--r-- | src/pyinterp.h (renamed from src/python/pyinterp.h) | 94 | ||||
-rw-r--r-- | src/pyledger.cc (renamed from src/python/pyledger.cc) | 18 | ||||
-rw-r--r-- | src/python/py_amount.cc | 320 | ||||
-rw-r--r-- | src/python/py_times.cc | 132 | ||||
-rw-r--r-- | src/python/py_utils.cc | 172 | ||||
-rw-r--r-- | src/python/pyinterp.cc | 238 | ||||
-rw-r--r-- | src/python/pyutils.h | 112 | ||||
-rw-r--r-- | src/python/tuples.hpp | 281 | ||||
-rw-r--r-- | src/pyutils.h | 189 | ||||
-rw-r--r-- | src/query.cc | 455 | ||||
-rw-r--r-- | src/query.h | 305 | ||||
-rw-r--r-- | src/quotes.cc | 109 | ||||
-rw-r--r-- | src/quotes.h (renamed from src/python/pyledger.h) | 34 | ||||
-rw-r--r-- | src/report.cc | 1307 | ||||
-rw-r--r-- | src/report.h | 958 | ||||
-rw-r--r-- | src/scope.cc | 74 | ||||
-rw-r--r-- | src/scope.h | 348 | ||||
-rw-r--r-- | src/session.cc | 248 | ||||
-rw-r--r-- | src/session.h | 151 | ||||
-rw-r--r-- | src/stats.cc | 121 | ||||
-rw-r--r-- | src/stats.h (renamed from src/utility/mask.h) | 34 | ||||
-rw-r--r-- | src/stream.cc | 139 | ||||
-rw-r--r-- | src/stream.h | 142 | ||||
-rw-r--r-- | src/system.hh.in (renamed from src/utility/system.hh) | 169 | ||||
-rw-r--r-- | src/temps.cc | 137 | ||||
-rw-r--r-- | src/temps.h | 76 | ||||
-rw-r--r-- | src/textual.cc | 1396 | ||||
-rw-r--r-- | src/timelog.cc | 158 | ||||
-rw-r--r-- | src/timelog.h (renamed from src/utility/flags.h) | 110 | ||||
-rw-r--r-- | src/times.cc | 1587 | ||||
-rw-r--r-- | src/times.h | 632 | ||||
-rw-r--r-- | src/token.cc | 485 | ||||
-rw-r--r-- | src/token.h | 135 | ||||
-rw-r--r-- | src/traversal/abbrev.cc | 94 | ||||
-rw-r--r-- | src/traversal/abbrev.h | 23 | ||||
-rw-r--r-- | src/traversal/transform.cc | 357 | ||||
-rw-r--r-- | src/traversal/transform.h | 164 | ||||
-rw-r--r-- | src/traversal/xpath.cc | 1670 | ||||
-rw-r--r-- | src/traversal/xpath.h | 873 | ||||
-rw-r--r-- | src/unistring.h | 128 | ||||
-rw-r--r-- | src/utility/binary.cc | 157 | ||||
-rw-r--r-- | src/utility/binary.h | 269 | ||||
-rw-r--r-- | src/utility/context.h | 142 | ||||
-rw-r--r-- | src/utility/pushvar.h | 214 | ||||
-rw-r--r-- | src/utility/times.h | 123 | ||||
-rw-r--r-- | src/utils.cc (renamed from src/utility/utils.cc) | 365 | ||||
-rw-r--r-- | src/utils.h (renamed from src/utility/utils.h) | 407 | ||||
-rw-r--r-- | src/value.cc | 1864 | ||||
-rw-r--r-- | src/value.h (renamed from src/numerics/value.h) | 764 | ||||
-rw-r--r-- | src/xact.cc | 781 | ||||
-rw-r--r-- | src/xact.h | 232 | ||||
-rw-r--r-- | src/xml.cc | 124 | ||||
-rw-r--r-- | src/xml.h | 90 |
158 files changed, 35075 insertions, 16596 deletions
diff --git a/src/TODO b/src/TODO deleted file mode 100644 index 611e8508..00000000 --- a/src/TODO +++ /dev/null @@ -1,19 +0,0 @@ -- What does SEQUENCE + VALUE mean in XPath? Does it add VALUE to - every member of SEQUENCE? - - Answer: No, it doesn't; this should throw an error - -- Make sure that if any constructors cause memory to be allocated, the - memory is held by an auto_ptr until the constructor is done; - otherwise, an exception raised from within the constructor will not - call the destructor to free the memory. - -- Using mmap for the binary reader; or determine if the performance is - even worth the maintenance headaches of that code altogether. - -- Rewrite the error context reporting logic, then tie in the logging - facility to it (so that warning can be reported with context, and - debug statements can be filtered by context). - - PUSH_CONTEXT("amount.divide"); - POP_CONTEXT(amount_context2("Dividing amounts", amt1, amt2)); diff --git a/src/account.cc b/src/account.cc new file mode 100644 index 00000000..e6c7af56 --- /dev/null +++ b/src/account.cc @@ -0,0 +1,551 @@ +/* + * 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 "account.h" +#include "post.h" +#include "xact.h" +#include "interactive.h" + +namespace ledger { + +account_t::~account_t() +{ + TRACE_DTOR(account_t); + + foreach (accounts_map::value_type& pair, accounts) + if (! pair.second->has_flags(ACCOUNT_TEMP)) + checked_delete(pair.second); +} + +account_t * account_t::find_account(const string& name, + const bool auto_create) +{ + accounts_map::const_iterator i = accounts.find(name); + if (i != accounts.end()) + return (*i).second; + + char buf[8192]; + + string::size_type sep = name.find(':'); + assert(sep < 256|| sep == string::npos); + + const char * first, * rest; + if (sep == string::npos) { + first = name.c_str(); + rest = NULL; + } else { + std::strncpy(buf, name.c_str(), sep); + buf[sep] = '\0'; + + first = buf; + rest = name.c_str() + sep + 1; + } + + account_t * account; + + i = accounts.find(first); + if (i == accounts.end()) { + if (! auto_create) + return NULL; + + account = new account_t(this, first); + std::pair<accounts_map::iterator, bool> result + = accounts.insert(accounts_map::value_type(first, account)); + assert(result.second); + } else { + account = (*i).second; + } + + if (rest) + account = account->find_account(rest, auto_create); + + return account; +} + +namespace { + account_t * find_account_re_(account_t * account, const mask_t& regexp) + { + if (regexp.match(account->fullname())) + return account; + + foreach (accounts_map::value_type& pair, account->accounts) + if (account_t * a = find_account_re_(pair.second, regexp)) + return a; + + return NULL; + } +} + +account_t * account_t::find_account_re(const string& regexp) +{ + return find_account_re_(this, mask_t(regexp)); +} + +bool account_t::remove_post(post_t * post) +{ + assert(! posts.empty()); + posts.remove(post); + post->account = NULL; + return true; +} + +string account_t::fullname() const +{ + if (! _fullname.empty()) { + return _fullname; + } else { + const account_t * first = this; + string fullname = name; + + while (first->parent) { + first = first->parent; + if (! first->name.empty()) + fullname = first->name + ":" + fullname; + } + + _fullname = fullname; + + return fullname; + } +} + +string account_t::partial_name(bool flat) const +{ + string pname = name; + + for (const account_t * acct = parent; + acct && acct->parent; + acct = acct->parent) { + if (! flat) { + std::size_t count = acct->children_with_flags(ACCOUNT_EXT_TO_DISPLAY); + assert(count > 0); + if (count > 1 || acct->has_xflags(ACCOUNT_EXT_TO_DISPLAY)) + break; + } + pname = acct->name + ":" + pname; + } + return pname; +} + +std::ostream& operator<<(std::ostream& out, const account_t& account) +{ + out << account.fullname(); + return out; +} + +namespace { + value_t get_partial_name(call_scope_t& scope) + { + in_context_t<account_t> env(scope, "&b"); + return string_value(env->partial_name(env.has(0) ? + env.get<bool>(0) : false)); + } + + value_t get_account(account_t& account) { // this gets the name + return string_value(account.fullname()); + } + + value_t get_account_base(account_t& account) { + return string_value(account.name); + } + + value_t get_amount(account_t& account) { + return SIMPLIFIED_VALUE_OR_ZERO(account.amount()); + } + + value_t get_total(account_t& account) { + return SIMPLIFIED_VALUE_OR_ZERO(account.total()); + } + + value_t get_subcount(account_t& account) { + return long(account.self_details().posts_count); + } + + value_t get_count(account_t& account) { + return long(account.family_details().posts_count); + } + + value_t get_depth(account_t& account) { + return long(account.depth); + } + + value_t ignore(account_t&) { + return false; + } + + value_t get_true(account_t&) { + return true; + } + + value_t get_depth_spacer(account_t& account) + { + std::size_t depth = 0; + for (const account_t * acct = account.parent; + acct && acct->parent; + acct = acct->parent) { + std::size_t count = acct->children_with_flags(ACCOUNT_EXT_TO_DISPLAY); + assert(count > 0); + if (count > 1 || acct->has_xflags(ACCOUNT_EXT_TO_DISPLAY)) + depth++; + } + + std::ostringstream out; + for (std::size_t i = 0; i < depth; i++) + out << " "; + + return string_value(out.str()); + } + + value_t get_latest_cleared(account_t& account) + { + return account.self_details().latest_cleared_post; + } + + template <value_t (*Func)(account_t&)> + value_t get_wrapper(call_scope_t& scope) { + return (*Func)(find_scope<account_t>(scope)); + } + + value_t get_parent(account_t& account) { + return value_t(static_cast<scope_t *>(account.parent)); + } +} + +expr_t::ptr_op_t account_t::lookup(const symbol_t::kind_t kind, + const string& name) +{ + if (kind != symbol_t::FUNCTION) + return NULL; + + switch (name[0]) { + case 'a': + if (name[1] == '\0' || name == "amount") + return WRAP_FUNCTOR(get_wrapper<&get_amount>); + else if (name == "account") + return WRAP_FUNCTOR(get_wrapper<&get_account>); + else if (name == "account_base") + return WRAP_FUNCTOR(get_wrapper<&get_account_base>); + break; + + case 'c': + if (name == "count") + return WRAP_FUNCTOR(get_wrapper<&get_count>); + break; + + case 'd': + if (name == "depth") + return WRAP_FUNCTOR(get_wrapper<&get_depth>); + else if (name == "depth_spacer") + return WRAP_FUNCTOR(get_wrapper<&get_depth_spacer>); + break; + + case 'i': + if (name == "is_account") + return WRAP_FUNCTOR(get_wrapper<&get_true>); + else if (name == "is_index") + return WRAP_FUNCTOR(get_wrapper<&get_subcount>); + break; + + case 'l': + if (name == "latest_cleared") + return WRAP_FUNCTOR(get_wrapper<&get_latest_cleared>); + else if (name[1] == '\0') + return WRAP_FUNCTOR(get_wrapper<&get_depth>); + break; + + case 'n': + if (name[1] == '\0') + return WRAP_FUNCTOR(get_wrapper<&get_subcount>); + break; + + case 'p': + if (name == "partial_account") + return WRAP_FUNCTOR(get_partial_name); + else if (name == "parent") + return WRAP_FUNCTOR(get_wrapper<&get_parent>); + break; + + case 's': + if (name == "subcount") + return WRAP_FUNCTOR(get_wrapper<&get_subcount>); + break; + + case 't': + if (name == "total") + return WRAP_FUNCTOR(get_wrapper<&get_total>); + break; + + case 'u': + if (name == "use_direct_amount") + return WRAP_FUNCTOR(get_wrapper<&ignore>); + break; + + case 'N': + if (name[1] == '\0') + return WRAP_FUNCTOR(get_wrapper<&get_count>); + break; + + case 'O': + if (name[1] == '\0') + return WRAP_FUNCTOR(get_wrapper<&get_total>); + break; + } + + return NULL; +} + +bool account_t::valid() const +{ + if (depth > 256) { + DEBUG("ledger.validate", "account_t: depth > 256"); + return false; + } + + foreach (const accounts_map::value_type& pair, accounts) { + if (this == pair.second) { + DEBUG("ledger.validate", "account_t: parent refers to itself!"); + return false; + } + + if (! pair.second->valid()) { + DEBUG("ledger.validate", "account_t: child not valid"); + return false; + } + } + + return true; +} + +bool account_t::children_with_xdata() const +{ + foreach (const accounts_map::value_type& pair, accounts) + if (pair.second->has_xdata() || + pair.second->children_with_xdata()) + return true; + + return false; +} + +std::size_t account_t::children_with_flags(xdata_t::flags_t flags) const +{ + std::size_t count = 0; + bool grandchildren_visited = false; + + foreach (const accounts_map::value_type& pair, accounts) + if (pair.second->has_xflags(flags) || + pair.second->children_with_flags(flags)) + count++; + + // Although no immediately children were visited, if any progeny at all were + // visited, it counts as one. + if (count == 0 && grandchildren_visited) + count = 1; + + return count; +} + +account_t::xdata_t::details_t& +account_t::xdata_t::details_t::operator+=(const details_t& other) +{ + posts_count += other.posts_count; + posts_virtuals_count += other.posts_virtuals_count; + posts_cleared_count += other.posts_cleared_count; + posts_last_7_count += other.posts_last_7_count; + posts_last_30_count += other.posts_last_30_count; + posts_this_month_count += other.posts_this_month_count; + + if (! is_valid(earliest_post) || + (is_valid(other.earliest_post) && + other.earliest_post < earliest_post)) + earliest_post = other.earliest_post; + if (! is_valid(earliest_cleared_post) || + (is_valid(other.earliest_cleared_post) && + other.earliest_cleared_post < earliest_cleared_post)) + earliest_cleared_post = other.earliest_cleared_post; + + if (! is_valid(latest_post) || + (is_valid(other.latest_post) && + other.latest_post > latest_post)) + latest_post = other.latest_post; + if (! is_valid(latest_cleared_post) || + (is_valid(other.latest_cleared_post) && + other.latest_cleared_post > latest_cleared_post)) + latest_cleared_post = other.latest_cleared_post; + + filenames.insert(other.filenames.begin(), other.filenames.end()); + accounts_referenced.insert(other.accounts_referenced.begin(), + other.accounts_referenced.end()); + payees_referenced.insert(other.payees_referenced.begin(), + other.payees_referenced.end()); + return *this; +} + +void account_t::clear_xdata() +{ + xdata_ = none; + + foreach (accounts_map::value_type& pair, accounts) + if (! pair.second->has_flags(ACCOUNT_TEMP)) + pair.second->clear_xdata(); +} + +value_t account_t::amount(const optional<expr_t&>& expr) const +{ + if (xdata_ && xdata_->has_flags(ACCOUNT_EXT_VISITED)) { + posts_list::const_iterator i; + if (xdata_->self_details.last_post) + i = *xdata_->self_details.last_post; + else + i = posts.begin(); + + for (; i != posts.end(); i++) { + if ((*i)->xdata().has_flags(POST_EXT_VISITED)) { + if (! (*i)->xdata().has_flags(POST_EXT_CONSIDERED)) { + (*i)->add_to_value(xdata_->self_details.total, expr); + (*i)->xdata().add_flags(POST_EXT_CONSIDERED); + } + } + xdata_->self_details.last_post = i; + } + + if (xdata_->self_details.last_reported_post) + i = *xdata_->self_details.last_reported_post; + else + i = xdata_->reported_posts.begin(); + + for (; i != xdata_->reported_posts.end(); i++) { + if ((*i)->xdata().has_flags(POST_EXT_VISITED)) { + if (! (*i)->xdata().has_flags(POST_EXT_CONSIDERED)) { + (*i)->add_to_value(xdata_->self_details.total, expr); + (*i)->xdata().add_flags(POST_EXT_CONSIDERED); + } + } + xdata_->self_details.last_reported_post = i; + } + + return xdata_->self_details.total; + } else { + return NULL_VALUE; + } +} + +value_t account_t::total(const optional<expr_t&>& expr) const +{ + if (! (xdata_ && xdata_->family_details.calculated)) { + const_cast<account_t&>(*this).xdata().family_details.calculated = true; + + value_t temp; + foreach (const accounts_map::value_type& pair, accounts) { + temp = pair.second->total(expr); + if (! temp.is_null()) + add_or_set_value(xdata_->family_details.total, temp); + } + + temp = amount(expr); + if (! temp.is_null()) + add_or_set_value(xdata_->family_details.total, temp); + } + return xdata_->family_details.total; +} + +const account_t::xdata_t::details_t& +account_t::self_details(bool gather_all) const +{ + if (! (xdata_ && xdata_->self_details.gathered)) { + const_cast<account_t&>(*this).xdata().self_details.gathered = true; + + foreach (const post_t * post, posts) + xdata_->self_details.update(const_cast<post_t&>(*post), gather_all); + } + return xdata_->self_details; +} + +const account_t::xdata_t::details_t& +account_t::family_details(bool gather_all) const +{ + if (! (xdata_ && xdata_->family_details.gathered)) { + const_cast<account_t&>(*this).xdata().family_details.gathered = true; + + foreach (const accounts_map::value_type& pair, accounts) + xdata_->family_details += pair.second->family_details(gather_all); + + xdata_->family_details += self_details(gather_all); + } + return xdata_->family_details; +} + +void account_t::xdata_t::details_t::update(post_t& post, + bool gather_all) +{ + posts_count++; + + if (post.has_flags(POST_VIRTUAL)) + posts_virtuals_count++; + + if (gather_all) + filenames.insert(post.pos->pathname); + + date_t date = post.date(); + + if (date.year() == CURRENT_DATE().year() && + date.month() == CURRENT_DATE().month()) + posts_this_month_count++; + + if ((CURRENT_DATE() - date).days() <= 30) + posts_last_30_count++; + if ((CURRENT_DATE() - date).days() <= 7) + posts_last_7_count++; + + if (! is_valid(earliest_post) || post.date() < earliest_post) + earliest_post = post.date(); + if (! is_valid(latest_post) || post.date() > latest_post) + latest_post = post.date(); + + if (post.state() == item_t::CLEARED) { + posts_cleared_count++; + + if (! is_valid(earliest_cleared_post) || + post.date() < earliest_cleared_post) + earliest_cleared_post = post.date(); + if (! is_valid(latest_cleared_post) || + post.date() > latest_cleared_post) + latest_cleared_post = post.date(); + } + + if (gather_all) { + accounts_referenced.insert(post.account->fullname()); + payees_referenced.insert(post.xact->payee); + } +} + +} // namespace ledger diff --git a/src/account.h b/src/account.h new file mode 100644 index 00000000..73cd35ac --- /dev/null +++ b/src/account.h @@ -0,0 +1,273 @@ +/* + * 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. + */ + +/** + * @addtogroup data + */ + +/** + * @file account.h + * @author John Wiegley + * + * @ingroup data + */ +#ifndef _ACCOUNT_H +#define _ACCOUNT_H + +#include "scope.h" + +namespace ledger { + +class account_t; +class xact_t; +class post_t; + +typedef std::list<post_t *> posts_list; +typedef std::map<const string, account_t *> accounts_map; + +class account_t : public supports_flags<>, public scope_t +{ +#define ACCOUNT_NORMAL 0x00 // no flags at all, a basic account +#define ACCOUNT_KNOWN 0x01 +#define ACCOUNT_TEMP 0x02 // account is a temporary object +#define ACCOUNT_GENERATED 0x04 // account never actually existed + +public: + account_t * parent; + string name; + optional<string> note; + unsigned short depth; + accounts_map accounts; + posts_list posts; + + mutable string _fullname; + + account_t(account_t * _parent = NULL, + const string& _name = "", + const optional<string>& _note = none) + : supports_flags<>(), scope_t(), parent(_parent), + name(_name), note(_note), + depth(static_cast<unsigned short>(parent ? parent->depth + 1 : 0)) { + TRACE_CTOR(account_t, "account_t *, const string&, const string&"); + } + account_t(const account_t& other) + : supports_flags<>(other.flags()), scope_t(), + parent(other.parent), + name(other.name), + note(other.note), + depth(other.depth), + accounts(other.accounts) { + TRACE_CTOR(account_t, "copy"); + } + ~account_t(); + + operator string() const { + return fullname(); + } + string fullname() const; + string partial_name(bool flat = false) const; + + void add_account(account_t * acct) { + accounts.insert(accounts_map::value_type(acct->name, acct)); + } + bool remove_account(account_t * acct) { + accounts_map::size_type n = accounts.erase(acct->name); + return n > 0; + } + + account_t * find_account(const string& name, bool auto_create = true); + account_t * find_account_re(const string& regexp); + + typedef transform_iterator<function<account_t *(accounts_map::value_type&)>, + accounts_map::iterator> + accounts_map_seconds_iterator; + + accounts_map_seconds_iterator accounts_begin() { + return make_transform_iterator + (accounts.begin(), bind(&accounts_map::value_type::second, _1)); + } + accounts_map_seconds_iterator accounts_end() { + return make_transform_iterator + (accounts.end(), bind(&accounts_map::value_type::second, _1)); + } + + void add_post(post_t * post) { + posts.push_back(post); + } + bool remove_post(post_t * post); + + posts_list::iterator posts_begin() { + return posts.begin(); + } + posts_list::iterator posts_end() { + return posts.end(); + } + + virtual expr_t::ptr_op_t lookup(const symbol_t::kind_t kind, + const string& name); + + bool valid() const; + + friend class journal_t; + + struct xdata_t : public supports_flags<> + { +#define ACCOUNT_EXT_SORT_CALC 0x01 +#define ACCOUNT_EXT_HAS_NON_VIRTUALS 0x02 +#define ACCOUNT_EXT_HAS_UNB_VIRTUALS 0x04 +#define ACCOUNT_EXT_AUTO_VIRTUALIZE 0x08 +#define ACCOUNT_EXT_VISITED 0x10 +#define ACCOUNT_EXT_MATCHING 0x20 +#define ACCOUNT_EXT_TO_DISPLAY 0x40 +#define ACCOUNT_EXT_DISPLAYED 0x80 + + struct details_t + { + value_t total; + bool calculated; + bool gathered; + + std::size_t posts_count; + std::size_t posts_virtuals_count; + std::size_t posts_cleared_count; + std::size_t posts_last_7_count; + std::size_t posts_last_30_count; + std::size_t posts_this_month_count; + + date_t earliest_post; + date_t earliest_cleared_post; + date_t latest_post; + date_t latest_cleared_post; + + std::set<path> filenames; + std::set<string> accounts_referenced; + std::set<string> payees_referenced; + + optional<posts_list::const_iterator> last_post; + optional<posts_list::const_iterator> last_reported_post; + + details_t() + : calculated(false), + gathered(false), + + posts_count(0), + posts_virtuals_count(0), + posts_cleared_count(0), + posts_last_7_count(0), + posts_last_30_count(0), + posts_this_month_count(0) {} + + details_t& operator+=(const details_t& other); + + void update(post_t& post, bool gather_all = false); + }; + + details_t self_details; + details_t family_details; + posts_list reported_posts; + + std::list<sort_value_t> sort_values; + + xdata_t() : supports_flags<>() + { + TRACE_CTOR(account_t::xdata_t, ""); + } + xdata_t(const xdata_t& other) + : supports_flags<>(other.flags()), + self_details(other.self_details), + family_details(other.family_details), + sort_values(other.sort_values) + { + TRACE_CTOR(account_t::xdata_t, "copy"); + } + ~xdata_t() throw() { + TRACE_DTOR(account_t::xdata_t); + } + }; + + // This variable holds optional "extended data" which is usually produced + // only during reporting, and only for the posting set being reported. + // It's a memory-saving measure to delay allocation until the last possible + // moment. + mutable optional<xdata_t> xdata_; + + bool has_xdata() const { + return xdata_; + } + void clear_xdata(); + xdata_t& xdata() { + if (! xdata_) + xdata_ = xdata_t(); + return *xdata_; + } + const xdata_t& xdata() const { + assert(xdata_); + return *xdata_; + } + + value_t amount(const optional<expr_t&>& expr = none) const; + value_t total(const optional<expr_t&>& expr = none) const; + + const xdata_t::details_t& self_details(bool gather_all = true) const; + const xdata_t::details_t& family_details(bool gather_all = true) const; + + bool has_xflags(xdata_t::flags_t flags) const { + return xdata_ && xdata_->has_flags(flags); + } + bool children_with_xdata() const; + std::size_t children_with_flags(xdata_t::flags_t flags) const; + +#if defined(HAVE_BOOST_SERIALIZATION) +private: + /** Serialization. */ + + friend class boost::serialization::access; + + template<class Archive> + void serialize(Archive& ar, const unsigned int /* version */) { + ar & boost::serialization::base_object<supports_flags<> >(*this); + ar & boost::serialization::base_object<scope_t>(*this); + ar & parent; + ar & name; + ar & note; + ar & depth; + ar & accounts; + ar & posts; + ar & _fullname; + } +#endif // HAVE_BOOST_SERIALIZATION +}; + +std::ostream& operator<<(std::ostream& out, const account_t& account); + +} // namespace ledger + +#endif // _ACCOUNT_H diff --git a/src/data/jbuilder.cc b/src/accum.cc index f37abca5..b918c76a 100644 --- a/src/data/jbuilder.cc +++ b/src/accum.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2007, John Wiegley. All rights reserved. + * 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 @@ -29,39 +29,48 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "jbuilder.h" -#include "compile.h" +#include <system.hh> + +#include "utils.h" namespace ledger { -namespace xml { -void journal_builder_t::begin_node(const node_t::nameid_t name_id, - bool terminal) +std::streamsize straccbuf::xsputn(const char * s, std::streamsize num) { - switch (name_id) { - case JOURNAL_NODE: - current = current->as_parent_node().create_child<journal_node_t>(name_id); - break; - case ENTRY_NODE: - current = current->as_parent_node().create_child<entry_node_t>(name_id); - break; - case TRANSACTION_NODE: - current = current->as_parent_node().create_child<transaction_node_t>(name_id); - break; - - default: - if (terminal) - current = current->as_parent_node().create_child<terminal_node_t>(name_id); - else - current = current->as_parent_node().create_child<parent_node_t>(name_id); - break; + if (index == 0) { + // The first item received is the format string + str = std::string(s, num); + index++; + return num; } + else { + std::ostringstream buf; - foreach (const attrs_list::value_type& pair, current_attrs) - current->set_attr(pair.first, pair.second.c_str()); + // Every item thereafter is an argument that substitutes for %# in the + // format string + bool matched = false; + for (const char * p = str.c_str(); *p; p++) { + if (*p == '%') { + const char * q = p + 1; + if (*q && *q != '%' && std::isdigit(*q) && + std::string::size_type(*q - '0') == index) { + p++; + buf << std::string(s, num); + matched = true; + } else { + buf << *p; + } + } else { + buf << *p; + } + } + if (! matched) + buf << std::string(s, num); - current_attrs.clear(); + str = buf.str(); + index++; + return num; + } } -} // namespace xml } // namespace ledger diff --git a/src/accum.h b/src/accum.h new file mode 100644 index 00000000..878c2b7c --- /dev/null +++ b/src/accum.h @@ -0,0 +1,86 @@ +/* + * 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. + */ + +/** + * @addtogroup util + */ + +/** + * @file accum.h + * @author John Wiegley + * + * @ingroup util + */ +#ifndef _ACCUM_H +#define _ACCUM_H + +namespace ledger { + +class straccbuf : public std::streambuf +{ +protected: + std::string str; // accumulator + std::string::size_type index; + +public: + straccbuf() : index(0) {} + +protected: + virtual std::streamsize xsputn(const char * s, std::streamsize num); + + friend class straccstream; +}; + +class straccstream : public std::ostream +{ +protected: + straccbuf buf; + +public: + straccstream() : std::ostream(0) { + rdbuf(&buf); + } + + void clear() { + buf.str.clear(); + buf.index = 0; + } + + std::string str() const { + return buf.str; + } +}; + +#define ACCUM(obj) (static_cast<straccstream&>(obj).str()) + +} // namespace ledger + +#endif // _ACCUM_H diff --git a/src/amount.cc b/src/amount.cc new file mode 100644 index 00000000..eddbca18 --- /dev/null +++ b/src/amount.cc @@ -0,0 +1,1278 @@ +/* + * 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 "amount.h" +#include "commodity.h" +#include "annotate.h" +#include "pool.h" + +namespace ledger { + +bool amount_t::stream_fullstrings = false; + +#if !defined(THREADSAFE) +// These global temporaries are pre-initialized for the sake of +// efficiency, and are reused over and over again. +static mpz_t temp; +static mpq_t tempq; +static mpfr_t tempf; +static mpfr_t tempfb; +#endif + +struct amount_t::bigint_t : public supports_flags<> +{ +#define BIGINT_BULK_ALLOC 0x01 +#define BIGINT_KEEP_PREC 0x02 + + mpq_t val; + precision_t prec; + uint_least32_t refc; + +#define MP(bigint) ((bigint)->val) + + bigint_t() : prec(0), refc(1) { + TRACE_CTOR(bigint_t, ""); + mpq_init(val); + } + bigint_t(const bigint_t& other) + : supports_flags<>(static_cast<uint_least8_t> + (other.flags() & ~BIGINT_BULK_ALLOC)), + prec(other.prec), refc(1) { + TRACE_CTOR(bigint_t, "copy"); + mpq_init(val); + mpq_set(val, other.val); + } + ~bigint_t() { + TRACE_DTOR(bigint_t); + assert(refc == 0); + mpq_clear(val); + } + + bool valid() const { + if (prec > 1024) { + DEBUG("ledger.validate", "amount_t::bigint_t: prec > 1024"); + return false; + } + if (flags() & ~(BIGINT_BULK_ALLOC | BIGINT_KEEP_PREC)) { + DEBUG("ledger.validate", + "amount_t::bigint_t: flags() & ~(BULK_ALLOC | KEEP_PREC)"); + return false; + } + return true; + } + +#if defined(HAVE_BOOST_SERIALIZATION) +private: + friend class boost::serialization::access; + + template<class Archive> + void serialize(Archive& ar, const unsigned int /* version */) + { + ar & boost::serialization::base_object<supports_flags<> >(*this); + ar & val; + ar & prec; + ar & refc; + } +#endif // HAVE_BOOST_SERIALIZATION +}; + +bool amount_t::is_initialized = false; + +namespace { + void stream_out_mpq(std::ostream& out, + mpq_t quant, + amount_t::precision_t prec, + int zeros_prec = -1, + const optional<commodity_t&>& comm = none) + { + char * buf = NULL; + try { + IF_DEBUG("amount.convert") { + char * tbuf = mpq_get_str(NULL, 10, quant); + DEBUG("amount.convert", "Rational to convert = " << tbuf); + std::free(tbuf); + } + + // Convert the rational number to a floating-point, extending the + // floating-point to a large enough size to get a precise answer. + const std::size_t bits = (mpz_sizeinbase(mpq_numref(quant), 2) + + mpz_sizeinbase(mpq_denref(quant), 2)); + mpfr_set_prec(tempfb, bits + amount_t::extend_by_digits*8); + mpfr_set_q(tempfb, quant, GMP_RNDN); + + mpfr_asprintf(&buf, "%.*Rf", prec, tempfb); + DEBUG("amount.convert", + "mpfr_print = " << buf << " (precision " << prec << ")"); + + if (zeros_prec >= 0) { + string::size_type index = std::strlen(buf); + string::size_type point = 0; + for (string::size_type i = 0; i < index; i++) { + if (buf[i] == '.') { + point = i; + break; + } + } + if (point > 0) { + while (--index >= (point + 1 + zeros_prec) && buf[index] == '0') + buf[index] = '\0'; + if (index >= (point + zeros_prec) && buf[index] == '.') + buf[index] = '\0'; + } + } + + if (comm) { + int integer_digits = 0; + if (comm && comm->has_flags(COMMODITY_STYLE_THOUSANDS)) { + // Count the number of integer digits + for (const char * p = buf; *p; p++) { + if (*p == '.') + break; + else if (*p != '-') + integer_digits++; + } + } + + for (const char * p = buf; *p; p++) { + if (*p == '.') { + if (commodity_t::european_by_default || + (comm && comm->has_flags(COMMODITY_STYLE_EUROPEAN))) + out << ','; + else + out << *p; + assert(integer_digits <= 3); + } + else if (*p == '-') { + out << *p; + } + else { + out << *p; + + if (integer_digits > 3 && --integer_digits % 3 == 0) { + if (commodity_t::european_by_default || + (comm && comm->has_flags(COMMODITY_STYLE_EUROPEAN))) + out << '.'; + else + out << ','; + } + } + } + } else { + out << buf; + } + } + catch (...) { + if (buf != NULL) + mpfr_free_str(buf); + throw; + } + if (buf != NULL) + mpfr_free_str(buf); + } +} + +void amount_t::initialize() +{ + if (! is_initialized) { + mpz_init(temp); + mpq_init(tempq); + mpfr_init(tempf); + mpfr_init(tempfb); + + commodity_pool_t::current_pool.reset(new commodity_pool_t); + + // Add time commodity conversions, so that timelog's may be parsed + // in terms of seconds, but reported as minutes or hours. + if (commodity_t * commodity = commodity_pool_t::current_pool->create("s")) + commodity->add_flags(COMMODITY_BUILTIN | COMMODITY_NOMARKET); + else + assert(false); + + // Add a "percentile" commodity + if (commodity_t * commodity = commodity_pool_t::current_pool->create("%")) + commodity->add_flags(COMMODITY_BUILTIN | COMMODITY_NOMARKET); + else + assert(false); + + is_initialized = true; + } +} + +void amount_t::shutdown() +{ + if (is_initialized) { + mpz_clear(temp); + mpq_clear(tempq); + mpfr_clear(tempf); + mpfr_clear(tempfb); + + commodity_pool_t::current_pool.reset(); + + is_initialized = false; + } +} + +void amount_t::_copy(const amount_t& amt) +{ + VERIFY(amt.valid()); + + if (quantity != amt.quantity) { + if (quantity) + _release(); + + // Never maintain a pointer into a bulk allocation pool; such + // pointers are not guaranteed to remain. + if (amt.quantity->has_flags(BIGINT_BULK_ALLOC)) { + quantity = new bigint_t(*amt.quantity); + } else { + quantity = amt.quantity; + DEBUG("amounts.refs", + quantity << " refc++, now " << (quantity->refc + 1)); + quantity->refc++; + } + } + commodity_ = amt.commodity_; + + VERIFY(valid()); +} + +void amount_t::_dup() +{ + VERIFY(valid()); + + if (quantity->refc > 1) { + bigint_t * q = new bigint_t(*quantity); + _release(); + quantity = q; + } + + VERIFY(valid()); +} + +void amount_t::_clear() +{ + if (quantity) { + _release(); + quantity = NULL; + commodity_ = NULL; + } else { + assert(! commodity_); + } +} + +void amount_t::_release() +{ + VERIFY(valid()); + + DEBUG("amounts.refs", quantity << " refc--, now " << (quantity->refc - 1)); + + if (--quantity->refc == 0) { + if (quantity->has_flags(BIGINT_BULK_ALLOC)) + quantity->~bigint_t(); + else + checked_delete(quantity); + quantity = NULL; + commodity_ = NULL; + } + + VERIFY(valid()); +} + + +amount_t::amount_t(const double val) : commodity_(NULL) +{ + TRACE_CTOR(amount_t, "const double"); + quantity = new bigint_t; + mpq_set_d(MP(quantity), val); + quantity->prec = extend_by_digits; // an approximation +} + +amount_t::amount_t(const unsigned long val) : commodity_(NULL) +{ + TRACE_CTOR(amount_t, "const unsigned long"); + quantity = new bigint_t; + mpq_set_ui(MP(quantity), val, 1); +} + +amount_t::amount_t(const long val) : commodity_(NULL) +{ + TRACE_CTOR(amount_t, "const long"); + quantity = new bigint_t; + mpq_set_si(MP(quantity), val, 1); +} + + +amount_t& amount_t::operator=(const amount_t& amt) +{ + if (this != &amt) { + if (amt.quantity) + _copy(amt); + else if (quantity) + _clear(); + } + return *this; +} + + +int amount_t::compare(const amount_t& amt) const +{ + VERIFY(amt.valid()); + + if (! quantity || ! amt.quantity) { + if (quantity) + throw_(amount_error, _("Cannot compare an amount to an uninitialized amount")); + else if (amt.quantity) + throw_(amount_error, _("Cannot compare an uninitialized amount to an amount")); + else + throw_(amount_error, _("Cannot compare two uninitialized amounts")); + } + + if (has_commodity() && amt.has_commodity() && + commodity() != amt.commodity()) + throw_(amount_error, + _("Cannot compare amounts with different commodities: %1 and %2") + << commodity().symbol() << amt.commodity().symbol()); + + return mpq_cmp(MP(quantity), MP(amt.quantity)); +} + +bool amount_t::operator==(const amount_t& amt) const +{ + if ((quantity && ! amt.quantity) || (! quantity && amt.quantity)) + return false; + else if (! quantity && ! amt.quantity) + return true; + else if (commodity() != amt.commodity()) + return false; + + return mpq_equal(MP(quantity), MP(amt.quantity)); +} + + +amount_t& amount_t::operator+=(const amount_t& amt) +{ + VERIFY(amt.valid()); + + if (! quantity || ! amt.quantity) { + if (quantity) + throw_(amount_error, _("Cannot add an uninitialized amount to an amount")); + else if (amt.quantity) + throw_(amount_error, _("Cannot add an amount to an uninitialized amount")); + else + throw_(amount_error, _("Cannot add two uninitialized amounts")); + } + + if (has_commodity() && amt.has_commodity() && + commodity() != amt.commodity()) + throw_(amount_error, + _("Adding amounts with different commodities: %1 != %2") + << (has_commodity() ? commodity().symbol() : _("NONE")) + << (amt.has_commodity() ? amt.commodity().symbol() : _("NONE"))); + + _dup(); + + mpq_add(MP(quantity), MP(quantity), MP(amt.quantity)); + + if (has_commodity() == amt.has_commodity()) + if (quantity->prec < amt.quantity->prec) + quantity->prec = amt.quantity->prec; + + return *this; +} + +amount_t& amount_t::operator-=(const amount_t& amt) +{ + VERIFY(amt.valid()); + + if (! quantity || ! amt.quantity) { + if (quantity) + throw_(amount_error, _("Cannot subtract an amount from an uninitialized amount")); + else if (amt.quantity) + throw_(amount_error, _("Cannot subtract an uninitialized amount from an amount")); + else + throw_(amount_error, _("Cannot subtract two uninitialized amounts")); + } + + if (has_commodity() && amt.has_commodity() && + commodity() != amt.commodity()) + throw_(amount_error, + _("Subtracting amounts with different commodities: %1 != %2") + << (has_commodity() ? commodity().symbol() : _("NONE")) + << (amt.has_commodity() ? amt.commodity().symbol() : _("NONE"))); + + _dup(); + + mpq_sub(MP(quantity), MP(quantity), MP(amt.quantity)); + + if (has_commodity() == amt.has_commodity()) + if (quantity->prec < amt.quantity->prec) + quantity->prec = amt.quantity->prec; + + return *this; +} + +amount_t& amount_t::operator*=(const amount_t& amt) +{ + VERIFY(amt.valid()); + + if (! quantity || ! amt.quantity) { + if (quantity) + throw_(amount_error, _("Cannot multiply an amount by an uninitialized amount")); + else if (amt.quantity) + throw_(amount_error, _("Cannot multiply an uninitialized amount by an amount")); + else + throw_(amount_error, _("Cannot multiply two uninitialized amounts")); + } + + _dup(); + + mpq_mul(MP(quantity), MP(quantity), MP(amt.quantity)); + quantity->prec = + static_cast<precision_t>(quantity->prec + amt.quantity->prec); + + if (! has_commodity()) + commodity_ = amt.commodity_; + + if (has_commodity() && ! keep_precision()) { + precision_t comm_prec = commodity().precision(); + if (quantity->prec > comm_prec + extend_by_digits) + quantity->prec = static_cast<precision_t>(comm_prec + extend_by_digits); + } + + return *this; +} + +amount_t& amount_t::operator/=(const amount_t& amt) +{ + VERIFY(amt.valid()); + + if (! quantity || ! amt.quantity) { + if (quantity) + throw_(amount_error, _("Cannot divide an amount by an uninitialized amount")); + else if (amt.quantity) + throw_(amount_error, _("Cannot divide an uninitialized amount by an amount")); + else + throw_(amount_error, _("Cannot divide two uninitialized amounts")); + } + + if (! amt) + throw_(amount_error, _("Divide by zero")); + + _dup(); + + // Increase the value's precision, to capture fractional parts after + // the divide. Round up in the last position. + + mpq_div(MP(quantity), MP(quantity), MP(amt.quantity)); + quantity->prec = + static_cast<precision_t>(quantity->prec + amt.quantity->prec + + extend_by_digits); + + if (! has_commodity()) + commodity_ = amt.commodity_; + + // If this amount has a commodity, and we're not dealing with plain + // numbers, or internal numbers (which keep full precision at all + // times), then round the number to within the commodity's precision + // plus six places. + + if (has_commodity() && ! keep_precision()) { + precision_t comm_prec = commodity().precision(); + if (quantity->prec > comm_prec + extend_by_digits) + quantity->prec = static_cast<precision_t>(comm_prec + extend_by_digits); + } + + return *this; +} + +amount_t::precision_t amount_t::precision() const +{ + if (! quantity) + throw_(amount_error, + _("Cannot determine precision of an uninitialized amount")); + + return quantity->prec; +} + +bool amount_t::keep_precision() const +{ + if (! quantity) + throw_(amount_error, + _("Cannot determine if precision of an uninitialized amount is kept")); + + return quantity->has_flags(BIGINT_KEEP_PREC); +} + +void amount_t::set_keep_precision(const bool keep) const +{ + if (! quantity) + throw_(amount_error, + _("Cannot set whether to keep the precision of an uninitialized amount")); + + if (keep) + quantity->add_flags(BIGINT_KEEP_PREC); + else + quantity->drop_flags(BIGINT_KEEP_PREC); +} + +amount_t::precision_t amount_t::display_precision() const +{ + if (! quantity) + throw_(amount_error, + _("Cannot determine display precision of an uninitialized amount")); + + commodity_t& comm(commodity()); + + if (! comm || keep_precision()) + return quantity->prec; + else if (comm.precision() != quantity->prec) + return comm.precision(); + else + return quantity->prec; +} + +void amount_t::in_place_negate() +{ + if (quantity) { + _dup(); + mpq_neg(MP(quantity), MP(quantity)); + } else { + throw_(amount_error, _("Cannot negate an uninitialized amount")); + } +} + +amount_t amount_t::inverted() const +{ + if (! quantity) + throw_(amount_error, _("Cannot invert an uninitialized amount")); + + amount_t t(*this); + t._dup(); + mpq_inv(MP(t.quantity), MP(t.quantity)); + + return t; +} + +void amount_t::in_place_round() +{ + if (! quantity) + throw_(amount_error, _("Cannot set rounding for an uninitialized amount")); + else if (! keep_precision()) + return; + + _dup(); + set_keep_precision(false); +} + +void amount_t::in_place_floor() +{ + if (! quantity) + throw_(amount_error, _("Cannot floor an uninitialized amount")); + + _dup(); + + std::ostringstream out; + stream_out_mpq(out, MP(quantity), 0); + + mpq_set_str(MP(quantity), out.str().c_str(), 10); +} + +void amount_t::in_place_unround() +{ + if (! quantity) + throw_(amount_error, _("Cannot unround an uninitialized amount")); + else if (keep_precision()) + return; + + _dup(); + + DEBUG("amount.unround", "Unrounding " << *this); + set_keep_precision(true); + DEBUG("amount.unround", "Unrounded = " << *this); +} + +void amount_t::in_place_reduce() +{ + if (! quantity) + throw_(amount_error, _("Cannot reduce an uninitialized amount")); + + while (commodity_ && commodity().smaller()) { + *this *= commodity().smaller()->number(); + commodity_ = commodity().smaller()->commodity_; + } +} + +void amount_t::in_place_unreduce() +{ + if (! quantity) + throw_(amount_error, _("Cannot unreduce an uninitialized amount")); + + amount_t temp = *this; + commodity_t * comm = commodity_; + bool shifted = false; + + while (comm && comm->larger()) { + amount_t next_temp = temp / comm->larger()->number(); + if (next_temp.abs() < amount_t(1L)) + break; + temp = next_temp; + comm = comm->larger()->commodity_; + shifted = true; + } + + if (shifted) { + *this = temp; + commodity_ = comm; + } +} + +optional<amount_t> +amount_t::value(const bool primary_only, + const optional<datetime_t>& moment, + const optional<commodity_t&>& in_terms_of) const +{ + if (quantity) { +#if defined(DEBUG_ON) + DEBUG("commodity.prices.find", + "amount_t::value of " << commodity().symbol()); + if (moment) + DEBUG("commodity.prices.find", + "amount_t::value: moment = " << *moment); + if (in_terms_of) + DEBUG("commodity.prices.find", + "amount_t::value: in_terms_of = " << in_terms_of->symbol()); +#endif + if (has_commodity() && + (! primary_only || ! commodity().has_flags(COMMODITY_PRIMARY))) { + if (in_terms_of && commodity() == *in_terms_of) { + return *this; + } + else if (has_annotation() && annotation().price && + annotation().has_flags(ANNOTATION_PRICE_FIXATED)) { + return (*annotation().price * number()).rounded(); + } + else { + optional<price_point_t> point = + commodity().find_price(in_terms_of, moment); + + // Whether a price was found or not, check whether we should attempt + // to download a price from the Internet. This is done if (a) no + // price was found, or (b) the price is "stale" according to the + // setting of --price-exp. + point = commodity().check_for_updated_price(point, moment, in_terms_of); + if (point) + return (point->price * number()).rounded(); + } + } + } else { + throw_(amount_error, + _("Cannot determine value of an uninitialized amount")); + } + return none; +} + +amount_t amount_t::price() const +{ + if (has_annotation() && annotation().price) { + amount_t temp(*annotation().price); + temp *= *this; + DEBUG("amount.price", "Returning price of " << *this << " = " << temp); + return temp; + } + return *this; +} + + +int amount_t::sign() const +{ + if (! quantity) + throw_(amount_error, _("Cannot determine sign of an uninitialized amount")); + + return mpq_sgn(MP(quantity)); +} + +bool amount_t::is_zero() const +{ + if (! quantity) + throw_(amount_error, _("Cannot determine if an uninitialized amount is zero")); + + if (has_commodity()) { + if (keep_precision() || quantity->prec <= commodity().precision()) { + return is_realzero(); + } + else if (is_realzero()) { + return true; + } + else if (mpz_cmp(mpq_numref(MP(quantity)), + mpq_denref(MP(quantity))) > 0) { + DEBUG("amount.is_zero", "Numerator is larger than the denominator"); + return false; + } + else { + DEBUG("amount.is_zero", "We have to print the number to check for zero"); + + std::ostringstream out; + stream_out_mpq(out, MP(quantity), commodity().precision()); + + string output = out.str(); + if (! output.empty()) { + for (const char * p = output.c_str(); *p; p++) + if (*p != '0' && *p != '.' && *p != '-') + return false; + } + return true; + } + } + return is_realzero(); +} + + +double amount_t::to_double() const +{ + if (! quantity) + throw_(amount_error, _("Cannot convert an uninitialized amount to a double")); + + mpfr_set_q(tempf, MP(quantity), GMP_RNDN); + return mpfr_get_d(tempf, GMP_RNDN); +} + +long amount_t::to_long() const +{ + if (! quantity) + throw_(amount_error, _("Cannot convert an uninitialized amount to a long")); + + mpfr_set_q(tempf, MP(quantity), GMP_RNDN); + return mpfr_get_si(tempf, GMP_RNDN); +} + +bool amount_t::fits_in_long() const +{ + mpfr_set_q(tempf, MP(quantity), GMP_RNDN); + return mpfr_fits_slong_p(tempf, GMP_RNDN); +} + +commodity_t& amount_t::commodity() const +{ + return (has_commodity() ? + *commodity_ : *commodity_pool_t::current_pool->null_commodity); +} + +bool amount_t::has_commodity() const +{ + return commodity_ && commodity_ != commodity_->pool().null_commodity; +} + +void amount_t::annotate(const annotation_t& details) +{ + commodity_t * this_base; + annotated_commodity_t * this_ann = NULL; + + if (! quantity) + throw_(amount_error, _("Cannot annotate the commodity of an uninitialized amount")); + else if (! has_commodity()) + return; // ignore attempt to annotate a "bare commodity + + if (commodity().has_annotation()) { + this_ann = &as_annotated_commodity(commodity()); + this_base = &this_ann->referent(); + } else { + this_base = &commodity(); + } + assert(this_base); + + DEBUG("amounts.commodities", "Annotating commodity for amount " + << *this << std::endl << details); + + if (commodity_t * ann_comm = + this_base->pool().find_or_create(*this_base, details)) + set_commodity(*ann_comm); +#ifdef ASSERTS_ON + else + assert(false); +#endif + + DEBUG("amounts.commodities", "Annotated amount is " << *this); +} + +bool amount_t::has_annotation() const +{ + if (! quantity) + throw_(amount_error, + _("Cannot determine if an uninitialized amount's commodity is annotated")); + + assert(! has_commodity() || ! commodity().has_annotation() || + as_annotated_commodity(commodity()).details); + return has_commodity() && commodity().has_annotation(); +} + +annotation_t& amount_t::annotation() +{ + if (! quantity) + throw_(amount_error, + _("Cannot return commodity annotation details of an uninitialized amount")); + + if (! commodity().has_annotation()) + throw_(amount_error, + _("Request for annotation details from an unannotated amount")); + + annotated_commodity_t& ann_comm(as_annotated_commodity(commodity())); + return ann_comm.details; +} + +amount_t amount_t::strip_annotations(const keep_details_t& what_to_keep) const +{ + if (! quantity) + throw_(amount_error, + _("Cannot strip commodity annotations from an uninitialized amount")); + + if (! what_to_keep.keep_all(commodity())) { + amount_t t(*this); + t.set_commodity(commodity().strip_annotations(what_to_keep)); + return t; + } + return *this; +} + + +namespace { + void parse_quantity(std::istream& in, string& value) + { + char buf[256]; + char c = peek_next_nonws(in); + READ_INTO(in, buf, 255, c, + std::isdigit(c) || c == '-' || c == '.' || c == ','); + + string::size_type len = std::strlen(buf); + while (len > 0 && ! std::isdigit(buf[len - 1])) { + buf[--len] = '\0'; + in.unget(); + } + + value = buf; + } +} + +bool amount_t::parse(std::istream& in, const parse_flags_t& flags) +{ + // The possible syntax for an amount is: + // + // [-]NUM[ ]SYM [@ AMOUNT] + // SYM[ ][-]NUM [@ AMOUNT] + + string symbol; + string quant; + annotation_t details; + bool negative = false; + + commodity_t::flags_t comm_flags = COMMODITY_STYLE_DEFAULTS; + + char c = peek_next_nonws(in); + if (c == '-') { + negative = true; + in.get(c); + c = peek_next_nonws(in); + } + + char n; + if (std::isdigit(c)) { + parse_quantity(in, quant); + + if (! in.eof() && ((n = static_cast<char>(in.peek())) != '\n')) { + if (std::isspace(n)) + comm_flags |= COMMODITY_STYLE_SEPARATED; + + commodity_t::parse_symbol(in, symbol); + + if (! symbol.empty()) + comm_flags |= COMMODITY_STYLE_SUFFIXED; + + if (! in.eof() && ((n = static_cast<char>(in.peek())) != '\n')) + details.parse(in); + } + } else { + commodity_t::parse_symbol(in, symbol); + + if (! in.eof() && ((n = static_cast<char>(in.peek())) != '\n')) { + if (std::isspace(static_cast<char>(in.peek()))) + comm_flags |= COMMODITY_STYLE_SEPARATED; + + parse_quantity(in, quant); + + if (! quant.empty() && ! in.eof() && + ((n = static_cast<char>(in.peek())) != '\n')) + details.parse(in); + } + } + + if (quant.empty()) { + if (flags.has_flags(PARSE_SOFT_FAIL)) + return false; + else + throw_(amount_error, _("No quantity specified for amount")); + } + + // Allocate memory for the amount's quantity value. We have to + // monitor the allocation in an auto_ptr because this function gets + // called sometimes from amount_t's constructor; and if there is an + // exeception thrown by any of the function calls after this point, + // the destructor will never be called and the memory never freed. + + std::auto_ptr<bigint_t> new_quantity; + + if (quantity) { + if (quantity->refc > 1) + _release(); + else + new_quantity.reset(quantity); + quantity = NULL; + } + + if (! new_quantity.get()) + new_quantity.reset(new bigint_t); + + // No one is holding a reference to this now. + new_quantity->refc--; + + // Create the commodity if has not already been seen, and update the + // precision if something greater was used for the quantity. + + bool newly_created = false; + + if (symbol.empty()) { + commodity_ = NULL; + } else { + commodity_ = commodity_pool_t::current_pool->find(symbol); + if (! commodity_) { + commodity_ = commodity_pool_t::current_pool->create(symbol); + newly_created = true; + } + assert(commodity_); + + if (details) + commodity_ = + commodity_pool_t::current_pool->find_or_create(*commodity_, details); + } + + // Quickly scan through and verify the correctness of the amount's use of + // punctuation. + + precision_t decimal_offset = 0; + string::size_type string_index = quant.length(); + string::size_type last_comma = string::npos; + string::size_type last_period = string::npos; + + bool no_more_commas = false; + bool no_more_periods = false; + bool european_style = (commodity_t::european_by_default || + commodity().has_flags(COMMODITY_STYLE_EUROPEAN)); + + new_quantity->prec = 0; + + BOOST_REVERSE_FOREACH (const char& ch, quant) { + string_index--; + + if (ch == '.') { + if (no_more_periods) + throw_(amount_error, _("Too many periods in amount")); + + if (european_style) { + if (decimal_offset % 3 != 0) + throw_(amount_error, _("Incorrect use of european-style period")); + comm_flags |= COMMODITY_STYLE_THOUSANDS; + no_more_commas = true; + } else { + if (last_comma != string::npos) { + european_style = true; + if (decimal_offset % 3 != 0) + throw_(amount_error, _("Incorrect use of european-style period")); + } else { + no_more_periods = true; + new_quantity->prec = decimal_offset; + decimal_offset = 0; + } + } + + if (last_period == string::npos) + last_period = string_index; + } + else if (ch == ',') { + if (no_more_commas) + throw_(amount_error, _("Too many commas in amount")); + + if (european_style) { + if (last_period != string::npos) { + throw_(amount_error, _("Incorrect use of european-style comma")); + } else { + no_more_commas = true; + new_quantity->prec = decimal_offset; + decimal_offset = 0; + } + } else { + if (decimal_offset % 3 != 0) { + if (last_comma != string::npos || + last_period != string::npos) { + throw_(amount_error, _("Incorrect use of American-style comma")); + } else { + european_style = true; + no_more_commas = true; + new_quantity->prec = decimal_offset; + decimal_offset = 0; + } + } else { + comm_flags |= COMMODITY_STYLE_THOUSANDS; + no_more_periods = true; + } + } + + if (last_comma == string::npos) + last_comma = string_index; + } + else { + decimal_offset++; + } + } + + if (european_style) + comm_flags |= COMMODITY_STYLE_EUROPEAN; + + if (flags.has_flags(PARSE_NO_MIGRATE)) { + // Can't call set_keep_precision here, because it assumes that `quantity' + // is non-NULL. + new_quantity->add_flags(BIGINT_KEEP_PREC); + } + else if (commodity_) { + commodity().add_flags(comm_flags); + + if (new_quantity->prec > commodity().precision()) + commodity().set_precision(new_quantity->prec); + } + + // Now we have the final number. Remove commas and periods, if necessary. + + if (last_comma != string::npos || last_period != string::npos) { + string::size_type len = quant.length(); + scoped_array<char> buf(new char[len + 1]); + const char * p = quant.c_str(); + char * t = buf.get(); + + while (*p) { + if (*p == ',' || *p == '.') + p++; + *t++ = *p++; + } + *t = '\0'; + + mpq_set_str(MP(new_quantity.get()), buf.get(), 10); + mpz_ui_pow_ui(temp, 10, new_quantity->prec); + mpq_set_z(tempq, temp); + mpq_div(MP(new_quantity.get()), MP(new_quantity.get()), tempq); + + IF_DEBUG("amount.parse") { + char * buf = mpq_get_str(NULL, 10, MP(new_quantity.get())); + DEBUG("amount.parse", "Rational parsed = " << buf); + std::free(buf); + } + } else { + mpq_set_str(MP(new_quantity.get()), quant.c_str(), 10); + } + + if (negative) + mpq_neg(MP(new_quantity.get()), MP(new_quantity.get())); + + new_quantity->refc++; + quantity = new_quantity.release(); + + if (! flags.has_flags(PARSE_NO_REDUCE)) + in_place_reduce(); // will not throw an exception + + VERIFY(valid()); + + return true; +} + +void amount_t::parse_conversion(const string& larger_str, + const string& smaller_str) +{ + amount_t larger, smaller; + + larger.parse(larger_str, PARSE_NO_REDUCE); + smaller.parse(smaller_str, PARSE_NO_REDUCE); + + larger *= smaller.number(); + + if (larger.commodity()) { + larger.commodity().set_smaller(smaller); + larger.commodity().add_flags(smaller.commodity().flags() | + COMMODITY_NOMARKET); + } + if (smaller.commodity()) + smaller.commodity().set_larger(larger); +} + +void amount_t::print(std::ostream& _out) const +{ + VERIFY(valid()); + + if (! quantity) { + _out << "<null>"; + return; + } + + std::ostringstream out; + + commodity_t& comm(commodity()); + + if (! comm.has_flags(COMMODITY_STYLE_SUFFIXED)) { + comm.print(out); + if (comm.has_flags(COMMODITY_STYLE_SEPARATED)) + out << " "; + } + + stream_out_mpq(out, MP(quantity), display_precision(), + comm ? commodity().precision() : 0, comm); + + if (comm.has_flags(COMMODITY_STYLE_SUFFIXED)) { + if (comm.has_flags(COMMODITY_STYLE_SEPARATED)) + out << " "; + comm.print(out); + } + + // If there are any annotations associated with this commodity, output them + // now. + comm.write_annotations(out); + + // Things are output to a string first, so that if anyone has specified a + // width or fill for _out, it will be applied to the entire amount string, + // and not just the first part. + _out << out.str(); +} + +bool amount_t::valid() const +{ + if (quantity) { + if (! quantity->valid()) { + DEBUG("ledger.validate", "amount_t: ! quantity->valid()"); + return false; + } + + if (quantity->refc == 0) { + DEBUG("ledger.validate", "amount_t: quantity->refc == 0"); + return false; + } + } + else if (commodity_) { + DEBUG("ledger.validate", "amount_t: commodity_ != NULL"); + return false; + } + return true; +} + +void to_xml(std::ostream& out, const amount_t& amt, bool commodity_details) +{ + push_xml x(out, "amount"); + + if (amt.has_commodity()) + to_xml(out, amt.commodity(), commodity_details); + + { + push_xml y(out, "quantity"); + out << y.guard(amt.quantity_string()); + } +} + +#if defined(HAVE_BOOST_SERIALIZATION) + +template<class Archive> +void amount_t::serialize(Archive& ar, const unsigned int /* version */) +{ + ar & is_initialized; + ar & quantity; + ar & commodity_; +} + +#endif // HAVE_BOOST_SERIALIZATION + +} // namespace ledger + +#if defined(HAVE_BOOST_SERIALIZATION) +namespace boost { +namespace serialization { + +template <class Archive> +void serialize(Archive& ar, MP_INT& mpz, const unsigned int /* version */) +{ + ar & mpz._mp_alloc; + ar & mpz._mp_size; + ar & mpz._mp_d; +} + +template <class Archive> +void serialize(Archive& ar, MP_RAT& mpq, const unsigned int /* version */) +{ + ar & mpq._mp_num; + ar & mpq._mp_den; +} + +template <class Archive> +void serialize(Archive& ar, long unsigned int& integer, + const unsigned int /* version */) +{ + ar & make_binary_object(&integer, sizeof(long unsigned int)); +} + +} // namespace serialization +} // namespace boost + +BOOST_CLASS_EXPORT(ledger::annotated_commodity_t) + +template void boost::serialization::serialize(boost::archive::binary_iarchive&, + MP_INT&, const unsigned int); +template void boost::serialization::serialize(boost::archive::binary_oarchive&, + MP_INT&, const unsigned int); +template void boost::serialization::serialize(boost::archive::binary_iarchive&, + MP_RAT&, const unsigned int); +template void boost::serialization::serialize(boost::archive::binary_oarchive&, + MP_RAT&, const unsigned int); +template void boost::serialization::serialize(boost::archive::binary_iarchive&, + long unsigned int&, + const unsigned int); +template void boost::serialization::serialize(boost::archive::binary_oarchive&, + long unsigned int&, + const unsigned int); + +template void ledger::amount_t::serialize(boost::archive::binary_iarchive&, + const unsigned int); +template void ledger::amount_t::serialize(boost::archive::binary_oarchive&, + const unsigned int); + +#endif // HAVE_BOOST_SERIALIZATION diff --git a/src/amount.h b/src/amount.h new file mode 100644 index 00000000..a37efdb8 --- /dev/null +++ b/src/amount.h @@ -0,0 +1,761 @@ +/* + * 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. + */ + +/** + * @defgroup math Mathematical objects + */ + +/** + * @file amount.h + * @author John Wiegley + * + * @ingroup math + * + * @brief Basic type for handling commoditized math: amount_t + * + * An amount is the most basic numerical type in Ledger, and relies on + * commodity.h to represent commoditized amounts, which allows Ledger to + * handle mathematical expressions involving disparate commodities. + * + * Amounts can be of virtually infinite size and precision. When + * division or multiplication is performed, the precision is + * automatically expanded to include as many extra digits as necessary + * to avoid losing information. + */ +#ifndef _AMOUNT_H +#define _AMOUNT_H + +#include "utils.h" +#include "times.h" +#include "flags.h" + +namespace ledger { + +class commodity_t; +class annotation_t; +class keep_details_t; +class commodity_pool_t; + +DECLARE_EXCEPTION(amount_error, std::runtime_error); + +enum parse_flags_enum_t { + PARSE_DEFAULT = 0x00, + PARSE_PARTIAL = 0x01, + PARSE_SINGLE = 0x02, + PARSE_NO_MIGRATE = 0x04, + PARSE_NO_REDUCE = 0x08, + PARSE_NO_ASSIGN = 0x10, + PARSE_NO_DATES = 0x20, + PARSE_OP_CONTEXT = 0x40, + PARSE_SOFT_FAIL = 0x80 +}; + +typedef basic_flags_t<parse_flags_enum_t, uint_least8_t> parse_flags_t; + +/** + * @brief Encapsulate infinite-precision commoditized amounts + * + * Used to represent commoditized infinite-precision numbers, and + * uncommoditized, plain numbers. In the commoditized case, commodities + * keep track of how they are used, and are always displayed back to the + * user after the same fashion. For uncommoditized numbers, no display + * truncation is ever done. In both cases, internal precision is always + * kept to an excessive degree. + */ +class amount_t + : public ordered_field_operators<amount_t, + ordered_field_operators<amount_t, double, + ordered_field_operators<amount_t, unsigned long, + ordered_field_operators<amount_t, long> > > > +{ +public: + /** Ready the amount subsystem for use. + @note Normally called by session_t::initialize(). */ + static void initialize(); + /** Shutdown the amount subsystem and free all resources. + @note Normally called by session_t::shutdown(). */ + static void shutdown(); + + static bool is_initialized; + + /** The amount's decimal precision. */ + typedef uint_least16_t precision_t; + + /** Number of places of precision by which values are extended to + avoid losing precision during division and multiplication. */ + static const std::size_t extend_by_digits = 6U; + + /** If amounts should be streamed using to_fullstring() rather than + to_string(), so that complete precision is always displayed no matter + what the precision of an individual commodity may be. */ + static bool stream_fullstrings; + +protected: + void _copy(const amount_t& amt); + void _dup(); + void _clear(); + void _release(); + + struct bigint_t; + + bigint_t * quantity; + commodity_t * commodity_; + +public: + /** @name Constructors + @{ */ + + /** Creates a value for which is_null() is true, and which has no + value or commodity. If used in a value expression it evaluates to + zero, and its commodity equals \c commodity_t::null_commodity. */ + amount_t() : quantity(NULL), commodity_(NULL) { + TRACE_CTOR(amount_t, ""); + } + + /** Convert a double to an amount. As much precision as possible is + decoded from the binary floating point number. */ + amount_t(const double val); + + /** Convert an unsigned long to an amount. It's precision is zero. */ + amount_t(const unsigned long val); + + /** Convert a long to an amount. It's precision is zero, and the sign + is preserved. */ + amount_t(const long val); + + /** Parse a string as an (optionally commoditized) amount. If no + commodity is present, the resulting commodity is \c + commodity_t::null_commodity. The number may be of infinite + precision. */ + explicit amount_t(const string& val) : quantity(NULL) { + TRACE_CTOR(amount_t, "const string&"); + parse(val); + } + /** Parse a pointer to a C string as an (optionally commoditized) + amount. If no commodity is present, the resulting commodity is \c + commodity_t::null_commodity. The number may be of infinite + precision. */ + explicit amount_t(const char * val) : quantity(NULL) { + TRACE_CTOR(amount_t, "const char *"); + assert(val); + parse(val); + } + + /*@}*/ + + /** Create an amount whose display precision is never truncated, even + if the amount uses a commodity (which normally causes "round on + streaming" to occur). This function is mostly used by debugging + code and unit tests. This is the proper way to specify \c + $100.005, where display of the extra digit precision is required. + If a regular constructor were used, the amount would stream as \c + $100.01, even though its internal value equals \c $100.005. */ + static amount_t exact(const string& value); + + /** Release the reference count held for the underlying \c + amount_t::bigint_t object. */ + ~amount_t() { + TRACE_DTOR(amount_t); + if (quantity) + _release(); + } + + /** @name Assignment and copy + @{*/ + + /** Copy an amount object. Copies are very efficient, using a + copy-on-write model. Until the copy is changed, it refers to the + same memory used by the original via reference counting. The \c + amount_t::bigint_t class in amount.cc maintains the reference. */ + amount_t(const amount_t& amt) : quantity(NULL) { + TRACE_CTOR(amount_t, "copy"); + if (amt.quantity) + _copy(amt); + else + commodity_ = NULL; + } + /** Copy an amount object, applying the given commodity annotation + details afterward. This is equivalent to doing a normal copy + (@see amount_t(const amount_t&)) and then calling + amount_t::annotate(). */ + amount_t(const amount_t& amt, const annotation_t& details) : quantity(NULL) { + TRACE_CTOR(amount_t, "const amount_t&, const annotation_t&"); + assert(amt.quantity); + _copy(amt); + annotate(details); + } + /** Assign an amount object. This is like copying if the amount was + null beforehand, otherwise the previous value's reference is must + be freed. */ + amount_t& operator=(const amount_t& amt); + + amount_t& operator=(const double val) { + return *this = amount_t(val); + } + amount_t& operator=(const unsigned long val) { + return *this = amount_t(val); + } + amount_t& operator=(const long val) { + return *this = amount_t(val); + } + + /* Assign a string to an amount. This causes the contents of the + string to be parsed, look for a commoditized or uncommoditized + amount specifier. */ + amount_t& operator=(const string& str) { + return *this = amount_t(str); + } + amount_t& operator=(const char * str) { + assert(str); + return *this = amount_t(str); + } + + /*@}*/ + + /** @name Comparison + @{ */ + + /** Compare two amounts, returning a number less than zero if \p amt + is greater, exactly zero if they are equal, and greater than zero + if \p amt is less. This method is used to implement all of the + other comparison methods.*/ + int compare(const amount_t& amt) const; + + /** Test two amounts for equality. First the commodity pointers are + quickly tested, then the multi-precision values themselves must be + compared. */ + bool operator==(const amount_t& amt) const; + + template <typename T> + bool operator==(const T& val) const { + return compare(val) == 0; + } + template <typename T> + bool operator<(const T& amt) const { + return compare(amt) < 0; + } + template <typename T> + bool operator>(const T& amt) const { + return compare(amt) > 0; + } + + /*@}*/ + + /** @name Binary arithmetic + */ + /*@{*/ + + amount_t& operator+=(const amount_t& amt); + amount_t& operator-=(const amount_t& amt); + amount_t& operator*=(const amount_t& amt); + + /** Divide two amounts while extending the precision to preserve the + accuracy of the result. For example, if \c 10 is divided by \c 3, + the result ends up having a precision of \link + amount_t::extend_by_digits \endlink place to avoid losing internal + resolution. */ + amount_t& operator/=(const amount_t& amt); + + /*@}*/ + + /** @name Unary arithmetic + @{ */ + + /** Return an amount's internal precision. To find the precision it + should be displayed at -- assuming it was not created using + amount_t::exact() -- use the following expression instead: + @code + amount.commodity().precision() + @endcode */ + precision_t precision() const; + bool keep_precision() const; + void set_keep_precision(const bool keep = true) const; + precision_t display_precision() const; + + /** Returns the negated value of an amount. + @see operator-() + */ + amount_t negated() const { + amount_t temp(*this); + temp.in_place_negate(); + return temp; + } + void in_place_negate(); + + amount_t operator-() const { + return negated(); + } + + /** Returns the absolute value of an amount. Equivalent to: + @code + (x < * 0) ? - x : x + @endcode + */ + amount_t abs() const { + if (sign() < 0) + return negated(); + return *this; + } + + amount_t inverted() const; + + /** Yields an amount whose display precision when output is truncated + to the display precision of its commodity. This is normally the + default state of an amount, but if one has become unrounded, this + sets the "keep precision" state back to false. + @see set_keep_precision */ + amount_t rounded() const { + amount_t temp(*this); + temp.in_place_round(); + return temp; + } + void in_place_round(); + + /** Yields an amount which has lost all of its extra precision, beyond what + the display precision of the commodity would have printed. */ + amount_t truncated() const { + amount_t temp(*this); + temp.in_place_truncate(); + return temp; + } + void in_place_truncate() { + *this = amount_t(to_string()); + } + + /** Yields an amount which has lost all of its extra precision, beyond what + the display precision of the commodity would have printed. */ + amount_t floored() const { + amount_t temp(*this); + temp.in_place_floor(); + return temp; + } + void in_place_floor(); + + /** Yields an amount whose display precision is never truncated, even + though its commodity normally displays only rounded values. */ + amount_t unrounded() const { + amount_t temp(*this); + temp.in_place_unround(); + return temp; + } + void in_place_unround(); + + /** reduces a value to its most basic commodity form, for amounts that + utilize "scaling commodities". For example, an amount of \c 1h + after reduction will be \c 3600s. + */ + amount_t reduced() const { + amount_t temp(*this); + temp.in_place_reduce(); + return temp; + } + void in_place_reduce(); + + /** unreduce(), if used with a "scaling commodity", yields the most + compact form greater than one. That is, \c 3599s will unreduce to + \c 59.98m, while \c 3601 unreduces to \c 1h. + */ + amount_t unreduced() const { + amount_t temp(*this); + temp.in_place_unreduce(); + return temp; + } + void in_place_unreduce(); + + /** Returns the historical value for an amount -- the default moment + returns the most recently known price -- based on the price history + for the given commodity (or determined automatically, if none is + provided). For example, if the amount were <tt>10 AAPL</tt>, and + on Apr 10, 2000 each share of \c AAPL was worth \c $10, then + calling value() for that moment in time would yield the amount \c + $100.00. + */ + optional<amount_t> + value(const bool primary_only = true, + const optional<datetime_t>& moment = none, + const optional<commodity_t&>& in_terms_of = none) const; + + amount_t price() const; + + /*@}*/ + + /** @name Truth tests + */ + /*@{*/ + + /** Truth tests. An amount may be truth test in several ways: + + sign() returns an integer less than, greater than, or equal to + zero depending on whether the amount is negative, zero, or + greater than zero. Note that this function tests the actual + value of the amount -- using its internal precision -- and not + the display value. To test its display value, use: + `round().sign()'. + + is_nonzero(), or operator bool, returns true if an amount's + display value is not zero. + + is_zero() returns true if an amount's display value is zero. + Thus, $0.0001 is considered zero if the current display precision + for dollars is two decimal places. + + is_realzero() returns true if an amount's actual value is zero. + Thus, $0.0001 is never considered realzero. + + is_null() returns true if an amount has no value and no + commodity. This only occurs if an uninitialized amount has never + been assigned a value. + */ + int sign() const; + + operator bool() const { + return is_nonzero(); + } + bool is_nonzero() const { + return ! is_zero(); + } + + bool is_zero() const; + bool is_realzero() const { + return sign() == 0; + } + + bool is_null() const { + if (! quantity) { + assert(! commodity_); + return true; + } + return false; + } + + /*@}*/ + + /** @name Conversion + */ + /*@{*/ + + /** Conversion methods. An amount may be converted to the same types + it can be constructed from -- with the exception of unsigned + long. Implicit conversions are not allowed in C++ (though they + are in Python), rather the following conversion methods must be + called explicitly: + + to_double([bool]) returns an amount as a double. If the optional + boolean argument is true (the default), an exception is thrown if + the conversion would lose information. + + to_long([bool]) returns an amount as a long integer. If the + optional boolean argument is true (the default), an exception is + thrown if the conversion would lose information. + + fits_in_long() returns true if to_long() would not lose + precision. + + to_string() returns an amount'ss "display value" as a string -- + after rounding the value according to the commodity's default + precision. It is equivalent to: `round().to_fullstring()'. + + to_fullstring() returns an amount's "internal value" as a string, + without any rounding. + + quantity_string() returns an amount's "display value", but + without any commodity. Note that this is different from + `number().to_string()', because in that case the commodity has + been stripped and the full, internal precision of the amount + would be displayed. + */ + double to_double() const; + long to_long() const; + bool fits_in_long() const; + + operator string() const { + return to_string(); + } + string to_string() const; + string to_fullstring() const; + string quantity_string() const; + + /*@}*/ + + /** @name Commodity methods + */ + /*@{*/ + + /** The following methods relate to an + amount's commodity: + + commodity() returns an amount's commodity. If the amount has no + commodity, the value returned is `current_pool->null_commodity'. + + has_commodity() returns true if the amount has a commodity. + + set_commodity(commodity_t) sets an amount's commodity to the + given value. Note that this merely sets the current amount to + that commodity, it does not "observe" the amount for possible + changes in the maximum display precision of the commodity, the + way that `parse' does. + + clear_commodity() sets an amount's commodity to null, such that + has_commodity() afterwards returns false. + + number() returns a commodity-less version of an amount. This is + useful for accessing just the numeric portion of an amount. + */ + commodity_t& commodity() const; + + bool has_commodity() const; + void set_commodity(commodity_t& comm) { + if (! quantity) + *this = 0L; + commodity_ = &comm; + } + void clear_commodity() { + commodity_ = NULL; + } + + amount_t number() const { + if (! has_commodity()) + return *this; + + amount_t temp(*this); + temp.clear_commodity(); + return temp; + } + + /*@}*/ + + /** @name Commodity annotations + */ + /*@{*/ + + /** An amount's commodity may be annotated with special details, such as the + price it was purchased for, when it was acquired, or an arbitrary note, + identifying perhaps the lot number of an item. + + annotate_commodity(amount_t price, [datetime_t date, string tag]) + sets the annotations for the current amount's commodity. Only + the price argument is required, although it can be passed as + `none' if no price is desired. + + commodity_annotated() returns true if an amount's commodity has + any annotation details associated with it. + + annotation_details() returns all of the details of an annotated + commodity's annotations. The structure returns will evaluate as + boolean false if there are no details. + + strip_annotations() returns an amount whose commodity's annotations have + been stripped. + */ + void annotate(const annotation_t& details); + bool has_annotation() const; + + annotation_t& annotation(); + const annotation_t& annotation() const { + return const_cast<amount_t&>(*this).annotation(); + } + + /** If the lot price is considered whenever working with commoditized + values. + + Let's say a user adds two values of the following form: + @code + 10 AAPL + 10 AAPL {$20} + @endcode + + This expression adds ten shares of Apple stock with another ten + shares that were purchased for \c $20 a share. If \c keep_price + is false, the result of this expression is an amount equal to + <tt>20 AAPL</tt>. If \c keep_price is \c true the expression + yields an exception for adding amounts with different commodities. + In that case, a \link balance_t \endlink object must be used to + store the combined sum. */ + amount_t strip_annotations(const keep_details_t& what_to_keep) const; + + /*@}*/ + + /** @name Parsing + */ + /*@{*/ + + /** The `flags' argument of both parsing may be one or more of the + following: + + PARSE_NO_MIGRATE means to not pay attention to the way an + amount is used. Ordinarily, if an amount were $100.001, for + example, it would cause the default display precision for $ to be + "widened" to three decimal places. If PARSE_NO_MIGRATE is + used, the commodity's default display precision is not changed. + + PARSE_NO_REDUCE means not to call in_place_reduce() on the + resulting amount after it is parsed. + + These parsing methods observe the amounts they parse (unless + PARSE_NO_MIGRATE is true), and set the display details of + the corresponding commodity accordingly. This way, amounts do + not require commodities to be pre-defined in any way, but merely + displays them back to the user in the same fashion as it saw them + used. + + There is also a static convenience method called + `parse_conversion' which can be used to define a relationship + between scaling commodity values. For example, Ledger uses it to + define the relationships among various time values: + + @code + amount_t::parse_conversion("1.0m", "60s"); // a minute is 60 seconds + amount_t::parse_conversion("1.0h", "60m"); // an hour is 60 minutes + @endcode + + The method parse() is used to parse an amount from an input stream + or a string. A global operator>>() is also defined which simply + calls parse on the input stream. The parse() method has two forms: + + parse(istream, flags_t) parses an amount from the given input + stream. + + parse(string, flags_t) parses an amount from the given string. + + parse(string, flags_t) also parses an amount from a string. + */ + bool parse(std::istream& in, + const parse_flags_t& flags = PARSE_DEFAULT); + bool parse(const string& str, + const parse_flags_t& flags = PARSE_DEFAULT) { + std::istringstream stream(str); + bool result = parse(stream, flags); + return result; + } + + static void parse_conversion(const string& larger_str, + const string& smaller_str); + + /*@}*/ + + /** @name Printing + */ + /*@{*/ + + /** An amount may be output to a stream using the `print' method. There is + also a global operator<< defined which simply calls print for an amount + on the given stream. There is one form of the print method, which takes + one required argument and two arguments with default values: + + print(ostream, bool omit_commodity = false, bool full_precision = false) + prints an amounts to the given output stream, using its commodity's + default display characteristics. If `omit_commodity' is true, the + commodity will not be displayed, only the amount (although the + commodity's display precision is still used). If `full_precision' is + true, the full internal precision of the amount is displayed, regardless + of its commodity's display precision. + */ + void print(std::ostream& out) const; + + /*@}*/ + + /** @name Debugging + */ + /*@{*/ + + /** There are two methods defined to help with debugging: + + dump(ostream) dumps an amount to an output stream. There is + little different from print(), it simply surrounds the display + value with a marker, for example "AMOUNT($1.00)". This code is + used by other dumping code elsewhere in Ledger. + + valid() returns true if an amount is valid. This ensures that if + an amount has a commodity, it has a valid value pointer, for + example, even if that pointer simply points to a zero value. + */ + void dump(std::ostream& out) const { + out << "AMOUNT("; + print(out); + out << ")"; + } + + bool valid() const; + +#if defined(HAVE_BOOST_SERIALIZATION) +private: + /** Serialization. */ + + friend class boost::serialization::access; + + template<class Archive> + void serialize(Archive& ar, const unsigned int /* version */); +#endif // HAVE_BOOST_SERIALIZATION + + /*@}*/ +}; + +inline amount_t amount_t::exact(const string& value) { + amount_t temp; + temp.parse(value, PARSE_NO_MIGRATE); + return temp; +} + +inline string amount_t::to_string() const { + std::ostringstream bufstream; + print(bufstream); + return bufstream.str(); +} + +inline string amount_t::to_fullstring() const { + std::ostringstream bufstream; + unrounded().print(bufstream); + return bufstream.str(); +} + +inline string amount_t::quantity_string() const { + std::ostringstream bufstream; + number().print(bufstream); + return bufstream.str(); +} + +inline std::ostream& operator<<(std::ostream& out, const amount_t& amt) { + if (amount_t::stream_fullstrings) + amt.unrounded().print(out); + else + amt.print(out); + return out; +} +inline std::istream& operator>>(std::istream& in, amount_t& amt) { + amt.parse(in); + return in; +} + +void to_xml(std::ostream& out, const amount_t& amt, + bool commodity_details = false); + +} // namespace ledger + +#endif // _AMOUNT_H diff --git a/src/annotate.cc b/src/annotate.cc new file mode 100644 index 00000000..146a7afd --- /dev/null +++ b/src/annotate.cc @@ -0,0 +1,205 @@ +/* + * 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 "amount.h" +#include "commodity.h" +#include "annotate.h" +#include "pool.h" + +namespace ledger { + +void annotation_t::parse(std::istream& in) +{ + do { + istream_pos_type pos = in.tellg(); + + char buf[256]; + char c = peek_next_nonws(in); + if (c == '{') { + if (price) + throw_(amount_error, _("Commodity specifies more than one price")); + + in.get(c); + c = peek_next_nonws(in); + if (c == '=') { + in.get(c); + add_flags(ANNOTATION_PRICE_FIXATED); + } + + READ_INTO(in, buf, 255, c, c != '}'); + if (c == '}') + in.get(c); + else + throw_(amount_error, _("Commodity price lacks closing brace")); + + amount_t temp; + temp.parse(buf, PARSE_NO_MIGRATE); + + DEBUG("commodity.annotations", "Parsed annotation price: " << temp); + + // Since this price will maintain its own precision, make sure + // it is at least as large as the base commodity, since the user + // may have only specified {$1} or something similar. + + if (temp.has_commodity() && + temp.precision() > temp.commodity().precision()) + temp = temp.rounded(); // no need to retain individual precision + + price = temp; + } + else if (c == '[') { + if (date) + throw_(amount_error, _("Commodity specifies more than one date")); + + in.get(c); + READ_INTO(in, buf, 255, c, c != ']'); + if (c == ']') + in.get(c); + else + throw_(amount_error, _("Commodity date lacks closing bracket")); + + date = parse_date(buf); + } + else if (c == '(') { + if (tag) + throw_(amount_error, _("Commodity specifies more than one tag")); + + in.get(c); + READ_INTO(in, buf, 255, c, c != ')'); + if (c == ')') + in.get(c); + else + throw_(amount_error, _("Commodity tag lacks closing parenthesis")); + + tag = buf; + } + else { + in.clear(); + in.seekg(pos, std::ios::beg); + break; + } + } while (true); + +#if defined(DEBUG_ON) + if (SHOW_DEBUG("amounts.commodities") && *this) { + DEBUG("amounts.commodities", + "Parsed commodity annotations: " << std::endl << *this); + } +#endif +} + +void annotation_t::print(std::ostream& out, bool keep_base) const +{ + if (price) + out << " {" + << (has_flags(ANNOTATION_PRICE_FIXATED) ? "=" : "") + << (keep_base ? *price : price->unreduced()).rounded() + << '}'; + + if (date) + out << " [" << format_date(*date, FMT_WRITTEN) << ']'; + + if (tag) + out << " (" << *tag << ')'; +} + +bool keep_details_t::keep_all(const commodity_t& comm) const +{ + return (! comm.has_annotation() || + (keep_price && keep_date && keep_tag && ! only_actuals)); +} + +bool keep_details_t::keep_any(const commodity_t& comm) const +{ + return comm.has_annotation() && (keep_price || keep_date || keep_tag); +} + +bool annotated_commodity_t::operator==(const commodity_t& comm) const +{ + // If the base commodities don't match, the game's up. + if (base != comm.base) + return false; + + assert(annotated); + if (! comm.annotated) + return false; + + if (details != as_annotated_commodity(comm).details) + return false; + + return true; +} + +commodity_t& +annotated_commodity_t::strip_annotations(const keep_details_t& what_to_keep) +{ + DEBUG("commodity.annotated.strip", + "Reducing commodity " << *this << std::endl + << " keep price " << what_to_keep.keep_price << " " + << " keep date " << what_to_keep.keep_date << " " + << " keep tag " << what_to_keep.keep_tag); + + commodity_t * new_comm; + + bool keep_price = (what_to_keep.keep_price && + (! what_to_keep.only_actuals || + ! details.has_flags(ANNOTATION_PRICE_CALCULATED))); + bool keep_date = (what_to_keep.keep_date && + (! what_to_keep.only_actuals || + ! details.has_flags(ANNOTATION_DATE_CALCULATED))); + bool keep_tag = (what_to_keep.keep_tag && + (! what_to_keep.only_actuals || + ! details.has_flags(ANNOTATION_TAG_CALCULATED))); + + if ((keep_price && details.price) || + (keep_date && details.date) || + (keep_tag && details.tag)) + { + new_comm = pool().find_or_create + (referent(), annotation_t(keep_price ? details.price : none, + keep_date ? details.date : none, + keep_tag ? details.tag : none)); + } else { + new_comm = pool().find_or_create(base_symbol()); + } + + assert(new_comm); + return *new_comm; +} + +void annotated_commodity_t::write_annotations(std::ostream& out) const +{ + details.print(out, pool().keep_base); +} + +} // namespace ledger diff --git a/src/annotate.h b/src/annotate.h new file mode 100644 index 00000000..38ebaeae --- /dev/null +++ b/src/annotate.h @@ -0,0 +1,265 @@ +/* + * 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. + */ + +/** + * @addtogroup math + */ + +/** + * @file annotate.h + * @author John Wiegley + * + * @ingroup math + * + * @brief Types for annotating commodities + * + * Long. + */ +#ifndef _ANNOTATE_H +#define _ANNOTATE_H + +namespace ledger { + +struct annotation_t : public supports_flags<>, + public equality_comparable<annotation_t> +{ +#define ANNOTATION_PRICE_CALCULATED 0x01 +#define ANNOTATION_PRICE_FIXATED 0x02 +#define ANNOTATION_DATE_CALCULATED 0x04 +#define ANNOTATION_TAG_CALCULATED 0x08 + + optional<amount_t> price; + optional<date_t> date; + optional<string> tag; + + explicit annotation_t(const optional<amount_t>& _price = none, + const optional<date_t>& _date = none, + const optional<string>& _tag = none) + : supports_flags<>(), price(_price), date(_date), tag(_tag) { + TRACE_CTOR(annotation_t, "const optional<amount_t>& + date_t + string"); + } + annotation_t(const annotation_t& other) + : supports_flags<>(other.flags()), + price(other.price), date(other.date), tag(other.tag) { + TRACE_CTOR(annotation_t, "copy"); + } + ~annotation_t() { + TRACE_DTOR(annotation_t); + } + + operator bool() const { + return price || date || tag; + } + + bool operator==(const annotation_t& rhs) const { + return (price == rhs.price && + date == rhs.date && + tag == rhs.tag); + } + + void parse(std::istream& in); + + void print(std::ostream& out, bool keep_base = false) const; + + bool valid() const { + assert(*this); + return true; + } + +#if defined(HAVE_BOOST_SERIALIZATION) +private: + /** Serialization. */ + + friend class boost::serialization::access; + + template<class Archive> + void serialize(Archive& ar, const unsigned int /* version */) { + ar & boost::serialization::base_object<supports_flags<> >(*this); + ar & price; + ar & date; + ar & tag; + } +#endif // HAVE_BOOST_SERIALIZATION +}; + +inline void to_xml(std::ostream& out, const annotation_t& details) +{ + push_xml x(out, "annotation"); + + if (details.price) + { + push_xml y(out, "price"); + to_xml(out, *details.price); + } + + if (details.date) + { + push_xml y(out, "date"); + to_xml(out, *details.date, false); + } + + if (details.tag) + { + push_xml y(out, "tag"); + out << y.guard(*details.tag); + } +} + +struct keep_details_t +{ + bool keep_price; + bool keep_date; + bool keep_tag; + bool only_actuals; + + explicit keep_details_t(bool _keep_price = false, + bool _keep_date = false, + bool _keep_tag = false, + bool _only_actuals = false) + : keep_price(_keep_price), + keep_date(_keep_date), + keep_tag(_keep_tag), + only_actuals(_only_actuals) + { + TRACE_CTOR(keep_details_t, "bool, bool, bool, bool"); + } + keep_details_t(const keep_details_t& other) + : keep_price(other.keep_price), keep_date(other.keep_date), + keep_tag(other.keep_tag), only_actuals(other.only_actuals) { + TRACE_CTOR(keep_details_t, "copy"); + } + ~keep_details_t() throw() { + TRACE_DTOR(keep_details_t); + } + + bool keep_all() const { + return keep_price && keep_date && keep_tag && ! only_actuals; + } + bool keep_all(const commodity_t& comm) const; + + bool keep_any() const { + return keep_price || keep_date || keep_tag; + } + bool keep_any(const commodity_t& comm) const; + +#if defined(HAVE_BOOST_SERIALIZATION) +private: + /** Serialization. */ + + friend class boost::serialization::access; + + template<class Archive> + void serialize(Archive& ar, const unsigned int /* version */) { + ar & keep_price; + ar & keep_date; + ar & keep_tag; + ar & only_actuals; + } +#endif // HAVE_BOOST_SERIALIZATION +}; + +inline std::ostream& operator<<(std::ostream& out, + const annotation_t& details) { + details.print(out); + return out; +} + +class annotated_commodity_t + : public commodity_t, + public equality_comparable<annotated_commodity_t, + equality_comparable2<annotated_commodity_t, commodity_t, + noncopyable> > +{ +protected: + friend class commodity_pool_t; + + commodity_t * ptr; + + explicit annotated_commodity_t(commodity_t * _ptr, + const annotation_t& _details) + : commodity_t(_ptr->parent_, _ptr->base), ptr(_ptr), details(_details) { + TRACE_CTOR(annotated_commodity_t, "commodity_t *, annotation_t"); + annotated = true; + } + +public: + annotation_t details; + + virtual ~annotated_commodity_t() { + TRACE_DTOR(annotated_commodity_t); + } + + virtual bool operator==(const commodity_t& comm) const; + virtual bool operator==(const annotated_commodity_t& comm) const { + return *this == static_cast<const commodity_t&>(comm); + } + + virtual commodity_t& referent() { + return *ptr; + } + virtual const commodity_t& referent() const { + return *ptr; + } + + virtual commodity_t& strip_annotations(const keep_details_t& what_to_keep); + virtual void write_annotations(std::ostream& out) const; + +#if defined(HAVE_BOOST_SERIALIZATION) +private: + explicit annotated_commodity_t() : ptr(NULL) { + TRACE_CTOR(annotated_commodity_t, ""); + } + + /** Serialization. */ + + friend class boost::serialization::access; + + template<class Archive> + void serialize(Archive& ar, const unsigned int /* version */) { + ar & boost::serialization::base_object<commodity_t>(*this); + ar & ptr; + ar & details; + } +#endif // HAVE_BOOST_SERIALIZATION +}; + +inline annotated_commodity_t& +as_annotated_commodity(commodity_t& commodity) { + return downcast<annotated_commodity_t>(commodity); +} +inline const annotated_commodity_t& +as_annotated_commodity(const commodity_t& commodity) { + return downcast<const annotated_commodity_t>(commodity); +} + +} // namespace ledger + +#endif // _ANNOTATE_H diff --git a/src/archive.cc b/src/archive.cc new file mode 100644 index 00000000..7306f8d3 --- /dev/null +++ b/src/archive.cc @@ -0,0 +1,295 @@ +/* + * 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> + +#if defined(HAVE_BOOST_SERIALIZATION) + +#include "archive.h" +#include "amount.h" +#include "commodity.h" +#include "pool.h" +#include "scope.h" +#include "account.h" +#include "post.h" +#include "xact.h" + +#define LEDGER_MAGIC 0x4c454447 +#define ARCHIVE_VERSION 0x03000006 + +//BOOST_IS_ABSTRACT(ledger::scope_t) +BOOST_CLASS_EXPORT(ledger::scope_t) +BOOST_CLASS_EXPORT(ledger::child_scope_t) +BOOST_CLASS_EXPORT(ledger::symbol_scope_t) +BOOST_CLASS_EXPORT(ledger::call_scope_t) +BOOST_CLASS_EXPORT(ledger::account_t) +BOOST_CLASS_EXPORT(ledger::item_t) +BOOST_CLASS_EXPORT(ledger::post_t) +BOOST_CLASS_EXPORT(ledger::xact_base_t) +BOOST_CLASS_EXPORT(ledger::xact_t) +BOOST_CLASS_EXPORT(ledger::auto_xact_t) +BOOST_CLASS_EXPORT(ledger::period_xact_t) + +template void ledger::journal_t::serialize(boost::archive::binary_oarchive&, + const unsigned int); +template void ledger::journal_t::serialize(boost::archive::binary_iarchive&, + const unsigned int); +namespace ledger { + +namespace { + bool read_header_bits(std::istream& in) { + uint32_t bytes; + + assert(sizeof(uint32_t) == 4); + in.read(reinterpret_cast<char *>(&bytes), sizeof(uint32_t)); + if (bytes != LEDGER_MAGIC) { + DEBUG("archive.journal", "Magic bytes not present"); + return false; + } + + in.read(reinterpret_cast<char *>(&bytes), sizeof(uint32_t)); + if (bytes != ARCHIVE_VERSION) { + DEBUG("archive.journal", "Archive version mismatch"); + return false; + } + + return true; + } + + void write_header_bits(std::ostream& out) { + uint32_t bytes; + + assert(sizeof(uint32_t) == 4); + bytes = LEDGER_MAGIC; + out.write(reinterpret_cast<char *>(&bytes), sizeof(uint32_t)); + + bytes = ARCHIVE_VERSION; + out.write(reinterpret_cast<char *>(&bytes), sizeof(uint32_t)); + } +} + +bool archive_t::read_header() +{ + uintmax_t size = file_size(file); + if (size < 8) + return false; + + // Open the stream, read the version number and the list of sources + ifstream stream(file, std::ios::binary); + if (! read_header_bits(stream)) + return false; + + boost::archive::binary_iarchive iarchive(stream); + + DEBUG("archive.journal", "Reading header from archive"); + iarchive >> *this; + + DEBUG("archive.journal", + "Version number: " << std::hex << ARCHIVE_VERSION << std::dec); + DEBUG("archive.journal", "Number of sources: " << sources.size()); + +#if defined(DEBUG_ON) + foreach (const journal_t::fileinfo_t& i, sources) + DEBUG("archive.journal", "Loaded source: " << *i.filename); +#endif + + return true; +} + +bool archive_t::should_load(const std::list<path>& data_files) +{ + std::size_t found = 0; + + DEBUG("archive.journal", "Should the archive be loaded?"); + + if (! exists(file)) { + DEBUG("archive.journal", "No, it does not exist"); + return false; + } + + if (! read_header()) { + DEBUG("archive.journal", "No, header failed to read"); + return false; + } + + if (data_files.empty()) { + DEBUG("archive.journal", "No, there were no data files!"); + return false; + } + + if (sources.empty()) { + DEBUG("archive.journal", "No, there were no sources!"); + return false; + } + + if (data_files.size() != sources.size()) { + DEBUG("archive.journal", "No, number of sources doesn't match: " + << data_files.size() << " != " << sources.size()); + return false; + } + + foreach (const path& p, data_files) { + DEBUG("archive.journal", "Scanning for data file: " << p); + + if (! exists(p)) { + DEBUG("archive.journal", "No, an input source no longer exists: " << p); + return false; + } + + foreach (const journal_t::fileinfo_t& i, sources) { + assert(! i.from_stream); + assert(i.filename); + + DEBUG("archive.journal", "Comparing against source file: " << *i.filename); + + if (*i.filename == p) { + if (! exists(*i.filename)) { + DEBUG("archive.journal", + "No, a referent source no longer exists: " << *i.filename); + return false; + } + + if (i.modtime != posix_time::from_time_t(last_write_time(p))) { + DEBUG("archive.journal", "No, a source's modtime has changed: " << p); + return false; + } + + if (i.size != file_size(p)) { + DEBUG("archive.journal", "No, a source's size has changed: " << p); + return false; + } + + found++; + } + } + } + + if (found != data_files.size()) { + DEBUG("archive.journal", "No, not every source's name matched"); + return false; + } + + DEBUG("archive.journal", "Yes, it should be loaded!"); + return true; +} + +bool archive_t::should_save(journal_t& journal) +{ + std::list<path> data_files; + + DEBUG("archive.journal", "Should the archive be saved?"); + + if (journal.was_loaded) { + DEBUG("archive.journal", "No, it's one we loaded before"); + return false; + } + + if (journal.sources.empty()) { + DEBUG("archive.journal", "No, there were no sources!"); + return false; + } + + foreach (const journal_t::fileinfo_t& i, journal.sources) { + if (i.from_stream) { + DEBUG("archive.journal", "No, one source was from a stream"); + return false; + } + + if (! exists(*i.filename)) { + DEBUG("archive.journal", + "No, a source no longer exists: " << *i.filename); + return false; + } + + data_files.push_back(*i.filename); + } + + if (should_load(data_files)) { + DEBUG("archive.journal", "No, because it's still loadable"); + return false; + } + + DEBUG("archive.journal", "Yes, it should be saved!"); + return true; +} + +void archive_t::save(journal_t& journal) +{ + INFO_START(archive, "Saved journal file cache"); + + ofstream stream(file, std::ios::binary); + + write_header_bits(stream); + sources = journal.sources; + +#if defined(DEBUG_ON) + foreach (const journal_t::fileinfo_t& i, sources) + DEBUG("archive.journal", "Saving source: " << *i.filename); +#endif + + boost::archive::binary_oarchive oa(stream); + + DEBUG("archive.journal", "Creating archive with version " + << std::hex << ARCHIVE_VERSION << std::dec); + oa << *this; + + DEBUG("archive.journal", + "Archiving journal with " << sources.size() << " sources"); + oa << journal; + + INFO_FINISH(archive); +} + +bool archive_t::load(journal_t& journal) +{ + INFO_START(archive, "Read cached journal file"); + + ifstream stream(file, std::ios::binary); + if (! read_header_bits(stream)) + return false; + + boost::archive::binary_iarchive iarchive(stream); + + // Skip past the archive header, it was already read in before + archive_t temp; + iarchive >> temp; + + iarchive >> journal; + journal.was_loaded = true; + + INFO_FINISH(archive); + + return true; +} + +} // namespace ledger + +#endif // HAVE_BOOST_SERIALIZATION diff --git a/src/python/py_commodity.cc b/src/archive.h index 0dab3cd3..03fc970a 100644 --- a/src/python/py_commodity.cc +++ b/src/archive.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2007, John Wiegley. All rights reserved. + * 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 @@ -29,35 +29,64 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "pyinterp.h" -#include "pyutils.h" -#include "amount.h" +/** + * @defgroup report Reporting + */ + +/** + * @file archive.h + * @author John Wiegley + * + * @ingroup report + */ +#ifndef _ARCHIVE_H +#define _ARCHIVE_H -#include <boost/python/exception_translator.hpp> -#include <boost/python/implicit.hpp> +#include "journal.h" namespace ledger { -using namespace boost::python; - -void export_commodity() +class archive_t { - scope().attr("COMMODITY_STYLE_DEFAULTS") = COMMODITY_STYLE_DEFAULTS; - scope().attr("COMMODITY_STYLE_SUFFIXED") = COMMODITY_STYLE_SUFFIXED; - scope().attr("COMMODITY_STYLE_SEPARATED") = COMMODITY_STYLE_SEPARATED; - scope().attr("COMMODITY_STYLE_EUROPEAN") = COMMODITY_STYLE_EUROPEAN; - scope().attr("COMMODITY_STYLE_THOUSANDS") = COMMODITY_STYLE_THOUSANDS; - scope().attr("COMMODITY_STYLE_NOMARKET") = COMMODITY_STYLE_NOMARKET; - scope().attr("COMMODITY_STYLE_BUILTIN") = COMMODITY_STYLE_BUILTIN; + path file; + + std::list<journal_t::fileinfo_t> sources; - class_< commodity_t, bases<>, - commodity_t, boost::noncopyable > ("commodity", no_init) - .def(self == self) +public: + archive_t() { + TRACE_CTOR(archive_t, ""); + } + archive_t(const path& _file) : file(_file) { + TRACE_CTOR(archive_t, "const path&"); + } + archive_t(const archive_t& ar) : file(ar.file) { + TRACE_CTOR(archive_t, "copy"); + } + ~archive_t() { + TRACE_DTOR(archive_t); + } - .def("drop_flags", &commodity_t::drop_flags) + bool read_header(); - .add_property("precision", &commodity_t::precision) - ; -} + bool should_load(const std::list<path>& data_files); + bool should_save(journal_t& journal); + + void save(journal_t& journal); + bool load(journal_t& journal); + +#if defined(HAVE_BOOST_SERIALIZATION) +private: + /** Serialization. */ + + friend class boost::serialization::access; + + template<class Archive> + void serialize(Archive& ar, const unsigned int /* version */) { + ar & sources; + } +#endif // HAVE_BOOST_SERIALIZATION +}; } // namespace ledger + +#endif // _ARCHIVE_H diff --git a/src/numerics/balance.cc b/src/balance.cc index 80637221..4ff51ffc 100644 --- a/src/numerics/balance.cc +++ b/src/balance.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2007, John Wiegley. All rights reserved. + * 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 @@ -29,16 +29,41 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#include <system.hh> + #include "balance.h" +#include "commodity.h" +#include "annotate.h" +#include "pool.h" +#include "unistring.h" // for justify() namespace ledger { +balance_t::balance_t(const double val) +{ + TRACE_CTOR(balance_t, "const double"); + amounts.insert + (amounts_map::value_type(commodity_pool_t::current_pool->null_commodity, val)); +} + +balance_t::balance_t(const unsigned long val) +{ + TRACE_CTOR(balance_t, "const unsigned long"); + amounts.insert + (amounts_map::value_type(commodity_pool_t::current_pool->null_commodity, val)); +} + +balance_t::balance_t(const long val) +{ + TRACE_CTOR(balance_t, "const long"); + amounts.insert + (amounts_map::value_type(commodity_pool_t::current_pool->null_commodity, val)); +} + balance_t& balance_t::operator+=(const balance_t& bal) { - for (amounts_map::const_iterator i = bal.amounts.begin(); - i != bal.amounts.end(); - i++) - *this += i->second; + foreach (const amounts_map::value_type& pair, bal.amounts) + *this += pair.second; return *this; } @@ -46,7 +71,7 @@ balance_t& balance_t::operator+=(const amount_t& amt) { if (amt.is_null()) throw_(balance_error, - "Cannot add an uninitialized amount to a balance"); + _("Cannot add an uninitialized amount to a balance")); if (amt.is_realzero()) return *this; @@ -62,10 +87,8 @@ balance_t& balance_t::operator+=(const amount_t& amt) balance_t& balance_t::operator-=(const balance_t& bal) { - for (amounts_map::const_iterator i = bal.amounts.begin(); - i != bal.amounts.end(); - i++) - *this -= i->second; + foreach (const amounts_map::value_type& pair, bal.amounts) + *this -= pair.second; return *this; } @@ -73,7 +96,7 @@ balance_t& balance_t::operator-=(const amount_t& amt) { if (amt.is_null()) throw_(balance_error, - "Cannot subtract an uninitialized amount from a balance"); + _("Cannot subtract an uninitialized amount from a balance")); if (amt.is_realzero()) return *this; @@ -84,7 +107,7 @@ balance_t& balance_t::operator-=(const amount_t& amt) if (i->second.is_realzero()) amounts.erase(i); } else { - amounts.insert(amounts_map::value_type(&amt.commodity(), amt.negate())); + amounts.insert(amounts_map::value_type(&amt.commodity(), amt.negated())); } return *this; } @@ -93,7 +116,7 @@ balance_t& balance_t::operator*=(const amount_t& amt) { if (amt.is_null()) throw_(balance_error, - "Cannot multiply a balance by an uninitialized amount"); + _("Cannot multiply a balance by an uninitialized amount")); if (is_realzero()) { ; @@ -104,10 +127,8 @@ balance_t& balance_t::operator*=(const amount_t& amt) else if (! amt.commodity()) { // Multiplying by an amount with no commodity causes all the // component amounts to be increased by the same factor. - for (amounts_map::iterator i = amounts.begin(); - i != amounts.end(); - i++) - i->second *= amt; + foreach (amounts_map::value_type& pair, amounts) + pair.second *= amt; } else if (amounts.size() == 1) { // Multiplying by a commoditized amount is only valid if the sole @@ -117,12 +138,12 @@ balance_t& balance_t::operator*=(const amount_t& amt) amounts.begin()->second *= amt; else throw_(balance_error, - "Cannot multiply a balance with annotated commodities by a commoditized amount"); + _("Cannot multiply a balance with annotated commodities by a commoditized amount")); } else { assert(amounts.size() > 1); throw_(balance_error, - "Cannot multiply a multi-commodity balance by a commoditized amount"); + _("Cannot multiply a multi-commodity balance by a commoditized amount")); } return *this; } @@ -131,21 +152,19 @@ balance_t& balance_t::operator/=(const amount_t& amt) { if (amt.is_null()) throw_(balance_error, - "Cannot divide a balance by an uninitialized amount"); + _("Cannot divide a balance by an uninitialized amount")); if (is_realzero()) { ; } else if (amt.is_realzero()) { - throw_(balance_error, "Divide by zero"); + throw_(balance_error, _("Divide by zero")); } else if (! amt.commodity()) { // Dividing by an amount with no commodity causes all the // component amounts to be divided by the same factor. - for (amounts_map::iterator i = amounts.begin(); - i != amounts.end(); - i++) - i->second /= amt; + foreach (amounts_map::value_type& pair, amounts) + pair.second /= amt; } else if (amounts.size() == 1) { // Dividing by a commoditized amount is only valid if the sole @@ -155,29 +174,42 @@ balance_t& balance_t::operator/=(const amount_t& amt) amounts.begin()->second /= amt; else throw_(balance_error, - "Cannot divide a balance with annotated commodities by a commoditized amount"); + _("Cannot divide a balance with annotated commodities by a commoditized amount")); } else { assert(amounts.size() > 1); throw_(balance_error, - "Cannot divide a multi-commodity balance by a commoditized amount"); + _("Cannot divide a multi-commodity balance by a commoditized amount")); } return *this; } optional<balance_t> -balance_t::value(const optional<moment_t>& moment) const +balance_t::value(const bool primary_only, + const optional<datetime_t>& moment, + const optional<commodity_t&>& in_terms_of) const { - optional<balance_t> temp; - - for (amounts_map::const_iterator i = amounts.begin(); - i != amounts.end(); - i++) - if (optional<amount_t> val = i->second.value(moment)) { - if (! temp) - temp = balance_t(); - *temp += *val; + balance_t temp; + bool resolved = false; + + foreach (const amounts_map::value_type& pair, amounts) { + if (optional<amount_t> val = pair.second.value(primary_only, moment, + in_terms_of)) { + temp += *val; + resolved = true; + } else { + temp += pair.second; } + } + return resolved ? temp : optional<balance_t>(); +} + +balance_t balance_t::price() const +{ + balance_t temp; + + foreach (const amounts_map::value_type& pair, amounts) + temp += pair.second.price(); return temp; } @@ -185,47 +217,46 @@ balance_t::value(const optional<moment_t>& moment) const optional<amount_t> balance_t::commodity_amount(const optional<const commodity_t&>& commodity) const { - // jww (2007-05-20): Needs work if (! commodity) { if (amounts.size() == 1) { - amounts_map::const_iterator i = amounts.begin(); - return i->second; + return amounts.begin()->second; } else if (amounts.size() > 1) { // Try stripping annotations before giving an error. - balance_t temp(strip_annotations()); + balance_t temp(strip_annotations(keep_details_t())); if (temp.amounts.size() == 1) return temp.commodity_amount(commodity); throw_(amount_error, - "Requested amount of a balance with multiple commodities: " << temp); + _("Requested amount of a balance with multiple commodities: %1") + << temp); } } else if (amounts.size() > 0) { - amounts_map::const_iterator i = amounts.find(&*commodity); + amounts_map::const_iterator i = + amounts.find(const_cast<commodity_t *>(&*commodity)); if (i != amounts.end()) return i->second; } return none; } -balance_t balance_t::strip_annotations(const bool keep_price, - const bool keep_date, - const bool keep_tag) const +balance_t +balance_t::strip_annotations(const keep_details_t& what_to_keep) const { balance_t temp; - for (amounts_map::const_iterator i = amounts.begin(); - i != amounts.end(); - i++) - temp += i->second.strip_annotations(keep_price, keep_date, keep_tag); + foreach (const amounts_map::value_type& pair, amounts) + temp += pair.second.strip_annotations(what_to_keep); return temp; } void balance_t::print(std::ostream& out, const int first_width, - const int latter_width) const + const int latter_width, + const bool right_justify, + const bool colorize) const { bool first = true; int lwidth = latter_width; @@ -236,18 +267,13 @@ void balance_t::print(std::ostream& out, typedef std::vector<const amount_t *> amounts_array; amounts_array sorted; - for (amounts_map::const_iterator i = amounts.begin(); - i != amounts.end(); - i++) - if (i->second) - sorted.push_back(&i->second); + foreach (const amounts_map::value_type& pair, amounts) + if (pair.second) + sorted.push_back(&pair.second); - std::stable_sort(sorted.begin(), sorted.end(), - compare_amount_commodities()); + std::stable_sort(sorted.begin(), sorted.end(), compare_amount_commodities()); - for (amounts_array::const_iterator i = sorted.begin(); - i != sorted.end(); - i++) { + foreach (const amount_t * amount, sorted) { int width; if (! first) { out << std::endl; @@ -257,16 +283,28 @@ void balance_t::print(std::ostream& out, width = first_width; } - out.width(width); - out.fill(' '); - out << std::right << **i; + std::ostringstream buf; + buf << *amount; + justify(out, buf.str(), width, right_justify, + colorize && amount->sign() < 0); } if (first) { out.width(first_width); - out.fill(' '); - out << std::right << "0"; + if (right_justify) + out << std::right; + else + out << std::left; + out << 0; } } +void to_xml(std::ostream& out, const balance_t& bal) +{ + push_xml x(out, "balance"); + + foreach (const balance_t::amounts_map::value_type& pair, bal.amounts) + to_xml(out, pair.second); +} + } // namespace ledger diff --git a/src/numerics/balance.h b/src/balance.h index 34b2fcc7..826de134 100644 --- a/src/numerics/balance.h +++ b/src/balance.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2007, John Wiegley. All rights reserved. + * 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 @@ -30,11 +30,16 @@ */ /** + * @addtogroup math + */ + +/** * @file balance.h * @author John Wiegley - * @date Sun May 20 15:28:44 2007 * - * @brief Basic type for adding multiple commodities together. + * @ingroup math + * + * @brief Basic type for adding multiple commodities together * * Unlike the amount_t class, which throws an exception if amounts of * differing commodities are added or subtracted, the balance_t class @@ -48,7 +53,7 @@ namespace ledger { -DECLARE_EXCEPTION(balance_error); +DECLARE_EXCEPTION(balance_error, std::runtime_error); /** * @class balance_t @@ -75,14 +80,10 @@ class balance_t multiplicative<balance_t, long> > > > > > > > > > > > > > { public: - typedef std::map<const commodity_t *, amount_t> amounts_map; + typedef std::map<commodity_t *, amount_t> amounts_map; amounts_map amounts; - // jww (2007-05-20): Remove these two by adding access methods - friend class value_t; - friend class entry_base_t; - /** * Constructors. balance_t supports similar forms of construction * to amount_t. @@ -110,25 +111,13 @@ public: TRACE_CTOR(balance_t, "const amount_t&"); if (amt.is_null()) throw_(balance_error, - "Cannot initialize a balance from an uninitialized amount"); + _("Cannot initialize a balance from an uninitialized amount")); if (! amt.is_realzero()) amounts.insert(amounts_map::value_type(&amt.commodity(), amt)); } - balance_t(const double val) { - TRACE_CTOR(balance_t, "const double"); - amounts.insert - (amounts_map::value_type(amount_t::current_pool->null_commodity, val)); - } - balance_t(const unsigned long val) { - TRACE_CTOR(balance_t, "const unsigned long"); - amounts.insert - (amounts_map::value_type(amount_t::current_pool->null_commodity, val)); - } - balance_t(const long val) { - TRACE_CTOR(balance_t, "const long"); - amounts.insert - (amounts_map::value_type(amount_t::current_pool->null_commodity, val)); - } + balance_t(const double val); + balance_t(const unsigned long val); + balance_t(const long val); explicit balance_t(const string& val) { TRACE_CTOR(balance_t, "const string&"); @@ -145,7 +134,7 @@ public: * Destructor. Destroys all of the accumulated amounts in the * balance. */ - virtual ~balance_t() { + ~balance_t() { TRACE_DTOR(balance_t); } @@ -164,7 +153,7 @@ public: balance_t& operator=(const amount_t& amt) { if (amt.is_null()) throw_(balance_error, - "Cannot assign an uninitialized amount to a balance"); + _("Cannot assign an uninitialized amount to a balance")); amounts.clear(); if (! amt.is_realzero()) @@ -208,7 +197,7 @@ public: bool operator==(const amount_t& amt) const { if (amt.is_null()) throw_(balance_error, - "Cannot compare a balance to an uninitialized amount"); + _("Cannot compare a balance to an uninitialized amount")); if (amt.is_realzero()) return amounts.empty(); @@ -218,7 +207,7 @@ public: template <typename T> bool operator==(const T& val) const { - return *this == balance_t(val); + return *this == amount_t(val); } /** @@ -228,11 +217,29 @@ public: */ balance_t& operator+=(const balance_t& bal); balance_t& operator+=(const amount_t& amt); + balance_t& operator+=(const double val) { + return *this += amount_t(val); + } + balance_t& operator+=(const unsigned long val) { + return *this += amount_t(val); + } + balance_t& operator+=(const long val) { + return *this += amount_t(val); + } + balance_t& operator-=(const balance_t& bal); balance_t& operator-=(const amount_t& amt); + balance_t& operator-=(const double val) { + return *this -= amount_t(val); + } + balance_t& operator-=(const unsigned long val) { + return *this -= amount_t(val); + } + balance_t& operator-=(const long val) { + return *this -= amount_t(val); + } - virtual balance_t& operator*=(const amount_t& amt); - + balance_t& operator*=(const amount_t& amt); balance_t& operator*=(const double val) { return *this *= amount_t(val); } @@ -243,8 +250,7 @@ public: return *this *= amount_t(val); } - virtual balance_t& operator/=(const amount_t& amt); - + balance_t& operator/=(const amount_t& amt); balance_t& operator/=(const double val) { return *this /= amount_t(val); } @@ -275,7 +281,7 @@ public: * amount. That is, a balance of 10m and 1799s will unreduce to * 39.98m. * - * value(optional<moment_t>) returns the total historical value for + * value(optional<datetime_t>) returns the total historical value for * a balance -- the default moment returns a value based on the most * recently known price -- based on the price history of its * component commodities. See amount_t::value for an example. @@ -289,64 +295,108 @@ public: * in_place_reduce() * in_place_unreduce() */ - balance_t negate() const { + balance_t negated() const { balance_t temp(*this); temp.in_place_negate(); return temp; } - virtual balance_t& in_place_negate() { - for (amounts_map::iterator i = amounts.begin(); - i != amounts.end(); - i++) - i->second.in_place_negate(); - return *this; + void in_place_negate() { + foreach (amounts_map::value_type& pair, amounts) + pair.second.in_place_negate(); } balance_t operator-() const { - return negate(); + return negated(); } balance_t abs() const { balance_t temp; - for (amounts_map::const_iterator i = amounts.begin(); - i != amounts.end(); - i++) - temp += i->second.abs(); + foreach (const amounts_map::value_type& pair, amounts) + temp += pair.second.abs(); + return temp; + } + + balance_t rounded() const { + balance_t temp(*this); + temp.in_place_round(); + return temp; + } + void in_place_round() { + balance_t temp; + foreach (const amounts_map::value_type& pair, amounts) + temp += pair.second.rounded(); + *this = temp; + } + + balance_t truncated() const { + balance_t temp(*this); + temp.in_place_truncate(); + return temp; + } + void in_place_truncate() { + balance_t temp; + foreach (const amounts_map::value_type& pair, amounts) + temp += pair.second.truncated(); + *this = temp; + } + + balance_t floored() const { + balance_t temp(*this); + temp.in_place_floor(); return temp; } + void in_place_floor() { + balance_t temp; + foreach (const amounts_map::value_type& pair, amounts) + temp += pair.second.floored(); + *this = temp; + } - balance_t reduce() const { + balance_t unrounded() const { + balance_t temp(*this); + temp.in_place_unround(); + return temp; + } + void in_place_unround() { + balance_t temp; + foreach (const amounts_map::value_type& pair, amounts) + temp += pair.second.unrounded(); + *this = temp; + } + + balance_t reduced() const { balance_t temp(*this); temp.in_place_reduce(); return temp; } - virtual balance_t& in_place_reduce() { + void in_place_reduce() { // A temporary must be used here because reduction may cause // multiple component amounts to collapse to the same commodity. balance_t temp; - for (amounts_map::const_iterator i = amounts.begin(); - i != amounts.end(); - i++) - temp += i->second.reduce(); - return *this = temp; + foreach (const amounts_map::value_type& pair, amounts) + temp += pair.second.reduced(); + *this = temp; } - balance_t unreduce() const { + balance_t unreduced() const { balance_t temp(*this); temp.in_place_unreduce(); return temp; } - virtual balance_t& in_place_unreduce() { + void in_place_unreduce() { // A temporary must be used here because unreduction may cause // multiple component amounts to collapse to the same commodity. balance_t temp; - for (amounts_map::const_iterator i = amounts.begin(); - i != amounts.end(); - i++) - temp += i->second.unreduce(); - return *this = temp; + foreach (const amounts_map::value_type& pair, amounts) + temp += pair.second.unreduced(); + *this = temp; } - optional<balance_t> value(const optional<moment_t>& moment = none) const; + optional<balance_t> + value(const bool primary_only = false, + const optional<datetime_t>& moment = none, + const optional<commodity_t&>& in_terms_of = none) const; + + balance_t price() const; /** * Truth tests. An balance may be truth test in two ways: @@ -367,10 +417,15 @@ public: * it. */ operator bool() const { - for (amounts_map::const_iterator i = amounts.begin(); - i != amounts.end(); - i++) - if (i->second.is_nonzero()) + return is_nonzero(); + } + + bool is_nonzero() const { + if (is_empty()) + return false; + + foreach (const amounts_map::value_type& pair, amounts) + if (pair.second.is_nonzero()) return true; return false; } @@ -379,10 +434,8 @@ public: if (is_empty()) return true; - for (amounts_map::const_iterator i = amounts.begin(); - i != amounts.end(); - i++) - if (! i->second.is_zero()) + foreach (const amounts_map::value_type& pair, amounts) + if (! pair.second.is_zero()) return false; return true; } @@ -391,10 +444,8 @@ public: if (is_empty()) return true; - for (amounts_map::const_iterator i = amounts.begin(); - i != amounts.end(); - i++) - if (! i->second.is_realzero()) + foreach (const amounts_map::value_type& pair, amounts) + if (! pair.second.is_realzero()) return false; return true; } @@ -402,19 +453,32 @@ public: bool is_empty() const { return amounts.size() == 0; } + bool single_amount() const { + return amounts.size() == 1; + } /** * Conversion methods. A balance can be converted to an amount, but * only if contains a single component amount. */ + operator string() const { + return to_string(); + } + string to_string() const { + std::ostringstream buf; + print(buf); + return buf.str(); + } + amount_t to_amount() const { if (is_empty()) - throw_(balance_error, "Cannot convert an empty balance to an amount"); + throw_(balance_error, _("Cannot convert an empty balance to an amount")); else if (amounts.size() == 1) return amounts.begin()->second; else throw_(balance_error, - "Cannot convert a balance with multiple commodities to an amount"); + _("Cannot convert a balance with multiple commodities to an amount")); + return amount_t(); } /** @@ -437,6 +501,13 @@ public: optional<amount_t> commodity_amount(const optional<const commodity_t&>& commodity = none) const; + balance_t number() const { + balance_t temp; + foreach (const amounts_map::value_type& pair, amounts) + temp += pair.second.number(); + return temp; + } + /** * Annotated commodity methods. The amounts contained by a balance * may use annotated commodities. The `strip_annotations' method @@ -444,10 +515,7 @@ public: * their commodity annotations likewise stripped. See * amount_t::strip_annotations for more details. */ - balance_t - strip_annotations(const bool keep_price = amount_t::keep_price, - const bool keep_date = amount_t::keep_date, - const bool keep_tag = amount_t::keep_tag) const; + balance_t strip_annotations(const keep_details_t& what_to_keep) const; /** * Printing methods. A balance may be output to a stream using the @@ -470,8 +538,11 @@ public: * relative amounts of those commodities. There is no option to * change this behavior. */ - void print(std::ostream& out, const int first_width, - const int latter_width = -1) const; + void print(std::ostream& out, + const int first_width = -1, + const int latter_width = -1, + const bool right_justify = false, + const bool colorize = false) const; /** * Debugging methods. There are two methods defined to help with @@ -487,26 +558,36 @@ public: void dump(std::ostream& out) const { out << "BALANCE("; bool first = true; - for (amounts_map::const_iterator i = amounts.begin(); - i != amounts.end(); - i++) { + foreach (const amounts_map::value_type& pair, amounts) { if (first) first = false; else out << ", "; - i->second.print(out); + pair.second.print(out); } out << ")"; } - virtual bool valid() const { - for (amounts_map::const_iterator i = amounts.begin(); - i != amounts.end(); - i++) - if (! i->second.valid()) + bool valid() const { + foreach (const amounts_map::value_type& pair, amounts) + if (! pair.second.valid()) { + DEBUG("ledger.validate", "balance_t: ! pair.second.valid()"); return false; + } return true; } + +#if defined(HAVE_BOOST_SERIALIZATION) +private: + /** Serialization. */ + + friend class boost::serialization::access; + + template<class Archive> + void serialize(Archive& ar, const unsigned int /* version */) { + ar & amounts; + } +#endif // HAVE_BOOST_SERIALIZATION }; inline std::ostream& operator<<(std::ostream& out, const balance_t& bal) { @@ -514,6 +595,8 @@ inline std::ostream& operator<<(std::ostream& out, const balance_t& bal) { return out; } +void to_xml(std::ostream& out, const balance_t& amt); + } // namespace ledger #endif // _BALANCE_H diff --git a/src/chain.cc b/src/chain.cc new file mode 100644 index 00000000..113a71d8 --- /dev/null +++ b/src/chain.cc @@ -0,0 +1,241 @@ +/* + * 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 "chain.h" +#include "predicate.h" +#include "filters.h" +#include "report.h" +#include "session.h" + +namespace ledger { + +post_handler_ptr chain_post_handlers(report_t& report, + post_handler_ptr base_handler, + bool for_accounts_report) +{ + post_handler_ptr handler(base_handler); + predicate_t display_predicate; + predicate_t only_predicate; + + assert(report.HANDLED(amount_)); + expr_t& expr(report.HANDLER(amount_).expr); + expr.set_context(&report); + + if (! for_accounts_report) { + // Make sure only forecast postings which match are allowed through + if (report.HANDLED(forecast_while_)) { + handler.reset(new filter_posts + (handler, predicate_t(report.HANDLER(forecast_while_).str(), + report.what_to_keep()), + report)); + } + + // truncate_xacts cuts off a certain number of _xacts_ from being + // displayed. It does not affect calculation. + if (report.HANDLED(head_) || report.HANDLED(tail_)) + handler.reset + (new truncate_xacts(handler, + report.HANDLED(head_) ? + report.HANDLER(head_).value.to_int() : 0, + report.HANDLED(tail_) ? + report.HANDLER(tail_).value.to_int() : 0)); + + // filter_posts will only pass through posts matching the + // `display_predicate'. + if (report.HANDLED(display_)) { + display_predicate = predicate_t(report.HANDLER(display_).str(), + report.what_to_keep()); + handler.reset(new filter_posts(handler, display_predicate, report)); + } + } + + // changed_value_posts adds virtual posts to the list to account for changes + // in market value of commodities, which otherwise would affect the running + // total unpredictably. + if (report.HANDLED(revalued) && (! for_accounts_report || + report.HANDLED(unrealized))) + handler.reset(new changed_value_posts(handler, report, + for_accounts_report, + report.HANDLED(unrealized))); + + // calc_posts computes the running total. When this appears will determine, + // for example, whether filtered posts are included or excluded from the + // running total. + handler.reset(new calc_posts(handler, expr, (! for_accounts_report || + (report.HANDLED(revalued) && + report.HANDLED(unrealized))))); + + // filter_posts will only pass through posts matching the + // `secondary_predicate'. + if (report.HANDLED(only_)) { + only_predicate = predicate_t(report.HANDLER(only_).str(), + report.what_to_keep()); + handler.reset(new filter_posts(handler, only_predicate, report)); + } + + if (! for_accounts_report) { + // sort_posts will sort all the posts it sees, based on the `sort_order' + // value expression. + if (report.HANDLED(sort_)) { + if (report.HANDLED(sort_xacts_)) + handler.reset(new sort_xacts(handler, report.HANDLER(sort_).str())); + else + handler.reset(new sort_posts(handler, report.HANDLER(sort_).str())); + } + else if (! report.HANDLED(period_) && + ! report.HANDLED(unsorted)) { + handler.reset(new sort_posts(handler, "date")); + } + + // collapse_posts causes xacts with multiple posts to appear as xacts + // with a subtotaled post for each commodity used. + if (report.HANDLED(collapse)) + handler.reset(new collapse_posts(handler, expr, + display_predicate, only_predicate, + report.HANDLED(collapse_if_zero))); + + // subtotal_posts combines all the posts it receives into one subtotal + // xact, which has one post for each commodity in each account. + // + // period_posts is like subtotal_posts, but it subtotals according to time + // periods rather than totalling everything. + // + // dow_posts is like period_posts, except that it reports all the posts + // that fall on each subsequent day of the week. + if (report.HANDLED(equity)) + handler.reset(new posts_as_equity(handler, expr)); + else if (report.HANDLED(subtotal)) + handler.reset(new subtotal_posts(handler, expr)); + } + + if (report.HANDLED(dow)) + handler.reset(new dow_posts(handler, expr)); + else if (report.HANDLED(by_payee)) + handler.reset(new by_payee_posts(handler, expr)); + + // interval_posts groups posts together based on a time period, such as + // weekly or monthly. + if (report.HANDLED(period_)) { + handler.reset(new interval_posts(handler, expr, + report.HANDLER(period_).str(), + report.HANDLED(exact), + report.HANDLED(empty))); + handler.reset(new sort_posts(handler, "date")); + } + + if (report.HANDLED(date_)) + handler.reset(new transfer_details(handler, transfer_details::SET_DATE, + report.session.journal->master, + report.HANDLER(date_).str(), + report)); + if (report.HANDLED(account_)) + handler.reset(new transfer_details(handler, transfer_details::SET_ACCOUNT, + report.session.journal->master, + report.HANDLER(account_).str(), + report)); + if (report.HANDLED(payee_)) + handler.reset(new transfer_details(handler, transfer_details::SET_PAYEE, + report.session.journal->master, + report.HANDLER(payee_).str(), + report)); + + // related_posts will pass along all posts related to the post received. If + // the `related_all' handler is on, then all the xact's posts are passed; + // meaning that if one post of an xact is to be printed, all the post for + // that xact will be printed. + if (report.HANDLED(related)) + handler.reset(new related_posts(handler, report.HANDLED(related_all))); + + // anonymize_posts removes all meaningful information from xact payee's and + // account names, for the sake of creating useful bug reports. + if (report.HANDLED(anon)) + handler.reset(new anonymize_posts(handler)); + + // This filter_posts will only pass through posts matching the `predicate'. + if (report.HANDLED(limit_)) { + DEBUG("report.predicate", + "Report predicate expression = " << report.HANDLER(limit_).str()); + handler.reset(new filter_posts + (handler, predicate_t(report.HANDLER(limit_).str(), + report.what_to_keep()), + report)); + } + + // budget_posts takes a set of posts from a data file and uses them to + // generate "budget posts" which balance against the reported posts. + // + // forecast_posts is a lot like budget_posts, except that it adds xacts + // only for the future, and does not balance them against anything but the + // future balance. + + if (report.budget_flags != BUDGET_NO_BUDGET) { + budget_posts * budget_handler = new budget_posts(handler, + report.budget_flags); + budget_handler->add_period_xacts(report.session.journal->period_xacts); + handler.reset(budget_handler); + + // Apply this before the budget handler, so that only matching posts are + // calculated toward the budget. The use of filter_posts above will + // further clean the results so that no automated posts that don't match + // the filter get reported. + if (report.HANDLED(limit_)) + handler.reset(new filter_posts + (handler, predicate_t(report.HANDLER(limit_).str(), + report.what_to_keep()), + report)); + } + else if (report.HANDLED(forecast_while_)) { + forecast_posts * forecast_handler + = new forecast_posts(handler, + predicate_t(report.HANDLER(forecast_while_).str(), + report.what_to_keep()), + report, + report.HANDLED(forecast_years_) ? + static_cast<std::size_t> + (report.HANDLER(forecast_years_).value.to_long()) : + 5UL); + forecast_handler->add_period_xacts(report.session.journal->period_xacts); + handler.reset(forecast_handler); + + // See above, under budget_posts. + if (report.HANDLED(limit_)) + handler.reset(new filter_posts + (handler, predicate_t(report.HANDLER(limit_).str(), + report.what_to_keep()), + report)); + } + + return handler; +} + +} // namespace ledger diff --git a/src/chain.h b/src/chain.h new file mode 100644 index 00000000..a2b75b3a --- /dev/null +++ b/src/chain.h @@ -0,0 +1,91 @@ +/* + * 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. + */ + +/** + * @addtogroup report + */ + +/** + * @file chain.h + * @author John Wiegley + * + * @ingroup report + */ +#ifndef _CHAIN_H +#define _CHAIN_H + +#include "utils.h" + +namespace ledger { + +class post_t; +class account_t; + +template <typename T> +struct item_handler : public noncopyable +{ + shared_ptr<item_handler> handler; + +public: + item_handler() { + TRACE_CTOR(item_handler, ""); + } + item_handler(shared_ptr<item_handler> _handler) : handler(_handler) { + TRACE_CTOR(item_handler, "shared_ptr<item_handler>"); + } + virtual ~item_handler() { + TRACE_DTOR(item_handler); + } + + virtual void flush() { + if (handler.get()) + handler->flush(); + } + virtual void operator()(T& item) { + if (handler.get()) { + check_for_signal(); + (*handler.get())(item); + } + } +}; + +typedef shared_ptr<item_handler<post_t> > post_handler_ptr; +typedef shared_ptr<item_handler<account_t> > acct_handler_ptr; + +class report_t; +post_handler_ptr +chain_post_handlers(report_t& report, + post_handler_ptr base_handler, + bool for_accounts_report = false); + +} // namespace ledger + +#endif // _CHAIN_H diff --git a/src/commodity.cc b/src/commodity.cc new file mode 100644 index 00000000..79ed77fe --- /dev/null +++ b/src/commodity.cc @@ -0,0 +1,705 @@ +/* + * 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 "amount.h" +#include "commodity.h" +#include "annotate.h" +#include "pool.h" + +namespace ledger { + +bool commodity_t::european_by_default = false; + +void commodity_t::history_t::add_price(commodity_t& source, + const datetime_t& date, + const amount_t& price, + const bool reflexive) +{ + DEBUG("commodity.prices.add", "add_price to " << source + << (reflexive ? " (secondary)" : " (primary)") + << " : " << date << ", " << price); + + history_map::iterator i = prices.find(date); + if (i != prices.end()) { + (*i).second = price; + } else { + std::pair<history_map::iterator, bool> result + = prices.insert(history_map::value_type(date, price)); + assert(result.second); + } + + if (reflexive) { + amount_t inverse = price.inverted(); + inverse.set_commodity(const_cast<commodity_t&>(source)); + price.commodity().add_price(date, inverse, false); + } else { + DEBUG("commodity.prices.add", + "marking commodity " << source.symbol() << " as primary"); + source.add_flags(COMMODITY_PRIMARY); + } +} + +bool commodity_t::history_t::remove_price(const datetime_t& date) +{ + DEBUG("commodity.prices.add", "remove_price: " << date); + + history_map::size_type n = prices.erase(date); + if (n > 0) + return true; + return false; +} + +void commodity_t::varied_history_t:: + add_price(commodity_t& source, + const datetime_t& date, + const amount_t& price, + const bool reflexive) +{ + optional<history_t&> hist = history(price.commodity()); + if (! hist) { + std::pair<history_by_commodity_map::iterator, bool> result + = histories.insert(history_by_commodity_map::value_type + (&price.commodity(), history_t())); + assert(result.second); + + hist = (*result.first).second; + } + assert(hist); + + hist->add_price(source, date, price, reflexive); +} + +bool commodity_t::varied_history_t::remove_price(const datetime_t& date, + commodity_t& comm) +{ + DEBUG("commodity.prices.add", "varied_remove_price: " << date << ", " << comm); + + if (optional<history_t&> hist = history(comm)) + return hist->remove_price(date); + return false; +} + +optional<price_point_t> +commodity_t::history_t::find_price(const optional<datetime_t>& moment, + const optional<datetime_t>& oldest +#if defined(DEBUG_ON) + , const int indent +#endif + ) const +{ + price_point_t point; + bool found = false; + +#define DEBUG_INDENT(cat, indent) \ + do { \ + if (SHOW_DEBUG(cat)) \ + for (int i = 0; i < indent; i++) \ + ledger::_log_buffer << " "; \ + } while (false) + +#if defined(DEBUG_ON) + DEBUG_INDENT("commodity.prices.find", indent); + if (moment) + DEBUG("commodity.prices.find", "find price nearest before or on: " << *moment); + else + DEBUG("commodity.prices.find", "find any price"); + + if (oldest) { + DEBUG_INDENT("commodity.prices.find", indent); + DEBUG("commodity.prices.find", " but no older than: " << *oldest); + } +#endif + + if (prices.size() == 0) { +#if defined(DEBUG_ON) + DEBUG_INDENT("commodity.prices.find", indent); + DEBUG("commodity.prices.find", " there are no prices in this history"); +#endif + return none; + } + + if (! moment) { + history_map::const_reverse_iterator r = prices.rbegin(); + point.when = (*r).first; + point.price = (*r).second; + found = true; +#if defined(DEBUG_ON) + DEBUG_INDENT("commodity.prices.find", indent); + DEBUG("commodity.prices.find", " using most recent price"); +#endif + } else { + history_map::const_iterator i = prices.lower_bound(*moment); + if (i == prices.end()) { + history_map::const_reverse_iterator r = prices.rbegin(); + point.when = (*r).first; + point.price = (*r).second; + found = true; +#if defined(DEBUG_ON) + DEBUG_INDENT("commodity.prices.find", indent); + DEBUG("commodity.prices.find", " using last price"); +#endif + } else { + point.when = (*i).first; + if (*moment < point.when) { + if (i != prices.begin()) { + --i; + point.when = (*i).first; + point.price = (*i).second; + found = true; + } + } else { + point.price = (*i).second; + found = true; + } +#if defined(DEBUG_ON) + DEBUG_INDENT("commodity.prices.find", indent); + DEBUG("commodity.prices.find", " using found price"); +#endif + } + } + + if (! found) { +#if defined(DEBUG_ON) + DEBUG_INDENT("commodity.prices.find", indent); + DEBUG("commodity.prices.find", " could not find a price"); +#endif + return none; + } + else if (moment && point.when > *moment) { +#if defined(DEBUG_ON) + DEBUG_INDENT("commodity.prices.find", indent); + DEBUG("commodity.prices.find", " price is too young "); +#endif + return none; + } + else if (oldest && point.when < *oldest) { +#if defined(DEBUG_ON) + DEBUG_INDENT("commodity.prices.find", indent); + DEBUG("commodity.prices.find", " price is too old "); +#endif + return none; + } + else { +#if defined(DEBUG_ON) + DEBUG_INDENT("commodity.prices.find", indent); + DEBUG("commodity.prices.find", + " returning price: " << point.when << ", " << point.price); +#endif + return point; + } +} + +optional<price_point_t> +commodity_t::varied_history_t::find_price(const commodity_t& source, + const optional<commodity_t&>& commodity, + const optional<datetime_t>& moment, + const optional<datetime_t>& oldest +#if defined(DEBUG_ON) + , const int indent +#endif + ) const +{ + optional<price_point_t> point; + optional<datetime_t> limit = oldest; + + assert(! commodity || source != *commodity); + +#if defined(DEBUG_ON) + DEBUG_INDENT("commodity.prices.find", indent); + DEBUG("commodity.prices.find", "varied_find_price for: " << source); + + DEBUG_INDENT("commodity.prices.find", indent); + if (commodity) + DEBUG("commodity.prices.find", " looking for: commodity '" << *commodity << "'"); + else + DEBUG("commodity.prices.find", " looking for: any commodity"); + + if (moment) { + DEBUG_INDENT("commodity.prices.find", indent); + DEBUG("commodity.prices.find", " time index: " << *moment); + } + + if (oldest) { + DEBUG_INDENT("commodity.prices.find", indent); + DEBUG("commodity.prices.find", " only consider prices younger than: " << *oldest); + } +#endif + + // Either we couldn't find a history for the target commodity, or we + // couldn't find a price. In either case, search all histories known + // to this commodity for a price which we can calculate in terms of + // the goal commodity. + price_point_t best; + bool found = false; + + foreach (history_by_commodity_map::value_type hist, histories) { + commodity_t& comm(*hist.first); + if (comm == source) + continue; + +#if defined(DEBUG_ON) + DEBUG_INDENT("commodity.prices.find", indent + 1); + DEBUG("commodity.prices.find", + " searching for price via commodity '" << comm << "'"); +#endif + + point = hist.second.find_price(moment, limit +#if defined(DEBUG_ON) + , indent + 2 +#endif + ); + assert(! point || point->price.commodity() == comm); + + if (point) { + optional<price_point_t> xlat; + + if (commodity && comm != *commodity) { +#if defined(DEBUG_ON) + DEBUG_INDENT("commodity.prices.find", indent + 1); + DEBUG("commodity.prices.find", " looking for translation price"); +#endif + + xlat = comm.find_price(commodity, moment, limit +#if defined(DEBUG_ON) + , indent + 2 +#endif + ); + if (xlat) { +#if defined(DEBUG_ON) + DEBUG_INDENT("commodity.prices.find", indent + 1); + DEBUG("commodity.prices.find", " found translated price " + << xlat->price << " from " << xlat->when); +#endif + + point->price = xlat->price * point->price; + if (xlat->when < point->when) { + point->when = xlat->when; +#if defined(DEBUG_ON) + DEBUG_INDENT("commodity.prices.find", indent + 1); + DEBUG("commodity.prices.find", + " adjusting date of result back to " << point->when); +#endif + } + } else { +#if defined(DEBUG_ON) + DEBUG_INDENT("commodity.prices.find", indent + 1); + DEBUG("commodity.prices.find", " saw no translated price there"); +#endif + continue; + } + } + + assert(! commodity || point->price.commodity() == *commodity); +#if defined(DEBUG_ON) + DEBUG_INDENT("commodity.prices.find", indent + 1); + DEBUG("commodity.prices.find", + " saw a price there: " << point->price << " from " << point->when); +#endif + if (! limit || point->when > *limit) { + limit = point->when; + best = *point; + found = true; + } + } else { +#if defined(DEBUG_ON) + DEBUG_INDENT("commodity.prices.find", indent + 1); + DEBUG("commodity.prices.find", " saw no price there"); +#endif + } + } + + if (found) { +#if defined(DEBUG_ON) + DEBUG_INDENT("commodity.prices.find", indent); + DEBUG("commodity.prices.find", + " found price " << best.price << " from " << best.when); + DEBUG("commodity.download", + "found price " << best.price << " from " << best.when); +#endif + return best; + } + return none; +} + +optional<commodity_t::history_t&> +commodity_t::varied_history_t::history(const optional<commodity_t&>& commodity) +{ + commodity_t * comm = NULL; + if (! commodity) { + if (histories.size() > 1) + return none; + comm = (*histories.begin()).first; + } else { + comm = &(*commodity); + } + + history_by_commodity_map::iterator i = histories.find(comm); + if (i != histories.end()) + return (*i).second; + + return none; +} + +optional<price_point_t> +commodity_t::check_for_updated_price(const optional<price_point_t>& point, + const optional<datetime_t>& moment, + const optional<commodity_t&>& in_terms_of) +{ + if (pool().get_quotes && ! has_flags(COMMODITY_NOMARKET)) { + bool exceeds_leeway = true; + + if (point) { + time_duration_t::sec_type seconds_diff; + if (moment) { + seconds_diff = (*moment - point->when).total_seconds(); + DEBUG("commodity.download", "moment = " << *moment); + DEBUG("commodity.download", "slip.moment = " << seconds_diff); + } else { + seconds_diff = (TRUE_CURRENT_TIME() - point->when).total_seconds(); + DEBUG("commodity.download", "slip.now = " << seconds_diff); + } + + DEBUG("commodity.download", "leeway = " << pool().quote_leeway); + if (seconds_diff < pool().quote_leeway) + exceeds_leeway = false; + } + + if (exceeds_leeway) { + DEBUG("commodity.download", + "attempting to download a more current quote..."); + if (optional<price_point_t> quote = + pool().get_commodity_quote(*this, in_terms_of)) { + if (! in_terms_of || + (quote->price.has_commodity() && + quote->price.commodity() == *in_terms_of)) + return quote; + } + } + } + return point; +} + +commodity_t::operator bool() const +{ + return this != pool().null_commodity; +} + +bool commodity_t::symbol_needs_quotes(const string& symbol) +{ + foreach (char ch, symbol) + if (std::isspace(ch) || std::isdigit(ch) || ch == '-' || ch == '.') + return true; + + return false; +} + +namespace { + bool is_reserved_token(const char * buf) + { + switch (buf[0]) { + case 'a': + return std::strcmp(buf, "and") == 0; + case 'd': + return std::strcmp(buf, "div") == 0; + case 'e': + return std::strcmp(buf, "else") == 0; + case 'f': + return std::strcmp(buf, "false") == 0; + case 'i': + return std::strcmp(buf, "if") == 0; + case 'o': + return std::strcmp(buf, "or") == 0; + case 'n': + return std::strcmp(buf, "not") == 0; + case 't': + return std::strcmp(buf, "true") == 0; + } + return false; + } +} + +void commodity_t::parse_symbol(std::istream& in, string& symbol) +{ + // Invalid commodity characters: + // SPACE, TAB, NEWLINE, RETURN + // 0-9 . , ; - + * / ^ ? : & | ! = + // < > { } [ ] ( ) @ + + static int invalid_chars[256] = { + /* 0 1 2 3 4 5 6 7 8 9 a b c d e f */ + /* 00 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, + /* 10 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* 20 */ 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, + /* 30 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + /* 40 */ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* 50 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, + /* 60 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* 70 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, + /* 80 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* 90 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* a0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* b0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* c0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* d0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* e0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* f0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }; + + istream_pos_type pos = in.tellg(); + + char buf[256]; + char c = peek_next_nonws(in); + if (c == '"') { + in.get(c); + READ_INTO(in, buf, 255, c, c != '"'); + if (c == '"') + in.get(c); + else + throw_(amount_error, _("Quoted commodity symbol lacks closing quote")); + } else { + char * _p = buf; + c = static_cast<char>(in.peek()); + while (_p - buf < 255 && in.good() && ! in.eof() && c != '\n') { + std::size_t bytes = 0; + std::ptrdiff_t size = _p - buf; + unsigned char d = c; + + // Check for the start of a UTF-8 multi-byte encoded string + if (d >= 192 && d <= 223 && size < 254) + bytes = 2; + else if (d >= 224 && d <= 239 && size < 253) + bytes = 3; + else if (d >= 240 && d <= 247 && size < 252) + bytes = 4; + else if (d >= 248 && d <= 251 && size < 251) + bytes = 5; + else if (d >= 252 && d <= 253 && size < 250) + bytes = 6; + else if (d >= 254) // UTF-8 encoding error + break; + + if (bytes > 0) { // we're looking at a UTF-8 encoding + for (std::size_t i = 0; i < bytes; i++) { + in.get(c); + if (in.bad() || in.eof()) + throw_(amount_error, _("Invalid UTF-8 encoding for commodity name")); + *_p++ = c; + } + } + else if (invalid_chars[static_cast<unsigned char>(c)]) { + break; + } + else { + in.get(c); + if (in.eof()) + break; + if (c == '\\') { + in.get(c); + if (in.eof()) + throw_(amount_error, _("Backslash at end of commodity name")); + } + *_p++ = c; + } + + c = static_cast<char>(in.peek()); + } + *_p = '\0'; + + if (is_reserved_token(buf)) + buf[0] = '\0'; + } + symbol = buf; + + if (symbol.length() == 0) { + in.clear(); + in.seekg(pos, std::ios::beg); + } +} + +void commodity_t::parse_symbol(char *& p, string& symbol) +{ + if (*p == '"') { + char * q = std::strchr(p + 1, '"'); + if (! q) + throw_(amount_error, _("Quoted commodity symbol lacks closing quote")); + symbol = string(p + 1, 0, q - p - 1); + p = q + 2; + } else { + char * q = next_element(p); + symbol = p; + if (q) + p = q; + else + p += symbol.length(); + } + if (symbol.empty()) + throw_(amount_error, _("Failed to parse commodity")); +} + +bool commodity_t::valid() const +{ + if (symbol().empty() && this != pool().null_commodity) { + DEBUG("ledger.validate", + "commodity_t: symbol().empty() && this != null_commodity"); + return false; + } + + if (annotated && ! base) { + DEBUG("ledger.validate", "commodity_t: annotated && ! base"); + return false; + } + + if (precision() > 16) { + DEBUG("ledger.validate", "commodity_t: precision() > 16"); + return false; + } + + return true; +} + +bool compare_amount_commodities::operator()(const amount_t * left, + const amount_t * right) const +{ + commodity_t& leftcomm(left->commodity()); + commodity_t& rightcomm(right->commodity()); + + DEBUG("commodity.compare", " left symbol (" << leftcomm << ")"); + DEBUG("commodity.compare", "right symbol (" << rightcomm << ")"); + + int cmp = leftcomm.base_symbol().compare(rightcomm.base_symbol()); + if (cmp != 0) + return cmp < 0; + + if (! leftcomm.has_annotation()) { + return rightcomm.has_annotation(); + } + else if (! rightcomm.has_annotation()) { + return ! leftcomm.has_annotation(); + } + else { + annotated_commodity_t& aleftcomm(static_cast<annotated_commodity_t&>(leftcomm)); + annotated_commodity_t& arightcomm(static_cast<annotated_commodity_t&>(rightcomm)); + + if (! aleftcomm.details.price && arightcomm.details.price) + return true; + if (aleftcomm.details.price && ! arightcomm.details.price) + return false; + + if (aleftcomm.details.price && arightcomm.details.price) { + amount_t leftprice(*aleftcomm.details.price); + amount_t rightprice(*arightcomm.details.price); + + if (leftprice.commodity() == rightprice.commodity()) { + return (leftprice - rightprice).sign() < 0; + } else { + // Since we have two different amounts, there's really no way + // to establish a true sorting order; we'll just do it based + // on the numerical values. + leftprice.clear_commodity(); + rightprice.clear_commodity(); + return (leftprice - rightprice).sign() < 0; + } + } + + if (! aleftcomm.details.date && arightcomm.details.date) + return true; + if (aleftcomm.details.date && ! arightcomm.details.date) + return false; + + if (aleftcomm.details.date && arightcomm.details.date) { + gregorian::date_duration diff = + *aleftcomm.details.date - *arightcomm.details.date; + return diff.is_negative(); + } + + if (! aleftcomm.details.tag && arightcomm.details.tag) + return true; + if (aleftcomm.details.tag && ! arightcomm.details.tag) + return false; + + if (aleftcomm.details.tag && arightcomm.details.tag) + return *aleftcomm.details.tag < *arightcomm.details.tag; + + assert(false); + return true; + } +} + +void to_xml(std::ostream& out, const commodity_t& comm, + bool commodity_details) +{ + push_xml x(out, "commodity", true); + + out << " flags=\""; + if (! (comm.has_flags(COMMODITY_STYLE_SUFFIXED))) out << 'P'; + if (comm.has_flags(COMMODITY_STYLE_SEPARATED)) out << 'S'; + if (comm.has_flags(COMMODITY_STYLE_THOUSANDS)) out << 'T'; + if (comm.has_flags(COMMODITY_STYLE_EUROPEAN)) out << 'E'; + out << '"'; + + x.close_attrs(); + + { + push_xml y(out, "symbol"); + out << y.guard(comm.symbol()); + } + + if (commodity_details) { + if (comm.has_annotation()) + to_xml(out, as_annotated_commodity(comm).details); + + if (comm.varied_history()) { + push_xml y(out, "varied-history"); + + foreach (const commodity_t::history_by_commodity_map::value_type& pair, + comm.varied_history()->histories) { + { + push_xml z(out, "symbol"); + out << y.guard(pair.first->symbol()); + } + { + push_xml z(out, "history"); + + foreach (const commodity_t::history_map::value_type& inner_pair, + pair.second.prices) { + push_xml w(out, "price-point"); + to_xml(out, inner_pair.first); + to_xml(out, inner_pair.second); + } + } + } + } + } +} + +} // namespace ledger diff --git a/src/commodity.h b/src/commodity.h new file mode 100644 index 00000000..3370f3f2 --- /dev/null +++ b/src/commodity.h @@ -0,0 +1,431 @@ +/* + * 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. + */ + +/** + * @addtogroup math + */ + +/** + * @file commodity.h + * @author John Wiegley + * + * @ingroup math + * + * @brief Types for handling commodities + * + * This file contains one of the most basic types in Ledger: + * commodity_t, and its annotated cousin, annotated_commodity_t. + */ +#ifndef _COMMODITY_H +#define _COMMODITY_H + +namespace ledger { + +class keep_details_t; + +DECLARE_EXCEPTION(commodity_error, std::runtime_error); + +struct price_point_t +{ + datetime_t when; + amount_t price; + +#if defined(HAVE_BOOST_SERIALIZATION) +private: + /** Serialization. */ + + friend class boost::serialization::access; + + template<class Archive> + void serialize(Archive& ar, const unsigned int /* version */) { + ar & when; + ar & price; + } +#endif // HAVE_BOOST_SERIALIZATION +}; + +class commodity_t + : public delegates_flags<uint_least16_t>, + public equality_comparable1<commodity_t, noncopyable> +{ +public: + typedef std::map<const datetime_t, amount_t> history_map; + + struct history_t + { + history_map prices; + + void add_price(commodity_t& source, + const datetime_t& date, + const amount_t& price, + const bool reflexive = true); + bool remove_price(const datetime_t& date); + + optional<price_point_t> + find_price(const optional<datetime_t>& moment = none, + const optional<datetime_t>& oldest = none +#if defined(DEBUG_ON) + , const int indent = 0 +#endif + ) const; + +#if defined(HAVE_BOOST_SERIALIZATION) + private: + /** Serialization. */ + + friend class boost::serialization::access; + + template<class Archive> + void serialize(Archive& ar, const unsigned int /* version */) { + ar & prices; + } +#endif // HAVE_BOOST_SERIALIZATION + }; + + typedef std::map<commodity_t *, history_t> history_by_commodity_map; + + struct varied_history_t + { + history_by_commodity_map histories; + + void add_price(commodity_t& source, + const datetime_t& date, + const amount_t& price, + const bool reflexive = true); + bool remove_price(const datetime_t& date, commodity_t& commodity); + + optional<price_point_t> + find_price(const commodity_t& source, + const optional<commodity_t&>& commodity = none, + const optional<datetime_t>& moment = none, + const optional<datetime_t>& oldest = none +#if defined(DEBUG_ON) + , const int indent = 0 +#endif + ) const; + + optional<history_t&> + history(const optional<commodity_t&>& commodity = none); + +#if defined(HAVE_BOOST_SERIALIZATION) + private: + /** Serialization. */ + + friend class boost::serialization::access; + + template<class Archive> + void serialize(Archive& ar, const unsigned int /* version */) { + ar & histories; + } +#endif // HAVE_BOOST_SERIALIZATION + }; + +protected: + friend class commodity_pool_t; + friend class annotated_commodity_t; + + class base_t : public noncopyable, public supports_flags<uint_least16_t> + { + public: +#define COMMODITY_STYLE_DEFAULTS 0x000 +#define COMMODITY_STYLE_SUFFIXED 0x001 +#define COMMODITY_STYLE_SEPARATED 0x002 +#define COMMODITY_STYLE_EUROPEAN 0x004 +#define COMMODITY_STYLE_THOUSANDS 0x008 +#define COMMODITY_NOMARKET 0x010 +#define COMMODITY_BUILTIN 0x020 +#define COMMODITY_WALKED 0x040 +#define COMMODITY_KNOWN 0x080 +#define COMMODITY_PRIMARY 0x100 + + string symbol; + amount_t::precision_t precision; + optional<string> name; + optional<string> note; + optional<varied_history_t> varied_history; + optional<amount_t> smaller; + optional<amount_t> larger; + + mutable bool searched; + + public: + explicit base_t(const string& _symbol) + : supports_flags<uint_least16_t> + (commodity_t::european_by_default ? + static_cast<uint_least16_t>(COMMODITY_STYLE_EUROPEAN) : + static_cast<uint_least16_t>(COMMODITY_STYLE_DEFAULTS)), + symbol(_symbol), precision(0), searched(false) { + TRACE_CTOR(base_t, "const string&"); + } + virtual ~base_t() { + TRACE_DTOR(base_t); + } + +#if defined(HAVE_BOOST_SERIALIZATION) + private: + base_t() { + TRACE_CTOR(base_t, ""); + } + + /** Serialization. */ + + friend class boost::serialization::access; + + template<class Archive> + void serialize(Archive& ar, const unsigned int /* version */) { + ar & boost::serialization::base_object<supports_flags<uint_least16_t> >(*this); + ar & symbol; + ar & precision; + ar & name; + ar & note; + ar & varied_history; + ar & smaller; + ar & larger; + } +#endif // HAVE_BOOST_SERIALIZATION + }; + + shared_ptr<base_t> base; + + commodity_pool_t * parent_; + optional<string> qualified_symbol; + optional<string> mapping_key_; + bool annotated; + + explicit commodity_t(commodity_pool_t * _parent, + const shared_ptr<base_t>& _base) + : delegates_flags<uint_least16_t>(*_base.get()), base(_base), + parent_(_parent), annotated(false) { + TRACE_CTOR(commodity_t, "commodity_pool_t *, shared_ptr<base_t>"); + } + +public: + static bool european_by_default; + + virtual ~commodity_t() { + TRACE_DTOR(commodity_t); + } + + operator bool() const; + + virtual bool operator==(const commodity_t& comm) const { + if (comm.annotated) + return comm == *this; + return base.get() == comm.base.get(); + } + + static bool symbol_needs_quotes(const string& symbol); + + virtual commodity_t& referent() { + return *this; + } + virtual const commodity_t& referent() const { + return *this; + } + + bool has_annotation() const { + return annotated; + } + + virtual commodity_t& strip_annotations(const keep_details_t&) { + return *this; + } + virtual void write_annotations(std::ostream&) const {} + + commodity_pool_t& pool() const { + return *parent_; + } + + string base_symbol() const { + return base->symbol; + } + string symbol() const { + return qualified_symbol ? *qualified_symbol : base_symbol(); + } + + string mapping_key() const { + if (mapping_key_) + return *mapping_key_; + else + return base_symbol(); + } + + optional<string> name() const { + return base->name; + } + void set_name(const optional<string>& arg = none) { + base->name = arg; + } + + optional<string> note() const { + return base->note; + } + void set_note(const optional<string>& arg = none) { + base->note = arg; + } + + amount_t::precision_t precision() const { + return base->precision; + } + void set_precision(amount_t::precision_t arg) { + base->precision = arg; + } + + optional<amount_t> smaller() const { + return base->smaller; + } + void set_smaller(const optional<amount_t>& arg = none) { + base->smaller = arg; + } + + optional<amount_t> larger() const { + return base->larger; + } + void set_larger(const optional<amount_t>& arg = none) { + base->larger = arg; + } + + optional<varied_history_t&> varied_history() { + if (base->varied_history) + return *base->varied_history; + return none; + } + optional<const varied_history_t&> varied_history() const { + if (base->varied_history) + return *base->varied_history; + return none; + } + + optional<history_t&> history(const optional<commodity_t&>& commodity); + + // These methods provide a transparent pass-through to the underlying + // base->varied_history object. + + void add_price(const datetime_t& date, const amount_t& price, + const bool reflexive = true) { + if (! base->varied_history) + base->varied_history = varied_history_t(); + + base->varied_history->add_price(*this, date, price, reflexive); + } + bool remove_price(const datetime_t& date, commodity_t& commodity) { + if (base->varied_history) + base->varied_history->remove_price(date, commodity); + return false; + } + + optional<price_point_t> + find_price(const optional<commodity_t&>& commodity = none, + const optional<datetime_t>& moment = none, + const optional<datetime_t>& oldest = none +#if defined(DEBUG_ON) + , const int indent = 0 +#endif + ) const { + if (base->varied_history && ! has_flags(COMMODITY_WALKED)) { + const_cast<commodity_t&>(*this).add_flags(COMMODITY_WALKED); + optional<price_point_t> point = + base->varied_history->find_price(*this, commodity, moment, oldest +#if defined(DEBUG_ON) + , indent +#endif + ); + const_cast<commodity_t&>(*this).drop_flags(COMMODITY_WALKED); + return point; + } + return none; + } + + optional<price_point_t> + check_for_updated_price(const optional<price_point_t>& point, + const optional<datetime_t>& moment, + const optional<commodity_t&>& in_terms_of); + + // Methods related to parsing, reading, writing, etc., the commodity + // itself. + + static void parse_symbol(std::istream& in, string& symbol); + static void parse_symbol(char *& p, string& symbol); + static string parse_symbol(std::istream& in) { + string temp; + parse_symbol(in, temp); + return temp; + } + + void print(std::ostream& out) const { + out << symbol(); + } + + bool valid() const; + +#if defined(HAVE_BOOST_SERIALIZATION) +private: + supports_flags<uint_least16_t> temp_flags; + +protected: + explicit commodity_t() + : delegates_flags<uint_least16_t>(temp_flags), parent_(NULL), + annotated(false) { + TRACE_CTOR(commodity_t, ""); + } + +private: + /** Serialization. */ + + friend class boost::serialization::access; + + template<class Archive> + void serialize(Archive& ar, const unsigned int /* version */) { + ar & boost::serialization::base_object<delegates_flags<uint_least16_t> >(*this); + ar & base; + ar & parent_; + ar & qualified_symbol; + ar & mapping_key_; + ar & annotated; + } +#endif // HAVE_BOOST_SERIALIZATION +}; + +inline std::ostream& operator<<(std::ostream& out, const commodity_t& comm) { + comm.print(out); + return out; +} + +struct compare_amount_commodities { + bool operator()(const amount_t * left, const amount_t * right) const; +}; + +void to_xml(std::ostream& out, const commodity_t& comm, + bool commodity_details = false); + +} // namespace ledger + +#endif // _COMMODITY_H diff --git a/src/compare.cc b/src/compare.cc new file mode 100644 index 00000000..36884a46 --- /dev/null +++ b/src/compare.cc @@ -0,0 +1,114 @@ +/* + * 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 "compare.h" +#include "op.h" +#include "post.h" +#include "account.h" + +namespace ledger { + +void push_sort_value(std::list<sort_value_t>& sort_values, + expr_t::ptr_op_t node, scope_t& scope) +{ + if (node->kind == expr_t::op_t::O_CONS) { + push_sort_value(sort_values, node->left(), scope); + push_sort_value(sort_values, node->right(), scope); + } else { + bool inverted = false; + + if (node->kind == expr_t::op_t::O_NEG) { + inverted = true; + node = node->left(); + } + + sort_values.push_back(sort_value_t()); + sort_values.back().inverted = inverted; + sort_values.back().value = expr_t(node).calc(scope).simplified(); + + if (sort_values.back().value.is_null()) + throw_(calc_error, + _("Could not determine sorting value based an expression")); + } +} + +template <> +bool compare_items<post_t>::operator()(post_t * left, post_t * right) +{ + assert(left); + assert(right); + + post_t::xdata_t& lxdata(left->xdata()); + if (! lxdata.has_flags(POST_EXT_SORT_CALC)) { + bind_scope_t bound_scope(*sort_order.get_context(), *left); + find_sort_values(lxdata.sort_values, bound_scope); + lxdata.add_flags(POST_EXT_SORT_CALC); + } + + post_t::xdata_t& rxdata(right->xdata()); + if (! rxdata.has_flags(POST_EXT_SORT_CALC)) { + bind_scope_t bound_scope(*sort_order.get_context(), *right); + find_sort_values(rxdata.sort_values, bound_scope); + rxdata.add_flags(POST_EXT_SORT_CALC); + } + + return sort_value_is_less_than(lxdata.sort_values, rxdata.sort_values); +} + +template <> +bool compare_items<account_t>::operator()(account_t * left, account_t * right) +{ + assert(left); + assert(right); + + account_t::xdata_t& lxdata(left->xdata()); + if (! lxdata.has_flags(ACCOUNT_EXT_SORT_CALC)) { + bind_scope_t bound_scope(*sort_order.get_context(), *left); + find_sort_values(lxdata.sort_values, bound_scope); + lxdata.add_flags(ACCOUNT_EXT_SORT_CALC); + } + + account_t::xdata_t& rxdata(right->xdata()); + if (! rxdata.has_flags(ACCOUNT_EXT_SORT_CALC)) { + bind_scope_t bound_scope(*sort_order.get_context(), *right); + find_sort_values(rxdata.sort_values, bound_scope); + rxdata.add_flags(ACCOUNT_EXT_SORT_CALC); + } + + DEBUG("value.sort", "Comparing accounts " << left->fullname() + << " <> " << right->fullname()); + + return sort_value_is_less_than(lxdata.sort_values, rxdata.sort_values); +} + +} // namespace ledger diff --git a/src/data/jbuilder.h b/src/compare.h index 4e109a35..740ba275 100644 --- a/src/data/jbuilder.h +++ b/src/compare.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2007, John Wiegley. All rights reserved. + * 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 @@ -29,53 +29,70 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef _JBUILDER_H -#define _JBUILDER_H - -#include "builder.h" -#include "journal.h" - -namespace ledger { -namespace xml { +/** + * @addtogroup data + */ /** - * @class journal_builder_t - * - * @brief This custom builder creates an XML-mirrored Ledger journal. + * @file compare.h + * @author John Wiegley * - * Rather than simply creating a node_t hierarchy, as xml_builder_t - * does, this code creates the associated journal elements referred to - * by those nodes, and then refers to those elements via minimalist - * "shadow nodes". - * - * Thus, after building a <transaction> element, the element itself - * will have no children, but instead will point to a transaction_t - * object. If later an XPath expression desires to traverse the - * <transaction> element, all of the appropriate child nodes will be - * constructed on the fly, as if they'd been created in the first - * place by a regular xml_builder_t. + * @ingroup data */ -class journal_builder_t : public document_builder_t -{ -public: - journal_t * journal; +#ifndef _COMPARE_H +#define _COMPARE_H + +#include "expr.h" + +namespace ledger { + +class post_t; +class account_t; + +void push_sort_value(std::list<sort_value_t>& sort_values, + expr_t::ptr_op_t node, scope_t& scope); - journal_builder_t(document_t& _document, journal_t * _journal) - : document_builder_t(_document), journal(_journal) {} +template <typename T> +class compare_items +{ + expr_t sort_order; - virtual void set_start_position(std::istream& in) { - set_position(position_t(in.tellg(), 1)); + compare_items(); + +public: + compare_items(const compare_items& other) : sort_order(other.sort_order) { + TRACE_CTOR(compare_items, "copy"); + } + compare_items(const expr_t& _sort_order) : sort_order(_sort_order) { + TRACE_CTOR(compare_items, "const value_expr&"); + } + ~compare_items() throw() { + TRACE_DTOR(compare_items); } - virtual void set_position(const position_t& position) { - current_position = position; + void find_sort_values(std::list<sort_value_t>& sort_values, scope_t& scope) { + push_sort_value(sort_values, sort_order.get_op(), scope); } - virtual void begin_node(const node_t::nameid_t name_id, - bool terminal = false); + bool operator()(T * left, T * right); }; -} // namespace xml +sort_value_t calc_sort_value(const expr_t::ptr_op_t op); + +template <typename T> +bool compare_items<T>::operator()(T * left, T * right) +{ + assert(left); assert(right); + return sort_value_is_less_than(find_sort_values(left), + find_sort_values(right)); +} + +template <> +bool compare_items<post_t>::operator()(post_t * left, post_t * right); +template <> +bool compare_items<account_t>::operator()(account_t * left, + account_t * right); + } // namespace ledger -#endif // _JBUILDER_H +#endif // _COMPARE_H diff --git a/src/data/builder.h b/src/data/builder.h deleted file mode 100644 index a193879e..00000000 --- a/src/data/builder.h +++ /dev/null @@ -1,256 +0,0 @@ -/* - * Copyright (c) 2003-2007, 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. - */ - -#ifndef _BUILDER_H -#define _BUILDER_H - -#include "document.h" - -namespace ledger { -namespace xml { - -/** - * @class builder_t - * - * @brief Represents an interface for building a data hierarchy. - * - * This interface is much like .NET's XmlWriter facility. It - * abstracts the kind of hierarchy we're building, instead focusing - * only on the relationships. - */ -class builder_t -{ -public: - struct position_t - { - typedef uint_least32_t file_pos_t; - typedef uint_least32_t file_line_t; - - file_pos_t offset; - file_line_t linenum; - - position_t() : offset(0), linenum(0) {} - - explicit position_t(file_pos_t _offset, - file_line_t _linenum) - : offset(_offset), linenum(_linenum) {} - }; - -protected: - position_t current_position; - -public: - virtual ~builder_t() {} - - virtual void set_start_position(std::istream& in) {} - virtual void set_position(const position_t& position) {} - virtual position_t& position() { return current_position; } - - virtual void push_attr(const string& name, - const string& value) = 0; - virtual void push_attr(const node_t::nameid_t name_id, - const string& value) = 0; - - virtual void begin_node(const string& name, bool terminal = false) = 0; - virtual void begin_node(const node_t::nameid_t name_id, bool terminal = false) = 0; - - virtual void push_node(const string& name, - const optional<position_t>& end_pos = none) = 0; - virtual void push_node(const node_t::nameid_t name_id, - const optional<position_t>& end_pos = none) = 0; - - virtual node_t * current_node() = 0; - - virtual void append_text(const string& text) = 0; - - virtual node_t * end_node(const string& name, - const optional<position_t>& end_pos = none) = 0; - virtual node_t * end_node(const node_t::nameid_t name_id, - const optional<position_t>& end_pos = none) = 0; -}; - -/** - * @class xml_builder_t - * - * @brief Build a generic node_t hierarchy. - * - * This builder can be used to parse ordinary XML into a document - * object structure which can then be traversed in memory. - */ -class document_builder_t : public builder_t -{ -protected: - typedef std::list<std::pair<node_t::nameid_t, string> > attrs_list; - - document_t& document_; - attrs_list current_attrs; - node_t * current; - string current_text; - -public: - document_builder_t(document_t& _document) - : document_(_document), current(&document_) {} - - virtual void push_attr(const string& name, - const string& value) { - push_attr(document().register_name(name), value); - } - virtual void push_attr(const node_t::nameid_t name_id, - const string& value) { - current_attrs.push_back(attrs_list::value_type(name_id, value.c_str())); - } - - virtual void begin_node(const string& name, bool terminal = false) { - begin_node(document().register_name(name), terminal); - } - virtual void begin_node(const node_t::nameid_t name_id, - bool terminal = false) { - if (terminal) - current = current->as_parent_node().create_child<terminal_node_t>(name_id); - else - current = current->as_parent_node().create_child<parent_node_t>(name_id); - - foreach (const attrs_list::value_type& pair, current_attrs) - current->set_attr(pair.first, pair.second.c_str()); - current_attrs.clear(); - } - - virtual void push_node(const string& name, - const optional<position_t>& end_pos = none) { - begin_node(name, true); - end_node(name, end_pos); - } - virtual void push_node(const node_t::nameid_t name_id, - const optional<position_t>& end_pos = none) { - begin_node(name_id, true); - end_node(name_id, end_pos); - } - - virtual document_t& document() { - return document_; - } - virtual node_t * current_node() { - return current; - } - - virtual void append_text(const string& text) { - assert(! current->is_parent_node()); - polymorphic_downcast<terminal_node_t *>(current)->set_text(text); - } - - virtual node_t * end_node(const string& name, - const optional<position_t>& end_pos = none) { - return current = &*current->parent(); - } - virtual node_t * end_node(const node_t::nameid_t name_id, - const optional<position_t>& end_pos = none) { - return current = &*current->parent(); - } -}; - -/** - * @class xml_writer_t - * - * @brief Create textual XML on the given output stream. - * - * This builder, rather than manipulating data structures in memory, - * simply streams its contents on the fly to the given output stream. - * It uses only enough memory to remember the currently push - * attributes and text. - */ -class xml_writer_t : public builder_t -{ - typedef std::list<std::pair<string, string> > attrs_list; - - attrs_list current_attrs; - std::ostream& outs; - -public: - xml_writer_t(std::ostream& _outs) : outs(_outs) { - outs << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"; - begin_node("ledger"); - } - ~xml_writer_t() { - end_node("ledger"); - } - - virtual void push_attr(const string& name, - const string& value) { - current_attrs.push_back(attrs_list::value_type(name, value)); - } - virtual void push_attr(const node_t::nameid_t name_id, - const string& value) { - push_attr("hello", value); - } - - virtual void begin_node(const string& name, bool terminal = false) { - outs << '<' << name; - foreach (const attrs_list::value_type& attr, current_attrs) - outs << ' ' << attr.first << "=\"" << attr.second << "\""; - current_attrs.clear(); - outs << '>'; - } - virtual void begin_node(const node_t::nameid_t name_id, bool terminal = false) { - begin_node("hello"); - } - - virtual void push_node(const string& name, - const optional<position_t>& end_pos = none) { - begin_node(name, true); - end_node(name, end_pos); - } - virtual void push_node(const node_t::nameid_t name_id, - const optional<position_t>& end_pos = none) { - push_node("hello", end_pos); - } - - virtual node_t * current_node() { return NULL; } - - virtual void append_text(const string& text) { - outs << text; - } - - virtual node_t * end_node(const string& name, - const optional<position_t>& end_pos = none) { - outs << "</" << name << '>'; - return NULL; - } - virtual node_t * end_node(const node_t::nameid_t name_id, - const optional<position_t>& end_pos = none) { - end_node("hello", end_pos); - return NULL; - } -}; - -} // namespace xml -} // namespace ledger - -#endif // _BUILDER_H diff --git a/src/data/compile.cc b/src/data/compile.cc deleted file mode 100644 index 07ce9394..00000000 --- a/src/data/compile.cc +++ /dev/null @@ -1,235 +0,0 @@ -/* - * Copyright (c) 2003-2007, 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 "compile.h" -#include "parser.h" - -namespace ledger { -namespace xml { - -void compile_node(node_t& node, xpath_t::scope_t& scope) -{ - switch (node.name_id()) { - case JOURNAL_NODE: - downcast<journal_node_t>(node).compile(scope); - break; - case ENTRY_NODE: - downcast<entry_node_t>(node).compile(scope); - break; - case TRANSACTION_NODE: - downcast<transaction_node_t>(node).compile(scope); - break; - - default: - break; - } - - node.compiled = true; - - if (node.is_parent_node()) - foreach (node_t * child, node.as_parent_node()) - compile_node(*child, scope); -} - -void journal_node_t::compile(xpath_t::scope_t& scope) -{ - if (! journal.get()) - journal.reset(new journal_t); -} - -void entry_node_t::compile(xpath_t::scope_t& scope) -{ - parent_node_t& parent_node(*parent()); - - assert(parent_node.name_id() == JOURNAL_NODE); - assert(parent_node.is_compiled()); - - journal_t * journal = downcast<journal_node_t>(parent_node).journal.get(); - - if (! entry.get()) { - entry.reset(new entry_t); -#if 0 - journal->add_entry(entry.get()); -#endif - } - entry->journal = journal; - - foreach (attr_pair& attr, *attributes) { - if (attr.first == DATE_ATTR && attr.second.is_string()) - entry->_date = parse_datetime(attr.second.as_string().c_str()); - else if (attr.first == EFF_DATE_ATTR && attr.second.is_string()) - entry->_date_eff = parse_datetime(attr.second.as_string().c_str()); - else if (attr.first == CODE_ATTR) - entry->code = attr.second.as_string(); - } -} - -void transaction_node_t::parse_amount_expr(xpath_t::scope_t& scope, - const char * amount_expr) -{ - value_t * amount; - - std::istringstream in(amount_expr); - - PUSH_CONTEXT(); - - // jww (2006-09-15): Make sure it doesn't gobble up the upcoming @ symbol - - unsigned long beg = (long)in.tellg(); - - amount_t temp; - temp.parse(in, AMOUNT_PARSE_NO_REDUCE); - - char c; - if (! in.eof() && (c = peek_next_nonws(in)) != '@' && - c != ';' && ! in.eof()) { - in.seekg(beg, std::ios::beg); - - xpath_t xpath(in, (XPATH_PARSE_NO_REDUCE | XPATH_PARSE_RELAXED | - XPATH_PARSE_PARTIAL)); - - xpath_t::context_scope_t node_scope(scope, this); - amount = &set_attr(AMOUNT_ATTR, xpath.calc(node_scope)); - - //unsigned long end = (long)in.tellg(); - } else { - amount = &set_attr(AMOUNT_ATTR, temp); - } - - // jww (2007-04-30): This should be a string context, or perhaps a - // file context - POP_CONTEXT(context("While parsing transaction amount")); - - // Parse the optional cost (@ PER-UNIT-COST, @@ TOTAL-COST) - - if (in.good() && ! in.eof()) { - char c = peek_next_nonws(in); - if (c == '@') { - DEBUG("ledger.textual.parse", "Found a price indicator"); - bool per_unit = true; - in.get(c); - if (in.peek() == '@') { - in.get(c); - per_unit = false; - DEBUG("ledger.textual.parse", "And it's for a total price"); - } - - if (in.good() && ! in.eof()) { - amount_t temp; - - PUSH_CONTEXT(); - - //unsigned long beg = (long)in.tellg(); - - temp.parse(in); - - if (temp.sign() < 0) - throw_(parse_error, "A transaction's cost may not be negative"); - - //unsigned long end = (long)in.tellg(); - - POP_CONTEXT(context("While parsing transaction cost")); - - amount_t per_unit_cost(temp); - amount_t& base_amount(amount->as_amount_lval()); - if (per_unit) - temp *= base_amount.number(); - else - per_unit_cost /= base_amount.number(); - - value_t& cost = set_attr(COST_ATTR, temp); - - if (base_amount.commodity() && ! base_amount.commodity().annotated) { - assert(transaction); - assert(transaction->entry); - base_amount.annotate_commodity - (annotation_t(per_unit_cost, transaction->entry->actual_date(), - transaction->entry->code)); - } - - DEBUG("ledger.textual.parse", "Total cost is " << cost); - DEBUG("ledger.textual.parse", "Per-unit cost is " << per_unit_cost); - DEBUG("ledger.textual.parse", "Annotated amount is " << base_amount); - DEBUG("ledger.textual.parse", "Bare amount is " << base_amount.number()); - } - } - } - - amount->in_place_reduce(); - - DEBUG("ledger.textual.parse", "Reduced amount is " << *amount); -} - -void transaction_node_t::compile(xpath_t::scope_t& scope) -{ - parent_node_t& parent_node(*parent()); - - assert(parent_node.name_id() == ENTRY_NODE); - assert(parent_node.is_compiled()); - - entry_t * entry = downcast<entry_node_t>(parent_node).entry.get(); - - if (! transaction.get()) { - transaction.reset(new transaction_t); -#if 0 - entry->add_transaction(transaction.get()); -#endif - } - transaction->entry = entry; - - foreach (node_t * child, *this) { - switch (child->name_id()) { - case AMOUNT_EXPR_NODE: - parse_amount_expr(scope, child->as_terminal_node().text()); - break; - - case ACCOUNT_PATH_NODE: { - assert(entry); - - journal_t * journal = entry->journal; - assert(journal); - - transaction->account = - journal->find_account(child->as_terminal_node().text()); - - // jww (2007-05-18): Need to set an attribute that refers to the - // unique id of the account - break; - } - - default: - break; - } - } -} - -} // namespace xml -} // namespace ledger diff --git a/src/data/compile.h b/src/data/compile.h deleted file mode 100644 index d8b9f536..00000000 --- a/src/data/compile.h +++ /dev/null @@ -1,206 +0,0 @@ -/* - * Copyright (c) 2003-2007, 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. - */ - -#ifndef _COMPILE_H -#define _COMPILE_H - -#include "node.h" -#include "journal.h" - -namespace ledger { -namespace xml { - -void compile_node(node_t& node, xml::xpath_t::scope_t& scope); - -#if 0 -class commodity_node_t : public parent_node_t -{ -public: - commodity_t * commodity; - - commodity_node_t(document_t * _document, - commodity_t * _commodity, - parent_node_t * _parent = NULL) - : parent_node_t(_document, _parent), commodity(_commodity) { - TRACE_CTOR(commodity_node_t, "document_t *, commodity_t *, parent_node_t *"); - set_name(document_t::COMMODITY); - } - virtual ~commodity_node_t() { - TRACE_DTOR(commodity_node_t); - } - - virtual node_t * children() const; -}; - -class amount_node_t : public parent_node_t -{ -public: - amount_t * amount; - - amount_node_t(document_t * _document, - amount_t * _amount, - parent_node_t * _parent = NULL) - : parent_node_t(_document, _parent), amount(_amount) { - TRACE_CTOR(amount_node_t, "document_t *, amount_t *, parent_node_t *"); - set_name(document_t::AMOUNT); - } - virtual ~amount_node_t() { - TRACE_DTOR(amount_node_t); - } - - virtual node_t * children() const; - - virtual value_t to_value() const { - return *amount; - } -}; -#endif - -class journal_node_t : public parent_node_t -{ -public: - shared_ptr<journal_t> journal; - - journal_node_t(nameid_t _name_id, - document_t& _document, - const optional<parent_node_t&>& _parent = none, - journal_t * _journal = NULL) - : parent_node_t(_name_id, _document, _parent), journal(_journal) { - TRACE_CTOR(journal_node_t, "document_t *, journal_t *, parent_node_t *"); - } - virtual ~journal_node_t() { - TRACE_DTOR(journal_node_t); - } - - void compile(xpath_t::scope_t& scope); -}; - -class entry_node_t : public parent_node_t -{ -public: - shared_ptr<entry_t> entry; - - entry_node_t(nameid_t _name_id, - document_t& _document, - const optional<parent_node_t&>& _parent = none, - entry_t * _entry = NULL) - : parent_node_t(_name_id, _document, _parent), entry(_entry) { - TRACE_CTOR(entry_node_t, "document_t&, parent_node_t, entry_t *"); - assert(_name_id == ENTRY_NODE); - } - virtual ~entry_node_t() { - TRACE_DTOR(entry_node_t); - } - - void compile(xpath_t::scope_t& scope); -}; - -class transaction_node_t : public parent_node_t -{ -public: - shared_ptr<transaction_t> transaction; - - transaction_node_t(nameid_t _name_id, - document_t& _document, - const optional<parent_node_t&>& _parent = none, - transaction_t * _transaction = NULL) - : parent_node_t(_name_id, _document, _parent), - transaction(_transaction) { - TRACE_CTOR(transaction_node_t, - "document_t&, parent_node_t, transaction_t *"); - assert(_name_id == TRANSACTION_NODE); - } - virtual ~transaction_node_t() { - TRACE_DTOR(transaction_node_t); - } - - void compile(xpath_t::scope_t& scope); - -private: - void parse_amount_expr(xpath_t::scope_t& scope, - const char * amount_expr); -}; - -#if 0 -class account_node_t : public parent_node_t -{ - account_t * account; - -public: - account_node_t(document_t * _document, account_t * _account, - parent_node_t * _parent = NULL) - : parent_node_t(_document, _parent), account(_account) { - TRACE_CTOR(account_node_t, "document_t *, account_t *, parent_node_t *"); - set_name(document_t::ACCOUNT); - } - virtual ~account_node_t() { - TRACE_DTOR(account_node_t); - } - - virtual node_t * children() const; -}; - -template <typename T> -inline typename T::node_type * -wrap_node(document_t * doc, T * item, void * parent_node = NULL) { - assert(false); - return NULL; -} - -template <> -inline transaction_t::node_type * -wrap_node(document_t * doc, transaction_t * xact, void * parent_node) { - return new transaction_node_t(doc, xact, (parent_node_t *)parent_node); -} - -template <> -inline entry_t::node_type * -wrap_node(document_t * doc, entry_t * entry, void * parent_node) { - return new entry_node_t(doc, entry, (parent_node_t *)parent_node); -} - -template <> -inline account_t::node_type * -wrap_node(document_t * doc, account_t * account, void * parent_node) { - return new account_node_t(doc, account, (parent_node_t *)parent_node); -} - -template <> -inline journal_t::node_type * -wrap_node(document_t * doc, journal_t * journal, void * parent_node) { - return new journal_node_t(doc, journal, (parent_node_t *)parent_node); -} -#endif - -} // namespace xml -} // namespace ledger - -#endif // _COMPILE_H diff --git a/src/data/document.cc b/src/data/document.cc deleted file mode 100644 index 41bf77a3..00000000 --- a/src/data/document.cc +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright (c) 2003-2007, 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 "document.h" - -namespace ledger { -namespace xml { - -namespace { - const std::size_t ledger_builtins_size = 39; - const char * ledger_builtins[] = { - "account", - "account-path", - "amount", - "amount", - "amount-expr", - "arg", - "auto-entry", - "balance", - "checkin", - "cleared", - "code", - "commodity", - "commodity-conversion", - "commodity-nomarket", - "commodity-template", - "cost", - "current-year", - "date", - "default-account", - "directive", - "effective", - "entries", - "entry", - "from", - "journal", - "ledger", - "name", - "note", - "payee", - "pending", - "period", - "period-entry", - "price", - "price-history", - "rule", - "symbol", - "template", - "time", - "to", - "transaction", - "virtual", - "year" - }; -} - -node_t::nameid_t document_t::register_name(const string& name) -{ - optional<nameid_t> index = lookup_name_id(name); - if (index) - return *index; - - if (! names) - names = names_t(); - - nameid_t real_index = names->size() + 1000; - names->push_back(name_pair(name, real_index)); - DEBUG("xml.lookup", this << " Inserted name: " << name); - - return real_index; -} - -optional<node_t::nameid_t> document_t::lookup_name_id(const string& name) const -{ - if (optional<node_t::nameid_t> id = lookup_builtin_id(name)) - return id; - - if (! names) - return none; - - DEBUG("xml.lookup", this << " Finding name: " << name); - - typedef names_t::nth_index<1>::type names_by_name; - - const names_by_name& name_index = names->get<1>(); - names_by_name::const_iterator i = name_index.find(name); - if (i != name_index.end()) - return (*i).second; - - return none; -} - -optional<node_t::nameid_t> document_t::lookup_builtin_id(const string& name) -{ - int first = 0; - int last = static_cast<int>(ledger_builtins_size); - - while (first <= last) { - int mid = (first + last) / 2; // compute mid point. - - int result; - if ((result = (static_cast<int>(name[0]) - - static_cast<int>(ledger_builtins[mid][0]))) == 0) - result = std::strcmp(name.c_str(), ledger_builtins[mid]); - - if (result > 0) - first = mid + 1; // repeat search in top half. - else if (result < 0) - last = mid - 1; // repeat search in bottom half. - else - return nameid_t(mid + 10); - } - - return none; -} - -optional<const char *> document_t::lookup_name(nameid_t id) const -{ - if (id < 1000) { - switch (id) { - case CURRENT: - return "."; - case PARENT: - return ".."; - case ROOT: - return ""; - case ALL: - return "*"; - - default: - assert(id >= 10); - return ledger_builtins[id - 10]; - } - } - else if (names) { - assert(id >= 1000); - std::size_t index = id - 1000; - typedef names_t::nth_index<0>::type names_by_random_access; - const names_by_random_access& random_access = names->get<0>(); - if (index < random_access.size()) - return random_access[index].first.c_str(); - } - return none; -} - -void document_t::print(std::ostream& out) const -{ - out << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"; - parent_node_t::print(out); -} - -} // namespace xml -} // namespace ledger diff --git a/src/data/document.h b/src/data/document.h deleted file mode 100644 index 2eac4043..00000000 --- a/src/data/document.h +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright (c) 2003-2007, 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. - */ - -#ifndef _DOCUMENT_H -#define _DOCUMENT_H - -#include "node.h" -#include "value.h" - -namespace ledger { -namespace xml { - -enum ledger_builtins_t { - ACCOUNT_ATTR = 10, - ACCOUNT_PATH_NODE, - AMOUNT_ATTR, - AMOUNT_NODE, - AMOUNT_EXPR_NODE, - ARG_ATTR, - AUTO_ENTRY_NODE, - BALANCE_ATTR, - CHECKIN_NODE, - CLEARED_ATTR, - CODE_ATTR, - COMMODITY_NODE, - COMMODITY_CONVERSION_NODE, - COMMODITY_NOMARKET_NODE, - COMMODITY_TEMPLATE_NODE, - COST_ATTR, - CURRENT_YEAR_NODE, - DATE_ATTR, - DEFAULT_ACCOUNT_NODE, - DIRECTIVE_NODE, - EFF_DATE_ATTR, - ENTRIES_NODE, - ENTRY_NODE, - FROM_ATTR, - JOURNAL_NODE, - LEDGER_NODE, - NAME_ATTR, - NOTE_NODE, - PAYEE_NODE, - PENDING_ATTR, - PERIOD_NODE, - PERIOD_ENTRY_NODE, - PRICE_ATTR, - PRICE_HISTORY_NODE, - RULE_NODE, - SYMBOL_ATTR, - TEMPLATE_ATTR, - TIME_ATTR, - TO_ATTR, - TRANSACTION_NODE, - VIRTUAL_ATTR, - YEAR_ATTR -}; - -class document_t : public parent_node_t -{ - typedef std::pair<string, nameid_t> name_pair; - - typedef multi_index_container< - name_pair, - multi_index::indexed_by< - multi_index::random_access<>, - multi_index::hashed_unique< - multi_index::member<name_pair, string, &name_pair::first> > - > - > names_t; - - optional<names_t> names; - -public: - // Ids 0-9 are reserved. 10-999 are for "builtin" names. 1000+ are - // for dynamically registered names. - enum special_names_t { - CURRENT, PARENT, ROOT, ALL, LAST_BUILTIN = 10 - }; - - document_t(node_t::nameid_t _name_id) - : parent_node_t(_name_id, *this) { - TRACE_CTOR(xml::document_t, "node_t::nameid_t"); - } - ~document_t() { - TRACE_DTOR(xml::document_t); - } - - nameid_t register_name(const string& name); - - optional<nameid_t> lookup_name_id(const string& name) const; - static optional<nameid_t> lookup_builtin_id(const string& name); - optional<const char *> lookup_name(nameid_t id) const; - - void print(std::ostream& out) const; - -#if 0 -#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE) - class parser_t - { - public: - document_t * document; - XML_Parser parser; - string have_error; - const char * pending; - node_t::attrs_map * pending_attrs; - bool handled_data; - - std::list<parent_node_t *> node_stack; - - parser_t() : document(NULL), pending(NULL), pending_attrs(NULL), - handled_data(false) {} - virtual ~parser_t() {} - - virtual bool test(std::istream& in) const; - virtual document_t * parse(std::istream& in); - }; -#endif -#endif -}; - -} // namespace xml -} // namespace ledger - -#endif // _DOCUMENT_H diff --git a/src/data/journal.cc b/src/data/journal.cc deleted file mode 100644 index bb33f4dc..00000000 --- a/src/data/journal.cc +++ /dev/null @@ -1,702 +0,0 @@ -/* - * Copyright (c) 2003-2007, 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 "journal.h" -#include "xpath.h" -#include "mask.h" - -namespace ledger { - -const string version = PACKAGE_VERSION; - -bool transaction_t::use_effective_date = false; - -transaction_t::~transaction_t() -{ - TRACE_DTOR(transaction_t); -} - -moment_t transaction_t::actual_date() const -{ - if (! _date && entry) - return entry->actual_date(); - return *_date; -} - -moment_t transaction_t::effective_date() const -{ - if (! _date_eff && entry) - return entry->effective_date(); - return *_date_eff; -} - -bool transaction_t::valid() const -{ - if (! entry) { - DEBUG("ledger.validate", "transaction_t: ! entry"); - return false; - } - - if (state != UNCLEARED && state != CLEARED && state != PENDING) { - DEBUG("ledger.validate", "transaction_t: state is bad"); - return false; - } - - transactions_list::const_iterator i = - std::find(entry->transactions.begin(), - entry->transactions.end(), this); - if (i == entry->transactions.end()) { - DEBUG("ledger.validate", "transaction_t: ! found"); - return false; - } - - if (! account) { - DEBUG("ledger.validate", "transaction_t: ! account"); - return false; - } - - if (! amount.valid()) { - DEBUG("ledger.validate", "transaction_t: ! amount.valid()"); - return false; - } - - if (cost && ! cost->valid()) { - DEBUG("ledger.validate", "transaction_t: cost && ! cost->valid()"); - return false; - } - - return true; -} - -void entry_base_t::add_transaction(transaction_t * xact) -{ - transactions.push_back(xact); -} - -bool entry_base_t::remove_transaction(transaction_t * xact) -{ - transactions.remove(xact); - return true; -} - -bool entry_base_t::finalize() -{ - // Scan through and compute the total balance for the entry. This - // is used for auto-calculating the value of entries with no cost, - // and the per-unit price of unpriced commodities. - - value_t balance; - bool no_amounts = true; - bool saw_null = false; - - for (transactions_list::const_iterator x = transactions.begin(); - x != transactions.end(); - x++) - if (! (*x)->has_flags(TRANSACTION_VIRTUAL) || - (*x)->has_flags(TRANSACTION_BALANCE)) { - amount_t& p((*x)->cost ? *(*x)->cost : (*x)->amount); - if (p) { - if (no_amounts) { - balance = p; - no_amounts = false; - } else { - balance += p; - } - - assert((*x)->amount); - if ((*x)->cost && (*x)->amount.commodity().annotated) { - annotated_commodity_t& - ann_comm(static_cast<annotated_commodity_t&> - ((*x)->amount.commodity())); - if (ann_comm.details.price) - balance += (*ann_comm.details.price * (*x)->amount.number() - - *((*x)->cost)); - } - } else { - saw_null = true; - } - } - - // If it's a null entry, then let the user have their fun - if (no_amounts) - return true; - - // If there is only one transaction, balance against the basket - // account if one has been set. - - if (journal && journal->basket && transactions.size() == 1) { - assert(balance.is_amount()); - transaction_t * nxact = new transaction_t(journal->basket); - // The amount doesn't need to be set because the code below will - // balance this transaction against the other. - add_transaction(nxact); - nxact->add_flags(TRANSACTION_CALCULATED); - } - - // If the first transaction of a two-transaction entry is of a - // different commodity than the other, and it has no per-unit price, - // determine its price by dividing the unit count into the value of - // the balance. This is done for the last eligible commodity. - - if (! saw_null && balance && balance.is_balance() && - balance.as_balance().amounts.size() == 2) { - transactions_list::const_iterator x = transactions.begin(); - assert((*x)->amount); - commodity_t& this_comm = (*x)->amount.commodity(); - - balance_t::amounts_map::const_iterator this_bal = - balance.as_balance().amounts.find(&this_comm); - balance_t::amounts_map::const_iterator other_bal = - balance.as_balance().amounts.begin(); - if (this_bal == other_bal) - other_bal++; - - amount_t per_unit_cost = - amount_t((*other_bal).second / (*this_bal).second.number()).unround(); - - for (; x != transactions.end(); x++) { - if ((*x)->cost || (*x)->has_flags(TRANSACTION_VIRTUAL) || - (*x)->amount.commodity() != this_comm) - continue; - - balance -= (*x)->amount; - - entry_t * entry = dynamic_cast<entry_t *>(this); - - if ((*x)->amount.commodity() && - ! (*x)->amount.commodity().annotated) - (*x)->amount.annotate_commodity - (annotation_t(per_unit_cost.abs(), - entry ? entry->actual_date() : optional<moment_t>(), - entry ? entry->code : optional<string>())); - - (*x)->cost = - (per_unit_cost * (*x)->amount.number()); - balance += *(*x)->cost; - } - } - - // Walk through each of the transactions, fixing up any that we - // can, and performing any on-the-fly calculations. - - bool empty_allowed = true; - - for (transactions_list::const_iterator x = transactions.begin(); - x != transactions.end(); - x++) { - if ((*x)->amount || - ((*x)->has_flags(TRANSACTION_VIRTUAL) && - ! (*x)->has_flags(TRANSACTION_BALANCE))) - continue; - - if (! empty_allowed) - throw_(std::logic_error, - "Only one transaction with null amount allowed per entry"); - empty_allowed = false; - - // If one transaction gives no value at all, its value will become - // the inverse of the value of the others. If multiple - // commodities are involved, multiple transactions will be - // generated to balance them all. - - const balance_t * bal = NULL; - switch (balance.type()) { - case value_t::BALANCE_PAIR: - bal = &balance.as_balance_pair().quantity(); - // fall through... - - case value_t::BALANCE: - if (! bal) - bal = &balance.as_balance(); - - if (bal->amounts.size() < 2) { - balance.cast(value_t::AMOUNT); - } else { - bool first = true; - for (balance_t::amounts_map::const_iterator - i = bal->amounts.begin(); - i != bal->amounts.end(); - i++) { - amount_t amt = (*i).second.negate(); - - if (first) { - (*x)->amount = amt; - first = false; - } else { - transaction_t * nxact = new transaction_t((*x)->account); - add_transaction(nxact); - nxact->add_flags(TRANSACTION_CALCULATED); - nxact->amount = amt; - } - - balance += amt; - } - break; - } - // fall through... - - case value_t::AMOUNT: - (*x)->amount = balance.as_amount().negate(); - (*x)->add_flags(TRANSACTION_CALCULATED); - - balance += (*x)->amount; - break; - - default: - break; - } - } - - if (balance) { -#if 1 - throw_(balance_error, "Entry does not balance"); -#else - error * err = - new balance_error("Entry does not balance", - new entry_context(*this, "While balancing entry:")); - err->context.push_front - (new value_context(balance, "Unbalanced remainder is:")); - throw err; -#endif - } - - return true; -} - -entry_t::entry_t(const entry_t& e) - : entry_base_t(e), _date(e._date), _date_eff(e._date_eff), - code(e.code), payee(e.payee) -{ - TRACE_CTOR(entry_t, "copy"); - for (transactions_list::const_iterator i = transactions.begin(); - i != transactions.end(); - i++) - (*i)->entry = this; -} - -bool entry_t::get_state(transaction_t::state_t * state) const -{ - bool first = true; - bool hetero = false; - - for (transactions_list::const_iterator i = transactions.begin(); - i != transactions.end(); - i++) { - if (first) { - *state = (*i)->state; - first = false; - } - else if (*state != (*i)->state) { - hetero = true; - break; - } - } - - return ! hetero; -} - -void entry_t::add_transaction(transaction_t * xact) -{ - xact->entry = this; - entry_base_t::add_transaction(xact); -} - -bool entry_t::valid() const -{ - if (! is_valid_moment(_date) || ! journal) { - DEBUG("ledger.validate", "entry_t: ! _date || ! journal"); - return false; - } - - for (transactions_list::const_iterator i = transactions.begin(); - i != transactions.end(); - i++) - if ((*i)->entry != this || ! (*i)->valid()) { - DEBUG("ledger.validate", "entry_t: transaction not valid"); - return false; - } - - return true; -} - -auto_entry_t::auto_entry_t() -{ - TRACE_CTOR(auto_entry_t, ""); -} - -auto_entry_t::auto_entry_t(const string& _predicate) - : predicate(new xml::xpath_t(_predicate)) -{ - TRACE_CTOR(auto_entry_t, "const string&"); -} - -auto_entry_t::~auto_entry_t() { - TRACE_DTOR(auto_entry_t); -} - -void auto_entry_t::extend_entry(entry_base_t& entry, bool post) -{ -#if 0 - transactions_list initial_xacts(entry.transactions.begin(), - entry.transactions.end()); - - for (transactions_list::iterator i = initial_xacts.begin(); - i != initial_xacts.end(); - i++) { - // jww (2006-09-10): Create a scope here based on entry - if (predicate->calc((xml::node_t *) NULL)) { - for (transactions_list::iterator t = transactions.begin(); - t != transactions.end(); - t++) { - amount_t amt; - assert((*t)->amount); - if (! (*t)->amount.commodity()) { - if (! post) - continue; - assert((*i)->amount); - amt = *(*i)->amount * *(*t)->amount; - } else { - if (post) - continue; - amt = *(*t)->amount; - } - - account_t * account = (*t)->account; - string fullname = account->fullname(); - assert(! fullname.empty()); - if (fullname == "$account" || fullname == "@account") - account = (*i)->account; - - transaction_t * xact - = new transaction_t(account, amt, (*t)->flags() | TRANSACTION_AUTO); - entry.add_transaction(xact); - } - } - } -#endif -} - -account_t::~account_t() -{ - TRACE_DTOR(account_t); - - for (accounts_map::iterator i = accounts.begin(); - i != accounts.end(); - i++) - checked_delete((*i).second); -} - -account_t * account_t::find_account(const string& name, - const bool auto_create) -{ - accounts_map::const_iterator i = accounts.find(name); - if (i != accounts.end()) - return (*i).second; - - char buf[256]; - - string::size_type sep = name.find(':'); - assert(sep < 256|| sep == string::npos); - - const char * first, * rest; - if (sep == string::npos) { - first = name.c_str(); - rest = NULL; - } else { - std::strncpy(buf, name.c_str(), sep); - buf[sep] = '\0'; - - first = buf; - rest = name.c_str() + sep + 1; - } - - account_t * account; - - i = accounts.find(first); - if (i == accounts.end()) { - if (! auto_create) - return NULL; - - account = new account_t(this, first); - account->journal = journal; - - std::pair<accounts_map::iterator, bool> result - = accounts.insert(accounts_map::value_type(first, account)); - assert(result.second); - } else { - account = (*i).second; - } - - if (rest) - account = account->find_account(rest, auto_create); - - return account; -} - -static inline -account_t * find_account_re_(account_t * account, const mask_t& regexp) -{ - if (regexp.match(account->fullname())) - return account; - - for (accounts_map::iterator i = account->accounts.begin(); - i != account->accounts.end(); - i++) - if (account_t * a = find_account_re_((*i).second, regexp)) - return a; - - return NULL; -} - -account_t * journal_t::find_account_re(const string& regexp) -{ - return find_account_re_(master, mask_t(regexp)); -} - -string account_t::fullname() const -{ - if (! _fullname.empty()) { - return _fullname; - } else { - const account_t * first = this; - string fullname = name; - - while (first->parent) { - first = first->parent; - if (! first->name.empty()) - fullname = first->name + ":" + fullname; - } - - _fullname = fullname; - - return fullname; - } -} - -std::ostream& operator<<(std::ostream& out, const account_t& account) -{ - out << account.fullname(); - return out; -} - -bool account_t::valid() const -{ - if (depth > 256 || ! journal) { - DEBUG("ledger.validate", "account_t: depth > 256 || ! journal"); - return false; - } - - for (accounts_map::const_iterator i = accounts.begin(); - i != accounts.end(); - i++) { - if (this == (*i).second) { - DEBUG("ledger.validate", "account_t: parent refers to itself!"); - return false; - } - - if (! (*i).second->valid()) { - DEBUG("ledger.validate", "account_t: child not valid"); - return false; - } - } - - return true; -} - -journal_t::~journal_t() -{ - TRACE_DTOR(journal_t); - - assert(master); - checked_delete(master); - - // Don't bother unhooking each entry's transactions from the - // accounts they refer to, because all accounts are about to - // be deleted. - for (entries_list::iterator i = entries.begin(); - i != entries.end(); - i++) { - if (! item_pool || - reinterpret_cast<char *>(*i) < item_pool || - reinterpret_cast<char *>(*i) >= item_pool_end) { - checked_delete(*i); - } else { - (*i)->~entry_t(); - } - } - - for (auto_entries_list::iterator i = auto_entries.begin(); - i != auto_entries.end(); - i++) - if (! item_pool || - reinterpret_cast<char *>(*i) < item_pool || - reinterpret_cast<char *>(*i) >= item_pool_end) - checked_delete(*i); - else - (*i)->~auto_entry_t(); - - for (period_entries_list::iterator i = period_entries.begin(); - i != period_entries.end(); - i++) - if (! item_pool || - reinterpret_cast<char *>(*i) < item_pool || - reinterpret_cast<char *>(*i) >= item_pool_end) - checked_delete(*i); - else - (*i)->~period_entry_t(); - - if (item_pool) - checked_array_delete(item_pool); -} - -bool journal_t::add_entry(entry_t * entry) -{ - entry->journal = this; - - if (! run_hooks(entry_finalize_hooks, *entry, false) || - ! entry->finalize() || - ! run_hooks(entry_finalize_hooks, *entry, true)) { - entry->journal = NULL; - return false; - } - - entries.push_back(entry); - - for (transactions_list::const_iterator i = entry->transactions.begin(); - i != entry->transactions.end(); - i++) - if ((*i)->cost) { - assert((*i)->amount); - (*i)->amount.commodity().add_price(entry->date(), - *(*i)->cost / (*i)->amount.number()); - } - - return true; -} - -bool journal_t::remove_entry(entry_t * entry) -{ - bool found = false; - entries_list::iterator i; - for (i = entries.begin(); i != entries.end(); i++) - if (*i == entry) { - found = true; - break; - } - if (! found) - return false; - - entries.erase(i); - entry->journal = NULL; - - return true; -} - -bool journal_t::valid() const -{ - if (! master->valid()) { - DEBUG("ledger.validate", "journal_t: master not valid"); - return false; - } - - for (entries_list::const_iterator i = entries.begin(); - i != entries.end(); - i++) - if (! (*i)->valid()) { - DEBUG("ledger.validate", "journal_t: entry not valid"); - return false; - } - - return true; -} - -void print_entry(std::ostream& out, const entry_base_t& entry_base, - const string& prefix) -{ - string print_format; - - if (dynamic_cast<const entry_t *>(&entry_base)) { - print_format = (prefix + "%D %X%C%P\n" + - prefix + " %-34A %12o\n%/" + - prefix + " %-34A %12o\n"); - } - else if (const auto_entry_t * entry = - dynamic_cast<const auto_entry_t *>(&entry_base)) { - out << "= " << entry->predicate->expr << '\n'; - print_format = prefix + " %-34A %12o\n"; - } - else if (const period_entry_t * entry = - dynamic_cast<const period_entry_t *>(&entry_base)) { - out << "~ " << entry->period_string << '\n'; - print_format = prefix + " %-34A %12o\n"; - } - else { - assert(false); - } - -#if 0 - format_entries formatter(out, print_format); - walk_transactions(const_cast<transactions_list&>(entry_base.transactions), - formatter); - formatter.flush(); - - clear_transaction_xdata cleaner; - walk_transactions(const_cast<transactions_list&>(entry_base.transactions), - cleaner); -#endif -} - -#if 0 -void entry_context::describe(std::ostream& out) const throw() -{ - if (! desc.empty()) - out << desc << std::endl; - - print_entry(out, entry, " "); -} - -xact_context::xact_context(const ledger::transaction_t& _xact, - const string& desc) throw() - : file_context("", 0, desc), xact(_xact) -{ - const ledger::strings_list& sources(xact.entry->journal->sources); - unsigned int x = 0; - for (ledger::strings_list::const_iterator i = sources.begin(); - i != sources.end(); - i++, x++) - if (x == xact.entry->src_idx) { - file = *i; - break; - } - line = xact.beg_line; -} -#endif - -} // namespace ledger diff --git a/src/data/journal.h b/src/data/journal.h deleted file mode 100644 index 821f6910..00000000 --- a/src/data/journal.h +++ /dev/null @@ -1,439 +0,0 @@ -/* - * Copyright (c) 2003-2007, 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. - */ - -#ifndef _JOURNAL_H -#define _JOURNAL_H - -#include "amount.h" -#include "xpath.h" - -namespace ledger { - -// These flags persist with the object -#define TRANSACTION_NORMAL 0x0000 -#define TRANSACTION_VIRTUAL 0x0001 -#define TRANSACTION_BALANCE 0x0002 -#define TRANSACTION_AUTO 0x0004 -#define TRANSACTION_BULK_ALLOC 0x0008 -#define TRANSACTION_CALCULATED 0x0010 - -class entry_t; -class account_t; - -class transaction_t : public supports_flags<> -{ - public: - enum state_t { UNCLEARED, CLEARED, PENDING }; - - entry_t * entry; - state_t state; - account_t * account; - optional<moment_t> _date; - optional<moment_t> _date_eff; - amount_t amount; - optional<amount_t> cost; - optional<string> note; - - static bool use_effective_date; - - explicit transaction_t(account_t * _account = NULL) - : supports_flags<>(TRANSACTION_NORMAL), entry(NULL), - state(UNCLEARED), account(_account) { - TRACE_CTOR(transaction_t, "account_t *"); - } - explicit transaction_t(account_t * _account, - const amount_t& _amount, - unsigned int _flags = TRANSACTION_NORMAL, - const optional<string> _note = none) - : supports_flags<>(_flags), entry(NULL), state(UNCLEARED), - account(_account), amount(_amount), note(_note) { - TRACE_CTOR(transaction_t, - "account_t *, const amount_t&, unsigned int, const string&"); - } - explicit transaction_t(const transaction_t& xact) - : supports_flags<>(xact), - entry(xact.entry), - state(xact.state), - account(xact.account), - _date(xact._date), - _date_eff(xact._date_eff), - amount(xact.amount), - cost(xact.cost), - note(xact.note) { - TRACE_CTOR(transaction_t, "copy"); - } - ~transaction_t(); - - moment_t actual_date() const; - moment_t effective_date() const; - moment_t date() const { - if (use_effective_date) - return effective_date(); - else - return actual_date(); - } - - bool valid() const; -}; - -#if 0 -class xact_context : public file_context { - public: - const transaction_t& xact; - - xact_context(const transaction_t& _xact, - const string& desc = "") throw(); - virtual ~xact_context() throw() {} -}; -#endif - -class journal_t; - -typedef std::list<transaction_t *> transactions_list; - -class entry_base_t -{ - public: - journal_t * journal; - transactions_list transactions; - - entry_base_t() : journal(NULL) { - TRACE_CTOR(entry_base_t, ""); - } - entry_base_t(const entry_base_t& e) : journal(NULL) - { - TRACE_CTOR(entry_base_t, "copy"); - for (transactions_list::const_iterator i = e.transactions.begin(); - i != e.transactions.end(); - i++) - transactions.push_back(new transaction_t(**i)); - } - virtual ~entry_base_t() { - TRACE_DTOR(entry_base_t); - for (transactions_list::iterator i = transactions.begin(); - i != transactions.end(); - i++) - if (! (*i)->has_flags(TRANSACTION_BULK_ALLOC)) - checked_delete(*i); - else - (*i)->~transaction_t(); - } - - bool operator==(const entry_base_t& entry) { - return this == &entry; - } - bool operator!=(const entry_base_t& entry) { - return ! (*this == entry); - } - - virtual void add_transaction(transaction_t * xact); - virtual bool remove_transaction(transaction_t * xact); - - virtual bool finalize(); - virtual bool valid() const = 0; -}; - -class entry_t : public entry_base_t -{ -public: - moment_t _date; - optional<moment_t> _date_eff; - optional<string> code; - string payee; - - entry_t() { - TRACE_CTOR(entry_t, ""); - } - entry_t(const entry_t& e); - - virtual ~entry_t() { - TRACE_DTOR(entry_t); - } - - moment_t actual_date() const { - return _date; - } - moment_t effective_date() const { - return _date_eff ? *_date_eff : _date; - } - moment_t date() const { - if (transaction_t::use_effective_date) - return effective_date(); - else - return actual_date(); - } - - virtual void add_transaction(transaction_t * xact); - virtual bool valid() const; - - bool get_state(transaction_t::state_t * state) const; -}; - -struct entry_finalizer_t { - virtual ~entry_finalizer_t() {} - virtual bool operator()(entry_t& entry, bool post) = 0; -}; - -void print_entry(std::ostream& out, const entry_base_t& entry, - const string& prefix = ""); - -#if 0 -class entry_context : public error_context { - public: - const entry_base_t& entry; - - entry_context(const entry_base_t& _entry, - const string& _desc = "") throw() - : error_context(_desc), entry(_entry) {} - virtual ~entry_context() throw() {} - - virtual void describe(std::ostream& out) const throw(); -}; -#endif - -class auto_entry_t : public entry_base_t -{ -public: - scoped_ptr<xml::xpath_t> predicate; - - auto_entry_t(); - auto_entry_t(const string& _predicate); - virtual ~auto_entry_t(); - - virtual void extend_entry(entry_base_t& entry, bool post); - virtual bool valid() const { - return true; - } -}; - -struct auto_entry_finalizer_t : public entry_finalizer_t { - journal_t * journal; - auto_entry_finalizer_t(journal_t * _journal) : journal(_journal) {} - virtual bool operator()(entry_t& entry, bool post); -}; - - -class period_entry_t : public entry_base_t -{ - public: - interval_t period; - string period_string; - - period_entry_t() { - TRACE_CTOR(period_entry_t, ""); - } - period_entry_t(const string& _period) - : period(_period), period_string(_period) { - TRACE_CTOR(period_entry_t, "const string&"); - } - period_entry_t(const period_entry_t& e) - : entry_base_t(e), period(e.period), period_string(e.period_string) { - TRACE_CTOR(period_entry_t, "copy"); - } - - virtual ~period_entry_t() { - TRACE_DTOR(period_entry_t); - } - - virtual bool valid() const { - return period; - } -}; - - -typedef std::map<const string, account_t *> accounts_map; - -class account_t -{ - public: - typedef unsigned long ident_t; - - journal_t * journal; - account_t * parent; - string name; - optional<string> note; - unsigned short depth; - accounts_map accounts; - - mutable ident_t ident; - mutable string _fullname; - - account_t(account_t * _parent = NULL, - const string& _name = "", - const optional<string> _note = none) - : parent(_parent), name(_name), note(_note), - depth(parent ? parent->depth + 1 : 0), ident(0) { - TRACE_CTOR(account_t, "account_t *, const string&, const string&"); - } - ~account_t(); - - operator string() const { - return fullname(); - } - string fullname() const; - - void add_account(account_t * acct) { - accounts.insert(accounts_map::value_type(acct->name, acct)); - acct->journal = journal; - } - bool remove_account(account_t * acct) { - accounts_map::size_type n = accounts.erase(acct->name); - acct->journal = NULL; - return n > 0; - } - - account_t * find_account(const string& name, bool auto_create = true); - - bool valid() const; - - friend class journal_t; -}; - -std::ostream& operator<<(std::ostream& out, const account_t& account); - - -struct func_finalizer_t : public entry_finalizer_t { - typedef bool (*func_t)(entry_t& entry, bool post); - func_t func; - func_finalizer_t(func_t _func) : func(_func) {} - func_finalizer_t(const func_finalizer_t& other) : - entry_finalizer_t(), func(other.func) {} - virtual bool operator()(entry_t& entry, bool post) { - return func(entry, post); - } -}; - -template <typename T> -void add_hook(std::list<T>& list, T obj, const bool prepend = false) { - if (prepend) - list.push_front(obj); - else - list.push_back(obj); -} - -template <typename T> -void remove_hook(std::list<T>& list, T obj) { - list.remove(obj); -} - -template <typename T, typename Data> -bool run_hooks(std::list<T>& list, Data& item, bool post) { - for (typename std::list<T>::const_iterator i = list.begin(); - i != list.end(); - i++) - if (! (*(*i))(item, post)) - return false; - return true; -} - - -typedef std::list<entry_t *> entries_list; -typedef std::list<auto_entry_t *> auto_entries_list; -typedef std::list<period_entry_t *> period_entries_list; -typedef std::list<path> path_list; -typedef std::list<string> strings_list; - -class journal_t -{ - public: - account_t * master; - account_t * basket; - entries_list entries; - path_list sources; - optional<path> price_db; - char * item_pool; - char * item_pool_end; - - auto_entries_list auto_entries; - period_entries_list period_entries; - mutable accounts_map accounts_cache; - - std::list<entry_finalizer_t *> entry_finalize_hooks; - - journal_t() : basket(NULL), item_pool(NULL), item_pool_end(NULL) { - TRACE_CTOR(journal_t, ""); - master = new account_t(NULL, ""); - master->journal = this; - } - ~journal_t(); - - void add_account(account_t * acct) { - master->add_account(acct); - acct->journal = this; - } - bool remove_account(account_t * acct) { - return master->remove_account(acct); - acct->journal = NULL; - } - - account_t * find_account(const string& name, bool auto_create = true) { - accounts_map::iterator c = accounts_cache.find(name); - if (c != accounts_cache.end()) - return (*c).second; - - account_t * account = master->find_account(name, auto_create); - accounts_cache.insert(accounts_map::value_type(name, account)); - account->journal = this; - return account; - } - account_t * find_account_re(const string& regexp); - - bool add_entry(entry_t * entry); - bool remove_entry(entry_t * entry); - - void add_entry_finalizer(entry_finalizer_t * finalizer) { - add_hook<entry_finalizer_t *>(entry_finalize_hooks, finalizer); - } - void remove_entry_finalizer(entry_finalizer_t * finalizer) { - remove_hook<entry_finalizer_t *>(entry_finalize_hooks, finalizer); - } - - bool valid() const; -}; - -inline void extend_entry_base(journal_t * journal, entry_base_t& entry, - bool post) { - for (auto_entries_list::iterator i = journal->auto_entries.begin(); - i != journal->auto_entries.end(); - i++) - (*i)->extend_entry(entry, post); -} - -inline bool auto_entry_finalizer_t::operator()(entry_t& entry, bool post) { - extend_entry_base(journal, entry, post); - return true; -} - -extern const string version; - -} // namespace ledger - -#endif // _JOURNAL_H diff --git a/src/data/node.cc b/src/data/node.cc deleted file mode 100644 index 0ca0a04c..00000000 --- a/src/data/node.cc +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright (c) 2003-2007, 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 "node.h" -#include "document.h" - -namespace ledger { -namespace xml { - -const char * node_t::name() const -{ - return *document().lookup_name(name_id()); -} - -value_t& node_t::set_attr(const string& _name, const char * value) -{ - nameid_t name_id = document().register_name(_name); - return set_attr(name_id, value); -} - -value_t& node_t::set_attr(const string& _name, const value_t& value) -{ - nameid_t name_id = document().register_name(_name); - return set_attr(name_id, value); -} - -optional<value_t&> node_t::get_attr(const string& _name) -{ - optional<nameid_t> name_id = document().lookup_name_id(_name); - if (name_id) - return get_attr(*name_id); - else - return none; -} - -void output_xml_string(std::ostream& out, const string& str) -{ - for (const char * s = str.c_str(); *s; s++) { - switch (*s) { - case '<': - out << "<"; - break; - case '>': - out << ">"; - break; - case '&': - out << "&"; - break; - default: - out << *s; - break; - } - } -} - -void node_t::print_attributes(std::ostream& out) const -{ - if (attributes) { -#if 1 - foreach (const attr_pair& attr, *attributes) - out << ' ' << *document().lookup_name(attr.first) - << "=\"" << attr.second << "\""; -#else - foreach (const attr_pair& attr, attributes->get<0>()) - out << ' ' << *document().lookup_name(attr.first) - << "=\"" << attr.second << "\""; -#endif - } - - IF_VERIFY() - out << " type=\"parent_node_t\""; -} - -void parent_node_t::print(std::ostream& out) const -{ - out << '<' << name(); - print_attributes(out); - out << '>'; - - foreach (node_t * child, *this) - child->print(out); - - out << "</" << name() << '>'; -} - -void terminal_node_t::print(std::ostream& out) const -{ - if (data.empty()) { - out << '<' << name(); - print_attributes(out); - out << " />"; - } else { - out << '<' << name(); - print_attributes(out); - out << '>'; - output_xml_string(out, text()); - out << "</" << name() << '>'; - } -} - -} // namespace xml -} // namespace ledger diff --git a/src/data/node.h b/src/data/node.h deleted file mode 100644 index 36964bdf..00000000 --- a/src/data/node.h +++ /dev/null @@ -1,352 +0,0 @@ -/* - * Copyright (c) 2003-2007, 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. - */ - -#ifndef _NODE_H -#define _NODE_H - -#include "value.h" - -namespace ledger { -namespace xml { - -#define XML_NODE_IS_PARENT 0x1 - -DECLARE_EXCEPTION(conversion_error); - -class document_t; -class parent_node_t; -class terminal_node_t; - -class node_t : public supports_flags<>, public noncopyable -{ -public: - typedef uint_fast16_t nameid_t; - - // This has to be public so that multi_index_container can reference - // it in parent_node_t. - nameid_t name_id_; - -protected: - document_t& document_; - optional<parent_node_t&> parent_; - -#if 1 - typedef std::map<const nameid_t, value_t> attributes_t; - typedef std::pair<const nameid_t, value_t> attr_pair; -#else - typedef std::pair<nameid_t, value_t> attr_pair; - - typedef multi_index_container< - attr_pair, - multi_index::indexed_by< - multi_index::sequenced<>, - multi_index::hashed_unique< - multi_index::member<attr_pair, nameid_t, &attr_pair::first> > - > - > attributes_t; - - typedef attributes_t::nth_index<0>::type attributes_by_order; - typedef attributes_t::nth_index<1>::type attributes_hashed; -#endif - - optional<attributes_t> attributes; - -public: - bool compiled; // so that compile_node() can access it - - node_t(nameid_t _name_id, document_t& _document, - const optional<parent_node_t&>& _parent = none, flags_t _flags = 0) - : supports_flags<>(_flags), name_id_(_name_id), - document_(_document), parent_(_parent) { - TRACE_CTOR(node_t, "document_t&, parent_node_t&, nameid_t, flags_t"); - } - - virtual ~node_t() { - TRACE_DTOR(node_t); - } - - void extract(); - - bool is_compiled() const { - return compiled; - } - - bool is_parent_node() const { - return has_flags(XML_NODE_IS_PARENT); - } - parent_node_t& as_parent_node() { - if (! is_parent_node()) - throw_(std::logic_error, "Request to cast leaf node to a parent node"); - return downcast<parent_node_t>(*this); - } - const parent_node_t& as_parent_node() const { - return const_cast<node_t *>(this)->as_parent_node(); - } - - bool is_terminal_node() const { - return ! has_flags(XML_NODE_IS_PARENT); - } - terminal_node_t& as_terminal_node() { - if (! is_terminal_node()) - throw_(std::logic_error, "Request to cast parent node to a leaf node"); - return downcast<terminal_node_t>(*this); - } - const terminal_node_t& as_terminal_node() const { - return const_cast<node_t *>(this)->as_terminal_node(); - } - - virtual value_t to_value() const = 0; - virtual void print(std::ostream& out) const = 0; - virtual void print_attributes(std::ostream& out) const; - - const char * name() const; - nameid_t name_id() const { - return name_id_; - } - - document_t& document() const { - return document_; - } - optional<parent_node_t&> parent() const { - return parent_; - } - - value_t& set_attr(const string& _name, const char * value); - value_t& set_attr(const string& _name, const value_t& value); - - value_t& set_attr(const nameid_t _name_id, const char * value) { - return set_attr(_name_id, string_value(value)); - } - value_t& set_attr(const nameid_t _name_id, const value_t& value) { - if (! attributes) - attributes = attributes_t(); - - attributes_t::iterator i = attributes->find(_name_id); - if (i == attributes->end()) { - std::pair<attributes_t::iterator, bool> result = - attributes->insert(attr_pair(_name_id, value)); - assert(result.second); - return (*result.first).second; - } else { - i->second = value; - return i->second; - } - } - - optional<value_t&> get_attr(const string& _name); - optional<value_t&> get_attr(const nameid_t _name_id) { - if (attributes) { -#if 1 - attributes_t::iterator i = attributes->find(_name_id); - if (i != attributes->end()) - return (*i).second; -#else - typedef attributes_t::nth_index<1>::type attributes_by_name; - - const attributes_by_name& name_index = attributes->get<1>(); - attributes_by_name::iterator i = name_index.find(_name_id); - if (i != name_index.end()) - return (*i).second; -#endif - } - return none; - } - - optional<const value_t&> get_attr(const string& _name) const { - if (optional<value_t&> value = - const_cast<node_t *>(this)->get_attr(_name)) - return *value; - else - return none; - } - optional<const value_t&> get_attr(const nameid_t _name_id) const { - if (optional<value_t&> value = - const_cast<node_t *>(this)->get_attr(_name_id)) - return *value; - else - return none; - } -}; - -class parent_node_t : public node_t -{ - typedef multi_index_container< - node_t *, - multi_index::indexed_by< - multi_index::sequenced<>, - multi_index::hashed_non_unique< - multi_index::member<node_t, nameid_t, &node_t::name_id_> >, - multi_index::hashed_unique<multi_index::identity<node_t *> > - > - > children_t; - - children_t children; - -public: - typedef children_t::nth_index<0>::type children_by_order; - typedef children_t::nth_index<1>::type children_by_nameid; - typedef children_t::nth_index<2>::type children_by_ptr; - - parent_node_t(nameid_t _name_id, document_t& _document, - const optional<parent_node_t&>& _parent = none) - : node_t(_name_id, _document, _parent, XML_NODE_IS_PARENT) { - TRACE_CTOR(parent_node_t, "document_t *, parent_node_t *"); - } - virtual ~parent_node_t() { - TRACE_DTOR(parent_node_t); - clear_children(); - } - - template <typename T> - T * create_child(nameid_t _name_id) { - T * child = new T(_name_id, document(), *this); - children.push_back(child); - return child; - } - - void remove_child(node_t * child) { - children_by_ptr& ptr_index = children.get<2>(); - children_by_ptr::iterator i = ptr_index.find(child); - if (i == ptr_index.end()) - throw_(std::logic_error, "Request to delete node which is not a child"); - ptr_index.erase(i); - } - - void delete_child(node_t * child) { - remove_child(child); - checked_delete(child); - } - - struct match_nameid { - nameid_t nameid; - match_nameid(nameid_t _nameid) : nameid(_nameid) {} - bool operator()(const node_t * node) const { - return node->name_id() == nameid; - } - }; - - typedef children_by_order::iterator iterator; - typedef children_by_order::iterator const_iterator; - - children_by_order::iterator begin() { - return children.get<0>().begin(); - } - children_by_order::const_iterator begin() const { - return children.get<0>().begin(); - } - children_by_order::iterator end() { - return children.get<0>().end(); - } - children_by_order::const_iterator end() const { - return children.get<0>().end(); - } - - std::size_t size() const { - return children.get<0>().size(); - } - - children_by_nameid::iterator begin(nameid_t _name_id) { - return std::find_if(children.get<1>().begin(), - children.get<1>().end(), match_nameid(_name_id)); - } - children_by_nameid::const_iterator begin(nameid_t _name_id) const { - return std::find_if(children.get<1>().begin(), - children.get<1>().end(), match_nameid(_name_id)); - } - children_by_nameid::iterator end(nameid_t) { - return children.get<1>().end(); - } - children_by_nameid::const_iterator end(nameid_t) const { - return children.get<1>().end(); - } - - void clear_children() { - typedef children_t::nth_index<0>::type children_by_index; - - children_by_index& child_index = children.get<0>(); - for (children_by_index::iterator i = child_index.begin(); - i != child_index.end(); - i++) - checked_delete(*i); - - children.clear(); - } - - virtual value_t to_value() const { - throw_(std::logic_error, "Cannot convert parent node to a value"); - return NULL_VALUE; - } - - void print(std::ostream& out) const; -}; - -inline void node_t::extract() -{ - if (parent_) - parent_->remove_child(this); -} - -class terminal_node_t : public node_t -{ - string data; - -public: - terminal_node_t(nameid_t _name_id, document_t& _document, - const optional<parent_node_t&>& _parent = none) - : node_t(_name_id, _document, _parent) - { - TRACE_CTOR(terminal_node_t, "document_t *, parent_node_t *"); - } - virtual ~terminal_node_t() { - TRACE_DTOR(terminal_node_t); - } - - const char * text() const { - return data.c_str(); - } - void set_text(const string& _data) { - data = _data; - } - void set_text(const char * _data) { - data = _data; - } - - virtual value_t to_value() const { - return value_t(text(), true); - } - - void print(std::ostream& out) const; -}; - -} // namespace xml -} // namespace ledger - -#endif // _NODE_H diff --git a/src/data/parser.h b/src/data/parser.h deleted file mode 100644 index ecc73a6f..00000000 --- a/src/data/parser.h +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (c) 2003-2007, 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. - */ - -#ifndef _PARSER_H -#define _PARSER_H - -#include "utils.h" -#include "builder.h" - -namespace ledger { - -class account_t; -class journal_t; - -class parser_t -{ - public: - virtual ~parser_t() {} - - virtual bool test(std::istream& in) const = 0; - - virtual std::size_t parse(std::istream& in, - const path& pathname, - xml::builder_t& builder) = 0; -}; - -DECLARE_EXCEPTION(parse_error); - -/************************************************************************ - * - * General utility parsing functions - */ - -inline char * skip_ws(char * ptr) { - while (*ptr == ' ' || *ptr == '\t' || *ptr == '\n') - ptr++; - return ptr; -} - -inline char * next_element(char * buf, bool variable = false) { - for (char * p = buf; *p; p++) { - if (! (*p == ' ' || *p == '\t')) - continue; - - if (! variable) { - *p = '\0'; - return skip_ws(p + 1); - } - else if (*p == '\t') { - *p = '\0'; - return skip_ws(p + 1); - } - else if (*(p + 1) == ' ') { - *p = '\0'; - return skip_ws(p + 2); - } - } - return NULL; -} - -inline char peek_next_nonws(std::istream& in) { - char c = in.peek(); - while (! in.eof() && std::isspace(c)) { - in.get(c); - c = in.peek(); - } - return c; -} - -#define READ_INTO(str, targ, size, var, cond) { \ - char * _p = targ; \ - var = str.peek(); \ - while (! str.eof() && var != '\n' && (cond) && _p - targ < size) { \ - str.get(var); \ - if (str.eof()) \ - break; \ - if (var == '\\') { \ - str.get(var); \ - if (in.eof()) \ - break; \ - } \ - *_p++ = var; \ - var = str.peek(); \ - } \ - *_p = '\0'; \ -} - -#define READ_INTO_(str, targ, size, var, idx, cond) { \ - char * _p = targ; \ - var = str.peek(); \ - while (! str.eof() && var != '\n' && (cond) && _p - targ < size) { \ - str.get(var); \ - if (str.eof()) \ - break; \ - idx++; \ - if (var == '\\') { \ - str.get(var); \ - if (in.eof()) \ - break; \ - idx++; \ - } \ - *_p++ = var; \ - var = str.peek(); \ - } \ - *_p = '\0'; \ -} - -} // namespace ledger - -#endif // _PARSER_H diff --git a/src/data/textual.cc b/src/data/textual.cc deleted file mode 100644 index 9bee6c39..00000000 --- a/src/data/textual.cc +++ /dev/null @@ -1,480 +0,0 @@ -/* - * Copyright (c) 2003-2007, 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 "textual.h" - -namespace ledger { - -using namespace xml; - -#define MAX_LINE 1024 - -typedef builder_t::position_t position_t; - -void parse_transaction(builder_t& builder, - char * line, - position_t& end_of_line) -{ - // First cut up the input line into its various parts. - - char * state = NULL; - char * account_path = NULL; - char * amount = NULL; - char * note = NULL; - - char * p = line; - - if (*p == '*' || *p == '!') - state = p++; - - account_path = skip_ws(p); - - amount = next_element(account_path, true); - if (amount) { - char * p = amount; - while (*p && *p != ';') - p++; - - if (*p == ';') { - *p++ = '\0'; - note = skip_ws(p); - } - - p = amount + (std::strlen(amount) - 1); - while (p > amount && std::isspace(*p)) - p--; - - if (std::isspace(*(p + 1))) - *++p = '\0'; - } - - // Setup the details for this node - - if (state) { - switch (*state) { - case '*': - builder.push_attr(CLEARED_ATTR, "yes"); - break; - case '!': - builder.push_attr(PENDING_ATTR, "yes"); - break; - } - } - - builder.begin_node(TRANSACTION_NODE); - - // Parse the account name - - char * b = &account_path[0]; - char * e = &account_path[std::strlen(account_path) - 1]; - if ((*b == '[' && *e == ']') || - (*b == '(' && *e == ')')) { - builder.push_attr(VIRTUAL_ATTR, "yes"); - if (*b == '[') - builder.push_attr(BALANCE_ATTR, "yes"); - *account_path++ = '\0'; - *e = '\0'; - } - - builder.begin_node(ACCOUNT_PATH_NODE, true); - builder.append_text(account_path); - builder.end_node(ACCOUNT_PATH_NODE); - - // Parse the optional amount - - if (amount) { - builder.begin_node(AMOUNT_EXPR_NODE, true); - builder.append_text(amount); - builder.end_node(AMOUNT_EXPR_NODE); - } - - // Parse the optional note - - if (note) { - builder.begin_node(NOTE_NODE, true); - builder.append_text(note); - builder.end_node(NOTE_NODE); - } - - builder.end_node(TRANSACTION_NODE, end_of_line); -} - -bool parse_transactions(std::istream& in, builder_t& builder) -{ - TRACE_START(entry_xacts, 1, "Time spent parsing transactions:"); - - bool added = false; - - while (! in.eof() && (in.peek() == ' ' || in.peek() == '\t')) { - static char line[MAX_LINE + 1]; - line[0] = '\0'; - in.getline(line, MAX_LINE); - if (in.eof() || line[0] == '\0') - break; - - position_t end_of_line(builder.position()); - end_of_line.offset += std::strlen(line) + 1; - end_of_line.linenum++; - - char * p = skip_ws(line); - if (! *p || *p == '\r' || *p == '\n') - break; - - parse_transaction(builder, line, end_of_line); - added = true; - } - - TRACE_STOP(entry_xacts, 1); - - return added; -} - -void parse_entry(std::istream& in, - builder_t& builder, - char * line, - position_t& end_of_line) -{ - TRACE_START(entry_text, 1, "Time spent preparing entry text:"); - - // First cut up the input line into its various parts - - char * date = NULL; - char * date_eff = NULL; - char * statep = NULL; - char * code = NULL; - char * payee = NULL; - - date = line; - - char * p = line; - - while (*p && (std::isdigit(*p) || *p == '/' || *p == '.' || *p == '-')) - p++; - assert(*p); - - if (*p == '=') { - *p++ = '\0'; - date_eff = p; - - while (*p && (std::isdigit(*p) || *p == '/' || *p == '.' || *p == '-')) - p++; - assert(*p); - } else { - *p++ = '\0'; - } - - p = skip_ws(p); - - if (*p == '*' || *p == '!') { - statep = p; - p++; *p++ = '\0'; - - p = skip_ws(p); - } - - if (*p == '(') { - code = ++p; - while (*p && *p != ')') - p++; - assert(*p); - *p++ = '\0'; - - p = skip_ws(p); - } - - payee = p; - - p = payee + (std::strlen(payee) - 1); - while (p > payee && std::isspace(*p)) - p--; - - if (std::isspace(*(p + 1))) - *++p = '\0'; - - TRACE_STOP(entry_text, 1); - - // Setup the details for this node - - TRACE_START(entry_details, 1, "Time spent parsing entry details:"); - - builder.push_attr(DATE_ATTR, date); - - if (date_eff) - builder.push_attr(EFF_DATE_ATTR, date_eff); - - if (statep) { - switch (*statep) { - case '*': - builder.push_attr(CLEARED_ATTR, "yes"); - break; - case '!': - builder.push_attr(PENDING_ATTR, "yes"); - break; - } - } - - if (code) - builder.push_attr(CODE_ATTR, code); - - builder.begin_node(ENTRY_NODE); - - builder.begin_node(PAYEE_NODE, true); - assert(payee); - builder.append_text(*payee != '\0' ? payee : "<Unspecified payee>"); - builder.end_node(PAYEE_NODE, end_of_line); - - TRACE_STOP(entry_details, 1); - - // Parse all the transactions associated with this entry - - if (! parse_transactions(in, builder)) - throw_(parse_error, "Entry has no transactions"); - - builder.end_node(ENTRY_NODE); -} - -bool textual_parser_t::test(std::istream& in) const -{ - char buf[5]; - - in.read(buf, 5); - if (std::strncmp(buf, "<?xml", 5) == 0) - throw_(parse_error, "Ledger file contains XML data, but format was not recognized"); - - in.clear(); - in.seekg(0, std::ios::beg); - assert(in.good()); - return true; -} - -std::size_t textual_parser_t::parse(std::istream& in, - const path& pathname, - builder_t& builder) -{ - TRACE_START(parsing_total, 1, "Total time spent parsing text:"); - - INFO("Parsing file '" << pathname.string() << "'"); - - builder.begin_node(JOURNAL_NODE); - - std::size_t count = 0; - - while (in.good() && ! in.eof()) { - static char line[MAX_LINE + 1]; - in.getline(line, MAX_LINE); - if (in.eof()) - break; - - position_t end_of_line(builder.position()); - end_of_line.offset += std::strlen(line) + 1; - end_of_line.linenum++; - - //PUSH_CONTEXT(); - - switch (line[0]) { - case '\0': - case '\r': - break; - - case ' ': - case '\t': { - char * p = skip_ws(line); - if (*p && *p != '\r') - throw_(parse_error, "Line begins with whitespace"); - break; - } - - case 'i': - case 'I': { - string date(line, 2, 19); - - char * p = skip_ws(line + 22); - char * n = next_element(p, true); - - builder.push_attr(TIME_ATTR, date); - builder.push_attr(ACCOUNT_ATTR, p); - builder.begin_node(CHECKIN_NODE, true); - builder.append_text(n); - builder.end_node(CHECKIN_NODE, end_of_line); - break; - } - - case 'o': - case 'O': { - string date(line, 2, 19); - - char * p = skip_ws(line + 22); - char * n = next_element(p, true); - - builder.push_attr(TIME_ATTR, date); - builder.push_attr(ACCOUNT_ATTR, p); - builder.begin_node(CHECKIN_NODE, true); - builder.append_text(n); - builder.end_node(CHECKIN_NODE, end_of_line); - break; - } - - case 'D': { // specifies default commodity flags - builder.push_attr(TEMPLATE_ATTR, skip_ws(line + 1)); - builder.push_node(COMMODITY_TEMPLATE_NODE, end_of_line); - break; - } - - case 'A': // a default account for unbalanced xacts - builder.push_attr(NAME_ATTR, skip_ws(line + 1)); - builder.push_node(DEFAULT_ACCOUNT_NODE, end_of_line); - break; - - case 'C': // a set of conversions - if (char * p = std::strchr(line + 1, '=')) { - *p++ = '\0'; - builder.push_attr(FROM_ATTR, skip_ws(line + 1)); - builder.push_attr(TO_ATTR, p); - builder.push_node(COMMODITY_CONVERSION_NODE, end_of_line); - } else { - throw_(parse_error, "Conversion entry (code C) must follow the format X=Y"); - } - break; - - case 'P': { // a pricing entry - char * date_field_ptr = skip_ws(line + 1); - char * time_field_ptr = next_element(date_field_ptr); - if (! time_field_ptr) - throw_(parse_error, "Pricing entry (code P) is missing arguments"); - string date_field = date_field_ptr; - - char * symbol_and_price; - moment_t datetime; - - if (std::isdigit(time_field_ptr[0])) { - symbol_and_price = next_element(time_field_ptr); - if (! symbol_and_price) - throw_(parse_error, "Pricing entry (code P) is missing a symbol name"); - } else { - symbol_and_price = time_field_ptr; - } - - builder.push_attr(DATE_ATTR, date_field_ptr); - builder.push_attr(TIME_ATTR, time_field_ptr); - - string symbol; - commodity_t::parse_symbol(symbol_and_price, symbol); - - builder.push_attr(SYMBOL_ATTR, symbol); - builder.push_attr(PRICE_ATTR, skip_ws(symbol_and_price)); - builder.push_node(PRICE_HISTORY_NODE, end_of_line); - break; - } - - case 'N': { // don't download prices - char * p = skip_ws(line + 1); - - string symbol; - commodity_t::parse_symbol(p, symbol); - - builder.push_attr(SYMBOL_ATTR, symbol); - builder.push_node(COMMODITY_NOMARKET_NODE, end_of_line); - break; - } - - case 'Y': // set current year - builder.push_attr(YEAR_ATTR, skip_ws(line + 1)); - builder.push_node(CURRENT_YEAR_NODE, end_of_line); - break; - - case 'h': - case 'b': - case ';': // comment - // jww (2007-05-12): Read in the comment and save it - break; - - case '@': - case '!': { // directive - char * p = next_element(line); - string word(line + 1); - - builder.push_attr(NAME_ATTR, word); - builder.push_attr(ARG_ATTR, p); - builder.push_node(DIRECTIVE_NODE, end_of_line); - break; - } - - case '-': // option setting - throw_(parse_error, "Option settings are not allowed in journal files"); - - case '=': { // automated entry - builder.begin_node(AUTO_ENTRY_NODE); - builder.begin_node(RULE_NODE, true); - builder.append_text(skip_ws(line + 1)); - builder.end_node(RULE_NODE); - - builder.set_position(end_of_line); - - if (! parse_transactions(in, builder)) - throw_(parse_error, "Automated entry has no transactions"); - - builder.end_node(AUTO_ENTRY_NODE); - break; - } - - case '~': // period entry - builder.begin_node(PERIOD_ENTRY_NODE); - builder.begin_node(PERIOD_NODE, true); - builder.append_text(skip_ws(line + 1)); - builder.end_node(PERIOD_NODE); - - builder.set_position(end_of_line); - - if (! parse_transactions(in, builder)) - throw_(parse_error, "Repeating entry has no transactions"); - - builder.end_node(PERIOD_ENTRY_NODE); - break; - - default: - TRACE_START(entries, 1, "Time spent handling entries:"); - parse_entry(in, builder, line, end_of_line); - count++; - TRACE_STOP(entries, 1); - break; - } - - //POP_CONTEXT(builder_context(builder)); - } - - builder.end_node(JOURNAL_NODE); - - TRACE_STOP(parsing_total, 1); - - return count; -} - -} // namespace ledger diff --git a/src/draft.cc b/src/draft.cc new file mode 100644 index 00000000..8478a31d --- /dev/null +++ b/src/draft.cc @@ -0,0 +1,527 @@ +/* + * 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 "draft.h" +#include "xact.h" +#include "post.h" +#include "account.h" +#include "journal.h" +#include "session.h" +#include "report.h" +#include "output.h" + +namespace ledger { + +void draft_t::xact_template_t::dump(std::ostream& out) const +{ + if (date) + out << _("Date: ") << *date << std::endl; + else + out << _("Date: <today>") << std::endl; + + if (code) + out << _("Code: ") << *code << std::endl; + if (note) + out << _("Note: ") << *note << std::endl; + + if (payee_mask.empty()) + out << _("Payee mask: INVALID (template expression will cause an error)") + << std::endl; + else + out << _("Payee mask: ") << payee_mask << std::endl; + + if (posts.empty()) { + out << std::endl + << _("<Posting copied from last related transaction>") + << std::endl; + } else { + bool has_only_from = true; + bool has_only_to = true; + + foreach (const post_template_t& post, posts) { + if (post.from) + has_only_to = false; + else + has_only_from = false; + } + + foreach (const post_template_t& post, posts) { + straccstream accum; + out << std::endl + << ACCUM(accum << _("[Posting \"%1\"]") + << (post.from ? _("from") : _("to"))) + << std::endl; + + if (post.account_mask) + out << _(" Account mask: ") << *post.account_mask << std::endl; + else if (post.from) + out << _(" Account mask: <use last of last related accounts>") << std::endl; + else + out << _(" Account mask: <use first of last related accounts>") << std::endl; + + if (post.amount) + out << _(" Amount: ") << *post.amount << std::endl; + + if (post.cost) + out << _(" Cost: ") << *post.cost_operator + << " " << *post.cost << std::endl; + } + } +} + +void draft_t::parse_args(const value_t& args) +{ + regex date_mask(_("([0-9]+(?:[-/.][0-9]+)?(?:[-/.][0-9]+))?")); + smatch what; + bool check_for_date = true; + + tmpl = xact_template_t(); + + optional<date_time::weekdays> weekday; + xact_template_t::post_template_t * post = NULL; + + value_t::sequence_t::const_iterator begin = args.begin(); + value_t::sequence_t::const_iterator end = args.end(); + + for (; begin != end; begin++) { + if (check_for_date && + regex_match((*begin).to_string(), what, date_mask)) { + tmpl->date = parse_date(what[0]); + check_for_date = false; + } + else if (check_for_date && + bool(weekday = string_to_day_of_week(what[0]))) { + short dow = static_cast<short>(*weekday); + date_t date = CURRENT_DATE() - date_duration(1); + while (date.day_of_week() != dow) + date -= date_duration(1); + tmpl->date = date; + check_for_date = false; + } + else { + string arg = (*begin).to_string(); + + if (arg == "at") { + if (begin == end) + throw std::runtime_error(_("Invalid xact command arguments")); + tmpl->payee_mask = (*++begin).to_string(); + } + else if (arg == "to" || arg == "from") { + if (! post || post->account_mask) { + tmpl->posts.push_back(xact_template_t::post_template_t()); + post = &tmpl->posts.back(); + } + if (begin == end) + throw std::runtime_error(_("Invalid xact command arguments")); + post->account_mask = mask_t((*++begin).to_string()); + post->from = arg == "from"; + } + else if (arg == "on") { + if (begin == end) + throw std::runtime_error(_("Invalid xact command arguments")); + tmpl->date = parse_date((*++begin).to_string()); + check_for_date = false; + } + else if (arg == "code") { + if (begin == end) + throw std::runtime_error(_("Invalid xact command arguments")); + tmpl->code = (*++begin).to_string(); + } + else if (arg == "note") { + if (begin == end) + throw std::runtime_error(_("Invalid xact command arguments")); + tmpl->note = (*++begin).to_string(); + } + else if (arg == "rest") { + ; // just ignore this argument + } + else if (arg == "@" || arg == "@@") { + amount_t cost; + post->cost_operator = arg; + if (begin == end) + throw std::runtime_error(_("Invalid xact command arguments")); + arg = (*++begin).to_string(); + if (! cost.parse(arg, PARSE_SOFT_FAIL | PARSE_NO_MIGRATE)) + throw std::runtime_error(_("Invalid xact command arguments")); + post->cost = cost; + } + else { + // Without a preposition, it is either: + // + // A payee, if we have not seen one + // An account or an amount, if we have + // An account if an amount has just been seen + // An amount if an account has just been seen + + if (tmpl->payee_mask.empty()) { + tmpl->payee_mask = arg; + } + else { + amount_t amt; + optional<mask_t> account; + + if (! amt.parse(arg, PARSE_SOFT_FAIL | PARSE_NO_MIGRATE)) + account = mask_t(arg); + + if (! post || + (account && post->account_mask) || + (! account && post->amount)) { + tmpl->posts.push_back(xact_template_t::post_template_t()); + post = &tmpl->posts.back(); + } + + if (account) { + post->from = false; + post->account_mask = account; + } else { + post->amount = amt; + } + } + } + } + } + + if (! tmpl->posts.empty()) { + bool has_only_from = true; + bool has_only_to = true; + + // A single account at the end of the line is the "from" account + if (tmpl->posts.size() > 1 && + tmpl->posts.back().account_mask && ! tmpl->posts.back().amount) + tmpl->posts.back().from = true; + + foreach (xact_template_t::post_template_t& post, tmpl->posts) { + if (post.from) + has_only_to = false; + else + has_only_from = false; + } + + if (has_only_from) { + tmpl->posts.push_front(xact_template_t::post_template_t()); + } + else if (has_only_to) { + tmpl->posts.push_back(xact_template_t::post_template_t()); + tmpl->posts.back().from = true; + } + } +} + +xact_t * draft_t::insert(journal_t& journal) +{ + if (tmpl->payee_mask.empty()) + throw std::runtime_error(_("xact' command requires at least a payee")); + + xact_t * matching = NULL; + + std::auto_ptr<xact_t> added(new xact_t); + + for (xacts_list::reverse_iterator j = journal.xacts.rbegin(); + j != journal.xacts.rend(); + j++) { + if (tmpl->payee_mask.match((*j)->payee)) { + matching = *j; + DEBUG("derive.xact", + "Found payee match: transaction on line " << (*j)->pos->beg_line); + break; + } + } + + if (! tmpl->date) { + added->_date = CURRENT_DATE(); + DEBUG("derive.xact", "Setting date to current date"); + } else { + added->_date = tmpl->date; + DEBUG("derive.xact", "Setting date to template date: " << *tmpl->date); + } + + added->set_state(item_t::UNCLEARED); + + if (matching) { + added->payee = matching->payee; + added->code = matching->code; + added->note = matching->note; + +#if defined(DEBUG_ON) + DEBUG("derive.xact", "Setting payee from match: " << added->payee); + if (added->code) + DEBUG("derive.xact", "Setting code from match: " << *added->code); + if (added->note) + DEBUG("derive.xact", "Setting note from match: " << *added->note); +#endif + } else { + added->payee = tmpl->payee_mask.str(); + DEBUG("derive.xact", "Setting payee from template: " << added->payee); + } + + if (tmpl->code) { + added->code = tmpl->code; + DEBUG("derive.xact", "Now setting code from template: " << *added->code); + } + if (tmpl->note) { + added->note = tmpl->note; + DEBUG("derive.xact", "Now setting note from template: " << *added->note); + } + + if (tmpl->posts.empty()) { + if (matching) { + DEBUG("derive.xact", "Template had no postings, copying from match"); + + foreach (post_t * post, matching->posts) { + added->add_post(new post_t(*post)); + added->posts.back()->set_state(item_t::UNCLEARED); + } + } else { + throw_(std::runtime_error, + _("No accounts, and no past transaction matching '%1'") + << tmpl->payee_mask); + } + } else { + DEBUG("derive.xact", "Template had postings"); + + bool any_post_has_amount = false; + foreach (xact_template_t::post_template_t& post, tmpl->posts) { + if (post.amount) { + DEBUG("derive.xact", " and at least one has an amount specified"); + any_post_has_amount = true; + break; + } + } + + foreach (xact_template_t::post_template_t& post, tmpl->posts) { + std::auto_ptr<post_t> new_post; + + commodity_t * found_commodity = NULL; + + if (matching) { + if (post.account_mask) { + DEBUG("derive.xact", + "Looking for matching posting based on account mask"); + + foreach (post_t * x, matching->posts) { + if (post.account_mask->match(x->account->fullname())) { + new_post.reset(new post_t(*x)); + DEBUG("derive.xact", + "Founding posting from line " << x->pos->beg_line); + break; + } + } + } else { + if (post.from) { + for (posts_list::reverse_iterator j = matching->posts.rbegin(); + j != matching->posts.rend(); + j++) { + if ((*j)->must_balance()) { + new_post.reset(new post_t(**j)); + DEBUG("derive.xact", + "Copied last real posting from matching"); + break; + } + } + } else { + for (posts_list::iterator j = matching->posts.begin(); + j != matching->posts.end(); + j++) { + if ((*j)->must_balance()) { + new_post.reset(new post_t(**j)); + DEBUG("derive.xact", + "Copied first real posting from matching"); + break; + } + } + } + } + } + + if (! new_post.get()) { + new_post.reset(new post_t); + DEBUG("derive.xact", "New posting was NULL, creating a blank one"); + } + + if (! new_post->account) { + DEBUG("derive.xact", "New posting still needs an account"); + + if (post.account_mask) { + DEBUG("derive.xact", "The template has an account mask"); + + account_t * acct = NULL; + if (! acct) { + acct = journal.find_account_re(post.account_mask->str()); +#if defined(DEBUG_ON) + if (acct) + DEBUG("derive.xact", "Found account as a regular expression"); +#endif + } + if (! acct) { + acct = journal.find_account(post.account_mask->str()); +#if defined(DEBUG_ON) + if (acct) + DEBUG("derive.xact", "Found (or created) account by name"); +#endif + } + + // Find out the default commodity to use by looking at the last + // commodity used in that account + for (xacts_list::reverse_iterator j = journal.xacts.rbegin(); + j != journal.xacts.rend(); + j++) { + foreach (post_t * x, (*j)->posts) { + if (x->account == acct && ! x->amount.is_null()) { + new_post.reset(new post_t(*x)); + DEBUG("derive.xact", + "Found account in journal postings, setting new posting"); + break; + } + } + } + + new_post->account = acct; + DEBUG("derive.xact", + "Set new posting's account to: " << acct->fullname()); + } else { + if (post.from) { + new_post->account = journal.find_account(_("Liabilities:Unknown")); + DEBUG("derive.xact", + "Set new posting's account to: Liabilities:Unknown"); + } else { + new_post->account = journal.find_account(_("Expenses:Unknown")); + DEBUG("derive.xact", + "Set new posting's account to: Expenses:Unknown"); + } + } + } + assert(new_post->account); + + if (new_post.get() && ! new_post->amount.is_null()) { + found_commodity = &new_post->amount.commodity(); + + if (any_post_has_amount) { + new_post->amount = amount_t(); + DEBUG("derive.xact", "New posting has an amount, but we cleared it"); + } else { + any_post_has_amount = true; + DEBUG("derive.xact", "New posting has an amount, and we're using it"); + } + } + + if (post.amount) { + new_post->amount = *post.amount; + DEBUG("derive.xact", "Copied over posting amount"); + + if (post.from) { + new_post->amount.in_place_negate(); + DEBUG("derive.xact", "Negated new posting amount"); + } + } + + if (post.cost) { + if (post.cost->sign() < 0) + throw parse_error(_("A posting's cost may not be negative")); + + post.cost->in_place_unround(); + + if (*post.cost_operator == "@") { + // For the sole case where the cost might be uncommoditized, + // guarantee that the commodity of the cost after multiplication + // is the same as it was before. + commodity_t& cost_commodity(post.cost->commodity()); + *post.cost *= new_post->amount; + post.cost->set_commodity(cost_commodity); + } + + new_post->cost = *post.cost; + DEBUG("derive.xact", "Copied over posting cost"); + } + + if (found_commodity && + ! new_post->amount.is_null() && + ! new_post->amount.has_commodity()) { + new_post->amount.set_commodity(*found_commodity); + DEBUG("derive.xact", "Set posting amount commodity to: " + << new_post->amount.commodity()); + + new_post->amount = new_post->amount.rounded(); + DEBUG("derive.xact", + "Rounded posting amount to: " << new_post->amount); + } + + added->add_post(new_post.release()); + added->posts.back()->account->add_post(added->posts.back()); + added->posts.back()->set_state(item_t::UNCLEARED); + + DEBUG("derive.xact", "Added new posting to derived entry"); + } + } + + if (! journal.add_xact(added.get())) + throw_(std::runtime_error, + _("Failed to finalize derived transaction (check commodities)")); + + return added.release(); +} + +value_t template_command(call_scope_t& args) +{ + report_t& report(find_scope<report_t>(args)); + std::ostream& out(report.output_stream); + + out << _("--- Input arguments ---") << std::endl; + args.value().dump(out); + out << std::endl << std::endl; + + draft_t draft(args.value()); + + out << _("--- Transaction template ---") << std::endl; + draft.dump(out); + + return true; +} + +value_t xact_command(call_scope_t& args) +{ + report_t& report(find_scope<report_t>(args)); + draft_t draft(args.value()); + + xact_t * new_xact = draft.insert(*report.session.journal.get()); + + // Only consider actual postings for the "xact" command + report.HANDLER(limit_).on(string("#xact"), "actual"); + + report.xact_report(post_handler_ptr + (new format_posts(report, + report.HANDLER(print_format_).str())), + *new_xact); + return true; +} + +} // namespace ledger diff --git a/src/utility/times.cc b/src/draft.h index fc6f2f1b..93e98ff5 100644 --- a/src/utility/times.cc +++ b/src/draft.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2007, John Wiegley. All rights reserved. + * 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 @@ -29,52 +29,85 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "utils.h" +/** + * @addtogroup expr + */ -namespace ledger { +/** + * @file draft.h + * @author John Wiegley + * + * @ingroup report + */ +#ifndef _DRAFT_H +#define _DRAFT_H -#ifdef BOOST_DATE_TIME_HAS_HIGH_PRECISION_CLOCK -const ptime time_now = boost::posix_time::microsec_clock::universal_time(); -#else -const ptime time_now = boost::posix_time::second_clock::universal_time(); -#endif -const date date_now = boost::gregorian::day_clock::universal_day(); +#include "exprbase.h" +#include "value.h" -#ifdef SUPPORT_DATE_AND_TIME -const moment_t& now(time_now); -#else -const moment_t& now(date_now); -#endif +namespace ledger { -bool day_before_month = false; -static bool day_before_month_initialized = false; +class journal_t; +class xact_t; -moment_t parse_datetime(const char * str) +class draft_t : public expr_base_t<value_t> { - if (! day_before_month_initialized) { -#ifdef HAVE_NL_LANGINFO - const char * d_fmt = nl_langinfo(D_FMT); - if (d_fmt && std::strlen(d_fmt) > 1 && d_fmt[1] == 'd') - day_before_month = true; - day_before_month_initialized = true; -#endif + typedef expr_base_t<value_t> base_type; + + struct xact_template_t + { + optional<date_t> date; + optional<string> code; + optional<string> note; + mask_t payee_mask; + + struct post_template_t { + bool from; + optional<mask_t> account_mask; + optional<amount_t> amount; + optional<string> cost_operator; + optional<amount_t> cost; + + post_template_t() : from(false) {} + }; + + std::list<post_template_t> posts; + + xact_template_t() {} + + void dump(std::ostream& out) const; + }; + + optional<xact_template_t> tmpl; + +public: + draft_t(const value_t& args) : base_type() { + TRACE_CTOR(draft_t, "value_t"); + if (! args.empty()) + parse_args(args); + } + virtual ~draft_t() { + TRACE_DTOR(draft_t); } -#if 0 - return parse_abs_datetime(in); -#else - int year = ((str[0] - '0') * 1000 + - (str[1] - '0') * 100 + - (str[2] - '0') * 10 + - (str[3] - '0')); - int mon = ((str[5] - '0') * 10 + - (str[6] - '0')); + void parse_args(const value_t& args); - int day = ((str[8] - '0') * 10 + - (str[9] - '0')); + virtual result_type real_calc(scope_t&) { + assert(false); + return true; + } - return moment_t(boost::gregorian::date(year, mon, day)); -#endif -} + xact_t * insert(journal_t& journal); + + virtual void dump(std::ostream& out) const { + if (tmpl) + tmpl->dump(out); + } +}; + +value_t xact_command(call_scope_t& args); +value_t template_command(call_scope_t& args); } // namespace ledger + +#endif // _DRAFT_H diff --git a/src/driver/fdstream.hpp b/src/driver/fdstream.hpp deleted file mode 100644 index ffcf5709..00000000 --- a/src/driver/fdstream.hpp +++ /dev/null @@ -1,215 +0,0 @@ -/* - * Copyright (c) 2003-2007, 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. - */ - -/* The following code declares classes to read from and write to - * file descriptore or file handles. - * - * See - * http://www.josuttis.com/cppcode - * for details and the latest version. - * - * - open: - * - integrating BUFSIZ on some systems? - * - optimized reading of multiple characters - * - stream for reading AND writing - * - i18n - * - * (C) Copyright Nicolai M. Josuttis 2001. - * Permission to copy, use, modify, sell and distribute this software - * is granted provided this copyright notice appears in all copies. - * This software is provided "as is" without express or implied - * warranty, and with no claim as to its suitability for any purpose. - * - * Version: Jul 28, 2002 - * History: - * Jul 28, 2002: bugfix memcpy() => memmove() - * fdinbuf::underflow(): cast for return statements - * Aug 05, 2001: first public version - */ -#ifndef BOOST_FDSTREAM_HPP -#define BOOST_FDSTREAM_HPP - -#include <istream> -#include <ostream> -#include <streambuf> -// for EOF: -#include <cstdio> -// for memmove(): -#include <cstring> - - -// low-level read and write functions -#ifdef _MSC_VER -# include <io.h> -#else -# include <unistd.h> -//extern "C" { -// int write (int fd, const char* buf, int num); -// int read (int fd, char* buf, int num); -//} -#endif - - -// BEGIN namespace BOOST -namespace boost { - - -/************************************************************ - * fdostream - * - a stream that writes on a file descriptor - ************************************************************/ - - -class fdoutbuf : public std::streambuf { - protected: - int fd; // file descriptor - public: - // constructor - fdoutbuf (int _fd) : fd(_fd) { - } - protected: - // write one character - virtual int_type overflow (int_type c) { - if (c != EOF) { - char z = c; - if (write (fd, &z, 1) != 1) { - return EOF; - } - } - return c; - } - // write multiple characters - virtual - std::streamsize xsputn (const char* s, - std::streamsize num) { - return write(fd,s,num); - } -}; - -class fdostream : public std::ostream { - protected: - fdoutbuf buf; - public: - fdostream (int fd) : std::ostream(0), buf(fd) { - rdbuf(&buf); - } -}; - - -/************************************************************ - * fdistream - * - a stream that reads on a file descriptor - ************************************************************/ - -class fdinbuf : public std::streambuf { - protected: - int fd; // file descriptor - protected: - /* data buffer: - * - at most, pbSize characters in putback area plus - * - at most, bufSize characters in ordinary read buffer - */ - static const int pbSize = 4; // size of putback area - static const int bufSize = 1024; // size of the data buffer - char buffer[bufSize+pbSize]; // data buffer - - public: - /* constructor - * - initialize file descriptor - * - initialize empty data buffer - * - no putback area - * => force underflow() - */ - fdinbuf (int _fd) : fd(_fd) { - setg (buffer+pbSize, // beginning of putback area - buffer+pbSize, // read position - buffer+pbSize); // end position - } - - protected: - // insert new characters into the buffer - virtual int_type underflow () { -#ifndef _MSC_VER - using std::memmove; -#endif - - // is read position before end of buffer? - if (gptr() < egptr()) { - return traits_type::to_int_type(*gptr()); - } - - /* process size of putback area - * - use number of characters read - * - but at most size of putback area - */ - int numPutback; - numPutback = gptr() - eback(); - if (numPutback > pbSize) { - numPutback = pbSize; - } - - /* copy up to pbSize characters previously read into - * the putback area - */ - memmove (buffer+(pbSize-numPutback), gptr()-numPutback, - numPutback); - - // read at most bufSize new characters - int num; - num = read (fd, buffer+pbSize, bufSize); - if (num <= 0) { - // ERROR or EOF - return EOF; - } - - // reset buffer pointers - setg (buffer+(pbSize-numPutback), // beginning of putback area - buffer+pbSize, // read position - buffer+pbSize+num); // end of buffer - - // return next character - return traits_type::to_int_type(*gptr()); - } -}; - -class fdistream : public std::istream { - protected: - fdinbuf buf; - public: - fdistream (int fd) : std::istream(0), buf(fd) { - rdbuf(&buf); - } -}; - - -} // END namespace boost - -#endif /*BOOST_FDSTREAM_HPP*/ diff --git a/src/driver/main.cc b/src/driver/main.cc deleted file mode 100644 index 469bb5ee..00000000 --- a/src/driver/main.cc +++ /dev/null @@ -1,481 +0,0 @@ -/* - * Copyright (c) 2003-2007, 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 "utils.h" -#include "option.h" -//#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE) -//#include "gnucash.h" -//#endif -//#include "qif.h" -//#include "ofx.h" -#include "jbuilder.h" -#include "compile.h" - -#include <ledger.h> - -#ifdef HAVE_UNIX_PIPES -#include <sys/types.h> -#include <sys/wait.h> -#include <fdstream.hpp> -#endif - -static int read_and_report(ledger::report_t& report, int argc, char * argv[], - char * envp[]) -{ - using namespace ledger; - - session_t& session(report.session); - - // Handle the command-line arguments - - strings_list args; - process_arguments(argc - 1, argv + 1, false, report, args); - - if (args.empty()) { -#if 0 - help(std::cerr); -#endif - return 1; - } - strings_list::iterator arg = args.begin(); - - if (! session.cache_file) - session.use_cache = false; - else - session.use_cache = ! session.data_file.empty() && session.price_db; - - DEBUG("ledger.session.cache", "1. use_cache = " << session.use_cache); - - // Process the environment settings - - TRACE_START(environment, 1, "Processed environment variables"); - process_environment(const_cast<const char **>(envp), "LEDGER_", report); - TRACE_FINISH(environment, 1); - - optional<path> home; - if (const char * home_var = std::getenv("HOME")) - home = home_var; - - if (! session.init_file) - session.init_file = home ? *home / ".ledgerrc" : "./.ledgerrc"; - if (! session.price_db) - session.price_db = home ? *home / ".pricedb" : "./.pricedb"; - - if (! session.cache_file) - session.cache_file = home ? *home / ".ledger-cache" : "./.ledger-cache"; - - if (session.data_file == *session.cache_file) - session.use_cache = false; - - DEBUG("ledger.session.cache", "2. use_cache = " << session.use_cache); - - INFO("Initialization file is " << session.init_file->string()); - INFO("Price database is " << session.price_db->string()); - INFO("Binary cache is " << session.cache_file->string()); - INFO("Journal file is " << session.data_file.string()); - - if (! session.use_cache) - INFO("Binary cache mechanism will not be used"); - - // Read the command word and create a command object based on it - - string verb = *arg++; - - xml::xpath_t::function_t command; - -#if 0 - if (verb == "register" || verb == "reg" || verb == "r") - command = register_command(); - else if (verb == "balance" || verb == "bal" || verb == "b") - command = balance_command(); - else if (verb == "print" || verb == "p") - command = print_command(); - else if (verb == "equity") - command = equity_command(); - else if (verb == "entry") - command = entry_command(); - else if (verb == "dump") - command = dump_command(); - else if (verb == "output") - command = output_command(); - else if (verb == "prices") - command = prices_command(); - else if (verb == "pricesdb") - command = pricesdb_command(); - else if (verb == "csv") - command = csv_command(); - else if (verb == "emacs" || verb == "lisp") - command = emacs_command(); - else -#endif - if (verb == "xml") - command = bind(xml_command, _1); - else if (verb == "expr") - ; - else if (verb == "xpath") - ; - else if (verb == "parse") { - xml::xpath_t expr(*arg); - xml::document_t temp(xml::LEDGER_NODE); - - xml::xpath_t::context_scope_t doc_scope(report, &temp); - - IF_INFO() { - std::cout << "Value expression tree:" << std::endl; - expr.dump(std::cout); - std::cout << std::endl; - - std::cout << "Value expression parsed was:" << std::endl; - expr.print(std::cout, doc_scope); - std::cout << std::endl << std::endl; - - expr.compile(doc_scope); - - std::cout << "Value expression after compiling:" << std::endl; - expr.dump(std::cout); - std::cout << std::endl; - - std::cout << "Value expression is now:" << std::endl; - expr.print(std::cout, doc_scope); - std::cout << std::endl << std::endl; - - std::cout << "Result of calculation: "; - } - - std::cout << expr.calc(doc_scope).strip_annotations() << std::endl; - - return 0; - } - else { - char buf[128]; - std::strcpy(buf, "command_"); - std::strcat(buf, verb.c_str()); - - if (xml::xpath_t::ptr_op_t def = report.lookup(buf)) - command = def->as_function(); - - if (! command) - throw_(std::logic_error, string("Unrecognized command '") + verb + "'"); - } - - // Parse the initialization file, which can only be textual; then - // parse the journal data. - - session.read_init(); - - INFO_START(journal, "Read journal file"); - - xml::document_t xml_document(xml::LEDGER_NODE); - journal_t * journal = session.create_journal(); - xml::journal_builder_t builder(xml_document, journal); - - if (! session.read_data(builder, journal, report.account)) - throw_(parse_error, "Failed to locate any journal entries; " - "did you specify a valid file with -f?"); - - INFO_FINISH(journal); - - TRACE_FINISH(entry_text, 1); - TRACE_FINISH(entry_date, 1); - TRACE_FINISH(entry_details, 1); - TRACE_FINISH(entry_xacts, 1); - TRACE_FINISH(entries, 1); - TRACE_FINISH(parsing_total, 1); - - // Configure the output stream - -#ifdef HAVE_UNIX_PIPES - int status, pfd[2]; // Pipe file descriptors -#endif - std::ostream * out = &std::cout; - - if (report.output_file) { - out = new ofstream(*report.output_file); - } -#ifdef HAVE_UNIX_PIPES - else if (report.pager) { - status = pipe(pfd); - if (status == -1) - throw_(std::logic_error, "Failed to create pipe"); - - status = fork(); - if (status < 0) { - throw_(std::logic_error, "Failed to fork child process"); - } - else if (status == 0) { // child - // Duplicate pipe's reading end into stdin - status = dup2(pfd[0], STDIN_FILENO); - if (status == -1) - perror("dup2"); - - // Close unuseful file descriptors: the pipe's writing and - // reading ends (the latter is not needed anymore, after the - // duplication). - close(pfd[1]); - close(pfd[0]); - - // Find command name: its the substring starting right of the - // rightmost '/' character in the pager pathname. See manpage - // for strrchr. - execlp(report.pager->native_file_string().c_str(), - basename(*report.pager).c_str(), (char *)0); - perror("execl"); - exit(1); - } - else { // parent - close(pfd[0]); - out = new boost::fdostream(pfd[1]); - } - } -#endif - - report.define("ostream", value_t(out)); - - // Are we handling the expr commands? Do so now. - - xml::xpath_t::context_scope_t doc_scope(report, &xml_document); - - if (verb == "expr") { - xml::xpath_t expr(*arg); - - IF_INFO() { - *out << "Value expression tree:" << std::endl; - expr.dump(*out); - *out << std::endl; - *out << "Value expression parsed was:" << std::endl; - expr.print(*out, doc_scope); - *out << std::endl << std::endl; - *out << "Result of calculation: "; - } - - *out << expr.calc(doc_scope).strip_annotations() << std::endl; - - return 0; - } - else if (verb == "xpath") { - std::cout << "XPath parsed: "; - - xml::xpath_t xpath(*arg); - xpath.print(*out, doc_scope); - *out << std::endl; - - IF_INFO() { - *out << "Raw results:" << std::endl; - - foreach (const value_t& value, xpath.find_all(doc_scope)) { - if (value.is_xml_node()) - value.as_xml_node()->print(std::cout); - else - std::cout << value; - std::cout << std::endl; - } - - *out << "Compiled results:" << std::endl; - } - - xml::compile_node(xml_document, report); - - foreach (const value_t& value, xpath.find_all(doc_scope)) { - if (value.is_xml_node()) - value.as_xml_node()->print(std::cout); - else - std::cout << value; - std::cout << std::endl; - } - return 0; - } - - // Apply transforms to the hierarchical document structure - - INFO_START(transforms, "Applied transforms"); - report.apply_transforms(doc_scope); - INFO_FINISH(transforms); - - // Create an argument scope containing the report command's - // arguments, and then invoke the command. - - xml::xpath_t::call_scope_t command_args(doc_scope); - - for (strings_list::iterator i = arg; i != args.end(); i++) - command_args.push_back(value_t(*i, true)); - - INFO_START(command, "Did user command '" << verb << "'"); - - command(command_args); - - INFO_FINISH(command); - - // Write out the binary cache, if need be - - if (session.use_cache && session.cache_dirty && session.cache_file) { - TRACE_START(binary_cache, 1, "Wrote binary journal file"); - -#if 0 - ofstream stream(*session.cache_file); - write_binary_journal(stream, journal); -#endif - - TRACE_FINISH(binary_cache, 1); - } - - // If the user specified a pager, wait for it to exit now - -#ifdef HAVE_UNIX_PIPES - if (! report.output_file && report.pager) { - checked_delete(out); - close(pfd[1]); - - // Wait for child to finish - wait(&status); - if (status & 0xffff != 0) - throw_(std::logic_error, "Something went wrong in the pager"); - } -#endif - else if (DO_VERIFY() && report.output_file) { - checked_delete(out); - } - - return 0; -} - -int main(int argc, char * argv[], char * envp[]) -{ - int status = 1; - - for (int i = 1; i < argc; i++) - if (argv[i][0] == '-') { - if (std::strcmp(argv[i], "--verify") == 0) { -#if defined(VERIFY_ON) - ledger::verify_enabled = true; -#endif - } - else if (std::strcmp(argv[i], "--verbose") == 0 || - std::strcmp(argv[i], "-v") == 0) { -#if defined(LOGGING_ON) - ledger::_log_level = ledger::LOG_INFO; -#endif - } - else if (i + 1 < argc && std::strcmp(argv[i], "--debug") == 0) { -#if defined(DEBUG_ON) - ledger::_log_level = ledger::LOG_DEBUG; - ledger::_log_category = argv[i + 1]; - i++; -#endif - } - else if (i + 1 < argc && std::strcmp(argv[i], "--trace") == 0) { -#if defined(TRACING_ON) - ledger::_log_level = ledger::LOG_TRACE; - ledger::_trace_level = boost::lexical_cast<int>(argv[i + 1]); - i++; -#endif - } - } - - IF_VERIFY() - ledger::initialize_memory_tracing(); - - try { - std::ios::sync_with_stdio(false); - - boost::filesystem::path::default_name_check - (boost::filesystem::portable_posix_name); - - INFO("Ledger starting"); - - std::auto_ptr<ledger::session_t> session(new ledger::session_t); - - ledger::set_session_context(session.get()); - -#if 0 - session->register_parser(new binary_parser_t); -#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE) - session->register_parser(new xml::xml_parser_t); - session->register_parser(new gnucash_parser_t); -#endif -#ifdef HAVE_LIBOFX - session->register_parser(new ofx_parser_t); -#endif - session->register_parser(new qif_parser_t); -#endif - session->register_parser(new ledger::textual_parser_t); - - std::auto_ptr<ledger::report_t> report(new ledger::report_t(*session.get())); - - status = read_and_report(*report.get(), argc, argv, envp); - - if (DO_VERIFY()) { - ledger::set_session_context(); - } else { - report.release(); - session.release(); - } - } -#if 0 - catch (error * err) { - std::cout.flush(); - // Push a null here since there's no file context - if (err->context.empty() || - ! dynamic_cast<xact_context *>(err->context.front())) - err->context.push_front(new error_context("")); - err->reveal_context(std::cerr, "Error"); - std::cerr << err->what() << std::endl; - checked_delete(err); - } - catch (fatal * err) { - std::cout.flush(); - // Push a null here since there's no file context - if (err->context.empty() || - ! dynamic_cast<xact_context *>(err->context.front())) - err->context.push_front(new error_context("")); - err->reveal_context(std::cerr, "Fatal"); - std::cerr << err->what() << std::endl; - checked_delete(err); - } -#endif - catch (const std::exception& err) { - std::cout.flush(); - std::cerr << "Error: " << err.what() << std::endl; - } - catch (int _status) { - status = _status; - } - - IF_VERIFY() { - INFO("Ledger ended (Boost/libstdc++ may still hold memory)"); - ledger::shutdown_memory_tracing(); - } else { - INFO("Ledger ended"); - } - - return status; -} - -// main.cc ends here. diff --git a/src/driver/option.cc b/src/driver/option.cc deleted file mode 100644 index 0b7f7ef9..00000000 --- a/src/driver/option.cc +++ /dev/null @@ -1,222 +0,0 @@ -/* - * Copyright (c) 2003-2007, 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 "option.h" - -namespace ledger { - -namespace { - typedef tuple<xml::xpath_t::ptr_op_t, bool> op_bool_tuple; - - op_bool_tuple find_option(xml::xpath_t::scope_t& scope, const string& name) - { - char buf[128]; - std::strcpy(buf, "option_"); - char * p = &buf[7]; - for (const char * q = name.c_str(); *q; q++) { - if (*q == '-') - *p++ = '_'; - else - *p++ = *q; - } - *p = '\0'; - - xml::xpath_t::ptr_op_t op = scope.lookup(buf); - if (op) - return op_bool_tuple(op, false); - - *p++ = '_'; - *p = '\0'; - - return op_bool_tuple(scope.lookup(buf), true); - } - - op_bool_tuple find_option(xml::xpath_t::scope_t& scope, const char letter) - { - char buf[10]; - std::strcpy(buf, "option_"); - buf[7] = letter; - buf[8] = '\0'; - - xml::xpath_t::ptr_op_t op = scope.lookup(buf); - if (op) - return op_bool_tuple(op, false); - - buf[8] = '_'; - buf[9] = '\0'; - - return op_bool_tuple(scope.lookup(buf), true); - } - - void process_option(const xml::xpath_t::function_t& opt, - xml::xpath_t::scope_t& scope, const char * arg) - { -#if 0 - try { -#endif - xml::xpath_t::call_scope_t args(scope); - if (arg) - args.push_back(value_t(arg, true)); - - opt(args); -#if 0 - } - catch (error * err) { - err->context.push_back - (new error_context - (string("While parsing option '--") + opt->long_opt + - "'" + (opt->short_opt != '\0' ? - (string(" (-") + opt->short_opt + "):") : ":"))); - throw err; - } -#endif - } -} - -void process_option(const string& name, xml::xpath_t::scope_t& scope, - const char * arg) -{ - op_bool_tuple opt(find_option(scope, name)); - if (opt.get<0>()) - process_option(opt.get<0>()->as_function(), scope, arg); -} - -void process_environment(const char ** envp, const string& tag, - xml::xpath_t::scope_t& scope) -{ - const char * tag_p = tag.c_str(); - unsigned int tag_len = tag.length(); - - for (const char ** p = envp; *p; p++) - if (! tag_p || std::strncmp(*p, tag_p, tag_len) == 0) { - char buf[128]; - char * r = buf; - const char * q; - for (q = *p + tag_len; - *q && *q != '=' && r - buf < 128; - q++) - if (*q == '_') - *r++ = '-'; - else - *r++ = std::tolower(*q); - *r = '\0'; - - if (*q == '=') { -#if 0 - try { -#endif - process_option(string(buf), scope, q + 1); -#if 0 - } - catch (error * err) { - err->context.push_back - (new error_context - (string("While parsing environment variable option '") + - *p + "':")); - throw err; - } -#endif - } - } -} - -void process_arguments(int argc, char ** argv, const bool anywhere, - xml::xpath_t::scope_t& scope, - std::list<string>& args) -{ - for (char ** i = argv; *i; i++) { - if ((*i)[0] != '-') { - if (anywhere) { - args.push_back(*i); - continue; - } else { - for (; *i; i++) - args.push_back(*i); - break; - } - } - - // --long-option or -s - if ((*i)[1] == '-') { - if ((*i)[2] == '\0') - break; - - char * name = *i + 2; - char * value = NULL; - if (char * p = std::strchr(name, '=')) { - *p++ = '\0'; - value = p; - } - - op_bool_tuple opt(find_option(scope, name)); - if (! opt.get<0>()) - throw_(option_error, "illegal option --" << name); - - if (opt.get<1>() && value == NULL) { - value = *++i; - if (value == NULL) - throw_(option_error, "missing option argument for --" << name); - } - process_option(opt.get<0>()->as_function(), scope, value); - } - else if ((*i)[1] == '\0') { - throw_(option_error, "illegal option -"); - } - else { - typedef tuple<xml::xpath_t::ptr_op_t, bool, char> op_bool_char_tuple; - - std::list<op_bool_char_tuple> option_queue; - - int x = 1; - for (char c = (*i)[x]; c != '\0'; x++, c = (*i)[x]) { - op_bool_tuple opt(find_option(scope, c)); - if (! opt.get<0>()) - throw_(option_error, "illegal option -" << c); - - option_queue.push_back - (op_bool_char_tuple(opt.get<0>(), opt.get<1>(), c)); - } - - foreach (op_bool_char_tuple& o, option_queue) { - char * value = NULL; - if (o.get<1>()) { - value = *++i; - if (value == NULL) - throw_(option_error, - "missing option argument for -" << o.get<2>()); - } - process_option(o.get<0>()->as_function(), scope, value); - } - } - } -} - -} // namespace ledger diff --git a/src/driver/report.cc b/src/driver/report.cc deleted file mode 100644 index 0404adc7..00000000 --- a/src/driver/report.cc +++ /dev/null @@ -1,224 +0,0 @@ -/* - * Copyright (c) 2003-2007, 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 "report.h" - -namespace ledger { - -report_t::~report_t() -{ - TRACE_DTOR(report_t); -} - -void report_t::apply_transforms(xml::xpath_t::scope_t& scope) -{ - typedef tuple<shared_ptr<transform_t>, value_t> transform_details_tuple; - - foreach (transform_details_tuple& transform_details, transforms) { - xml::xpath_t::call_scope_t call_args(scope); - call_args.set_args(transform_details.get<1>()); - (*transform_details.get<0>())(call_args); - } -} - -value_t report_t::abbrev(xml::xpath_t::call_scope_t& args) -{ - if (args.size() < 2) - throw_(std::logic_error, "usage: abbrev(STRING, WIDTH [, STYLE, ABBREV_LEN])"); - - string str = args[0].as_string(); - long wid = args[1]; - - elision_style_t style = session.elision_style; - if (args.size() == 3) - style = static_cast<elision_style_t>(args[2].as_long()); - - long abbrev_len = session.abbrev_length; - if (args.size() == 4) - abbrev_len = args[3].as_long(); - - return value_t(abbreviate(str, wid, style, true, - static_cast<int>(abbrev_len)), true); -} - -value_t report_t::ftime(xml::xpath_t::call_scope_t& args) -{ - if (args.size() < 1) - throw_(std::logic_error, "usage: ftime(DATE [, DATE_FORMAT])"); - - moment_t date = args[0].as_datetime(); - - string date_format; - if (args.size() == 2) - date_format = args[1].as_string(); -#if 0 - // jww (2007-04-18): Need to setup an output facet here - else - date_format = moment_t::output_format; - - return value_t(date.as_string(date_format), true); -#else - return NULL_VALUE; -#endif -} - -#if 0 -optional<value_t> -report_t::resolve(const string& name, xml::xpath_t::call_scope_t& args) -{ - const char * p = name.c_str(); - switch (*p) { - case 'a': - if (name == "abbrev") { - return abbrev(args); - } - break; - - case 'f': - if (name == "ftime") { - return ftime(args); - } - break; - } - return xml::xpath_t::scope_t::resolve(name, args); -} -#endif - -xml::xpath_t::ptr_op_t report_t::lookup(const string& name) -{ - const char * p = name.c_str(); - switch (*p) { - case 'o': - if (std::strncmp(p, "option_", 7) == 0) { - p = p + 7; - switch (*p) { - case 'a': -#if 0 - if (std::strcmp(p, "accounts") == 0) - return MAKE_FUNCTOR(report_t::option_accounts); - else -#endif - if (std::strcmp(p, "amount") == 0) - return MAKE_FUNCTOR(report_t::option_amount); - break; - - case 'b': - if (std::strcmp(p, "bar") == 0) - return MAKE_FUNCTOR(report_t::option_bar); - break; - -#if 0 - case 'c': - if (std::strcmp(p, "clean") == 0) - return MAKE_FUNCTOR(report_t::option_clean); - else if (std::strcmp(p, "compact") == 0) - return MAKE_FUNCTOR(report_t::option_compact); - break; -#endif - - case 'e': -#if 0 - if (std::strcmp(p, "entries") == 0) - return MAKE_FUNCTOR(report_t::option_entries); - else if (std::strcmp(p, "eval") == 0) - return MAKE_FUNCTOR(report_t::option_eval); - else if (std::strcmp(p, "exclude") == 0) - return MAKE_FUNCTOR(report_t::option_remove); -#endif - break; - - case 'f': -#if 0 - if (std::strcmp(p, "foo") == 0) - return MAKE_FUNCTOR(report_t::option_foo); - else -#endif - if (std::strcmp(p, "format") == 0) - return MAKE_FUNCTOR(report_t::option_format); - break; - - case 'i': -#if 0 - if (std::strcmp(p, "include") == 0) - return MAKE_FUNCTOR(report_t::option_select); -#endif - break; - - case 'l': -#if 0 - if (! *(p + 1) || std::strcmp(p, "limit") == 0) - return MAKE_FUNCTOR(report_t::option_limit); -#endif - break; - -#if 0 - case 'm': - if (std::strcmp(p, "merge") == 0) - return MAKE_FUNCTOR(report_t::option_merge); - break; -#endif - - case 'r': -#if 0 - if (std::strcmp(p, "remove") == 0) - return MAKE_FUNCTOR(report_t::option_remove); -#endif - break; - -#if 0 - case 's': - if (std::strcmp(p, "select") == 0) - return MAKE_FUNCTOR(report_t::option_select); - else if (std::strcmp(p, "split") == 0) - return MAKE_FUNCTOR(report_t::option_split); - break; -#endif - - case 't': - if (! *(p + 1)) - return MAKE_FUNCTOR(report_t::option_amount); - else if (std::strcmp(p, "total") == 0) - return MAKE_FUNCTOR(report_t::option_total); - break; - - case 'T': - if (! *(p + 1)) - return MAKE_FUNCTOR(report_t::option_total); - break; - } - } - break; - } - - return xml::xpath_t::symbol_scope_t::lookup(name); -} - -} // namespace ledger diff --git a/src/driver/report.h b/src/driver/report.h deleted file mode 100644 index b435341b..00000000 --- a/src/driver/report.h +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright (c) 2003-2007, 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. - */ - -#ifndef _REPORT_H -#define _REPORT_H - -#include "session.h" -#include "transform.h" - -namespace ledger { - -typedef std::list<string> strings_list; - -class report_t : public xml::xpath_t::symbol_scope_t -{ -public: - optional<path> output_file; - string format_string; - string amount_expr; - string total_expr; - string date_output_format; - - unsigned long budget_flags; - - string account; - optional<path> pager; - - bool show_totals; - bool raw_mode; - - session_t& session; - transform_t * last_transform; - - std::list<tuple<shared_ptr<transform_t>, value_t> > transforms; - - explicit report_t(session_t& _session) - : xml::xpath_t::symbol_scope_t(downcast<xml::xpath_t::scope_t>(_session)), - show_totals(false), - raw_mode(false), - session(_session), - last_transform(NULL) - { - TRACE_CTOR(report_t, "session_t&"); -#if 0 - eval("t=total,TOT=0,T()=(TOT=TOT+t,TOT)"); -#endif - } - - virtual ~report_t(); - - void apply_transforms(xml::xpath_t::scope_t& scope); - - // - // Utility functions for value expressions - // - - value_t ftime(xml::xpath_t::call_scope_t& args); - value_t abbrev(xml::xpath_t::call_scope_t& args); - - // - // Config options - // - - void eval(const string& expr) { -#if 0 - xml::xpath_t(expr).compile((xml::document_t *)NULL, this); -#endif - } - value_t option_eval(xml::xpath_t::call_scope_t& args) { - eval(args[0].as_string()); - return NULL_VALUE; - } - - value_t option_amount(xml::xpath_t::call_scope_t& args) { - eval(string("t=") + args[0].as_string()); - return NULL_VALUE; - } - value_t option_total(xml::xpath_t::call_scope_t& args) { - eval(string("T()=") + args[0].as_string()); - return NULL_VALUE; - } - - value_t option_format(xml::xpath_t::call_scope_t& args) { - format_string = args[0].as_string(); - return NULL_VALUE; - } - - value_t option_raw(xml::xpath_t::call_scope_t& args) { - raw_mode = true; - return NULL_VALUE; - } - - value_t option_foo(xml::xpath_t::call_scope_t& args) { - std::cout << "This is foo" << std::endl; - return NULL_VALUE; - } - value_t option_bar(xml::xpath_t::call_scope_t& args) { - std::cout << "This is bar: " << args[0] << std::endl; - return NULL_VALUE; - } - - // - // Transform options - // - -#if 0 - value_t option_select(xml::xpath_t::call_scope_t& args) { - transforms.push_back(new select_transform(args[0].as_string())); - return NULL_VALUE; - } - value_t option_limit(xml::xpath_t::call_scope_t& args) { - string expr = (string("//xact[") + - args[0].as_string() + "]"); - transforms.push_back(new select_transform(expr)); - return NULL_VALUE; - } - - value_t option_remove(xml::xpath_t::call_scope_t& args) { - transforms.push_back(new remove_transform(args[0].as_string())); - return NULL_VALUE; - } - - value_t option_accounts(xml::xpath_t::call_scope_t& args) { - transforms.push_back(new accounts_transform); - return NULL_VALUE; - } - value_t option_compact(xml::xpath_t::call_scope_t& args) { - transforms.push_back(new compact_transform); - return NULL_VALUE; - } - value_t option_clean(xml::xpath_t::call_scope_t& args) { - transforms.push_back(new clean_transform); - return NULL_VALUE; - } - value_t option_entries(xml::xpath_t::call_scope_t& args) { - transforms.push_back(new entries_transform); - return NULL_VALUE; - } - - value_t option_split(xml::xpath_t::call_scope_t& args) { - transforms.push_back(new split_transform); - return NULL_VALUE; - } - value_t option_merge(xml::xpath_t::call_scope_t& args) { - transforms.push_back(new merge_transform); - return NULL_VALUE; - } -#endif - - // - // Scope members - // - - virtual xml::xpath_t::ptr_op_t lookup(const string& name); -}; - -string abbrev(const string& str, unsigned int width, - const bool is_account); - -} // namespace ledger - -#endif // _REPORT_H diff --git a/src/driver/session.cc b/src/driver/session.cc deleted file mode 100644 index 98ae9613..00000000 --- a/src/driver/session.cc +++ /dev/null @@ -1,313 +0,0 @@ -/* - * Copyright (c) 2003-2007, 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 "session.h" - -namespace ledger { - -session_t * session_t::current = NULL; - -#if 0 -boost::mutex session_t::session_mutex; -#endif - -static void initialize(); -static void shutdown(); - -void set_session_context(session_t * session) -{ -#if 0 - session_t::session_mutex.lock(); -#endif - - if (session && ! session_t::current) { - initialize(); - } - else if (! session && session_t::current) { - shutdown(); -#if 0 - session_t::session_mutex.unlock(); -#endif - } - - session_t::current = session; -} - -void release_session_context() -{ -#if 0 - session_t::session_mutex.unlock(); -#endif -} - -session_t::session_t() - : symbol_scope_t(), - - register_format - ("%((//entry)%{date} %-.20{payee}" - "%((./xact)%32|%-22{abbrev(account, 22)} %12.67t %12.80T\n))"), - wide_register_format - ("%D %-.35P %-.38A %22.108t %!22.132T\n%/" - "%48|%-.38A %22.108t %!22.132T\n"), - print_format -#if 1 - ("%(/%(/%{date} %-.20{payee}\n%(: %-34{account} %12t\n)\n))"), -#else - ("\n%d %Y%C%P\n %-34W %12o%n\n%/ %-34W %12o%n\n"), -#endif - balance_format - ("%(/%(//%20t %{\" \" * rdepth}%{rname}\n))--------------------\n%20t\n"), - equity_format - - ("%((/)%{ftime(now, date_format)} %-.20{\"Opening Balance\"}\n%((.//account[value != 0]) %-34{fullname} %12{value}\n)\n)"), - plot_amount_format - ("%D %(@S(@t))\n"), - plot_total_format - ("%D %(@S(@T))\n"), - write_hdr_format - ("%d %Y%C%P\n"), - write_xact_format - (" %-34W %12o%n\n"), - prices_format - ("%[%Y/%m/%d %H:%M:%S %Z] %-10A %12t %12T\n"), - pricesdb_format - ("P %[%Y/%m/%d %H:%M:%S] %A %t\n"), - - pricing_leeway(24 * 3600), - - download_quotes(false), - use_cache(false), - cache_dirty(false), - - now(now), - - elision_style(ABBREVIATE), - abbrev_length(2), - - ansi_codes(false), - ansi_invert(false) -{ - TRACE_CTOR(session_t, "xml::xpath_t::scope_t&"); -} - -std::size_t session_t::read_journal(std::istream& in, - const path& pathname, - xml::builder_t& builder) -{ -#if 0 - if (! master) - master = journal->master; -#endif - - foreach (parser_t& parser, parsers) - if (parser.test(in)) - return parser.parse(in, pathname, builder); - - return 0; -} - -std::size_t session_t::read_journal(const path& pathname, - xml::builder_t& builder) -{ -#if 0 - journal->sources.push_back(pathname); -#endif - - if (! exists(pathname)) - throw_(std::logic_error, "Cannot read file" << pathname); - - ifstream stream(pathname); - return read_journal(stream, pathname, builder); -} - -void session_t::read_init() -{ - if (! init_file) - return; - - if (! exists(*init_file)) - throw_(std::logic_error, "Cannot read init file" << *init_file); - - ifstream init(*init_file); - - // jww (2006-09-15): Read initialization options here! -} - -std::size_t session_t::read_data(xml::builder_t& builder, - journal_t * journal, - const string& master_account) -{ - if (data_file.empty()) - throw_(parse_error, "No journal file was specified (please use -f)"); - - TRACE_START(parser, 1, "Parsing journal file"); - - std::size_t entry_count = 0; - - DEBUG("ledger.cache", "3. use_cache = " << use_cache); - - if (use_cache && cache_file) { - DEBUG("ledger.cache", "using_cache " << cache_file->string()); - cache_dirty = true; - if (exists(*cache_file)) { - push_variable<optional<path> > - save_price_db(journal->price_db, price_db); - - entry_count += read_journal(*cache_file, builder); - if (entry_count > 0) - cache_dirty = false; - } - } - - if (entry_count == 0) { - account_t * acct = NULL; - if (! master_account.empty()) - acct = journal->find_account(master_account); - - journal->price_db = price_db; - if (journal->price_db && exists(*journal->price_db)) { - if (read_journal(*journal->price_db, builder)) { - throw_(parse_error, "Entries not allowed in price history file"); - } else { - DEBUG("ledger.cache", - "read price database " << journal->price_db->string()); - journal->sources.pop_back(); - } - } - - DEBUG("ledger.cache", "rejected cache, parsing " << data_file.string()); - if (data_file == "-") { - use_cache = false; - journal->sources.push_back("<stdin>"); - entry_count += read_journal(std::cin, "<stdin>", builder); - } - else if (exists(data_file)) { - entry_count += read_journal(data_file, builder); - if (journal->price_db) - journal->sources.push_back(*journal->price_db); - } - } - - VERIFY(journal->valid()); - - TRACE_STOP(parser, 1); - - return entry_count; -} - -#if 0 -optional<value_t> -session_t::resolve(const string& name, xml::xpath_t::scope_t& locals) -{ - const char * p = name.c_str(); - switch (*p) { - case 'd': -#if 0 - if (name == "date_format") { - // jww (2007-04-18): What to do here? - return value_t(moment_t::output_format, true); - } -#endif - break; - - case 'n': - switch (*++p) { - case 'o': - if (name == "now") - return value_t(now); - break; - } - break; - - case 'r': - if (name == "register_format") - return value_t(register_format, true); - break; - } - return xml::xpath_t::scope_t::resolve(name, locals); -} -#endif - -xml::xpath_t::ptr_op_t session_t::lookup(const string& name) -{ - const char * p = name.c_str(); - switch (*p) { - case 'o': - if (std::strncmp(p, "option_", 7) == 0) { - p = p + 7; - switch (*p) { - case 'd': - if (std::strcmp(p, "debug_") == 0) - return MAKE_FUNCTOR(session_t::option_debug_); - break; - - case 'f': - if ((*(p + 1) == '_' && ! *(p + 2)) || - std::strcmp(p, "file_") == 0) - return MAKE_FUNCTOR(session_t::option_file_); - break; - - case 't': - if (std::strcmp(p, "trace_") == 0) - return MAKE_FUNCTOR(session_t::option_trace_); - break; - - case 'v': - if (! *(p + 1) || std::strcmp(p, "verbose") == 0) - return MAKE_FUNCTOR(session_t::option_verbose); - else if (std::strcmp(p, "verify") == 0) - return MAKE_FUNCTOR(session_t::option_verify); - break; - } - } - break; - } - - return xml::xpath_t::symbol_scope_t::lookup(name); -} - -// jww (2007-04-26): All of Ledger should be accessed through a -// session_t object -static void initialize() -{ - amount_t::initialize(); - value_t::initialize(); - xml::xpath_t::initialize(); -} - -static void shutdown() -{ - xml::xpath_t::shutdown(); - value_t::shutdown(); - amount_t::shutdown(); -} - -} // namespace ledger diff --git a/src/driver/session.h b/src/driver/session.h deleted file mode 100644 index 43d7d722..00000000 --- a/src/driver/session.h +++ /dev/null @@ -1,196 +0,0 @@ -/* - * Copyright (c) 2003-2007, 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. - */ - -#ifndef _SESSION_H -#define _SESSION_H - -#include "xpath.h" -#include "journal.h" -#include "parser.h" -#include "abbrev.h" - -namespace ledger { - -class session_t : public xml::xpath_t::symbol_scope_t -{ - public: - static session_t * current; - - path data_file; - optional<path> init_file; - optional<path> cache_file; - optional<path> price_db; - - string register_format; - string wide_register_format; - string print_format; - string balance_format; - string equity_format; - string plot_amount_format; - string plot_total_format; - string write_hdr_format; - string write_xact_format; - string prices_format; - string pricesdb_format; - - unsigned long pricing_leeway; - - bool download_quotes; - bool use_cache; - bool cache_dirty; - - moment_t now; - - elision_style_t elision_style; - - int abbrev_length; - - bool ansi_codes; - bool ansi_invert; - - ptr_list<journal_t> journals; - ptr_list<parser_t> parsers; - - session_t(); - virtual ~session_t() { - TRACE_DTOR(session_t); - } - - journal_t * create_journal() { - journal_t * journal = new journal_t; - journals.push_back(journal); - return journal; - } - void close_journal(journal_t * journal) { - for (ptr_list<journal_t>::iterator i = journals.begin(); - i != journals.end(); - i++) - if (&*i == journal) { - journals.erase(i); - return; - } - assert(false); - checked_delete(journal); - } - - std::size_t read_journal(std::istream& in, - const path& pathname, - xml::builder_t& builder); - std::size_t read_journal(const path& pathname, - xml::builder_t& builder); - - void read_init(); - - std::size_t read_data(xml::builder_t& builder, - journal_t * journal, - const string& master_account = ""); - - void register_parser(parser_t * parser) { - parsers.push_back(parser); - } - void unregister_parser(parser_t * parser) { - for (ptr_list<parser_t>::iterator i = parsers.begin(); - i != parsers.end(); - i++) - if (&*i == parser) { - parsers.erase(i); - return; - } - assert(false); - checked_delete(parser); - } - - // - // Scope members - // - - virtual xml::xpath_t::ptr_op_t lookup(const string& name); - - // - // Debug options - // - - value_t option_trace_(xml::xpath_t::scope_t& locals) { - return NULL_VALUE; - } - value_t option_debug_(xml::xpath_t::scope_t& locals) { - return NULL_VALUE; - } - - value_t option_verify(xml::xpath_t::scope_t&) { - return NULL_VALUE; - } - value_t option_verbose(xml::xpath_t::scope_t&) { -#if defined(LOGGING_ON) - if (_log_level < LOG_INFO) - _log_level = LOG_INFO; -#endif - return NULL_VALUE; - } - - // - // Option handlers - // - - value_t option_file_(xml::xpath_t::call_scope_t& args) { - assert(args.size() == 1); - data_file = args[0].as_string(); - return NULL_VALUE; - } - -#if 0 -#if defined(USE_BOOST_PYTHON) - value_t option_import_(xml::xpath_t::call_scope_t& args) { - python_import(optarg); - return NULL_VALUE; - } - value_t option_import_stdin(xml::xpath_t::call_scope_t& args) { - python_eval(std::cin, PY_EVAL_MULTI); - return NULL_VALUE; - } -#endif -#endif -}; - -/** - * This sets the current session context, transferring all static - * globals to point at the data structures related to this session. - * Although Ledger itself is not thread-safe, by locking, switching - * session context, then unlocking after the operation is done, - * multiple threads can sequentially make use of the library. Thus, a - * session_t maintains all of the information relating to a single - * usage of the Ledger library. - */ -void set_session_context(session_t * session = NULL); - -} // namespace ledger - -#endif // _SESSION_H diff --git a/src/emacs.cc b/src/emacs.cc new file mode 100644 index 00000000..dc1a18ae --- /dev/null +++ b/src/emacs.cc @@ -0,0 +1,108 @@ +/* + * 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 "emacs.h" +#include "xact.h" +#include "post.h" +#include "account.h" + +namespace ledger { + +void format_emacs_posts::write_xact(xact_t& xact) +{ + out << "\"" << xact.pos->pathname << "\" " + << xact.pos->beg_line << " "; + + tm when = gregorian::to_tm(xact.date()); + std::time_t date = std::mktime(&when); + + out << "(" << (date / 65536) << " " << (date % 65536) << " 0) "; + + if (! xact.code) + out << "nil "; + else + out << "\"" << *xact.code << "\" "; + + if (xact.payee.empty()) + out << "nil"; + else + out << "\"" << xact.payee << "\""; + + out << "\n"; +} + +void format_emacs_posts::operator()(post_t& post) +{ + if (! post.has_xdata() || + ! post.xdata().has_flags(POST_EXT_DISPLAYED)) { + if (! last_xact) { + out << "(("; + write_xact(*post.xact); + } + else if (post.xact != last_xact) { + out << ")\n ("; + write_xact(*post.xact); + } + else { + out << "\n"; + } + + out << " (" << post.pos->beg_line << " "; + out << "\"" << post.reported_account()->fullname() << "\" \"" + << post.amount << "\""; + + switch (post.state()) { + case item_t::CLEARED: + out << " t"; + break; + case item_t::PENDING: + out << " pending"; + break; + default: + out << " nil"; + break; + } + + if (post.cost) + out << " \"" << *post.cost << "\""; + if (post.note) + out << " \"" << *post.note << "\""; + out << ")"; + + last_xact = post.xact; + + post.xdata().add_flags(POST_EXT_DISPLAYED); + } +} + +} // namespace ledger diff --git a/src/emacs.h b/src/emacs.h new file mode 100644 index 00000000..4b2a452a --- /dev/null +++ b/src/emacs.h @@ -0,0 +1,79 @@ +/* + * 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. + */ + +/** + * @addtogroup report + */ + +/** + * @file emacs.h + * @author John Wiegley + * + * @ingroup report + */ +#ifndef _EMACS_H +#define _EMACS_H + +#include "chain.h" + +namespace ledger { + +class xact_t; + +class format_emacs_posts : public item_handler<post_t> +{ + format_emacs_posts(); + +protected: + std::ostream& out; + xact_t * last_xact; + +public: + format_emacs_posts(std::ostream& _out) + : out(_out), last_xact(NULL) { + TRACE_CTOR(format_emacs_posts, "std::ostream&"); + } + ~format_emacs_posts() { + TRACE_DTOR(format_emacs_posts); + } + + virtual void write_xact(xact_t& xact); + virtual void flush() { + if (last_xact) + out << "))\n"; + out.flush(); + } + virtual void operator()(post_t& post); +}; + +} // namespace ledger + +#endif // _REPORT_H diff --git a/src/error.cc b/src/error.cc new file mode 100644 index 00000000..d5abe4de --- /dev/null +++ b/src/error.cc @@ -0,0 +1,118 @@ +/* + * 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 "utils.h" + +namespace ledger { + +straccstream _ctxt_accum; +std::ostringstream _ctxt_buffer; +straccstream _desc_accum; +std::ostringstream _desc_buffer; + +string error_context() +{ + string context = _ctxt_buffer.str(); + _ctxt_buffer.str(""); + return context; +} + +string file_context(const path& file, const std::size_t line) +{ + std::ostringstream buf; + buf << "\"" << file << "\", line " << line << ": "; + return buf.str(); +} + +string line_context(const string& line, + const string::size_type pos, + const string::size_type end_pos) +{ + std::ostringstream buf; + buf << " " << line << "\n"; + + if (pos != 0) { + buf << " "; + if (end_pos == 0) { + for (string::size_type i = 0; i < pos; i += 1) + buf << " "; + buf << "^"; + } else { + for (string::size_type i = 0; i < end_pos; i += 1) { + if (i >= pos) + buf << "^"; + else + buf << " "; + } + } + } + return buf.str(); +} + +string source_context(const path& file, + const istream_pos_type pos, + const istream_pos_type end_pos, + const string& prefix) +{ + const std::streamoff len = end_pos - pos; + if (! len || file == path("/dev/stdin")) + return _("<no source context>"); + + assert(len > 0); + assert(len < 2048); + + std::ostringstream out; + + ifstream in(file); + in.seekg(pos, std::ios::beg); + + scoped_array<char> buf(new char[static_cast<std::size_t>(len) + 1]); + in.read(buf.get(), static_cast<std::streamsize>(len)); + assert(in.gcount() == static_cast<std::streamsize>(len)); + buf[static_cast<std::size_t>(len)] = '\0'; + + bool first = true; + for (char * p = std::strtok(buf.get(), "\n"); + p; + p = std::strtok(NULL, "\n")) { + if (first) + first = false; + else + out << '\n'; + out << prefix << p; + } + + return out.str(); +} + +} // namespace ledger diff --git a/src/error.h b/src/error.h new file mode 100644 index 00000000..5369faf1 --- /dev/null +++ b/src/error.h @@ -0,0 +1,104 @@ +/* + * 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. + */ + +/** + * @addtogroup util + */ + +/** + * @file error.h + * @author John Wiegley + * + * @ingroup util + */ +#ifndef _ERROR_H +#define _ERROR_H + +#include "accum.h" + +namespace ledger { + +extern straccstream _desc_accum; +extern std::ostringstream _desc_buffer; + +template <typename T> +inline void throw_func(const string& message) { + _desc_buffer.str(""); + throw T(message); +} + +#define throw_(cls, msg) \ + ((_desc_buffer << ACCUM(_desc_accum << msg)), \ + _desc_accum.clear(), \ + throw_func<cls>(_desc_buffer.str())) + +inline void warning_func(const string& message) { + std::cerr << "Warning: " << message << std::endl; + _desc_buffer.str(""); +} + +#define warning_(msg) \ + ((_desc_buffer << ACCUM(_desc_accum << msg)), \ + _desc_accum.clear(), \ + warning_func(_desc_buffer.str())) + +extern straccstream _ctxt_accum; +extern std::ostringstream _ctxt_buffer; + +#define add_error_context(msg) \ + ((long(_ctxt_buffer.tellp()) == 0) ? \ + ((_ctxt_buffer << ACCUM(_ctxt_accum << msg)), \ + _ctxt_accum.clear()) : \ + ((_ctxt_buffer << std::endl << ACCUM(_ctxt_accum << msg)), \ + _ctxt_accum.clear())) + +string error_context(); + +string file_context(const path& file, std::size_t line); +string line_context(const string& line, + const string::size_type pos = 0, + const string::size_type end_pos = 0); + +string source_context(const path& file, + const istream_pos_type pos, + const istream_pos_type end_pos, + const string& prefix = ""); + +#define DECLARE_EXCEPTION(name, kind) \ + class name : public kind { \ + public: \ + explicit name(const string& why) throw() : kind(why) {} \ + virtual ~name() throw() {} \ + } + +} // namespace ledger + +#endif // _ERROR_H diff --git a/src/expr.cc b/src/expr.cc new file mode 100644 index 00000000..c59f8a57 --- /dev/null +++ b/src/expr.cc @@ -0,0 +1,143 @@ +/* + * 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 "expr.h" +#include "parser.h" + +namespace ledger { + +void expr_t::parse(std::istream& in, const parse_flags_t& flags, + const optional<string>& original_string) +{ + base_type::parse(in, flags, original_string); + + parser_t parser; + ptr = parser.parse(in, flags, original_string); +} + +void expr_t::compile(scope_t& scope) +{ + if (! compiled && ptr) { + ptr = ptr->compile(scope); + base_type::compile(scope); + } +} + +value_t expr_t::real_calc(scope_t& scope) +{ + if (ptr) { + ptr_op_t locus; + try { + return ptr->calc(scope, &locus); + } + catch (const std::exception& err) { + if (locus) { + add_error_context(_("While evaluating value expression:")); + add_error_context(op_context(ptr, locus)); + + if (SHOW_INFO()) { + add_error_context(_("The value expression tree was:")); + std::ostringstream buf; + ptr->dump(buf, 0); + + std::istringstream in(buf.str()); + std::ostringstream out; + char linebuf[1024]; + bool first = true; + while (in.good() && ! in.eof()) { + in.getline(linebuf, 1023); + std::streamsize len = in.gcount(); + if (len > 0) { + if (first) + first = false; + else + out << '\n'; + out << " " << linebuf; + } + } + add_error_context(out.str()); + } + } + throw; + } + } + return NULL_VALUE; +} + +bool expr_t::is_constant() const +{ + assert(compiled); + return ptr && ptr->is_value(); +} + +bool expr_t::is_function() const +{ + assert(compiled); + return ptr && ptr->is_function(); +} + +value_t& expr_t::constant_value() +{ + assert(is_constant()); + return ptr->as_value_lval(); +} + +const value_t& expr_t::constant_value() const +{ + assert(is_constant()); + return ptr->as_value(); +} + +expr_t::func_t& expr_t::get_function() +{ + assert(is_function()); + return ptr->as_function_lval(); +} + +string expr_t::context_to_str() const +{ + return ptr ? op_context(ptr) : _("<empty expression>"); +} + +void expr_t::print(std::ostream& out) const +{ + if (ptr) + ptr->print(out); +} + +void expr_t::dump(std::ostream& out) const +{ + if (ptr) ptr->dump(out, 0); +} + +} // namespace ledger diff --git a/src/expr.h b/src/expr.h new file mode 100644 index 00000000..638855b1 --- /dev/null +++ b/src/expr.h @@ -0,0 +1,147 @@ +/* + * 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. + */ + +/** + * @addtogroup expr + */ + +/** + * @file expr.h + * @author John Wiegley + * + * @ingroup expr + */ +#ifndef _EXPR_H +#define _EXPR_H + +#include "exprbase.h" +#include "value.h" + +namespace ledger { + +class expr_t : public expr_base_t<value_t> +{ + struct token_t; + class parser_t; + + typedef expr_base_t<value_t> base_type; + +public: + class op_t; + typedef intrusive_ptr<op_t> ptr_op_t; + typedef intrusive_ptr<const op_t> const_ptr_op_t; + +protected: + ptr_op_t ptr; + +public: + expr_t() : base_type() { + TRACE_CTOR(expr_t, ""); + } + expr_t(const expr_t& other) + : base_type(other), ptr(other.ptr) { + TRACE_CTOR(expr_t, "copy"); + } + expr_t(ptr_op_t _ptr, scope_t * _context = NULL) + : base_type(_context), ptr(_ptr) { + TRACE_CTOR(expr_t, "const ptr_op_t&, scope_t *"); + } + + expr_t(const string& _str, const parse_flags_t& flags = PARSE_DEFAULT) + : base_type() { + TRACE_CTOR(expr_t, "string, parse_flags_t"); + if (! _str.empty()) + parse(_str, flags); + } + expr_t(std::istream& in, const parse_flags_t& flags = PARSE_DEFAULT) + : base_type() { + TRACE_CTOR(expr_t, "std::istream&, parse_flags_t"); + parse(in, flags); + } + + virtual ~expr_t() { + TRACE_DTOR(expr_t); + } + + expr_t& operator=(const expr_t& _expr) { + if (this != &_expr) { + base_type::operator=(_expr); + ptr = _expr.ptr; + } + return *this; + } + + virtual operator bool() const throw() { + return ptr.get() != NULL; + } + + ptr_op_t get_op() throw() { + return ptr; + } + + void parse(const string& str, const parse_flags_t& flags = PARSE_DEFAULT) { + std::istringstream stream(str); + return parse(stream, flags, str); + } + + virtual void parse(std::istream& in, + const parse_flags_t& flags = PARSE_DEFAULT, + const optional<string>& original_string = none); + virtual void compile(scope_t& scope); + virtual value_t real_calc(scope_t& scope); + + bool is_constant() const; + value_t& constant_value(); + const value_t& constant_value() const; + bool is_function() const; + func_t& get_function(); + + virtual string context_to_str() const; + virtual void print(std::ostream& out) const; + virtual void dump(std::ostream& out) const; + +#if defined(HAVE_BOOST_SERIALIZATION) +private: + /** Serialization. */ + + friend class boost::serialization::access; + + template<class Archive> + void serialize(Archive& ar, const unsigned int /* version */) { + ar & boost::serialization::base_object<base_type>(*this); + ar & ptr; + } +#endif // HAVE_BOOST_SERIALIZATION +}; + +} // namespace ledger + +#endif // _EXPR_H diff --git a/src/exprbase.h b/src/exprbase.h new file mode 100644 index 00000000..0b3466b0 --- /dev/null +++ b/src/exprbase.h @@ -0,0 +1,253 @@ +/* + * 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. + */ + +/** + * @addtogroup expr + */ + +/** + * @file exprbase.h + * @author John Wiegley + * + * @ingroup expr + * + * This class provides basic behavior for all the domain specific expression + * languages used in Leger: + * + * | Typename | Description | result_type | Derives | + * |-------------+----------------------------+-----------------+-------------| + * | expr_t | Value expressions | value_t | | + * | predicate_t | Special form of expr_t | bool | expr_t | + * | query_t | Report queries | bool | predicate_t | + * | period_t | Time periods and durations | date_interval_t | | + * | draft_t | Partially filled xacts | xact_t * | | + * | format_t | Format strings | string | | + */ +#ifndef _EXPRBASE_H +#define _EXPRBASE_H + +#include "utils.h" +#include "amount.h" + +namespace ledger { + +DECLARE_EXCEPTION(parse_error, std::runtime_error); +DECLARE_EXCEPTION(compile_error, std::runtime_error); +DECLARE_EXCEPTION(calc_error, std::runtime_error); +DECLARE_EXCEPTION(usage_error, std::runtime_error); + +class scope_t; +class call_scope_t; + +template <typename ResultType> +class expr_base_t +{ +public: + typedef ResultType result_type; + + typedef function<result_type (call_scope_t&)> func_t; + +protected: + scope_t * context; + string str; + bool compiled; + + virtual result_type real_calc(scope_t& scope) = 0; + +public: + expr_base_t(const expr_base_t& other) + : context(other.context), str(other.str), compiled(false) { + TRACE_CTOR(expr_base_t, "copy"); + } + expr_base_t(scope_t * _context = NULL) + : context(_context), compiled(false) + { + TRACE_CTOR(expr_base_t, "scope_t *"); + } + virtual ~expr_base_t() { + TRACE_DTOR(expr_base_t); + } + + expr_base_t& operator=(const expr_base_t& _expr) { + if (this != &_expr) { + str = _expr.str; + context = _expr.context; + compiled = _expr.compiled; + } + return *this; + } + expr_base_t& operator=(const string& _expr) { + parse(_expr); + return *this; + } + + virtual operator bool() const throw() { + return ! str.empty(); + } + + virtual string text() { + return str; + } + void set_text(const string& txt) { + str = txt; + compiled = false; + } + + void parse(const string& str, const parse_flags_t& flags = PARSE_DEFAULT) { + std::istringstream stream(str); + return parse(stream, flags, str); + } + virtual void parse(std::istream&, + const parse_flags_t& = PARSE_DEFAULT, + const optional<string>& original_string = none) { + set_text(original_string ? *original_string : "<stream>"); + } + + void mark_uncompiled() { + compiled = false; + } + + void recompile(scope_t& scope) { + compiled = false; + compile(scope); + } + + virtual void compile(scope_t& scope) { + if (! compiled) { + // Derived classes need to do something here. + context = &scope; + compiled = true; + } + } + + result_type operator()(scope_t& scope) { + return calc(scope); + } + + result_type calc(scope_t& scope) + { + if (! compiled) { + if (SHOW_DEBUG("expr.compile")) { + DEBUG("expr.compile", "Before compilation:"); + dump(*_log_stream); + } + + compile(scope); + + if (SHOW_DEBUG("expr.compile")) { + DEBUG("expr.compile", "After compilation:"); + dump(*_log_stream); + } + } + + return real_calc(scope); + } + + result_type calc() { + assert(context); + return calc(*context); + } + + scope_t * get_context() { + return context; + } + void set_context(scope_t * scope) { + context = scope; + } + + virtual string context_to_str() const { + return empty_string; + } + + string print_to_str() const { + std::ostringstream out; + print(out); + return out.str(); + } + string dump_to_str() const { + std::ostringstream out; + dump(out); + return out.str(); + } + string preview_to_str(scope_t& scope) const { + std::ostringstream out; + preview(out); + return out.str(); + } + + virtual void print(std::ostream&) const {} + virtual void dump(std::ostream&) const {} + + result_type preview(std::ostream& out, scope_t& scope) const { + out << _("--- Input expression ---") << std::endl; + out << text() << std::endl; + + out << std::endl << _("--- Text as parsed ---") << std::endl; + print(out); + out << std::endl; + + out << std::endl << _("--- Expression tree ---") << std::endl; + dump(out); + + out << std::endl << _("--- Compiled tree ---") << std::endl; + compile(scope); + dump(out); + + out << std::endl << _("--- Result value ---") << std::endl; + return calc(); + } + +#if defined(HAVE_BOOST_SERIALIZATION) +private: + /** Serialization. */ + + friend class boost::serialization::access; + + template<class Archive> + void serialize(Archive& ar, const unsigned int /* version */) { + ar & context; + ar & str; + if (Archive::is_loading::value) + compiled = false; + } +#endif // HAVE_BOOST_SERIALIZATION +}; + +template <typename ResultType> +std::ostream& operator<<(std::ostream& out, + const expr_base_t<ResultType>& expr) { + expr.print(out); + return out; +} + +} // namespace ledger + +#endif // _EXPRBASE_H diff --git a/src/filters.cc b/src/filters.cc new file mode 100644 index 00000000..0084fac7 --- /dev/null +++ b/src/filters.cc @@ -0,0 +1,1055 @@ +/* + * 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 "filters.h" +#include "iterators.h" +#include "journal.h" +#include "report.h" +#include "compare.h" + +namespace ledger { + +pass_down_posts::pass_down_posts(post_handler_ptr handler, + posts_iterator& iter) + : item_handler<post_t>(handler) +{ + TRACE_CTOR(pass_down_posts, "post_handler_ptr, posts_iterator"); + + for (post_t * post = iter(); post; post = iter()) { + try { + item_handler<post_t>::operator()(*post); + } + catch (const std::exception& err) { + add_error_context(item_context(*post, _("While handling posting"))); + throw; + } + } + + item_handler<post_t>::flush(); +} + +void truncate_xacts::flush() +{ + if (! posts.size()) + return; + + xact_t * xact = (*posts.begin())->xact; + + int l = 0; + foreach (post_t * post, posts) + if (xact != post->xact) { + l++; + xact = post->xact; + } + l++; + + xact = (*posts.begin())->xact; + + int i = 0; + foreach (post_t * post, posts) { + if (xact != post->xact) { + xact = post->xact; + i++; + } + + bool print = false; + if (head_count) { + if (head_count > 0 && i < head_count) + print = true; + else if (head_count < 0 && i >= - head_count) + print = true; + } + + if (! print && tail_count) { + if (tail_count > 0 && l - i <= tail_count) + print = true; + else if (tail_count < 0 && l - i > - tail_count) + print = true; + } + + if (print) + item_handler<post_t>::operator()(*post); + } + posts.clear(); + + item_handler<post_t>::flush(); +} + +void truncate_xacts::operator()(post_t& post) +{ + if (last_xact != post.xact) { + if (last_xact) + xacts_seen++; + last_xact = post.xact; + } + + if (tail_count == 0 && head_count > 0 && + static_cast<int>(xacts_seen) >= head_count) + return; + + posts.push_back(&post); +} + +void sort_posts::post_accumulated_posts() +{ + std::stable_sort(posts.begin(), posts.end(), + compare_items<post_t>(sort_order)); + + foreach (post_t * post, posts) { + post->xdata().drop_flags(POST_EXT_SORT_CALC); + item_handler<post_t>::operator()(*post); + } + + posts.clear(); +} + +namespace { + void split_string(const string& str, const char ch, + std::list<string>& strings) + { + const char * b = str.c_str(); + for (const char * p = b; *p; p++) { + if (*p == ch) { + strings.push_back(string(b, p - b)); + b = p + 1; + } + } + strings.push_back(string(b)); + } + + account_t * create_temp_account_from_path(std::list<string>& account_names, + temporaries_t& temps, + account_t * master) + { + account_t * new_account = NULL; + foreach (const string& name, account_names) { + if (new_account) { + new_account = new_account->find_account(name); + } else { + new_account = master->find_account(name, false); + if (! new_account) + new_account = &temps.create_account(name, master); + } + } + + assert(new_account != NULL); + return new_account; + } +} + +void anonymize_posts::operator()(post_t& post) +{ + SHA1 sha; + uint_least32_t message_digest[5]; + bool copy_xact_details = false; + + if (last_xact != post.xact) { + temps.copy_xact(*post.xact); + last_xact = post.xact; + copy_xact_details = true; + } + xact_t& xact = temps.last_xact(); + + if (copy_xact_details) { + xact.copy_details(*post.xact); + + sha.Reset(); + sha << post.xact->payee.c_str(); + sha.Result(message_digest); + + xact.payee = to_hex(message_digest); + xact.note = none; + } + + std::list<string> account_names; + + for (account_t * acct = post.account; + acct; + acct = acct->parent) { + sha.Reset(); + sha << acct->name.c_str(); + sha.Result(message_digest); + + account_names.push_front(to_hex(message_digest)); + } + + account_t * new_account = + create_temp_account_from_path(account_names, temps, xact.journal->master); + post_t& temp = temps.copy_post(post, xact, new_account); + temp.note = none; + + (*handler)(temp); +} + +void calc_posts::operator()(post_t& post) +{ + post_t::xdata_t& xdata(post.xdata()); + + if (last_post) { + assert(last_post->has_xdata()); + if (calc_running_total) + xdata.total = last_post->xdata().total; + xdata.count = last_post->xdata().count + 1; + } else { + xdata.count = 1; + } + + post.add_to_value(xdata.visited_value, amount_expr); + xdata.add_flags(POST_EXT_VISITED); + + account_t * acct = post.reported_account(); + acct->xdata().add_flags(ACCOUNT_EXT_VISITED); + + if (calc_running_total) + add_or_set_value(xdata.total, xdata.visited_value); + + item_handler<post_t>::operator()(post); + + last_post = &post; +} + +namespace { + typedef function<void (post_t&)> post_functor_t; + + void handle_value(const value_t& value, + account_t * account, + xact_t * xact, + temporaries_t& temps, + post_handler_ptr handler, + const date_t& date = date_t(), + const value_t& total = value_t(), + const bool direct_amount = false, + const bool mark_visited = false, + const optional<post_functor_t>& functor = none) + { + post_t& post = temps.create_post(*xact, account); + post.add_flags(ITEM_GENERATED); + + // If the account for this post is all virtual, then report the post as + // such. This allows subtotal reports to show "(Account)" for accounts + // that contain only virtual posts. + if (account && account->has_xdata() && + account->xdata().has_flags(ACCOUNT_EXT_AUTO_VIRTUALIZE)) { + if (! account->xdata().has_flags(ACCOUNT_EXT_HAS_NON_VIRTUALS)) { + post.add_flags(POST_VIRTUAL); + if (! account->xdata().has_flags(ACCOUNT_EXT_HAS_UNB_VIRTUALS)) + post.add_flags(POST_MUST_BALANCE); + } + } + + post_t::xdata_t& xdata(post.xdata()); + + if (is_valid(date)) + xdata.date = date; + + value_t temp(value); + + switch (value.type()) { + case value_t::BOOLEAN: + case value_t::INTEGER: + temp.in_place_cast(value_t::AMOUNT); + // fall through... + + case value_t::AMOUNT: + post.amount = temp.as_amount(); + break; + + case value_t::BALANCE: + case value_t::SEQUENCE: + xdata.compound_value = temp; + xdata.add_flags(POST_EXT_COMPOUND); + break; + + case value_t::DATETIME: + case value_t::DATE: + default: + assert(false); + break; + } + + if (! total.is_null()) + xdata.total = total; + + if (direct_amount) + xdata.add_flags(POST_EXT_DIRECT_AMT); + + if (functor) + (*functor)(post); + + DEBUG("filter.changed_value.rounding", "post.amount = " << post.amount); + + (*handler)(post); + + if (mark_visited) { + post.xdata().add_flags(POST_EXT_VISITED); + post.account->xdata().add_flags(ACCOUNT_EXT_VISITED); + } + } +} + +void collapse_posts::report_subtotal() +{ + if (! count) + return; + + std::size_t displayed_count = 0; + foreach (post_t * post, component_posts) { + if (only_predicate(*post) && display_predicate(*post)) + displayed_count++; + } + + if (displayed_count == 1) { + item_handler<post_t>::operator()(*last_post); + } + else if (only_collapse_if_zero && ! subtotal.is_zero()) { + foreach (post_t * post, component_posts) + item_handler<post_t>::operator()(*post); + } + else { + date_t earliest_date; + + foreach (post_t * post, component_posts) { + date_t reported = post->date(); + if (! is_valid(earliest_date) || + reported < earliest_date) + earliest_date = reported; + } + + xact_t& xact = temps.create_xact(); + xact.payee = last_xact->payee; + xact._date = (is_valid(earliest_date) ? + earliest_date : last_xact->_date); + DEBUG("filter.collapse", "Pseudo-xact date = " << *xact._date); + + handle_value(subtotal, &totals_account, &xact, temps, handler); + } + + component_posts.clear(); + + last_xact = NULL; + last_post = NULL; + subtotal = 0L; + count = 0; +} + +void collapse_posts::operator()(post_t& post) +{ + // If we've reached a new xact, report on the subtotal + // accumulated thus far. + + if (last_xact != post.xact && count > 0) + report_subtotal(); + + post.add_to_value(subtotal, amount_expr); + + component_posts.push_back(&post); + + last_xact = post.xact; + last_post = &post; + count++; +} + +void related_posts::flush() +{ + if (posts.size() > 0) { + foreach (post_t * post, posts) { + if (post->xact) { + foreach (post_t * r_post, post->xact->posts) { + post_t::xdata_t& xdata(r_post->xdata()); + if (! xdata.has_flags(POST_EXT_HANDLED) && + (! xdata.has_flags(POST_EXT_RECEIVED) ? + ! r_post->has_flags(ITEM_GENERATED | POST_VIRTUAL) : + also_matching)) { + xdata.add_flags(POST_EXT_HANDLED); + item_handler<post_t>::operator()(*r_post); + } + } + } else { + // This code should only be reachable from the "output" + // command, since that is the only command which attempts to + // output auto or period xacts. + post_t::xdata_t& xdata(post->xdata()); + if (! xdata.has_flags(POST_EXT_HANDLED) && + ! post->has_flags(ITEM_GENERATED)) { + xdata.add_flags(POST_EXT_HANDLED); + item_handler<post_t>::operator()(*post); + } + } + } + } + + item_handler<post_t>::flush(); +} + +changed_value_posts::changed_value_posts(post_handler_ptr handler, + report_t& _report, + bool _for_accounts_report, + bool _show_unrealized) + : item_handler<post_t>(handler), report(_report), + for_accounts_report(_for_accounts_report), + show_unrealized(_show_unrealized), last_post(NULL), + revalued_account(temps.create_account(_("<Revalued>"))), + rounding_account(temps.create_account(_("<Rounding>"))) +{ + TRACE_CTOR(changed_value_posts, "post_handler_ptr, report_t&, bool"); + + display_amount_expr = report.HANDLER(display_amount_).expr; + total_expr = (report.HANDLED(revalued_total_) ? + report.HANDLER(revalued_total_).expr : + report.HANDLER(display_total_).expr); + display_total_expr = report.HANDLER(display_total_).expr; + changed_values_only = report.HANDLED(revalued_only); + + string gains_equity_account_name; + if (report.HANDLED(unrealized_gains_)) + gains_equity_account_name = report.HANDLER(unrealized_gains_).str(); + else + gains_equity_account_name = _("Equity:Unrealized Gains"); + gains_equity_account = + report.session.journal->master->find_account(gains_equity_account_name); + gains_equity_account->add_flags(ACCOUNT_GENERATED); + + string losses_equity_account_name; + if (report.HANDLED(unrealized_losses_)) + losses_equity_account_name = report.HANDLER(unrealized_losses_).str(); + else + losses_equity_account_name = _("Equity:Unrealized Losses"); + losses_equity_account = + report.session.journal->master->find_account(losses_equity_account_name); + losses_equity_account->add_flags(ACCOUNT_GENERATED); +} + +void changed_value_posts::flush() +{ + if (last_post && last_post->date() <= report.terminus.date()) { + output_revaluation(*last_post, report.terminus.date()); + last_post = NULL; + } + item_handler<post_t>::flush(); +} + +void changed_value_posts::output_revaluation(post_t& post, const date_t& date) +{ + if (is_valid(date)) + post.xdata().date = date; + + value_t repriced_total; + try { + bind_scope_t bound_scope(report, post); + repriced_total = total_expr.calc(bound_scope); + } + catch (...) { + post.xdata().date = date_t(); + throw; + } + post.xdata().date = date_t(); + + DEBUG("filter.changed_value", + "output_revaluation(last_balance) = " << last_total); + DEBUG("filter.changed_value", + "output_revaluation(repriced_total) = " << repriced_total); + + if (! last_total.is_null()) { + if (value_t diff = repriced_total - last_total) { + DEBUG("filter.changed_value", "output_revaluation(strip(diff)) = " + << diff.strip_annotations(report.what_to_keep())); + + xact_t& xact = temps.create_xact(); + xact.payee = _("Commodities revalued"); + xact._date = is_valid(date) ? date : post.date(); + + if (! for_accounts_report) { + handle_value + (/* value= */ diff, + /* account= */ &revalued_account, + /* xact= */ &xact, + /* temps= */ temps, + /* handler= */ handler, + /* date= */ *xact._date, + /* total= */ repriced_total, + /* direct_amount= */ false, + /* mark_visited= */ false, + /* functor= */ (optional<post_functor_t> + (bind(&changed_value_posts::output_rounding, + this, _1)))); + } + else if (show_unrealized) { + handle_value + (/* value= */ - diff, + /* account= */ (diff < 0L ? + losses_equity_account : + gains_equity_account), + /* xact= */ &xact, + /* temps= */ temps, + /* handler= */ handler, + /* date= */ *xact._date, + /* total= */ value_t(), + /* direct_amount= */ false, + /* mark_visited= */ true); + } + } + } +} + +void changed_value_posts::output_rounding(post_t& post) +{ + bind_scope_t bound_scope(report, post); + value_t new_display_total(display_total_expr.calc(bound_scope)); + + DEBUG("filter.changed_value.rounding", + "rounding.new_display_total = " << new_display_total); + + if (! last_display_total.is_null()) { + if (value_t repriced_amount = display_amount_expr.calc(bound_scope)) { + DEBUG("filter.changed_value.rounding", + "rounding.repriced_amount = " << repriced_amount); + + value_t precise_display_total(new_display_total.truncated() - + repriced_amount.truncated()); + + DEBUG("filter.changed_value.rounding", + "rounding.precise_display_total = " << precise_display_total); + DEBUG("filter.changed_value.rounding", + "rounding.last_display_total = " << last_display_total); + + if (value_t diff = precise_display_total - last_display_total) { + DEBUG("filter.changed_value.rounding", + "rounding.diff = " << diff); + + xact_t& xact = temps.create_xact(); + xact.payee = _("Commodity rounding"); + xact._date = post.date(); + + handle_value(diff, &rounding_account, &xact, temps, handler, + *xact._date, precise_display_total, true); + } + } + } + last_display_total = new_display_total; +} + +void changed_value_posts::operator()(post_t& post) +{ + if (last_post) + output_revaluation(*last_post, post.date()); + + if (changed_values_only) + post.xdata().add_flags(POST_EXT_DISPLAYED); + + if (! for_accounts_report) + output_rounding(post); + + item_handler<post_t>::operator()(post); + + bind_scope_t bound_scope(report, post); + last_total = total_expr.calc(bound_scope); + last_post = &post; +} + +void subtotal_posts::report_subtotal(const char * spec_fmt, + const optional<date_interval_t>& interval) +{ + if (component_posts.empty()) + return; + + optional<date_t> range_start = interval ? interval->start : none; + optional<date_t> range_finish = interval ? interval->inclusive_end() : none; + + if (! range_start || ! range_finish) { + foreach (post_t * post, component_posts) { + date_t date = post->date(); + if (! range_start || date < *range_start) + range_start = date; + if (! range_finish || date > *range_finish) + range_finish = date; + } + } + component_posts.clear(); + + std::ostringstream out_date; + if (spec_fmt) { + out_date << format_date(*range_finish, FMT_CUSTOM, spec_fmt); + } + else if (date_format) { + out_date << "- " << format_date(*range_finish, FMT_CUSTOM, + date_format->c_str()); + } + else { + out_date << "- " << format_date(*range_finish); + } + + xact_t& xact = temps.create_xact(); + xact.payee = out_date.str(); + xact._date = *range_start; + + foreach (values_map::value_type& pair, values) + handle_value(pair.second.value, pair.second.account, &xact, temps, + handler); + + values.clear(); +} + +void subtotal_posts::operator()(post_t& post) +{ + component_posts.push_back(&post); + + account_t * acct = post.reported_account(); + assert(acct); + + values_map::iterator i = values.find(acct->fullname()); + if (i == values.end()) { + value_t temp; + post.add_to_value(temp, amount_expr); + std::pair<values_map::iterator, bool> result + = values.insert(values_pair(acct->fullname(), acct_value_t(acct, temp))); + assert(result.second); + } else { + post.add_to_value((*i).second.value, amount_expr); + } + + // If the account for this post is all virtual, mark it as + // such, so that `handle_value' can show "(Account)" for accounts + // that contain only virtual posts. + + post.reported_account()->xdata().add_flags(ACCOUNT_EXT_AUTO_VIRTUALIZE); + + if (! post.has_flags(POST_VIRTUAL)) + post.reported_account()->xdata().add_flags(ACCOUNT_EXT_HAS_NON_VIRTUALS); + else if (! post.has_flags(POST_MUST_BALANCE)) + post.reported_account()->xdata().add_flags(ACCOUNT_EXT_HAS_UNB_VIRTUALS); +} + +void interval_posts::report_subtotal(const date_interval_t& interval) +{ + if (last_post && interval) { + if (exact_periods) + subtotal_posts::report_subtotal(); + else + subtotal_posts::report_subtotal(NULL, interval); + } + + last_post = NULL; +} + +void interval_posts::operator()(post_t& post) +{ + date_t date = post.date(); + + if (! interval.find_period(post.date())) + return; + + if (interval.duration) { + if (last_interval && interval != last_interval) { + report_subtotal(last_interval); + + if (generate_empty_posts) { + for (++last_interval; interval != last_interval; ++last_interval) { + // Generate a null posting, so the intervening periods can be + // seen when -E is used, or if the calculated amount ends up being + // non-zero + xact_t& null_xact = temps.create_xact(); + null_xact._date = last_interval.inclusive_end(); + + post_t& null_post = temps.create_post(null_xact, &empty_account); + null_post.add_flags(POST_CALCULATED); + null_post.amount = 0L; + + last_post = &null_post; + subtotal_posts::operator()(null_post); + + report_subtotal(last_interval); + } + assert(interval == last_interval); + } else { + last_interval = interval; + } + } else { + last_interval = interval; + } + subtotal_posts::operator()(post); + } else { + item_handler<post_t>::operator()(post); + } + + last_post = &post; +} + +void posts_as_equity::report_subtotal() +{ + date_t finish; + foreach (post_t * post, component_posts) { + date_t date = post->date(); + if (! is_valid(finish) || date > finish) + finish = date; + } + component_posts.clear(); + + xact_t& xact = temps.create_xact(); + xact.payee = _("Opening Balances"); + xact._date = finish; + + value_t total = 0L; + foreach (values_map::value_type& pair, values) { + if (pair.second.value.is_balance()) { + foreach (balance_t::amounts_map::value_type amount_pair, + pair.second.value.as_balance().amounts) + handle_value(amount_pair.second, pair.second.account, &xact, temps, + handler); + } else { + handle_value(pair.second.value, pair.second.account, &xact, temps, + handler); + } + total += pair.second.value; + } + values.clear(); + + if (total.is_balance()) { + foreach (balance_t::amounts_map::value_type pair, + total.as_balance().amounts) { + post_t& balance_post = temps.create_post(xact, balance_account); + balance_post.amount = - pair.second; + (*handler)(balance_post); + } + } else { + post_t& balance_post = temps.create_post(xact, balance_account); + balance_post.amount = - total.to_amount(); + (*handler)(balance_post); + } +} + +void by_payee_posts::flush() +{ + foreach (payee_subtotals_map::value_type& pair, payee_subtotals) + pair.second->report_subtotal(pair.first.c_str()); + + item_handler<post_t>::flush(); + + payee_subtotals.clear(); +} + +void by_payee_posts::operator()(post_t& post) +{ + payee_subtotals_map::iterator i = payee_subtotals.find(post.xact->payee); + if (i == payee_subtotals.end()) { + payee_subtotals_pair + temp(post.xact->payee, + shared_ptr<subtotal_posts>(new subtotal_posts(handler, amount_expr))); + std::pair<payee_subtotals_map::iterator, bool> result + = payee_subtotals.insert(temp); + + assert(result.second); + if (! result.second) + return; + i = result.first; + } + + (*(*i).second)(post); +} + +void transfer_details::operator()(post_t& post) +{ + xact_t& xact = temps.copy_xact(*post.xact); + xact._date = post.date(); + + post_t& temp = temps.copy_post(post, xact); + temp.set_state(post.state()); + + bind_scope_t bound_scope(scope, temp); + value_t substitute(expr.calc(bound_scope)); + + switch (which_element) { + case SET_DATE: + temp.xdata().date = substitute.to_date(); + break; + + case SET_ACCOUNT: { + std::list<string> account_names; + temp.account->remove_post(&temp); + split_string(substitute.to_string(), ':', account_names); + temp.account = create_temp_account_from_path(account_names, temps, + xact.journal->master); + temp.account->add_post(&temp); + break; + } + + case SET_PAYEE: + xact.payee = substitute.to_string(); + break; + + default: + assert(false); + break; + } + + item_handler<post_t>::operator()(temp); +} + +void dow_posts::flush() +{ + for (int i = 0; i < 7; i++) { + foreach (post_t * post, days_of_the_week[i]) + subtotal_posts::operator()(*post); + subtotal_posts::report_subtotal("%As"); + days_of_the_week[i].clear(); + } + + subtotal_posts::flush(); +} + +void generate_posts::add_period_xacts(period_xacts_list& period_xacts) +{ + foreach (period_xact_t * xact, period_xacts) + foreach (post_t * post, xact->posts) + add_post(xact->period, *post); +} + +void generate_posts::add_post(const date_interval_t& period, post_t& post) +{ + pending_posts.push_back(pending_posts_pair(period, &post)); +} + +void budget_posts::report_budget_items(const date_t& date) +{ + if (pending_posts.size() == 0) + return; + + bool reported; + do { + reported = false; + foreach (pending_posts_list::value_type& pair, pending_posts) { + optional<date_t> begin = pair.first.start; + if (! begin) { + if (! pair.first.find_period(date)) + throw_(std::runtime_error, _("Something odd has happened")); + begin = pair.first.start; + } + assert(begin); + + if (*begin <= date && + (! pair.first.finish || *begin < *pair.first.finish)) { + post_t& post = *pair.second; + + DEBUG("budget.generate", "Reporting budget for " + << post.reported_account()->fullname()); + + xact_t& xact = temps.create_xact(); + xact.payee = _("Budget transaction"); + xact._date = begin; + + post_t& temp = temps.copy_post(post, xact); + temp.amount.in_place_negate(); + + if (flags & BUDGET_WRAP_VALUES) { + value_t seq; + seq.push_back(0L); + seq.push_back(temp.amount); + + temp.xdata().compound_value = seq; + temp.xdata().add_flags(POST_EXT_COMPOUND); + } + + ++pair.first; + begin = *pair.first.start; + + item_handler<post_t>::operator()(temp); + + reported = true; + } + } + } while (reported); +} + +void budget_posts::operator()(post_t& post) +{ + bool post_in_budget = false; + + foreach (pending_posts_list::value_type& pair, pending_posts) { + for (account_t * acct = post.reported_account(); + acct; + acct = acct->parent) { + if (acct == (*pair.second).reported_account()) { + post_in_budget = true; + // Report the post as if it had occurred in the parent account. + if (post.reported_account() != acct) + post.set_reported_account(acct); + goto handle; + } + } + } + + handle: + if (post_in_budget && flags & BUDGET_BUDGETED) { + report_budget_items(post.date()); + item_handler<post_t>::operator()(post); + } + else if (! post_in_budget && flags & BUDGET_UNBUDGETED) { + item_handler<post_t>::operator()(post); + } +} + +void forecast_posts::add_post(const date_interval_t& period, post_t& post) +{ + generate_posts::add_post(period, post); + + // Advance the period's interval until it is at or beyond the current date. + date_interval_t& i = pending_posts.back().first; + if (! i.start) { + if (! i.find_period(CURRENT_DATE())) + throw_(std::runtime_error, _("Something odd has happened")); + ++i; + } else { + while (*i.start < CURRENT_DATE()) + ++i; + } +} + +void forecast_posts::flush() +{ + posts_list passed; + date_t last = CURRENT_DATE(); + + // If there are period transactions to apply in a continuing series until + // the forecast condition is met, generate those transactions now. Note + // that no matter what, we abandon forecasting beyond the next 5 years. + // + // It works like this: + // + // Earlier, in forecast_posts::add_period_xacts, we cut up all the periodic + // transactions into their components postings, so that we have N "periodic + // postings". For example, if the user had this: + // + // ~ daily + // Expenses:Food $10 + // Expenses:Auto:Gas $20 + // ~ monthly + // Expenses:Food $100 + // Expenses:Auto:Gas $200 + // + // We now have 4 periodic postings in `pending_posts'. + // + // Each periodic postings gets its own copy of its parent transaction's + // period, which is modified as we go. This is found in the second member + // of the pending_posts_list for each posting. + // + // The algorithm below works by iterating through the N periodic postings + // over and over, until each of them mets the termination critera for the + // forecast and is removed from the set. + + while (pending_posts.size() > 0) { + // At each step through the loop, we find the first periodic posting whose + // period contains the earliest starting date. + pending_posts_list::iterator least = pending_posts.begin(); + for (pending_posts_list::iterator i = ++pending_posts.begin(); + i != pending_posts.end(); + i++) { + if (*(*i).first.start < *(*least).first.start) + least = i; + } + + date_t& begin = *(*least).first.start; + if ((*least).first.finish) + assert(begin < *(*least).first.finish); + + // If the next date in the series for this periodic posting is more than 5 + // years beyond the last valid post we generated, drop it from further + // consideration. + date_t next = *(*least).first.next; + assert(next > begin); + + if (static_cast<std::size_t>((next - last).days()) > + static_cast<std::size_t>(365U) * forecast_years) { + DEBUG("filters.forecast", + "Forecast transaction exceeds " << forecast_years + << " years beyond today"); + pending_posts.erase(least); + continue; + } + + begin = next; + ++(*least).first; + + // `post' refers to the posting defined in the period transaction. We + // make a copy of it within a temporary transaction with the payee + // "Forecast transaction". + post_t& post = *(*least).second; + xact_t& xact = temps.create_xact(); + xact.payee = _("Forecast transaction"); + xact._date = begin; + post_t& temp = temps.copy_post(post, xact); + + // Submit the generated posting + DEBUG("filters.forecast", + "Forecast transaction: " << temp.date() + << " " << temp.account->fullname() + << " " << temp.amount); + item_handler<post_t>::operator()(temp); + + // If the generated posting matches the user's report query, check whether + // it also fails to match the continuation condition for the forecast. If + // it does, drop this periodic posting from consideration. + if (temp.has_xdata() && temp.xdata().has_flags(POST_EXT_MATCHES)) { + DEBUG("filters.forecast", " matches report query"); + bind_scope_t bound_scope(context, temp); + if (! pred(bound_scope)) { + DEBUG("filters.forecast", " fails to match continuation criteria"); + pending_posts.erase(least); + continue; + } + } + } + + item_handler<post_t>::flush(); +} + +pass_down_accounts::pass_down_accounts(acct_handler_ptr handler, + accounts_iterator& iter, + const optional<predicate_t>& _pred, + const optional<scope_t&>& _context) + : item_handler<account_t>(handler), pred(_pred), context(_context) +{ + TRACE_CTOR(pass_down_accounts, "acct_handler_ptr, accounts_iterator, ..."); + + for (account_t * account = iter(); account; account = iter()) { + if (! pred) { + item_handler<account_t>::operator()(*account); + } else { + bind_scope_t bound_scope(*context, *account); + if ((*pred)(bound_scope)) + item_handler<account_t>::operator()(*account); + } + } + + item_handler<account_t>::flush(); +} + +} // namespace ledger diff --git a/src/filters.h b/src/filters.h new file mode 100644 index 00000000..92148dbe --- /dev/null +++ b/src/filters.h @@ -0,0 +1,721 @@ +/* + * 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. + */ + +/** + * @addtogroup report + */ + +/** + * @file filters.h + * @author John Wiegley + * + * @ingroup report + */ +#ifndef _FILTERS_H +#define _FILTERS_H + +#include "chain.h" +#include "xact.h" +#include "post.h" +#include "account.h" +#include "temps.h" + +namespace ledger { + +////////////////////////////////////////////////////////////////////// +// +// Posting filters +// + +class ignore_posts : public item_handler<post_t> +{ +public: + virtual void operator()(post_t&) {} +}; + +class collect_posts : public item_handler<post_t> +{ +public: + std::vector<post_t *> posts; + + collect_posts() : item_handler<post_t>() { + TRACE_CTOR(collect_posts, ""); + } + virtual ~collect_posts() { + TRACE_DTOR(collect_posts); + } + + std::size_t length() const { + return posts.size(); + } + + std::vector<post_t *>::iterator begin() { + return posts.begin(); + } + std::vector<post_t *>::iterator end() { + return posts.end(); + } + + virtual void flush() {} + virtual void operator()(post_t& post) { + posts.push_back(&post); + } +}; + +class posts_iterator; + +class pass_down_posts : public item_handler<post_t> +{ + pass_down_posts(); + +public: + pass_down_posts(post_handler_ptr handler, posts_iterator& iter); + + virtual ~pass_down_posts() { + TRACE_DTOR(pass_down_posts); + } +}; + +class push_to_posts_list : public item_handler<post_t> +{ + push_to_posts_list(); + +public: + posts_list& posts; + + push_to_posts_list(posts_list& _posts) : posts(_posts) { + TRACE_CTOR(push_to_posts_list, "posts_list&"); + } + virtual ~push_to_posts_list() { + TRACE_DTOR(push_to_posts_list); + } + + virtual void operator()(post_t& post) { + posts.push_back(&post); + } +}; + +class truncate_xacts : public item_handler<post_t> +{ + int head_count; + int tail_count; + + posts_list posts; + std::size_t xacts_seen; + xact_t * last_xact; + + truncate_xacts(); + +public: + truncate_xacts(post_handler_ptr handler, + int _head_count, int _tail_count) + : item_handler<post_t>(handler), + head_count(_head_count), tail_count(_tail_count), + xacts_seen(0), last_xact(NULL) { + TRACE_CTOR(truncate_xacts, "post_handler_ptr, int, int"); + } + virtual ~truncate_xacts() { + TRACE_DTOR(truncate_xacts); + } + + virtual void flush(); + virtual void operator()(post_t& post); +}; + +class sort_posts : public item_handler<post_t> +{ + typedef std::deque<post_t *> posts_deque; + + posts_deque posts; + const expr_t sort_order; + + sort_posts(); + +public: + sort_posts(post_handler_ptr handler, + const expr_t& _sort_order) + : item_handler<post_t>(handler), + sort_order(_sort_order) { + TRACE_CTOR(sort_posts, + "post_handler_ptr, const value_expr&"); + } + sort_posts(post_handler_ptr handler, + const string& _sort_order) + : item_handler<post_t>(handler), + sort_order(_sort_order) { + TRACE_CTOR(sort_posts, + "post_handler_ptr, const string&"); + } + virtual ~sort_posts() { + TRACE_DTOR(sort_posts); + } + + virtual void post_accumulated_posts(); + + virtual void flush() { + post_accumulated_posts(); + item_handler<post_t>::flush(); + } + + virtual void operator()(post_t& post) { + posts.push_back(&post); + } +}; + +class sort_xacts : public item_handler<post_t> +{ + sort_posts sorter; + xact_t * last_xact; + + sort_xacts(); + +public: + sort_xacts(post_handler_ptr handler, + const expr_t& _sort_order) + : sorter(handler, _sort_order) { + TRACE_CTOR(sort_xacts, + "post_handler_ptr, const value_expr&"); + } + sort_xacts(post_handler_ptr handler, + const string& _sort_order) + : sorter(handler, _sort_order) { + TRACE_CTOR(sort_xacts, + "post_handler_ptr, const string&"); + } + virtual ~sort_xacts() { + TRACE_DTOR(sort_xacts); + } + + virtual void flush() { + sorter.flush(); + item_handler<post_t>::flush(); + } + + virtual void operator()(post_t& post) { + if (last_xact && post.xact != last_xact) + sorter.post_accumulated_posts(); + + sorter(post); + + last_xact = post.xact; + } +}; + +class filter_posts : public item_handler<post_t> +{ + predicate_t pred; + scope_t& context; + + filter_posts(); + +public: + filter_posts(post_handler_ptr handler, + const predicate_t& predicate, + scope_t& _context) + : item_handler<post_t>(handler), pred(predicate), context(_context) { + TRACE_CTOR(filter_posts, "post_handler_ptr, predicate_t, scope_t&"); + } + virtual ~filter_posts() { + TRACE_DTOR(filter_posts); + } + + virtual void operator()(post_t& post) { + bind_scope_t bound_scope(context, post); + if (pred(bound_scope)) { + post.xdata().add_flags(POST_EXT_MATCHES); + (*handler)(post); + } + } +}; + +class anonymize_posts : public item_handler<post_t> +{ + temporaries_t temps; + xact_t * last_xact; + + anonymize_posts(); + +public: + anonymize_posts(post_handler_ptr handler) + : item_handler<post_t>(handler), last_xact(NULL) { + TRACE_CTOR(anonymize_posts, "post_handler_ptr"); + } + virtual ~anonymize_posts() { + TRACE_DTOR(anonymize_posts); + } + + virtual void operator()(post_t& post); +}; + +class calc_posts : public item_handler<post_t> +{ + post_t * last_post; + expr_t& amount_expr; + bool calc_running_total; + + calc_posts(); + +public: + calc_posts(post_handler_ptr handler, + expr_t& _amount_expr, + bool _calc_running_total = false) + : item_handler<post_t>(handler), last_post(NULL), + amount_expr(_amount_expr), calc_running_total(_calc_running_total) { + TRACE_CTOR(calc_posts, "post_handler_ptr, expr_t&, bool"); + } + virtual ~calc_posts() { + TRACE_DTOR(calc_posts); + } + + virtual void operator()(post_t& post); +}; + +class collapse_posts : public item_handler<post_t> +{ + expr_t& amount_expr; + predicate_t display_predicate; + predicate_t only_predicate; + value_t subtotal; + std::size_t count; + xact_t * last_xact; + post_t * last_post; + temporaries_t temps; + account_t& totals_account; + bool only_collapse_if_zero; + std::list<post_t *> component_posts; + + collapse_posts(); + +public: + collapse_posts(post_handler_ptr handler, + expr_t& _amount_expr, + predicate_t _display_predicate, + predicate_t _only_predicate, + bool _only_collapse_if_zero = false) + : item_handler<post_t>(handler), amount_expr(_amount_expr), + display_predicate(_display_predicate), + only_predicate(_only_predicate), count(0), + last_xact(NULL), last_post(NULL), + totals_account(temps.create_account(_("<Total>"))), + only_collapse_if_zero(_only_collapse_if_zero) { + TRACE_CTOR(collapse_posts, "post_handler_ptr"); + } + virtual ~collapse_posts() { + TRACE_DTOR(collapse_posts); + } + + virtual void flush() { + report_subtotal(); + item_handler<post_t>::flush(); + } + + void report_subtotal(); + + virtual void operator()(post_t& post); +}; + +class related_posts : public item_handler<post_t> +{ + posts_list posts; + bool also_matching; + + related_posts(); + +public: + related_posts(post_handler_ptr handler, + const bool _also_matching = false) + : item_handler<post_t>(handler), + also_matching(_also_matching) { + TRACE_CTOR(related_posts, + "post_handler_ptr, const bool"); + } + virtual ~related_posts() throw() { + TRACE_DTOR(related_posts); + } + + virtual void flush(); + virtual void operator()(post_t& post) { + post.xdata().add_flags(POST_EXT_RECEIVED); + posts.push_back(&post); + } +}; + +class changed_value_posts : public item_handler<post_t> +{ + // This filter requires that calc_posts be used at some point + // later in the chain. + + expr_t display_amount_expr; + expr_t total_expr; + expr_t display_total_expr; + report_t& report; + bool changed_values_only; + bool for_accounts_report; + bool show_unrealized; + post_t * last_post; + value_t last_total; + value_t last_display_total; + temporaries_t temps; + account_t& revalued_account; + account_t& rounding_account; + account_t * gains_equity_account; + account_t * losses_equity_account; + + changed_value_posts(); + +public: + changed_value_posts(post_handler_ptr handler, + report_t& _report, + bool _for_accounts_report, + bool _show_unrealized); + + virtual ~changed_value_posts() { + TRACE_DTOR(changed_value_posts); + } + + virtual void flush(); + + void output_revaluation(post_t& post, const date_t& current); + void output_rounding(post_t& post); + + virtual void operator()(post_t& post); +}; + +class subtotal_posts : public item_handler<post_t> +{ + subtotal_posts(); + +protected: + class acct_value_t + { + acct_value_t(); + + public: + account_t * account; + value_t value; + + acct_value_t(account_t * a) : account(a) { + TRACE_CTOR(acct_value_t, "account_t *"); + } + acct_value_t(account_t * a, value_t& v) : account(a), value(v) { + TRACE_CTOR(acct_value_t, "account_t *, value_t&"); + } + acct_value_t(const acct_value_t& av) + : account(av.account), value(av.value) { + TRACE_CTOR(acct_value_t, "copy"); + } + ~acct_value_t() throw() { + TRACE_DTOR(acct_value_t); + } + }; + + typedef std::map<string, acct_value_t> values_map; + typedef std::pair<string, acct_value_t> values_pair; + +protected: + expr_t& amount_expr; + values_map values; + optional<string> date_format; + temporaries_t temps; + std::list<post_t *> component_posts; + +public: + subtotal_posts(post_handler_ptr handler, expr_t& _amount_expr, + const optional<string>& _date_format = none) + : item_handler<post_t>(handler), amount_expr(_amount_expr), + date_format(_date_format) { + TRACE_CTOR(subtotal_posts, + "post_handler_ptr, expr_t&, const optional<string>&"); + } + virtual ~subtotal_posts() { + TRACE_DTOR(subtotal_posts); + } + + void report_subtotal(const char * spec_fmt = NULL, + const optional<date_interval_t>& interval = none); + + virtual void flush() { + if (values.size() > 0) + report_subtotal(); + item_handler<post_t>::flush(); + } + virtual void operator()(post_t& post); +}; + +class interval_posts : public subtotal_posts +{ + date_interval_t interval; + date_interval_t last_interval; + post_t * last_post; + account_t& empty_account; + bool exact_periods; + bool generate_empty_posts; + + interval_posts(); + +public: + + interval_posts(post_handler_ptr _handler, + expr_t& amount_expr, + const date_interval_t& _interval, + bool _exact_periods = false, + bool _generate_empty_posts = false) + : subtotal_posts(_handler, amount_expr), interval(_interval), + last_post(NULL), empty_account(temps.create_account(_("<None>"))), + exact_periods(_exact_periods), + generate_empty_posts(_generate_empty_posts) { + TRACE_CTOR(interval_posts, + "post_handler_ptr, expr_t&, date_interval_t, bool, bool"); + } + virtual ~interval_posts() throw() { + TRACE_DTOR(interval_posts); + } + + void report_subtotal(const date_interval_t& interval); + + virtual void flush() { + if (last_post && interval.duration) { + if (interval.is_valid()) + report_subtotal(interval); + subtotal_posts::flush(); + } + } + virtual void operator()(post_t& post); +}; + +class posts_as_equity : public subtotal_posts +{ + post_t * last_post; + account_t& equity_account; + account_t * balance_account; + + posts_as_equity(); + +public: + posts_as_equity(post_handler_ptr _handler, expr_t& amount_expr) + : subtotal_posts(_handler, amount_expr), + equity_account(temps.create_account(_("Equity"))) { + TRACE_CTOR(posts_as_equity, "post_handler_ptr, expr_t&"); + balance_account = equity_account.find_account(_("Opening Balances")); + } + virtual ~posts_as_equity() throw() { + TRACE_DTOR(posts_as_equity); + } + + void report_subtotal(); + + virtual void flush() { + report_subtotal(); + subtotal_posts::flush(); + } +}; + +class by_payee_posts : public item_handler<post_t> +{ + typedef std::map<string, shared_ptr<subtotal_posts> > payee_subtotals_map; + typedef std::pair<string, shared_ptr<subtotal_posts> > payee_subtotals_pair; + + expr_t& amount_expr; + payee_subtotals_map payee_subtotals; + + by_payee_posts(); + + public: + by_payee_posts(post_handler_ptr handler, expr_t& _amount_expr) + : item_handler<post_t>(handler), amount_expr(_amount_expr) { + TRACE_CTOR(by_payee_posts, "post_handler_ptr, expr_t&"); + } + virtual ~by_payee_posts() { + TRACE_DTOR(by_payee_posts); + } + + virtual void flush(); + virtual void operator()(post_t& post); +}; + +class transfer_details : public item_handler<post_t> +{ + account_t * master; + expr_t expr; + scope_t& scope; + temporaries_t temps; + + transfer_details(); + +public: + enum element_t { + SET_DATE, + SET_ACCOUNT, + SET_PAYEE + } which_element; + + transfer_details(post_handler_ptr handler, + element_t _which_element, + account_t * _master, + const expr_t& _expr, + scope_t& _scope) + : item_handler<post_t>(handler), master(_master), + expr(_expr), scope(_scope), which_element(_which_element) { + TRACE_CTOR(transfer_details, + "post_handler_ptr, element_t, account_t *, expr_t, scope_t&"); + } + virtual ~transfer_details() { + TRACE_DTOR(transfer_details); + } + + virtual void operator()(post_t& post); +}; + +class dow_posts : public subtotal_posts +{ + posts_list days_of_the_week[7]; + + dow_posts(); + +public: + dow_posts(post_handler_ptr handler, expr_t& amount_expr) + : subtotal_posts(handler, amount_expr) { + TRACE_CTOR(dow_posts, "post_handler_ptr, bool"); + } + virtual ~dow_posts() throw() { + TRACE_DTOR(dow_posts); + } + + virtual void flush(); + virtual void operator()(post_t& post) { + days_of_the_week[post.date().day_of_week()].push_back(&post); + } +}; + +class generate_posts : public item_handler<post_t> +{ + generate_posts(); + +protected: + typedef std::pair<date_interval_t, post_t *> pending_posts_pair; + typedef std::list<pending_posts_pair> pending_posts_list; + + pending_posts_list pending_posts; + temporaries_t temps; + +public: + generate_posts(post_handler_ptr handler) + : item_handler<post_t>(handler) { + TRACE_CTOR(generate_posts, "post_handler_ptr"); + } + + virtual ~generate_posts() { + TRACE_DTOR(generate_posts); + } + + void add_period_xacts(period_xacts_list& period_xacts); + + virtual void add_post(const date_interval_t& period, post_t& post); +}; + +class budget_posts : public generate_posts +{ +#define BUDGET_NO_BUDGET 0x00 +#define BUDGET_BUDGETED 0x01 +#define BUDGET_UNBUDGETED 0x02 +#define BUDGET_WRAP_VALUES 0x04 + + uint_least8_t flags; + + budget_posts(); + +public: + budget_posts(post_handler_ptr handler, + uint_least8_t _flags = BUDGET_BUDGETED) + : generate_posts(handler), flags(_flags) { + TRACE_CTOR(budget_posts, "post_handler_ptr, uint_least8_t"); + } + virtual ~budget_posts() throw() { + TRACE_DTOR(budget_posts); + } + + void report_budget_items(const date_t& date); + + virtual void operator()(post_t& post); +}; + +class forecast_posts : public generate_posts +{ + predicate_t pred; + scope_t& context; + const std::size_t forecast_years; + + public: + forecast_posts(post_handler_ptr handler, + const predicate_t& predicate, + scope_t& _context, + const std::size_t _forecast_years) + : generate_posts(handler), pred(predicate), context(_context), + forecast_years(_forecast_years) { + TRACE_CTOR(forecast_posts, + "post_handler_ptr, predicate_t, scope_t&, std::size_t"); + } + virtual ~forecast_posts() throw() { + TRACE_DTOR(forecast_posts); + } + + virtual void add_post(const date_interval_t& period, post_t& post); + virtual void flush(); +}; + +////////////////////////////////////////////////////////////////////// +// +// Account filters +// + +class accounts_iterator; + +class pass_down_accounts : public item_handler<account_t> +{ + pass_down_accounts(); + + optional<predicate_t> pred; + optional<scope_t&> context; + +public: + pass_down_accounts(acct_handler_ptr handler, + accounts_iterator& iter, + const optional<predicate_t>& _pred = none, + const optional<scope_t&>& _context = none); + + virtual ~pass_down_accounts() { + TRACE_DTOR(pass_down_accounts); + } +}; + +} // namespace ledger + +#endif // _FILTERS_H diff --git a/src/flags.h b/src/flags.h new file mode 100644 index 00000000..69e40e4b --- /dev/null +++ b/src/flags.h @@ -0,0 +1,209 @@ +/* + * 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. + */ + +/** + * @addtogroup util + */ + +/** + * @file flags.h + * @author John Wiegley + * + * @ingroup util + */ +#ifndef _FLAGS_H +#define _FLAGS_H + + +template <typename T = boost::uint_least8_t, typename U = T> +class supports_flags +{ +public: + typedef T flags_t; + +protected: + flags_t _flags; + +public: + supports_flags() : _flags(static_cast<T>(0)) { + TRACE_CTOR(supports_flags, ""); + } + supports_flags(const supports_flags& arg) : _flags(arg._flags) { + TRACE_CTOR(supports_flags, "copy"); + } + supports_flags(const flags_t& arg) : _flags(arg) { + TRACE_CTOR(supports_flags, "const flags_t&"); + } + ~supports_flags() throw() { + TRACE_DTOR(supports_flags); + } + + supports_flags& operator=(const supports_flags& other) { + _flags = other._flags; + return *this; + } + + flags_t flags() const { + return _flags; + } + bool has_flags(const flags_t arg) const { + return _flags & arg; + } + + void set_flags(const flags_t arg) { + _flags = arg; + } + void clear_flags() { + _flags = static_cast<T>(0); + } + void add_flags(const flags_t arg) { + _flags = static_cast<T>(static_cast<U>(_flags) | static_cast<U>(arg)); + } + void drop_flags(const flags_t arg) { + _flags = static_cast<T>(static_cast<U>(_flags) & static_cast<U>(~arg)); + } + +#if defined(HAVE_BOOST_SERIALIZATION) +private: + friend class boost::serialization::access; + + template<class Archive> + void serialize(Archive& ar, const unsigned int /* version */) + { + ar & _flags; + } +#endif // HAVE_BOOST_SERIALIZATION +}; + +template <typename T = boost::uint_least8_t, typename U = T> +class basic_flags_t : public supports_flags<T, U> +{ +public: + basic_flags_t() { + TRACE_CTOR(basic_flags_t, ""); + } + basic_flags_t(const T& bits) { + TRACE_CTOR(basic_flags_t, "const T&"); + set_flags(bits); + } + basic_flags_t(const U& bits) { + TRACE_CTOR(basic_flags_t, "const U&"); + set_flags(static_cast<T>(bits)); + } + ~basic_flags_t() throw() { + TRACE_DTOR(basic_flags_t); + } + + basic_flags_t(const basic_flags_t& other) + : supports_flags<T, U>(other) { + TRACE_CTOR(basic_flags_t, "copy"); + } + basic_flags_t& operator=(const basic_flags_t& other) { + set_flags(other.flags()); + return *this; + } + basic_flags_t& operator=(const T& bits) { + set_flags(bits); + return *this; + } + + operator T() const { + return supports_flags<T, U>::flags(); + } + operator U() const { + return supports_flags<T, U>::flags(); + } + + basic_flags_t plus_flags(const T& arg) const { + basic_flags_t temp(*this); + temp.add_flags(arg); + return temp; + } + basic_flags_t minus_flags(const T& arg) const { + basic_flags_t temp(*this); + temp.drop_flags(arg); + return temp; + } +}; + +template <typename T = boost::uint_least8_t> +class delegates_flags : public boost::noncopyable +{ +public: + typedef T flags_t; + +protected: + supports_flags<T>& _flags; + +public: + delegates_flags() : _flags() { + TRACE_CTOR(delegates_flags, ""); + } + delegates_flags(supports_flags<T>& arg) : _flags(arg) { + TRACE_CTOR(delegates_flags, "const supports_flags<T>&"); + } + ~delegates_flags() throw() { + TRACE_DTOR(delegates_flags); + } + + flags_t flags() const { + return _flags.flags(); + } + bool has_flags(const flags_t arg) const { + return _flags.has_flags(arg); + } + + void set_flags(const flags_t arg) { + _flags.set_flags(arg); + } + void clear_flags() { + _flags.clear_flags(); + } + void add_flags(const flags_t arg) { + _flags.add_flags(arg); + } + void drop_flags(const flags_t arg) { + _flags.drop_flags(arg); + } + +#if defined(HAVE_BOOST_SERIALIZATION) +private: + friend class boost::serialization::access; + + template<class Archive> + void serialize(Archive& ar, const unsigned int /* version */) + { + ar & _flags; + } +#endif // HAVE_BOOST_SERIALIZATION +}; + +#endif // _FLAGS_H diff --git a/src/format.cc b/src/format.cc new file mode 100644 index 00000000..f26a86a1 --- /dev/null +++ b/src/format.cc @@ -0,0 +1,488 @@ +/* + * 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 "format.h" +#include "scope.h" +#include "pstream.h" + +namespace ledger { + +format_t::elision_style_t format_t::default_style = TRUNCATE_TRAILING; +bool format_t::default_style_changed = false; + +void format_t::element_t::dump(std::ostream& out) const +{ + out << "Element: "; + + switch (type) { + case STRING: out << " STRING"; break; + case EXPR: out << " EXPR"; break; + } + + out << " flags: 0x" << std::hex << int(flags()); + out << " min: "; + out << std::right; + out.width(2); + out << std::dec << int(min_width); + out << " max: "; + out << std::right; + out.width(2); + out << std::dec << int(max_width); + + switch (type) { + case STRING: + out << " str: '" << boost::get<string>(data) << "'" << std::endl; + break; + case EXPR: + out << " expr: " << boost::get<expr_t>(data) << std::endl; + break; + } +} + +namespace { + expr_t parse_single_expression(const char *& p, bool single_expr = true) + { + string temp(p); + ptristream str(const_cast<char *&>(p)); + expr_t expr; + expr.parse(str, single_expr ? PARSE_SINGLE : PARSE_PARTIAL, temp); + if (str.eof()) { + expr.set_text(p); + p += std::strlen(p); + } else { + assert(str.good()); + istream_pos_type pos = str.tellg(); + expr.set_text(string(p, p + long(pos))); + p += long(pos) - 1; + + // Don't gobble up any whitespace + const char * base = p; + while (p >= base && std::isspace(*p)) + p--; + } + return expr; + } +} + +format_t::element_t * format_t::parse_elements(const string& fmt, + const optional<format_t&>& tmpl) +{ + std::auto_ptr<element_t> result; + + element_t * current = NULL; + + char buf[1024]; + char * q = buf; + + for (const char * p = fmt.c_str(); *p; p++) { + if (*p != '%' && *p != '\\') { + *q++ = *p; + continue; + } + + if (! result.get()) { + result.reset(new element_t); + current = result.get(); + } else { + current->next.reset(new element_t); + current = current->next.get(); + } + + if (q != buf) { + current->type = element_t::STRING; + current->data = string(buf, q); + q = buf; + + current->next.reset(new element_t); + current = current->next.get(); + } + + if (*p == '\\') { + p++; + current->type = element_t::STRING; + switch (*p) { + case 'b': current->data = string("\b"); break; + case 'f': current->data = string("\f"); break; + case 'n': current->data = string("\n"); break; + case 'r': current->data = string("\r"); break; + case 't': current->data = string("\t"); break; + case 'v': current->data = string("\v"); break; + case '\\': current->data = string("\\"); break; + default: current->data = string(1, *p); break; + } + continue; + } + + ++p; + while (*p == '-') { + switch (*p) { + case '-': + current->add_flags(ELEMENT_ALIGN_LEFT); + break; + } + ++p; + } + + std::size_t num = 0; + while (*p && std::isdigit(*p)) { + num *= 10; + num += *p++ - '0'; + } + current->min_width = num; + + if (*p == '.') { + ++p; + num = 0; + while (*p && std::isdigit(*p)) { + num *= 10; + num += *p++ - '0'; + } + current->max_width = num; + if (current->min_width == 0) + current->min_width = current->max_width; + } + + switch (*p) { + case '%': + current->type = element_t::STRING; + current->data = string("%"); + break; + + case '$': { + if (! tmpl) + throw_(format_error, _("Prior field reference, but no template")); + + p++; + if (*p == '0' || (! std::isdigit(*p) && + *p != 'A' && *p != 'B' && *p != 'C' && + *p != 'D' && *p != 'E' && *p != 'F')) + throw_(format_error, _("%$ field reference must be a digit from 1-9")); + + unsigned int index = std::isdigit(*p) ? *p - '0' : (*p - 'A' + 10); + element_t * tmpl_elem = tmpl->elements.get(); + + for (unsigned int i = 1; i < index && tmpl_elem; i++) { + tmpl_elem = tmpl_elem->next.get(); + while (tmpl_elem && tmpl_elem->type != element_t::EXPR) + tmpl_elem = tmpl_elem->next.get(); + } + + if (! tmpl_elem) + throw_(format_error, _("%$ reference to a non-existent prior field")); + + *current = *tmpl_elem; + break; + } + + case '(': + case '{': { + bool format_amount = *p == '{'; + if (format_amount) p++; + + current->type = element_t::EXPR; + current->data = parse_single_expression(p, ! format_amount); + + // Wrap the subexpression in calls to justify and scrub + if (format_amount) { + if (! *p || *(p + 1) != '}') + throw_(format_error, _("Expected closing brace")); + else + p++; + + expr_t::ptr_op_t op = boost::get<expr_t>(current->data).get_op(); + + expr_t::ptr_op_t amount_op; + expr_t::ptr_op_t colorize_op; + if (op->kind == expr_t::op_t::O_CONS) { + amount_op = op->left(); + colorize_op = op->right(); + } else { + amount_op = op; + } + + expr_t::ptr_op_t scrub_node(new expr_t::op_t(expr_t::op_t::IDENT)); + scrub_node->set_ident("scrub"); + + expr_t::ptr_op_t call1_node(new expr_t::op_t(expr_t::op_t::O_CALL)); + call1_node->set_left(scrub_node); + call1_node->set_right(amount_op); + + expr_t::ptr_op_t arg1_node(new expr_t::op_t(expr_t::op_t::VALUE)); + expr_t::ptr_op_t arg2_node(new expr_t::op_t(expr_t::op_t::VALUE)); + expr_t::ptr_op_t arg3_node(new expr_t::op_t(expr_t::op_t::VALUE)); + + arg1_node->set_value(current->min_width > 0 ? + long(current->min_width) : -1); + arg2_node->set_value(current->max_width > 0 ? + long(current->max_width) : -1); + arg3_node->set_value(! current->has_flags(ELEMENT_ALIGN_LEFT)); + + current->min_width = 0; + current->max_width = 0; + + expr_t::ptr_op_t args1_node(new expr_t::op_t(expr_t::op_t::O_CONS)); + args1_node->set_left(arg2_node); + args1_node->set_right(arg3_node); + + expr_t::ptr_op_t args2_node(new expr_t::op_t(expr_t::op_t::O_CONS)); + args2_node->set_left(arg1_node); + args2_node->set_right(args1_node); + + expr_t::ptr_op_t args3_node(new expr_t::op_t(expr_t::op_t::O_CONS)); + args3_node->set_left(call1_node); + args3_node->set_right(args2_node); + + expr_t::ptr_op_t seq1_node(new expr_t::op_t(expr_t::op_t::O_SEQ)); + seq1_node->set_left(args3_node); + + expr_t::ptr_op_t justify_node(new expr_t::op_t(expr_t::op_t::IDENT)); + justify_node->set_ident("justify"); + + expr_t::ptr_op_t call2_node(new expr_t::op_t(expr_t::op_t::O_CALL)); + call2_node->set_left(justify_node); + call2_node->set_right(seq1_node); + + string prev_expr = boost::get<expr_t>(current->data).text(); + + if (colorize_op) { + expr_t::ptr_op_t ansify_if_node(new expr_t::op_t(expr_t::op_t::IDENT)); + ansify_if_node->set_ident("ansify_if"); + + expr_t::ptr_op_t args4_node(new expr_t::op_t(expr_t::op_t::O_CONS)); + args4_node->set_left(call2_node); + args4_node->set_right(colorize_op); + + expr_t::ptr_op_t seq2_node(new expr_t::op_t(expr_t::op_t::O_SEQ)); + seq2_node->set_left(args4_node); + + expr_t::ptr_op_t call3_node(new expr_t::op_t(expr_t::op_t::O_CALL)); + call3_node->set_left(ansify_if_node); + call3_node->set_right(seq2_node); + + current->data = expr_t(call3_node); + } else { + current->data = expr_t(call2_node); + } + + boost::get<expr_t>(current->data).set_text(prev_expr); + } + break; + } + + default: + throw_(format_error, _("Unrecognized formatting character: %1") << *p); + } + } + + if (q != buf) { + if (! result.get()) { + result.reset(new element_t); + current = result.get(); + } else { + current->next.reset(new element_t); + current = current->next.get(); + } + current->type = element_t::STRING; + current->data = string(buf, q); + } + + return result.release(); +} + +string format_t::real_calc(scope_t& scope) +{ + std::ostringstream out_str; + + for (element_t * elem = elements.get(); elem; elem = elem->next.get()) { + std::ostringstream out; + string name; + + if (elem->has_flags(ELEMENT_ALIGN_LEFT)) + out << std::left; + else + out << std::right; + + switch (elem->type) { + case element_t::STRING: + if (elem->min_width > 0) + out.width(elem->min_width); + out << boost::get<string>(elem->data); + break; + + case element_t::EXPR: { + expr_t& expr(boost::get<expr_t>(elem->data)); + try { + + expr.compile(scope); + + value_t value; + if (expr.is_function()) { + call_scope_t args(scope); + args.push_back(long(elem->max_width)); + value = expr.get_function()(args); + } else { + value = expr.calc(scope); + } + DEBUG("format.expr", "value = (" << value << ")"); + + if (elem->min_width > 0) + value.print(out, static_cast<int>(elem->min_width), -1, + ! elem->has_flags(ELEMENT_ALIGN_LEFT)); + else + out << value.to_string(); + } + catch (const calc_error&) { + add_error_context(_("While calculating format expression:")); + add_error_context(expr.context_to_str()); + throw; + } + break; + } + + default: + assert(false); + break; + } + + if (elem->max_width > 0 || elem->min_width > 0) { + unistring temp(out.str()); + string result; + + if (elem->max_width > 0 && elem->max_width < temp.length()) { + result = truncate(temp, elem->max_width); + } else { + result = temp.extract(); + if (elem->min_width > temp.length()) + for (std::size_t i = 0; i < elem->min_width - temp.length(); i++) + result += " "; + } + out_str << result; + } else { + out_str << out.str(); + } + } + + return out_str.str(); +} + +string format_t::truncate(const unistring& ustr, + const std::size_t width, + const std::size_t account_abbrev_length) +{ + assert(width < 4095); + + const std::size_t len = ustr.length(); + if (width == 0 || len <= width) + return ustr.extract(); + + std::ostringstream buf; + + elision_style_t style = default_style; + if (account_abbrev_length > 0 && ! default_style_changed) + style = ABBREVIATE; + + switch (style) { + case TRUNCATE_LEADING: + // This method truncates at the beginning. + buf << ".." << ustr.extract(len - (width - 2), width - 2); + break; + + case TRUNCATE_MIDDLE: + // This method truncates in the middle. + buf << ustr.extract(0, (width - 2) / 2) + << ".." + << ustr.extract(len - ((width - 2) / 2 + (width - 2) % 2), + (width - 2) / 2 + (width - 2) % 2); + break; + + case ABBREVIATE: + if (account_abbrev_length > 0) { + std::list<string> parts; + string::size_type beg = 0; + string strcopy(ustr.extract()); + for (string::size_type pos = strcopy.find(':'); + pos != string::npos; + beg = pos + 1, pos = strcopy.find(':', beg)) + parts.push_back(string(strcopy, beg, pos - beg)); + parts.push_back(string(strcopy, beg)); + + std::ostringstream result; + + std::size_t newlen = len; + for (std::list<string>::iterator i = parts.begin(); + i != parts.end(); + i++) { + // Don't contract the last element + std::list<string>::iterator x = i; + if (++x == parts.end()) { + result << *i; + break; + } + + if (newlen > width) { + unistring temp(*i); + if (temp.length() > account_abbrev_length) { + result << temp.extract(0, account_abbrev_length) << ":"; + newlen -= temp.length() - account_abbrev_length; + } else { + result << temp.extract() << ":"; + newlen -= temp.length(); + } + } else { + result << *i << ":"; + } + } + + if (newlen > width) { + // Even abbreviated its too big to show the last account, so + // abbreviate all but the last and truncate at the beginning. + unistring temp(result.str()); + assert(temp.length() > width - 2); + buf << ".." << temp.extract(temp.length() - (width - 2), width - 2); + } else { + buf << result.str(); + } + break; + } + // fall through... + + case TRUNCATE_TRAILING: + // This method truncates at the end (the default). + buf << ustr.extract(0, width - 2) << ".."; + break; + } + + return buf.str(); +} + +} // namespace ledger diff --git a/src/format.h b/src/format.h new file mode 100644 index 00000000..a2bf1015 --- /dev/null +++ b/src/format.h @@ -0,0 +1,161 @@ +/* + * 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. + */ + +/** + * @addtogroup expr + */ + +/** + * @file format.h + * @author John Wiegley + * + * @ingroup expr + */ +#ifndef _FORMAT_H +#define _FORMAT_H + +#include "expr.h" +#include "unistring.h" + +namespace ledger { + +class unistring; + +DECLARE_EXCEPTION(format_error, std::runtime_error); + +class format_t : public expr_base_t<string> +{ + typedef expr_base_t<string> base_type; + + struct element_t : public supports_flags<> + { +#define ELEMENT_ALIGN_LEFT 0x01 + + enum kind_t { STRING, EXPR }; + + kind_t type; + std::size_t min_width; + std::size_t max_width; + variant<string, expr_t> data; + scoped_ptr<struct element_t> next; + + element_t() throw() + : supports_flags<>(), type(STRING), min_width(0), max_width(0) { + TRACE_CTOR(element_t, ""); + } + ~element_t() throw() { + TRACE_DTOR(element_t); + } + element_t(const element_t& elem) : supports_flags<>() { + *this = elem; + } + + element_t& operator=(const element_t& elem) { + if (this != &elem) { + supports_flags<>::operator=(elem); + type = elem.type; + min_width = elem.min_width; + max_width = elem.max_width; + data = elem.data; + } + return *this; + } + + friend inline void mark_red(std::ostream& out, const element_t * elem) { + out.setf(std::ios::left); + out.width(0); + out << "\033[31m"; + + if (elem->has_flags(ELEMENT_ALIGN_LEFT)) + out << std::left; + else + out << std::right; + + if (elem->min_width > 0) + out.width(elem->min_width); + } + + void dump(std::ostream& out) const; + }; + + scoped_ptr<element_t> elements; + +public: + static enum elision_style_t { + TRUNCATE_TRAILING, + TRUNCATE_MIDDLE, + TRUNCATE_LEADING, + ABBREVIATE + } default_style; + + static bool default_style_changed; + +private: + static element_t * parse_elements(const string& fmt, + const optional<format_t&>& tmpl); + +public: + format_t() : base_type() { + TRACE_CTOR(format_t, ""); + } + format_t(const string& _str, scope_t * context = NULL) + : base_type(context) { + TRACE_CTOR(format_t, "const string&"); + if (! _str.empty()) + parse_format(_str); + } + virtual ~format_t() { + TRACE_DTOR(format_t); + } + + void parse_format(const string& _format, + const optional<format_t&>& tmpl = none) { + elements.reset(parse_elements(_format, tmpl)); + set_text(_format); + } + + virtual result_type real_calc(scope_t& scope); + + virtual void dump(std::ostream& out) const { + for (const element_t * elem = elements.get(); + elem; + elem = elem->next.get()) + elem->dump(out); + } + + static string truncate(const unistring& str, + const std::size_t width, + const std::size_t account_abbrev_length = 0); +}; + +} // namespace ledger + +#endif // _FORMAT_H diff --git a/src/generate.cc b/src/generate.cc new file mode 100644 index 00000000..3549adc8 --- /dev/null +++ b/src/generate.cc @@ -0,0 +1,385 @@ +/* + * 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 "generate.h" +#include "session.h" + +namespace ledger { + +generate_posts_iterator::generate_posts_iterator + (session_t& _session, + unsigned int _seed, + std::size_t _quantity, + bool _allow_invalid) + : session(_session), seed(_seed), quantity(_quantity), + allow_invalid(_allow_invalid), + + rnd_gen(seed == 0 ? static_cast<unsigned int>(std::time(0)) : seed), + + year_range(1900, 2300), year_gen(rnd_gen, year_range), + mon_range(1, 12), mon_gen(rnd_gen, mon_range), + day_range(1, 28), day_gen(rnd_gen, day_range), + + upchar_range('A', 'Z'), upchar_gen(rnd_gen, upchar_range), + downchar_range('a', 'z'), downchar_gen(rnd_gen, downchar_range), + numchar_range('0', '9'), numchar_gen(rnd_gen, numchar_range), + + truth_range(0, 1), truth_gen(rnd_gen, truth_range), + three_range(1, 3), three_gen(rnd_gen, three_range), + six_range(1, 6), six_gen(rnd_gen, six_range), + two_six_range(2, 6), two_six_gen(rnd_gen, two_six_range), + strlen_range(1, 40), strlen_gen(rnd_gen, strlen_range), + + neg_number_range(-1000000, -1), neg_number_gen(rnd_gen, neg_number_range), + pos_number_range(1, 1000000), pos_number_gen(rnd_gen, pos_number_range) +{ + TRACE_CTOR(generate_posts_iterator, "bool"); + + std::ostringstream next_date_buf; + generate_date(next_date_buf); + next_date = parse_date(next_date_buf.str()); + + std::ostringstream next_eff_date_buf; + generate_date(next_eff_date_buf); + next_eff_date = parse_date(next_eff_date_buf.str()); + +} + +void generate_posts_iterator::generate_string(std::ostream& out, int len, + bool only_alpha) +{ + DEBUG("generate.post.string", + "Generating string of length " << len << ", only alpha " << only_alpha); + + int last = -1; + bool first = true; + for (int i = 0; i < len; i++) { + int next = only_alpha ? 3 : three_gen(); + bool output = true; + switch (next) { + case 1: // colon + if (! first && last == 3 && strlen_gen() % 10 == 0 && i + 1 != len) + out << ':'; + else { + i--; + output = false; + } + break; + case 2: // space + if (! first && last == 3 && strlen_gen() % 20 == 0 && i + 1 != len) + out << ' '; + else { + i--; + output = false; + } + break; + case 3: // character + switch (three_gen()) { + case 1: // uppercase + out << char(upchar_gen()); + break; + case 2: // lowercase + out << char(downchar_gen()); + break; + case 3: // number + if (! only_alpha && ! first) + out << char(numchar_gen()); + else { + i--; + output = false; + } + break; + } + break; + } + if (output) { + last = next; + first = false; + } + } +} + +bool generate_posts_iterator::generate_account(std::ostream& out, + bool no_virtual) +{ + bool must_balance = true; + bool is_virtual = false; + + if (! no_virtual) { + switch (three_gen()) { + case 1: + out << '['; + is_virtual = true; + break; + case 2: + out << '('; + must_balance = false; + is_virtual = true; + break; + case 3: + break; + } + } + + generate_string(out, strlen_gen()); + + if (is_virtual) { + if (must_balance) + out << ']'; + else + out << ')'; + } + + return must_balance; +} + +void generate_posts_iterator::generate_commodity(std::ostream& out) +{ + string comm; + do { + std::ostringstream buf; + generate_string(buf, six_gen(), true); + comm = buf.str(); + } + while (comm == "h" || comm == "m" || comm == "s" || + comm == "and" || comm == "div" || comm == "false" || + comm == "or" || comm == "not" || comm == "true" || + comm == "if" || comm == "else"); + + out << comm; +} + +string generate_posts_iterator::generate_amount(std::ostream& out, + value_t not_this_amount, + bool no_negative) +{ + std::ostringstream buf; + + if (truth_gen()) { // commodity goes in front + generate_commodity(buf); + if (truth_gen()) + buf << ' '; + if (no_negative || truth_gen()) + buf << pos_number_gen(); + else + buf << neg_number_gen(); + } else { + if (no_negative || truth_gen()) + buf << pos_number_gen(); + else + buf << neg_number_gen(); + if (truth_gen()) + buf << ' '; + generate_commodity(buf); + } + + // Possibly generate an annotized commodity, but make it rarer + if (! no_negative && three_gen() == 1) { + if (three_gen() == 1) { + buf << " {"; + generate_amount(buf, value_t(), true); + buf << '}'; + } + if (six_gen() == 1) { + buf << " ["; + generate_date(buf); + buf << ']'; + } + if (six_gen() == 1) { + buf << " ("; + generate_string(buf, six_gen()); + buf << ')'; + } + } + + if (! not_this_amount.is_null() && + value_t(buf.str()).as_amount().commodity() == + not_this_amount.as_amount().commodity()) + return ""; + + out << buf.str(); + + return buf.str(); +} + +bool generate_posts_iterator::generate_post(std::ostream& out, bool no_amount) +{ + out << " "; + bool must_balance = generate_account(out, no_amount); + out << " "; + + if (! no_amount) { + value_t amount(generate_amount(out)); + if (truth_gen()) + generate_cost(out, amount); + } + if (truth_gen()) + generate_note(out); + out << '\n'; + + return must_balance; +} + +void generate_posts_iterator::generate_cost(std::ostream& out, value_t amount) +{ + std::ostringstream buf; + + if (truth_gen()) + buf << " @ "; + else + buf << " @@ "; + + if (! generate_amount(buf, amount, true).empty()) + out << buf.str(); +} + +void generate_posts_iterator::generate_date(std::ostream& out) +{ + out.width(4); + out.fill('0'); + out << year_gen(); + + out.width(1); + out << '/'; + + out.width(2); + out.fill('0'); + out << mon_gen(); + + out.width(1); + out << '/'; + + out.width(2); + out.fill('0'); + out << day_gen(); +} + +void generate_posts_iterator::generate_state(std::ostream& out) +{ + switch (three_gen()) { + case 1: + out << "* "; + break; + case 2: + out << "! "; + break; + case 3: + out << ""; + break; + } +} + +void generate_posts_iterator::generate_code(std::ostream& out) +{ + out << '('; + generate_string(out, six_gen()); + out << ") "; +} + +void generate_posts_iterator::generate_payee(std::ostream& out) +{ + generate_string(out, strlen_gen()); +} + +void generate_posts_iterator::generate_note(std::ostream& out) +{ + out << "\n ; "; + generate_string(out, strlen_gen()); +} + +void generate_posts_iterator::generate_xact(std::ostream& out) +{ + out << format_date(next_date, FMT_WRITTEN); + next_date += gregorian::days(six_gen()); + if (truth_gen()) { + out << '='; + out << format_date(next_eff_date, FMT_WRITTEN); + next_eff_date += gregorian::days(six_gen()); + } + out << ' '; + + generate_state(out); + generate_code(out); + generate_payee(out); + if (truth_gen()) + generate_note(out); + out << '\n'; + + int count = three_gen() * 2; + bool has_must_balance = false; + for (int i = 0; i < count; i++) { + if (generate_post(out)) + has_must_balance = true; + } + if (has_must_balance) + generate_post(out, true); + + out << '\n'; +} + +post_t * generate_posts_iterator::operator()() +{ + post_t * post = posts(); + if (post == NULL && quantity > 0) { + std::ostringstream buf; + generate_xact(buf); + + DEBUG("generate.post", "The post we intend to parse:\n" << buf.str()); + + std::istringstream in(buf.str()); + try { + if (session.journal->parse(in, session) != 0) { + VERIFY(session.journal->xacts.back()->valid()); + posts.reset(*session.journal->xacts.back()); + post = posts(); + } + } + catch (std::exception& err) { + add_error_context(_("While parsing generated transaction (seed %1):") + << seed); + add_error_context(buf.str()); + throw; + } + catch (int status) { + add_error_context(_("While parsing generated transaction (seed %1):") + << seed); + add_error_context(buf.str()); + throw; + } + + quantity--; + } + return post; +} + +} // namespace ledger diff --git a/src/generate.h b/src/generate.h new file mode 100644 index 00000000..66513fc8 --- /dev/null +++ b/src/generate.h @@ -0,0 +1,129 @@ +/* + * 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. + */ + +/** + * @addtogroup generate + */ + +/** + * @file generate.h + * @author John Wiegley + * + * @ingroup report + */ +#ifndef _GENERATE_H +#define _GENERATE_H + +#include "iterators.h" + +namespace ledger { + +class session_t; + +class generate_posts_iterator : public posts_iterator +{ + session_t& session; + unsigned int seed; + std::size_t quantity; + bool allow_invalid; + date_t next_date; + date_t next_eff_date; + + mt19937 rnd_gen; + + typedef variate_generator<mt19937&, uniform_int<> > int_generator_t; + typedef variate_generator<mt19937&, uniform_real<> > real_generator_t; + + uniform_int<> year_range; + int_generator_t year_gen; + uniform_int<> mon_range; + int_generator_t mon_gen; + uniform_int<> day_range; + int_generator_t day_gen; + + uniform_int<> upchar_range; + int_generator_t upchar_gen; + uniform_int<> downchar_range; + int_generator_t downchar_gen; + uniform_int<> numchar_range; + int_generator_t numchar_gen; + + uniform_int<> truth_range; + int_generator_t truth_gen; + uniform_int<> three_range; + int_generator_t three_gen; + uniform_int<> six_range; + int_generator_t six_gen; + uniform_int<> two_six_range; + int_generator_t two_six_gen; + + uniform_int<> strlen_range; + int_generator_t strlen_gen; + + uniform_real<> neg_number_range; + real_generator_t neg_number_gen; + uniform_real<> pos_number_range; + real_generator_t pos_number_gen; + + xact_posts_iterator posts; + +public: + generate_posts_iterator(session_t& _session, + unsigned int _seed = 0, + std::size_t _quantity = 100, + bool _allow_invalid = false); + + virtual ~generate_posts_iterator() throw() { + TRACE_DTOR(generate_posts_iterator); + } + + virtual post_t * operator()(); + +protected: + void generate_string(std::ostream& out, int len, bool only_alpha = false); + bool generate_account(std::ostream& out, bool no_virtual = false); + void generate_commodity(std::ostream& out); + string generate_amount(std::ostream& out, + value_t not_this_amount = NULL_VALUE, + bool no_negative = false); + bool generate_post(std::ostream& out, bool no_amount = false); + void generate_cost(std::ostream& out, value_t amount); + void generate_date(std::ostream& out); + void generate_state(std::ostream& out); + void generate_code(std::ostream& out); + void generate_payee(std::ostream& out); + void generate_note(std::ostream& out); + void generate_xact(std::ostream& out); +}; + +} // namespace ledger + +#endif // _GENERATE_H diff --git a/src/global.cc b/src/global.cc new file mode 100644 index 00000000..e120e5d5 --- /dev/null +++ b/src/global.cc @@ -0,0 +1,480 @@ +/* + * 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 "global.h" +#if defined(HAVE_BOOST_PYTHON) +#include "pyinterp.h" +#else +#include "session.h" +#endif +#include "item.h" +#include "journal.h" +#include "pool.h" + +namespace ledger { + +static bool args_only = false; + +global_scope_t::global_scope_t(char ** envp) +{ + TRACE_CTOR(global_scope_t, ""); + +#if defined(HAVE_BOOST_PYTHON) + if (! python_session.get()) { + python_session.reset(new ledger::python_interpreter_t); + session_ptr = python_session; + } +#else + session_ptr.reset(new session_t); +#endif + + set_session_context(session_ptr.get()); + + // Create the report object, which maintains state relating to each + // command invocation. Because we're running from main(), the + // distinction between session and report doesn't really matter, but if + // a GUI were calling into Ledger it would have one session object per + // open document, with a separate report_t object for each report it + // generated. + report_stack.push_front(new report_t(session())); + scope_t::default_scope = &report(); + + // Read the user's options, in the following order: + // + // 1. environment variables (LEDGER_<option>) + // 2. initialization file (~/.ledgerrc) + // 3. command-line (--option or -o) + // + // Before processing command-line options, we must notify the session object + // that such options are beginning, since options like -f cause a complete + // override of files found anywhere else. + if (! args_only) { + session().set_flush_on_next_data_file(true); + read_environment_settings(envp); + session().set_flush_on_next_data_file(true); + read_init(); + } else { + session().HANDLER(price_db_).off(); + } +} + +global_scope_t::~global_scope_t() +{ + TRACE_DTOR(global_scope_t); + + // If memory verification is being performed (which can be very slow), + // clean up everything by closing the session and deleting the session + // object, and then shutting down the memory tracing subsystem. + // Otherwise, let it all leak because we're about to exit anyway. + IF_VERIFY() set_session_context(NULL); + +#if defined(HAVE_BOOST_PYTHON) + python_session.reset(); +#endif +} + +void global_scope_t::read_init() +{ + if (HANDLED(init_file_)) { + path init_file(HANDLER(init_file_).str()); + if (exists(init_file)) { + TRACE_START(init, 1, "Read initialization file"); + + ifstream init(init_file); + + if (session().journal->read(init_file, NULL, &report()) > 0 || + session().journal->auto_xacts.size() > 0 || + session().journal->period_xacts.size() > 0) { + throw_(parse_error, _("Transactions found in initialization file '%1'") + << init_file); + } + + TRACE_FINISH(init, 1); + } + } +} + +char * global_scope_t::prompt_string() +{ + static char prompt[32]; + std::size_t i; + for (i = 0; i < report_stack.size(); i++) + prompt[i] = ']'; + prompt[i++] = ' '; + prompt[i] = '\0'; + return prompt; +} + +void global_scope_t::report_error(const std::exception& err) +{ + std::cout.flush(); // first display anything that was pending + + if (caught_signal == NONE_CAUGHT) { + // Display any pending error context information + string context = error_context(); + if (! context.empty()) + std::cerr << context << std::endl; + + std::cerr << _("Error: ") << err.what() << std::endl; + } else { + caught_signal = NONE_CAUGHT; + } +} + +void global_scope_t::execute_command(strings_list args, bool at_repl) +{ + session().set_flush_on_next_data_file(true); + + // Process the command verb, arguments and options + if (at_repl) { + args = read_command_arguments(report(), args); + if (args.empty()) + return; + } + + strings_list::iterator arg = args.begin(); + string verb = *arg++; + + // Look for a precommand first, which is defined as any defined function + // whose name starts with "ledger_precmd_". The difference between a + // precommand and a regular command is that precommands ignore the journal + // data file completely, nor is the user's init file read. + // + // Here are some examples of pre-commands: + // + // parse STRING ; show how a value expression is parsed + // eval STRING ; simply evaluate a value expression + // format STRING ; show how a format string is parsed + // + // If such a command is found, create the output stream for the result and + // then invoke the command. + + expr_t::func_t command; + bool is_precommand = false; + bind_scope_t bound_scope(*this, report()); + + if (bool(command = look_for_precommand(bound_scope, verb))) + is_precommand = true; + + // If it is not a pre-command, then parse the user's ledger data at this + // time if not done alreday (i.e., if not at a REPL). Then patch up the + // report options based on the command verb. + + if (! is_precommand) { + if (! at_repl) + session().read_journal_files(); + + report().normalize_options(verb); + + if (! bool(command = look_for_command(bound_scope, verb))) + throw_(std::logic_error, _("Unrecognized command '%1'") << verb); + } + + // Create the output stream (it might be a file, the console or a PAGER + // subprocess) and invoke the report command. The output stream is closed + // by the caller of this function. + + report().output_stream + .initialize(report().HANDLED(output_) ? + optional<path>(path(report().HANDLER(output_).str())) : + optional<path>(), + report().HANDLED(pager_) ? + optional<path>(path(report().HANDLER(pager_).str())) : + optional<path>()); + + // Now that the output stream is initialized, report the options that will + // participate in this report, if the user specified --options + + if (HANDLED(options)) + report_options(report(), report().output_stream); + + // Create an argument scope containing the report command's arguments, and + // then invoke the command. The bound scope causes lookups to happen + // first in the global scope, and then in the report scope. + + call_scope_t command_args(bound_scope); + for (strings_list::iterator i = arg; i != args.end(); i++) + command_args.push_back(string_value(*i)); + + INFO_START(command, "Finished executing command"); + command(command_args); + INFO_FINISH(command); +} + +int global_scope_t::execute_command_wrapper(strings_list args, bool at_repl) +{ + int status = 1; + + try { + if (at_repl) push_report(); + execute_command(args, at_repl); + if (at_repl) pop_report(); + + // If we've reached this point, everything succeeded fine. Ledger uses + // exceptions to notify of error conditions, so if you're using gdb, + // just type "catch throw" to find the source point of any error. + status = 0; + } + catch (const std::exception& err) { + if (at_repl) pop_report(); + report_error(err); + } + + return status; +} + +void global_scope_t::report_options(report_t& report, std::ostream& out) +{ + out << "===============================================================================" + << std::endl; + out << "[Global scope options]" << std::endl; + + HANDLER(args_only).report(out); + HANDLER(debug_).report(out); + HANDLER(init_file_).report(out); + HANDLER(script_).report(out); + HANDLER(trace_).report(out); + HANDLER(verbose).report(out); + HANDLER(verify).report(out); + + out << std::endl << "[Session scope options]" << std::endl; + report.session.report_options(out); + + out << std::endl << "[Report scope options]" << std::endl; + report.report_options(out); + out << "===============================================================================" + << std::endl; +} + +option_t<global_scope_t> * global_scope_t::lookup_option(const char * p) +{ + switch (*p) { + case 'a': + OPT(args_only); + break; + case 'd': + OPT(debug_); + break; + case 'f': + OPT(full_help); + break; + case 'h': + OPT_(help); + else OPT(help_calc); + else OPT(help_comm); + else OPT(help_disp); + break; + case 'i': + OPT(init_file_); + break; + case 'o': + OPT(options); + break; + case 's': + OPT(script_); + break; + case 't': + OPT(trace_); + break; + case 'v': + OPT_(verbose); + else OPT(verify); + else OPT(version); + break; + } + return NULL; +} + +expr_t::ptr_op_t global_scope_t::lookup(const symbol_t::kind_t kind, + const string& name) +{ + switch (kind) { + case symbol_t::FUNCTION: + if (option_t<global_scope_t> * handler = lookup_option(name.c_str())) + return MAKE_OPT_FUNCTOR(global_scope_t, handler); + break; + + case symbol_t::OPTION: + if (option_t<global_scope_t> * handler = lookup_option(name.c_str())) + return MAKE_OPT_HANDLER(global_scope_t, handler); + break; + + case symbol_t::PRECOMMAND: { + const char * p = name.c_str(); + switch (*p) { + case 'p': + if (is_eq(p, "push")) + return MAKE_FUNCTOR(global_scope_t::push_command); + else if (is_eq(p, "pop")) + return MAKE_FUNCTOR(global_scope_t::pop_command); + break; + } + } + default: + break; + } + + // If you're wondering how symbols from report() will be found, it's + // because of the bind_scope_t object in execute_command() below. + return NULL; +} + +void global_scope_t::read_environment_settings(char * envp[]) +{ + TRACE_START(environment, 1, "Processed environment variables"); + + process_environment(const_cast<const char **>(envp), "LEDGER_", report()); + +#if 1 + // These are here for backwards compatability, but are deprecated. + + if (const char * p = std::getenv("LEDGER")) { + if (! std::getenv("LEDGER_FILE")) + process_option("environ", "file", report(), p, "LEDGER"); + } + if (const char * p = std::getenv("LEDGER_INIT")) { + if (! std::getenv("LEDGER_INIT_FILE")) + process_option("environ", "init-file", report(), p, "LEDGER_INIT"); + } + if (const char * p = std::getenv("PRICE_HIST")) { + if (! std::getenv("LEDGER_PRICEDB")) + process_option("environ", "price-db", report(), p, "PRICE_HIST"); + } + if (const char * p = std::getenv("PRICE_EXP")) + process_option("environ", "price-exp", report(), p, "PRICE_EXP"); +#endif + + TRACE_FINISH(environment, 1); +} + +strings_list +global_scope_t::read_command_arguments(scope_t& scope, strings_list args) +{ + TRACE_START(arguments, 1, "Processed command-line arguments"); + + strings_list remaining = process_arguments(args, scope); + + TRACE_FINISH(arguments, 1); + + return remaining; +} + +void global_scope_t::normalize_session_options() +{ + INFO("Initialization file is " << HANDLER(init_file_).str()); + INFO("Price database is " << session().HANDLER(price_db_).str()); + + foreach (const path& pathname, session().HANDLER(file_).data_files) + INFO("Journal file is " << pathname.string()); +} + +expr_t::func_t global_scope_t::look_for_precommand(scope_t& scope, + const string& verb) +{ + if (expr_t::ptr_op_t def = scope.lookup(symbol_t::PRECOMMAND, verb)) + return def->as_function(); + else + return expr_t::func_t(); +} + +expr_t::func_t global_scope_t::look_for_command(scope_t& scope, + const string& verb) +{ + if (expr_t::ptr_op_t def = scope.lookup(symbol_t::COMMAND, verb)) + return def->as_function(); + else + return expr_t::func_t(); +} + +void global_scope_t::visit_man_page() const +{ + int pid = fork(); + if (pid < 0) { + throw std::logic_error(_("Failed to fork child process")); + } + else if (pid == 0) { // child + execlp("man", "man", "1", "ledger", NULL); + + // We should never, ever reach here + perror("execlp: man"); + exit(1); + } + + int status = -1; + wait(&status); + exit(0); // parent +} + +void handle_debug_options(int argc, char * argv[]) +{ + for (int i = 1; i < argc; i++) { + if (argv[i][0] == '-') { + if (std::strcmp(argv[i], "--args-only") == 0) { + args_only = true; + } + else if (std::strcmp(argv[i], "--verify") == 0) { +#if defined(VERIFY_ON) + verify_enabled = true; // global in utils.h +#endif + } + else if (std::strcmp(argv[i], "--verbose") == 0 || + std::strcmp(argv[i], "-v") == 0) { +#if defined(LOGGING_ON) + _log_level = LOG_INFO; // global in utils.h +#endif + } + else if (i + 1 < argc && std::strcmp(argv[i], "--debug") == 0) { +#if defined(DEBUG_ON) + _log_level = LOG_DEBUG; // global in utils.h + _log_category = argv[i + 1]; // global in utils.h + i++; +#endif + } + else if (i + 1 < argc && std::strcmp(argv[i], "--trace") == 0) { +#if defined(TRACING_ON) + _log_level = LOG_TRACE; // global in utils.h + try { + // global in utils.h + _trace_level = boost::lexical_cast<uint8_t>(argv[i + 1]); + } + catch (const boost::bad_lexical_cast& e) { + throw std::logic_error(_("Argument to --trace must be an integer")); + } + i++; +#endif + } + } + } +} + +} // namespace ledger diff --git a/src/global.h b/src/global.h new file mode 100644 index 00000000..ab3afed4 --- /dev/null +++ b/src/global.h @@ -0,0 +1,163 @@ +/* + * 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. + */ + +/** + * @file global.h + * @author John Wiegley + * + * @brief Contains the top-level functions used by main.cc + */ +#ifndef _GLOBAL_H +#define _GLOBAL_H + +#include "interactive.h" +#include "option.h" +#include "report.h" + +namespace ledger { + +class session_t; +class report_t; + +class global_scope_t : public noncopyable, public scope_t +{ + shared_ptr<session_t> session_ptr; + ptr_list<report_t> report_stack; + +public: + global_scope_t(char ** envp); + ~global_scope_t(); + + void read_init(); + void read_environment_settings(char * envp[]); + strings_list read_command_arguments(scope_t& scope, strings_list args); + void normalize_session_options(); + expr_t::func_t look_for_precommand(scope_t& scope, const string& verb); + expr_t::func_t look_for_command(scope_t& scope, const string& verb); + + char * prompt_string(); + + session_t& session() { + return *session_ptr.get(); + } + report_t& report() { + return report_stack.front(); + } + + void push_report() { + report_stack.push_front(new report_t(report_stack.front())); + scope_t::default_scope = &report(); + } + void pop_report() { + assert(! report_stack.empty()); + report_stack.pop_front(); + // There should always be the "default report" waiting on the stack. + assert(! report_stack.empty()); + scope_t::default_scope = &report(); + } + + void report_error(const std::exception& err); + + /** + * @return \c true if a command was actually executed; otherwise, it probably + * just resulted in setting some options. + */ + void execute_command(strings_list args, bool at_repl); + int execute_command_wrapper(strings_list args, bool at_repl); + + value_t push_command(call_scope_t&) { + // Make a copy at position 2, because the topmost report object has an + // open output stream at this point. We want it to get popped off as + // soon as this command terminate so that the stream is closed cleanly. + report_stack.insert(++report_stack.begin(), + new report_t(report_stack.front())); + return true; + } + value_t pop_command(call_scope_t&) { + pop_report(); + return true; + } + + void show_version_info(std::ostream& out) { + out << + "Ledger " << ledger::version << _(", the command-line accounting tool"); + out << + _("\n\nCopyright (c) 2003-2009, John Wiegley. All rights reserved.\n\n\ +This program is made available under the terms of the BSD Public License.\n\ +See LICENSE file included with the distribution for details and disclaimer."); + out << std::endl; + } + + void report_options(report_t& report, std::ostream& out); + + option_t<global_scope_t> * lookup_option(const char * p); + + virtual expr_t::ptr_op_t lookup(const symbol_t::kind_t kind, + const string& name); + + OPTION(global_scope_t, args_only); + OPTION(global_scope_t, debug_); + + void visit_man_page() const; + + OPTION_(global_scope_t, full_help, DO() { parent->visit_man_page(); }); // -H + OPTION_(global_scope_t, help, DO() { parent->visit_man_page(); }); // -h + OPTION_(global_scope_t, help_calc, DO() { parent->visit_man_page(); }); + OPTION_(global_scope_t, help_comm, DO() { parent->visit_man_page(); }); + OPTION_(global_scope_t, help_disp, DO() { parent->visit_man_page(); }); + + OPTION__ + (global_scope_t, init_file_, // -i + + CTOR(global_scope_t, init_file_) { + if (const char * home_var = std::getenv("HOME")) + on(none, (path(home_var) / ".ledgerrc").string()); + else + on(none, path("./.ledgerrc").string()); + }); + + OPTION(global_scope_t, options); + OPTION(global_scope_t, script_); + OPTION(global_scope_t, trace_); + OPTION(global_scope_t, verbose); + OPTION(global_scope_t, verify); + + OPTION_(global_scope_t, version, DO() { // -v + parent->show_version_info(std::cout); + throw int(0); // exit immediately + }); +}; + +void handle_debug_options(int argc, char * argv[]); + +} // namespace ledger + +#endif // _GLOBAL_H diff --git a/src/interactive.cc b/src/interactive.cc new file mode 100644 index 00000000..35382e8f --- /dev/null +++ b/src/interactive.cc @@ -0,0 +1,194 @@ +/* + * 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 "interactive.h" + +namespace ledger { + +void interactive_t::verify_arguments() const +{ + value_t::sequence_t::const_iterator i; + + const char * p = spec.c_str(); + const char * label = _("unknown"); + bool wrong_arg = false; + bool dont_skip = false; + bool optional = *p == '&'; + bool exit_loop = *p == '*'; + std::size_t offset = 1; + bool is_seq = args.value().is_sequence(); + const value_t * next_arg = NULL; + string vlabel; + + if (is_seq) { + i = args.begin(); + if (i != args.end()) + next_arg = &(*i); + } + else if (! args.value().is_null()) { + next_arg = &args.value(); + } + + 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"); + wrong_arg = (! next_arg->is_long() && + ! next_arg->is_amount() && + ! next_arg->is_balance()); + break; + case 'b': + label = _("a boolean"); + wrong_arg = false; // booleans are converted + break; + case 'd': + label = _("a date"); + wrong_arg = (! next_arg->is_date() && + ! next_arg->is_datetime()); + break; + case 't': + label = _("a date/time"); + wrong_arg = (! next_arg->is_date() && + ! next_arg->is_datetime()); + break; + 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; + } + } + } + else { + wrong_arg = true; + } + break; + case 'm': + label = _("a regex"); + wrong_arg = ! next_arg->is_mask(); + break; + case 's': + label = _("a string"); + wrong_arg = ! next_arg->is_string(); + break; + case 'v': + label = _("any value"); + wrong_arg = false; + break; + case '^': + label = _("a scope"); + wrong_arg = ! next_arg->is_scope(); + break; + case 'S': + label = _("a sequence"); + wrong_arg = false; + break; + case '&': + optional = true; + dont_skip = true; + break; + case '*': + optional = true; + exit_loop = true; + dont_skip = true; + break; + } + if (wrong_arg && optional && next_arg->is_null()) + wrong_arg = false; + + if (wrong_arg) + vlabel = next_arg->label(); + + if (! dont_skip) { + if (is_seq) { + if (++i != args.end()) { + next_arg = &(*i); + offset++; + } else { + next_arg = NULL; + } + } else { + next_arg = NULL; + } + } + dont_skip = false; + } + + if (*p == '&' || *p == '*') + optional = true; + + DEBUG("interactive.verify", "Remaining args are optional"); + + if (wrong_arg) { + throw_(std::logic_error, + _("Expected %1 for argument %2, but received %3") + << label << offset << vlabel); + } + else if (*p && ! optional && ! next_arg) { + throw_(std::logic_error, _("Too few arguments to function")); + } + else if (! *p && next_arg) { + throw_(std::logic_error, _("Too many arguments to function")); + } +} + +string join_args(call_scope_t& args) +{ + std::ostringstream buf; + bool first = true; + + for (std::size_t i = 0; i < args.size(); i++) { + if (first) + first = false; + else + buf << ' '; + buf << args[i]; + } + + return buf.str(); +} + +} // namespace ledger diff --git a/src/interactive.h b/src/interactive.h new file mode 100644 index 00000000..199b7b71 --- /dev/null +++ b/src/interactive.h @@ -0,0 +1,151 @@ +/* + * 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. + */ + +/** + * @addtogroup expr + */ + +/** + * @file interactive.h + * @author John Wiegley + * + * @ingroup expr + */ +#ifndef _INTERACTIVE_H +#define _INTERACTIVE_H + +#include "scope.h" + +namespace ledger { + +class interactive_t : public noncopyable +{ + call_scope_t& args; + string spec; + +public: + explicit interactive_t(call_scope_t& _args, const string& _spec = "") + : args(_args), spec(_spec) { + TRACE_CTOR(interactive_t, "call_scope_t&, const string&"); + verify_arguments(); + } + virtual ~interactive_t() { + TRACE_DTOR(interactive_t); + } + + void verify_arguments() const; + + bool has(std::size_t index) const { + if (index < args.size() && ! args[index].is_null()) + return true; + return false; + } + + value_t& value_at(std::size_t index) { + assert(has(index)); + return args[index]; + } + + template <typename T> + T get(std::size_t index); +}; + +template <> +inline bool interactive_t::get<bool>(std::size_t index) { + return value_at(index).to_boolean(); +} +template <> +inline int interactive_t::get<int>(std::size_t index) { + return value_at(index).to_int(); +} +template <> +inline long interactive_t::get<long>(std::size_t index) { + return value_at(index).to_long(); +} +template <> +inline amount_t interactive_t::get<amount_t>(std::size_t index) { + return value_at(index).to_amount(); +} +template <> +inline string interactive_t::get<string>(std::size_t index) { + return value_at(index).to_string(); +} +template <> +inline mask_t interactive_t::get<mask_t>(std::size_t index) { + return value_at(index).to_mask(); +} +template <> +inline date_t interactive_t::get<date_t>(std::size_t index) { + return value_at(index).to_date(); +} +template <> +inline datetime_t interactive_t::get<datetime_t>(std::size_t index) { + return value_at(index).to_datetime(); +} +template <> +inline value_t::sequence_t& +interactive_t::get<value_t::sequence_t&>(std::size_t index) { + return value_at(index).as_sequence_lval(); +} +template <> +inline const value_t::sequence_t& +interactive_t::get<const value_t::sequence_t&>(std::size_t index) { + return value_at(index).as_sequence(); +} + +template <typename T> +class in_context_t : public interactive_t +{ + T& context; + +public: + explicit in_context_t(call_scope_t& args, const string& spec) + : interactive_t(args, spec), context(find_scope<T>(args)) { + TRACE_CTOR(in_context_t, "call_scope_t&, const string&"); + } + virtual ~in_context_t() { + TRACE_DTOR(in_context_t); + } + + T& operator *() { + return context; + } + T * operator->() { + return &context; + } +}; + +string join_args(call_scope_t& args); + +} // namespace ledger + +#endif // _INTERACTIVE_H + diff --git a/src/item.cc b/src/item.cc new file mode 100644 index 00000000..99d1d835 --- /dev/null +++ b/src/item.cc @@ -0,0 +1,467 @@ +/* + * 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 "item.h" +#include "interactive.h" + +namespace ledger { + +bool item_t::use_effective_date = false; + +bool item_t::has_tag(const string& tag) const +{ + DEBUG("item.meta", "Checking if item has tag: " << tag); + if (! metadata) { + DEBUG("item.meta", "Item has no metadata at all"); + return false; + } + string_map::const_iterator i = metadata->find(tag); +#if defined(DEBUG_ON) + if (SHOW_DEBUG("item.meta")) { + if (i == metadata->end()) + DEBUG("item.meta", "Item does not have this tag"); + else + DEBUG("item.meta", "Item has the tag!"); + } +#endif + return i != metadata->end(); +} + +bool item_t::has_tag(const mask_t& tag_mask, + const optional<mask_t>& value_mask) const +{ + if (metadata) { + foreach (const string_map::value_type& data, *metadata) { + if (tag_mask.match(data.first)) { + if (! value_mask) + return true; + else if (data.second) + return value_mask->match(*data.second); + } + } + } + return false; +} + +optional<string> item_t::get_tag(const string& tag) const +{ + DEBUG("item.meta", "Getting item tag: " << tag); + if (metadata) { + DEBUG("item.meta", "Item has metadata"); + string_map::const_iterator i = metadata->find(tag); + if (i != metadata->end()) { + DEBUG("item.meta", "Found the item!"); + return (*i).second; + } + } + return none; +} + +optional<string> item_t::get_tag(const mask_t& tag_mask, + const optional<mask_t>& value_mask) const +{ + if (metadata) { + foreach (const string_map::value_type& data, *metadata) { + if (tag_mask.match(data.first) && + (! value_mask || + (data.second && value_mask->match(*data.second)))) + return data.second; + } + } + return none; +} + +void item_t::set_tag(const string& tag, + const optional<string>& value) +{ + if (! metadata) + metadata = string_map(); + + DEBUG("item.meta", "Setting tag '" << tag << "' to value '" + << (value ? *value : string("<none>")) << "'"); + + std::pair<string_map::iterator, bool> result + = metadata->insert(string_map::value_type(tag, value)); + assert(result.second); +} + +void item_t::parse_tags(const char * p, + optional<date_t::year_type> current_year) +{ + if (const char * b = std::strchr(p, '[')) { + 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 (! std::strchr(p, ':')) + return; + + scoped_array<char> buf(new char[std::strlen(p) + 1]); + + std::strcpy(buf.get(), p); + + string tag; + for (char * q = std::strtok(buf.get(), " \t"); + q; + q = std::strtok(NULL, " \t")) { + const string::size_type len = std::strlen(q); + if (! tag.empty()) { + if (! has_tag(tag)) + set_tag(tag, string(p + (q - buf.get()))); + break; + } + else if (q[0] == ':' && q[len - 1] == ':') { // a series of tags + for (char * r = std::strtok(q + 1, ":"); + r; + r = std::strtok(NULL, ":")) + set_tag(r); + } + else if (q[len - 1] == ':') { // a metadata setting + tag = string(q, len - 1); + } + } +} + +void item_t::append_note(const char * p, + optional<date_t::year_type> current_year) +{ + if (note) { + *note += '\n'; + *note += p; + } else { + note = p; + } + + parse_tags(p, current_year); +} + +namespace { + value_t get_status(item_t& item) { + return long(item.state()); + } + value_t get_uncleared(item_t& item) { + return item.state() == item_t::UNCLEARED; + } + value_t get_cleared(item_t& item) { + return item.state() == item_t::CLEARED; + } + value_t get_pending(item_t& item) { + return item.state() == item_t::PENDING; + } + + value_t get_actual(item_t& item) { + return ! item.has_flags(ITEM_GENERATED | ITEM_TEMP); + } + + value_t get_date(item_t& item) { + return item.date(); + } + value_t get_effective_date(item_t& item) { + if (optional<date_t> effective = item.effective_date()) + return *effective; + return NULL_VALUE; + } + value_t get_note(item_t& item) { + return string_value(item.note ? *item.note : empty_string); + } + + value_t has_tag(call_scope_t& args) { + item_t& item(find_scope<item_t>(args)); + + if (args.size() == 1) { + if (args[0].is_string()) + return item.has_tag(args[0].as_string()); + else if (args[0].is_mask()) + return item.has_tag(args[0].as_mask()); + else + throw_(std::runtime_error, + _("Expected string or mask for argument 1, but received %1") + << args[0].label()); + } + else if (args.size() == 2) { + if (args[0].is_mask() && args[1].is_mask()) + return item.has_tag(args[0].to_mask(), args[1].to_mask()); + else + throw_(std::runtime_error, + _("Expected masks for arguments 1 and 2, but received %1 and %2") + << args[0].label() << args[1].label()); + } + else if (args.size() == 0) { + throw_(std::runtime_error, _("Too few arguments to function")); + } + else { + throw_(std::runtime_error, _("Too many arguments to function")); + } + return false; + } + + value_t get_tag(call_scope_t& scope) { + in_context_t<item_t> env(scope, "s"); + if (optional<string> value = env->get_tag(env.get<string>(0))) + return string_value(*value); + return string_value(empty_string); + } + + value_t get_pathname(item_t& item) { + if (item.pos) + return string_value(item.pos->pathname.string()); + else + return string_value(empty_string); + } + + value_t get_beg_pos(item_t& item) { + return item.pos ? long(item.pos->beg_pos) : 0L; + } + + value_t get_beg_line(item_t& item) { + return item.pos ? long(item.pos->beg_line) : 0L; + } + + value_t get_end_pos(item_t& item) { + return item.pos ? long(item.pos->end_pos) : 0L; + } + + value_t get_end_line(item_t& item) { + return item.pos ? long(item.pos->end_line) : 0L; + } + + value_t get_depth(item_t&) { + return 0L; + } + + value_t ignore(item_t&) { + return false; + } + + template <value_t (*Func)(item_t&)> + value_t get_wrapper(call_scope_t& scope) { + return (*Func)(find_scope<item_t>(scope)); + } +} + +value_t get_comment(item_t& item) +{ + if (! item.note) { + return string_value(""); + } else { + std::ostringstream buf; + if (item.note->length() > 15) + buf << "\n ;"; + else + buf << " ;"; + + bool need_separator = false; + for (const char * p = item.note->c_str(); *p; p++) { + if (*p == '\n') { + need_separator = true; + } else { + if (need_separator) { + buf << "\n ;"; + need_separator = false; + } + buf << *p; + } + } + return string_value(buf.str()); + } +} + +expr_t::ptr_op_t item_t::lookup(const symbol_t::kind_t kind, + const string& name) +{ + if (kind != symbol_t::FUNCTION) + return NULL; + + switch (name[0]) { + case 'a': + if (name == "actual") + return WRAP_FUNCTOR(get_wrapper<&get_actual>); + break; + + case 'b': + if (name == "beg_line") + return WRAP_FUNCTOR(get_wrapper<&get_beg_line>); + else if (name == "beg_pos") + return WRAP_FUNCTOR(get_wrapper<&get_beg_pos>); + break; + + case 'c': + if (name == "cleared") + return WRAP_FUNCTOR(get_wrapper<&get_cleared>); + else if (name == "comment") + return WRAP_FUNCTOR(get_wrapper<&get_comment>); + break; + + case 'd': + if (name[1] == '\0' || name == "date") + return WRAP_FUNCTOR(get_wrapper<&get_date>); + else if (name == "depth") + return WRAP_FUNCTOR(get_wrapper<&get_depth>); + break; + + case 'e': + if (name == "end_line") + return WRAP_FUNCTOR(get_wrapper<&get_end_line>); + else if (name == "end_pos") + return WRAP_FUNCTOR(get_wrapper<&get_end_pos>); + else if (name == "effective_date") + return WRAP_FUNCTOR(get_wrapper<&get_effective_date>); + break; + + case 'f': + if (name == "filename") + return WRAP_FUNCTOR(get_wrapper<&get_pathname>); + break; + + case 'h': + if (name == "has_tag") + return WRAP_FUNCTOR(ledger::has_tag); + else if (name == "has_meta") + return WRAP_FUNCTOR(ledger::has_tag); + break; + + case 'i': + if (name == "is_account") + return WRAP_FUNCTOR(get_wrapper<&ignore>); + break; + + case 'm': + if (name == "meta") + return WRAP_FUNCTOR(ledger::get_tag); + break; + + case 'n': + if (name == "note") + return WRAP_FUNCTOR(get_wrapper<&get_note>); + break; + + case 'p': + if (name == "pending") + return WRAP_FUNCTOR(get_wrapper<&get_pending>); + else if (name == "parent") + return WRAP_FUNCTOR(get_wrapper<&ignore>); + break; + + case 's': + if (name == "status") + return WRAP_FUNCTOR(get_wrapper<&get_status>); + break; + + case 't': + if (name == "tag") + return WRAP_FUNCTOR(ledger::get_tag); + break; + + case 'u': + if (name == "uncleared") + return WRAP_FUNCTOR(get_wrapper<&get_uncleared>); + break; + + case 'L': + if (name[1] == '\0') + return WRAP_FUNCTOR(get_wrapper<&get_actual>); + break; + + case 'X': + if (name[1] == '\0') + return WRAP_FUNCTOR(get_wrapper<&get_cleared>); + break; + + case 'Y': + if (name[1] == '\0') + return WRAP_FUNCTOR(get_wrapper<&get_pending>); + break; + } + + return NULL; +} + +bool item_t::valid() const +{ + if (_state != UNCLEARED && _state != CLEARED && _state != PENDING) { + DEBUG("ledger.validate", "item_t: state is bad"); + return false; + } + + return true; +} + +void print_item(std::ostream& out, const item_t& item, const string& prefix) +{ + out << source_context(item.pos->pathname, item.pos->beg_pos, + item.pos->end_pos, prefix); +} + +string item_context(const item_t& item, const string& desc) +{ + std::streamoff len = item.pos->end_pos - item.pos->beg_pos; + if (! len) + return _("<no item context>"); + + assert(len > 0); + assert(len < 2048); + + std::ostringstream out; + + if (item.pos->pathname == path("/dev/stdin")) { + out << desc << _(" from standard input:"); + return out.str(); + } + + out << desc << _(" from \"") << item.pos->pathname.string() << "\""; + + if (item.pos->beg_line != item.pos->end_line) + out << _(", lines ") << item.pos->beg_line << "-" + << item.pos->end_line << ":\n"; + else + out << _(", line ") << item.pos->beg_line << ":\n"; + + print_item(out, item, "> "); + + return out.str(); +} + +} // namespace ledger diff --git a/src/item.h b/src/item.h new file mode 100644 index 00000000..84385eb7 --- /dev/null +++ b/src/item.h @@ -0,0 +1,215 @@ +/* + * 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. + */ + +/** + * @defgroup data Data representation + */ + +/** + * @file item.h + * @author John Wiegley + * + * @ingroup data + */ +#ifndef _ITEM_H +#define _ITEM_H + +#include "scope.h" + +namespace ledger { + +struct position_t +{ + path pathname; + istream_pos_type beg_pos; + std::size_t beg_line; + istream_pos_type end_pos; + std::size_t end_line; + + position_t() : beg_pos(0), beg_line(0), end_pos(0), end_line(0) { + TRACE_CTOR(position_t, ""); + } + position_t(const position_t& pos) { + TRACE_CTOR(position_t, "copy"); + *this = pos; + } + ~position_t() throw() { + TRACE_DTOR(position_t); + } + + position_t& operator=(const position_t& pos) { + if (this != &pos) { + pathname = pos.pathname; + beg_pos = pos.beg_pos; + beg_line = pos.beg_line; + end_pos = pos.end_pos; + end_line = pos.end_line; + } + return *this; + } + +#if defined(HAVE_BOOST_SERIALIZATION) +private: + /** Serialization. */ + + friend class boost::serialization::access; + + template<class Archive> + void serialize(Archive& ar, const unsigned int /* version */) { + ar & pathname; + ar & beg_pos; + ar & beg_line; + ar & end_pos; + ar & end_line; + } +#endif // HAVE_BOOST_SERIALIZATION +}; + +class item_t : public supports_flags<>, public scope_t +{ +public: +#define ITEM_NORMAL 0x00 // no flags at all, a basic posting +#define ITEM_GENERATED 0x01 // posting was not found in a journal +#define ITEM_TEMP 0x02 // posting is a managed temporary + + enum state_t { UNCLEARED = 0, CLEARED, PENDING }; + + typedef std::map<string, optional<string> > string_map; + + state_t _state; + optional<date_t> _date; + optional<date_t> _date_eff; + optional<string> note; + optional<position_t> pos; + optional<string_map> metadata; + + item_t(flags_t _flags = ITEM_NORMAL, const optional<string>& _note = none) + : supports_flags<>(_flags), _state(UNCLEARED), note(_note) + { + TRACE_CTOR(item_t, "flags_t, const string&"); + } + item_t(const item_t& item) : supports_flags<>(), scope_t() + { + TRACE_CTOR(item_t, "copy"); + copy_details(item); + } + virtual ~item_t() { + TRACE_DTOR(item_t); + } + + void copy_details(const item_t& item) + { + set_flags(item.flags()); + set_state(item.state()); + + _date = item._date; + _date_eff = item._date_eff; + note = item.note; + pos = item.pos; + metadata = item.metadata; + } + + virtual bool operator==(const item_t& xact) { + return this == &xact; + } + virtual bool operator!=(const item_t& xact) { + return ! (*this == xact); + } + + virtual bool has_tag(const string& tag) const; + virtual bool has_tag(const mask_t& tag_mask, + const optional<mask_t>& value_mask = none) const; + + virtual optional<string> get_tag(const string& tag) const; + virtual optional<string> get_tag(const mask_t& tag_mask, + const optional<mask_t>& value_mask = none) const; + + virtual void set_tag(const string& tag, + const optional<string>& value = none); + + virtual void parse_tags(const char * p, + optional<date_t::year_type> current_year = none); + virtual void append_note(const char * p, + optional<date_t::year_type> current_year = none); + + static bool use_effective_date; + + virtual date_t date() const { + assert(_date); + if (use_effective_date) + if (optional<date_t> effective = effective_date()) + return *effective; + return *_date; + } + virtual optional<date_t> effective_date() const { + return _date_eff; + } + + void set_state(state_t new_state) { + _state = new_state; + } + virtual state_t state() const { + return _state; + } + + virtual expr_t::ptr_op_t lookup(const symbol_t::kind_t kind, + const string& name); + + bool valid() const; + +#if defined(HAVE_BOOST_SERIALIZATION) +private: + /** Serialization. */ + + friend class boost::serialization::access; + + template<class Archive> + void serialize(Archive& ar, const unsigned int /* version */) { + ar & boost::serialization::base_object<supports_flags<> >(*this); + ar & boost::serialization::base_object<scope_t>(*this); + ar & _state; + ar & _date; + ar & _date_eff; + ar & note; + ar & pos; + ar & metadata; + } +#endif // HAVE_BOOST_SERIALIZATION +}; + +value_t get_comment(item_t& item); +void print_item(std::ostream& out, const item_t& item, + const string& prefix = ""); +string item_context(const item_t& item, const string& desc); + +} // namespace ledger + +#endif // _ITEM_H diff --git a/src/iterators.cc b/src/iterators.cc new file mode 100644 index 00000000..540ba8ae --- /dev/null +++ b/src/iterators.cc @@ -0,0 +1,258 @@ +/* + * 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 "iterators.h" +#include "journal.h" +#include "compare.h" + +namespace ledger { + +void xacts_iterator::reset(journal_t& journal) +{ + xacts_i = journal.xacts.begin(); + xacts_end = journal.xacts.end(); + xacts_uninitialized = false; +} + +xact_t * xacts_iterator::operator()() +{ + if (xacts_i != xacts_end) + return *xacts_i++; + else + return NULL; +} + +void journal_posts_iterator::reset(journal_t& journal) +{ + xacts.reset(journal); + + xact_t * xact = xacts(); + if (xact != NULL) + posts.reset(*xact); +} + +post_t * journal_posts_iterator::operator()() +{ + post_t * post = posts(); + if (post == NULL) { + xact_t * xact = xacts(); + if (xact != NULL) { + posts.reset(*xact); + post = posts(); + } + } + return post; +} + +void posts_commodities_iterator::reset(journal_t& journal) +{ + journal_posts.reset(journal); + + std::set<commodity_t *> commodities; + + for (post_t * post = journal_posts(); post; post = journal_posts()) { + commodity_t& comm(post->amount.commodity()); + if (comm.flags() & COMMODITY_NOMARKET) + continue; + commodities.insert(&comm); + } + + std::map<string, xact_t *> xacts_by_commodity; + + foreach (commodity_t * comm, commodities) { + if (optional<commodity_t::varied_history_t&> history = + comm->varied_history()) { + account_t * account = journal.master->find_account(comm->symbol()); + + foreach (commodity_t::history_by_commodity_map::value_type pair, + history->histories) { + foreach (commodity_t::history_map::value_type hpair, + pair.second.prices) { + xact_t * xact; + string symbol = hpair.second.commodity().symbol(); + + std::map<string, xact_t *>::iterator i = + xacts_by_commodity.find(symbol); + if (i != xacts_by_commodity.end()) { + xact = (*i).second; + } else { + xact = &temps.create_xact(); + xact_temps.push_back(xact); + xact->payee = symbol; + xact->_date = hpair.first.date(); + xacts_by_commodity.insert + (std::pair<string, xact_t *>(symbol, xact)); + } + + bool post_already_exists = false; + + foreach (post_t * post, xact->posts) { + if (post->_date == hpair.first.date() && + post->amount == hpair.second) { + post_already_exists = true; + break; + } + } + + if (! post_already_exists) { + post_t& temp = temps.create_post(*xact, account); + temp._date = hpair.first.date(); + temp.amount = hpair.second; + + temp.xdata().datetime = hpair.first; + } + } + } + } + } + + xacts.xacts_i = xact_temps.begin(); + xacts.xacts_end = xact_temps.end(); + + xacts.xacts_uninitialized = false; + + xact_t * xact = xacts(); + if (xact != NULL) + posts.reset(*xact); +} + +post_t * posts_commodities_iterator::operator()() +{ + post_t * post = posts(); + if (post == NULL) { + xact_t * xact = xacts(); + if (xact != NULL) { + posts.reset(*xact); + post = posts(); + } + } + return post; +} + +account_t * basic_accounts_iterator::operator()() +{ + while (! accounts_i.empty() && + accounts_i.back() == accounts_end.back()) { + accounts_i.pop_back(); + accounts_end.pop_back(); + } + if (accounts_i.empty()) + return NULL; + + account_t * account = (*(accounts_i.back()++)).second; + assert(account); + + // If this account has children, queue them up to be iterated next. + if (! account->accounts.empty()) + push_back(*account); + + return account; +} + +void sorted_accounts_iterator::push_back(account_t& account) +{ + accounts_list.push_back(accounts_deque_t()); + + if (flatten_all) { + push_all(account, accounts_list.back()); + + std::stable_sort(accounts_list.back().begin(), + accounts_list.back().end(), + compare_items<account_t>(sort_cmp)); + +#if defined(DEBUG_ON) + if (SHOW_DEBUG("accounts.sorted")) { + foreach (account_t * account, accounts_list.back()) + DEBUG("accounts.sorted", + "Account (flat): " << account->fullname()); + } +#endif + } else { + sort_accounts(account, accounts_list.back()); + } + + sorted_accounts_i.push_back(accounts_list.back().begin()); + sorted_accounts_end.push_back(accounts_list.back().end()); +} + +void sorted_accounts_iterator::push_all(account_t& account, + accounts_deque_t& deque) +{ + foreach (accounts_map::value_type& pair, account.accounts) { + deque.push_back(pair.second); + push_all(*pair.second, deque); + } +} + +void sorted_accounts_iterator::sort_accounts(account_t& account, + accounts_deque_t& deque) +{ + foreach (accounts_map::value_type& pair, account.accounts) + deque.push_back(pair.second); + + std::stable_sort(deque.begin(), deque.end(), + compare_items<account_t>(sort_cmp)); + +#if defined(DEBUG_ON) + if (SHOW_DEBUG("accounts.sorted")) { + foreach (account_t * account, deque) + DEBUG("accounts.sorted", "Account: " << account->fullname()); + } +#endif +} + +account_t * sorted_accounts_iterator::operator()() +{ + while (! sorted_accounts_i.empty() && + sorted_accounts_i.back() == sorted_accounts_end.back()) { + sorted_accounts_i.pop_back(); + sorted_accounts_end.pop_back(); + assert(! accounts_list.empty()); + accounts_list.pop_back(); + } + if (sorted_accounts_i.empty()) + return NULL; + + account_t * account = *sorted_accounts_i.back()++; + assert(account); + + // If this account has children, queue them up to be iterated next. + if (! flatten_all && ! account->accounts.empty()) + push_back(*account); + + // Make sure the sorting value gets recalculated for this account + account->xdata().drop_flags(ACCOUNT_EXT_SORT_CALC); + return account; +} + +} // namespace ledger diff --git a/src/iterators.h b/src/iterators.h new file mode 100644 index 00000000..1cbe4c25 --- /dev/null +++ b/src/iterators.h @@ -0,0 +1,230 @@ +/* + * 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. + */ + +/** + * @addtogroup data + */ + +/** + * @file iterators.h + * @author John Wiegley + * + * @ingroup data + */ +#ifndef _ITERATORS_H +#define _ITERATORS_H + +#include "xact.h" +#include "post.h" +#include "account.h" +#include "temps.h" + +namespace ledger { + +class journal_t; + +class posts_iterator : public noncopyable +{ +public: + virtual ~posts_iterator() throw() {} + virtual post_t * operator()() = 0; +}; + +class xact_posts_iterator : public posts_iterator +{ + posts_list::iterator posts_i; + posts_list::iterator posts_end; + + bool posts_uninitialized; + +public: + xact_posts_iterator() : posts_uninitialized(true) { + TRACE_CTOR(xact_posts_iterator, ""); + } + xact_posts_iterator(xact_t& xact) + : posts_uninitialized(true) { + TRACE_CTOR(xact_posts_iterator, "xact_t&"); + reset(xact); + } + virtual ~xact_posts_iterator() throw() { + TRACE_DTOR(xact_posts_iterator); + } + + void reset(xact_t& xact) { + posts_i = xact.posts.begin(); + posts_end = xact.posts.end(); + + posts_uninitialized = false; + } + + virtual post_t * operator()() { + if (posts_uninitialized || posts_i == posts_end) + return NULL; + return *posts_i++; + } +}; + +class xacts_iterator : public noncopyable +{ +public: + xacts_list::iterator xacts_i; + xacts_list::iterator xacts_end; + + bool xacts_uninitialized; + + xacts_iterator() : xacts_uninitialized(true) { + TRACE_CTOR(xacts_iterator, ""); + } + xacts_iterator(journal_t& journal) : xacts_uninitialized(true) { + TRACE_CTOR(xacts_iterator, "journal_t&"); + reset(journal); + } + virtual ~xacts_iterator() throw() { + TRACE_DTOR(xacts_iterator); + } + + void reset(journal_t& journal); + + xact_t * operator()(); +}; + +class journal_posts_iterator : public posts_iterator +{ + xacts_iterator xacts; + xact_posts_iterator posts; + +public: + journal_posts_iterator() { + TRACE_CTOR(journal_posts_iterator, ""); + } + journal_posts_iterator(journal_t& journal) { + TRACE_CTOR(journal_posts_iterator, "journal_t&"); + reset(journal); + } + virtual ~journal_posts_iterator() throw() { + TRACE_DTOR(journal_posts_iterator); + } + + void reset(journal_t& journal); + + virtual post_t * operator()(); +}; + +class posts_commodities_iterator : public posts_iterator +{ +protected: + journal_posts_iterator journal_posts; + xacts_iterator xacts; + xact_posts_iterator posts; + temporaries_t temps; + xacts_list xact_temps; + +public: + posts_commodities_iterator() { + TRACE_CTOR(posts_commodities_iterator, ""); + } + posts_commodities_iterator(journal_t& journal) { + TRACE_CTOR(posts_commodities_iterator, "journal_t&"); + reset(journal); + } + virtual ~posts_commodities_iterator() throw() { + TRACE_DTOR(posts_commodities_iterator); + } + + void reset(journal_t& journal); + + virtual post_t * operator()(); +}; + +class accounts_iterator : public noncopyable +{ +public: + virtual ~accounts_iterator() throw() {} + virtual account_t * operator()() = 0; +}; + +class basic_accounts_iterator : public accounts_iterator +{ + std::list<accounts_map::const_iterator> accounts_i; + std::list<accounts_map::const_iterator> accounts_end; + +public: + basic_accounts_iterator() { + TRACE_CTOR(basic_accounts_iterator, ""); + } + basic_accounts_iterator(account_t& account) { + TRACE_CTOR(basic_accounts_iterator, "account_t&"); + push_back(account); + } + virtual ~basic_accounts_iterator() throw() { + TRACE_DTOR(basic_accounts_iterator); + } + + void push_back(account_t& account) { + accounts_i.push_back(account.accounts.begin()); + accounts_end.push_back(account.accounts.end()); + } + + virtual account_t * operator()(); +}; + +class sorted_accounts_iterator : public accounts_iterator +{ + expr_t sort_cmp; + bool flatten_all; + + typedef std::deque<account_t *> accounts_deque_t; + + std::list<accounts_deque_t> accounts_list; + std::list<accounts_deque_t::const_iterator> sorted_accounts_i; + std::list<accounts_deque_t::const_iterator> sorted_accounts_end; + +public: + sorted_accounts_iterator(account_t& account, + const expr_t& _sort_cmp, bool _flatten_all) + : sort_cmp(_sort_cmp), flatten_all(_flatten_all) { + TRACE_CTOR(sorted_accounts_iterator, "const expr_t&, bool, account_t&"); + push_back(account); + } + virtual ~sorted_accounts_iterator() throw() { + TRACE_DTOR(sorted_accounts_iterator); + } + + void push_back(account_t& account); + void push_all(account_t& account, accounts_deque_t& deque); + void sort_accounts(account_t& account, accounts_deque_t& deque); + + virtual account_t * operator()(); +}; + +} // namespace ledger + +#endif // _ITERATORS_H diff --git a/src/journal.cc b/src/journal.cc new file mode 100644 index 00000000..6ebccd66 --- /dev/null +++ b/src/journal.cc @@ -0,0 +1,251 @@ +/* + * 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 "journal.h" +#include "amount.h" +#include "commodity.h" +#include "pool.h" +#include "xact.h" +#include "account.h" + +namespace ledger { + +journal_t::journal_t() +{ + TRACE_CTOR(journal_t, ""); + initialize(); +} + +journal_t::journal_t(const path& pathname) +{ + TRACE_CTOR(journal_t, "path"); + initialize(); + read(pathname); +} + +journal_t::journal_t(const string& str) +{ + TRACE_CTOR(journal_t, "string"); + initialize(); + read(str); +} + +journal_t::~journal_t() +{ + TRACE_DTOR(journal_t); + + // Don't bother unhooking each xact's posts from the + // accounts they refer to, because all accounts are about to + // be deleted. + foreach (xact_t * xact, xacts) + checked_delete(xact); + + foreach (auto_xact_t * xact, auto_xacts) + checked_delete(xact); + + foreach (period_xact_t * xact, period_xacts) + checked_delete(xact); + + checked_delete(master); +} + +void journal_t::initialize() +{ + master = new account_t; + bucket = NULL; + was_loaded = false; +} + +void journal_t::add_account(account_t * acct) +{ + master->add_account(acct); +} + +bool journal_t::remove_account(account_t * acct) +{ + return master->remove_account(acct); +} + +account_t * journal_t::find_account(const string& name, bool auto_create) +{ + return master->find_account(name, auto_create); +} + +account_t * journal_t::find_account_re(const string& regexp) +{ + return master->find_account_re(regexp); +} + +bool journal_t::add_xact(xact_t * xact) +{ + xact->journal = this; + + if (! xact->finalize()) { + xact->journal = NULL; + return false; + } + + extend_xact(xact); + xacts.push_back(xact); + + return true; +} + +void journal_t::extend_xact(xact_base_t * xact) +{ + foreach (auto_xact_t * auto_xact, auto_xacts) + auto_xact->extend_xact(*xact); +} + +bool journal_t::remove_xact(xact_t * xact) +{ + bool found = false; + xacts_list::iterator i; + for (i = xacts.begin(); i != xacts.end(); i++) + if (*i == xact) { + found = true; + break; + } + if (! found) + return false; + + xacts.erase(i); + xact->journal = NULL; + + return true; +} + +std::size_t journal_t::read(std::istream& in, + const path& pathname, + account_t * master_alt, + scope_t * scope) +{ + std::size_t count = 0; + try { + if (! scope) + scope = scope_t::default_scope; + + if (! scope) + throw_(std::runtime_error, + _("No default scope in which to read journal file '%1'") + << pathname); + + value_t strict = expr_t("strict").calc(*scope); + + count = parse(in, *scope, master_alt ? master_alt : master, + &pathname, strict.to_boolean()); + } + catch (...) { + clear_xdata(); + throw; + } + + // xdata may have been set for some accounts and transaction due to the use + // of balance assertions or other calculations performed in valexpr-based + // posting amounts. + clear_xdata(); + + return count; +} + +std::size_t journal_t::read(const path& pathname, + account_t * master, + scope_t * scope) +{ + path filename = resolve_path(pathname); + + if (! exists(filename)) + throw_(std::runtime_error, + _("Cannot read journal file '%1'") << filename); + + ifstream stream(filename); + std::size_t count = read(stream, filename, master, scope); + if (count > 0) + sources.push_back(fileinfo_t(filename)); + return count; +} + +bool journal_t::has_xdata() +{ + foreach (xact_t * xact, xacts) + if (xact->has_xdata()) + return true; + + foreach (auto_xact_t * xact, auto_xacts) + if (xact->has_xdata()) + return true; + + foreach (period_xact_t * xact, period_xacts) + if (xact->has_xdata()) + return true; + + if (master->has_xdata() || master->children_with_xdata()) + return true; + + return false; +} + +void journal_t::clear_xdata() +{ + foreach (xact_t * xact, xacts) + if (! xact->has_flags(ITEM_TEMP)) + xact->clear_xdata(); + + foreach (auto_xact_t * xact, auto_xacts) + if (! xact->has_flags(ITEM_TEMP)) + xact->clear_xdata(); + + foreach (period_xact_t * xact, period_xacts) + if (! xact->has_flags(ITEM_TEMP)) + xact->clear_xdata(); + + master->clear_xdata(); +} + +bool journal_t::valid() const +{ + if (! master->valid()) { + DEBUG("ledger.validate", "journal_t: master not valid"); + return false; + } + + foreach (const xact_t * xact, xacts) + if (! xact->valid()) { + DEBUG("ledger.validate", "journal_t: xact not valid"); + return false; + } + + return true; +} + +} // namespace ledger diff --git a/src/journal.h b/src/journal.h new file mode 100644 index 00000000..8d59e3b4 --- /dev/null +++ b/src/journal.h @@ -0,0 +1,197 @@ +/* + * 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. + */ + +/** + * @addtogroup data + */ + +/** + * @file journal.h + * @author John Wiegley + * + * @ingroup data + */ +#ifndef _JOURNAL_H +#define _JOURNAL_H + +#include "utils.h" +#include "times.h" + +namespace ledger { + +class xact_base_t; +class xact_t; +class auto_xact_t; +class period_xact_t; +class account_t; +class scope_t; + +typedef std::list<xact_t *> xacts_list; +typedef std::list<auto_xact_t *> auto_xacts_list; +typedef std::list<period_xact_t *> period_xacts_list; + +class journal_t : public noncopyable +{ +public: + struct fileinfo_t + { + optional<path> filename; + uintmax_t size; + datetime_t modtime; + bool from_stream; + + fileinfo_t() : size(0), from_stream(true) { + TRACE_CTOR(journal_t::fileinfo_t, ""); + } + fileinfo_t(const path& _filename) + : filename(_filename), from_stream(false) { + TRACE_CTOR(journal_t::fileinfo_t, "const path&"); + size = file_size(*filename); + modtime = posix_time::from_time_t(last_write_time(*filename)); + } + fileinfo_t(const fileinfo_t& info) + : filename(info.filename), size(info.size), + modtime(info.modtime), from_stream(info.from_stream) + { + TRACE_CTOR(journal_t::fileinfo_t, "copy"); + } + ~fileinfo_t() throw() { + TRACE_DTOR(journal_t::fileinfo_t); + } + +#if defined(HAVE_BOOST_SERIALIZATION) + private: + /** Serialization. */ + + friend class boost::serialization::access; + + template<class Archive> + void serialize(Archive& ar, const unsigned int /* version */) { + ar & filename; + ar & size; + ar & modtime; + ar & from_stream; + } +#endif // HAVE_BOOST_SERIALIZATION + }; + + account_t * master; + account_t * bucket; + xacts_list xacts; + auto_xacts_list auto_xacts; + period_xacts_list period_xacts; + std::list<fileinfo_t> sources; + bool was_loaded; + + journal_t(); + journal_t(const path& pathname); + journal_t(const string& str); + ~journal_t(); + + void initialize(); + + std::list<fileinfo_t>::iterator sources_begin() { + return sources.begin(); + } + std::list<fileinfo_t>::iterator sources_end() { + return sources.end(); + } + + // These four methods are delegated to the current session, since all + // accounts processed are gathered together at the session level. + void add_account(account_t * acct); + bool remove_account(account_t * acct); + account_t * find_account(const string& name, bool auto_create = true); + account_t * find_account_re(const string& regexp); + + bool add_xact(xact_t * xact); + void extend_xact(xact_base_t * xact); + bool remove_xact(xact_t * xact); + + xacts_list::iterator xacts_begin() { + return xacts.begin(); + } + xacts_list::iterator xacts_end() { + return xacts.end(); + } + auto_xacts_list::iterator auto_xacts_begin() { + return auto_xacts.begin(); + } + auto_xacts_list::iterator auto_xacts_end() { + return auto_xacts.end(); + } + period_xacts_list::iterator period_xacts_begin() { + return period_xacts.begin(); + } + period_xacts_list::iterator period_xacts_end() { + return period_xacts.end(); + } + + std::size_t read(std::istream& in, + const path& pathname, + account_t * master = NULL, + scope_t * scope = NULL); + std::size_t read(const path& pathname, + account_t * master = NULL, + scope_t * scope = NULL); + + std::size_t parse(std::istream& in, + scope_t& session_scope, + account_t * master = NULL, + const path * original_file = NULL, + bool strict = false); + + bool has_xdata(); + void clear_xdata(); + + bool valid() const; + +#if defined(HAVE_BOOST_SERIALIZATION) +private: + /** Serialization. */ + + friend class boost::serialization::access; + + template<class Archive> + void serialize(Archive& ar, const unsigned int /* version */) { + ar & master; + ar & bucket; + ar & xacts; + ar & auto_xacts; + ar & period_xacts; + ar & sources; + } +#endif // HAVE_BOOST_SERIALIZATION +}; + +} // namespace ledger + +#endif // _JOURNAL_H diff --git a/src/ledger.h b/src/ledger.h deleted file mode 100644 index 76e10d7b..00000000 --- a/src/ledger.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2003-2007, 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. - */ - -#ifndef _LEDGER_H -#define _LEDGER_H - -////////////////////////////////////////////////////////////////////// -// -// Ledger Accounting Tool -// -// A command-line tool for general double-entry accounting. -// -// Copyright (c) 2003,2004 John Wiegley <johnw@newartisans.com> -// - -#include <amount.h> -#include <balance.h> -#include <value.h> -#include <xpath.h> -#include <session.h> -#include <journal.h> -#include <parser.h> -#include <textual.h> -#include <binary.h> -#include <report.h> -#include <transform.h> - -#endif // _LEDGER_H diff --git a/src/main.cc b/src/main.cc new file mode 100644 index 00000000..0aec8886 --- /dev/null +++ b/src/main.cc @@ -0,0 +1,217 @@ +/* + * 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 "global.h" // This is where the meat of main() is, which + // was moved there for the sake of clarity here +#include "session.h" + +using namespace ledger; + +#ifdef HAVE_BOOST_PYTHON +namespace ledger { + extern char * argv0; +} +#endif + +int main(int argc, char * argv[], char * envp[]) +{ + int status = 1; + +#ifdef HAVE_BOOST_PYTHON + argv0 = argv[0]; +#endif + + // The very first thing we do is handle some very special command-line + // options, since they affect how the environment is setup: + // + // --verify ; turns on memory tracing + // --verbose ; turns on logging + // --debug CATEGORY ; turns on debug logging + // --trace LEVEL ; turns on trace logging + handle_debug_options(argc, argv); +#if defined(VERIFY_ON) + IF_VERIFY() initialize_memory_tracing(); +#endif + + INFO("Ledger starting"); + + // Initialize global Boost/C++ environment + std::ios::sync_with_stdio(false); + filesystem::path::default_name_check(filesystem::portable_posix_name); + + std::signal(SIGINT, sigint_handler); + std::signal(SIGPIPE, sigpipe_handler); + +#if defined(HAVE_GETTEXT) + ::textdomain("ledger"); +#endif + + std::auto_ptr<global_scope_t> global_scope; + + try { + // Create the session object, which maintains nearly all state relating to + // this invocation of Ledger; and register all known journal parsers. + global_scope.reset(new global_scope_t(envp)); + + global_scope->session().set_flush_on_next_data_file(true); + + // Construct an STL-style argument list from the process command arguments + strings_list args; + for (int i = 1; i < argc; i++) + args.push_back(argv[i]); + + // Look for options and a command verb in the command-line arguments + bind_scope_t bound_scope(*global_scope.get(), global_scope->report()); + + args = global_scope->read_command_arguments(bound_scope, args); + + if (global_scope->HANDLED(script_)) { + // Ledger is being invoked as a script command interpreter + global_scope->session().read_journal_files(); + + status = 0; + + ifstream in(global_scope->HANDLER(script_).str()); + while (status == 0 && ! in.eof()) { + char line[1024]; + in.getline(line, 1023); + + char * p = skip_ws(line); + if (*p && *p != '#') + status = global_scope->execute_command_wrapper(split_arguments(p), + true); + } + } + else if (! args.empty()) { + // User has invoke a verb at the interactive command-line + status = global_scope->execute_command_wrapper(args, false); + } + else { + // Commence the REPL by displaying the current Ledger version + global_scope->show_version_info(std::cout); + + global_scope->session().read_journal_files(); + + bool exit_loop = false; + +#ifdef HAVE_LIBEDIT + + rl_readline_name = const_cast<char *>("Ledger"); +#if 0 + // jww (2009-02-05): NYI + rl_attempted_completion_function = ledger_completion; +#endif + + while (char * p = readline(global_scope->prompt_string())) { + char * expansion = NULL; + int result; + + result = history_expand(skip_ws(p), &expansion); + + if (result < 0 || result == 2) { + if (expansion) + std::free(expansion); + std::free(p); + throw_(std::logic_error, + _("Failed to expand history reference '%1'") << p); + } + else if (expansion) { + add_history(expansion); + } + +#else // HAVE_LIBEDIT + + while (! std::cin.eof()) { + std::cout << global_scope->prompt_string(); + char line[1024]; + std::cin.getline(line, 1023); + + char * p = skip_ws(line); + +#endif // HAVE_LIBEDIT + + check_for_signal(); + + if (*p && *p != '#') { + if (std::strncmp(p, "quit", 4) == 0) + exit_loop = true; + else + global_scope->execute_command_wrapper(split_arguments(p), true); + } + +#ifdef HAVE_LIBEDIT + if (expansion) + std::free(expansion); + std::free(p); +#endif + + if (exit_loop) + break; + } + + status = 0; // report success + } + } + catch (const std::exception& err) { + if (global_scope.get()) + global_scope->report_error(err); + else + std::cerr << "Exception during initialization: " << err.what() + << std::endl; + } + catch (int _status) { + status = _status; // used for a "quick" exit, and is used only + // if help text (such as --help) was displayed + } + + // If memory verification is being performed (which can be very slow), clean + // up everything by closing the session and deleting the session object, and + // then shutting down the memory tracing subsystem. Otherwise, let it all + // leak because we're about to exit anyway. + IF_VERIFY() { + global_scope.reset(); + + INFO("Ledger ended (Boost/libstdc++ may still hold memory)"); +#if defined(VERIFY_ON) + shutdown_memory_tracing(); +#endif + } else { + INFO("Ledger ended"); + } + + // Return the final status to the operating system, either 1 for error or 0 + // for a successful completion. + return status; +} + +// main.cc ends here. diff --git a/src/utility/mask.cc b/src/mask.cc index 959df8ea..c1e66ced 100644 --- a/src/utility/mask.cc +++ b/src/mask.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2007, John Wiegley. All rights reserved. + * 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 @@ -29,27 +29,27 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#include <system.hh> + #include "mask.h" namespace ledger { -mask_t::mask_t(const string& pat) : exclude(false) +mask_t::mask_t(const string& pat) : expr() { - const char * p = pat.c_str(); - - if (*p == '-') { - exclude = true; - p++; - while (std::isspace(*p)) - p++; - } - else if (*p == '+') { - p++; - while (std::isspace(*p)) - p++; - } + TRACE_CTOR(mask_t, "const string&"); + *this = pat; +} - expr.assign(p); +mask_t& mask_t::operator=(const string& pat) +{ +#if defined(HAVE_BOOST_REGEX_UNICODE) + expr = boost::make_u32regex(pat.c_str(), boost::regex::perl | boost::regex::icase); +#else + expr.assign(pat.c_str(), boost::regex::perl | boost::regex::icase); +#endif + VERIFY(valid()); + return *this; } } // namespace ledger diff --git a/src/mask.h b/src/mask.h new file mode 100644 index 00000000..4608898f --- /dev/null +++ b/src/mask.h @@ -0,0 +1,157 @@ +/* + * 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. + */ + +/** + * @addtogroup util + */ + +/** + * @file mask.h + * @author John Wiegley + * + * @ingroup util + * + * @brief Regular expression masking. + */ +#ifndef _MASK_H +#define _MASK_H + +#include "utils.h" +#if defined(HAVE_BOOST_REGEX_UNICODE) +#include "unistring.h" +#endif + +namespace ledger { + +class mask_t +{ +public: +#if defined(HAVE_BOOST_REGEX_UNICODE) + boost::u32regex expr; +#else + boost::regex expr; +#endif + + explicit mask_t(const string& pattern); + + mask_t() : expr() { + TRACE_CTOR(mask_t, ""); + } + mask_t(const mask_t& m) : expr(m.expr) { + TRACE_CTOR(mask_t, "copy"); + } + ~mask_t() throw() { + TRACE_DTOR(mask_t); + } + + mask_t& operator=(const string& other); + + bool operator==(const mask_t& other) const { + return expr == other.expr; + } + + bool match(const string& text) const { +#if defined(HAVE_BOOST_REGEX_UNICODE) + DEBUG("mask.match", + "Matching: \"" << text << "\" =~ /" << str() << "/ = " + << (boost::u32regex_search(text, expr) ? "true" : "false")); + return boost::u32regex_search(text, expr); +#else + DEBUG("mask.match", + "Matching: \"" << text << "\" =~ /" << str() << "/ = " + << (boost::regex_search(text, expr) ? "true" : "false")); + return boost::regex_search(text, expr); +#endif + } + + bool empty() const { + return expr.empty(); + } + + string str() const { + if (! empty()) { +#if defined(HAVE_BOOST_REGEX_UNICODE) + assert(sizeof(boost::uint32_t) == sizeof(UChar32)); + unistring ustr; + std::basic_string<UChar32> expr_str = expr.str(); + std::copy(expr_str.begin(), expr_str.end(), + std::back_inserter(ustr.utf32chars)); + return ustr.extract(); +#else + return expr.str(); +#endif + } else { + return empty_string; + } + } + + bool valid() const { + if (expr.status() != 0) { + DEBUG("ledger.validate", "mask_t: expr.status() != 0"); + return false; + } + return true; + } + +#if defined(HAVE_BOOST_SERIALIZATION) +private: + /** Serialization. */ + + friend class boost::serialization::access; + + template<class Archive> + void serialize(Archive& ar, const unsigned int /* version */) { + string temp; + if (Archive::is_loading::value) { + ar & temp; + *this = temp; + } else { + temp = str(); + ar & temp; + } + } +#endif // HAVE_BOOST_SERIALIZATION +}; + +inline std::ostream& operator<<(std::ostream& out, const mask_t& mask) { + out << mask.str(); + return out; +} + +inline void to_xml(std::ostream& out, const mask_t& mask) +{ + push_xml x(out, "mask"); + out << x.guard(mask.str()); +} + +} // namespace ledger + +#endif // _MASK_H diff --git a/src/numerics/amount.cc b/src/numerics/amount.cc deleted file mode 100644 index c7139052..00000000 --- a/src/numerics/amount.cc +++ /dev/null @@ -1,1401 +0,0 @@ -/* - * Copyright (c) 2003-2007, 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. - */ - -/** - * @file amount.cc - * @author John Wiegley - * @date Thu Apr 26 15:19:46 2007 - * - * @brief Types for handling commoditized math. - * - * This file defines member functions for amount_t, and also defines a - * helper class, bigint_t, which is used as a refcounted wrapper - * around libgmp's mpz_t type. - */ - -#include "amount.h" -#include "binary.h" -#include "parser.h" - -namespace ledger { - -commodity_pool_t * amount_t::current_pool = NULL; - -bool amount_t::keep_base = false; - -bool amount_t::keep_price = false; -bool amount_t::keep_date = false; -bool amount_t::keep_tag = false; - -bool amount_t::stream_fullstrings = false; - -#ifndef THREADSAFE -/** - * These global temporaries are pre-initialized for the sake of - * efficiency, and reused over and over again. - */ -static mpz_t temp; -static mpz_t divisor; -#endif - -struct amount_t::bigint_t : public supports_flags<> -{ -#define BIGINT_BULK_ALLOC 0x01 -#define BIGINT_KEEP_PREC 0x02 - - mpz_t val; - precision_t prec; - uint_least16_t ref; - uint_fast32_t index; - -#define MPZ(bigint) ((bigint)->val) - - bigint_t() : prec(0), ref(1), index(0) { - TRACE_CTOR(bigint_t, ""); - mpz_init(val); - } - bigint_t(mpz_t _val) : prec(0), ref(1), index(0) { - TRACE_CTOR(bigint_t, "mpz_t"); - mpz_init_set(val, _val); - } - bigint_t(const bigint_t& other) - : supports_flags<>(other.flags() & BIGINT_KEEP_PREC), - prec(other.prec), ref(1), index(0) { - TRACE_CTOR(bigint_t, "copy"); - mpz_init_set(val, other.val); - } - ~bigint_t() { - TRACE_DTOR(bigint_t); - assert(ref == 0); - mpz_clear(val); - } -}; - -void amount_t::initialize() -{ - mpz_init(temp); - mpz_init(divisor); - - // jww (2007-05-02): Be very careful here! - if (! current_pool) - current_pool = new commodity_pool_t; - - // Add time commodity conversions, so that timelog's may be parsed - // in terms of seconds, but reported as minutes or hours. - if (commodity_t * commodity = current_pool->create("s")) { - commodity->add_flags(COMMODITY_STYLE_NOMARKET | COMMODITY_STYLE_BUILTIN); - - parse_conversion("1.0m", "60s"); - parse_conversion("1.0h", "60m"); - } else { - assert(false); - } -} - -void amount_t::shutdown() -{ - mpz_clear(temp); - mpz_clear(divisor); - - // jww (2007-05-02): Be very careful here! - if (current_pool) { - checked_delete(current_pool); - current_pool = NULL; - } -} - -void amount_t::_copy(const amount_t& amt) -{ - if (quantity != amt.quantity) { - if (quantity) - _release(); - - // Never maintain a pointer into a bulk allocation pool; such - // pointers are not guaranteed to remain. - if (amt.quantity->has_flags(BIGINT_BULK_ALLOC)) { - quantity = new bigint_t(*amt.quantity); - } else { - quantity = amt.quantity; - DEBUG("amounts.refs", - quantity << " ref++, now " << (quantity->ref + 1)); - quantity->ref++; - } - } - commodity_ = amt.commodity_; -} - -void amount_t::_dup() -{ - if (quantity->ref > 1) { - bigint_t * q = new bigint_t(*quantity); - _release(); - quantity = q; - } -} - -void amount_t::_resize(precision_t prec) -{ - assert(prec < 256); - - if (! quantity || prec == quantity->prec) - return; - - _dup(); - - assert(prec > quantity->prec); - mpz_ui_pow_ui(divisor, 10, prec - quantity->prec); - mpz_mul(MPZ(quantity), MPZ(quantity), divisor); - - quantity->prec = prec; -} - -void amount_t::_clear() -{ - if (quantity) { - _release(); - quantity = NULL; - commodity_ = NULL; - } else { - assert(! commodity_); - } -} - -void amount_t::_release() -{ - DEBUG("amounts.refs", quantity << " ref--, now " << (quantity->ref - 1)); - - if (--quantity->ref == 0) { - if (quantity->has_flags(BIGINT_BULK_ALLOC)) - quantity->~bigint_t(); - else - checked_delete(quantity); - } -} - - -namespace { - amount_t::precision_t convert_double(mpz_t dest, double val) - { -#ifndef HAVE_GDTOA - // This code is far too imprecise to be worthwhile. - - mpf_t temp; - mpf_init_set_d(temp, val); - - mp_exp_t exp; - char * buf = mpf_get_str(NULL, &exp, 10, 1000, temp); - - int len = std::strlen(buf); - if (len > 0 && buf[0] == '-') - exp++; - - if (exp <= len) { - exp = len - exp; - } else { - // There were trailing zeros, which we have to put back on in - // order to convert this buffer into an integer. - - int zeroes = exp - len; - - char * newbuf = (char *)std::malloc(len + zeroes); - std::strcpy(newbuf, buf); - - int i; - for (i = 0; i < zeroes; i++) - newbuf[len + i] = '0'; - newbuf[len + i] = '\0'; - - free(buf); - buf = newbuf; - - exp = (len - exp) + zeroes; - } - - mpz_set_str(dest, buf, 10); - free(buf); - - return amount_t::precision_t(exp); -#else - int decpt, sign; - char * buf = dtoa(val, 0, 0, &decpt, &sign, NULL); - char * result; - int len = std::strlen(buf); - - if (decpt <= len) { - decpt = len - decpt; - result = NULL; - } else { - // There were trailing zeros, which we have to put back on in - // order to convert this buffer into an integer. - - int zeroes = decpt - len; - result = new char[len + zeroes + 1]; - - std::strcpy(result, buf); - int i; - for (i = 0; i < zeroes; i++) - result[len + i] = '0'; - result[len + i] = '\0'; - - decpt = (len - decpt) + zeroes; - } - - if (sign) { - char * newbuf = new char[std::strlen(result ? result : buf) + 2]; - newbuf[0] = '-'; - std::strcpy(&newbuf[1], result ? result : buf); - mpz_set_str(dest, newbuf, 10); - checked_array_delete(newbuf); - } else { - mpz_set_str(dest, result ? result : buf, 10); - } - - if (result) - checked_array_delete(result); - freedtoa(buf); - - return decpt; -#endif - } -} - -amount_t::amount_t(const double val) : commodity_(NULL) -{ - TRACE_CTOR(amount_t, "const double"); - quantity = new bigint_t; - quantity->prec = convert_double(MPZ(quantity), val); -} - -amount_t::amount_t(const unsigned long val) : commodity_(NULL) -{ - TRACE_CTOR(amount_t, "const unsigned long"); - quantity = new bigint_t; - mpz_set_ui(MPZ(quantity), val); -} - -amount_t::amount_t(const long val) : commodity_(NULL) -{ - TRACE_CTOR(amount_t, "const long"); - quantity = new bigint_t; - mpz_set_si(MPZ(quantity), val); -} - - -amount_t& amount_t::operator=(const amount_t& amt) -{ - if (this != &amt) { - if (amt.quantity) - _copy(amt); - else if (quantity) - _clear(); - } - return *this; -} - - -int amount_t::compare(const amount_t& amt) const -{ - if (! quantity || ! amt.quantity) { - if (quantity) - throw_(amount_error, "Cannot compare an amount to an uninitialized amount"); - else if (amt.quantity) - throw_(amount_error, "Cannot compare an uninitialized amount to an amount"); - else - throw_(amount_error, "Cannot compare two uninitialized amounts"); - } - - if (has_commodity() && amt.has_commodity() && - commodity() != amt.commodity()) - throw_(amount_error, - "Cannot compare amounts with different commodities: " << - commodity().symbol() << " and " << amt.commodity().symbol()); - - if (quantity->prec == amt.quantity->prec) { - return mpz_cmp(MPZ(quantity), MPZ(amt.quantity)); - } - else if (quantity->prec < amt.quantity->prec) { - amount_t t(*this); - t._resize(amt.quantity->prec); - return mpz_cmp(MPZ(t.quantity), MPZ(amt.quantity)); - } - else { - amount_t t = amt; - t._resize(quantity->prec); - return mpz_cmp(MPZ(quantity), MPZ(t.quantity)); - } -} - - -amount_t& amount_t::operator+=(const amount_t& amt) -{ - if (! quantity || ! amt.quantity) { - if (quantity) - throw_(amount_error, "Cannot add an amount to an uninitialized amount"); - else if (amt.quantity) - throw_(amount_error, "Cannot add an uninitialized amount to an amount"); - else - throw_(amount_error, "Cannot add two uninitialized amounts"); - } - - if (commodity() != amt.commodity()) - throw_(amount_error, - "Adding amounts with different commodities: " << - (has_commodity() ? commodity().symbol() : "NONE") << - " != " << - (amt.has_commodity() ? amt.commodity().symbol() : "NONE")); - - _dup(); - - if (quantity->prec == amt.quantity->prec) { - mpz_add(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity)); - } - else if (quantity->prec < amt.quantity->prec) { - _resize(amt.quantity->prec); - mpz_add(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity)); - } - else { - amount_t t = amt; - t._resize(quantity->prec); - mpz_add(MPZ(quantity), MPZ(quantity), MPZ(t.quantity)); - } - - return *this; -} - -amount_t& amount_t::operator-=(const amount_t& amt) -{ - if (! quantity || ! amt.quantity) { - if (quantity) - throw_(amount_error, "Cannot subtract an amount from an uninitialized amount"); - else if (amt.quantity) - throw_(amount_error, "Cannot subtract an uninitialized amount from an amount"); - else - throw_(amount_error, "Cannot subtract two uninitialized amounts"); - } - - if (commodity() != amt.commodity()) - throw_(amount_error, - "Subtracting amounts with different commodities: " << - (has_commodity() ? commodity().symbol() : "NONE") << - " != " << - (amt.has_commodity() ? amt.commodity().symbol() : "NONE")); - - _dup(); - - if (quantity->prec == amt.quantity->prec) { - mpz_sub(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity)); - } - else if (quantity->prec < amt.quantity->prec) { - _resize(amt.quantity->prec); - mpz_sub(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity)); - } - else { - amount_t t = amt; - t._resize(quantity->prec); - mpz_sub(MPZ(quantity), MPZ(quantity), MPZ(t.quantity)); - } - - return *this; -} - -namespace { - void mpz_round(mpz_t out, mpz_t value, int value_prec, int round_prec) - { - // Round `value', with an encoding precision of `value_prec', to a - // rounded value with precision `round_prec'. Result is stored in - // `out'. - - assert(value_prec > round_prec); - - mpz_t quotient; - mpz_t remainder; - - mpz_init(quotient); - mpz_init(remainder); - - mpz_ui_pow_ui(divisor, 10, value_prec - round_prec); - mpz_tdiv_qr(quotient, remainder, value, divisor); - mpz_divexact_ui(divisor, divisor, 10); - mpz_mul_ui(divisor, divisor, 5); - - if (mpz_sgn(remainder) < 0) { - mpz_neg(divisor, divisor); - if (mpz_cmp(remainder, divisor) < 0) { - mpz_ui_pow_ui(divisor, 10, value_prec - round_prec); - mpz_add(remainder, divisor, remainder); - mpz_ui_sub(remainder, 0, remainder); - mpz_add(out, value, remainder); - } else { - mpz_sub(out, value, remainder); - } - } else { - if (mpz_cmp(remainder, divisor) >= 0) { - mpz_ui_pow_ui(divisor, 10, value_prec - round_prec); - mpz_sub(remainder, divisor, remainder); - mpz_add(out, value, remainder); - } else { - mpz_sub(out, value, remainder); - } - } - mpz_clear(quotient); - mpz_clear(remainder); - - // chop off the rounded bits - mpz_ui_pow_ui(divisor, 10, value_prec - round_prec); - mpz_tdiv_q(out, out, divisor); - } -} - -amount_t& amount_t::operator*=(const amount_t& amt) -{ - if (! quantity || ! amt.quantity) { - if (quantity) - throw_(amount_error, "Cannot multiply an amount by an uninitialized amount"); - else if (amt.quantity) - throw_(amount_error, "Cannot multiply an uninitialized amount by an amount"); - else - throw_(amount_error, "Cannot multiply two uninitialized amounts"); - } - - if (has_commodity() && amt.has_commodity() && - commodity() != amt.commodity()) - throw_(amount_error, - "Multiplying amounts with different commodities: " << - (has_commodity() ? commodity().symbol() : "NONE") << - " != " << - (amt.has_commodity() ? amt.commodity().symbol() : "NONE")); - - _dup(); - - mpz_mul(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity)); - quantity->prec += amt.quantity->prec; - - if (! has_commodity()) - commodity_ = amt.commodity_; - - if (has_commodity() && ! (quantity->has_flags(BIGINT_KEEP_PREC))) { - precision_t comm_prec = commodity().precision(); - if (quantity->prec > comm_prec + 6U) { - mpz_round(MPZ(quantity), MPZ(quantity), quantity->prec, comm_prec + 6U); - quantity->prec = comm_prec + 6U; - } - } - - return *this; -} - -amount_t& amount_t::operator/=(const amount_t& amt) -{ - if (! quantity || ! amt.quantity) { - if (quantity) - throw_(amount_error, "Cannot divide an amount by an uninitialized amount"); - else if (amt.quantity) - throw_(amount_error, "Cannot divide an uninitialized amount by an amount"); - else - throw_(amount_error, "Cannot divide two uninitialized amounts"); - } - - if (has_commodity() && amt.has_commodity() && - commodity() != amt.commodity()) - throw_(amount_error, - "Dividing amounts with different commodities: " << - (has_commodity() ? commodity().symbol() : "NONE") << - " != " << - (amt.has_commodity() ? amt.commodity().symbol() : "NONE")); - - if (! amt) - throw_(amount_error, "Divide by zero"); - - _dup(); - - // Increase the value's precision, to capture fractional parts after - // the divide. Round up in the last position. - - mpz_ui_pow_ui(divisor, 10, (2 * amt.quantity->prec) + quantity->prec + 7U); - mpz_mul(MPZ(quantity), MPZ(quantity), divisor); - mpz_tdiv_q(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity)); - quantity->prec += amt.quantity->prec + quantity->prec + 7U; - - mpz_round(MPZ(quantity), MPZ(quantity), quantity->prec, quantity->prec - 1); - quantity->prec -= 1; - - if (! has_commodity()) - commodity_ = amt.commodity_; - - // If this amount has a commodity, and we're not dealing with plain - // numbers, or internal numbers (which keep full precision at all - // times), then round the number to within the commodity's precision - // plus six places. - - if (has_commodity() && ! (quantity->has_flags(BIGINT_KEEP_PREC))) { - precision_t comm_prec = commodity().precision(); - if (quantity->prec > comm_prec + 6U) { - mpz_round(MPZ(quantity), MPZ(quantity), quantity->prec, comm_prec + 6U); - quantity->prec = comm_prec + 6U; - } - } - - return *this; -} - - -amount_t::precision_t amount_t::precision() const -{ - if (! quantity) - throw_(amount_error, "Cannot determine precision of an uninitialized amount"); - - return quantity->prec; -} - -amount_t& amount_t::in_place_negate() -{ - if (quantity) { - _dup(); - mpz_neg(MPZ(quantity), MPZ(quantity)); - } else { - throw_(amount_error, "Cannot negate an uninitialized amount"); - } - return *this; -} - -amount_t amount_t::round() const -{ - if (! quantity) - throw_(amount_error, "Cannot round an uninitialized amount"); - - if (! has_commodity()) - return *this; - - return round(commodity().precision()); -} - -amount_t amount_t::round(precision_t prec) const -{ - if (! quantity) - throw_(amount_error, "Cannot round an uninitialized amount"); - - amount_t t(*this); - - if (quantity->prec <= prec) { - if (quantity && quantity->has_flags(BIGINT_KEEP_PREC)) { - t._dup(); - t.quantity->drop_flags(BIGINT_KEEP_PREC); - } - return t; - } - - t._dup(); - - mpz_round(MPZ(t.quantity), MPZ(t.quantity), t.quantity->prec, prec); - - t.quantity->prec = prec; - t.quantity->drop_flags(BIGINT_KEEP_PREC); - - return t; -} - -amount_t amount_t::unround() const -{ - if (! quantity) - throw_(amount_error, "Cannot unround an uninitialized amount"); - else if (quantity->has_flags(BIGINT_KEEP_PREC)) - return *this; - - amount_t t(*this); - t._dup(); - t.quantity->add_flags(BIGINT_KEEP_PREC); - - return t; -} - -amount_t& amount_t::in_place_reduce() -{ - if (! quantity) - throw_(amount_error, "Cannot reduce an uninitialized amount"); - - while (commodity_ && commodity().smaller()) { - *this *= commodity().smaller()->number(); - commodity_ = commodity().smaller()->commodity_; - } - return *this; -} - -amount_t& amount_t::in_place_unreduce() -{ - if (! quantity) - throw_(amount_error, "Cannot unreduce an uninitialized amount"); - - while (commodity_ && commodity().larger()) { - *this /= commodity().larger()->number(); - commodity_ = commodity().larger()->commodity_; - if (abs() < amount_t(1.0)) - break; - } - return *this; -} - -optional<amount_t> amount_t::value(const optional<moment_t>& moment) const -{ - if (quantity) { - optional<amount_t> amt(commodity().value(moment)); - if (amt) - return (*amt * number()).round(); - } else { - throw_(amount_error, "Cannot determine value of an uninitialized amount"); - } - return none; -} - - -int amount_t::sign() const -{ - if (! quantity) - throw_(amount_error, "Cannot determine sign of an uninitialized amount"); - - return mpz_sgn(MPZ(quantity)); -} - -bool amount_t::is_zero() const -{ - if (! quantity) - throw_(amount_error, "Cannot determine sign if an uninitialized amount is zero"); - - if (has_commodity()) { - if (quantity->prec <= commodity().precision()) - return is_realzero(); - else - return round(commodity().precision()).sign() == 0; - } - return is_realzero(); -} - - -double amount_t::to_double(bool no_check) const -{ - if (! quantity) - throw_(amount_error, "Cannot convert an uninitialized amount to a double"); - - mpz_t remainder; - mpz_init(remainder); - - mpz_set(temp, MPZ(quantity)); - mpz_ui_pow_ui(divisor, 10, quantity->prec); - mpz_tdiv_qr(temp, remainder, temp, divisor); - - char * quotient_s = mpz_get_str(NULL, 10, temp); - char * remainder_s = mpz_get_str(NULL, 10, remainder); - - std::ostringstream num; - num << quotient_s << '.' << remainder_s; - - std::free(quotient_s); - std::free(remainder_s); - - mpz_clear(remainder); - - double value = lexical_cast<double>(num.str()); - - if (! no_check && *this != value) - throw_(amount_error, "Conversion of amount to_double loses precision"); - - return value; -} - -long amount_t::to_long(bool no_check) const -{ - if (! quantity) - throw_(amount_error, "Cannot convert an uninitialized amount to a long"); - - mpz_set(temp, MPZ(quantity)); - mpz_ui_pow_ui(divisor, 10, quantity->prec); - mpz_tdiv_q(temp, temp, divisor); - - long value = mpz_get_si(temp); - - if (! no_check && *this != value) - throw_(amount_error, "Conversion of amount to_long loses precision"); - - return value; -} - -bool amount_t::fits_in_double() const -{ - double value = to_double(true); - return *this == amount_t(value); -} - -bool amount_t::fits_in_long() const -{ - long value = to_long(true); - return *this == amount_t(value); -} - - -void amount_t::annotate_commodity(const annotation_t& details) -{ - commodity_t * this_base; - annotated_commodity_t * this_ann = NULL; - - if (! quantity) - throw_(amount_error, "Cannot annotate the commodity of an uninitialized amount"); - else if (! has_commodity()) - throw_(amount_error, "Cannot annotate an amount with no commodity"); - - if (commodity().annotated) { - this_ann = &as_annotated_commodity(commodity()); - this_base = &this_ann->referent(); - } else { - this_base = &commodity(); - } - assert(this_base); - - DEBUG("amounts.commodities", "Annotating commodity for amount " - << *this << std::endl << details); - - if (commodity_t * ann_comm = - this_base->parent().find_or_create(*this_base, details)) - set_commodity(*ann_comm); -#ifdef ASSERTS_ON - else - assert(false); -#endif - - DEBUG("amounts.commodities", " Annotated amount is " << *this); -} - -bool amount_t::commodity_annotated() const -{ - if (! quantity) - throw_(amount_error, - "Cannot determine if an uninitialized amount's commodity is annotated"); - - assert(! commodity().annotated || as_annotated_commodity(commodity()).details); - return commodity().annotated; -} - -annotation_t amount_t::annotation_details() const -{ - if (! quantity) - throw_(amount_error, - "Cannot return commodity annotation details of an uninitialized amount"); - - assert(! commodity().annotated || as_annotated_commodity(commodity()).details); - - if (commodity().annotated) { - annotated_commodity_t& ann_comm(as_annotated_commodity(commodity())); - return ann_comm.details; - } - return annotation_t(); -} - -amount_t amount_t::strip_annotations(const bool _keep_price, - const bool _keep_date, - const bool _keep_tag) const -{ - if (! quantity) - throw_(amount_error, - "Cannot strip commodity annotations from an uninitialized amount"); - - if (! commodity().annotated || - (_keep_price && _keep_date && _keep_tag)) - return *this; - - amount_t t(*this); - t.set_commodity(as_annotated_commodity(commodity()). - strip_annotations(_keep_price, _keep_date, _keep_tag)); - return t; -} - - -namespace { - void parse_quantity(std::istream& in, string& value) - { - char buf[256]; - char c = peek_next_nonws(in); - READ_INTO(in, buf, 255, c, - std::isdigit(c) || c == '-' || c == '.' || c == ','); - - int len = std::strlen(buf); - while (len > 0 && ! std::isdigit(buf[len - 1])) { - buf[--len] = '\0'; - in.unget(); - } - - value = buf; - } -} - -void amount_t::parse(std::istream& in, flags_t flags) -{ - // The possible syntax for an amount is: - // - // [-]NUM[ ]SYM [@ AMOUNT] - // SYM[ ][-]NUM [@ AMOUNT] - - string symbol; - string quant; - annotation_t details; - bool negative = false; - - commodity_t::flags_t comm_flags = COMMODITY_STYLE_DEFAULTS; - - char c = peek_next_nonws(in); - if (c == '-') { - negative = true; - in.get(c); - c = peek_next_nonws(in); - } - - char n; - if (std::isdigit(c)) { - parse_quantity(in, quant); - - if (! in.eof() && ((n = in.peek()) != '\n')) { - if (std::isspace(n)) - comm_flags |= COMMODITY_STYLE_SEPARATED; - - commodity_t::parse_symbol(in, symbol); - - if (! symbol.empty()) - comm_flags |= COMMODITY_STYLE_SUFFIXED; - - if (! in.eof() && ((n = in.peek()) != '\n')) - details.parse(in); - } - } else { - commodity_t::parse_symbol(in, symbol); - - if (! in.eof() && ((n = in.peek()) != '\n')) { - if (std::isspace(in.peek())) - comm_flags |= COMMODITY_STYLE_SEPARATED; - - parse_quantity(in, quant); - - if (! quant.empty() && ! in.eof() && ((n = in.peek()) != '\n')) - details.parse(in); - } - } - - if (quant.empty()) - throw_(amount_error, "No quantity specified for amount"); - - // Allocate memory for the amount's quantity value. We have to - // monitor the allocation in an auto_ptr because this function gets - // called sometimes from amount_t's constructor; and if there is an - // exeception thrown by any of the function calls after this point, - // the destructor will never be called and the memory never freed. - - std::auto_ptr<bigint_t> safe_holder; - - if (! quantity) { - quantity = new bigint_t; - safe_holder.reset(quantity); - } - else if (quantity->ref > 1) { - _release(); - quantity = new bigint_t; - safe_holder.reset(quantity); - } - - // Create the commodity if has not already been seen, and update the - // precision if something greater was used for the quantity. - - bool newly_created = false; - - if (symbol.empty()) { - commodity_ = NULL; - } else { - commodity_ = current_pool->find(symbol); - if (! commodity_) { - commodity_ = current_pool->create(symbol); - newly_created = true; - } - assert(commodity_); - - if (details) - commodity_ = current_pool->find_or_create(*commodity_, details); - } - - // Determine the precision of the amount, based on the usage of - // comma or period. - - string::size_type last_comma = quant.rfind(','); - string::size_type last_period = quant.rfind('.'); - - if (last_comma != string::npos && last_period != string::npos) { - comm_flags |= COMMODITY_STYLE_THOUSANDS; - if (last_comma > last_period) { - comm_flags |= COMMODITY_STYLE_EUROPEAN; - quantity->prec = quant.length() - last_comma - 1; - } else { - quantity->prec = quant.length() - last_period - 1; - } - } - else if (last_comma != string::npos && - commodity().has_flags(COMMODITY_STYLE_EUROPEAN)) { - quantity->prec = quant.length() - last_comma - 1; - } - else if (last_period != string::npos && - ! (commodity().has_flags(COMMODITY_STYLE_EUROPEAN))) { - quantity->prec = quant.length() - last_period - 1; - } - else { - quantity->prec = 0; - } - - // Set the commodity's flags and precision accordingly - - if (commodity_ && (newly_created || ! (flags & AMOUNT_PARSE_NO_MIGRATE))) { - commodity().add_flags(comm_flags); - - if (quantity->prec > commodity().precision()) - commodity().set_precision(quantity->prec); - } - - // Setup the amount's own flags - - if (flags & AMOUNT_PARSE_NO_MIGRATE) - quantity->add_flags(BIGINT_KEEP_PREC); - - // Now we have the final number. Remove commas and periods, if - // necessary. - - if (last_comma != string::npos || last_period != string::npos) { - int len = quant.length(); - char * buf = new char[len + 1]; - const char * p = quant.c_str(); - char * t = buf; - - while (*p) { - if (*p == ',' || *p == '.') - p++; - *t++ = *p++; - } - *t = '\0'; - - mpz_set_str(MPZ(quantity), buf, 10); - checked_array_delete(buf); - } else { - mpz_set_str(MPZ(quantity), quant.c_str(), 10); - } - - if (negative) - in_place_negate(); - - if (! (flags & AMOUNT_PARSE_NO_REDUCE)) - in_place_reduce(); - - safe_holder.release(); // `this->quantity' owns the pointer -} - -void amount_t::parse_conversion(const string& larger_str, - const string& smaller_str) -{ - amount_t larger, smaller; - - larger.parse(larger_str, AMOUNT_PARSE_NO_REDUCE); - smaller.parse(smaller_str, AMOUNT_PARSE_NO_REDUCE); - - larger *= smaller.number(); - - if (larger.commodity()) { - larger.commodity().set_smaller(smaller); - larger.commodity().add_flags(smaller.commodity().flags() | - COMMODITY_STYLE_NOMARKET); - } - if (smaller.commodity()) - smaller.commodity().set_larger(larger); -} - - -void amount_t::print(std::ostream& _out, bool omit_commodity, - bool full_precision) const -{ - if (! quantity) - throw_(amount_error, "Cannot write out an uninitialized amount"); - - amount_t base(*this); - if (! amount_t::keep_base) - base.in_place_unreduce(); - - std::ostringstream out; - - mpz_t quotient; - mpz_t rquotient; - mpz_t remainder; - - mpz_init(quotient); - mpz_init(rquotient); - mpz_init(remainder); - - bool negative = false; - - // Ensure the value is rounded to the commodity's precision before - // outputting it. NOTE: `rquotient' is used here as a temp variable! - - commodity_t& comm(base.commodity()); - precision_t precision = 0; - - if (quantity) { - if (! comm || full_precision || base.quantity->has_flags(BIGINT_KEEP_PREC)) { - mpz_ui_pow_ui(divisor, 10, base.quantity->prec); - mpz_tdiv_qr(quotient, remainder, MPZ(base.quantity), divisor); - precision = base.quantity->prec; - } - else if (comm.precision() < base.quantity->prec) { - mpz_round(rquotient, MPZ(base.quantity), base.quantity->prec, - comm.precision()); - mpz_ui_pow_ui(divisor, 10, comm.precision()); - mpz_tdiv_qr(quotient, remainder, rquotient, divisor); - precision = comm.precision(); - } - else if (comm.precision() > base.quantity->prec) { - mpz_ui_pow_ui(divisor, 10, comm.precision() - base.quantity->prec); - mpz_mul(rquotient, MPZ(base.quantity), divisor); - mpz_ui_pow_ui(divisor, 10, comm.precision()); - mpz_tdiv_qr(quotient, remainder, rquotient, divisor); - precision = comm.precision(); - } - else if (base.quantity->prec) { - mpz_ui_pow_ui(divisor, 10, base.quantity->prec); - mpz_tdiv_qr(quotient, remainder, MPZ(base.quantity), divisor); - precision = base.quantity->prec; - } - else { - mpz_set(quotient, MPZ(base.quantity)); - mpz_set_ui(remainder, 0); - precision = 0; - } - - if (mpz_sgn(quotient) < 0 || mpz_sgn(remainder) < 0) { - negative = true; - - mpz_abs(quotient, quotient); - mpz_abs(remainder, remainder); - } - mpz_set(rquotient, remainder); - } - - if (! omit_commodity && ! comm.has_flags(COMMODITY_STYLE_SUFFIXED)) { - comm.print(out); - if (comm.has_flags(COMMODITY_STYLE_SEPARATED)) - out << " "; - } - - if (negative) - out << "-"; - - if (! quantity || mpz_sgn(quotient) == 0) { - out << '0'; - } - else if (omit_commodity || ! comm.has_flags(COMMODITY_STYLE_THOUSANDS)) { - char * p = mpz_get_str(NULL, 10, quotient); - out << p; - std::free(p); - } - else { - std::list<string> strs; - char buf[4]; - - for (int powers = 0; true; powers += 3) { - if (powers > 0) { - mpz_ui_pow_ui(divisor, 10, powers); - mpz_tdiv_q(temp, quotient, divisor); - if (mpz_sgn(temp) == 0) - break; - mpz_tdiv_r_ui(temp, temp, 1000); - } else { - mpz_tdiv_r_ui(temp, quotient, 1000); - } - mpz_get_str(buf, 10, temp); - strs.push_back(buf); - } - - bool printed = false; - - for (std::list<string>::reverse_iterator i = strs.rbegin(); - i != strs.rend(); - i++) { - if (printed) { - out << (comm.has_flags(COMMODITY_STYLE_EUROPEAN) ? '.' : ','); - out.width(3); - out.fill('0'); - } - out << *i; - - printed = true; - } - } - - if (quantity && precision) { - std::ostringstream final; - final.width(precision); - final.fill('0'); - char * p = mpz_get_str(NULL, 10, rquotient); - final << p; - std::free(p); - - const string& str(final.str()); - int i, len = str.length(); - const char * q = str.c_str(); - for (i = len; i > 0; i--) - if (q[i - 1] != '0') - break; - - string ender; - if (i == len) - ender = str; - else if (i < comm.precision()) - ender = string(str, 0, comm.precision()); - else - ender = string(str, 0, i); - - if (! ender.empty()) { - if (omit_commodity) - out << '.'; - else - out << (comm.has_flags(COMMODITY_STYLE_EUROPEAN) ? ',' : '.'); - out << ender; - } - } - - if (! omit_commodity && comm.has_flags(COMMODITY_STYLE_SUFFIXED)) { - if (comm.has_flags(COMMODITY_STYLE_SEPARATED)) - out << " "; - comm.print(out); - } - - mpz_clear(quotient); - mpz_clear(rquotient); - mpz_clear(remainder); - - // If there are any annotations associated with this commodity, - // output them now. - - if (! omit_commodity && comm.annotated) { - annotated_commodity_t& ann(static_cast<annotated_commodity_t&>(comm)); - assert(&*ann.details.price != this); - ann.write_annotations(out); - } - - // Things are output to a string first, so that if anyone has - // specified a width or fill for _out, it will be applied to the - // entire amount string, and not just the first part. - - _out << out.str(); -} - - -namespace { - char * bigints; - char * bigints_next; - uint_fast32_t bigints_index; - uint_fast32_t bigints_count; - char buf[4096]; -} - -void amount_t::read(std::istream& in) -{ - using namespace ledger::binary; - - // Read in the commodity for this amount - - commodity_t::ident_t ident; - read_long(in, ident); - if (ident == 0xffffffff) - commodity_ = NULL; - else if (ident == 0) - commodity_ = current_pool->null_commodity; - else { - commodity_ = current_pool->find(ident); - assert(commodity_); - } - - // Read in the quantity - - char byte; - in.read(&byte, sizeof(byte)); - - if (byte < 3) { - quantity = new bigint_t; - - unsigned short len; - in.read((char *)&len, sizeof(len)); - assert(len < 4096); - in.read(buf, len); - mpz_import(MPZ(quantity), len / sizeof(short), 1, sizeof(short), - 0, 0, buf); - - char negative; - in.read(&negative, sizeof(negative)); - if (negative) - mpz_neg(MPZ(quantity), MPZ(quantity)); - - in.read((char *)&quantity->prec, sizeof(quantity->prec)); - - bigint_t::flags_t tflags; - in.read((char *)&tflags, sizeof(tflags)); - quantity->set_flags(tflags); - } - else { - assert(false); - } -} - -void amount_t::read(const char *& data) -{ - using namespace ledger::binary; - - // Read in the commodity for this amount - - commodity_t::ident_t ident; - read_long(data, ident); - if (ident == 0xffffffff) - commodity_ = NULL; - else if (ident == 0) - commodity_ = current_pool->null_commodity; - else { - commodity_ = current_pool->find(ident); - assert(commodity_); - } - - // Read in the quantity - - char byte = *data++;; - - if (byte < 3) { - if (byte == 2) { - quantity = new((bigint_t *)bigints_next) bigint_t; - bigints_next += sizeof(bigint_t); - } else { - quantity = new bigint_t; - } - - unsigned short len = *((unsigned short *) data); - data += sizeof(unsigned short); - mpz_import(MPZ(quantity), len / sizeof(short), 1, sizeof(short), - 0, 0, data); - data += len; - - char negative = *data++; - if (negative) - mpz_neg(MPZ(quantity), MPZ(quantity)); - - quantity->prec = *((precision_t *) data); - data += sizeof(precision_t); - quantity->set_flags(*((flags_t *) data)); - data += sizeof(flags_t); - - if (byte == 2) - quantity->add_flags(BIGINT_BULK_ALLOC); - } else { - uint_fast32_t index = *((uint_fast32_t *) data); - data += sizeof(uint_fast32_t); - - quantity = (bigint_t *) (bigints + (index - 1) * sizeof(bigint_t)); - DEBUG("amounts.refs", - quantity << " ref++, now " << (quantity->ref + 1)); - quantity->ref++; - } -} - -void amount_t::write(std::ostream& out, bool optimized) const -{ - using namespace ledger::binary; - - // Write out the commodity for this amount - - if (! quantity) - throw_(amount_error, "Cannot serialize an uninitialized amount"); - - if (commodity_) - write_long(out, commodity_->ident); - else - write_long<commodity_t::ident_t>(out, 0xffffffff); - - // Write out the quantity - - char byte; - - if (! optimized || quantity->index == 0) { - if (optimized) { - quantity->index = ++bigints_index; // if !optimized, this is garbage - bigints_count++; - byte = 2; - } else { - byte = 1; - } - out.write(&byte, sizeof(byte)); - - std::size_t size; - mpz_export(buf, &size, 1, sizeof(short), 0, 0, MPZ(quantity)); - unsigned short len = size * sizeof(short); - out.write((char *)&len, sizeof(len)); - if (len) { - assert(len < 4096); - out.write(buf, len); - } - - byte = mpz_sgn(MPZ(quantity)) < 0 ? 1 : 0; - out.write(&byte, sizeof(byte)); - - out.write((char *)&quantity->prec, sizeof(quantity->prec)); - bigint_t::flags_t tflags = quantity->flags() & ~BIGINT_BULK_ALLOC; - assert(sizeof(tflags) == sizeof(bigint_t::flags_t)); - out.write((char *)&tflags, sizeof(tflags)); - } else { - assert(quantity->ref > 1); - - // Since this value has already been written, we simply write - // out a reference to which one it was. - byte = 3; - out.write(&byte, sizeof(byte)); - out.write((char *)&quantity->index, sizeof(quantity->index)); - } -} - - -bool amount_t::valid() const -{ - if (quantity) { - if (quantity->ref == 0) { - DEBUG("ledger.validate", "amount_t: quantity->ref == 0"); - return false; - } - } - else if (commodity_) { - DEBUG("ledger.validate", "amount_t: commodity_ != NULL"); - return false; - } - return true; -} - -} // namespace ledger diff --git a/src/numerics/amount.h b/src/numerics/amount.h deleted file mode 100644 index 158b8b8a..00000000 --- a/src/numerics/amount.h +++ /dev/null @@ -1,713 +0,0 @@ -/* - * Copyright (c) 2003-2007, 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. - */ - -/** - * @file amount.h - * @author John Wiegley - * @date Wed Apr 18 22:05:53 2007 - * - * @brief Basic type for handling commoditized math: amount_t. - * - * This file contains the most basic numerical type in Ledger: - * amount_t, which relies upon commodity.h (commodity_t) for handling - * commoditized amounts. This class allows Ledger to handle - * mathematical expressions involving differing commodities, as well - * as math using no commodities at all (such as increasing a dollar - * amount by a multiplier). - */ -#ifndef _AMOUNT_H -#define _AMOUNT_H - -#include "utils.h" - -namespace ledger { - -class commodity_t; -class annotation_t; -class commodity_pool_t; - -DECLARE_EXCEPTION(amount_error); - -/** - * @class amount_t - * - * @brief Encapsulates infinite-precision commoditized amounts. - * - * The amount_t class can be used for commoditized infinite-precision - * math, and also for uncommoditized math. In the commoditized case, - * commodities keep track of how they are used, and will always - * display back to the user after the same fashion. For - * uncommoditized numbers, no display truncation is ever done. In - * both cases, internal precision is always kept to an excessive - * degree. - */ -class amount_t - : public ordered_field_operators<amount_t, - ordered_field_operators<amount_t, double, - ordered_field_operators<amount_t, unsigned long, - ordered_field_operators<amount_t, long> > > > -{ - // jww (2007-05-03): Make this private, and then make - // ledger::initialize into a member function of session_t. -public: - /** - * The initialize and shutdown methods ready the amount subsystem - * for use. Normally they are called by `ledger::initialize' and - * `ledger::shutdown'. - */ - static void initialize(); - static void shutdown(); - -public: - typedef uint_least16_t precision_t; - - /** - * The current_pool is a static variable indicating which commodity - * pool should be used. - */ - static commodity_pool_t * current_pool; - - /** - * The `keep_base' member determines whether scalable commodities - * are automatically converted to their most reduced form when - * printing. The default is true. - * - * For example, Ledger supports time values specified in seconds - * (10s), hours (5.2h) or minutes. Internally, such amounts are - * always kept as quantities of seconds. However, when streaming - * the amount Ledger will convert it to its "least representation", - * which is "5.2h" in the second case. If `keep_base' is true, this - * amount is displayed as "18720s". - */ - static bool keep_base; - - /** - * The following three members determine whether lot details are - * maintained when working with commoditized values. The default is - * false for all three. - * - * Let's say a user adds two values of the following form: - * 10 AAPL + 10 AAPL {$20} - * - * This expression adds ten shares of Apple stock with another ten - * shares that were purchased for $20 a share. If `keep_price' is - * false, the result of this expression will be an amount equal to - * 20 AAPL. If `keep_price' is true, the expression yields an - * exception for adding amounts with different commodities. In that - * case, a balance_t object must be used to store the combined sum. - */ - static bool keep_price; - static bool keep_date; - static bool keep_tag; - - /** - * The `stream_fullstrings' static member is currently only used by - * the unit testing code. It causes amounts written to streams to - * use the `to_fullstring' method rather than the `to_string' - * method, so that complete precision is always displayed, no matter - * what the precision of an individual commodity might be. - * @see to_string - * @see to_fullstring - */ - static bool stream_fullstrings; - -protected: - void _copy(const amount_t& amt); - void _dup(); - void _resize(precision_t prec); - void _clear(); - void _release(); - - struct bigint_t; - - bigint_t * quantity; - commodity_t * commodity_; - -public: - /** - * Constructors. amount_t supports several forms of construction: - * - * amount_t() creates a value for which `is_null' is true, and which - * has no value or commodity. If used in value situations it will - * be zero, and its commodity equals `commodity_t::null_commodity'. - * - * amount_t(double), amount_t(unsigned long), amount_t(long) all - * convert from the respective numerics type to an amount. No - * precision or sign is lost in any of these conversions. The - * resulting commodity is always `commodity_t::null_commodity'. - * - * amount_t(string), amount_t(const char *) both convert from a - * string representation of an amount, which may or may not include - * a commodity. This is the proper way to initialize an amount like - * '$100.00'. - */ - amount_t() : quantity(NULL), commodity_(NULL) { - TRACE_CTOR(amount_t, ""); - } - amount_t(const double val); - amount_t(const unsigned long val); - amount_t(const long val); - - explicit amount_t(const string& val) : quantity(NULL) { - TRACE_CTOR(amount_t, "const string&"); - parse(val); - } - explicit amount_t(const char * val) : quantity(NULL) { - TRACE_CTOR(amount_t, "const char *"); - parse(val); - } - - /** - * Static creator function. Calling amount_t::exact(string) will - * create an amount whose display precision is never truncated, even - * if the amount uses a commodity (which normally causes "round on - * streaming" to occur). This function is mostly used by the - * debugging code. It is the proper way to initialize '$100.005', - * where display of the extra precision is required. If a regular - * constructor is used, this amount will stream as '$100.01', even - * though its internal value always equals $100.005. - */ - static amount_t exact(const string& value); - - /** - * Destructor. Releases the reference count held for the underlying - * bigint_t object pointed to be `quantity'. - */ - ~amount_t() { - TRACE_DTOR(amount_t); - if (quantity) - _release(); - } - - /** - * Assignment and copy operators. An amount may be assigned or - * copied. If a double, long or unsigned long is assigned to an - * amount, a temporary is constructed, and then the temporary is - * assigned to `this'. Both the value and the commodity are copied, - * causing the result to compare equal to the reference amount. - * - * Note: `quantity' must be initialized to NULL first, otherwise the - * `_copy' function will attempt to release the uninitialized pointer. - */ - amount_t(const amount_t& amt) : quantity(NULL) { - TRACE_CTOR(amount_t, "copy"); - if (amt.quantity) - _copy(amt); - else - commodity_ = NULL; - } - amount_t& operator=(const amount_t& amt); - - amount_t& operator=(const string& str) { - return *this = amount_t(str); - } - amount_t& operator=(const char * str) { - return *this = amount_t(str); - } - - /** - * Comparison operators. The fundamental comparison operation for - * amounts is `compare', which returns a value less than, greater - * than or equal to zero. All the other comparison operators are - * defined in terms of this method. The only special detail is that - * `operator==' will fail immediately if amounts with different - * commodities are being compared. Otherwise, if the commodities - * are equivalent (@see keep_price, et al), then the amount - * quantities are compared numerically. - * - * Comparison between an amount and a double, long or unsigned long - * is allowed. In such cases the non-amount value is constructed as - * an amount temporary, which is then compared to `this'. - */ - int compare(const amount_t& amt) const; - - bool operator==(const amount_t& amt) const; - - template <typename T> - bool operator==(const T& val) const { - return compare(val) == 0; - } - template <typename T> - bool operator<(const T& amt) const { - return compare(amt) < 0; - } - template <typename T> - bool operator>(const T& amt) const { - return compare(amt) > 0; - } - - /** - * Binary arithmetic operators. Amounts support addition, - * subtraction, multiplication and division -- but not modulus, - * bitwise operations, or shifting. Arithmetic is also supported - * between amounts, double, long and unsigned long, in which case - * temporary amount are constructed for the life of the expression. - * - * Although only in-place operators are defined here, the remainder - * are provided by `boost::ordered_field_operators<>'. - */ - amount_t& operator+=(const amount_t& amt); - amount_t& operator-=(const amount_t& amt); - amount_t& operator*=(const amount_t& amt); - amount_t& operator/=(const amount_t& amt); - - /** - * Unary arithmetic operators. There are several unary methods - * support on amounts: - * - * precision() return an amount's current, internal precision. To - * find the precision it will be displayed at -- assuming it was not - * created using the static method `amount_t::exact' -- refer to - * commodity().precision. - * - * negate(), also unary minus (- x), returns the negated value of an - * amount. - * - * abs() returns the absolute value of an amount. It is equivalent - * to: `(x < 0) ? - x : x'. - * - * round(precision_t) and round() round an amount's internal value - * to the given precision, or to the commodity's current display - * precision if no precision value is given. This method changes - * the internal value of the amount, if it's internal precision was - * greater than the rounding precision. - * - * unround() yields an amount whose display precision is never - * truncated, even though its commodity normally displays only - * rounded values. - * - * reduce() reduces a value to its most basic commodity form, for - * amounts that utilize "scaling commodities". For example, an - * amount of 1h after reduction will be 3600s. - * - * unreduce(), if used with a "scaling commodity", yields the most - * compact form greater than 1.0. That is, 3599s will unreduce to - * 59.98m, while 3601 unreduces to 1h. - * - * value(optional<moment_t>) returns the historical value for an - * amount -- the default moment returns the most recently known - * price -- based on the price history of its commodity. For - * example, if the amount were 10 AAPL, and on Apr 10, 2000 each - * share of AAPL was worth $10, then call value() for that moment in - * time would yield the amount $100.00. - * - * Further, for the sake of efficiency and avoiding temporary - * objects, the following methods support "in-place" variants that - * act on the amount itself and return a reference to the result - * (`*this'): - * - * in_place_negate() - * in_place_reduce() - * in_place_unreduce() - */ - precision_t precision() const; - - amount_t negate() const { - amount_t temp(*this); - temp.in_place_negate(); - return temp; - } - amount_t& in_place_negate(); - - amount_t operator-() const { - return negate(); - } - - amount_t abs() const { - if (sign() < 0) - return negate(); - return *this; - } - - amount_t round() const; - amount_t round(precision_t prec) const; - amount_t unround() const; - - amount_t reduce() const { - amount_t temp(*this); - temp.in_place_reduce(); - return temp; - } - amount_t& in_place_reduce(); - - amount_t unreduce() const { - amount_t temp(*this); - temp.in_place_unreduce(); - return temp; - } - amount_t& in_place_unreduce(); - - optional<amount_t> value(const optional<moment_t>& moment = none) const; - - /** - * Truth tests. An amount may be truth test in several ways: - * - * sign() returns an integer less than, greater than, or equal to - * zero depending on whether the amount is negative, zero, or - * greater than zero. Note that this function tests the actual - * value of the amount -- using its internal precision -- and not - * the display value. To test its display value, use: - * `round().sign()'. - * - * is_nonzero(), or operator bool, returns true if an amount's - * display value is not zero. - * - * is_zero() returns true if an amount's display value is zero. - * Thus, $0.0001 is considered zero if the current display precision - * for dollars is two decimal places. - * - * is_realzero() returns true if an amount's actual value is zero. - * Thus, $0.0001 is never considered realzero. - * - * is_null() returns true if an amount has no value and no - * commodity. This only occurs if an uninitialized amount has never - * been assigned a value. - */ - int sign() const; - - operator bool() const { - return is_nonzero(); - } - bool is_nonzero() const { - return ! is_zero(); - } - - bool is_zero() const; - bool is_realzero() const { - return sign() == 0; - } - - bool is_null() const { - if (! quantity) { - assert(! commodity_); - return true; - } - return false; - } - - /** - * Conversion methods. An amount may be converted to the same types - * it can be constructed from -- with the exception of unsigned - * long. Implicit conversions are not allowed in C++ (though they - * are in Python), rather the following conversion methods must be - * called explicitly: - * - * to_double([bool]) returns an amount as a double. If the optional - * boolean argument is true (the default), an exception is thrown if - * the conversion would lose information. - * - * to_long([bool]) returns an amount as a long integer. If the - * optional boolean argument is true (the default), an exception is - * thrown if the conversion would lose information. - * - * fits_in_double() returns true if to_double() would not lose - * precision. - * - * fits_in_long() returns true if to_long() would not lose - * precision. - * - * to_string() returns an amount'ss "display value" as a string -- - * after rounding the value according to the commodity's default - * precision. It is equivalent to: `round().to_fullstring()'. - * - * to_fullstring() returns an amount's "internal value" as a string, - * without any rounding. - * - * quantity_string() returns an amount's "display value", but - * without any commodity. Note that this is different from - * `number().to_string()', because in that case the commodity has - * been stripped and the full, internal precision of the amount - * would be displayed. - */ - double to_double(bool no_check = false) const; - long to_long(bool no_check = false) const; - string to_string() const; - string to_fullstring() const; - string quantity_string() const; - - bool fits_in_double() const; - bool fits_in_long() const; - - /** - * Commodity-related methods. The following methods relate to an - * amount's commodity: - * - * commodity() returns an amount's commodity. If the amount has no - * commodity, the value returned is `current_pool->null_commodity'. - * - * has_commodity() returns true if the amount has a commodity. - * - * set_commodity(commodity_t) sets an amount's commodity to the - * given value. Note that this merely sets the current amount to - * that commodity, it does not "observe" the amount for possible - * changes in the maximum display precision of the commodity, the - * way that `parse' does. - * - * clear_commodity() sets an amount's commodity to null, such that - * has_commodity() afterwards returns false. - * - * number() returns a commodity-less version of an amount. This is - * useful for accessing just the numeric portion of an amount. - */ - commodity_t& commodity() const; - - bool has_commodity() const; - void set_commodity(commodity_t& comm) { - if (! quantity) - *this = 0L; - commodity_ = &comm; - } - void clear_commodity() { - commodity_ = NULL; - } - - amount_t number() const { - if (! has_commodity()) - return *this; - - amount_t temp(*this); - temp.clear_commodity(); - return temp; - } - - /** - * Annotated commodity methods. An amount's commodity may be - * annotated with special details, such as the price it was - * purchased for, when it was acquired, or an arbitrary note, - * identifying perhaps the lot number of an item. - * - * annotate_commodity(amount_t price, [moment_t date, string tag]) - * sets the annotations for the current amount's commodity. Only - * the price argument is required, although it can be passed as - * `none' if no price is desired. - * - * commodity_annotated() returns true if an amount's commodity has - * any annotation details associated with it. - * - * annotation_details() returns all of the details of an annotated - * commodity's annotations. The structure returns will evaluate as - * boolean false if there are no details. - * - * strip_annotations([keep_price, keep_date, keep_tag]) returns an - * amount whose commodity's annotations have been stripped. The - * three `keep_' arguments determine which annotation detailed are - * kept, meaning that the default is to follow whatever - * amount_t::keep_price, amount_t::keep_date and amount_t::keep_tag - * have been set to (which all default to false). - */ - void annotate_commodity(const annotation_t& details); - bool commodity_annotated() const; - annotation_t annotation_details() const; - amount_t strip_annotations(const bool _keep_price = keep_price, - const bool _keep_date = keep_date, - const bool _keep_tag = keep_tag) const; - - /** - * Parsing methods. The method `parse' is used to parse an amount - * from an input stream or a string. A global operator>> is also - * defined which simply calls parse on the input stream. The - * `parse' method has two forms: - * - * parse(istream, flags_t) parses an amount from the given input - * stream. - * - * parse(string, flags_t) parses an amount from the given string. - * - * parse(string, flags_t) also parses an amount from a string. - * - * The `flags' argument of both parsing may be one or more of the - * following: - * - * AMOUNT_PARSE_NO_MIGRATE means to not pay attention to the way an - * amount is used. Ordinarily, if an amount were $100.001, for - * example, it would cause the default display precision for $ to be - * "widened" to three decimal places. If AMOUNT_PARSE_NO_MIGRATE is - * used, the commodity's default display precision is not changed. - * - * AMOUNT_PARSE_NO_REDUCE means not to call in_place_reduce() on the - * resulting amount after it is parsed. - * - * These parsing methods observe the amounts they parse (unless - * AMOUNT_PARSE_NO_MIGRATE is true), and set the display details of - * the corresponding commodity accordingly. This way, amounts do - * not require commodities to be pre-defined in any way, but merely - * displays them back to the user in the same fashion as it saw them - * used. - * - * There is also a static convenience method called - * `parse_conversion' which can be used to define a relationship - * between scaling commodity values. For example, Ledger uses it to - * define the relationships among various time values: - * - * amount_t::parse_conversion("1.0m", "60s"); // a minute is 60 seconds - * amount_t::parse_conversion("1.0h", "60m"); // an hour is 60 minutes - */ -#define AMOUNT_PARSE_NO_MIGRATE 0x01 -#define AMOUNT_PARSE_NO_REDUCE 0x02 - - typedef uint_least8_t flags_t; - - void parse(std::istream& in, flags_t flags = 0); - void parse(const string& str, flags_t flags = 0) { - std::istringstream stream(str); - parse(stream, flags); - assert(stream.eof()); - } - - static void parse_conversion(const string& larger_str, - const string& smaller_str); - - /** - * Printing methods. An amount may be output to a stream using the - * `print' method. There is also a global operator<< defined which - * simply calls print for an amount on the given stream. There is - * one form of the print method, which takes one required argument - * and two arguments with default values: - * - * print(ostream, bool omit_commodity = false, bool full_precision = - * false) prints an amounts to the given output stream, using its - * commodity's default display characteristics. If `omit_commodity' - * is true, the commodity will not be displayed, only the amount - * (although the commodity's display precision is still used). If - * `full_precision' is true, the full internal precision of the - * amount is displayed, regardless of its commodity's display - * precision. - */ - void print(std::ostream& out, bool omit_commodity = false, - bool full_precision = false) const; - - /** - * Serialization methods. An amount may be deserialized from an - * input stream or a character pointer, and it may be serialized to - * an output stream. The methods used are: - * - * read(istream) reads an amount from the given input stream. It - * must have been put there using `write(ostream)'. The required - * flow of logic is: - * amount_t::current_pool->write(out) - * amount.write(out) // write out all amounts - * amount_t::current_pool->read(in) - * amount.read(in) - * - * read(char *&) reads an amount from data which has been read from - * an input stream into a buffer. It advances the pointer passed in - * to the end of the deserialized amount. - * - * write(ostream, [bool]) writes an amount to an output stream in a - * compact binary format. If the second parameter is true, - * quantities with multiple reference counts will be written in an - * optimized fashion. NOTE: This form of usage is valid only for - * the binary journal writer, it should not be used otherwise, as it - * has strict requirements for reading that only the binary reader - * knows about. - */ - void read(std::istream& in); - void read(const char *& data); - void write(std::ostream& out, bool optimize = false) const; - - /** - * Debugging methods. There are two methods defined to help with - * debugging: - * - * dump(ostream) dumps an amount to an output stream. There is - * little different from print(), it simply surrounds the display - * value with a marker, for example "AMOUNT($1.00)". This code is - * used by other dumping code elsewhere in Ledger. - * - * valid() returns true if an amount is valid. This ensures that if - * an amount has a commodity, it has a valid value pointer, for - * example, even if that pointer simply points to a zero value. - */ - void dump(std::ostream& out) const { - out << "AMOUNT("; - print(out); - out << ")"; - } - - bool valid() const; -}; - -inline amount_t amount_t::exact(const string& value) { - amount_t temp; - temp.parse(value, AMOUNT_PARSE_NO_MIGRATE); - return temp; -} - -inline string amount_t::to_string() const { - std::ostringstream bufstream; - print(bufstream); - return bufstream.str(); -} - -inline string amount_t::to_fullstring() const { - std::ostringstream bufstream; - print(bufstream, false, true); - return bufstream.str(); -} - -inline string amount_t::quantity_string() const { - std::ostringstream bufstream; - print(bufstream, true); - return bufstream.str(); -} - -inline std::ostream& operator<<(std::ostream& out, const amount_t& amt) { - amt.print(out, false, amount_t::stream_fullstrings); - return out; -} -inline std::istream& operator>>(std::istream& in, amount_t& amt) { - amt.parse(in); - return in; -} - -} // namespace ledger - -#include "commodity.h" - -namespace ledger { - -inline bool amount_t::operator==(const amount_t& amt) const { - if (commodity() != amt.commodity()) - return false; - return compare(amt) == 0; -} - -inline commodity_t& amount_t::commodity() const { - return has_commodity() ? *commodity_ : *current_pool->null_commodity; -} - -inline bool amount_t::has_commodity() const { - return commodity_ && commodity_ != commodity_->parent().null_commodity; -} - -} // namespace ledger - -#endif // _AMOUNT_H diff --git a/src/numerics/balpair.h b/src/numerics/balpair.h deleted file mode 100644 index 96ccf42a..00000000 --- a/src/numerics/balpair.h +++ /dev/null @@ -1,367 +0,0 @@ -/* - * Copyright (c) 2003-2007, 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. - */ - -/** - * @file balpair.h - * @author John Wiegley - * @date Sun May 20 19:11:58 2007 - * - * @brief Provides an abstraction around balance_t for tracking costs. - * - * When a transaction's amount is added to a balance, only the "value" - * of the amount is added -- not the associated cost of the - * transaction. To provide for this, the balance_pair_t type allows - * for adding amounts and costs simultaneously to a single balance. - * Both are tracked, and any time either the total amount balance or - * the total cost balance may be extracted. - * - * Note: By default, all balance-like operations operate on the amount - * balance, and not the cost. Also, the cost is entirely optional, in - * which case a balance_pair_t may be used as if it were a balance_t, - * from which is it derived. - */ -#ifndef _BALPAIR_H -#define _BARPAIR_H - -#include "balance.h" - -namespace ledger { - -class balance_pair_t - : public balance_t, - public equality_comparable<balance_pair_t, - equality_comparable<balance_pair_t, balance_t, - equality_comparable<balance_pair_t, amount_t, - equality_comparable<balance_pair_t, double, - equality_comparable<balance_pair_t, unsigned long, - equality_comparable<balance_pair_t, long, - additive<balance_pair_t, - additive<balance_pair_t, balance_t, - additive<balance_pair_t, amount_t, - additive<balance_pair_t, double, - additive<balance_pair_t, unsigned long, - additive<balance_pair_t, long, - multiplicative<balance_pair_t, amount_t, - multiplicative<balance_pair_t, balance_t, - multiplicative<balance_pair_t, double, - multiplicative<balance_pair_t, unsigned long, - multiplicative<balance_pair_t, long> > > > > > > > > > > > > > > > > -{ - /** - * The `cost' member of a balance pair tracks the cost associated - * with each transaction amount that is added. This member is - * optional, and if not cost-bearing transactions are added, it will - * remain uninitialized. - */ - optional<balance_t> cost; - - friend class value_t; - friend class entry_base_t; - -public: - /** - * Constructors. balance_pair_t supports identical forms of construction - * to balance_t. See balance_t for more information. - */ - balance_pair_t() { - TRACE_CTOR(balance_pair_t, ""); - } - balance_pair_t(const balance_t& bal) : balance_t(bal) { - TRACE_CTOR(balance_pair_t, "const balance_t&"); - } - balance_pair_t(const balance_t& bal, - const balance_t& cost_bal) - : balance_t(bal), cost(cost_bal) { - TRACE_CTOR(balance_pair_t, "const balance_t&, const balance_t&"); - } - balance_pair_t(const amount_t& amt) : balance_t(amt) { - TRACE_CTOR(balance_pair_t, "const amount_t&"); - } - balance_pair_t(const amount_t& amt, const amount_t& cost_amt) - : balance_t(amt), cost(cost_amt) { - TRACE_CTOR(balance_pair_t, "const amount_t&, const amount_t&"); - } - balance_pair_t(const double val) : balance_t(val) { - TRACE_CTOR(balance_pair_t, "const double"); - } - balance_pair_t(const unsigned long val) : balance_t(val) { - TRACE_CTOR(balance_pair_t, "const unsigned long"); - } - balance_pair_t(const long val) : balance_t(val) { - TRACE_CTOR(balance_pair_t, "const long"); - } - - explicit balance_pair_t(const string& val) : balance_t(val) { - TRACE_CTOR(balance_pair_t, "const string&"); - } - explicit balance_pair_t(const char * val) : balance_t(val) { - TRACE_CTOR(balance_pair_t, "const char *"); - } - - /** - * Destructor. - */ - virtual ~balance_pair_t() { - TRACE_DTOR(balance_pair_t); - } - - /** - * Assignment and copy operators. A balance pair may be assigned or - * copied, and assigned or copied from a balance. - */ - balance_pair_t(const balance_pair_t& bal_pair) - : balance_t(bal_pair), cost(bal_pair.cost) { - TRACE_CTOR(balance_pair_t, "copy"); - } - - balance_pair_t& operator=(const balance_pair_t& bal_pair) { - if (this != &bal_pair) { - balance_t::operator=(bal_pair.quantity()); - cost = bal_pair.cost; - } - return *this; - } - balance_pair_t& operator=(const balance_t& bal) { - balance_t::operator=(bal); - return *this; - } - balance_pair_t& operator=(const amount_t& amt) { - balance_t::operator=(amt); - return *this; - } - - balance_t& operator=(const string& str) { - return *this = balance_t(str); - } - balance_t& operator=(const char * str) { - return *this = balance_t(str); - } - - /** - * Binary arithmetic operators. Balances support addition and - * subtraction of other balance pairs, balances or amounts, but - * multiplication and division are restricted to uncommoditized - * amounts only. - * - * There is also an additional additive method called `add' which - * allows for adding an amount and an associated cost - * simultaneously. The signature is: - * add(amount_t amount, optional<amount_t> cost) - */ - balance_pair_t& operator+=(const balance_pair_t& bal_pair) { - balance_t::operator+=(bal_pair); - if (bal_pair.cost) { - if (! cost) - cost = quantity(); - *cost += *bal_pair.cost; - } - return *this; - } - balance_pair_t& operator-=(const balance_pair_t& bal_pair) { - balance_t::operator+=(bal_pair); - if (bal_pair.cost) { - if (! cost) - cost = quantity(); - *cost += *bal_pair.cost; - } - return *this; - } - - virtual balance_pair_t& operator*=(const amount_t& amt) { - balance_t::operator*=(amt); - if (cost) - *cost *= amt; - return *this; - } - - virtual balance_pair_t& operator/=(const amount_t& amt) { - balance_t::operator/=(amt); - if (cost) - *cost /= amt; - return *this; - } - - balance_pair_t& add(const amount_t& amt, - const optional<amount_t>& a_cost = none) { - if (a_cost && ! cost) - cost = quantity(); - - *this += amt; - - if (cost) - *cost += a_cost ? *a_cost : amt; - - return *this; - } - - /** - * Unary arithmetic operators. There are only a few unary methods - * supported for balance pairs (otherwise, the operators inherited - * from balance_t are used): - * - * abs() returns the absolute value of both the quantity and the - * cost of a balance pair. - * - * in_place_negate() negates all the amounts in both the quantity - * and the cost. - * - * in_place_reduce() reduces all the amounts in both the quantity - * and the cost. - * - * in_place_unreduce() unreduces all the amounts in both the - * quantity and the cost. - * - * quantity() returns the balance part of a balance. It is the same - * as doing a downcast<balance_t>(balance_pair). - */ - balance_pair_t abs() const { - balance_t temp; - for (amounts_map::const_iterator i = amounts.begin(); - i != amounts.end(); - i++) - temp += i->second.abs(); - - if (cost) { - balance_t cost_temp; - for (amounts_map::const_iterator i = cost->amounts.begin(); - i != cost->amounts.end(); - i++) - cost_temp += i->second.abs(); - return balance_pair_t(temp, cost_temp); - } - return temp; - } - - virtual balance_t& in_place_negate() { - balance_t::in_place_negate(); - if (cost) - cost->in_place_negate(); - return *this; - } - - virtual balance_t& in_place_reduce() { - // A temporary must be used here because reduction may cause - // multiple component amounts to collapse to the same commodity. - balance_t temp; - for (amounts_map::const_iterator i = amounts.begin(); - i != amounts.end(); - i++) - temp += i->second.reduce(); - - if (cost) { - balance_t cost_temp; - for (amounts_map::const_iterator i = cost->amounts.begin(); - i != cost->amounts.end(); - i++) - cost_temp += i->second.reduce(); - return *this = balance_pair_t(temp, cost_temp); - } - return *this = temp; - } - - virtual balance_t& in_place_unreduce() { - // A temporary must be used here because unreduction may cause - // multiple component amounts to collapse to the same commodity. - balance_t temp; - for (amounts_map::const_iterator i = amounts.begin(); - i != amounts.end(); - i++) - temp += i->second.unreduce(); - - if (cost) { - balance_t cost_temp; - for (amounts_map::const_iterator i = cost->amounts.begin(); - i != cost->amounts.end(); - i++) - cost_temp += i->second.unreduce(); - return *this = balance_pair_t(temp, cost_temp); - } - return *this = temp; - } - - balance_t& quantity() { - return *this; - } - const balance_t& quantity() const { - return *this; - } - - /** - * Truth tests. An balance pair may be truth tested by comparison - * to another balance pair, or by using one of the inherited - * operators from balance_t. - */ - bool operator==(const balance_pair_t& bal_pair) const { - if (quantity() != bal_pair.quantity()) - return false; - - if ((cost && ! bal_pair.cost) || - (! cost && bal_pair.cost)) - return false; - - if (*cost != *bal_pair.cost) - return false; - - return true; - } - - bool operator==(const balance_t& bal) const { - return balance_t::operator==(bal); - } - bool operator==(const amount_t& amt) const { - return balance_t::operator==(amt); - } - template <typename T> - bool operator==(const T& val) const { - return balance_t::operator==(val); - } - - /** - * Debugging methods. There is only one method specifically for - * balance pairs to help with debugging: - * - * valid() returns true if the balances within the balance pair are - * valid. - */ - virtual bool valid() { - if (! balance_t::valid()) - return false; - - if (cost && ! cost->valid()) - return false; - - return true; - } -}; - -} // namespace ledger - -#endif // _BALPAIR_H diff --git a/src/numerics/commodity.cc b/src/numerics/commodity.cc deleted file mode 100644 index 76614f92..00000000 --- a/src/numerics/commodity.cc +++ /dev/null @@ -1,598 +0,0 @@ -/* - * Copyright (c) 2003-2007, 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. - */ - -/** - * @file commodity.cc - * @author John Wiegley - * @date Thu Apr 26 15:19:46 2007 - * - * @brief Types for dealing with commodities. - * - * This file defines member functions for flavors of commodity_t. - */ - -#include "amount.h" -#include "parser.h" // for parsing utility functions - -namespace ledger { - -void commodity_t::add_price(const moment_t& date, - const amount_t& price) -{ - if (! base->history) - base->history = history_t(); - - history_map::iterator i = base->history->prices.find(date); - if (i != base->history->prices.end()) { - (*i).second = price; - } else { - std::pair<history_map::iterator, bool> result - = base->history->prices.insert(history_map::value_type(date, price)); - assert(result.second); - } -} - -bool commodity_t::remove_price(const moment_t& date) -{ - if (base->history) { - history_map::size_type n = base->history->prices.erase(date); - if (n > 0) { - if (base->history->prices.empty()) - base->history.reset(); - return true; - } - } - return false; -} - -optional<amount_t> commodity_t::value(const optional<moment_t>& moment) -{ - optional<moment_t> age; - optional<amount_t> price; - - if (base->history) { - assert(base->history->prices.size() > 0); - - if (! moment) { - history_map::reverse_iterator r = base->history->prices.rbegin(); - age = (*r).first; - price = (*r).second; - } else { - history_map::iterator i = base->history->prices.lower_bound(*moment); - if (i == base->history->prices.end()) { - history_map::reverse_iterator r = base->history->prices.rbegin(); - age = (*r).first; - price = (*r).second; - } else { - age = (*i).first; - if (*moment != *age) { - if (i != base->history->prices.begin()) { - --i; - age = (*i).first; - price = (*i).second; - } else { - age = none; - } - } else { - price = (*i).second; - } - } - } - } - - if (! has_flags(COMMODITY_STYLE_NOMARKET) && parent().get_quote) { - if (optional<amount_t> quote = parent().get_quote - (*this, age, moment, - (base->history && base->history->prices.size() > 0 ? - (*base->history->prices.rbegin()).first : optional<moment_t>()))) - return *quote; - } - return price; -} - -commodity_t::operator bool() const -{ - return this != parent().null_commodity; -} - -bool commodity_t::symbol_needs_quotes(const string& symbol) -{ - for (const char * p = symbol.c_str(); *p; p++) - if (std::isspace(*p) || std::isdigit(*p) || *p == '-' || *p == '.') - return true; - - return false; -} - -void commodity_t::parse_symbol(std::istream& in, string& symbol) -{ - // Invalid commodity characters: - // SPACE, TAB, NEWLINE, RETURN - // 0-9 . , ; - + * / ^ ? : & | ! = - // < > { } [ ] ( ) @ - - static int invalid_chars[256] = { - /* 0 1 2 3 4 5 6 7 8 9 a b c d e f */ - /* 00 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, - /* 10 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - /* 20 */ 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, - /* 30 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - /* 40 */ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - /* 50 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, - /* 60 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - /* 70 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, - /* 80 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - /* 90 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - /* a0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - /* b0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - /* c0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - /* d0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - /* e0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - /* f0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - }; - - char buf[256]; - char c = peek_next_nonws(in); - if (c == '"') { - in.get(c); - READ_INTO(in, buf, 255, c, c != '"'); - if (c == '"') - in.get(c); - else - throw_(amount_error, "Quoted commodity symbol lacks closing quote"); - } else { - READ_INTO(in, buf, 255, c, ! invalid_chars[(unsigned char)c]); - } - symbol = buf; -} - -void commodity_t::parse_symbol(char *& p, string& symbol) -{ - if (*p == '"') { - char * q = std::strchr(p + 1, '"'); - if (! q) - throw_(parse_error, "Quoted commodity symbol lacks closing quote"); - symbol = string(p + 1, 0, q - p - 1); - p = q + 2; - } else { - char * q = next_element(p); - symbol = p; - if (q) - p = q; - else - p += symbol.length(); - } - if (symbol.empty()) - throw_(parse_error, "Failed to parse commodity"); -} - -bool commodity_t::valid() const -{ - if (symbol().empty() && this != parent().null_commodity) { - DEBUG("ledger.validate", - "commodity_t: symbol().empty() && this != null_commodity"); - return false; - } - - if (annotated && ! base) { - DEBUG("ledger.validate", "commodity_t: annotated && ! base"); - return false; - } - - if (precision() > 16) { - DEBUG("ledger.validate", "commodity_t: precision() > 16"); - return false; - } - - return true; -} - -void annotation_t::parse(std::istream& in) -{ - do { - char buf[256]; - char c = peek_next_nonws(in); - if (c == '{') { - if (price) - throw_(amount_error, "Commodity specifies more than one price"); - - in.get(c); - READ_INTO(in, buf, 255, c, c != '}'); - if (c == '}') - in.get(c); - else - throw_(amount_error, "Commodity price lacks closing brace"); - - amount_t temp; - temp.parse(buf, AMOUNT_PARSE_NO_MIGRATE); - temp.in_place_reduce(); - - // Since this price will maintain its own precision, make sure - // it is at least as large as the base commodity, since the user - // may have only specified {$1} or something similar. - - if (temp.has_commodity() && - temp.precision() < temp.commodity().precision()) - temp = temp.round(); // no need to retain individual precision - - price = temp; - } - else if (c == '[') { - if (date) - throw_(amount_error, "Commodity specifies more than one date"); - - in.get(c); - READ_INTO(in, buf, 255, c, c != ']'); - if (c == ']') - in.get(c); - else - throw_(amount_error, "Commodity date lacks closing bracket"); - - date = parse_datetime(buf); - } - else if (c == '(') { - if (tag) - throw_(amount_error, "Commodity specifies more than one tag"); - - in.get(c); - READ_INTO(in, buf, 255, c, c != ')'); - if (c == ')') - in.get(c); - else - throw_(amount_error, "Commodity tag lacks closing parenthesis"); - - tag = buf; - } - else { - break; - } - } while (true); - - DEBUG("amounts.commodities", - "Parsed commodity annotations: " << std::endl << *this); -} - -bool annotated_commodity_t::operator==(const commodity_t& comm) const -{ - // If the base commodities don't match, the game's up. - if (base != comm.base) - return false; - - assert(annotated); - if (! comm.annotated) - return false; - - if (details != as_annotated_commodity(comm).details) - return false; - - return true; -} - -commodity_t& -annotated_commodity_t::strip_annotations(const bool _keep_price, - const bool _keep_date, - const bool _keep_tag) -{ - DEBUG("commodity.annotated.strip", - "Reducing commodity " << *this << std::endl - << " keep price " << _keep_price << " " - << " keep date " << _keep_date << " " - << " keep tag " << _keep_tag); - - commodity_t * new_comm; - - if ((_keep_price && details.price) || - (_keep_date && details.date) || - (_keep_tag && details.tag)) - { - new_comm = parent().find_or_create - (referent(), - annotation_t(_keep_price ? details.price : none, - _keep_date ? details.date : none, - _keep_tag ? details.tag : none)); - } else { - new_comm = parent().find_or_create(base_symbol()); - } - - assert(new_comm); - return *new_comm; -} - -void annotated_commodity_t::write_annotations(std::ostream& out, - const annotation_t& info) -{ - if (info.price) - out << " {" << *info.price << '}'; - - if (info.date) - out << " [" << *info.date << ']'; - - if (info.tag) - out << " (" << *info.tag << ')'; -} - -bool compare_amount_commodities::operator()(const amount_t * left, - const amount_t * right) const -{ - commodity_t& leftcomm(left->commodity()); - commodity_t& rightcomm(right->commodity()); - - int cmp = leftcomm.base_symbol().compare(rightcomm.base_symbol()); - if (cmp != 0) - return cmp < 0; - - if (! leftcomm.annotated) { - assert(rightcomm.annotated); - return true; - } - else if (! rightcomm.annotated) { - assert(leftcomm.annotated); - return false; - } - else { - annotated_commodity_t& aleftcomm(static_cast<annotated_commodity_t&>(leftcomm)); - annotated_commodity_t& arightcomm(static_cast<annotated_commodity_t&>(rightcomm)); - - if (! aleftcomm.details.price && arightcomm.details.price) - return true; - if (aleftcomm.details.price && ! arightcomm.details.price) - return false; - - if (aleftcomm.details.price && arightcomm.details.price) { - amount_t leftprice(*aleftcomm.details.price); - leftprice.in_place_reduce(); - amount_t rightprice(*arightcomm.details.price); - rightprice.in_place_reduce(); - - if (leftprice.commodity() == rightprice.commodity()) { - return (leftprice - rightprice).sign() < 0; - } else { - // Since we have two different amounts, there's really no way - // to establish a true sorting order; we'll just do it based - // on the numerical values. - leftprice.clear_commodity(); - rightprice.clear_commodity(); - return (leftprice - rightprice).sign() < 0; - } - } - - if (! aleftcomm.details.date && arightcomm.details.date) - return true; - if (aleftcomm.details.date && ! arightcomm.details.date) - return false; - - if (aleftcomm.details.date && arightcomm.details.date) { - duration_t diff = *aleftcomm.details.date - *arightcomm.details.date; - return diff.is_negative(); - } - - if (! aleftcomm.details.tag && arightcomm.details.tag) - return true; - if (aleftcomm.details.tag && ! arightcomm.details.tag) - return false; - - if (aleftcomm.details.tag && arightcomm.details.tag) - return *aleftcomm.details.tag < *arightcomm.details.tag; - - assert(false); - return true; - } -} - -commodity_pool_t::commodity_pool_t() : default_commodity(NULL) -{ - null_commodity = create(""); - null_commodity->add_flags(COMMODITY_STYLE_NOMARKET | - COMMODITY_STYLE_BUILTIN); -} - -commodity_t * commodity_pool_t::create(const string& symbol) -{ - shared_ptr<commodity_t::base_t> - base_commodity(new commodity_t::base_t(symbol)); - std::auto_ptr<commodity_t> commodity(new commodity_t(this, base_commodity)); - - DEBUG("amounts.commodities", "Creating base commodity " << symbol); - - // Create the "qualified symbol" version of this commodity's symbol - if (commodity_t::symbol_needs_quotes(symbol)) { - commodity->qualified_symbol = "\""; - *commodity->qualified_symbol += symbol; - *commodity->qualified_symbol += "\""; - } - - DEBUG("amounts.commodities", - "Creating commodity '" << commodity->symbol() << "'"); - - // Start out the new commodity with the default commodity's flags - // and precision, if one has been defined. -#if 0 - // jww (2007-05-02): This doesn't do anything currently! - if (default_commodity) - commodity->drop_flags(COMMODITY_STYLE_THOUSANDS | - COMMODITY_STYLE_NOMARKET); -#endif - - commodity->ident = commodities.size(); - - commodities.push_back(commodity.get()); - return commodity.release(); -} - -commodity_t * commodity_pool_t::find_or_create(const string& symbol) -{ - DEBUG("amounts.commodities", "Find-or-create commodity " << symbol); - - commodity_t * commodity = find(symbol); - if (commodity) - return commodity; - return create(symbol); -} - -commodity_t * commodity_pool_t::find(const string& symbol) -{ - DEBUG("amounts.commodities", "Find commodity " << symbol); - - typedef commodity_pool_t::commodities_t::nth_index<1>::type - commodities_by_name; - - commodities_by_name& name_index = commodities.get<1>(); - commodities_by_name::const_iterator i = name_index.find(symbol); - if (i != name_index.end()) - return *i; - else - return NULL; -} - -commodity_t * commodity_pool_t::find(const commodity_t::ident_t ident) -{ - DEBUG("amounts.commodities", "Find commodity by ident " << ident); - - typedef commodity_pool_t::commodities_t::nth_index<0>::type - commodities_by_ident; - - commodities_by_ident& ident_index = commodities.get<0>(); - return ident_index[ident]; -} - -commodity_t * -commodity_pool_t::create(const string& symbol, const annotation_t& details) -{ - commodity_t * new_comm = create(symbol); - if (! new_comm) - return NULL; - - if (details) - return find_or_create(*new_comm, details); - else - return new_comm; -} - -namespace { - string make_qualified_name(const commodity_t& comm, - const annotation_t& details) - { - assert(details); - - if (details.price && details.price->sign() < 0) - throw_(amount_error, "A commodity's price may not be negative"); - - std::ostringstream name; - comm.print(name); - annotated_commodity_t::write_annotations(name, details); - - DEBUG("amounts.commodities", "make_qualified_name for " - << comm.qualified_symbol << std::endl << details); - DEBUG("amounts.commodities", "qualified_name is " << name.str()); - - return name.str(); - } -} - -commodity_t * -commodity_pool_t::find(const string& symbol, const annotation_t& details) -{ - commodity_t * comm = find(symbol); - if (! comm) - return NULL; - - if (details) { - string name = make_qualified_name(*comm, details); - - if (commodity_t * ann_comm = find(name)) { - assert(ann_comm->annotated && as_annotated_commodity(*ann_comm).details); - return ann_comm; - } - return NULL; - } else { - return comm; - } -} - -commodity_t * -commodity_pool_t::find_or_create(const string& symbol, - const annotation_t& details) -{ - commodity_t * comm = find(symbol); - if (! comm) - return NULL; - - if (details) - return find_or_create(*comm, details); - else - return comm; -} - -commodity_t * -commodity_pool_t::create(commodity_t& comm, - const annotation_t& details, - const string& mapping_key) -{ - assert(comm); - assert(details); - assert(! mapping_key.empty()); - - std::auto_ptr<commodity_t> commodity - (new annotated_commodity_t(&comm, details)); - - commodity->qualified_symbol = comm.symbol(); - assert(! commodity->qualified_symbol->empty()); - - DEBUG("amounts.commodities", "Creating annotated commodity " - << "symbol " << commodity->symbol() - << " key " << mapping_key << std::endl << details); - - // Add the fully annotated name to the map, so that this symbol may - // quickly be found again. - commodity->ident = commodities.size(); - commodity->mapping_key_ = mapping_key; - - commodities.push_back(commodity.get()); - return commodity.release(); -} - -commodity_t * commodity_pool_t::find_or_create(commodity_t& comm, - const annotation_t& details) -{ - assert(comm); - assert(details); - - string name = make_qualified_name(comm, details); - assert(! name.empty()); - - if (commodity_t * ann_comm = find(name)) { - assert(ann_comm->annotated && as_annotated_commodity(*ann_comm).details); - return ann_comm; - } - return create(comm, details, name); -} - -} // namespace ledger diff --git a/src/numerics/commodity.h b/src/numerics/commodity.h deleted file mode 100644 index 767023e8..00000000 --- a/src/numerics/commodity.h +++ /dev/null @@ -1,394 +0,0 @@ -/* - * Copyright (c) 2003-2007, 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. - */ - -/** - * @file commodity.h - * @author John Wiegley - * @date Wed Apr 18 22:05:53 2007 - * - * @brief Types for handling commodities. - * - * This file contains one of the most basic types in Ledger: - * commodity_t, and its annotated cousin, annotated_commodity_t. - */ -#ifndef _COMMODITY_H -#define _COMMODITY_H - -namespace ledger { - -class commodity_t - : public delegates_flags<>, - public equality_comparable1<commodity_t, noncopyable> -{ - friend class commodity_pool_t; - - class base_t : public noncopyable, public supports_flags<> - { - public: - typedef std::map<const moment_t, amount_t> history_map; - - struct history_t { - history_map prices; - ptime last_lookup; - }; - -#define COMMODITY_STYLE_DEFAULTS 0x00 -#define COMMODITY_STYLE_SUFFIXED 0x01 -#define COMMODITY_STYLE_SEPARATED 0x02 -#define COMMODITY_STYLE_EUROPEAN 0x04 -#define COMMODITY_STYLE_THOUSANDS 0x08 -#define COMMODITY_STYLE_NOMARKET 0x10 -#define COMMODITY_STYLE_BUILTIN 0x20 - - string symbol; - amount_t::precision_t precision; - optional<string> name; - optional<string> note; - optional<history_t> history; - optional<amount_t> smaller; - optional<amount_t> larger; - - public: - explicit base_t(const string& _symbol) - : supports_flags<>(COMMODITY_STYLE_DEFAULTS), - symbol(_symbol), precision(0) { - TRACE_CTOR(base_t, "const string&"); - } - ~base_t() { - TRACE_DTOR(base_t); - } - }; - -public: - static bool symbol_needs_quotes(const string& symbol); - - typedef base_t::history_t history_t; - typedef base_t::history_map history_map; - typedef uint_least32_t ident_t; - - shared_ptr<base_t> base; - - commodity_pool_t * parent_; - ident_t ident; - optional<string> qualified_symbol; - optional<string> mapping_key_; - bool annotated; - -public: - explicit commodity_t(commodity_pool_t * _parent, - const shared_ptr<base_t>& _base) - : delegates_flags<>(*_base.get()), base(_base), - parent_(_parent), annotated(false) { - TRACE_CTOR(commodity_t, ""); - } - virtual ~commodity_t() { - TRACE_DTOR(commodity_t); - } - - operator bool() const; - - virtual bool operator==(const commodity_t& comm) const { - if (comm.annotated) - return comm == *this; - return base.get() == comm.base.get(); - } - - commodity_pool_t& parent() const { - return *parent_; - } - - string base_symbol() const { - return base->symbol; - } - string symbol() const { - return qualified_symbol ? *qualified_symbol : base_symbol(); - } - - string mapping_key() const { - if (mapping_key_) - return *mapping_key_; - else - return base_symbol(); - } - - optional<string> name() const { - return base->name; - } - void set_name(const optional<string>& arg = none) { - base->name = arg; - } - - optional<string> note() const { - return base->note; - } - void set_note(const optional<string>& arg = none) { - base->note = arg; - } - - amount_t::precision_t precision() const { - return base->precision; - } - void set_precision(amount_t::precision_t arg) { - base->precision = arg; - } - - optional<amount_t> smaller() const { - return base->smaller; - } - void set_smaller(const optional<amount_t>& arg = none) { - base->smaller = arg; - } - - optional<amount_t> larger() const { - return base->larger; - } - void set_larger(const optional<amount_t>& arg = none) { - base->larger = arg; - } - - optional<history_t> history() const { - return base->history; - } - - void add_price(const moment_t& date, const amount_t& price); - bool remove_price(const moment_t& date); - - optional<amount_t> value(const optional<moment_t>& moment = none); - - static void parse_symbol(std::istream& in, string& symbol); - static void parse_symbol(char *& p, string& symbol); - static string parse_symbol(std::istream& in) { - string temp; - parse_symbol(in, temp); - return temp; - } - - void print(std::ostream& out) const { - out << symbol(); - } - - void read(std::istream& in); - void read(char *& data); - void write(std::ostream& out) const; - - bool valid() const; -}; - -inline std::ostream& operator<<(std::ostream& out, const commodity_t& comm) { - comm.print(out); - return out; -} - -struct annotation_t : public equality_comparable<annotation_t> -{ - optional<amount_t> price; - optional<moment_t> date; - optional<string> tag; - - explicit annotation_t - (const optional<amount_t>& _price = none, - const optional<moment_t>& _date = none, - const optional<string>& _tag = none) - : price(_price), date(_date), tag(_tag) {} - - operator bool() const { - return price || date || tag; - } - - bool operator==(const annotation_t& rhs) const { - return (price == rhs.price && - date == rhs.date && - tag == rhs.tag); - } - - void parse(std::istream& in); - void print(std::ostream& out) const { - out << "price " << (price ? price->to_string() : "NONE") << " " - << "date " << (date ? *date : moment_t()) << " " - << "tag " << (tag ? *tag : "NONE"); - } - - bool valid() const { - assert(*this); - return true; - } -}; - -inline std::ostream& operator<<(std::ostream& out, const annotation_t& details) { - details.print(out); - return out; -} - -class annotated_commodity_t - : public commodity_t, - public equality_comparable<annotated_commodity_t, - equality_comparable2<annotated_commodity_t, commodity_t, - noncopyable> > -{ -public: - commodity_t * ptr; - annotation_t details; - - explicit annotated_commodity_t(commodity_t * _ptr, - const annotation_t& _details) - : commodity_t(_ptr->parent_, _ptr->base), ptr(_ptr), details(_details) { - TRACE_CTOR(annotated_commodity_t, ""); - annotated = true; - } - virtual ~annotated_commodity_t() { - TRACE_DTOR(annotated_commodity_t); - } - - virtual bool operator==(const commodity_t& comm) const; - virtual bool operator==(const annotated_commodity_t& comm) const { - return *this == static_cast<const commodity_t&>(comm); - } - - commodity_t& referent() { - return *ptr; - } - const commodity_t& referent() const { - return *ptr; - } - - commodity_t& strip_annotations(const bool _keep_price, - const bool _keep_date, - const bool _keep_tag); - - void write_annotations(std::ostream& out) const { - annotated_commodity_t::write_annotations(out, details); - } - - static void write_annotations(std::ostream& out, - const annotation_t& info); -}; - -inline annotated_commodity_t& -as_annotated_commodity(commodity_t& commodity) { - return downcast<annotated_commodity_t>(commodity); -} -inline const annotated_commodity_t& -as_annotated_commodity(const commodity_t& commodity) { - return downcast<const annotated_commodity_t>(commodity); -} - - -struct compare_amount_commodities { - bool operator()(const amount_t * left, const amount_t * right) const; -}; - -class commodity_pool_t : public noncopyable -{ - /** - * The commodities collection in commodity_pool_t maintains pointers - * to all the commodities which have ever been created by the user, - * whether explicitly by calling the create methods of - * commodity_pool_t, or implicitly by parsing a commoditized amount. - * - * The `commodities' member variable represents a collection which - * is indexed by two vertices: first, and ordered sequence of unique - * integer which identify commodities by a numerical identifier; and - * second, by a hashed set of symbolic names which reflect how the - * commodity was referred to by the user. - */ - typedef multi_index_container< - commodity_t *, - multi_index::indexed_by< - multi_index::random_access<>, - multi_index::hashed_unique< - multi_index::const_mem_fun<commodity_t, - string, &commodity_t::mapping_key> > - > - > commodities_t; - - commodities_t commodities; - -public: - commodity_t * null_commodity; - commodity_t * default_commodity; - -private: - template<typename T> - struct first_initialized - { - typedef T result_type; - - template<typename InputIterator> - T operator()(InputIterator first, InputIterator last) const - { - for (; first != last; first++) - if (*first) - return *first; - return T(); - } - }; - -public: - boost::function<optional<amount_t> - (commodity_t& commodity, - const optional<moment_t>& date, - const optional<moment_t>& moment, - const optional<moment_t>& last)> get_quote; - - explicit commodity_pool_t(); - - ~commodity_pool_t() { - typedef commodity_pool_t::commodities_t::nth_index<0>::type - commodities_by_ident; - - commodities_by_ident& ident_index = commodities.get<0>(); - for (commodities_by_ident::iterator i = ident_index.begin(); - i != ident_index.end(); - i++) - checked_delete(*i); - } - - commodity_t * create(const string& symbol); - commodity_t * find(const string& name); - commodity_t * find(const commodity_t::ident_t ident); - commodity_t * find_or_create(const string& symbol); - - commodity_t * create(const string& symbol, const annotation_t& details); - commodity_t * find(const string& symbol, const annotation_t& details); - commodity_t * find_or_create(const string& symbol, - const annotation_t& details); - - commodity_t * create(commodity_t& comm, - const annotation_t& details, - const string& mapping_key); - - commodity_t * find_or_create(commodity_t& comm, - const annotation_t& details); -}; - -} // namespace ledger - -#endif // _COMMODITY_H diff --git a/src/numerics/value.cc b/src/numerics/value.cc deleted file mode 100644 index 236d0582..00000000 --- a/src/numerics/value.cc +++ /dev/null @@ -1,1512 +0,0 @@ -/* - * Copyright (c) 2003-2007, 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 "value.h" -#include "node.h" - -namespace ledger { - -intrusive_ptr<value_t::storage_t> value_t::true_value; -intrusive_ptr<value_t::storage_t> value_t::false_value; - -void value_t::storage_t::destroy() -{ - switch (type) { - case AMOUNT: - reinterpret_cast<amount_t *>(data)->~amount_t(); - break; - case BALANCE: - checked_delete(*reinterpret_cast<balance_t **>(data)); - break; - case BALANCE_PAIR: - checked_delete(*reinterpret_cast<balance_pair_t **>(data)); - break; - case STRING: - reinterpret_cast<string *>(data)->~string(); - break; - case SEQUENCE: - checked_delete(*reinterpret_cast<sequence_t **>(data)); - break; - case POINTER: - reinterpret_cast<boost::any *>(data)->~any(); - break; - - default: - break; - } - type = VOID; -} - -void value_t::initialize() -{ -#if 0 - LOGGER("value.initialize"); -#endif - - true_value = new storage_t; - true_value->type = BOOLEAN; - *reinterpret_cast<bool *>(true_value->data) = true; - - false_value = new storage_t; - false_value->type = BOOLEAN; - *reinterpret_cast<bool *>(false_value->data) = false; - - BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(bool)); - BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(moment_t)); - BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(long)); - BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(amount_t)); - BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(balance_t *)); - BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(balance_pair_t *)); - BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(string)); - BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(sequence_t *)); - BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(xml::node_t *)); - BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(boost::any)); - -#if 0 - DEBUG_(std::setw(3) << std::right << sizeof(bool) - << " sizeof(bool)"); - DEBUG_(std::setw(3) << std::right << sizeof(moment_t) - << " sizeof(moment_t)"); - DEBUG_(std::setw(3) << std::right << sizeof(long) - << " sizeof(long)"); - DEBUG_(std::setw(3) << std::right << sizeof(amount_t) - << " sizeof(amount_t)"); - DEBUG_(std::setw(3) << std::right << sizeof(balance_t *) - << " sizeof(balance_t *)"); - DEBUG_(std::setw(3) << std::right << sizeof(balance_pair_t *) - << " sizeof(balance_pair_t *)"); - DEBUG_(std::setw(3) << std::right << sizeof(string) - << " sizeof(string)"); - DEBUG_(std::setw(3) << std::right << sizeof(sequence_t *) - << " sizeof(sequence_t *)"); - DEBUG_(std::setw(3) << std::right << sizeof(boost::any) - << " sizeof(boost::any)"); -#endif -} - -void value_t::shutdown() -{ - true_value = intrusive_ptr<storage_t>(); - false_value = intrusive_ptr<storage_t>(); -} - -void value_t::_dup() -{ - assert(storage); - if (storage->refc > 1) { - storage = new storage_t(*storage.get()); - - // If the data referenced by storage is an allocated pointer, we - // need to create a new object in order to achieve duplication. - switch (storage->type) { - case BALANCE: - *(balance_t **) storage->data = - new balance_t(**(balance_t **) storage->data); - break; - case BALANCE_PAIR: - *(balance_pair_t **) storage->data = - new balance_pair_t(**(balance_pair_t **) storage->data); - break; - case SEQUENCE: - *(sequence_t **) storage->data = - new sequence_t(**(sequence_t **) storage->data); - break; - default: - break; // everything else has been duplicated - } - } -} - -value_t::operator bool() const -{ - switch (type()) { - case BOOLEAN: - return as_boolean(); - case INTEGER: - return as_long(); - case DATETIME: - return is_valid_moment(as_datetime()); - case AMOUNT: - return as_amount(); - case BALANCE: - return as_balance(); - case BALANCE_PAIR: - return as_balance_pair(); - case STRING: - return ! as_string().empty(); - case SEQUENCE: - return ! as_sequence().empty(); - case XML_NODE: - return as_xml_node()->to_value(); - case POINTER: - return ! as_any_pointer().empty(); - default: - assert(false); - break; - } - assert(false); - return 0; -} - -bool value_t::to_boolean() const -{ - if (is_boolean()) { - return as_boolean(); - } else { - value_t temp(*this); - temp.in_place_cast(BOOLEAN); - return temp.as_boolean(); - } -} - -long value_t::to_long() const -{ - if (is_long()) { - return as_long(); - } else { - value_t temp(*this); - temp.in_place_cast(INTEGER); - return temp.as_long(); - } -} - -moment_t value_t::to_datetime() const -{ - if (is_datetime()) { - return as_datetime(); - } else { - value_t temp(*this); - temp.in_place_cast(DATETIME); - return temp.as_datetime(); - } -} - -amount_t value_t::to_amount() const -{ - if (is_amount()) { - return as_amount(); - } else { - value_t temp(*this); - temp.in_place_cast(AMOUNT); - return temp.as_amount(); - } -} - -balance_t value_t::to_balance() const -{ - if (is_balance()) { - return as_balance(); - } else { - value_t temp(*this); - temp.in_place_cast(BALANCE); - return temp.as_balance(); - } -} - -balance_pair_t value_t::to_balance_pair() const -{ - if (is_balance_pair()) { - return as_balance_pair(); - } else { - value_t temp(*this); - temp.in_place_cast(BALANCE_PAIR); - return temp.as_balance_pair(); - } -} - -string value_t::to_string() const -{ - if (is_string()) { - return as_string(); - } else { - value_t temp(*this); - temp.in_place_cast(STRING); - return temp.as_string(); - } -} - -value_t::sequence_t value_t::to_sequence() const -{ - if (is_sequence()) { - return as_sequence(); - } else { - value_t temp(*this); - temp.in_place_cast(SEQUENCE); - return temp.as_sequence(); - } -} - - -void value_t::in_place_simplify() -{ - LOGGER("amounts.values.simplify"); - - if (is_realzero()) { - DEBUG_("Zeroing type " << type()); - set_long(0L); - return; - } - - if (is_balance_pair() && - (! as_balance_pair().cost || as_balance_pair().cost->is_realzero())) { - DEBUG_("Reducing balance pair to balance"); - in_place_cast(BALANCE); - } - - if (is_balance() && as_balance().amounts.size() == 1) { - DEBUG_("Reducing balance to amount"); - in_place_cast(AMOUNT); - } - -#if 0 - if (is_amount() && ! as_amount().has_commodity() && - as_amount().fits_in_long()) { - DEBUG_("Reducing amount to integer"); - in_place_cast(INTEGER); - } -#endif -} - -value_t& value_t::operator+=(const value_t& val) -{ - if (is_string()) { - if (val.is_string()) - as_string_lval() += val.as_string(); - else - as_string_lval() += val.to_string(); - return *this; - } - else if (is_sequence()) { - if (val.is_sequence()) { - sequence_t& seq(as_sequence_lval()); - seq.insert(seq.end(), val.as_sequence().begin(), - val.as_sequence().end()); - } else { - as_sequence_lval().push_back(val); - } - return *this; - } - - if (val.is_xml_node()) // recurse - return *this += val.as_xml_node()->to_value(); - - switch (type()) { - case DATETIME: - switch (val.type()) { - case INTEGER: - as_datetime_lval() += date_duration(val.as_long()); - return *this; - case AMOUNT: - as_datetime_lval() += date_duration(val.as_amount().to_long()); - return *this; - default: - break; - } - break; - - case INTEGER: - switch (val.type()) { - case INTEGER: - as_long_lval() += val.as_long(); - return *this; - case AMOUNT: - in_place_cast(AMOUNT); - as_amount_lval() += val.as_amount(); - return *this; - case BALANCE: - in_place_cast(BALANCE); - as_balance_lval() += val.as_balance(); - return *this; - case BALANCE_PAIR: - in_place_cast(BALANCE_PAIR); - as_balance_pair_lval() += val.as_balance_pair(); - return *this; - default: - break; - } - break; - - case AMOUNT: - switch (val.type()) { - case INTEGER: - if (as_amount().has_commodity()) { - in_place_cast(BALANCE); - return *this += val; - } else { - as_amount_lval() += val.as_long(); - return *this; - } - break; - - case AMOUNT: - if (as_amount().commodity() != val.as_amount().commodity()) { - in_place_cast(BALANCE); - return *this += val; - } else { - as_amount_lval() += val.as_amount(); - return *this; - } - break; - - case BALANCE: - in_place_cast(BALANCE); - as_balance_lval() += val.as_balance(); - return *this; - - case BALANCE_PAIR: - in_place_cast(BALANCE_PAIR); - as_balance_pair_lval() += val.as_balance_pair(); - return *this; - default: - break; - } - break; - - case BALANCE: - switch (val.type()) { - case INTEGER: - as_balance_lval() += val.to_amount(); - return *this; - case AMOUNT: - as_balance_lval() += val.as_amount(); - return *this; - case BALANCE: - as_balance_lval() += val.as_balance(); - return *this; - case BALANCE_PAIR: - in_place_cast(BALANCE_PAIR); - as_balance_pair_lval() += val.as_balance_pair(); - return *this; - default: - break; - } - break; - - case BALANCE_PAIR: - switch (val.type()) { - case INTEGER: - as_balance_pair_lval() += val.to_amount(); - return *this; - case AMOUNT: - as_balance_pair_lval() += val.as_amount(); - return *this; - case BALANCE: - as_balance_pair_lval() += val.as_balance(); - return *this; - case BALANCE_PAIR: - as_balance_pair_lval() += val.as_balance_pair(); - return *this; - default: - break; - } - break; - - default: - break; - } - - throw_(value_error, "Cannot add " << label() << " to " << val.label()); - - return *this; -} - -value_t& value_t::operator-=(const value_t& val) -{ - if (is_sequence()) { - sequence_t& seq(as_sequence_lval()); - - if (val.is_sequence()) { - for (sequence_t::const_iterator i = val.as_sequence().begin(); - i != val.as_sequence().end(); - i++) { - sequence_t::iterator j = std::find(seq.begin(), seq.end(), *i); - if (j != seq.end()) - seq.erase(j); - } - } else { - sequence_t::iterator i = std::find(seq.begin(), seq.end(), val); - if (i != seq.end()) - seq.erase(i); - } - return *this; - } - - if (val.is_xml_node()) // recurse - return *this -= val.as_xml_node()->to_value(); - - switch (type()) { - case DATETIME: - switch (val.type()) { - case INTEGER: - as_datetime_lval() -= date_duration(val.as_long()); - return *this; - case AMOUNT: - as_datetime_lval() -= date_duration(val.as_amount().to_long()); - return *this; - default: - break; - } - break; - - case INTEGER: - switch (val.type()) { - case INTEGER: - as_long_lval() -= val.as_long(); - return *this; - case AMOUNT: - in_place_cast(AMOUNT); - as_amount_lval() -= val.as_amount(); - in_place_simplify(); - return *this; - case BALANCE: - in_place_cast(BALANCE); - as_balance_lval() -= val.as_balance(); - in_place_simplify(); - return *this; - case BALANCE_PAIR: - in_place_cast(BALANCE_PAIR); - as_balance_pair_lval() -= val.as_balance_pair(); - in_place_simplify(); - return *this; - default: - break; - } - break; - - case AMOUNT: - switch (val.type()) { - case INTEGER: - if (as_amount().has_commodity()) { - in_place_cast(BALANCE); - *this -= val; - in_place_simplify(); - return *this; - } else { - as_amount_lval() -= val.as_long(); - in_place_simplify(); - return *this; - } - break; - - case AMOUNT: - if (as_amount().commodity() != val.as_amount().commodity()) { - in_place_cast(BALANCE); - *this -= val; - in_place_simplify(); - return *this; - } else { - as_amount_lval() -= val.as_amount(); - in_place_simplify(); - return *this; - } - break; - - case BALANCE: - in_place_cast(BALANCE); - as_balance_lval() -= val.as_balance(); - in_place_simplify(); - return *this; - - case BALANCE_PAIR: - in_place_cast(BALANCE_PAIR); - as_balance_pair_lval() -= val.as_balance_pair(); - in_place_simplify(); - return *this; - default: - break; - } - break; - - case BALANCE: - switch (val.type()) { - case INTEGER: - as_balance_lval() -= val.to_amount(); - in_place_simplify(); - return *this; - case AMOUNT: - as_balance_lval() -= val.as_amount(); - in_place_simplify(); - return *this; - case BALANCE: - as_balance_lval() -= val.as_balance(); - in_place_simplify(); - return *this; - case BALANCE_PAIR: - in_place_cast(BALANCE_PAIR); - as_balance_pair_lval() -= val.as_balance_pair(); - in_place_simplify(); - return *this; - default: - break; - } - break; - - case BALANCE_PAIR: - switch (val.type()) { - case INTEGER: - as_balance_pair_lval() -= val.to_amount(); - in_place_simplify(); - return *this; - case AMOUNT: - as_balance_pair_lval() -= val.as_amount(); - in_place_simplify(); - return *this; - case BALANCE: - as_balance_pair_lval() -= val.as_balance(); - in_place_simplify(); - return *this; - case BALANCE_PAIR: - as_balance_pair_lval() -= val.as_balance_pair(); - in_place_simplify(); - return *this; - default: - break; - } - break; - - default: - break; - } - - throw_(value_error, "Cannot subtract " << label() << " from " << val.label()); - - return *this; -} - -value_t& value_t::operator*=(const value_t& val) -{ - if (is_string()) { - string temp; - long count = val.to_long(); - for (long i = 0; i < count; i++) - temp += as_string(); - set_string(temp); - return *this; - } - else if (is_sequence()) { - value_t temp; - long count = val.to_long(); - for (long i = 0; i < count; i++) - temp += as_sequence(); - return *this = temp; - } - - if (val.is_xml_node()) // recurse - return *this *= val.as_xml_node()->to_value(); - - switch (type()) { - case INTEGER: - switch (val.type()) { - case INTEGER: - as_long_lval() *= val.as_long(); - return *this; - case AMOUNT: - set_amount(val.as_amount() * as_long()); - return *this; - default: - break; - } - break; - - case AMOUNT: - switch (val.type()) { - case INTEGER: - as_amount_lval() *= val.as_long(); - return *this; - case AMOUNT: - if (as_amount().commodity() == val.as_amount().commodity() || - ! val.as_amount().has_commodity()) { - as_amount_lval() *= val.as_amount(); - return *this; - } - break; - default: - break; - } - break; - - case BALANCE: - switch (val.type()) { - case INTEGER: - as_balance_lval() *= val.as_long(); - return *this; - case AMOUNT: - if (! val.as_amount().has_commodity()) { - as_balance_lval() *= val.as_amount(); - return *this; - } - break; - default: - break; - } - break; - - case BALANCE_PAIR: - switch (val.type()) { - case INTEGER: - as_balance_pair_lval() *= val.as_long(); - return *this; - case AMOUNT: - if (! val.as_amount().has_commodity()) { - as_balance_pair_lval() *= val.as_amount(); - return *this; - } - break; - default: - break; - } - break; - - default: - break; - } - - throw_(value_error, "Cannot multiply " << label() << " with " << val.label()); - - return *this; -} - -value_t& value_t::operator/=(const value_t& val) -{ - if (val.is_xml_node()) // recurse - return *this /= val.as_xml_node()->to_value(); - - switch (type()) { - case INTEGER: - switch (val.type()) { - case INTEGER: - as_long_lval() /= val.as_long(); - return *this; - case AMOUNT: - set_amount(val.as_amount() / as_long()); - return *this; - default: - break; - } - break; - - case AMOUNT: - switch (val.type()) { - case INTEGER: - as_amount_lval() /= val.as_long(); - return *this; - - case AMOUNT: - if (as_amount().commodity() == val.as_amount().commodity() || - ! val.as_amount().has_commodity()) { - as_amount_lval() /= val.as_amount(); - return *this; - } - break; - default: - break; - } - break; - - case BALANCE: - switch (val.type()) { - case INTEGER: - as_balance_lval() /= val.as_long(); - return *this; - case AMOUNT: - if (! val.as_amount().has_commodity()) { - as_balance_lval() /= val.as_amount(); - return *this; - } - break; - default: - break; - } - break; - - case BALANCE_PAIR: - switch (val.type()) { - case INTEGER: - as_balance_pair_lval() /= val.as_long(); - return *this; - case AMOUNT: - if (! val.as_amount().has_commodity()) { - as_balance_pair_lval() /= val.as_amount(); - return *this; - } - break; - default: - break; - } - break; - - default: - break; - } - - throw_(value_error, "Cannot divide " << label() << " by " << val.label()); - - return *this; -} - - -bool value_t::operator==(const value_t& val) const -{ - if (is_xml_node() && val.is_xml_node()) - return as_xml_node() == val.as_xml_node(); - else if (is_xml_node()) - return as_xml_node()->to_value() == val; - else if (val.is_xml_node()) - return *this == val.as_xml_node()->to_value(); - - switch (type()) { - case BOOLEAN: - if (val.is_boolean()) - return as_boolean() == val.as_boolean(); - break; - - case DATETIME: - if (val.is_datetime()) - return as_datetime() == val.as_datetime(); - break; - - case INTEGER: - switch (val.type()) { - case INTEGER: - return as_long() == val.as_long(); - case AMOUNT: - return val.as_amount() == to_amount(); - case BALANCE: - return val.as_balance() == to_amount(); - case BALANCE_PAIR: - return val.as_balance_pair() == to_amount(); - default: - break; - } - break; - - case AMOUNT: - switch (val.type()) { - case INTEGER: - return as_amount() == val.as_long(); - case AMOUNT: - return as_amount() == val.as_amount(); - case BALANCE: - return val.as_balance() == as_amount(); - case BALANCE_PAIR: - return val.as_balance_pair() == as_amount(); - default: - break; - } - break; - - case BALANCE: - switch (val.type()) { - case INTEGER: - return as_balance() == val.to_amount(); - case AMOUNT: - return as_balance() == val.as_amount(); - case BALANCE: - return as_balance() == val.as_balance(); - case BALANCE_PAIR: - return val.as_balance_pair() == as_balance(); - default: - break; - } - break; - - case BALANCE_PAIR: - switch (val.type()) { - case INTEGER: - return as_balance_pair() == val.to_amount(); - case AMOUNT: - return as_balance_pair() == val.as_amount(); - case BALANCE: - return as_balance_pair() == val.as_balance(); - case BALANCE_PAIR: - return as_balance_pair() == val.as_balance_pair(); - default: - break; - } - break; - - case STRING: - if (val.is_string()) - return as_string() == val.as_string(); - break; - - case SEQUENCE: - if (val.is_sequence()) - return as_sequence() == val.as_sequence(); - break; - - default: - break; - } - - throw_(value_error, "Cannot compare " << label() << " to " << val.label()); - - return *this; -} - -bool value_t::operator<(const value_t& val) const -{ - if (is_xml_node() && val.is_xml_node()) - return as_xml_node() < val.as_xml_node(); - else if (is_xml_node()) - return as_xml_node()->to_value() < val; - else if (val.is_xml_node()) - return *this < val.as_xml_node()->to_value(); - - switch (type()) { - case DATETIME: - if (val.is_datetime()) - return as_datetime() < val.as_datetime(); - break; - - case INTEGER: - switch (val.type()) { - case INTEGER: - return as_long() < val.as_long(); - case AMOUNT: - return val.as_amount() < as_long(); - default: - break; - } - break; - - case AMOUNT: - switch (val.type()) { - case INTEGER: - return as_amount() < val.as_long(); - case AMOUNT: - return as_amount() < val.as_amount(); - default: - break; - } - break; - - case STRING: - if (val.is_string()) - return as_string() < val.as_string(); - break; - - default: - break; - } - - throw_(value_error, "Cannot compare " << label() << " to " << val.label()); - - return *this; -} - -#if 0 -bool value_t::operator>(const value_t& val) const -{ - if (is_xml_node() && val.is_xml_node()) - return as_xml_node() > val.as_xml_node(); - else if (is_xml_node()) - return as_xml_node()->to_value() > val; - else if (val.is_xml_node()) - return *this > val.as_xml_node()->to_value(); - - switch (type()) { - case DATETIME: - if (val.is_datetime()) - return as_datetime() > val.as_datetime(); - break; - - case INTEGER: - switch (val.type()) { - case INTEGER: - return as_long() > val.as_long(); - case AMOUNT: - return val.as_amount() > as_long(); - default: - break; - } - break; - - case AMOUNT: - switch (val.type()) { - case INTEGER: - return as_amount() > val.as_long(); - case AMOUNT: - return as_amount() > val.as_amount(); - default: - break; - } - break; - - case STRING: - if (val.is_string()) - return as_string() > val.as_string(); - break; - - default: - break; - } - - throw_(value_error, - "Cannot compare " << label() << " to " << val.label()); - - return *this; -} -#endif - -void value_t::in_place_cast(type_t cast_type) -{ - if (type() == cast_type) - return; - - if (cast_type == BOOLEAN) { - set_boolean(bool(*this)); - return; - } - else if (cast_type == SEQUENCE) { - sequence_t temp; - if (! is_null()) - temp.push_back(*this); - set_sequence(temp); - return; - } - - // This must came after the if's above, otherwise it would be - // impossible to turn an XML node into a sequence containing that - // same XML node. - if (is_xml_node()) { - *this = as_xml_node()->to_value().cast(cast_type); - return; - } - - switch (type()) { - case BOOLEAN: - switch (cast_type) { - case STRING: - set_string(as_boolean() ? "true" : "false"); - return; - default: - break; - } - break; - - case INTEGER: - switch (cast_type) { - case AMOUNT: - set_amount(as_long()); - return; - case BALANCE: - set_balance(to_amount()); - return; - case BALANCE_PAIR: - set_balance_pair(to_amount()); - return; - case STRING: - set_string(lexical_cast<string>(as_long())); - return; - default: - break; - } - break; - - case AMOUNT: - switch (cast_type) { - case INTEGER: - set_long(as_amount().to_long()); - return; - case BALANCE: - set_balance(as_amount()); - return; - case BALANCE_PAIR: - set_balance_pair(as_amount()); - return; - case STRING: - set_string(as_amount().to_string()); - return; - default: - break; - } - break; - - case BALANCE: - switch (cast_type) { - case AMOUNT: { - const balance_t& temp(as_balance()); - if (temp.amounts.size() == 1) { - set_amount((*temp.amounts.begin()).second); - return; - } - else if (temp.amounts.size() == 0) { - set_amount(0L); - return; - } - else { - throw_(value_error, "Cannot convert " << label() << - " with multiple commodities to " << label(cast_type)); - } - break; - } - case BALANCE_PAIR: - set_balance_pair(as_balance()); - return; - default: - break; - } - break; - - case BALANCE_PAIR: - switch (cast_type) { - case AMOUNT: { - const balance_t& temp(as_balance_pair().quantity()); - if (temp.amounts.size() == 1) { - set_amount((*temp.amounts.begin()).second); - return; - } - else if (temp.amounts.size() == 0) { - set_amount(0L); - return; - } - else { - throw_(value_error, "Cannot convert " << label() << - " with multiple commodities to " << label(cast_type)); - } - break; - } - case BALANCE: - set_balance(as_balance_pair().quantity()); - return; - default: - break; - } - break; - - case STRING: - switch (cast_type) { - case INTEGER: { - if (all(as_string(), is_digit())) { - set_long(lexical_cast<long>(as_string())); - return; - } else { - throw_(value_error, - "Cannot convert string '" << *this << "' to an integer"); - } - break; - } - case AMOUNT: - set_amount(amount_t(as_string())); - return; - default: - break; - } - break; - - default: - break; - } - - throw_(value_error, - "Cannot convert " << label() << " to " << label(cast_type)); -} - -void value_t::in_place_negate() -{ - switch (type()) { - case BOOLEAN: - set_boolean(! as_boolean()); - return; - case INTEGER: - set_long(- as_long()); - return; - case AMOUNT: - as_amount_lval().in_place_negate(); - return; - case BALANCE: - as_balance_lval().in_place_negate(); - return; - case BALANCE_PAIR: - as_balance_pair_lval().in_place_negate(); - return; - case XML_NODE: - *this = as_xml_node()->to_value(); - in_place_negate(); - return; - default: - break; - } - - throw_(value_error, "Cannot negate " << label()); -} - -bool value_t::is_realzero() const -{ - switch (type()) { - case BOOLEAN: - return ! as_boolean(); - case INTEGER: - return as_long() == 0; - case DATETIME: - return ! is_valid_moment(as_datetime()); - case AMOUNT: - return as_amount().is_realzero(); - case BALANCE: - return as_balance().is_realzero(); - case BALANCE_PAIR: - return as_balance_pair().is_realzero(); - case STRING: - return as_string().empty(); - case SEQUENCE: - return as_sequence().empty(); - - case XML_NODE: - return as_xml_node() == NULL; - case POINTER: - return as_any_pointer().empty(); - - default: - assert(false); - break; - } - assert(false); - return true; -} - -value_t value_t::value(const optional<moment_t>& moment) const -{ - switch (type()) { - case INTEGER: - return *this; - - case AMOUNT: { - if (optional<amount_t> val = as_amount().value(moment)) - return *val; - return false; - } - case BALANCE: { - if (optional<balance_t> bal = as_balance().value(moment)) - return *bal; - return false; - } - case BALANCE_PAIR: { - if (optional<balance_t> bal_pair = - as_balance_pair().quantity().value(moment)) - return *bal_pair; - return false; - } - case XML_NODE: - return as_xml_node()->to_value().value(moment); - - default: - break; - } - - throw_(value_error, "Cannot find the value of " << label()); - return value_t(); -} - -void value_t::in_place_reduce() -{ - switch (type()) { - case INTEGER: - return; - case AMOUNT: - as_amount_lval().in_place_reduce(); - return; - case BALANCE: - as_balance_lval().in_place_reduce(); - return; - case BALANCE_PAIR: - as_balance_pair_lval().in_place_reduce(); - return; - case XML_NODE: - *this = as_xml_node()->to_value(); - in_place_reduce(); // recurse - return; - default: - break; - } - - throw_(value_error, "Cannot reduce " << label()); -} - -value_t value_t::round() const -{ - switch (type()) { - case INTEGER: - return *this; - case AMOUNT: - return as_amount().round(); - case XML_NODE: - return as_xml_node()->to_value().round(); - default: - break; - } - - throw_(value_error, "Cannot round " << label()); - return value_t(); -} - -value_t value_t::unround() const -{ - switch (type()) { - case INTEGER: - return *this; - case AMOUNT: - return as_amount().unround(); - case XML_NODE: - return as_xml_node()->to_value().unround(); - default: - break; - } - - throw_(value_error, "Cannot unround " << label()); - return value_t(); -} - -value_t value_t::annotated_price() const -{ - switch (type()) { - case AMOUNT: { - optional<amount_t> temp = as_amount().annotation_details().price; - if (! temp) - return false; - return *temp; - } - - case XML_NODE: - return as_xml_node()->to_value().annotated_price(); - - default: - break; - } - - throw_(value_error, "Cannot find the annotated price of " << label()); - return value_t(); -} - -value_t value_t::annotated_date() const -{ - switch (type()) { - case DATETIME: - return *this; - - case AMOUNT: { - optional<moment_t> temp = as_amount().annotation_details().date; - if (! temp) - return false; - return *temp; - } - - case XML_NODE: - return as_xml_node()->to_value().annotated_date(); - - default: - break; - } - - throw_(value_error, "Cannot find the annotated date of " << label()); - return value_t(); -} - -value_t value_t::annotated_tag() const -{ - switch (type()) { - case DATETIME: - return *this; - - case AMOUNT: { - optional<string> temp = as_amount().annotation_details().tag; - if (! temp) - return false; - return value_t(*temp, true); - } - - case XML_NODE: - return as_xml_node()->to_value().annotated_tag(); - - default: - break; - } - - throw_(value_error, "Cannot find the annotated tag of " << label()); - return value_t(); -} - -value_t value_t::strip_annotations(const bool keep_price, - const bool keep_date, - const bool keep_tag) const -{ - switch (type()) { - case VOID: - case BOOLEAN: - case INTEGER: - case DATETIME: - case STRING: - case XML_NODE: - case POINTER: - return *this; - - case SEQUENCE: { - sequence_t temp; - foreach (const value_t& value, as_sequence()) - temp.push_back(value.strip_annotations(keep_price, keep_date, keep_tag)); - return temp; - } - - case AMOUNT: - return as_amount().strip_annotations(keep_price, keep_date, keep_tag); - case BALANCE: - return as_balance().strip_annotations(keep_price, keep_date, keep_tag); - case BALANCE_PAIR: - return as_balance_pair().quantity().strip_annotations(keep_price, keep_date, - keep_tag); - - default: - assert(false); - break; - } - assert(false); - return value_t(); -} - -value_t value_t::cost() const -{ - switch (type()) { - case INTEGER: - case AMOUNT: - case BALANCE: - return *this; - - case BALANCE_PAIR: - assert(as_balance_pair().cost); - if (as_balance_pair().cost) - return *(as_balance_pair().cost); - else - return as_balance_pair().quantity(); - - case XML_NODE: - return as_xml_node()->to_value().cost(); - - default: - break; - } - - throw_(value_error, "Cannot find the cost of " << label()); - return value_t(); -} - -value_t& value_t::add(const amount_t& amount, const optional<amount_t>& tcost) -{ - switch (type()) { - case INTEGER: - case AMOUNT: - if (tcost) { - in_place_cast(BALANCE_PAIR); - return add(amount, tcost); - } - else if ((is_amount() && - as_amount().commodity() != amount.commodity()) || - (! is_amount() && amount.commodity())) { - in_place_cast(BALANCE); - return add(amount, tcost); - } - else if (! is_amount()) { - in_place_cast(AMOUNT); - } - *this += amount; - break; - - case BALANCE: - if (tcost) { - in_place_cast(BALANCE_PAIR); - return add(amount, tcost); - } - *this += amount; - break; - - case BALANCE_PAIR: - as_balance_pair_lval().add(amount, tcost); - break; - - default: - break; - } - - throw_(value_error, "Cannot add an amount to " << label()); - return *this; -} - -void value_t::print(std::ostream& out, const int first_width, - const int latter_width) const -{ - switch (type()) { - case VOID: - out << "NULL"; - break; - - case BOOLEAN: - case DATETIME: - case INTEGER: - case AMOUNT: - case STRING: - case POINTER: - // jww (2007-05-14): I need a version of this print just for XPath - // expression, since amounts and strings need to be output with - // special syntax. - out << *this; - break; - - case XML_NODE: - as_xml_node()->print(out); - break; - - case SEQUENCE: { - out << '('; - bool first = true; - foreach (const value_t& value, as_sequence()) { - if (first) - first = false; - else - out << ", "; - - value.print(out, first_width, latter_width); - } - out << ')'; - break; - } - - case BALANCE: - as_balance().print(out, first_width, latter_width); - break; - case BALANCE_PAIR: - as_balance_pair().print(out, first_width, latter_width); - break; - default: - assert(false); - break; - } -} - -} // namespace ledger diff --git a/src/op.cc b/src/op.cc new file mode 100644 index 00000000..f38fc86b --- /dev/null +++ b/src/op.cc @@ -0,0 +1,732 @@ +/* + * 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 "op.h" +#include "scope.h" +#include "commodity.h" +#include "pool.h" + +namespace ledger { + +expr_t::ptr_op_t expr_t::op_t::compile(scope_t& scope, const int depth) +{ + if (is_ident()) { + DEBUG("expr.compile", "lookup: " << as_ident()); + + if (ptr_op_t def = scope.lookup(symbol_t::FUNCTION, as_ident())) { + // Identifier references are first looked up at the point of + // definition, and then at the point of every use if they could + // not be found there. + if (SHOW_DEBUG("expr.compile")) { + DEBUG("expr.compile", "Found definition:"); + def->dump(*_log_stream, 0); + } + return copy(def); + } + else if (left()) { + return copy(); + } + return this; + } + + if (kind < TERMINALS) + return this; + + if (kind == O_DEFINE) { + switch (left()->kind) { + case IDENT: + scope.define(symbol_t::FUNCTION, left()->as_ident(), right()); + break; + case O_CALL: + if (left()->left()->is_ident()) + scope.define(symbol_t::FUNCTION, left()->left()->as_ident(), this); + else + throw_(compile_error, _("Invalid function definition")); + break; + default: + throw_(compile_error, _("Invalid function definition")); + } + return wrap_value(value_t()); + } + + ptr_op_t lhs(left()->compile(scope, depth)); + ptr_op_t rhs(kind > UNARY_OPERATORS && has_right() ? + (kind == O_LOOKUP ? right() : + right()->compile(scope, depth)) : NULL); + + if (lhs == left() && (! rhs || rhs == right())) + return this; + + ptr_op_t intermediate(copy(lhs, rhs)); + + // Reduce constants immediately if possible + if ((! lhs || lhs->is_value()) && (! rhs || rhs->is_value())) + return wrap_value(intermediate->calc(scope, NULL, depth)); + + return intermediate; +} + +value_t expr_t::op_t::calc(scope_t& scope, ptr_op_t * locus, const int depth) +{ +#if defined(DEBUG_ON) + bool skip_debug = false; +#endif + try { + + value_t result; + + switch (kind) { + case VALUE: + result = as_value(); + break; + + case IDENT: { + if (! left()) + throw_(calc_error, _("Unknown identifier '%1'") << as_ident()); + + // Evaluating an identifier is the same as calling its definition + // directly, so we create an empty call_scope_t to reflect the scope for + // this implicit call. + call_scope_t call_args(scope); + result = left()->compile(call_args, depth + 1) + ->calc(call_args, locus, depth + 1); + break; + } + + case FUNCTION: { + // Evaluating a FUNCTION is the same as calling it directly; this happens + // when certain functions-that-look-like-variables (such as "amount") are + // resolved. + call_scope_t call_args(scope); + result = as_function()(call_args); +#if defined(DEBUG_ON) + skip_debug = true; +#endif + break; + } + + case O_DEFINE: { + call_scope_t& call_args(downcast<call_scope_t>(scope)); + std::size_t args_count = call_args.size(); + std::size_t args_index = 0; + + assert(left()->kind == O_CALL); + + for (ptr_op_t sym = left()->right(); + sym; + sym = sym->has_right() ? sym->right() : NULL) { + ptr_op_t varname = sym; + if (sym->kind == O_CONS) + varname = sym->left(); + + if (! varname->is_ident()) + throw_(calc_error, _("Invalid function definition")); + else if (args_index == args_count) + scope.define(symbol_t::FUNCTION, varname->as_ident(), + wrap_value(false)); + else + scope.define(symbol_t::FUNCTION, varname->as_ident(), + wrap_value(call_args[args_index++])); + } + + if (args_index < args_count) + throw_(calc_error, + _("Too many arguments in function call (saw %1)") << args_count); + + result = right()->calc(scope, locus, depth + 1); + break; + } + + case O_LOOKUP: + if (value_t obj = left()->calc(scope, locus, depth + 1)) { + if (obj.is_scope()) { + if (obj.as_scope() == NULL) { + throw_(calc_error, _("Left operand of . operator is NULL")); + } else { + scope_t& objscope(*obj.as_scope()); + if (ptr_op_t member = + objscope.lookup(symbol_t::FUNCTION, right()->as_ident())) { + result = member->calc(objscope, NULL, depth + 1); + break; + } + } + } + } + if (right()->kind != IDENT) + throw_(calc_error, + _("Right operand of . operator must be an identifier")); + else + throw_(calc_error, + _("Failed to lookup member '%1'") << right()->as_ident()); + break; + + case O_CALL: { + call_scope_t call_args(scope); + + if (has_right()) + call_args.set_args(right()->calc(scope, locus, depth + 1)); + + ptr_op_t func = left(); + const string& name(func->as_ident()); + + func = func->left(); + if (! func) + throw_(calc_error, _("Calling unknown function '%1'") << name); + + if (func->is_function()) + result = func->as_function()(call_args); + else + result = func->calc(call_args, locus, depth + 1); + break; + } + + case O_MATCH: + result = (right()->calc(scope, locus, depth + 1).as_mask() + .match(left()->calc(scope, locus, depth + 1).to_string())); + break; + + case O_EQ: + result = (left()->calc(scope, locus, depth + 1) == + right()->calc(scope, locus, depth + 1)); + break; + case O_LT: + result = (left()->calc(scope, locus, depth + 1) < + right()->calc(scope, locus, depth + 1)); + break; + case O_LTE: + result = (left()->calc(scope, locus, depth + 1) <= + right()->calc(scope, locus, depth + 1)); + break; + case O_GT: + result = (left()->calc(scope, locus, depth + 1) > + right()->calc(scope, locus, depth + 1)); + break; + case O_GTE: + result = (left()->calc(scope, locus, depth + 1) >= + right()->calc(scope, locus, depth + 1)); + break; + + case O_ADD: + result = (left()->calc(scope, locus, depth + 1) + + right()->calc(scope, locus, depth + 1)); + break; + case O_SUB: + result = (left()->calc(scope, locus, depth + 1) - + right()->calc(scope, locus, depth + 1)); + break; + case O_MUL: + result = (left()->calc(scope, locus, depth + 1) * + right()->calc(scope, locus, depth + 1)); + break; + case O_DIV: + result = (left()->calc(scope, locus, depth + 1) / + right()->calc(scope, locus, depth + 1)); + break; + + case O_NEG: + result = left()->calc(scope, locus, depth + 1).negated(); + break; + + case O_NOT: + result = ! left()->calc(scope, locus, depth + 1); + break; + + case O_AND: + if (left()->calc(scope, locus, depth + 1)) + result = right()->calc(scope, locus, depth + 1); + else + result = false; + break; + + case O_OR: + if (value_t temp = left()->calc(scope, locus, depth + 1)) + result = temp; + else + result = right()->calc(scope, locus, depth + 1); + break; + + case O_QUERY: + assert(right()); + assert(right()->kind == O_COLON); + + if (value_t temp = left()->calc(scope, locus, depth + 1)) + result = right()->left()->calc(scope, locus, depth + 1); + else + result = right()->right()->calc(scope, locus, depth + 1); + break; + + case O_COLON: + assert(! "We should never calculate an O_COLON operator"); + break; + + case O_CONS: + result = left()->calc(scope, locus, depth + 1); + DEBUG("op.cons", "car = " << result); + + if (has_right()) { + value_t temp; + temp.push_back(result); + + ptr_op_t next = right(); + while (next) { + ptr_op_t value_op; + if (next->kind == O_CONS) { + value_op = next->left(); + next = next->right(); + } else { + value_op = next; + next = NULL; + } + temp.push_back(value_op->calc(scope, locus, depth + 1)); + DEBUG("op.cons", "temp now = " << temp); + } + result = temp; + } + break; + + case O_SEQ: { + symbol_scope_t seq_scope(scope); + + // An O_SEQ is very similar to an O_CONS except that only the last result + // value in the series is kept. O_CONS builds up a list. + // + // Another feature of O_SEQ is that it pushes a new symbol scope onto the + // stack. + result = left()->calc(seq_scope, locus, depth + 1); + + if (has_right()) { + ptr_op_t next = right(); + while (next) { + ptr_op_t value_op; + if (next->kind == O_SEQ) { + value_op = next->left(); + next = next->right(); + } else { + value_op = next; + next = NULL; + } + result = value_op->calc(seq_scope, locus, depth + 1); + } + } + break; + } + + case LAST: + default: + assert(false); + break; + } + +#if defined(DEBUG_ON) + if (! skip_debug && SHOW_DEBUG("expr.calc")) { + for (int i = 0; i < depth; i++) + ledger::_log_buffer << '.'; + ledger::_log_buffer << op_context(this) << " => "; + result.dump(ledger::_log_buffer, true); + DEBUG("expr.calc", ""); + } +#endif + + return result; + + } + catch (const std::exception& err) { + if (locus && ! *locus) + *locus = this; + throw; + } +} + +namespace { + bool print_cons(std::ostream& out, const expr_t::const_ptr_op_t op, + const expr_t::op_t::context_t& context) + { + bool found = false; + + assert(op->left()); + if (op->left()->print(out, context)) + found = true; + + if (op->has_right()) { + out << ", "; + if (op->right()->kind == expr_t::op_t::O_CONS) + found = print_cons(out, op->right(), context); + else if (op->right()->print(out, context)) + found = true; + } + return found; + } + + bool print_seq(std::ostream& out, const expr_t::const_ptr_op_t op, + const expr_t::op_t::context_t& context) + { + bool found = false; + + assert(op->left()); + if (op->left()->print(out, context)) + found = true; + + if (op->has_right()) { + out << "; "; + + if (op->right()->kind == expr_t::op_t::O_CONS) + found = print_cons(out, op->right(), context); + else if (op->right()->print(out, context)) + found = true; + } + + return found; + } +} + +bool expr_t::op_t::print(std::ostream& out, const context_t& context) const +{ + bool found = false; + + if (context.start_pos && this == context.op_to_find) { + *context.start_pos = out.tellp(); + *context.start_pos -= 1; + found = true; + } + + string symbol; + + switch (kind) { + case VALUE: + as_value().dump(out, context.relaxed); + break; + + case IDENT: + out << as_ident(); + break; + + case FUNCTION: + out << "<FUNCTION>"; + break; + + case O_NOT: + out << "!("; + if (left() && left()->print(out, context)) + found = true; + out << ")"; + break; + case O_NEG: + out << "-("; + if (left() && left()->print(out, context)) + found = true; + out << ")"; + break; + + case O_ADD: + out << "("; + if (left() && left()->print(out, context)) + found = true; + out << " + "; + if (has_right() && right()->print(out, context)) + found = true; + out << ")"; + break; + case O_SUB: + out << "("; + if (left() && left()->print(out, context)) + found = true; + out << " - "; + if (has_right() && right()->print(out, context)) + found = true; + out << ")"; + break; + case O_MUL: + out << "("; + if (left() && left()->print(out, context)) + found = true; + out << " * "; + if (has_right() && right()->print(out, context)) + found = true; + out << ")"; + break; + case O_DIV: + out << "("; + if (left() && left()->print(out, context)) + found = true; + out << " / "; + if (has_right() && right()->print(out, context)) + found = true; + out << ")"; + break; + + case O_EQ: + out << "("; + if (left() && left()->print(out, context)) + found = true; + out << " == "; + if (has_right() && right()->print(out, context)) + found = true; + out << ")"; + break; + case O_LT: + out << "("; + if (left() && left()->print(out, context)) + found = true; + out << " < "; + if (has_right() && right()->print(out, context)) + found = true; + out << ")"; + break; + case O_LTE: + out << "("; + if (left() && left()->print(out, context)) + found = true; + out << " <= "; + if (has_right() && right()->print(out, context)) + found = true; + out << ")"; + break; + case O_GT: + out << "("; + if (left() && left()->print(out, context)) + found = true; + out << " > "; + if (has_right() && right()->print(out, context)) + found = true; + out << ")"; + break; + case O_GTE: + out << "("; + if (left() && left()->print(out, context)) + found = true; + out << " >= "; + if (has_right() && right()->print(out, context)) + found = true; + out << ")"; + break; + + case O_AND: + out << "("; + if (left() && left()->print(out, context)) + found = true; + out << " & "; + if (has_right() && right()->print(out, context)) + found = true; + out << ")"; + break; + case O_OR: + out << "("; + if (left() && left()->print(out, context)) + found = true; + out << " | "; + if (has_right() && right()->print(out, context)) + found = true; + out << ")"; + break; + + case O_QUERY: + if (left() && left()->print(out, context)) + found = true; + out << " ? "; + if (has_right() && right()->print(out, context)) + found = true; + break; + + case O_COLON: + if (left() && left()->print(out, context)) + found = true; + out << " : "; + if (has_right() && right()->print(out, context)) + found = true; + break; + + case O_CONS: + found = print_cons(out, this, context); + break; + + case O_SEQ: + out << "("; + found = print_seq(out, this, context); + out << ")"; + break; + + case O_DEFINE: + if (left() && left()->print(out, context)) + found = true; + out << " := "; + if (has_right() && right()->print(out, context)) + found = true; + break; + + case O_LOOKUP: + if (left() && left()->print(out, context)) + found = true; + out << "."; + if (has_right() && right()->print(out, context)) + found = true; + break; + + case O_CALL: + if (left() && left()->print(out, context)) + found = true; + if (has_right()) { + if (right()->kind == O_SEQ) { + if (right()->print(out, context)) + found = true; + } else { + out << "("; + if (has_right() && right()->print(out, context)) + found = true; + out << ")"; + } + } else { + out << "()"; + } + break; + + case O_MATCH: + if (left() && left()->print(out, context)) + found = true; + out << " =~ "; + if (has_right() && right()->print(out, context)) + found = true; + break; + + case LAST: + default: + assert(false); + break; + } + + if (! symbol.empty()) { + if (commodity_pool_t::current_pool->find(symbol)) + out << '@'; + out << symbol; + } + + if (context.end_pos && this == context.op_to_find) { + *context.end_pos = out.tellp(); + *context.end_pos -= 1; + } + + return found; +} + +void expr_t::op_t::dump(std::ostream& out, const int depth) const +{ + out.setf(std::ios::left); + out.width((sizeof(void *) * 2) + 2); + out << this; + + for (int i = 0; i < depth; i++) + out << " "; + + switch (kind) { + case VALUE: + out << "VALUE: "; + as_value().dump(out); + break; + + case IDENT: + out << "IDENT: " << as_ident(); + break; + + case FUNCTION: + out << "FUNCTION"; + break; + + case O_DEFINE: out << "O_DEFINE"; break; + case O_LOOKUP: out << "O_LOOKUP"; break; + case O_CALL: out << "O_CALL"; break; + case O_MATCH: out << "O_MATCH"; break; + + case O_NOT: out << "O_NOT"; break; + case O_NEG: out << "O_NEG"; break; + + case O_ADD: out << "O_ADD"; break; + case O_SUB: out << "O_SUB"; break; + case O_MUL: out << "O_MUL"; break; + case O_DIV: out << "O_DIV"; break; + + case O_EQ: out << "O_EQ"; break; + case O_LT: out << "O_LT"; break; + case O_LTE: out << "O_LTE"; break; + case O_GT: out << "O_GT"; break; + case O_GTE: out << "O_GTE"; break; + + case O_AND: out << "O_AND"; break; + case O_OR: out << "O_OR"; break; + + case O_QUERY: out << "O_QUERY"; break; + case O_COLON: out << "O_COLON"; break; + + case O_CONS: out << "O_CONS"; break; + case O_SEQ: out << "O_SEQ"; break; + + case LAST: + default: + assert(false); + break; + } + + out << " (" << refc << ')' << std::endl; + + // An identifier is a special non-terminal, in that its left() can + // hold the compiled definition of the identifier. + if (kind > TERMINALS || is_ident()) { + if (left()) { + left()->dump(out, depth + 1); + if (kind > UNARY_OPERATORS && has_right()) + right()->dump(out, depth + 1); + } + else if (kind > UNARY_OPERATORS) { + assert(! has_right()); + } + } +} + +string op_context(const expr_t::ptr_op_t op, + const expr_t::ptr_op_t locus) +{ + ostream_pos_type start_pos, end_pos; + expr_t::op_t::context_t context(op, locus, &start_pos, &end_pos); + std::ostringstream buf; + buf << " "; + if (op->print(buf, context)) { + buf << "\n"; + for (int i = 0; i <= end_pos; i++) { + if (i > start_pos) + buf << "^"; + else + buf << " "; + } + } + return buf.str(); +} + +} // namespace ledger diff --git a/src/op.h b/src/op.h new file mode 100644 index 00000000..347eac1d --- /dev/null +++ b/src/op.h @@ -0,0 +1,343 @@ +/* + * 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. + */ + +/** + * @addtogroup expr + */ + +/** + * @file op.h + * @author John Wiegley + * + * @ingroup expr + */ +#ifndef _OP_H +#define _OP_H + +#include "expr.h" + +namespace ledger { + +class expr_t::op_t : public noncopyable +{ + friend class expr_t; + friend class expr_t::parser_t; + +public: + typedef expr_t::ptr_op_t ptr_op_t; + +private: + mutable short refc; + ptr_op_t left_; + + variant<ptr_op_t, // used by all binary operators + value_t, // used by constant VALUE + string, // used by constant IDENT + expr_t::func_t // used by terminal FUNCTION + > data; + +public: + enum kind_t { + // Constants + VALUE, + IDENT, + + CONSTANTS, + + FUNCTION, + + TERMINALS, + + // Binary operators + O_NOT, + O_NEG, + + UNARY_OPERATORS, + + O_EQ, + O_LT, + O_LTE, + O_GT, + O_GTE, + + O_AND, + O_OR, + + O_ADD, + O_SUB, + O_MUL, + O_DIV, + + O_QUERY, + O_COLON, + + O_CONS, + O_SEQ, + + O_DEFINE, + O_LOOKUP, + O_CALL, + O_MATCH, + + BINARY_OPERATORS, + + OPERATORS, + + UNKNOWN, + + LAST + }; + + kind_t kind; + + explicit op_t() : refc(0), kind(UNKNOWN) { + TRACE_CTOR(op_t, ""); + } + explicit op_t(const kind_t _kind) : refc(0), kind(_kind) { + TRACE_CTOR(op_t, "const kind_t"); + } + ~op_t() { + TRACE_DTOR(op_t); + assert(refc == 0); + } + + bool is_value() const { + if (kind == VALUE) { + assert(data.type() == typeid(value_t)); + return true; + } + return false; + } + value_t& as_value_lval() { + assert(is_value()); + value_t& val(boost::get<value_t>(data)); + VERIFY(val.valid()); + return val; + } + const value_t& as_value() const { + return const_cast<op_t *>(this)->as_value_lval(); + } + void set_value(const value_t& val) { + VERIFY(val.valid()); + data = val; + } + + bool is_ident() const { + if (kind == IDENT) { + assert(data.type() == typeid(string)); + return true; + } + return false; + } + string& as_ident_lval() { + assert(is_ident()); + return boost::get<string>(data); + } + const string& as_ident() const { + return const_cast<op_t *>(this)->as_ident_lval(); + } + void set_ident(const string& val) { + data = val; + } + + bool is_function() const { + return kind == FUNCTION; + } + expr_t::func_t& as_function_lval() { + assert(kind == FUNCTION); + return boost::get<expr_t::func_t>(data); + } + const expr_t::func_t& as_function() const { + return const_cast<op_t *>(this)->as_function_lval(); + } + void set_function(const expr_t::func_t& val) { + data = val; + } + + ptr_op_t& left() { + assert(kind > TERMINALS || kind == IDENT); + return left_; + } + const ptr_op_t& left() const { + assert(kind > TERMINALS || kind == IDENT); + return left_; + } + void set_left(const ptr_op_t& expr) { + assert(kind > TERMINALS || kind == IDENT); + left_ = expr; + } + + ptr_op_t& as_op_lval() { + assert(kind > TERMINALS || kind == IDENT); + return boost::get<ptr_op_t>(data); + } + const ptr_op_t& as_op() const { + return const_cast<op_t *>(this)->as_op_lval(); + } + + ptr_op_t& right() { + assert(kind > TERMINALS); + return as_op_lval(); + } + const ptr_op_t& right() const { + assert(kind > TERMINALS); + return as_op(); + } + void set_right(const ptr_op_t& expr) { + assert(kind > TERMINALS); + data = expr; + } + bool has_right() const { + if (kind < TERMINALS) + return false; + return as_op(); + } + +private: + void acquire() const { + DEBUG("op.memory", + "Acquiring " << this << ", refc now " << refc + 1); + assert(refc >= 0); + refc++; + } + void release() const { + DEBUG("op.memory", + "Releasing " << this << ", refc now " << refc - 1); + assert(refc > 0); + if (--refc == 0) + checked_delete(this); + } + + friend inline void intrusive_ptr_add_ref(const op_t * op) { + op->acquire(); + } + friend inline void intrusive_ptr_release(const op_t * op) { + op->release(); + } + + ptr_op_t copy(ptr_op_t _left = NULL, ptr_op_t _right = NULL) const { + ptr_op_t node(new_node(kind, _left, _right)); + if (kind < TERMINALS) + node->data = data; + return node; + } + +public: + static ptr_op_t new_node(kind_t _kind, ptr_op_t _left = NULL, + ptr_op_t _right = NULL); + + ptr_op_t compile(scope_t& scope, const int depth = 0); + value_t calc(scope_t& scope, ptr_op_t * locus = NULL, + const int depth = 0); + + struct context_t + { + ptr_op_t expr_op; + ptr_op_t op_to_find; + ostream_pos_type * start_pos; + ostream_pos_type * end_pos; + bool relaxed; + + context_t(const ptr_op_t& _expr_op = NULL, + const ptr_op_t& _op_to_find = NULL, + ostream_pos_type * const _start_pos = NULL, + ostream_pos_type * const _end_pos = NULL, + const bool _relaxed = true) + : expr_op(_expr_op), op_to_find(_op_to_find), + start_pos(_start_pos), end_pos(_end_pos), + relaxed(_relaxed) {} + }; + + bool print(std::ostream& out, const context_t& context = context_t()) const; + void dump(std::ostream& out, const int depth) const; + + static ptr_op_t wrap_value(const value_t& val); + static ptr_op_t wrap_functor(const expr_t::func_t& fobj); + +#if defined(HAVE_BOOST_SERIALIZATION) +private: + /** Serialization. */ + + friend class boost::serialization::access; + + template<class Archive> + void serialize(Archive& ar, const unsigned int /* version */) { + ar & refc; + ar & kind; + if (Archive::is_loading::value || ! left_ || left_->kind != FUNCTION) { + ar & left_; + } else { + ptr_op_t temp_op; + ar & temp_op; + } + if (Archive::is_loading::value || kind == VALUE || kind == IDENT || + (kind > UNARY_OPERATORS && + (! has_right() || ! right()->is_function()))) { + ar & data; + } else { + variant<ptr_op_t, value_t, string, expr_t::func_t> temp_data; + ar & temp_data; + } + } +#endif // HAVE_BOOST_SERIALIZATION +}; + +inline expr_t::ptr_op_t +expr_t::op_t::new_node(kind_t _kind, ptr_op_t _left, ptr_op_t _right) +{ + ptr_op_t node(new op_t(_kind)); + if (_left) node->set_left(_left); + if (_right) node->set_right(_right); + return node; +} + +inline expr_t::ptr_op_t expr_t::op_t::wrap_value(const value_t& val) { + ptr_op_t temp(new op_t(op_t::VALUE)); + temp->set_value(val); + return temp; +} + +inline expr_t::ptr_op_t +expr_t::op_t::wrap_functor(const expr_t::func_t& fobj) { + ptr_op_t temp(new op_t(op_t::FUNCTION)); + temp->set_function(fobj); + return temp; +} + +#define MAKE_FUNCTOR(x) expr_t::op_t::wrap_functor(bind(&x, this, _1)) +#define WRAP_FUNCTOR(x) expr_t::op_t::wrap_functor(x) + +string op_context(const expr_t::ptr_op_t op, + const expr_t::ptr_op_t locus = NULL); + +} // namespace ledger + +#endif // _OP_H diff --git a/src/option.cc b/src/option.cc new file mode 100644 index 00000000..d2ec8808 --- /dev/null +++ b/src/option.cc @@ -0,0 +1,249 @@ +/* + * 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 "option.h" + +namespace ledger { + +namespace { + typedef std::pair<expr_t::ptr_op_t, bool> op_bool_tuple; + + op_bool_tuple find_option(scope_t& scope, const string& name) + { + char buf[128]; + char * p = buf; + foreach (char ch, name) { + if (ch == '-') + *p++ = '_'; + else + *p++ = ch; + } + *p++ = '_'; + *p = '\0'; + + if (expr_t::ptr_op_t op = scope.lookup(symbol_t::OPTION, buf)) + return op_bool_tuple(op, true); + + *--p = '\0'; + + return op_bool_tuple(scope.lookup(symbol_t::OPTION, buf), false); + } + + op_bool_tuple find_option(scope_t& scope, const char letter) + { + char buf[4]; + buf[0] = letter; + buf[1] = '_'; + buf[2] = '\0'; + + if (expr_t::ptr_op_t op = scope.lookup(symbol_t::OPTION, buf)) + return op_bool_tuple(op, true); + + buf[1] = '\0'; + + return op_bool_tuple(scope.lookup(symbol_t::OPTION, buf), false); + } + + void process_option(const string& whence, const expr_t::func_t& opt, + scope_t& scope, const char * arg, const string& name) + { + try { + call_scope_t args(scope); + + args.push_back(string_value(whence)); + if (arg) + args.push_back(string_value(arg)); + + opt(args); + } + catch (const std::exception& err) { + if (name[0] == '-') + add_error_context(_("While parsing option '%1'") << name); + + else + add_error_context(_("While parsing environent variable '%1'") << name); + throw; + } + } +} + +bool process_option(const string& whence, const string& name, scope_t& scope, + const char * arg, const string& varname) +{ + op_bool_tuple opt(find_option(scope, name)); + if (opt.first) { + process_option(whence, opt.first->as_function(), scope, arg, varname); + return true; + } + return false; +} + +void process_environment(const char ** envp, const string& tag, + scope_t& scope) +{ + const char * tag_p = tag.c_str(); + string::size_type tag_len = tag.length(); + + assert(tag_p); + assert(tag_len > 0); + + for (const char ** p = envp; *p; p++) { + if (std::strlen(*p) >= tag_len && std::strncmp(*p, tag_p, tag_len) == 0) { + char buf[8192]; + char * r = buf; + const char * q; + for (q = *p + tag_len; + *q && *q != '=' && r - buf < 8191; + q++) + if (*q == '_') + *r++ = '-'; + else + *r++ = static_cast<char>(std::tolower(*q)); + *r = '\0'; + + if (*q == '=') { + try { + string value = string(*p, q - *p); + if (! value.empty()) + process_option(string("$") + buf, string(buf), scope, q + 1, value); + } + catch (const std::exception& err) { + add_error_context(_("While parsing environment variable option '%1':") + << *p); + throw; + } + } + } + } +} + +namespace { + struct op_bool_char_tuple { + expr_t::ptr_op_t op; + bool truth; + char ch; + + op_bool_char_tuple(expr_t::ptr_op_t _op, bool _truth, char _ch) + : op(_op), truth(_truth), ch(_ch) {} + }; +} + +strings_list process_arguments(strings_list args, scope_t& scope) +{ + bool anywhere = true; + + strings_list remaining; + + for (strings_list::iterator i = args.begin(); + i != args.end(); + i++) { + DEBUG("option.args", "Examining argument '" << *i << "'"); + + if (! anywhere || (*i)[0] != '-') { + DEBUG("option.args", " adding to list of real args"); + remaining.push_back(*i); + continue; + } + + // --long-option or -s + if ((*i)[1] == '-') { + if ((*i)[2] == '\0') { + DEBUG("option.args", " it's a --, ending options processing"); + anywhere = false; + continue; + } + + DEBUG("option.args", " it's an option string"); + + string opt_name; + const char * name = (*i).c_str() + 2; + const char * value = NULL; + + if (const char * p = std::strchr(name, '=')) { + opt_name = string(name, p - name); + value = ++p; + DEBUG("option.args", " read option value from option: " << value); + } else { + opt_name = name; + } + + op_bool_tuple opt(find_option(scope, opt_name)); + if (! opt.first) + throw_(option_error, _("Illegal option --%1") << name); + + if (opt.second && ! value && ++i != args.end() && value == NULL) { + value = (*i).c_str(); + DEBUG("option.args", " read option value from arg: " << value); + if (value == NULL) + throw_(option_error, _("Missing option argument for --%1") << name); + } + process_option(string("--") + name, + opt.first->as_function(), scope, value, + string("--") + name); + } + else if ((*i)[1] == '\0') { + throw_(option_error, _("illegal option -%1") << (*i)[0]); + } + else { + DEBUG("option.args", " single-char option"); + + std::list<op_bool_char_tuple> option_queue; + + int x = 1; + for (char c = (*i)[x]; c != '\0'; x++, c = (*i)[x]) { + op_bool_tuple opt(find_option(scope, c)); + if (! opt.first) + throw_(option_error, _("Illegal option -%1") << c); + + option_queue.push_back(op_bool_char_tuple(opt.first, opt.second, c)); + } + + foreach (op_bool_char_tuple& o, option_queue) { + const char * value = NULL; + if (o.truth && ++i != args.end()) { + value = (*i).c_str(); + DEBUG("option.args", " read option value from arg: " << value); + if (value == NULL) + throw_(option_error, + _("Missing option argument for -%1") << o.ch); + } + process_option(string("-") + o.ch, o.op->as_function(), scope, value, + string("-") + o.ch); + } + } + } + + return remaining; +} + +} // namespace ledger diff --git a/src/option.h b/src/option.h new file mode 100644 index 00000000..b81c2ce7 --- /dev/null +++ b/src/option.h @@ -0,0 +1,294 @@ +/* + * 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. + */ + +/** + * @addtogroup expr + */ + +/** + * @file option.h + * @author John Wiegley + * + * @ingroup expr + */ +#ifndef _OPTION_H +#define _OPTION_H + +#include "scope.h" + +namespace ledger { + +class call_scope_t; + +template <typename T> +class option_t +{ +protected: + const char * name; + string::size_type name_len; + const char ch; + bool handled; + optional<string> source; + + option_t& operator=(const option_t&); + +public: + T * parent; + value_t value; + bool wants_arg; + + option_t(const char * _name, const char _ch = '\0') + : name(_name), name_len(std::strlen(name)), ch(_ch), + handled(false), parent(NULL), value(), + wants_arg(name[name_len - 1] == '_') { + TRACE_CTOR(option_t, "const char *, const char"); + DEBUG("option.names", "Option: " << name); + } + option_t(const option_t& other) + : name(other.name), + name_len(other.name_len), + ch(other.ch), + handled(other.handled), + parent(NULL), + value(other.value), + wants_arg(other.wants_arg) + { + TRACE_CTOR(option_t, "copy"); + } + + virtual ~option_t() { + TRACE_DTOR(option_t); + } + + void report(std::ostream& out) const { + if (handled && source) { + if (wants_arg) { + out << desc() << " => "; + value.dump(out); + } else { + out << desc(); + } + out << " <" << *source << ">" << std::endl; + } + } + + string desc() const { + std::ostringstream out; + out << "--"; + for (const char * p = name; *p; p++) { + if (*p == '_') { + if (*(p + 1)) + out << '-'; + } else { + out << *p; + } + } + if (ch) + out << " (-" << ch << ")"; + return out.str(); + } + + operator bool() const { + return handled; + } + + string& str() { + assert(handled); + if (! value) + throw_(std::runtime_error, _("No argument provided for %1") << desc()); + return value.as_string_lval(); + } + + string str() const { + assert(handled); + if (! value) + throw_(std::runtime_error, _("No argument provided for %1") << desc()); + return value.as_string(); + } + + void on_only(const optional<string>& whence) { + handled = true; + source = whence; + } + void on(const optional<string>& whence, const string& str) { + on_with(whence, string_value(str)); + } + virtual void on_with(const optional<string>& whence, + const value_t& val) { + handled = true; + value = val; + source = whence; + } + + void off() { + handled = false; + value = value_t(); + source = none; + } + + virtual void handler_thunk(call_scope_t&) {} + + virtual void handler(call_scope_t& args) { + if (wants_arg) { + if (args.size() < 2) + throw_(std::runtime_error, _("No argument provided for %1") << desc()); + else if (args.size() > 2) + throw_(std::runtime_error, _("To many arguments provided for %1") << desc()); + else if (! args[0].is_string()) + throw_(std::runtime_error, _("Context argument for %1 not a string") << desc()); + on_with(args[0].as_string(), args[1]); + } + else if (args.size() < 1) { + throw_(std::runtime_error, _("No argument provided for %1") << desc()); + } + else if (! args[0].is_string()) { + throw_(std::runtime_error, _("Context argument for %1 not a string") << desc()); + } + else { + on_only(args[0].as_string()); + } + + handler_thunk(args); + } + + virtual value_t handler_wrapper(call_scope_t& args) { + handler(args); + return true; + } + + virtual value_t operator()(call_scope_t& args) { + if (! args.empty()) { + args.push_front(string_value("?expr")); + return handler_wrapper(args); + } + else if (wants_arg) { + if (handled) + return value; + else + return NULL_VALUE; + } + else { + return handled; + } + } +}; + +#define BEGIN(type, name) \ + struct name ## _option_t : public option_t<type> + +#define CTOR(type, name) \ + name ## _option_t() : option_t<type>(#name) +#define DECL1(type, name, vartype, var, value) \ + vartype var ; \ + name ## _option_t() : option_t<type>(#name), var(value) + +#define DO() virtual void handler_thunk(call_scope_t&) +#define DO_(var) virtual void handler_thunk(call_scope_t& var) + +#define END(name) name ## _handler + +#define COPY_OPT(name, other) name ## _handler(other.name ## _handler) + +#define MAKE_OPT_HANDLER(type, x) \ + expr_t::op_t::wrap_functor(bind(&option_t<type>::handler_wrapper, x, _1)) + +#define MAKE_OPT_FUNCTOR(type, x) \ + expr_t::op_t::wrap_functor(bind(&option_t<type>::operator(), x, _1)) + +inline bool is_eq(const char * p, const char * n) { + // Test whether p matches n, substituting - in p for _ in n. + for (; *p && *n; p++, n++) { + if (! (*p == '-' && *n == '_' ) && *p != *n) + return false; + } + // Ignore any trailing underscore + return *p == *n || (! *p && *n == '_' && ! *(n + 1)); +} + +#define OPT(name) \ + if (is_eq(p, #name)) \ + return ((name ## _handler).parent = this, &(name ## _handler)) + +#define OPT_ALT(name, alt) \ + if (is_eq(p, #name) || is_eq(p, #alt)) \ + return ((name ## _handler).parent = this, &(name ## _handler)) + +#define OPT_(name) \ + if (! *(p + 1) || \ + ((name ## _handler).wants_arg && \ + *(p + 1) == '_' && ! *(p + 2)) || \ + is_eq(p, #name)) \ + return ((name ## _handler).parent = this, &(name ## _handler)) + +#define OPT_CH(name) \ + if (! *(p + 1) || \ + ((name ## _handler).wants_arg && \ + *(p + 1) == '_' && ! *(p + 2))) \ + return ((name ## _handler).parent = this, &(name ## _handler)) + +#define HANDLER(name) name ## _handler +#define HANDLED(name) HANDLER(name) + +#define OPTION(type, name) \ + BEGIN(type, name) \ + { \ + CTOR(type, name) {} \ + } \ + END(name) + +#define OPTION_(type, name, body) \ + BEGIN(type, name) \ + { \ + CTOR(type, name) {} \ + body \ + } \ + END(name) + +#define OPTION__(type, name, body) \ + BEGIN(type, name) \ + { \ + body \ + } \ + END(name) + +bool process_option(const string& whence, const string& name, scope_t& scope, + const char * arg, const string& varname); + +void process_environment(const char ** envp, const string& tag, + scope_t& scope); + +strings_list process_arguments(strings_list args, scope_t& scope); + +DECLARE_EXCEPTION(option_error, std::runtime_error); + +} // namespace ledger + +#endif // _OPTION_H diff --git a/src/output.cc b/src/output.cc new file mode 100644 index 00000000..71ec6d88 --- /dev/null +++ b/src/output.cc @@ -0,0 +1,254 @@ +/* + * 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 "output.h" +#include "xact.h" +#include "post.h" +#include "account.h" +#include "session.h" +#include "report.h" + +namespace ledger { + +format_posts::format_posts(report_t& _report, + const string& format, + bool _print_raw, + const optional<string>& _prepend_format) + : report(_report), last_xact(NULL), last_post(NULL), + print_raw(_print_raw) +{ + TRACE_CTOR(format_posts, "report&, const string&, bool"); + + const char * f = format.c_str(); + + if (const char * p = std::strstr(f, "%/")) { + first_line_format.parse_format(string(f, 0, p - f)); + const char * n = p + 2; + if (const char * p = std::strstr(n, "%/")) { + next_lines_format.parse_format(string(n, 0, p - n), + first_line_format); + between_format.parse_format(string(p + 2), + first_line_format); + } else { + next_lines_format.parse_format(string(n), first_line_format); + } + } else { + first_line_format.parse_format(format); + next_lines_format.parse_format(format); + } + + if (_prepend_format) + prepend_format.parse_format(*_prepend_format); +} + +void format_posts::flush() +{ + report.output_stream.flush(); +} + +void format_posts::operator()(post_t& post) +{ + std::ostream& out(report.output_stream); + + if (print_raw) { + if (! post.has_xdata() || + ! post.xdata().has_flags(POST_EXT_DISPLAYED)) { + if (last_xact != post.xact) { + if (last_xact) { + bind_scope_t xact_scope(report, *last_xact); + out << between_format(xact_scope); + } + print_item(out, *post.xact); + out << '\n'; + last_xact = post.xact; + } + post.xdata().add_flags(POST_EXT_DISPLAYED); + last_post = &post; + } + } + else if (! post.has_xdata() || + ! post.xdata().has_flags(POST_EXT_DISPLAYED)) { + bind_scope_t bound_scope(report, post); + + if (prepend_format) + out << prepend_format(bound_scope); + + if (last_xact != post.xact) { + if (last_xact) { + bind_scope_t xact_scope(report, *last_xact); + out << between_format(xact_scope); + } + out << first_line_format(bound_scope); + last_xact = post.xact; + } + else if (last_post && last_post->date() != post.date()) { + out << first_line_format(bound_scope); + } + else { + out << next_lines_format(bound_scope); + } + + post.xdata().add_flags(POST_EXT_DISPLAYED); + last_post = &post; + } +} + +format_accounts::format_accounts(report_t& _report, + const string& format, + const optional<string>& _prepend_format) + : report(_report), disp_pred() +{ + TRACE_CTOR(format_accounts, "report&, const string&"); + + const char * f = format.c_str(); + + if (const char * p = std::strstr(f, "%/")) { + account_line_format.parse_format(string(f, 0, p - f)); + const char * n = p + 2; + if (const char * p = std::strstr(n, "%/")) { + total_line_format.parse_format(string(n, 0, p - n), account_line_format); + separator_format.parse_format(string(p + 2), account_line_format); + } else { + total_line_format.parse_format(n, account_line_format); + } + } else { + account_line_format.parse_format(format); + total_line_format.parse_format(format, account_line_format); + } + + if (_prepend_format) + prepend_format.parse_format(*_prepend_format); +} + +std::size_t format_accounts::post_account(account_t& account, const bool flat) +{ + if (account.xdata().has_flags(ACCOUNT_EXT_TO_DISPLAY) && + ! account.xdata().has_flags(ACCOUNT_EXT_DISPLAYED)) { + if (! flat && account.parent && + account.parent->xdata().has_flags(ACCOUNT_EXT_TO_DISPLAY) && + ! account.parent->xdata().has_flags(ACCOUNT_EXT_DISPLAYED)) + post_account(*account.parent, flat); + + account.xdata().add_flags(ACCOUNT_EXT_DISPLAYED); + + bind_scope_t bound_scope(report, account); + + if (prepend_format) + static_cast<std::ostream&>(report.output_stream) + << prepend_format(bound_scope); + + static_cast<std::ostream&>(report.output_stream) + << account_line_format(bound_scope); + + return 1; + } + return 0; +} + +std::pair<std::size_t, std::size_t> +format_accounts::mark_accounts(account_t& account, const bool flat) +{ + std::size_t visited = 0; + std::size_t to_display = 0; + + foreach (accounts_map::value_type& pair, account.accounts) { + std::pair<std::size_t, std::size_t> i = mark_accounts(*pair.second, flat); + visited += i.first; + to_display += i.second; + } + +#if defined(DEBUG_ON) + DEBUG("account.display", "Considering account: " << account.fullname()); + if (account.has_xflags(ACCOUNT_EXT_VISITED)) + DEBUG("account.display", " it was visited itself"); + DEBUG("account.display", " it has " << visited << " visited children"); + DEBUG("account.display", + " it has " << to_display << " children to display"); +#endif + + if (account.parent && + (account.has_xflags(ACCOUNT_EXT_VISITED) || (! flat && visited > 0))) { + bind_scope_t bound_scope(report, account); + if ((! flat && to_display > 1) || + ((flat || to_display != 1 || + account.has_xflags(ACCOUNT_EXT_VISITED)) && + disp_pred(bound_scope))) { + account.xdata().add_flags(ACCOUNT_EXT_TO_DISPLAY); + DEBUG("account.display", "Marking account as TO_DISPLAY"); + to_display = 1; + } + visited = 1; + } + + return std::pair<std::size_t, std::size_t>(visited, to_display); +} + +void format_accounts::flush() +{ + std::ostream& out(report.output_stream); + + if (report.HANDLED(display_)) { + DEBUG("account.display", + "Account display predicate: " << report.HANDLER(display_).str()); + disp_pred.parse(report.HANDLER(display_).str()); + } + + mark_accounts(*report.session.journal->master, report.HANDLED(flat)); + + std::size_t displayed = 0; + + foreach (account_t * account, posted_accounts) + displayed += post_account(*account, report.HANDLED(flat)); + + if (displayed > 1 && + ! report.HANDLED(no_total) && ! report.HANDLED(percent)) { + bind_scope_t bound_scope(report, *report.session.journal->master); + out << separator_format(bound_scope); + + if (prepend_format) + static_cast<std::ostream&>(report.output_stream) + << prepend_format(bound_scope); + + out << total_line_format(bound_scope); + } + + out.flush(); +} + +void format_accounts::operator()(account_t& account) +{ + DEBUG("account.display", "Posting account: " << account.fullname()); + posted_accounts.push_back(&account); +} + +} // namespace ledger diff --git a/src/output.h b/src/output.h new file mode 100644 index 00000000..778a9335 --- /dev/null +++ b/src/output.h @@ -0,0 +1,110 @@ +/* + * 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. + */ + +/** + * @addtogroup report + */ + +/** + * @file output.h + * @author John Wiegley + * + * @ingroup report + */ +#ifndef _OUTPUT_H +#define _OUTPUT_H + +#include "chain.h" +#include "predicate.h" +#include "format.h" +#include "account.h" + +namespace ledger { + +class xact_t; +class post_t; +class report_t; + +class format_posts : public item_handler<post_t> +{ +protected: + report_t& report; + format_t first_line_format; + format_t next_lines_format; + format_t between_format; + format_t prepend_format; + xact_t * last_xact; + post_t * last_post; + bool print_raw; + +public: + format_posts(report_t& _report, const string& format, + bool _print_raw = false, + const optional<string>& _prepend_format = none); + virtual ~format_posts() { + TRACE_DTOR(format_posts); + } + + virtual void flush(); + virtual void operator()(post_t& post); +}; + +class format_accounts : public item_handler<account_t> +{ +protected: + report_t& report; + format_t account_line_format; + format_t total_line_format; + format_t separator_format; + format_t prepend_format; + predicate_t disp_pred; + + std::list<account_t *> posted_accounts; + +public: + format_accounts(report_t& _report, const string& _format, + const optional<string>& _prepend_format = none); + virtual ~format_accounts() { + TRACE_DTOR(format_accounts); + } + + std::pair<std::size_t, std::size_t> + mark_accounts(account_t& account, const bool flat); + + virtual std::size_t post_account(account_t& account, const bool flat); + virtual void flush(); + + virtual void operator()(account_t& account); +}; + +} // namespace ledger + +#endif // _OUTPUT_H diff --git a/src/parser.cc b/src/parser.cc new file mode 100644 index 00000000..ef778411 --- /dev/null +++ b/src/parser.cc @@ -0,0 +1,517 @@ +/* + * 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 "parser.h" + +namespace ledger { + +expr_t::ptr_op_t +expr_t::parser_t::parse_value_term(std::istream& in, + const parse_flags_t& tflags) const +{ + ptr_op_t node; + + token_t& tok = next_token(in, tflags); + + switch (tok.kind) { + case token_t::VALUE: + node = new op_t(op_t::VALUE); + node->set_value(tok.value); + break; + + case token_t::IDENT: { + string ident = tok.value.as_string(); + + node = new op_t(op_t::IDENT); + node->set_ident(ident); + + // An identifier followed by ( represents a function call + tok = next_token(in, tflags.plus_flags(PARSE_OP_CONTEXT)); + if (tok.kind == token_t::LPAREN) { + ptr_op_t call_node(new op_t(op_t::O_CALL)); + call_node->set_left(node); + node = call_node; + + push_token(tok); // let the parser see it again + node->set_right(parse_value_expr(in, tflags.plus_flags(PARSE_SINGLE))); + + if (node->has_right() && node->right()->kind == op_t::O_CONS) + node->set_right(node->right()->left()); + } else { + push_token(tok); + } + break; + } + + case token_t::LPAREN: + node = parse_value_expr(in, tflags.plus_flags(PARSE_PARTIAL) + .minus_flags(PARSE_SINGLE)); + tok = next_token(in, tflags); + if (tok.kind != token_t::RPAREN) + tok.expected(')'); + + if (node->kind == op_t::O_CONS) { + ptr_op_t prev(node); + node = new op_t(op_t::O_SEQ); + node->set_left(prev); + } + break; + + default: + push_token(tok); + break; + } + + return node; +} + +expr_t::ptr_op_t +expr_t::parser_t::parse_dot_expr(std::istream& in, + const parse_flags_t& tflags) const +{ + ptr_op_t node(parse_value_term(in, tflags)); + + if (node && ! tflags.has_flags(PARSE_SINGLE)) { + while (true) { + token_t& tok = next_token(in, tflags.plus_flags(PARSE_OP_CONTEXT)); + if (tok.kind == token_t::DOT) { + ptr_op_t prev(node); + node = new op_t(op_t::O_LOOKUP); + node->set_left(prev); + node->set_right(parse_value_term(in, tflags)); + if (! node->right()) + throw_(parse_error, + _("%1 operator not followed by argument") << tok.symbol); + } else { + push_token(tok); + break; + } + } + } + + return node; +} + +expr_t::ptr_op_t +expr_t::parser_t::parse_unary_expr(std::istream& in, + const parse_flags_t& tflags) const +{ + ptr_op_t node; + + token_t& tok = next_token(in, tflags); + + switch (tok.kind) { + case token_t::EXCLAM: { + ptr_op_t term(parse_dot_expr(in, tflags)); + if (! term) + throw_(parse_error, + _("%1 operator not followed by argument") << tok.symbol); + + // A very quick optimization + if (term->kind == op_t::VALUE) { + term->as_value_lval().in_place_not(); + node = term; + } else { + node = new op_t(op_t::O_NOT); + node->set_left(term); + } + break; + } + + case token_t::MINUS: { + ptr_op_t term(parse_dot_expr(in, tflags)); + if (! term) + throw_(parse_error, + _("%1 operator not followed by argument") << tok.symbol); + + // A very quick optimization + if (term->kind == op_t::VALUE) { + term->as_value_lval().in_place_negate(); + node = term; + } else { + node = new op_t(op_t::O_NEG); + node->set_left(term); + } + break; + } + + default: + push_token(tok); + node = parse_dot_expr(in, tflags); + break; + } + + return node; +} + +expr_t::ptr_op_t +expr_t::parser_t::parse_mul_expr(std::istream& in, + const parse_flags_t& tflags) const +{ + ptr_op_t node(parse_unary_expr(in, tflags)); + + if (node && ! tflags.has_flags(PARSE_SINGLE)) { + while (true) { + token_t& tok = next_token(in, tflags.plus_flags(PARSE_OP_CONTEXT)); + + if (tok.kind == token_t::STAR || tok.kind == token_t::SLASH || + tok.kind == token_t::KW_DIV) { + ptr_op_t prev(node); + node = new op_t(tok.kind == token_t::STAR ? + op_t::O_MUL : op_t::O_DIV); + node->set_left(prev); + node->set_right(parse_unary_expr(in, tflags)); + if (! node->right()) + throw_(parse_error, + _("%1 operator not followed by argument") << tok.symbol); + } else { + push_token(tok); + break; + } + } + } + + return node; +} + +expr_t::ptr_op_t +expr_t::parser_t::parse_add_expr(std::istream& in, + const parse_flags_t& tflags) const +{ + ptr_op_t node(parse_mul_expr(in, tflags)); + + if (node && ! tflags.has_flags(PARSE_SINGLE)) { + while (true) { + token_t& tok = next_token(in, tflags.plus_flags(PARSE_OP_CONTEXT)); + + if (tok.kind == token_t::PLUS || + tok.kind == token_t::MINUS) { + ptr_op_t prev(node); + node = new op_t(tok.kind == token_t::PLUS ? + op_t::O_ADD : op_t::O_SUB); + node->set_left(prev); + node->set_right(parse_mul_expr(in, tflags)); + if (! node->right()) + throw_(parse_error, + _("%1 operator not followed by argument") << tok.symbol); + } else { + push_token(tok); + break; + } + } + } + + return node; +} + +expr_t::ptr_op_t +expr_t::parser_t::parse_logic_expr(std::istream& in, + const parse_flags_t& tflags) const +{ + ptr_op_t node(parse_add_expr(in, tflags)); + + if (node && ! tflags.has_flags(PARSE_SINGLE)) { + while (true) { + op_t::kind_t kind = op_t::LAST; + parse_flags_t _flags = tflags; + token_t& tok = next_token(in, tflags.plus_flags(PARSE_OP_CONTEXT)); + bool negate = false; + + switch (tok.kind) { + case token_t::DEFINE: + kind = op_t::O_DEFINE; + break; + case token_t::EQUAL: + if (tflags.has_flags(PARSE_NO_ASSIGN)) + tok.rewind(in); + else + kind = op_t::O_EQ; + break; + case token_t::NEQUAL: + kind = op_t::O_EQ; + negate = true; + break; + case token_t::MATCH: + kind = op_t::O_MATCH; + break; + case token_t::NMATCH: + kind = op_t::O_MATCH; + negate = true; + break; + case token_t::LESS: + kind = op_t::O_LT; + break; + case token_t::LESSEQ: + kind = op_t::O_LTE; + break; + case token_t::GREATER: + kind = op_t::O_GT; + break; + case token_t::GREATEREQ: + kind = op_t::O_GTE; + break; + default: + push_token(tok); + goto exit_loop; + } + + if (kind != op_t::LAST) { + ptr_op_t prev(node); + node = new op_t(kind); + node->set_left(prev); + node->set_right(parse_add_expr(in, _flags)); + + if (! node->right()) + throw_(parse_error, + _("%1 operator not followed by argument") << tok.symbol); + + if (negate) { + prev = node; + node = new op_t(op_t::O_NOT); + node->set_left(prev); + } + } + } + } + + exit_loop: + return node; +} + +expr_t::ptr_op_t +expr_t::parser_t::parse_and_expr(std::istream& in, + const parse_flags_t& tflags) const +{ + ptr_op_t node(parse_logic_expr(in, tflags)); + + if (node && ! tflags.has_flags(PARSE_SINGLE)) { + while (true) { + token_t& tok = next_token(in, tflags.plus_flags(PARSE_OP_CONTEXT)); + + if (tok.kind == token_t::KW_AND) { + ptr_op_t prev(node); + node = new op_t(op_t::O_AND); + node->set_left(prev); + node->set_right(parse_logic_expr(in, tflags)); + if (! node->right()) + throw_(parse_error, + _("%1 operator not followed by argument") << tok.symbol); + } else { + push_token(tok); + break; + } + } + } + return node; +} + +expr_t::ptr_op_t +expr_t::parser_t::parse_or_expr(std::istream& in, + const parse_flags_t& tflags) const +{ + ptr_op_t node(parse_and_expr(in, tflags)); + + if (node && ! tflags.has_flags(PARSE_SINGLE)) { + while (true) { + token_t& tok = next_token(in, tflags.plus_flags(PARSE_OP_CONTEXT)); + + if (tok.kind == token_t::KW_OR) { + ptr_op_t prev(node); + node = new op_t(op_t::O_OR); + node->set_left(prev); + node->set_right(parse_and_expr(in, tflags)); + if (! node->right()) + throw_(parse_error, + _("%1 operator not followed by argument") << tok.symbol); + } else { + push_token(tok); + break; + } + } + } + return node; +} + +expr_t::ptr_op_t +expr_t::parser_t::parse_querycolon_expr(std::istream& in, + const parse_flags_t& tflags) const +{ + ptr_op_t node(parse_or_expr(in, tflags)); + + if (node && ! tflags.has_flags(PARSE_SINGLE)) { + token_t& tok = next_token(in, tflags.plus_flags(PARSE_OP_CONTEXT)); + + if (tok.kind == token_t::QUERY) { + ptr_op_t prev(node); + node = new op_t(op_t::O_QUERY); + node->set_left(prev); + node->set_right(parse_or_expr(in, tflags)); + if (! node->right()) + throw_(parse_error, + _("%1 operator not followed by argument") << tok.symbol); + + token_t& next_tok = next_token(in, tflags.plus_flags(PARSE_OP_CONTEXT)); + if (next_tok.kind != token_t::COLON) + next_tok.expected(':'); + + prev = node->right(); + ptr_op_t subnode = new op_t(op_t::O_COLON); + subnode->set_left(prev); + subnode->set_right(parse_or_expr(in, tflags)); + if (! subnode->right()) + throw_(parse_error, + _("%1 operator not followed by argument") << tok.symbol); + + node->set_right(subnode); + } + else if (tok.kind == token_t::KW_IF) { + ptr_op_t if_op(parse_or_expr(in, tflags)); + if (! if_op) + throw_(parse_error, _("'if' keyword not followed by argument")); + + tok = next_token(in, tflags.plus_flags(PARSE_OP_CONTEXT)); + if (tok.kind == token_t::KW_ELSE) { + ptr_op_t else_op(parse_or_expr(in, tflags)); + if (! else_op) + throw_(parse_error, _("'else' keyword not followed by argument")); + + ptr_op_t subnode = new op_t(op_t::O_COLON); + subnode->set_left(node); + subnode->set_right(else_op); + + node = new op_t(op_t::O_QUERY); + node->set_left(if_op); + node->set_right(subnode); + } else { + ptr_op_t null_node = new op_t(op_t::VALUE); + null_node->set_value(NULL_VALUE); + + ptr_op_t subnode = new op_t(op_t::O_COLON); + subnode->set_left(node); + subnode->set_right(null_node); + + node = new op_t(op_t::O_QUERY); + node->set_left(if_op); + node->set_right(subnode); + + push_token(tok); + } + } + else { + push_token(tok); + } + } + return node; +} + +expr_t::ptr_op_t +expr_t::parser_t::parse_value_expr(std::istream& in, + const parse_flags_t& tflags) const +{ + ptr_op_t node(parse_querycolon_expr(in, tflags)); + + if (node && ! tflags.has_flags(PARSE_SINGLE)) { + token_t& tok = next_token(in, tflags.plus_flags(PARSE_OP_CONTEXT)); + + if (tok.kind == token_t::COMMA || tok.kind == token_t::SEMI) { + bool comma_op = tok.kind == token_t::COMMA; + + ptr_op_t prev(node); + node = new op_t(comma_op ? op_t::O_CONS : op_t::O_SEQ); + node->set_left(prev); + node->set_right(parse_value_expr(in, tflags)); + if (! node->right()) + throw_(parse_error, + _("%1 operator not followed by argument") << tok.symbol); + + tok = next_token(in, tflags.plus_flags(PARSE_OP_CONTEXT)); + } + + if (tok.kind != token_t::TOK_EOF) { + if (tflags.has_flags(PARSE_PARTIAL)) + push_token(tok); + else + tok.unexpected(); + } + } + else if (! tflags.has_flags(PARSE_PARTIAL) && + ! tflags.has_flags(PARSE_SINGLE)) { + throw_(parse_error, _("Failed to parse value expression")); + } + + return node; +} + +expr_t::ptr_op_t +expr_t::parser_t::parse(std::istream& in, + const parse_flags_t& flags, + const optional<string>& original_string) +{ + try { + ptr_op_t top_node = parse_value_expr(in, flags); + + if (use_lookahead) { + use_lookahead = false; + lookahead.rewind(in); + } + lookahead.clear(); + + return top_node; + } + catch (const std::exception& err) { + if (original_string) { + add_error_context(_("While parsing value expression:")); + + std::streamoff end_pos = 0; + if (in.good()) + end_pos = in.tellg(); + std::streamoff pos = end_pos; + + if (pos > 0) + pos -= lookahead.length; + + DEBUG("parser.error", "original_string = '" << *original_string << "'"); + DEBUG("parser.error", " pos = " << pos); + DEBUG("parser.error", " end_pos = " << end_pos); + DEBUG("parser.error", " token kind = " << int(lookahead.kind)); + DEBUG("parser.error", " token length = " << lookahead.length); + + add_error_context(line_context(*original_string, + static_cast<string::size_type>(pos), + static_cast<string::size_type>(end_pos))); + } + throw; + } +} + +} // namespace ledger diff --git a/src/parser.h b/src/parser.h new file mode 100644 index 00000000..7a69fe08 --- /dev/null +++ b/src/parser.h @@ -0,0 +1,107 @@ +/* + * 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. + */ + +/** + * @addtogroup expr + */ + +/** + * @file parser.h + * @author John Wiegley + * + * @ingroup expr + */ +#ifndef _PARSER_H +#define _PARSER_H + +#include "token.h" +#include "op.h" + +namespace ledger { + +class expr_t::parser_t : public noncopyable +{ + mutable token_t lookahead; + mutable bool use_lookahead; + + token_t& next_token(std::istream& in, const parse_flags_t& tflags) const { + if (use_lookahead) + use_lookahead = false; + else + lookahead.next(in, tflags); + return lookahead; + } + + void push_token(const token_t& tok) const { + assert(&tok == &lookahead); + use_lookahead = true; + } + void push_token() const { + use_lookahead = true; + } + + ptr_op_t parse_value_term(std::istream& in, + const parse_flags_t& flags) const; + ptr_op_t parse_dot_expr(std::istream& in, + const parse_flags_t& flags) const; + ptr_op_t parse_unary_expr(std::istream& in, + const parse_flags_t& flags) const; + ptr_op_t parse_mul_expr(std::istream& in, + const parse_flags_t& flags) const; + ptr_op_t parse_add_expr(std::istream& in, + const parse_flags_t& flags) const; + ptr_op_t parse_logic_expr(std::istream& in, + const parse_flags_t& flags) const; + ptr_op_t parse_and_expr(std::istream& in, + const parse_flags_t& flags) const; + ptr_op_t parse_or_expr(std::istream& in, + const parse_flags_t& flags) const; + ptr_op_t parse_querycolon_expr(std::istream& in, + const parse_flags_t& flags) const; + ptr_op_t parse_value_expr(std::istream& in, + const parse_flags_t& flags) const; + +public: + parser_t() : use_lookahead(false) { + TRACE_CTOR(parser_t, ""); + } + ~parser_t() throw() { + TRACE_DTOR(parser_t); + } + + ptr_op_t parse(std::istream& in, + const parse_flags_t& flags = PARSE_DEFAULT, + const optional<string>& original_string = NULL); +}; + +} // namespace ledger + +#endif // _PARSER_H diff --git a/src/pool.cc b/src/pool.cc new file mode 100644 index 00000000..70f4eed6 --- /dev/null +++ b/src/pool.cc @@ -0,0 +1,364 @@ +/* + * 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 "amount.h" +#include "commodity.h" +#include "annotate.h" +#include "pool.h" +#include "quotes.h" + +namespace ledger { + +shared_ptr<commodity_pool_t> commodity_pool_t::current_pool; + +commodity_pool_t::commodity_pool_t() + : default_commodity(NULL), keep_base(false), + quote_leeway(86400), get_quotes(false), + get_commodity_quote(commodity_quote_from_script) +{ + TRACE_CTOR(commodity_pool_t, ""); + null_commodity = create(""); + null_commodity->add_flags(COMMODITY_BUILTIN | COMMODITY_NOMARKET); +} + +commodity_t * commodity_pool_t::create(const string& symbol) +{ + shared_ptr<commodity_t::base_t> + base_commodity(new commodity_t::base_t(symbol)); + std::auto_ptr<commodity_t> commodity(new commodity_t(this, base_commodity)); + + DEBUG("amounts.commodities", "Creating base commodity " << symbol); + + // Create the "qualified symbol" version of this commodity's symbol + if (commodity_t::symbol_needs_quotes(symbol)) { + commodity->qualified_symbol = "\""; + *commodity->qualified_symbol += symbol; + *commodity->qualified_symbol += "\""; + } + + DEBUG("amounts.commodities", + "Creating commodity '" << commodity->symbol() << "'"); + + std::pair<commodities_map::iterator, bool> result + = commodities.insert(commodities_map::value_type(commodity->mapping_key(), + commodity.get())); + assert(result.second); + + return commodity.release(); +} + +commodity_t * commodity_pool_t::find_or_create(const string& symbol) +{ + DEBUG("amounts.commodities", "Find-or-create commodity " << symbol); + + commodity_t * commodity = find(symbol); + if (commodity) + return commodity; + return create(symbol); +} + +commodity_t * commodity_pool_t::find(const string& symbol) +{ + DEBUG("amounts.commodities", "Find commodity " << symbol); + + commodities_map::const_iterator i = commodities.find(symbol); + if (i != commodities.end()) + return (*i).second; + return NULL; +} + +commodity_t * +commodity_pool_t::create(const string& symbol, const annotation_t& details) +{ + commodity_t * new_comm = create(symbol); + if (! new_comm) + return NULL; + + if (details) + return find_or_create(*new_comm, details); + else + return new_comm; +} + +string commodity_pool_t::make_qualified_name(const commodity_t& comm, + const annotation_t& details) +{ + assert(details); + + if (details.price && details.price->sign() < 0) + throw_(amount_error, _("A commodity's price may not be negative")); + + std::ostringstream name; + comm.print(name); + details.print(name, comm.pool().keep_base); + + DEBUG("amounts.commodities", "make_qualified_name for " + << *comm.qualified_symbol << std::endl << details); + DEBUG("amounts.commodities", "qualified_name is " << name.str()); + + return name.str(); +} + +commodity_t * +commodity_pool_t::find(const string& symbol, const annotation_t& details) +{ + commodity_t * comm = find(symbol); + if (! comm) + return NULL; + + if (details) { + string name = make_qualified_name(*comm, details); + + if (commodity_t * ann_comm = find(name)) { + assert(ann_comm->annotated && as_annotated_commodity(*ann_comm).details); + return ann_comm; + } + return NULL; + } else { + return comm; + } +} + +commodity_t * +commodity_pool_t::find_or_create(const string& symbol, + const annotation_t& details) +{ + commodity_t * comm = find(symbol); + if (! comm) + return NULL; + + if (details) + return find_or_create(*comm, details); + else + return comm; +} + +commodity_t * +commodity_pool_t::create(commodity_t& comm, + const annotation_t& details, + const string& mapping_key) +{ + assert(comm); + assert(details); + assert(! mapping_key.empty()); + + std::auto_ptr<commodity_t> commodity + (new annotated_commodity_t(&comm, details)); + + commodity->qualified_symbol = comm.symbol(); + assert(! commodity->qualified_symbol->empty()); + + DEBUG("amounts.commodities", "Creating annotated commodity " + << "symbol " << commodity->symbol() + << " key " << mapping_key << std::endl << details); + + // Add the fully annotated name to the map, so that this symbol may + // quickly be found again. + commodity->mapping_key_ = mapping_key; + + std::pair<commodities_map::iterator, bool> result + = commodities.insert(commodities_map::value_type(mapping_key, + commodity.get())); + assert(result.second); + + return commodity.release(); +} + +commodity_t * commodity_pool_t::find_or_create(commodity_t& comm, + const annotation_t& details) +{ + assert(comm); + assert(details); + + string name = make_qualified_name(comm, details); + assert(! name.empty()); + + if (commodity_t * ann_comm = find(name)) { + assert(ann_comm->annotated && as_annotated_commodity(*ann_comm).details); + return ann_comm; + } + return create(comm, details, name); +} + +void commodity_pool_t::exchange(commodity_t& commodity, + const amount_t& per_unit_cost, + const datetime_t& moment) +{ + DEBUG("commodity.prices.add", "exchanging commodity " << commodity + << " at per unit cost " << per_unit_cost << " on " << moment); + + commodity_t& base_commodity + (commodity.annotated ? + as_annotated_commodity(commodity).referent() : commodity); + + base_commodity.add_price(moment, per_unit_cost); +} + +cost_breakdown_t +commodity_pool_t::exchange(const amount_t& amount, + const amount_t& cost, + const bool is_per_unit, + const optional<datetime_t>& moment, + const optional<string>& tag) +{ + DEBUG("commodity.prices.add", "exchange: " << amount << " for " << cost); + DEBUG("commodity.prices.add", "exchange: is-per-unit = " << is_per_unit); +#if defined(DEBUG_ON) + if (moment) + DEBUG("commodity.prices.add", "exchange: moment = " << *moment); + if (tag) + DEBUG("commodity.prices.add", "exchange: tag = " << *tag); +#endif + + commodity_t& commodity(amount.commodity()); + + annotation_t * current_annotation = NULL; + if (commodity.annotated) + current_annotation = &as_annotated_commodity(commodity).details; + + amount_t per_unit_cost = + (is_per_unit || amount.is_realzero() ? cost : cost / amount).abs(); + + DEBUG("commodity.prices.add", "exchange: per-unit-cost = " << per_unit_cost); + + if (! per_unit_cost.is_realzero()) + exchange(commodity, per_unit_cost, moment ? *moment : CURRENT_TIME()); + + cost_breakdown_t breakdown; + breakdown.final_cost = ! is_per_unit ? cost : cost * amount; + + DEBUG("commodity.prices.add", + "exchange: final-cost = " << breakdown.final_cost); + + if (current_annotation && current_annotation->price) + breakdown.basis_cost + = (*current_annotation->price * amount).unrounded(); + else + breakdown.basis_cost = breakdown.final_cost; + + DEBUG("commodity.prices.add", + "exchange: basis-cost = " << breakdown.basis_cost); + + annotation_t annotation(per_unit_cost, moment ? + moment->date() : optional<date_t>(), tag); + + annotation.add_flags(ANNOTATION_PRICE_CALCULATED); + if (moment) + annotation.add_flags(ANNOTATION_DATE_CALCULATED); + if (tag) + annotation.add_flags(ANNOTATION_TAG_CALCULATED); + + breakdown.amount = amount_t(amount, annotation); + + DEBUG("commodity.prices.add", + "exchange: amount = " << breakdown.amount); + + return breakdown; +} + +optional<std::pair<commodity_t *, price_point_t> > +commodity_pool_t::parse_price_directive(char * line, bool do_not_add_price) +{ + char * date_field_ptr = line; + char * time_field_ptr = next_element(date_field_ptr); + if (! time_field_ptr) return none; + string date_field = date_field_ptr; + + char * symbol_and_price; + datetime_t datetime; + string symbol; + + if (std::isdigit(time_field_ptr[0])) { + symbol_and_price = next_element(time_field_ptr); + if (! symbol_and_price) return none; + + datetime = parse_datetime(date_field + " " + time_field_ptr); + } + else if (std::isdigit(date_field_ptr[0])) { + symbol_and_price = time_field_ptr; + datetime = datetime_t(parse_date(date_field)); + } + else { + symbol = date_field_ptr; + symbol_and_price = time_field_ptr; + datetime = CURRENT_TIME(); + } + + if (symbol.empty()) + commodity_t::parse_symbol(symbol_and_price, symbol); + + price_point_t point; + point.when = datetime; + point.price.parse(symbol_and_price, PARSE_NO_MIGRATE); + VERIFY(point.price.valid()); + + DEBUG("commodity.download", "Looking up symbol: " << symbol); + if (commodity_t * commodity = find_or_create(symbol)) { + DEBUG("commodity.download", "Adding price for " << symbol << ": " + << point.when << " " << point.price); + if (! do_not_add_price) + commodity->add_price(point.when, point.price, true); + commodity->add_flags(COMMODITY_KNOWN); + return std::pair<commodity_t *, price_point_t>(commodity, point); + } + + return none; +} + +commodity_t * +commodity_pool_t::parse_price_expression(const std::string& str, + const bool add_prices, + const optional<datetime_t>& moment) +{ + scoped_array<char> buf(new char[str.length() + 1]); + + std::strcpy(buf.get(), str.c_str()); + + char * price = std::strchr(buf.get(), '='); + if (price) + *price++ = '\0'; + + if (commodity_t * commodity = find_or_create(trim_ws(buf.get()))) { + if (price && add_prices) { + for (char * p = std::strtok(price, ";"); + p; + p = std::strtok(NULL, ";")) { + commodity->add_price(moment ? *moment : CURRENT_TIME(), amount_t(p)); + } + } + return commodity; + } + return NULL; +} + +} // namespace ledger diff --git a/src/pool.h b/src/pool.h new file mode 100644 index 00000000..995ab23c --- /dev/null +++ b/src/pool.h @@ -0,0 +1,156 @@ +/* + * 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. + */ + +/** + * @addtogroup math + */ + +/** + * @file pool.h + * @author John Wiegley + * + * @ingroup math + * + * @brief Types for managing commodity pools + * + * Long. + */ +#ifndef _POOL_H +#define _POOL_H + +namespace ledger { + +struct cost_breakdown_t +{ + amount_t amount; + amount_t final_cost; + amount_t basis_cost; +}; + +class commodity_pool_t : public noncopyable +{ +public: + /** + * The commodities collection in commodity_pool_t maintains pointers to all + * the commodities which have ever been created by the user, whether + * explicitly by calling the create methods of commodity_pool_t, or + * implicitly by parsing a commoditized amount. + */ + typedef std::map<string, commodity_t *> commodities_map; + + commodities_map commodities; + commodity_t * null_commodity; + commodity_t * default_commodity; + + bool keep_base; // --base + + optional<path> price_db; // --price-db= + long quote_leeway; // --leeway= + bool get_quotes; // --download + + static shared_ptr<commodity_pool_t> current_pool; + + function<optional<price_point_t> + (commodity_t& commodity, const optional<commodity_t&>& in_terms_of)> + get_commodity_quote; + + explicit commodity_pool_t(); + + virtual ~commodity_pool_t() { + TRACE_DTOR(commodity_pool_t); + foreach (commodities_map::value_type pair, commodities) + checked_delete(pair.second); + } + + string make_qualified_name(const commodity_t& comm, + const annotation_t& details); + + commodity_t * create(const string& symbol); + commodity_t * find(const string& name); + commodity_t * find_or_create(const string& symbol); + + commodity_t * create(const string& symbol, const annotation_t& details); + commodity_t * find(const string& symbol, const annotation_t& details); + commodity_t * find_or_create(const string& symbol, + const annotation_t& details); + + commodity_t * create(commodity_t& comm, + const annotation_t& details, + const string& mapping_key); + + commodity_t * find_or_create(commodity_t& comm, + const annotation_t& details); + + // Exchange one commodity for another, while recording the factored price. + + void exchange(commodity_t& commodity, + const amount_t& per_unit_cost, + const datetime_t& moment); + + cost_breakdown_t exchange(const amount_t& amount, + const amount_t& cost, + const bool is_per_unit = false, + const optional<datetime_t>& moment = none, + const optional<string>& tag = none); + + // Parse commodity prices from a textual representation + + optional<std::pair<commodity_t *, price_point_t> > + parse_price_directive(char * line, bool do_not_add_price = false); + + commodity_t * + parse_price_expression(const std::string& str, + const bool add_prices = true, + const optional<datetime_t>& moment = none); + +#if defined(HAVE_BOOST_SERIALIZATION) +private: + /** Serialization. */ + + friend class boost::serialization::access; + + template<class Archive> + void serialize(Archive& ar, const unsigned int /* version */) { + ar & current_pool; + ar & commodities; + ar & null_commodity; + ar & default_commodity; + ar & keep_base; + ar & price_db; + ar & quote_leeway; + ar & get_quotes; + } +#endif // HAVE_BOOST_SERIALIZATION +}; + +} // namespace ledger + +#endif // _POOL_H diff --git a/src/post.cc b/src/post.cc new file mode 100644 index 00000000..34284e1b --- /dev/null +++ b/src/post.cc @@ -0,0 +1,576 @@ +/* + * 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 "post.h" +#include "xact.h" +#include "account.h" +#include "journal.h" +#include "interactive.h" +#include "format.h" + +namespace ledger { + +bool post_t::has_tag(const string& tag) const +{ + if (item_t::has_tag(tag)) + return true; + if (xact) + return xact->has_tag(tag); + return false; +} + +bool post_t::has_tag(const mask_t& tag_mask, + const optional<mask_t>& value_mask) const +{ + if (item_t::has_tag(tag_mask, value_mask)) + return true; + if (xact) + return xact->has_tag(tag_mask, value_mask); + return false; +} + +optional<string> post_t::get_tag(const string& tag) const +{ + if (optional<string> value = item_t::get_tag(tag)) + return value; + if (xact) + return xact->get_tag(tag); + return none; +} + +optional<string> post_t::get_tag(const mask_t& tag_mask, + const optional<mask_t>& value_mask) const +{ + if (optional<string> value = item_t::get_tag(tag_mask, value_mask)) + return value; + if (xact) + return xact->get_tag(tag_mask, value_mask); + return none; +} + +date_t post_t::date() const +{ + if (xdata_ && is_valid(xdata_->date)) + return xdata_->date; + + if (item_t::use_effective_date) { + if (_date_eff) + return *_date_eff; + else if (xact && xact->_date_eff) + return *xact->_date_eff; + } + + if (! _date) { + assert(xact); + return xact->date(); + } + return *_date; +} + +optional<date_t> post_t::effective_date() const +{ + optional<date_t> date = item_t::effective_date(); + if (! date && xact) + return xact->effective_date(); + return date; +} + +namespace { + value_t get_this(post_t& post) { + return value_t(static_cast<scope_t *>(&post)); + } + + value_t get_is_calculated(post_t& post) { + return post.has_flags(POST_CALCULATED); + } + + value_t get_is_cost_calculated(post_t& post) { + return post.has_flags(POST_COST_CALCULATED); + } + + value_t get_virtual(post_t& post) { + return post.has_flags(POST_VIRTUAL); + } + + value_t get_real(post_t& post) { + return ! post.has_flags(POST_VIRTUAL); + } + + value_t get_xact(post_t& post) { + return value_t(static_cast<scope_t *>(post.xact)); + } + + value_t get_code(post_t& post) { + if (post.xact->code) + return string_value(*post.xact->code); + else + return string_value(empty_string); + } + + value_t get_payee(post_t& post) { + return string_value(post.xact->payee); + } + + value_t get_note(post_t& post) { + string note = post.note ? *post.note : empty_string; + note += post.xact->note ? *post.xact->note : empty_string; + return string_value(note); + } + + value_t get_magnitude(post_t& post) { + return post.xact->magnitude(); + } + value_t get_idstring(post_t& post) { + return string_value(post.xact->idstring()); + } + value_t get_id(post_t& post) { + return string_value(post.xact->id()); + } + + value_t get_amount(post_t& post) { + if (post.has_xdata() && post.xdata().has_flags(POST_EXT_COMPOUND)) + return post.xdata().compound_value; + else if (post.amount.is_null()) + return 0L; + else + return post.amount; + } + + value_t get_use_direct_amount(post_t& post) { + return post.has_xdata() && post.xdata().has_flags(POST_EXT_DIRECT_AMT); + } + + value_t get_commodity(post_t& post) { + return string_value(post.amount.commodity().symbol()); + } + + value_t get_commodity_is_primary(post_t& post) { + return post.amount.commodity().has_flags(COMMODITY_PRIMARY); + } + + value_t get_has_cost(post_t& post) { + return post.cost ? true : false; + } + + value_t get_cost(post_t& post) { + if (post.cost) + return *post.cost; + else if (post.has_xdata() && + post.xdata().has_flags(POST_EXT_COMPOUND)) + return post.xdata().compound_value; + else if (post.amount.is_null()) + return 0L; + else + return post.amount; + } + + value_t get_total(post_t& post) { + if (post.xdata_ && ! post.xdata_->total.is_null()) + return post.xdata_->total; + else if (post.amount.is_null()) + return 0L; + else + return post.amount; + } + + value_t get_count(post_t& post) { + if (post.xdata_) + return long(post.xdata_->count); + else + return 1L; + } + + value_t get_account(call_scope_t& scope) + { + in_context_t<post_t> env(scope, "&v"); + + string name; + + if (env.has(0)) { + if (env.value_at(0).is_long()) { + if (env.get<long>(0) > 2) + name = format_t::truncate(env->reported_account()->fullname(), + env.get<long>(0) - 2, + 2 /* account_abbrev_length */); + else + name = env->reported_account()->fullname(); + } else { + account_t * account = NULL; + account_t * master = env->account; + while (master->parent) + master = master->parent; + + if (env.value_at(0).is_string()) { + name = env.get<string>(0); + account = master->find_account(name, false); + } + else if (env.value_at(0).is_mask()) { + name = env.get<mask_t>(0).str(); + account = master->find_account_re(name); + } + else { + throw_(std::runtime_error, + _("Expected string or mask for argument 1, but received %1") + << env.value_at(0).label()); + } + + if (! account) + throw_(std::runtime_error, + _("Could not find an account matching ") << env.value_at(0)); + else + return value_t(static_cast<scope_t *>(account)); + } + } else { + name = env->reported_account()->fullname(); + } + + if (env->has_flags(POST_VIRTUAL)) { + if (env->must_balance()) + name = string("[") + name + "]"; + else + name = string("(") + name + ")"; + } + return string_value(name); + } + + value_t get_account_base(post_t& post) { + return string_value(post.reported_account()->name); + } + + value_t get_account_depth(post_t& post) { + return long(post.reported_account()->depth); + } + + value_t get_datetime(post_t& post) { + return post.xdata().datetime; + } + + template <value_t (*Func)(post_t&)> + value_t get_wrapper(call_scope_t& scope) { + return (*Func)(find_scope<post_t>(scope)); + } +} + +expr_t::ptr_op_t post_t::lookup(const symbol_t::kind_t kind, + const string& name) +{ + if (kind != symbol_t::FUNCTION) + return item_t::lookup(kind, name); + + switch (name[0]) { + case 'a': + if (name[1] == '\0' || name == "amount") + return WRAP_FUNCTOR(get_wrapper<&get_amount>); + else if (name == "account") + return WRAP_FUNCTOR(get_account); + else if (name == "account_base") + return WRAP_FUNCTOR(get_wrapper<&get_account_base>); + break; + + case 'b': + if (name[1] == '\0') + return WRAP_FUNCTOR(get_wrapper<&get_cost>); + break; + + case 'c': + if (name == "code") + return WRAP_FUNCTOR(get_wrapper<&get_code>); + else if (name == "cost") + return WRAP_FUNCTOR(get_wrapper<&get_cost>); + else if (name == "cost_calculated") + return WRAP_FUNCTOR(get_wrapper<&get_is_cost_calculated>); + else if (name == "count") + return WRAP_FUNCTOR(get_wrapper<&get_count>); + else if (name == "calculated") + return WRAP_FUNCTOR(get_wrapper<&get_is_calculated>); + else if (name == "commodity") + return WRAP_FUNCTOR(get_wrapper<&get_commodity>); + break; + + case 'd': + if (name == "depth") + return WRAP_FUNCTOR(get_wrapper<&get_account_depth>); + else if (name == "datetime") + return WRAP_FUNCTOR(get_wrapper<&get_datetime>); + break; + + case 'h': + if (name == "has_cost") + return WRAP_FUNCTOR(get_wrapper<&get_has_cost>); + break; + + case 'i': + if (name == "index") + return WRAP_FUNCTOR(get_wrapper<&get_count>); + else if (name == "id") + return WRAP_FUNCTOR(get_wrapper<&get_id>); + else if (name == "idstring") + return WRAP_FUNCTOR(get_wrapper<&get_idstring>); + break; + + case 'm': + if (name == "magnitude") + return WRAP_FUNCTOR(get_wrapper<&get_magnitude>); + break; + + case 'n': + if (name == "note") + return WRAP_FUNCTOR(get_wrapper<&get_note>); + else if (name[1] == '\0') + return WRAP_FUNCTOR(get_wrapper<&get_count>); + break; + + case 'p': + if (name == "post") + return WRAP_FUNCTOR(get_wrapper<&get_this>); + else if (name == "payee") + return WRAP_FUNCTOR(get_wrapper<&get_payee>); + else if (name == "primary") + return WRAP_FUNCTOR(get_wrapper<&get_commodity_is_primary>); + else if (name == "parent") + return WRAP_FUNCTOR(get_wrapper<&get_xact>); + break; + + case 'r': + if (name == "real") + return WRAP_FUNCTOR(get_wrapper<&get_real>); + break; + + case 't': + if (name == "total") + return WRAP_FUNCTOR(get_wrapper<&get_total>); + break; + + case 'u': + if (name == "use_direct_amount") + return WRAP_FUNCTOR(get_wrapper<&get_use_direct_amount>); + break; + + case 'v': + if (name == "virtual") + return WRAP_FUNCTOR(get_wrapper<&get_virtual>); + break; + + case 'x': + if (name == "xact") + return WRAP_FUNCTOR(get_wrapper<&get_xact>); + break; + + case 'N': + if (name[1] == '\0') + return WRAP_FUNCTOR(get_wrapper<&get_count>); + break; + + case 'O': + if (name[1] == '\0') + return WRAP_FUNCTOR(get_wrapper<&get_total>); + break; + + case 'R': + if (name[1] == '\0') + return WRAP_FUNCTOR(get_wrapper<&get_real>); + break; + } + + return item_t::lookup(kind, name); +} + +bool post_t::valid() const +{ + if (! xact) { + DEBUG("ledger.validate", "post_t: ! xact"); + return false; + } + + posts_list::const_iterator i = + std::find(xact->posts.begin(), + xact->posts.end(), this); + if (i == xact->posts.end()) { + DEBUG("ledger.validate", "post_t: ! found"); + return false; + } + + if (! account) { + DEBUG("ledger.validate", "post_t: ! account"); + return false; + } + + if (! amount.valid()) { + DEBUG("ledger.validate", "post_t: ! amount.valid()"); + return false; + } + + if (cost) { + if (! cost->valid()) { + DEBUG("ledger.validate", "post_t: cost && ! cost->valid()"); + return false; + } + if (! cost->keep_precision()) { + DEBUG("ledger.validate", "post_t: ! cost->keep_precision()"); + return false; + } + } + + return true; +} + +void post_t::add_to_value(value_t& value, const optional<expr_t&>& expr) const +{ + if (xdata_ && xdata_->has_flags(POST_EXT_COMPOUND)) { + add_or_set_value(value, xdata_->compound_value); + } + else if (expr) { + bind_scope_t bound_scope(*expr->get_context(), + const_cast<post_t&>(*this)); +#if 1 + value_t temp(expr->calc(bound_scope)); + add_or_set_value(value, temp); +#else + if (! xdata_) xdata_ = xdata_t(); + xdata_->value = expr->calc(bound_scope); + xdata_->add_flags(POST_EXT_COMPOUND); + + add_or_set_value(value, xdata_->value); +#endif + } + else if (xdata_ && xdata_->has_flags(POST_EXT_VISITED) && + ! xdata_->visited_value.is_null()) { + add_or_set_value(value, xdata_->visited_value); + } + else { + add_or_set_value(value, amount); + } +} + +void post_t::set_reported_account(account_t * account) +{ + xdata().account = account; + account->xdata().reported_posts.push_back(this); +} + +void to_xml(std::ostream& out, const post_t& post) +{ + push_xml x(out, "posting", true); + + if (post.state() == item_t::CLEARED) + out << " state=\"cleared\""; + else if (post.state() == item_t::PENDING) + out << " state=\"pending\""; + + if (post.has_flags(POST_VIRTUAL)) + out << " virtual=\"true\""; + if (post.has_flags(ITEM_GENERATED)) + out << " generated=\"true\""; + + x.close_attrs(); + + if (post._date) { + push_xml y(out, "date"); + to_xml(out, *post._date, false); + } + if (post._date_eff) { + push_xml y(out, "effective-date"); + to_xml(out, *post._date_eff, false); + } + + if (post.account) { + push_xml y(out, "account", true); + + out << " ref=\""; + out.width(sizeof(unsigned long) * 2); + out.fill('0'); + out << std::hex << reinterpret_cast<unsigned long>(post.account); + out << '"'; + y.close_attrs(); + + { + push_xml z(out, "name"); + out << z.guard(post.account->fullname()); + } + } + + { + push_xml y(out, "post-amount"); + if (post.has_xdata() && post.xdata().has_flags(POST_EXT_COMPOUND)) + to_xml(out, post.xdata().compound_value); + else + to_xml(out, post.amount); + } + + if (post.cost) { + push_xml y(out, "cost"); + to_xml(out, *post.cost); + } + + if (post.assigned_amount) { + if (post.has_flags(POST_CALCULATED)) { + push_xml y(out, "balance-assertion"); + to_xml(out, *post.assigned_amount); + } else { + push_xml y(out, "balance-assignment"); + to_xml(out, *post.assigned_amount); + } + } + + if (post.note) { + push_xml y(out, "note"); + out << y.guard(*post.note); + } + + if (post.metadata) { + push_xml y(out, "metadata"); + foreach (const item_t::string_map::value_type& pair, *post.metadata) { + if (pair.second) { + push_xml z(out, "variable"); + { + push_xml z(out, "key"); + out << y.guard(pair.first); + } + { + push_xml z(out, "value"); + out << y.guard(*pair.second); + } + } else { + push_xml z(out, "tag"); + out << y.guard(pair.first); + } + } + } + + if (post.xdata_ && ! post.xdata_->total.is_null()) { + push_xml y(out, "total"); + to_xml(out, post.xdata_->total); + } +} + +} // namespace ledger diff --git a/src/post.h b/src/post.h new file mode 100644 index 00000000..a0ab5ffd --- /dev/null +++ b/src/post.h @@ -0,0 +1,224 @@ +/* + * 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. + */ + +/** + * @addtogroup data + */ + +/** + * @file post.h + * @author John Wiegley + * + * @ingroup data + */ +#ifndef _POST_H +#define _POST_H + +#include "item.h" + +namespace ledger { + +class xact_t; +class account_t; + +class post_t : public item_t +{ +public: +#define POST_VIRTUAL 0x10 // the account was specified with (parens) +#define POST_MUST_BALANCE 0x20 // posting must balance in the transaction +#define POST_CALCULATED 0x40 // posting's amount was calculated +#define POST_COST_CALCULATED 0x80 // posting's cost was calculated + + xact_t * xact; // only set for posts of regular xacts + account_t * account; + + amount_t amount; // can be null until finalization + optional<expr_t> amount_expr; + optional<amount_t> cost; + optional<amount_t> assigned_amount; + + post_t(account_t * _account = NULL, + flags_t _flags = ITEM_NORMAL) + : item_t(_flags), + xact(NULL), account(_account) + { + TRACE_CTOR(post_t, "account_t *, flags_t"); + } + post_t(account_t * _account, + const amount_t& _amount, + flags_t _flags = ITEM_NORMAL, + const optional<string>& _note = none) + : item_t(_flags, _note), + xact(NULL), account(_account), amount(_amount) + { + TRACE_CTOR(post_t, "account_t *, const amount_t&, flags_t, const optional<string>&"); + } + post_t(const post_t& post) + : item_t(post), + xact(post.xact), + account(post.account), + amount(post.amount), + cost(post.cost), + assigned_amount(post.assigned_amount), + xdata_(post.xdata_) + { + TRACE_CTOR(post_t, "copy"); + } + virtual ~post_t() { + TRACE_DTOR(post_t); + } + + virtual bool has_tag(const string& tag) const; + virtual bool has_tag(const mask_t& tag_mask, + const optional<mask_t>& value_mask = none) const; + + virtual optional<string> get_tag(const string& tag) const; + virtual optional<string> get_tag(const mask_t& tag_mask, + const optional<mask_t>& value_mask = none) const; + + virtual date_t date() const; + virtual optional<date_t> effective_date() const; + + bool must_balance() const { + return ! has_flags(POST_VIRTUAL) || has_flags(POST_MUST_BALANCE); + } + + virtual expr_t::ptr_op_t lookup(const symbol_t::kind_t kind, + const string& name); + + bool valid() const; + + struct xdata_t : public supports_flags<uint_least16_t> + { +#define POST_EXT_RECEIVED 0x0001 +#define POST_EXT_HANDLED 0x0002 +#define POST_EXT_DISPLAYED 0x0004 +#define POST_EXT_DIRECT_AMT 0x0008 +#define POST_EXT_SORT_CALC 0x0010 +#define POST_EXT_COMPOUND 0x0020 +#define POST_EXT_VISITED 0x0040 +#define POST_EXT_MATCHES 0x0080 +#define POST_EXT_CONSIDERED 0x0100 + + value_t visited_value; + value_t compound_value; + value_t total; + std::size_t count; + date_t date; + datetime_t datetime; + account_t * account; + + std::list<sort_value_t> sort_values; + + xdata_t() + : supports_flags<uint_least16_t>(), count(0), account(NULL) { + TRACE_CTOR(post_t::xdata_t, ""); + } + xdata_t(const xdata_t& other) + : supports_flags<uint_least16_t>(other.flags()), + visited_value(other.visited_value), + compound_value(other.compound_value), + total(other.total), + count(other.count), + date(other.date), + account(other.account), + sort_values(other.sort_values) + { + TRACE_CTOR(post_t::xdata_t, "copy"); + } + ~xdata_t() throw() { + TRACE_DTOR(post_t::xdata_t); + } + }; + + // This variable holds optional "extended data" which is usually produced + // only during reporting, and only for the posting set being reported. + // It's a memory-saving measure to delay allocation until the last possible + // moment. + mutable optional<xdata_t> xdata_; + + bool has_xdata() const { + return xdata_; + } + void clear_xdata() { + xdata_ = none; + } + xdata_t& xdata() { + if (! xdata_) + xdata_ = xdata_t(); + return *xdata_; + } + const xdata_t& xdata() const { + return const_cast<post_t *>(this)->xdata(); + } + + void add_to_value(value_t& value, + const optional<expr_t&>& expr = none) const; + + void set_reported_account(account_t * account); + + account_t * reported_account() { + if (xdata_) + if (account_t * acct = xdata_->account) + return acct; + return account; + } + + const account_t * reported_account() const { + return const_cast<post_t *>(this)->reported_account(); + } + + friend class xact_t; + +#if defined(HAVE_BOOST_SERIALIZATION) +private: + /** Serialization. */ + + friend class boost::serialization::access; + + template<class Archive> + void serialize(Archive& ar, const unsigned int /* version */) { + ar & boost::serialization::base_object<item_t>(*this); + ar & xact; + ar & account; + ar & amount; + ar & amount_expr; + ar & cost; + ar & assigned_amount; + } +#endif // HAVE_BOOST_SERIALIZATION +}; + +void to_xml(std::ostream& out, const post_t& post); + +} // namespace ledger + +#endif // _POST_H diff --git a/src/precmd.cc b/src/precmd.cc new file mode 100644 index 00000000..31249016 --- /dev/null +++ b/src/precmd.cc @@ -0,0 +1,211 @@ +/* + * 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 "precmd.h" +#include "xact.h" +#include "post.h" +#include "account.h" +#include "query.h" +#include "session.h" +#include "report.h" +#include "format.h" + +namespace ledger { + +namespace { + post_t * get_sample_xact(report_t& report) + { + { + string str; + { + std::ostringstream buf; + + buf << "2004/05/27 Book Store\n" + << " ; This note applies to all postings. :SecondTag:\n" + << " Expenses:Books 20 BOOK @ $10\n" + << " ; Metadata: Some Value\n" + << " ; :ExampleTag:\n" + << " ; Here follows a note describing the posting.\n" + << " Liabilities:MasterCard $-200.00\n"; + + str = buf.str(); + } + + std::ostream& out(report.output_stream); + + out << _("--- Context is first posting of the following transaction ---") + << std::endl << str << std::endl; + { + std::istringstream in(str); + report.session.journal->parse(in, report.session); + report.session.journal->clear_xdata(); + } + } + xact_t * first = report.session.journal->xacts.front(); + return first->posts.front(); + } +} + +value_t parse_command(call_scope_t& args) +{ + string arg = join_args(args); + if (arg.empty()) { + throw std::logic_error(_("Usage: parse TEXT")); + return 1L; + } + + report_t& report(find_scope<report_t>(args)); + std::ostream& out(report.output_stream); + + post_t * post = get_sample_xact(report); + + out << _("--- Input expression ---") << std::endl; + out << arg << std::endl; + + out << std::endl << _("--- Text as parsed ---") << std::endl; + expr_t expr(arg); + expr.print(out); + out << std::endl; + + out << std::endl << _("--- Expression tree ---") << std::endl; + expr.dump(out); + + bind_scope_t bound_scope(args, *post); + expr.compile(bound_scope); + out << std::endl << _("--- Compiled tree ---") << std::endl; + expr.dump(out); + + out << std::endl << _("--- Calculated value ---") << std::endl; + value_t result(expr.calc()); + result.strip_annotations(report.what_to_keep()).dump(out); + out << std::endl; + + return NULL_VALUE; +} + +value_t eval_command(call_scope_t& args) +{ + report_t& report(find_scope<report_t>(args)); + expr_t expr(join_args(args)); + value_t result(expr.calc(args).strip_annotations(report.what_to_keep())); + + if (! result.is_null()) + report.output_stream << result << std::endl; + + return NULL_VALUE; +} + +value_t format_command(call_scope_t& args) +{ + string arg = join_args(args); + if (arg.empty()) { + throw std::logic_error(_("Usage: format TEXT")); + return 1L; + } + + report_t& report(find_scope<report_t>(args)); + std::ostream& out(report.output_stream); + + post_t * post = get_sample_xact(report); + + out << _("--- Input format string ---") << std::endl; + out << arg << std::endl << std::endl; + + out << _("--- Format elements ---") << std::endl; + format_t fmt(arg); + fmt.dump(out); + + out << std::endl << _("--- Formatted string ---") << std::endl; + bind_scope_t bound_scope(args, *post); + out << '"'; + out << fmt(bound_scope); + out << "\"\n"; + + return NULL_VALUE; +} + +value_t period_command(call_scope_t& args) +{ + string arg = join_args(args); + if (arg.empty()) { + throw std::logic_error(_("Usage: period TEXT")); + return 1L; + } + + report_t& report(find_scope<report_t>(args)); + std::ostream& out(report.output_stream); + + show_period_tokens(out, arg); + out << std::endl; + + date_interval_t interval(arg); + interval.dump(out, report.session.current_year); + + return NULL_VALUE; +} + +value_t args_command(call_scope_t& args) +{ + report_t& report(find_scope<report_t>(args)); + std::ostream& out(report.output_stream); + + out << _("--- Input arguments ---") << std::endl; + args.value().dump(out); + out << std::endl << std::endl; + + query_t query(args.value(), report.what_to_keep()); + if (query) { + call_scope_t sub_args(static_cast<scope_t&>(args)); + sub_args.push_back(string_value(query.text())); + + parse_command(sub_args); + } + + if (query.tokens_remaining()) { + out << std::endl << _("====== Display predicate ======") + << std::endl << std::endl; + + query.parse_again(); + + if (query) { + call_scope_t disp_sub_args(static_cast<scope_t&>(args)); + disp_sub_args.push_back(string_value(query.text())); + + parse_command(disp_sub_args); + } + } + + return NULL_VALUE; +} + +} // namespace ledger diff --git a/src/driver/option.h b/src/precmd.h index d26c8417..e0f81cf8 100644 --- a/src/driver/option.h +++ b/src/precmd.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2007, John Wiegley. All rights reserved. + * 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 @@ -29,25 +29,31 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef _OPTION_H -#define _OPTION_H - -#include "xpath.h" +/** + * @addtogroup report + */ -namespace ledger { +/** + * @file precmd.h + * @author John Wiegley + * + * @ingroup report + */ +#ifndef _PRECMD_H +#define _PRECMD_H -void process_option(const string& name, xml::xpath_t::scope_t& scope, - const char * arg = NULL); +#include "value.h" -void process_environment(const char ** envp, const string& tag, - xml::xpath_t::scope_t& scope); +namespace ledger { -void process_arguments(int argc, char ** argv, const bool anywhere, - xml::xpath_t::scope_t& scope, - std::list<string>& args); +class call_scope_t; -DECLARE_EXCEPTION(option_error); +value_t parse_command(call_scope_t& args); +value_t eval_command(call_scope_t& args); +value_t format_command(call_scope_t& args); +value_t period_command(call_scope_t& args); +value_t args_command(call_scope_t& args); } // namespace ledger -#endif // _OPTION_H +#endif // _PRECMD_H diff --git a/src/data/textual.h b/src/predicate.cc index f4d81f19..4da4decf 100644 --- a/src/data/textual.h +++ b/src/predicate.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2007, John Wiegley. All rights reserved. + * 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 @@ -29,23 +29,18 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef _TEXTUAL_H -#define _TEXTUAL_H +#include <system.hh> -#include "parser.h" +#include "predicate.h" +#include "query.h" +#include "op.h" namespace ledger { -class textual_parser_t : public parser_t +predicate_t::predicate_t(const query_t& other) + : expr_t(other), what_to_keep(other.what_to_keep) { - public: - virtual bool test(std::istream& in) const; - - virtual std::size_t parse(std::istream& in, - const path& pathname, - xml::builder_t& builder); -}; + TRACE_CTOR(predicate_t, "query_t"); +} } // namespace ledger - -#endif // _TEXTUAL_H diff --git a/src/predicate.h b/src/predicate.h new file mode 100644 index 00000000..b3b81f9b --- /dev/null +++ b/src/predicate.h @@ -0,0 +1,106 @@ +/* + * 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. + */ + +/** + * @addtogroup expr + */ + +/** + * @file predicate.h + * @author John Wiegley + * + * @ingroup expr + */ +#ifndef _PREDICATE_H +#define _PREDICATE_H + +#include "expr.h" +#include "commodity.h" +#include "annotate.h" + +namespace ledger { + +class query_t; + +class predicate_t : public expr_t +{ +public: + keep_details_t what_to_keep; + + predicate_t(const keep_details_t& _what_to_keep = keep_details_t()) + : what_to_keep(_what_to_keep) { + TRACE_CTOR(predicate_t, ""); + } + predicate_t(const predicate_t& other) + : expr_t(other), what_to_keep(other.what_to_keep) { + TRACE_CTOR(predicate_t, "copy"); + } + predicate_t(const query_t& other); + + predicate_t(const string& str, const keep_details_t& _what_to_keep, + const parse_flags_t& flags = PARSE_DEFAULT) + : expr_t(str, flags), what_to_keep(_what_to_keep) { + TRACE_CTOR(predicate_t, "string, keep_details_t, parse_flags_t"); + } + predicate_t(std::istream& in, const keep_details_t& _what_to_keep, + const parse_flags_t& flags = PARSE_DEFAULT) + : expr_t(in, flags), what_to_keep(_what_to_keep) { + TRACE_CTOR(predicate_t, "std::istream&, keep_details_t, parse_flags_t"); + } + virtual ~predicate_t() { + TRACE_DTOR(predicate_t); + } + + virtual value_t real_calc(scope_t& scope) { + return (*this ? + expr_t::real_calc(scope) + .strip_annotations(what_to_keep) + .to_boolean() : + true); + } + +#if defined(HAVE_BOOST_SERIALIZATION) +private: + /** Serialization. */ + + friend class boost::serialization::access; + + template<class Archive> + void serialize(Archive& ar, const unsigned int /* version */) { + ar & boost::serialization::base_object<expr_t>(*this); + ar & what_to_keep; + } +#endif // HAVE_BOOST_SERIALIZATION +}; + +} // namespace ledger + +#endif // _PREDICATE_H diff --git a/src/pstream.h b/src/pstream.h new file mode 100644 index 00000000..00caf4e0 --- /dev/null +++ b/src/pstream.h @@ -0,0 +1,109 @@ +/* + * 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. + */ + +/** + * @addtogroup util + */ + +/** + * @file pstream.h + * @author John Wiegley + * + * @ingroup util + */ +#ifndef _PSTREAM_H +#define _PSTREAM_H + +//#include <istream> +//#include <streambuf> + +class ptristream : public std::istream +{ + class ptrinbuf : public std::streambuf + { + ptrinbuf(const ptrinbuf&); + ptrinbuf& operator=(const ptrinbuf&); + + protected: + char * ptr; + std::size_t len; + + public: + ptrinbuf(char * _ptr, std::size_t _len) : ptr(_ptr), len(_len) { + if (*ptr && len == 0) + len = std::strlen(ptr); + + setg(ptr, // beginning of putback area + ptr, // read position + ptr+len); // end position + } + + protected: + virtual int_type underflow() { + // is read position before end of buffer? + if (gptr() < egptr()) + return traits_type::to_int_type(*gptr()); + else + return EOF; + } + + virtual pos_type seekoff(off_type off, ios_base::seekdir way, + ios_base::openmode) + { + switch (way) { + case std::ios::cur: + setg(ptr, gptr()+off, ptr+len); + break; + case std::ios::beg: + setg(ptr, ptr+off, ptr+len); + break; + case std::ios::end: + setg(ptr, egptr()+off, ptr+len); + break; + + default: + return pos_type(off_type(-1)); + } + return pos_type(gptr() - ptr); + } + }; + +protected: + ptrinbuf buf; + +public: + ptristream(char * ptr, std::size_t len = 0) + : std::istream(0), buf(ptr, len) { + rdbuf(&buf); + } +}; + +#endif // _PSTREAM_H diff --git a/src/py_account.cc b/src/py_account.cc new file mode 100644 index 00000000..056cd722 --- /dev/null +++ b/src/py_account.cc @@ -0,0 +1,235 @@ +/* + * 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 "pyinterp.h" +#include "pyutils.h" +#include "account.h" +#include "post.h" + +namespace ledger { + +using namespace boost::python; + +namespace { + + long accounts_len(account_t& account) + { + return account.accounts.size(); + } + + account_t& accounts_getitem(account_t& account, long i) + { + static long last_index = 0; + static account_t * last_account = NULL; + static accounts_map::iterator elem; + + long len = account.accounts.size(); + + if (labs(i) >= len) { + PyErr_SetString(PyExc_IndexError, _("Index out of range")); + throw_error_already_set(); + } + + if (&account == last_account && i == last_index + 1) { + last_index = i; + return *(*++elem).second; + } + + long x = i < 0 ? len + i : i; + elem = account.accounts.begin(); + while (--x >= 0) + elem++; + + last_account = &account; + last_index = i; + + return *(*elem).second; + } + + account_t * py_find_account_1(journal_t& journal, const string& name) + { + return journal.find_account(name); + } + + account_t * py_find_account_2(journal_t& journal, const string& name, + const bool auto_create) + { + return journal.find_account(name, auto_create); + } + + account_t::xdata_t& py_xdata(account_t& account) { + return account.xdata(); + } + + PyObject * py_account_unicode(account_t& account) { + return str_to_py_unicode(account.fullname()); + } + +} // unnamed namespace + +void export_account() +{ + scope().attr("ACCOUNT_EXT_SORT_CALC") = ACCOUNT_EXT_SORT_CALC; + scope().attr("ACCOUNT_EXT_HAS_NON_VIRTUALS") = ACCOUNT_EXT_HAS_NON_VIRTUALS; + scope().attr("ACCOUNT_EXT_HAS_UNB_VIRTUALS") = ACCOUNT_EXT_HAS_UNB_VIRTUALS; + scope().attr("ACCOUNT_EXT_AUTO_VIRTUALIZE") = ACCOUNT_EXT_AUTO_VIRTUALIZE; + scope().attr("ACCOUNT_EXT_VISITED") = ACCOUNT_EXT_VISITED; + scope().attr("ACCOUNT_EXT_MATCHING") = ACCOUNT_EXT_MATCHING; + scope().attr("ACCOUNT_EXT_TO_DISPLAY") = ACCOUNT_EXT_TO_DISPLAY; + scope().attr("ACCOUNT_EXT_DISPLAYED") = ACCOUNT_EXT_DISPLAYED; + + class_< account_t::xdata_t::details_t > ("AccountXDataDetails") + .def_readonly("total", &account_t::xdata_t::details_t::total) + .def_readonly("calculated", &account_t::xdata_t::details_t::calculated) + .def_readonly("gathered", &account_t::xdata_t::details_t::gathered) + + .def_readonly("posts_count", &account_t::xdata_t::details_t::posts_count) + .def_readonly("posts_virtuals_count", + &account_t::xdata_t::details_t::posts_virtuals_count) + .def_readonly("posts_cleared_count", + &account_t::xdata_t::details_t::posts_cleared_count) + .def_readonly("posts_last_7_count", + &account_t::xdata_t::details_t::posts_last_7_count) + .def_readonly("posts_last_30_count", + &account_t::xdata_t::details_t::posts_last_30_count) + .def_readonly("posts_this_month_count", + &account_t::xdata_t::details_t::posts_this_month_count) + + .def_readonly("earliest_post", + &account_t::xdata_t::details_t::earliest_post) + .def_readonly("earliest_cleared_post", + &account_t::xdata_t::details_t::earliest_cleared_post) + .def_readonly("latest_post", + &account_t::xdata_t::details_t::latest_post) + .def_readonly("latest_cleared_post", + &account_t::xdata_t::details_t::latest_cleared_post) + + .def_readonly("filenames", &account_t::xdata_t::details_t::filenames) + .def_readonly("accounts_referenced", + &account_t::xdata_t::details_t::accounts_referenced) + .def_readonly("payees_referenced", + &account_t::xdata_t::details_t::payees_referenced) + + .def(self += self) + + .def("update", &account_t::xdata_t::details_t::update) + ; + + class_< account_t::xdata_t > ("AccountXData") +#if 1 + .add_property("flags", + &supports_flags<uint_least16_t>::flags, + &supports_flags<uint_least16_t>::set_flags) + .def("has_flags", &supports_flags<uint_least16_t>::has_flags) + .def("clear_flags", &supports_flags<uint_least16_t>::clear_flags) + .def("add_flags", &supports_flags<uint_least16_t>::add_flags) + .def("drop_flags", &supports_flags<uint_least16_t>::drop_flags) +#endif + + .def_readonly("self_details", &account_t::xdata_t::self_details) + .def_readonly("family_details", &account_t::xdata_t::family_details) + .def_readonly("reported_posts", &account_t::xdata_t::reported_posts) + .def_readonly("sort_values", &account_t::xdata_t::sort_values) + ; + + scope().attr("ACCOUNT_NORMAL") = ACCOUNT_NORMAL; + scope().attr("ACCOUNT_KNOWN") = ACCOUNT_KNOWN; + scope().attr("ACCOUNT_TEMP") = ACCOUNT_TEMP; + + class_< account_t > ("Account") +#if 1 + .add_property("flags", + &supports_flags<>::flags, + &supports_flags<>::set_flags) + .def("has_flags", &supports_flags<>::has_flags) + .def("clear_flags", &supports_flags<>::clear_flags) + .def("add_flags", &supports_flags<>::add_flags) + .def("drop_flags", &supports_flags<>::drop_flags) +#endif + + .add_property("parent", + make_getter(&account_t::parent, + return_internal_reference<>())) + + .def_readwrite("name", &account_t::name) + .def_readwrite("note", &account_t::note) + .def_readonly("depth", &account_t::depth) + + .def("__str__", &account_t::fullname) + .def("__unicode__", py_account_unicode) + + .def("fullname", &account_t::fullname) + .def("partial_name", &account_t::partial_name) + + .def("add_account", &account_t::add_account) + .def("remove_account", &account_t::remove_account) + + .def("find_account", &account_t::find_account, + return_internal_reference<>()) + .def("find_account_re", &account_t::find_account, + return_internal_reference<>()) + + .def("add_post", &account_t::add_post) + .def("remove_post", &account_t::remove_post) + + .def("valid", &account_t::valid) + + .def("__len__", accounts_len) + .def("__getitem__", accounts_getitem, return_internal_reference<>()) + + .def("__iter__", range<return_internal_reference<> > + (&account_t::accounts_begin, &account_t::accounts_end)) + .def("accounts", range<return_internal_reference<> > + (&account_t::accounts_begin, &account_t::accounts_end)) + .def("posts", range<return_internal_reference<> > + (&account_t::posts_begin, &account_t::posts_end)) + + .def("has_xdata", &account_t::has_xdata) + .def("clear_xdata", &account_t::clear_xdata) + .def("xdata", py_xdata, + return_internal_reference<>()) + + .def("amount", &account_t::amount) + .def("total", &account_t::total) + + .def("self_details", &account_t::self_details, + return_internal_reference<>()) + .def("family_details", &account_t::family_details, + return_internal_reference<>()) + + .def("has_xflags", &account_t::has_xflags) + .def("children_with_flags", &account_t::children_with_flags) + ; +} + +} // namespace ledger diff --git a/src/py_amount.cc b/src/py_amount.cc new file mode 100644 index 00000000..8fb507a3 --- /dev/null +++ b/src/py_amount.cc @@ -0,0 +1,314 @@ +/* + * 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 "pyinterp.h" +#include "pyutils.h" +#include "pyfstream.h" +#include "commodity.h" +#include "annotate.h" +#include "pool.h" + +namespace ledger { + +using namespace boost::python; + +namespace { + + boost::optional<amount_t> py_value_0(const amount_t& amount) { + return amount.value(false, CURRENT_TIME()); + } + boost::optional<amount_t> py_value_1(const amount_t& amount, + commodity_t& in_terms_of) { + return amount.value(false, CURRENT_TIME(), in_terms_of); + } + boost::optional<amount_t> py_value_2(const amount_t& amount, + commodity_t& in_terms_of, + datetime_t& moment) { + return amount.value(false, moment, in_terms_of); + } + + void py_parse_2(amount_t& amount, object in, unsigned char flags) { + if (PyFile_Check(in.ptr())) { + pyifstream instr(reinterpret_cast<PyFileObject *>(in.ptr())); + amount.parse(instr, flags); + } else { + PyErr_SetString(PyExc_IOError, + _("Argument to amount.parse(file) is not a file object")); + } + } + void py_parse_1(amount_t& amount, object in) { + py_parse_2(amount, in, 0); + } + + void py_parse_str_1(amount_t& amount, const string& str) { + amount.parse(str); + } + void py_parse_str_2(amount_t& amount, const string& str, unsigned char flags) { + amount.parse(str, flags); + } + + void py_print(amount_t& amount, object out) { + if (PyFile_Check(out.ptr())) { + pyofstream outstr(reinterpret_cast<PyFileObject *>(out.ptr())); + amount.print(outstr); + } else { + PyErr_SetString(PyExc_IOError, + _("Argument to amount.print_(file) is not a file object")); + } + } + + annotation_t& py_amount_annotation(amount_t& amount) { + return amount.annotation(); + } + + amount_t py_strip_annotations_0(amount_t& amount) { + return amount.strip_annotations(keep_details_t()); + } + amount_t py_strip_annotations_1(amount_t& amount, const keep_details_t& keep) { + return amount.strip_annotations(keep); + } + + PyObject * py_amount_unicode(amount_t& amount) { + return str_to_py_unicode(amount.to_string()); + } + +} // unnamed namespace + +#define EXC_TRANSLATOR(type) \ + void exc_translate_ ## type(const type& err) { \ + PyErr_SetString(PyExc_ArithmeticError, err.what()); \ + } + +EXC_TRANSLATOR(amount_error) + +void export_amount() +{ + class_< amount_t > ("Amount") + .def("initialize", &amount_t::initialize) // only for the PyUnitTests + .staticmethod("initialize") + .def("shutdown", &amount_t::shutdown) + .staticmethod("shutdown") + + .add_static_property("is_initialized", + make_getter(&amount_t::is_initialized), + make_setter(&amount_t::is_initialized)) + .add_static_property("stream_fullstrings", + make_getter(&amount_t::stream_fullstrings), + make_setter(&amount_t::stream_fullstrings)) + + .def(init<long>()) + .def(init<std::string>()) + + .def("exact", &amount_t::exact, args("value"), + _("Construct an amount object whose display precision is always equal to its\n\ +internal precision.")) + .staticmethod("exact") + + .def(init<amount_t>()) + + .def("compare", &amount_t::compare, args("amount"), + _("Compare two amounts for equality, returning <0, 0 or >0.")) + + .def(self == self) + .def(self == long()) + .def(long() == self) + + .def(self != self) + .def(self != long()) + .def(long() != self) + + .def(! self) + + .def(self < self) + .def(self < long()) + .def(long() < self) + + .def(self <= self) + .def(self <= long()) + .def(long() <= self) + + .def(self > self) + .def(self > long()) + .def(long() > self) + + .def(self >= self) + .def(self >= long()) + .def(long() >= self) + + .def(self += self) + .def(self += long()) + + .def(self + self) + .def(self + long()) + .def(long() + self) + + .def(self -= self) + .def(self -= long()) + + .def(self - self) + .def(self - long()) + .def(long() - self) + + .def(self *= self) + .def(self *= long()) + + .def(self * self) + .def(self * long()) + .def(long() * self) + + .def(self /= self) + .def(self /= long()) + + .def(self / self) + .def(self / long()) + .def(long() / self) + + .add_property("precision", &amount_t::precision) + .add_property("display_precision", &amount_t::display_precision) + .add_property("keep_precision", + &amount_t::keep_precision, + &amount_t::set_keep_precision) + + .def("negated", &amount_t::negated) + .def("in_place_negate", &amount_t::in_place_negate, + return_internal_reference<>()) + .def(- self) + + .def("abs", &amount_t::abs) + .def("__abs__", &amount_t::abs) + + .def("inverted", &amount_t::inverted) + + .def("rounded", &amount_t::rounded) + .def("in_place_round", &amount_t::in_place_round, + return_internal_reference<>()) + + .def("truncated", &amount_t::truncated) + .def("in_place_truncate", &amount_t::in_place_truncate, + return_internal_reference<>()) + + .def("floored", &amount_t::floored) + .def("in_place_floor", &amount_t::in_place_floor, + return_internal_reference<>()) + + .def("unrounded", &amount_t::unrounded) + .def("in_place_unround", &amount_t::in_place_unround, + return_internal_reference<>()) + + .def("reduced", &amount_t::reduced) + .def("in_place_reduce", &amount_t::in_place_reduce, + return_internal_reference<>()) + + .def("unreduced", &amount_t::unreduced) + .def("in_place_unreduce", &amount_t::in_place_unreduce, + return_internal_reference<>()) + + .def("value", py_value_0) + .def("value", py_value_1, args("in_terms_of")) + .def("value", py_value_2, args("in_terms_of", "moment")) + + .def("price", &amount_t::price) + + .def("sign", &amount_t::sign) + .def("__nonzero__", &amount_t::is_nonzero) + .def("is_nonzero", &amount_t::is_nonzero) + .def("is_zero", &amount_t::is_zero) + .def("is_realzero", &amount_t::is_realzero) + .def("is_null", &amount_t::is_null) + + .def("to_double", &amount_t::to_double) + .def("__float__", &amount_t::to_double) + .def("to_long", &amount_t::to_long) + .def("__int__", &amount_t::to_long) + .def("fits_in_long", &amount_t::fits_in_long) + + .def("__str__", &amount_t::to_string) + .def("to_string", &amount_t::to_string) + .def("__unicode__", py_amount_unicode) + .def("to_fullstring", &amount_t::to_fullstring) + .def("__repr__", &amount_t::to_fullstring) + .def("quantity_string", &amount_t::quantity_string) + + .add_property("commodity", + make_function(&amount_t::commodity, + return_value_policy<reference_existing_object>()), + make_function(&amount_t::set_commodity, + with_custodian_and_ward<1, 2>())) + .def("has_commodity", &amount_t::has_commodity) + .def("clear_commodity", &amount_t::clear_commodity) + + .def("number", &amount_t::number) + + .def("annotate", &amount_t::annotate) + .def("has_annotation", &amount_t::has_annotation) + .add_property("annotation", + make_function(py_amount_annotation, + return_internal_reference<>())) + .def("strip_annotations", py_strip_annotations_0) + .def("strip_annotations", py_strip_annotations_1) + + .def("parse", py_parse_1) + .def("parse", py_parse_2) + .def("parse", py_parse_str_1) + .def("parse", py_parse_str_2) + + .def("parse_conversion", &amount_t::parse_conversion) + .staticmethod("parse_conversion") + + .def("valid", &amount_t::valid) + ; + + enum_< parse_flags_enum_t >("ParseFlags") + .value("Default", PARSE_DEFAULT) + .value("Partial", PARSE_PARTIAL) + .value("Single", PARSE_SINGLE) + .value("NoMigrate", PARSE_NO_MIGRATE) + .value("NoReduce", PARSE_NO_REDUCE) + .value("NoAssign", PARSE_NO_ASSIGN) + .value("NoDates", PARSE_NO_DATES) + .value("OpContext", PARSE_OP_CONTEXT) + .value("SoftFail", PARSE_SOFT_FAIL) + ; + + register_optional_to_python<amount_t>(); + + implicitly_convertible<long, amount_t>(); + implicitly_convertible<string, amount_t>(); + +#define EXC_TRANSLATE(type) \ + register_exception_translator<type>(&exc_translate_ ## type); + + EXC_TRANSLATE(amount_error); +} + +} // namespace ledger diff --git a/src/py_balance.cc b/src/py_balance.cc new file mode 100644 index 00000000..8c0c4c58 --- /dev/null +++ b/src/py_balance.cc @@ -0,0 +1,238 @@ +/* + * 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 "pyinterp.h" +#include "pyutils.h" +#include "pyfstream.h" +#include "commodity.h" +#include "annotate.h" +#include "balance.h" + +namespace ledger { + +using namespace boost::python; + +namespace { + + boost::optional<balance_t> py_value_0(const balance_t& balance) { + return balance.value(false, CURRENT_TIME()); + } + boost::optional<balance_t> py_value_1(const balance_t& balance, + commodity_t& in_terms_of) { + return balance.value(false, CURRENT_TIME(), in_terms_of); + } + boost::optional<balance_t> py_value_2(const balance_t& balance, + commodity_t& in_terms_of, + datetime_t& moment) { + return balance.value(false, moment, in_terms_of); + } + + boost::optional<amount_t> + py_commodity_amount_0(const balance_t& balance) { + return balance.commodity_amount(); + } + + boost::optional<amount_t> + py_commodity_amount_1(const balance_t& balance, + const boost::optional<const commodity_t&>& commodity) { + return balance.commodity_amount(commodity); + } + + void py_print(balance_t& balance, object out) { + if (PyFile_Check(out.ptr())) { + pyofstream outstr(reinterpret_cast<PyFileObject *>(out.ptr())); + balance.print(outstr); + } else { + PyErr_SetString(PyExc_IOError, + _("Argument to balance.print_(file) is not a file object")); + } + } + + long balance_len(balance_t& bal) { + return bal.amounts.size(); + } + + amount_t balance_getitem(balance_t& bal, long i) { + long len = bal.amounts.size(); + + if (labs(i) >= len) { + PyErr_SetString(PyExc_IndexError, _("Index out of range")); + throw_error_already_set(); + } + + long x = i < 0 ? len + i : i; + balance_t::amounts_map::iterator elem = bal.amounts.begin(); + while (--x >= 0) + elem++; + + return (*elem).second; + } + + balance_t py_strip_annotations_0(balance_t& balance) { + return balance.strip_annotations(keep_details_t()); + } + balance_t py_strip_annotations_1(balance_t& balance, const keep_details_t& keep) { + return balance.strip_annotations(keep); + } + + PyObject * py_balance_unicode(balance_t& balance) { + return str_to_py_unicode(balance.to_string()); + } + +} // unnamed namespace + +#define EXC_TRANSLATOR(type) \ + void exc_translate_ ## type(const type& err) { \ + PyErr_SetString(PyExc_ArithmeticError, err.what()); \ + } + +EXC_TRANSLATOR(balance_error) + +void export_balance() +{ + class_< balance_t > ("Balance") + .def(init<balance_t>()) + .def(init<amount_t>()) + .def(init<long>()) + .def(init<string>()) + + .def(self += self) + .def(self += other<amount_t>()) + .def(self += long()) + .def(self + self) + .def(self + other<amount_t>()) + .def(self + long()) + .def(self -= self) + .def(self -= other<amount_t>()) + .def(self -= long()) + .def(self - self) + .def(self - other<amount_t>()) + .def(self - long()) + .def(self *= other<amount_t>()) + .def(self *= long()) + .def(self * other<amount_t>()) + .def(self * long()) + .def(self /= other<amount_t>()) + .def(self /= long()) + .def(self / other<amount_t>()) + .def(self / long()) + .def(- self) + + .def(self == self) + .def(self == other<amount_t>()) + .def(self == long()) + .def(self != self) + .def(self != other<amount_t>()) + .def(self != long()) + .def(! self) + + .def("__str__", &balance_t::to_string) + .def("to_string", &balance_t::to_string) + .def("__unicode__", py_balance_unicode) + + .def("negated", &balance_t::negated) + .def("in_place_negate", &balance_t::in_place_negate, + return_internal_reference<>()) + .def(- self) + + .def("abs", &balance_t::abs) + .def("__abs__", &balance_t::abs) + + .def("__len__", balance_len) + .def("__getitem__", balance_getitem) + + .def("rounded", &balance_t::rounded) + .def("in_place_round", &balance_t::in_place_round, + return_internal_reference<>()) + + .def("truncated", &balance_t::truncated) + .def("in_place_truncate", &balance_t::in_place_truncate, + return_internal_reference<>()) + + .def("floored", &balance_t::floored) + .def("in_place_floor", &balance_t::in_place_floor, + return_internal_reference<>()) + + .def("unrounded", &balance_t::unrounded) + .def("in_place_unround", &balance_t::in_place_unround, + return_internal_reference<>()) + + .def("reduced", &balance_t::reduced) + .def("in_place_reduce", &balance_t::in_place_reduce, + return_internal_reference<>()) + + .def("unreduced", &balance_t::unreduced) + .def("in_place_unreduce", &balance_t::in_place_unreduce, + return_internal_reference<>()) + + .def("value", py_value_0) + .def("value", py_value_1, args("in_terms_of")) + .def("value", py_value_2, args("in_terms_of", "moment")) + + .def("price", &balance_t::price) + + .def("__nonzero__", &balance_t::is_nonzero) + .def("is_nonzero", &balance_t::is_nonzero) + .def("is_zero", &balance_t::is_zero) + .def("is_realzero", &balance_t::is_realzero) + + .def("is_empty", &balance_t::is_empty) + .def("single_amount", &balance_t::single_amount) + + .def("to_amount", &balance_t::to_amount) + + .def("commodity_count", &balance_t::commodity_count) + .def("commodity_amount", py_commodity_amount_0) + .def("commodity_amount", py_commodity_amount_1) + + .def("number", &balance_t::number) + + .def("strip_annotations", py_strip_annotations_0) + .def("strip_annotations", py_strip_annotations_1) + + .def("valid", &balance_t::valid) + ; + + register_optional_to_python<balance_t>(); + + implicitly_convertible<long, balance_t>(); + implicitly_convertible<string, balance_t>(); + implicitly_convertible<amount_t, balance_t>(); + +#define EXC_TRANSLATE(type) \ + register_exception_translator<type>(&exc_translate_ ## type); + + EXC_TRANSLATE(balance_error); +} + +} // namespace ledger diff --git a/src/py_commodity.cc b/src/py_commodity.cc new file mode 100644 index 00000000..984be5f0 --- /dev/null +++ b/src/py_commodity.cc @@ -0,0 +1,451 @@ +/* + * 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 "pyinterp.h" +#include "pyutils.h" +#include "commodity.h" +#include "annotate.h" +#include "pool.h" + +namespace ledger { + +using namespace boost::python; + +namespace { + + commodity_t * py_create_1(commodity_pool_t& pool, + const string& symbol) + { + return pool.create(symbol); + } + commodity_t * py_create_2(commodity_pool_t& pool, + const string& symbol, + const annotation_t& details) + { + return pool.create(symbol, details); + } + + commodity_t * py_find_or_create_1(commodity_pool_t& pool, + const string& symbol) + { + return pool.find_or_create(symbol); + } + commodity_t * py_find_or_create_2(commodity_pool_t& pool, + const string& symbol, + const annotation_t& details) + { + return pool.find_or_create(symbol, details); + } + + commodity_t * py_find_1(commodity_pool_t& pool, + const string& name) + { + return pool.find(name); + } + + commodity_t * py_find_2(commodity_pool_t& pool, + const string& symbol, + const annotation_t& details) + { + return pool.find(symbol, details); + } + + // Exchange one commodity for another, while recording the factored price. + + void py_exchange_2(commodity_pool_t& pool, + commodity_t& commodity, + const amount_t& per_unit_cost) + { + pool.exchange(commodity, per_unit_cost, CURRENT_TIME()); + } + void py_exchange_3(commodity_pool_t& pool, + commodity_t& commodity, + const amount_t& per_unit_cost, + const datetime_t& moment) + { + pool.exchange(commodity, per_unit_cost, moment); + } + + cost_breakdown_t py_exchange_5(commodity_pool_t& pool, + const amount_t& amount, + const amount_t& cost, + const bool is_per_unit, + const boost::optional<datetime_t>& moment, + const boost::optional<string>& tag) + { + return pool.exchange(amount, cost, is_per_unit, moment, tag); + } + + commodity_t * py_pool_getitem(commodity_pool_t& pool, const string& symbol) + { + commodity_pool_t::commodities_map::iterator i = + pool.commodities.find(symbol); + if (i == pool.commodities.end()) { + PyErr_SetString(PyExc_ValueError, + (string("Could not find commodity ") + symbol).c_str()); + throw boost::python::error_already_set(); + } + return (*i).second; + } + + python::list py_pool_keys(commodity_pool_t& pool) { + python::list keys; + BOOST_REVERSE_FOREACH + (const commodity_pool_t::commodities_map::value_type& pair, + pool.commodities) { + keys.insert(0, pair.first); + } + return keys; + } + + bool py_pool_contains(commodity_pool_t& pool, const string& symbol) { + return pool.commodities.find(symbol) != pool.commodities.end(); + } + + commodity_pool_t::commodities_map::iterator + py_pool_commodities_begin(commodity_pool_t& pool) { + return pool.commodities.begin(); + } + commodity_pool_t::commodities_map::iterator + py_pool_commodities_end(commodity_pool_t& pool) { + return pool.commodities.end(); + } + + typedef transform_iterator + <function<string(commodity_pool_t::commodities_map::value_type&)>, + commodity_pool_t::commodities_map::iterator> + commodities_map_firsts_iterator; + commodities_map_firsts_iterator + + py_pool_commodities_keys_begin(commodity_pool_t& pool) { + return make_transform_iterator + (pool.commodities.begin(), + bind(&commodity_pool_t::commodities_map::value_type::first, _1)); + } + commodities_map_firsts_iterator + py_pool_commodities_keys_end(commodity_pool_t& pool) { + return make_transform_iterator + (pool.commodities.end(), + bind(&commodity_pool_t::commodities_map::value_type::first, _1)); + } + + typedef transform_iterator + <function<commodity_t *(commodity_pool_t::commodities_map::value_type&)>, + commodity_pool_t::commodities_map::iterator> + commodities_map_seconds_iterator; + + commodities_map_seconds_iterator + py_pool_commodities_values_begin(commodity_pool_t& pool) { + return make_transform_iterator + (pool.commodities.begin(), + bind(&commodity_pool_t::commodities_map::value_type::second, _1)); + } + commodities_map_seconds_iterator + py_pool_commodities_values_end(commodity_pool_t& pool) { + return make_transform_iterator + (pool.commodities.end(), + bind(&commodity_pool_t::commodities_map::value_type::second, _1)); + } + + void py_add_price_2(commodity_t& commodity, + const datetime_t& date, const amount_t& price) { + commodity.add_price(date, price); + } + + void py_add_price_3(commodity_t& commodity, const datetime_t& date, + const amount_t& price, const bool reflexive) { + commodity.add_price(date, price, reflexive); + } + + bool py_keep_all_0(keep_details_t& details) { + return details.keep_all(); + } + bool py_keep_all_1(keep_details_t& details, const commodity_t& comm) { + return details.keep_all(comm); + } + + bool py_keep_any_0(keep_details_t& details) { + return details.keep_any(); + } + bool py_keep_any_1(keep_details_t& details, const commodity_t& comm) { + return details.keep_any(comm); + } + + commodity_t& py_commodity_referent(commodity_t& comm) { + return comm.referent(); + } + commodity_t& py_annotated_commodity_referent(annotated_commodity_t& comm) { + return comm.referent(); + } + + commodity_t& py_strip_annotations_0(commodity_t& comm) { + return comm.strip_annotations(keep_details_t()); + } + commodity_t& py_strip_annotations_1(commodity_t& comm, + const keep_details_t& keep) { + return comm.strip_annotations(keep); + } + + commodity_t& py_strip_ann_annotations_0(annotated_commodity_t& comm) { + return comm.strip_annotations(keep_details_t()); + } + commodity_t& py_strip_ann_annotations_1(annotated_commodity_t& comm, + const keep_details_t& keep) { + return comm.strip_annotations(keep); + } + + boost::optional<amount_t> py_price(annotation_t& ann) { + return ann.price; + } + boost::optional<amount_t> py_set_price(annotation_t& ann, + const boost::optional<amount_t>& price) { + return ann.price = price; + } + + PyObject * py_commodity_unicode(commodity_t& commodity) { + return str_to_py_unicode(commodity.symbol()); + } + +} // unnamed namespace + +void export_commodity() +{ + class_< commodity_pool_t, shared_ptr<commodity_pool_t>, + boost::noncopyable > ("CommodityPool", no_init) + .add_property("null_commodity", + make_getter(&commodity_pool_t::null_commodity, + return_internal_reference<>())) + .add_property("default_commodity", + make_getter(&commodity_pool_t::default_commodity, + return_internal_reference<>()), + make_setter(&commodity_pool_t::default_commodity, + with_custodian_and_ward<1, 2>())) + + .add_property("keep_base", + make_getter(&commodity_pool_t::keep_base), + make_setter(&commodity_pool_t::keep_base)) + .add_property("price_db", + make_getter(&commodity_pool_t::price_db), + make_setter(&commodity_pool_t::price_db)) + .add_property("quote_leeway", + make_getter(&commodity_pool_t::quote_leeway), + make_setter(&commodity_pool_t::quote_leeway)) + .add_property("get_quotes", + make_getter(&commodity_pool_t::get_quotes), + make_setter(&commodity_pool_t::get_quotes)) + .add_property("get_commodity_quote", + make_getter(&commodity_pool_t::get_commodity_quote), + make_setter(&commodity_pool_t::get_commodity_quote)) + + .def("make_qualified_name", &commodity_pool_t::make_qualified_name) + + .def("create", py_create_1, + return_value_policy<reference_existing_object>()) + .def("create", py_create_2, + return_value_policy<reference_existing_object>()) + + .def("find_or_create", py_find_or_create_1, + return_value_policy<reference_existing_object>()) + .def("find_or_create", py_find_or_create_2, + return_value_policy<reference_existing_object>()) + + .def("find", py_find_1, return_value_policy<reference_existing_object>()) + .def("find", py_find_2, return_value_policy<reference_existing_object>()) + + .def("exchange", py_exchange_2, with_custodian_and_ward<1, 2>()) + .def("exchange", py_exchange_3, with_custodian_and_ward<1, 2>()) + .def("exchange", py_exchange_5) + + .def("parse_price_directive", &commodity_pool_t::parse_price_directive) + .def("parse_price_expression", &commodity_pool_t::parse_price_expression, + return_value_policy<reference_existing_object>()) + + .def("__getitem__", py_pool_getitem, + return_value_policy<reference_existing_object>()) + .def("keys", py_pool_keys) + .def("has_key", py_pool_contains) + .def("__contains__", py_pool_contains) + .def("__iter__", range<return_value_policy<reference_existing_object> > + (py_pool_commodities_begin, py_pool_commodities_end)) + .def("iteritems", range<return_value_policy<reference_existing_object> > + (py_pool_commodities_begin, py_pool_commodities_end)) + .def("iterkeys", range<>(py_pool_commodities_keys_begin, + py_pool_commodities_keys_end)) + .def("itervalues", range<return_value_policy<reference_existing_object> > + (py_pool_commodities_values_begin, py_pool_commodities_values_end)) + ; + + map_value_type_converter<commodity_pool_t::commodities_map>(); + + scope().attr("commodities") = commodity_pool_t::current_pool; + + scope().attr("COMMODITY_STYLE_DEFAULTS") = COMMODITY_STYLE_DEFAULTS; + scope().attr("COMMODITY_STYLE_SUFFIXED") = COMMODITY_STYLE_SUFFIXED; + scope().attr("COMMODITY_STYLE_SEPARATED") = COMMODITY_STYLE_SEPARATED; + scope().attr("COMMODITY_STYLE_EUROPEAN") = COMMODITY_STYLE_EUROPEAN; + scope().attr("COMMODITY_STYLE_THOUSANDS") = COMMODITY_STYLE_THOUSANDS; + scope().attr("COMMODITY_NOMARKET") = COMMODITY_NOMARKET; + scope().attr("COMMODITY_BUILTIN") = COMMODITY_BUILTIN; + scope().attr("COMMODITY_WALKED") = COMMODITY_WALKED; + scope().attr("COMMODITY_KNOWN") = COMMODITY_KNOWN; + scope().attr("COMMODITY_PRIMARY") = COMMODITY_PRIMARY; + + class_< commodity_t, boost::noncopyable > ("Commodity", no_init) +#if 1 + .add_property("flags", + &supports_flags<uint_least16_t>::flags, + &supports_flags<uint_least16_t>::set_flags) + .def("has_flags", &delegates_flags<uint_least16_t>::has_flags) + .def("clear_flags", &delegates_flags<uint_least16_t>::clear_flags) + .def("add_flags", &delegates_flags<uint_least16_t>::add_flags) + .def("drop_flags", &delegates_flags<uint_least16_t>::drop_flags) +#endif + + .add_static_property("european_by_default", + make_getter(&commodity_t::european_by_default), + make_setter(&commodity_t::european_by_default)) + + .def("__str__", &commodity_t::symbol) + .def("__unicode__", py_commodity_unicode) + .def("__nonzero__", &commodity_t::operator bool) + + .def(self == self) + + .def("symbol_needs_quotes", &commodity_t::symbol_needs_quotes) + .staticmethod("symbol_needs_quotes") + + .add_property("referent", + make_function(py_commodity_referent, + return_value_policy<reference_existing_object>())) + + .def("has_annotation", &commodity_t::has_annotation) + .def("strip_annotations", py_strip_annotations_0, + return_value_policy<reference_existing_object>()) + .def("strip_annotations", py_strip_annotations_1, + return_value_policy<reference_existing_object>()) + .def("write_annotations", &commodity_t::write_annotations) + + .def("pool", &commodity_t::pool, + return_value_policy<reference_existing_object>()) + + .add_property("base_symbol", &commodity_t::base_symbol) + .add_property("symbol", &commodity_t::symbol) + .add_property("mapping_key", &commodity_t::mapping_key) + + .add_property("name", &commodity_t::name, &commodity_t::set_name) + .add_property("note", &commodity_t::note, &commodity_t::set_note) + .add_property("precision", &commodity_t::precision, + &commodity_t::set_precision) + .add_property("smaller", &commodity_t::smaller, &commodity_t::set_smaller) + .add_property("larger", &commodity_t::larger, &commodity_t::set_larger) + + .def("add_price", py_add_price_2) + .def("add_price", py_add_price_3) + .def("remove_price", &commodity_t::remove_price, + with_custodian_and_ward<1, 3>()) + .def("find_price", &commodity_t::find_price) + .def("check_for_updated_price", &commodity_t::check_for_updated_price) + + .def("valid", &commodity_t::valid) + ; + + class_< annotation_t > ("Annotation", no_init) +#if 1 + .add_property("flags", &supports_flags<>::flags, + &supports_flags<>::set_flags) + .def("has_flags", &supports_flags<>::has_flags) + .def("clear_flags", &supports_flags<>::clear_flags) + .def("add_flags", &supports_flags<>::add_flags) + .def("drop_flags", &supports_flags<>::drop_flags) +#endif + + .add_property("price", py_price, py_set_price) + .add_property("date", + make_getter(&annotation_t::date), + make_setter(&annotation_t::date)) + .add_property("tag", + make_getter(&annotation_t::tag), + make_setter(&annotation_t::tag)) + + .def("__nonzero__", &annotation_t::operator bool) + + .def(self == self) + + .def("valid", &annotation_t::valid) + ; + + class_< keep_details_t > ("KeepDetails") + .def(init<bool, bool, bool, bool>()) + + .add_property("keep_price", + make_getter(&keep_details_t::keep_price), + make_setter(&keep_details_t::keep_price)) + .add_property("keep_date", + make_getter(&keep_details_t::keep_date), + make_setter(&keep_details_t::keep_date)) + .add_property("keep_tag", + make_getter(&keep_details_t::keep_tag), + make_setter(&keep_details_t::keep_tag)) + .add_property("only_actuals", + make_getter(&keep_details_t::only_actuals), + make_setter(&keep_details_t::only_actuals)) + + .def("keep_all", py_keep_all_0) + .def("keep_all", py_keep_all_1) + .def("keep_any", py_keep_any_0) + .def("keep_any", py_keep_any_1) + ; + + class_< annotated_commodity_t, bases<commodity_t>, + annotated_commodity_t, boost::noncopyable > + ("AnnotatedCommodity", no_init) + .add_property("details", + make_getter(&annotated_commodity_t::details), + make_setter(&annotated_commodity_t::details)) + + .def(self == self) + .def(self == other<commodity_t>()) + + .add_property("referent", + make_function(py_annotated_commodity_referent, + return_value_policy<reference_existing_object>())) + + .def("strip_annotations", py_strip_ann_annotations_0, + return_value_policy<reference_existing_object>()) + .def("strip_annotations", py_strip_ann_annotations_1, + return_value_policy<reference_existing_object>()) + .def("write_annotations", &annotated_commodity_t::write_annotations) + ; +} + +} // namespace ledger diff --git a/src/py_expr.cc b/src/py_expr.cc new file mode 100644 index 00000000..cad2c0fa --- /dev/null +++ b/src/py_expr.cc @@ -0,0 +1,68 @@ +/* + * 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 "pyinterp.h" + +namespace ledger { + +using namespace boost::python; + +namespace { + value_t py_expr_call(expr_t& expr) + { + return expr.calc(); + } +} + +void export_expr() +{ + class_< expr_t > ("Expr") + .def(init<string>()) + + .def("__nonzero__", &expr_t::operator bool) + .def("text", &expr_t::text) + .def("set_text", &expr_t::set_text) + + //.def("parse", &expr_t::parse) + + .def("__call__", py_expr_call) + .def("compile", &expr_t::compile) + //.def("calc", &expr_t::calc) + + .def("is_constant", &expr_t::is_constant) + + //.def("constant_value", &expr_t::constant_value) + ; +} + +} // namespace ledger diff --git a/src/py_format.cc b/src/py_format.cc new file mode 100644 index 00000000..d4825b42 --- /dev/null +++ b/src/py_format.cc @@ -0,0 +1,64 @@ +/* + * 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 "pyinterp.h" + +namespace ledger { + +using namespace boost::python; + +#define EXC_TRANSLATOR(type) \ + void exc_translate_ ## type(const type& err) { \ + PyErr_SetString(PyExc_ArithmeticError, err.what()); \ + } + +//EXC_TRANSLATOR(format_error) + +void export_format() +{ +#if 0 + class_< format_t > ("Format") + ; +#endif + + //register_optional_to_python<amount_t>(); + + //implicitly_convertible<string, amount_t>(); + +#define EXC_TRANSLATE(type) \ + register_exception_translator<type>(&exc_translate_ ## type); + + //EXC_TRANSLATE(format_error); +} + +} // namespace ledger diff --git a/src/py_item.cc b/src/py_item.cc new file mode 100644 index 00000000..8a6e495a --- /dev/null +++ b/src/py_item.cc @@ -0,0 +1,166 @@ +/* + * 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 "pyinterp.h" +#include "scope.h" +#include "mask.h" +#include "item.h" + +namespace ledger { + +using namespace boost::python; + +namespace { + + bool py_has_tag_1s(item_t& item, const string& tag) { + return item.has_tag(tag); + } + bool py_has_tag_1m(item_t& item, const mask_t& tag_mask) { + return item.has_tag(tag_mask); + } + bool py_has_tag_2m(item_t& item, const mask_t& tag_mask, + const boost::optional<mask_t>& value_mask) { + return item.has_tag(tag_mask, value_mask); + } + + boost::optional<string> py_get_tag_1s(item_t& item, const string& tag) { + return item.get_tag(tag); + } + boost::optional<string> py_get_tag_1m(item_t& item, const mask_t& tag_mask) { + return item.get_tag(tag_mask); + } + boost::optional<string> py_get_tag_2m(item_t& item, const mask_t& tag_mask, + const boost::optional<mask_t>& value_mask) { + return item.get_tag(tag_mask, value_mask); + } + +} // unnamed namespace + +#define EXC_TRANSLATOR(type) \ + void exc_translate_ ## type(const type& err) { \ + PyErr_SetString(PyExc_ArithmeticError, err.what()); \ + } + +//EXC_TRANSLATOR(item_error) + +void export_item() +{ + class_< position_t > ("Position") + .add_property("pathname", + make_getter(&position_t::pathname), + make_setter(&position_t::pathname)) + .add_property("beg_pos", + make_getter(&position_t::beg_pos), + make_setter(&position_t::beg_pos)) + .add_property("beg_line", + make_getter(&position_t::beg_line), + make_setter(&position_t::beg_line)) + .add_property("end_pos", + make_getter(&position_t::end_pos), + make_setter(&position_t::end_pos)) + .add_property("end_line", + make_getter(&position_t::end_line), + make_setter(&position_t::end_line)) + ; + + scope().attr("ITEM_NORMAL") = ITEM_NORMAL; + scope().attr("ITEM_GENERATED") = ITEM_GENERATED; + scope().attr("ITEM_TEMP") = ITEM_TEMP; + + enum_< item_t::state_t > ("State") + .value("Uncleared", item_t::UNCLEARED) + .value("Cleared", item_t::CLEARED) + .value("Pending", item_t::PENDING) + ; + +#if 0 + class_< item_t, bases<scope_t> > ("JournalItem", init<uint_least8_t>()) +#else + class_< item_t > ("JournalItem", init<uint_least8_t>()) +#endif +#if 1 + .add_property("flags", &supports_flags<>::flags, + &supports_flags<>::set_flags) + .def("has_flags", &supports_flags<>::has_flags) + .def("clear_flags", &supports_flags<>::clear_flags) + .def("add_flags", &supports_flags<>::add_flags) + .def("drop_flags", &supports_flags<>::drop_flags) +#endif + + .add_property("note", + make_getter(&item_t::note), + make_setter(&item_t::note)) + .add_property("pos", + make_getter(&item_t::pos), + make_setter(&item_t::pos)) + .add_property("metadata", + make_getter(&item_t::metadata), + make_setter(&item_t::metadata)) + + .def("copy_details", &item_t::copy_details) + + .def(self == self) + .def(self != self) + + .def("has_tag", py_has_tag_1s) + .def("has_tag", py_has_tag_1m) + .def("has_tag", py_has_tag_2m) + .def("get_tag", py_get_tag_1s) + .def("get_tag", py_get_tag_1m) + .def("get_tag", py_get_tag_2m) + .def("tag", py_get_tag_1s) + .def("tag", py_get_tag_1m) + .def("tag", py_get_tag_2m) + + .def("set_tag", &item_t::set_tag) + + .def("parse_tags", &item_t::parse_tags) + .def("append_note", &item_t::append_note) + + .add_static_property("use_effective_date", + make_getter(&item_t::use_effective_date), + make_setter(&item_t::use_effective_date)) + + .add_property("date", &item_t::date, make_setter(&item_t::_date)) + .add_property("effective_date", &item_t::effective_date, + make_setter(&item_t::_date_eff)) + + .add_property("state", &item_t::state, &item_t::set_state) + + .def("lookup", &item_t::lookup) + + .def("valid", &item_t::valid) + ; +} + +} // namespace ledger diff --git a/src/py_journal.cc b/src/py_journal.cc new file mode 100644 index 00000000..5be9cbe1 --- /dev/null +++ b/src/py_journal.cc @@ -0,0 +1,321 @@ +/* + * 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 "pyinterp.h" +#include "pyutils.h" +#include "journal.h" +#include "xact.h" +#include "post.h" +#include "chain.h" +#include "filters.h" +#include "iterators.h" +#include "scope.h" +#include "report.h" + +namespace ledger { + +using namespace boost::python; + +namespace { + + account_t& py_account_master(journal_t& journal) { + return *journal.master; + } + + long xacts_len(journal_t& journal) + { + return journal.xacts.size(); + } + + xact_t& xacts_getitem(journal_t& journal, long i) + { + static long last_index = 0; + static journal_t * last_journal = NULL; + static xacts_list::iterator elem; + + long len = journal.xacts.size(); + + if (labs(i) >= len) { + PyErr_SetString(PyExc_IndexError, _("Index out of range")); + throw_error_already_set(); + } + + if (&journal == last_journal && i == last_index + 1) { + last_index = i; + return **++elem; + } + + long x = i < 0 ? len + i : i; + elem = journal.xacts.begin(); + while (--x >= 0) + elem++; + + last_journal = &journal; + last_index = i; + + return **elem; + } + + long accounts_len(account_t& account) + { + return account.accounts.size(); + } + + account_t& accounts_getitem(account_t& account, long i) + { + static long last_index = 0; + static account_t * last_account = NULL; + static accounts_map::iterator elem; + + long len = account.accounts.size(); + + if (labs(i) >= len) { + PyErr_SetString(PyExc_IndexError, _("Index out of range")); + throw_error_already_set(); + } + + if (&account == last_account && i == last_index + 1) { + last_index = i; + return *(*++elem).second; + } + + long x = i < 0 ? len + i : i; + elem = account.accounts.begin(); + while (--x >= 0) + elem++; + + last_account = &account; + last_index = i; + + return *(*elem).second; + } + + account_t * py_find_account_1(journal_t& journal, const string& name) + { + return journal.find_account(name); + } + + account_t * py_find_account_2(journal_t& journal, const string& name, + const bool auto_create) + { + return journal.find_account(name, auto_create); + } + + std::size_t py_read(journal_t& journal, const string& pathname) + { + return journal.read(pathname); + } + + struct collector_wrapper + { + journal_t& journal; + report_t report; + collect_posts * posts_collector; + post_handler_ptr chain; + + collector_wrapper(journal_t& _journal, report_t& base) + : journal(_journal), report(base), + posts_collector(new collect_posts) {} + ~collector_wrapper() { + journal.clear_xdata(); + } + + std::size_t length() const { + return posts_collector->length(); + } + + std::vector<post_t *>::iterator begin() { + return posts_collector->begin(); + } + std::vector<post_t *>::iterator end() { + return posts_collector->end(); + } + }; + + shared_ptr<collector_wrapper> + py_collect(journal_t& journal, const string& query) + { + if (journal.has_xdata()) { + PyErr_SetString(PyExc_RuntimeError, + _("Cannot have multiple journal collections open at once")); + throw_error_already_set(); + } + + report_t& current_report(downcast<report_t>(*scope_t::default_scope)); + shared_ptr<collector_wrapper> coll(new collector_wrapper(journal, + current_report)); + std::auto_ptr<journal_t> save_journal + (current_report.session.journal.release()); + current_report.session.journal.reset(&journal); + + try { + strings_list remaining = + process_arguments(split_arguments(query.c_str()), coll->report); + coll->report.normalize_options("register"); + + value_t args; + foreach (const string& arg, remaining) + args.push_back(string_value(arg)); + coll->report.parse_query_args(args, "@Journal.collect"); + + journal_posts_iterator walker(coll->journal); + coll->chain = + chain_post_handlers(coll->report, + post_handler_ptr(coll->posts_collector)); + pass_down_posts(coll->chain, walker); + } + catch (...) { + current_report.session.journal.release(); + current_report.session.journal.reset(save_journal.release()); + throw; + } + current_report.session.journal.release(); + current_report.session.journal.reset(save_journal.release()); + + return coll; + } + + post_t * posts_getitem(collector_wrapper& collector, long i) + { + post_t * post = collector.posts_collector->posts[i]; + std::cerr << typeid(post).name() << std::endl; + std::cerr << typeid(*post).name() << std::endl; + std::cerr << typeid(post->account).name() << std::endl; + std::cerr << typeid(*post->account).name() << std::endl; + return post; + } + +} // unnamed namespace + +void export_journal() +{ + class_< item_handler<post_t>, shared_ptr<item_handler<post_t> >, + boost::noncopyable >("PostHandler") + ; + + class_< collect_posts, bases<item_handler<post_t> >, + shared_ptr<collect_posts>, boost::noncopyable >("PostCollector") + .def("__len__", &collect_posts::length) + .def("__iter__", range<return_internal_reference<1, + with_custodian_and_ward_postcall<1, 0> > > + (&collect_posts::begin, &collect_posts::end)) + ; + + class_< collector_wrapper, shared_ptr<collector_wrapper>, + boost::noncopyable >("PostCollectorWrapper", no_init) + .def("__len__", &collector_wrapper::length) + .def("__getitem__", posts_getitem, return_internal_reference<1, + with_custodian_and_ward_postcall<0, 1> >()) + .def("__iter__", range<return_value_policy<reference_existing_object, + with_custodian_and_ward_postcall<0, 1> > > + (&collector_wrapper::begin, &collector_wrapper::end)) + ; + + class_< journal_t::fileinfo_t > ("FileInfo") + .def(init<path>()) + + .add_property("filename", + make_getter(&journal_t::fileinfo_t::filename), + make_setter(&journal_t::fileinfo_t::filename)) + .add_property("size", + make_getter(&journal_t::fileinfo_t::size), + make_setter(&journal_t::fileinfo_t::size)) + .add_property("modtime", + make_getter(&journal_t::fileinfo_t::modtime), + make_setter(&journal_t::fileinfo_t::modtime)) + .add_property("from_stream", + make_getter(&journal_t::fileinfo_t::from_stream), + make_setter(&journal_t::fileinfo_t::from_stream)) + ; + + class_< journal_t, boost::noncopyable > ("Journal") + .def(init<path>()) + .def(init<string>()) + + .add_property("master", + make_getter(&journal_t::master, + return_internal_reference<1, + with_custodian_and_ward_postcall<1, 0> >())) + .add_property("bucket", + make_getter(&journal_t::bucket, + return_internal_reference<1, + with_custodian_and_ward_postcall<1, 0> >()), + make_setter(&journal_t::bucket)) + .add_property("was_loaded", make_getter(&journal_t::was_loaded)) + + .def("add_account", &journal_t::add_account) + .def("remove_account", &journal_t::remove_account) + + .def("find_account", py_find_account_1, + return_internal_reference<1, + with_custodian_and_ward_postcall<0, 1> >()) + .def("find_account", py_find_account_2, + return_internal_reference<1, + with_custodian_and_ward_postcall<0, 1> >()) + .def("find_account_re", &journal_t::find_account_re, + return_internal_reference<1, + with_custodian_and_ward_postcall<0, 1> >()) + + .def("add_xact", &journal_t::add_xact) + .def("remove_xact", &journal_t::remove_xact) + + .def("__len__", xacts_len) +#if 0 + .def("__getitem__", xacts_getitem, + return_internal_reference<1, + with_custodian_and_ward_postcall<0, 1> >()) +#endif + + .def("__iter__", range<return_internal_reference<> > + (&journal_t::xacts_begin, &journal_t::xacts_end)) + .def("xacts", range<return_internal_reference<> > + (&journal_t::xacts_begin, &journal_t::xacts_end)) + .def("auto_xacts", range<return_internal_reference<> > + (&journal_t::auto_xacts_begin, &journal_t::auto_xacts_end)) + .def("period_xacts", range<return_internal_reference<> > + (&journal_t::period_xacts_begin, &journal_t::period_xacts_end)) + .def("sources", range<return_internal_reference<> > + (&journal_t::sources_begin, &journal_t::sources_end)) + + .def("read", py_read) + + .def("has_xdata", &journal_t::has_xdata) + .def("clear_xdata", &journal_t::clear_xdata) + + .def("collect", py_collect, with_custodian_and_ward_postcall<0, 1>()) + + .def("valid", &journal_t::valid) + ; +} + +} // namespace ledger diff --git a/src/py_post.cc b/src/py_post.cc new file mode 100644 index 00000000..20cdba6b --- /dev/null +++ b/src/py_post.cc @@ -0,0 +1,184 @@ +/* + * 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 "pyinterp.h" +#include "post.h" +#include "xact.h" + +namespace ledger { + +using namespace boost::python; + +namespace { + + bool py_has_tag_1s(post_t& post, const string& tag) { + return post.has_tag(tag); + } + bool py_has_tag_1m(post_t& post, const mask_t& tag_mask) { + return post.has_tag(tag_mask); + } + bool py_has_tag_2m(post_t& post, const mask_t& tag_mask, + const boost::optional<mask_t>& value_mask) { + return post.has_tag(tag_mask, value_mask); + } + + boost::optional<string> py_get_tag_1s(post_t& post, const string& tag) { + return post.get_tag(tag); + } + boost::optional<string> py_get_tag_1m(post_t& post, const mask_t& tag_mask) { + return post.get_tag(tag_mask); + } + boost::optional<string> py_get_tag_2m(post_t& post, const mask_t& tag_mask, + const boost::optional<mask_t>& value_mask) { + return post.get_tag(tag_mask, value_mask); + } + + post_t::xdata_t& py_xdata(post_t& post) { + return post.xdata(); + } + + account_t * py_reported_account(post_t& post) { + return post.reported_account(); + } + +} // unnamed namespace + +void export_post() +{ + scope().attr("POST_EXT_RECEIVED") = POST_EXT_RECEIVED; + scope().attr("POST_EXT_HANDLED") = POST_EXT_HANDLED; + scope().attr("POST_EXT_DISPLAYED") = POST_EXT_DISPLAYED; + scope().attr("POST_EXT_DIRECT_AMT") = POST_EXT_DIRECT_AMT; + scope().attr("POST_EXT_SORT_CALC") = POST_EXT_SORT_CALC; + scope().attr("POST_EXT_COMPOUND") = POST_EXT_COMPOUND; + scope().attr("POST_EXT_VISITED") = POST_EXT_VISITED; + scope().attr("POST_EXT_MATCHES") = POST_EXT_MATCHES; + scope().attr("POST_EXT_CONSIDERED") = POST_EXT_CONSIDERED; + + class_< post_t::xdata_t > ("PostingXData") +#if 1 + .add_property("flags", + &supports_flags<uint_least16_t>::flags, + &supports_flags<uint_least16_t>::set_flags) + .def("has_flags", &supports_flags<uint_least16_t>::has_flags) + .def("clear_flags", &supports_flags<uint_least16_t>::clear_flags) + .def("add_flags", &supports_flags<uint_least16_t>::add_flags) + .def("drop_flags", &supports_flags<uint_least16_t>::drop_flags) +#endif + + .add_property("visited_value", + make_getter(&post_t::xdata_t::visited_value), + make_setter(&post_t::xdata_t::visited_value)) + .add_property("compound_value", + make_getter(&post_t::xdata_t::compound_value), + make_setter(&post_t::xdata_t::compound_value)) + .add_property("total", + make_getter(&post_t::xdata_t::total), + make_setter(&post_t::xdata_t::total)) + .add_property("count", + make_getter(&post_t::xdata_t::count), + make_setter(&post_t::xdata_t::count)) + .add_property("date", + make_getter(&post_t::xdata_t::date), + make_setter(&post_t::xdata_t::date)) + .add_property("datetime", + make_getter(&post_t::xdata_t::datetime), + make_setter(&post_t::xdata_t::datetime)) + .add_property("account", + make_getter(&post_t::xdata_t::account, + return_value_policy<reference_existing_object>()), + make_setter(&post_t::xdata_t::account, + with_custodian_and_ward<1, 2>())) + .add_property("sort_values", + make_getter(&post_t::xdata_t::sort_values), + make_setter(&post_t::xdata_t::sort_values)) + ; + + scope().attr("POST_VIRTUAL") = POST_VIRTUAL; + scope().attr("POST_MUST_BALANCE") = POST_MUST_BALANCE; + scope().attr("POST_CALCULATED") = POST_CALCULATED; + scope().attr("POST_COST_CALCULATED") = POST_COST_CALCULATED; + + class_< post_t, bases<item_t> > ("Posting") + //.def(init<account_t *>()) + + .add_property("xact", + make_getter(&post_t::xact, + return_internal_reference<>()), + make_setter(&post_t::xact, + with_custodian_and_ward<1, 2>())) + .add_property("account", + make_getter(&post_t::account, + return_internal_reference<>()), + make_setter(&post_t::account, + with_custodian_and_ward<1, 2>())) + .add_property("amount", + make_getter(&post_t::amount), + make_setter(&post_t::amount)) + .add_property("cost", + make_getter(&post_t::cost), + make_setter(&post_t::cost)) + .add_property("assigned_amount", + make_getter(&post_t::assigned_amount), + make_setter(&post_t::assigned_amount)) + + .def("has_tag", py_has_tag_1s) + .def("has_tag", py_has_tag_1m) + .def("has_tag", py_has_tag_2m) + .def("get_tag", py_get_tag_1s) + .def("get_tag", py_get_tag_1m) + .def("get_tag", py_get_tag_2m) + + .def("date", &post_t::date) + .def("effective_date", &post_t::effective_date) + + .def("must_balance", &post_t::must_balance) + + .def("lookup", &post_t::lookup) + + .def("valid", &post_t::valid) + + .def("has_xdata", &post_t::has_xdata) + .def("clear_xdata", &post_t::clear_xdata) + .def("xdata", py_xdata, + return_internal_reference<>()) + + .def("add_to_value", &post_t::add_to_value) + .def("set_reported_account", &post_t::set_reported_account) + + .def("reported_account", py_reported_account, + return_internal_reference<>()) + ; +} + +} // namespace ledger diff --git a/src/py_times.cc b/src/py_times.cc new file mode 100644 index 00000000..a62bb9b6 --- /dev/null +++ b/src/py_times.cc @@ -0,0 +1,253 @@ +/* + * 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 "pyinterp.h" +#include "pyutils.h" +#include "times.h" + +// jww (2007-05-04): Convert time duration objects to PyDelta + +namespace ledger { + +using namespace boost::python; + +#define MY_PyDateTime_IMPORT \ + PyDateTimeAPI = (PyDateTime_CAPI*) \ + PyCObject_Import(const_cast<char *>("datetime"), \ + const_cast<char *>("datetime_CAPI")) + +struct date_to_python +{ + static PyObject* convert(const date_t& dte) + { + MY_PyDateTime_IMPORT; + return PyDate_FromDate(dte.year(), dte.month(), dte.day()); + } +}; + +struct date_from_python +{ + static void* convertible(PyObject* obj_ptr) + { + MY_PyDateTime_IMPORT; + if (PyDate_Check(obj_ptr)) return obj_ptr; + return 0; + } + + static void construct(PyObject * obj_ptr, + converter::rvalue_from_python_stage1_data * data) + { + MY_PyDateTime_IMPORT; + + int year = PyDateTime_GET_YEAR(obj_ptr); + date::year_type y = gregorian::greg_year(static_cast<unsigned short>(year)); + date::month_type m = + static_cast<date::month_type>(PyDateTime_GET_MONTH(obj_ptr)); + date::day_type d = + static_cast<date::day_type>(PyDateTime_GET_DAY(obj_ptr)); + + date_t * dte = new date_t(y, m, d); + + data->convertible = (void *) dte; + } +}; + +typedef register_python_conversion<date_t, date_to_python, date_from_python> + date_python_conversion; + + +struct datetime_to_python +{ + static PyObject* convert(const datetime_t& moment) + { + MY_PyDateTime_IMPORT; + + date_t dte = moment.date(); + datetime_t::time_duration_type tod = moment.time_of_day(); + + return PyDateTime_FromDateAndTime + (static_cast<int>(dte.year()), static_cast<int>(dte.month()), + static_cast<int>(dte.day()), static_cast<int>(tod.hours()), + static_cast<int>(tod.minutes()), static_cast<int>(tod.seconds()), + static_cast<int>(tod.total_microseconds() % 1000000)); + } +}; + +struct datetime_from_python +{ + static void* convertible(PyObject* obj_ptr) + { + MY_PyDateTime_IMPORT; + if(PyDateTime_Check(obj_ptr)) return obj_ptr; + return 0; + } + + static void construct(PyObject * obj_ptr, + converter::rvalue_from_python_stage1_data * data) + { + MY_PyDateTime_IMPORT; + + int year = PyDateTime_GET_YEAR(obj_ptr); + date::year_type y = gregorian::greg_year(static_cast<unsigned short>(year)); + date::month_type m = + static_cast<date::month_type>(PyDateTime_GET_MONTH(obj_ptr)); + date::day_type d = + static_cast<date::day_type>(PyDateTime_GET_DAY(obj_ptr)); + + datetime_t::time_duration_type::hour_type h = + static_cast<datetime_t::time_duration_type::hour_type> + (PyDateTime_DATE_GET_HOUR(obj_ptr)); + datetime_t::time_duration_type::min_type min = + static_cast<datetime_t::time_duration_type::min_type> + (PyDateTime_DATE_GET_MINUTE(obj_ptr)); + datetime_t::time_duration_type::sec_type s = + static_cast<datetime_t::time_duration_type::sec_type> + (PyDateTime_DATE_GET_SECOND(obj_ptr)); + datetime_t::time_duration_type::fractional_seconds_type ms = + static_cast<datetime_t::time_duration_type::fractional_seconds_type> + (PyDateTime_DATE_GET_MICROSECOND(obj_ptr)) * 1000000; + + datetime_t * moment + = new datetime_t(date_t(y, m, d), + datetime_t::time_duration_type(h, min, s, ms)); + + data->convertible = (void *) moment; + } +}; + +typedef register_python_conversion<datetime_t, + datetime_to_python, datetime_from_python> + datetime_python_conversion; + + +/* Convert time_duration to/from python */ +struct duration_to_python +{ + static int get_usecs(boost::posix_time::time_duration const& d) + { + static int64_t resolution = + boost::posix_time::time_duration::ticks_per_second(); + int64_t fracsecs = d.fractional_seconds(); + if (resolution > 1000000) + return static_cast<int>(fracsecs / (resolution / 1000000)); + else + return static_cast<int>(fracsecs * (1000000 / resolution)); + } + + static PyObject * convert(posix_time::time_duration d) + { + int days = d.hours() / 24; + if (days < 0) + days --; + int seconds = d.total_seconds() - days*(24*3600); + int usecs = get_usecs(d); + if (days < 0) + usecs = 1000000-1 - usecs; + return PyDelta_FromDSU(days, seconds, usecs); + } +}; + +/* Should support the negative values, but not the special boost time + durations */ +struct duration_from_python +{ + static void* convertible(PyObject * obj_ptr) + { + if ( ! PyDelta_Check(obj_ptr)) + return 0; + return obj_ptr; + } + + static void construct(PyObject* obj_ptr, + python::converter::rvalue_from_python_stage1_data * data) + { + PyDateTime_Delta const* pydelta + = reinterpret_cast<PyDateTime_Delta*>(obj_ptr); + + int days = pydelta->days; + bool is_negative = (days < 0); + if (is_negative) + days = -days; + + // Create time duration object + posix_time::time_duration + duration = (posix_time::hours(24) * days + + posix_time::seconds(pydelta->seconds) + + posix_time::microseconds(pydelta->microseconds)); + if (is_negative) + duration = duration.invert_sign(); + + void * storage = + reinterpret_cast<converter::rvalue_from_python_storage + <posix_time::time_duration> *> + (data)->storage.bytes; + + new (storage) posix_time::time_duration(duration); + data->convertible = storage; + } +}; + +typedef register_python_conversion<time_duration_t, + duration_to_python, duration_from_python> + duration_python_conversion; + + +datetime_t py_parse_datetime(const string& str) { + return parse_datetime(str); +} + +date_t py_parse_date(const string& str) { + return parse_date(str); +} + +void export_times() +{ + datetime_python_conversion(); + date_python_conversion(); + duration_python_conversion(); + + register_optional_to_python<datetime_t>(); + register_optional_to_python<date_t>(); + + scope().attr("parse_datetime") = &py_parse_datetime; + scope().attr("parse_date") = &py_parse_date; + scope().attr("times_initialize") = ×_initialize; + scope().attr("times_shutdown") = ×_shutdown; + +#if 0 + class_< interval_t > ("Interval") + ; +#endif +} + +} // namespace ledger diff --git a/src/py_utils.cc b/src/py_utils.cc new file mode 100644 index 00000000..5203599f --- /dev/null +++ b/src/py_utils.cc @@ -0,0 +1,254 @@ +/* + * 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 "pyinterp.h" +#include "pyutils.h" +#include "pyfstream.h" + +namespace ledger { + +using namespace boost::python; + +struct bool_to_python +{ + static PyObject * convert(const bool truth) + { + if (truth) + Py_RETURN_TRUE; + else + Py_RETURN_FALSE; + } +}; + +struct bool_from_python +{ + static void* convertible(PyObject* obj_ptr) + { + if (!PyBool_Check(obj_ptr)) return 0; + return obj_ptr; + } + + static void construct(PyObject* obj_ptr, + converter::rvalue_from_python_stage1_data* data) + { + void * storage = + ((converter::rvalue_from_python_storage<bool>*) data)->storage.bytes; + if (obj_ptr == Py_True) + new (storage) bool(true); + else + new (storage) bool(false); + data->convertible = storage; + } +}; + +typedef register_python_conversion<bool, bool_to_python, bool_from_python> + bool_python_conversion; + + +struct string_to_python +{ + static PyObject* convert(const string& str) + { + // Return bytes, not characters; see __unicode__ methods for that + return incref(object(static_cast<const std::string&>(str)).ptr()); + } +}; + +struct string_from_python +{ + static void* convertible(PyObject* obj_ptr) + { + if (!PyUnicode_Check(obj_ptr) && + !PyString_Check(obj_ptr)) return 0; + return obj_ptr; + } + + static void construct(PyObject* obj_ptr, + converter::rvalue_from_python_stage1_data* data) + { + if (PyString_Check(obj_ptr)) { + const char* value = PyString_AsString(obj_ptr); + if (value == 0) throw_error_already_set(); + void* storage = + reinterpret_cast<converter::rvalue_from_python_storage<string> *> + (data)->storage.bytes; + new (storage) string(value); + data->convertible = storage; + } else { + VERIFY(PyUnicode_Check(obj_ptr)); + + Py_ssize_t size = PyUnicode_GET_SIZE(obj_ptr); + const Py_UNICODE* value = PyUnicode_AS_UNICODE(obj_ptr); + + string str; + if (sizeof(Py_UNICODE) == 2) // UTF-16 + utf8::unchecked::utf16to8(value, value + size, std::back_inserter(str)); + else if (sizeof(Py_UNICODE) == 4) // UTF-32 + utf8::unchecked::utf32to8(value, value + size, std::back_inserter(str)); + else + assert(! "Py_UNICODE has an unexpected size"); + + if (value == 0) throw_error_already_set(); + void* storage = + reinterpret_cast<converter::rvalue_from_python_storage<string> *> + (data)->storage.bytes; + new (storage) string(str); + data->convertible = storage; + } + } +}; + +typedef register_python_conversion<string, string_to_python, string_from_python> + string_python_conversion; + + +struct istream_to_python +{ + static PyObject* convert(const std::istream&) + { + return incref(boost::python::detail::none()); + } +}; + +struct istream_from_python +{ + static void* convertible(PyObject* obj_ptr) + { + if (!PyFile_Check(obj_ptr)) return 0; + return obj_ptr; + } + + static void construct(PyObject* obj_ptr, + converter::rvalue_from_python_stage1_data* data) + { + void* storage = + reinterpret_cast<converter::rvalue_from_python_storage<pyifstream> *> + (data)->storage.bytes; + new (storage) pyifstream(reinterpret_cast<PyFileObject *>(obj_ptr)); + data->convertible = storage; + } +}; + +typedef register_python_conversion<std::istream, + istream_to_python, istream_from_python> + istream_python_conversion; + + +struct ostream_to_python +{ + static PyObject* convert(const std::ostream&) + { + return incref(boost::python::detail::none()); + } +}; + +struct ostream_from_python +{ + static void* convertible(PyObject* obj_ptr) + { + if (!PyFile_Check(obj_ptr)) return 0; + return obj_ptr; + } + + static void construct(PyObject* obj_ptr, + converter::rvalue_from_python_stage1_data* data) + { + void* storage = + reinterpret_cast<converter::rvalue_from_python_storage<pyofstream> *> + (data)->storage.bytes; + new (storage) pyofstream(reinterpret_cast<PyFileObject *>(obj_ptr)); + data->convertible = storage; + } +}; + +typedef register_python_conversion<std::ostream, + ostream_to_python, ostream_from_python> + ostream_python_conversion; + + +void export_utils() +{ + class_< supports_flags<uint_least8_t> > ("SupportFlags8") + .def(init<supports_flags<uint_least8_t> >()) + .def(init<uint_least8_t>()) + + .add_property("flags", + &supports_flags<uint_least8_t>::flags, + &supports_flags<uint_least8_t>::set_flags) + .def("has_flags", &supports_flags<uint_least8_t>::has_flags) + .def("clear_flags", &supports_flags<uint_least8_t>::clear_flags) + .def("add_flags", &supports_flags<uint_least8_t>::add_flags) + .def("drop_flags", &supports_flags<uint_least8_t>::drop_flags) + ; + + class_< supports_flags<uint_least16_t> > ("SupportFlags16") + .def(init<supports_flags<uint_least16_t> >()) + .def(init<uint_least16_t>()) + + .add_property("flags", + &supports_flags<uint_least16_t>::flags, + &supports_flags<uint_least16_t>::set_flags) + .def("has_flags", &supports_flags<uint_least16_t>::has_flags) + .def("clear_flags", &supports_flags<uint_least16_t>::clear_flags) + .def("add_flags", &supports_flags<uint_least16_t>::add_flags) + .def("drop_flags", &supports_flags<uint_least16_t>::drop_flags) + ; + +#if 0 + class_< basic_flags_t<uint_least8_t>, + bases<supports_flags<uint_least8_t> > > ("BasicFlags8") + .def(init<uint_least8_t>()) + + .def("plus_flags", &basic_flags_t<uint_least8_t>::plus_flags) + .def("minus_flags", &basic_flags_t<uint_least8_t>::minus_flags) + ; +#endif + + class_< delegates_flags<uint_least16_t>, + boost::noncopyable > ("DelegatesFlags16", no_init) + .add_property("flags", + &delegates_flags<uint_least16_t>::flags, + &delegates_flags<uint_least16_t>::set_flags) + .def("has_flags", &delegates_flags<uint_least16_t>::has_flags) + .def("clear_flags", &delegates_flags<uint_least16_t>::clear_flags) + .def("add_flags", &delegates_flags<uint_least16_t>::add_flags) + .def("drop_flags", &delegates_flags<uint_least16_t>::drop_flags) + ; + + bool_python_conversion(); + string_python_conversion(); + istream_python_conversion(); + ostream_python_conversion(); +} + +} // namespace ledger diff --git a/src/py_value.cc b/src/py_value.cc new file mode 100644 index 00000000..713dc3d4 --- /dev/null +++ b/src/py_value.cc @@ -0,0 +1,380 @@ +/* + * 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 "pyinterp.h" +#include "pyutils.h" +#include "commodity.h" +#include "annotate.h" + +namespace ledger { + +using namespace boost::python; + +BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(value_overloads, value, 0, 2) +BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(exchange_commodities_overloads, + exchange_commodities, 1, 2) +BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(set_string_overloads, set_string, 0, 2) + +namespace { + + boost::optional<value_t> py_value_0(const value_t& value) { + return value.value(false, CURRENT_TIME()); + } + boost::optional<value_t> py_value_1(const value_t& value, + commodity_t& in_terms_of) { + return value.value(false, CURRENT_TIME(), in_terms_of); + } + boost::optional<value_t> py_value_2(const value_t& value, + commodity_t& in_terms_of, + datetime_t& moment) { + return value.value(false, moment, in_terms_of); + } + + PyObject * py_base_type(value_t& value) + { + if (value.is_boolean()) { + return (PyObject *)&PyBool_Type; + } + else if (value.is_long()) { + return (PyObject *)&PyInt_Type; + } + else if (value.is_string()) { + return (PyObject *)&PyUnicode_Type; + } + else { + object typeobj(object(value).attr("__class__")); + return typeobj.ptr(); + } + } + + string py_dump(const value_t& value) { + std::ostringstream buf; + value.dump(buf); + return buf.str(); + } + + string py_dump_relaxed(const value_t& value) { + std::ostringstream buf; + value.dump(buf, true); + return buf.str(); + } + + void py_set_string(value_t& value, const string& str) { + return value.set_string(str); + } + + annotation_t& py_value_annotation(value_t& value) { + return value.annotation(); + } + + value_t py_strip_annotations_0(value_t& value) { + return value.strip_annotations(keep_details_t()); + } + value_t py_strip_annotations_1(value_t& value, const keep_details_t& keep) { + return value.strip_annotations(keep); + } + + PyObject * py_value_unicode(value_t& value) { + return str_to_py_unicode(value.to_string()); + } + +} // unnamed namespace + +#define EXC_TRANSLATOR(type) \ + void exc_translate_ ## type(const type& err) { \ + PyErr_SetString(PyExc_ArithmeticError, err.what()); \ + } + +EXC_TRANSLATOR(value_error) + +void export_value() +{ + enum_< value_t::type_t >("ValueType") + .value("Void", value_t::VOID) + .value("Boolean", value_t::BOOLEAN) + .value("DateTime", value_t::DATETIME) + .value("Date", value_t::DATE) + .value("Integer", value_t::INTEGER) + .value("Amount", value_t::AMOUNT) + .value("Balance", value_t::BALANCE) + .value("String", value_t::STRING) + .value("Sequence", value_t::SEQUENCE) + .value("Scope", value_t::SCOPE) + ; + + class_< value_t > ("Value") + .def("initialize", &value_t::initialize) + .staticmethod("initialize") + .def("shutdown", &value_t::shutdown) + .staticmethod("shutdown") + + .def(init<bool>()) + .def(init<datetime_t>()) + .def(init<date_t>()) + .def(init<long>()) + .def(init<double>()) + .def(init<amount_t>()) + .def(init<balance_t>()) + .def(init<mask_t>()) + .def(init<std::string>()) + // jww (2009-11-02): Need to support conversion eof value_t::sequence_t + //.def(init<value_t::sequence_t>()) + .def(init<value_t>()) + + .def("is_equal_to", &value_t::is_equal_to) + .def("is_less_than", &value_t::is_less_than) + .def("is_greater_than", &value_t::is_greater_than) + + .def(self == self) + .def(self == long()) + .def(long() == self) + .def(self == other<amount_t>()) + .def(other<amount_t>() == self) + .def(self == other<balance_t>()) + .def(other<balance_t>() == self) + + .def(self != self) + .def(self != long()) + .def(long() != self) + .def(self != other<amount_t>()) + .def(other<amount_t>() != self) + .def(self != other<balance_t>()) + .def(other<balance_t>() != self) + + .def(! self) + + .def(self < self) + .def(self < long()) + .def(long() < self) + .def(self < other<amount_t>()) + .def(other<amount_t>() < self) + + .def(self <= self) + .def(self <= long()) + .def(long() <= self) + .def(self <= other<amount_t>()) + .def(other<amount_t>() <= self) + + .def(self > self) + .def(self > long()) + .def(long() > self) + .def(self > other<amount_t>()) + .def(other<amount_t>() > self) + + .def(self >= self) + .def(self >= long()) + .def(long() >= self) + .def(self >= other<amount_t>()) + .def(other<amount_t>() >= self) + + .def(self += self) + .def(self += long()) + .def(self += other<amount_t>()) + .def(self += other<balance_t>()) + + .def(self + self) + .def(self + long()) + .def(long() + self) + .def(self + other<amount_t>()) + .def(other<amount_t>() + self) + .def(self + other<balance_t>()) + + .def(self -= self) + .def(self -= long()) + .def(self -= other<amount_t>()) + .def(self -= other<balance_t>()) + + .def(self - self) + .def(self - long()) + .def(long() - self) + .def(self - other<amount_t>()) + .def(other<amount_t>() - self) + .def(self - other<balance_t>()) + + .def(self *= self) + .def(self *= long()) + .def(self *= other<amount_t>()) + + .def(self * self) + .def(self * long()) + .def(long() * self) + .def(self * other<amount_t>()) + .def(other<amount_t>() * self) + + .def(self /= self) + .def(self /= long()) + .def(self /= other<amount_t>()) + + .def(self / self) + .def(self / long()) + .def(long() / self) + .def(self / other<amount_t>()) + .def(other<amount_t>() / self) + + .def("negated", &value_t::negated) + .def("in_place_negate", &value_t::in_place_negate) + .def("in_place_not", &value_t::in_place_not) + .def(- self) + + .def("abs", &value_t::abs) + .def("__abs__", &value_t::abs) + + .def("rounded", &value_t::rounded) + .def("in_place_round", &value_t::in_place_round) + .def("truncated", &value_t::truncated) + .def("in_place_truncate", &value_t::in_place_truncate) + .def("floored", &value_t::floored) + .def("in_place_floor", &value_t::in_place_floor) + .def("unrounded", &value_t::unrounded) + .def("in_place_unround", &value_t::in_place_unround) + .def("reduced", &value_t::reduced) + .def("in_place_reduce", &value_t::in_place_reduce) + .def("unreduced", &value_t::unreduced) + .def("in_place_unreduce", &value_t::in_place_unreduce) + + .def("value", py_value_0) + .def("value", py_value_1, args("in_terms_of")) + .def("value", py_value_2, args("in_terms_of", "moment")) + + .def("value", &value_t::value, value_overloads()) + .def("price", &value_t::price) + .def("exchange_commodities", &value_t::exchange_commodities, + exchange_commodities_overloads()) + + .def("__nonzero__", &value_t::is_nonzero) + .def("is_nonzero", &value_t::is_nonzero) + .def("is_realzero", &value_t::is_realzero) + .def("is_zero", &value_t::is_zero) + .def("is_null", &value_t::is_null) + + .def("type", &value_t::type) + .def("is_type", &value_t::is_type) + + .def("is_boolean", &value_t::is_boolean) + .def("set_boolean", &value_t::set_boolean) + + .def("is_datetime", &value_t::is_datetime) + .def("set_datetime", &value_t::set_datetime) + + .def("is_date", &value_t::is_date) + .def("set_date", &value_t::set_date) + + .def("is_long", &value_t::is_long) + .def("set_long", &value_t::set_long) + + .def("is_amount", &value_t::is_amount) + .def("is_amount", &value_t::is_amount) + + .def("is_balance", &value_t::is_balance) + .def("is_balance", &value_t::is_balance) + + .def("is_string", &value_t::is_string) + .def("set_string", py_set_string) + + .def("is_mask", &value_t::is_mask) + .def("is_mask", &value_t::is_mask) + + .def("is_sequence", &value_t::is_sequence) + .def("set_sequence", &value_t::set_sequence) + + .def("to_boolean", &value_t::to_boolean) + .def("to_long", &value_t::to_long) + .def("__int__", &value_t::to_long) + .def("to_datetime", &value_t::to_datetime) + .def("to_date", &value_t::to_date) + .def("to_amount", &value_t::to_amount) + .def("to_balance", &value_t::to_balance) + .def("__str__", &value_t::to_string) + .def("__unicode__", py_value_unicode) + .def("to_string", &value_t::to_string) + .def("to_mask", &value_t::to_mask) + .def("to_sequence", &value_t::to_sequence) + + .def("__unicode__", py_dump_relaxed) + .def("__repr__", py_dump) + + .def("casted", &value_t::casted) + .def("in_place_cast", &value_t::in_place_cast) + .def("simplified", &value_t::simplified) + .def("in_place_simplify", &value_t::in_place_simplify) + + .def("number", &value_t::number) + + .def("annotate", &value_t::annotate) + .def("has_annotation", &value_t::has_annotation) + .add_property("annotation", + make_function(py_value_annotation, + return_internal_reference<>())) + .def("strip_annotations", py_strip_annotations_0) + .def("strip_annotations", py_strip_annotations_1) + +#if 0 + .def("__getitem__", &value_t::operator[]) +#endif + .def("push_back", &value_t::push_back) + .def("pop_back", &value_t::pop_back) + .def("size", &value_t::size) + + .def("label", &value_t::label) + + .def("valid", &value_t::valid) + + .def("basetype", py_base_type) + ; + + class_< value_t::sequence_t > ("ValueSequence") + .def(vector_indexing_suite< value_t::sequence_t >()); + ; + + scope().attr("NULL_VALUE") = NULL_VALUE; + scope().attr("string_value") = &string_value; + scope().attr("mask_value") = &mask_value; + scope().attr("value_context") = &value_context; + + register_optional_to_python<value_t>(); + + implicitly_convertible<long, value_t>(); + implicitly_convertible<string, value_t>(); + implicitly_convertible<amount_t, value_t>(); + implicitly_convertible<balance_t, value_t>(); + implicitly_convertible<mask_t, value_t>(); + implicitly_convertible<date_t, value_t>(); + implicitly_convertible<datetime_t, value_t>(); + +#define EXC_TRANSLATE(type) \ + register_exception_translator<type>(&exc_translate_ ## type); + + EXC_TRANSLATE(value_error); +} + +} // namespace ledger diff --git a/src/py_xact.cc b/src/py_xact.cc new file mode 100644 index 00000000..59c599d9 --- /dev/null +++ b/src/py_xact.cc @@ -0,0 +1,153 @@ +/* + * 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 "pyinterp.h" +#include "pyutils.h" +#include "xact.h" +#include "post.h" + +namespace ledger { + +using namespace boost::python; + +namespace { + + long posts_len(xact_base_t& xact) + { + return xact.posts.size(); + } + + post_t& posts_getitem(xact_base_t& xact, long i) + { + static long last_index = 0; + static xact_base_t * last_xact = NULL; + static posts_list::iterator elem; + + long len = xact.posts.size(); + + if (labs(i) >= len) { + PyErr_SetString(PyExc_IndexError, _("Index out of range")); + throw_error_already_set(); + } + + if (&xact == last_xact && i == last_index + 1) { + last_index = i; + return **++elem; + } + + long x = i < 0 ? len + i : i; + elem = xact.posts.begin(); + while (--x >= 0) + elem++; + + last_xact = &xact; + last_index = i; + + return **elem; + } + +} // unnamed namespace + +using namespace boost::python; + +void export_xact() +{ + class_< xact_base_t, bases<item_t> > ("TransactionBase") + .add_property("journal", + make_getter(&xact_base_t::journal, + return_internal_reference<>()), + make_setter(&xact_base_t::journal, + with_custodian_and_ward<1, 2>())) + + .def("__len__", posts_len) + .def("__getitem__", posts_getitem, + return_internal_reference<>()) + + .def("add_post", &xact_base_t::add_post, with_custodian_and_ward<1, 2>()) + .def("remove_post", &xact_base_t::add_post) + + .def("finalize", &xact_base_t::finalize) + + .def("__iter__", range<return_internal_reference<> > + (&xact_t::posts_begin, &xact_t::posts_end)) + .def("posts", range<return_internal_reference<> > + (&xact_t::posts_begin, &xact_t::posts_end)) + + .def("valid", &xact_base_t::valid) + ; + + class_< xact_t, bases<xact_base_t> > ("Transaction") + .add_property("code", + make_getter(&xact_t::code), + make_setter(&xact_t::code)) + .add_property("payee", + make_getter(&xact_t::payee), + make_setter(&xact_t::payee)) + + .def("add_post", &xact_t::add_post, with_custodian_and_ward<1, 2>()) + + .def("magnitude", &xact_t::magnitude) + .def("idstring", &xact_t::idstring) + .def("id", &xact_t::id) + + .def("lookup", &xact_t::lookup) + + .def("has_xdata", &xact_t::has_xdata) + .def("clear_xdata", &xact_t::clear_xdata) + + .def("valid", &xact_t::valid) + ; + + class_< auto_xact_t, bases<xact_base_t> > ("AutomatedTransaction") + .def(init<predicate_t>()) + + .add_property("predicate", + make_getter(&auto_xact_t::predicate), + make_setter(&auto_xact_t::predicate)) + + .def("extend_xact", &auto_xact_t::extend_xact) + ; + + class_< period_xact_t, bases<xact_base_t> > ("PeriodicTransaction") + .def(init<string>()) + + .add_property("period", + make_getter(&period_xact_t::period), + make_setter(&period_xact_t::period)) + .add_property("period_string", + make_getter(&period_xact_t::period_string), + make_setter(&period_xact_t::period_string)) + ; +} + +} // namespace ledger diff --git a/src/python/pyfstream.h b/src/pyfstream.h index f4650e0a..3da37523 100644 --- a/src/python/pyfstream.h +++ b/src/pyfstream.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2007, John Wiegley. All rights reserved. + * 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 @@ -35,21 +35,30 @@ // pyofstream // - a stream that writes on a Python file object -class pyoutbuf : public std::streambuf { - protected: +class pyoutbuf : public boost::noncopyable, public std::streambuf +{ + pyoutbuf(); + +protected: PyFileObject * fo; // Python file object - public: + +public: // constructor - pyoutbuf (PyFileObject * _fo) : fo(_fo) {} + pyoutbuf(PyFileObject * _fo) : fo(_fo) { + TRACE_CTOR(pyoutbuf, "PyFileObject *"); + } + ~pyoutbuf() throw() { + TRACE_DTOR(pyoutbuf); + } - protected: +protected: // write one character virtual int_type overflow (int_type c) { if (c != EOF) { char z[2]; - z[0] = c; + z[0] = static_cast<char>(c); z[1] = '\0'; - if (PyFile_WriteString(z, (PyObject *)fo) < 0) { + if (PyFile_WriteString(z, reinterpret_cast<PyObject *>(fo)) < 0) { return EOF; } } @@ -61,38 +70,50 @@ class pyoutbuf : public std::streambuf { char * buf = new char[num + 1]; std::strncpy(buf, s, num); buf[num] = '\0'; - if (PyFile_WriteString(buf, (PyObject *)fo) < 0) + if (PyFile_WriteString(buf, reinterpret_cast<PyObject *>(fo)) < 0) num = 0; - delete[] buf; + boost::checked_array_delete(buf); return num; } }; -class pyofstream : public std::ostream { - protected: +class pyofstream : public boost::noncopyable, public std::ostream +{ + pyofstream(); + +protected: pyoutbuf buf; - public: + +public: pyofstream (PyFileObject * fo) : std::ostream(0), buf(fo) { + TRACE_CTOR(pyofstream, "PyFileObject *"); rdbuf(&buf); } + ~pyofstream() throw() { + TRACE_DTOR(pyofstream); + } }; // pyifstream // - a stream that reads on a file descriptor -class pyinbuf : public std::streambuf { - protected: +class pyinbuf : public boost::noncopyable, public std::streambuf +{ + pyinbuf(); + +protected: PyFileObject * fo; // Python file object - protected: + +protected: /* data buffer: * - at most, pbSize characters in putback area plus * - at most, bufSize characters in ordinary read buffer */ - static const int pbSize = 4; // size of putback area - static const int bufSize = 1024; // size of the data buffer - char buffer[bufSize + pbSize]; // data buffer + static const size_t pbSize = 4; // size of putback area + static const size_t bufSize = 1024; // size of the data buffer + char buffer[bufSize + pbSize]; // data buffer - public: +public: /* constructor * - initialize file descriptor * - initialize empty data buffer @@ -100,12 +121,17 @@ class pyinbuf : public std::streambuf { * => force underflow() */ pyinbuf (PyFileObject * _fo) : fo(_fo) { + TRACE_CTOR(pyinbuf, "PyFileObject *"); + setg (buffer+pbSize, // beginning of putback area buffer+pbSize, // read position buffer+pbSize); // end position } + ~pyinbuf() throw() { + TRACE_DTOR(pyinbuf); + } - protected: +protected: // insert new characters into the buffer virtual int_type underflow () { #ifndef _MSC_VER @@ -121,7 +147,7 @@ class pyinbuf : public std::streambuf { * - use number of characters read * - but at most size of putback area */ - int numPutback; + size_t numPutback; numPutback = gptr() - eback(); if (numPutback > pbSize) { numPutback = pbSize; @@ -134,14 +160,13 @@ class pyinbuf : public std::streambuf { numPutback); // read at most bufSize new characters - int num; - PyObject *line = PyFile_GetLine((PyObject *)fo, bufSize); + PyObject *line = PyFile_GetLine(reinterpret_cast<PyObject *>(fo), bufSize); if (! line || ! PyString_Check(line)) { // ERROR or EOF return EOF; } - num = PyString_Size(line); + Py_ssize_t num = PyString_Size(line); if (num == 0) return EOF; @@ -157,13 +182,21 @@ class pyinbuf : public std::streambuf { } }; -class pyifstream : public std::istream { - protected: +class pyifstream : public boost::noncopyable, public std::istream +{ + pyifstream(); + +protected: pyinbuf buf; - public: + +public: pyifstream (PyFileObject * fo) : std::istream(0), buf(fo) { + TRACE_CTOR(pyifstream, "PyFileObject *"); rdbuf(&buf); } + ~pyifstream() throw() { + TRACE_DTOR(pyifstream); + } }; #endif // _PYFSTREAM_H diff --git a/src/pyinterp.cc b/src/pyinterp.cc new file mode 100644 index 00000000..0701176f --- /dev/null +++ b/src/pyinterp.cc @@ -0,0 +1,505 @@ +/* + * 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 "pyinterp.h" +#include "account.h" +#include "xact.h" +#include "post.h" + +namespace ledger { + +using namespace python; + +shared_ptr<python_interpreter_t> python_session; + +char * argv0; + +void export_account(); +void export_amount(); +void export_balance(); +void export_commodity(); +void export_expr(); +void export_format(); +void export_item(); +void export_journal(); +void export_post(); +void export_times(); +void export_utils(); +void export_value(); +void export_xact(); + +void initialize_for_python() +{ + export_times(); + export_utils(); + export_commodity(); + export_amount(); + export_value(); + export_account(); + export_balance(); + export_expr(); + export_format(); + export_item(); + export_post(); + export_xact(); + export_journal(); +} + +struct python_run +{ + object result; + + python_run(python_interpreter_t * intepreter, + const string& str, int input_mode) + : result(handle<>(borrowed(PyRun_String(str.c_str(), input_mode, + intepreter->main_nspace.ptr(), + intepreter->main_nspace.ptr())))) {} + operator object() { + return result; + } +}; + +void python_interpreter_t::initialize() +{ + TRACE_START(python_init, 1, "Initialized Python"); + + try { + DEBUG("python.interp", "Initializing Python"); + + Py_Initialize(); + assert(Py_IsInitialized()); + + hack_system_paths(); + + object main_module = python::import("__main__"); + if (! main_module) + throw_(std::runtime_error, + _("Python failed to initialize (couldn't find __main__)")); + + main_nspace = extract<dict>(main_module.attr("__dict__")); + if (! main_nspace) + throw_(std::runtime_error, + _("Python failed to initialize (couldn't find __dict__)")); + + python::detail::init_module("ledger", &initialize_for_python); + + is_initialized = true; + } + catch (const error_already_set&) { + PyErr_Print(); + throw_(std::runtime_error, _("Python failed to initialize")); + } + + TRACE_FINISH(python_init, 1); +} + +void python_interpreter_t::hack_system_paths() +{ + // Hack ledger.__path__ so it points to a real location + python::object sys_module = python::import("sys"); + python::object sys_dict = sys_module.attr("__dict__"); + + python::list paths(sys_dict["path"]); + +#if defined(DEBUG_ON) + bool path_initialized = false; +#endif + int n = python::extract<int>(paths.attr("__len__")()); + for (int i = 0; i < n; i++) { + python::extract<std::string> str(paths[i]); + path pathname(str); + DEBUG("python.interp", "sys.path = " << pathname); + + if (exists(pathname / "ledger" / "__init__.py")) { + if (python::object module_ledger = python::import("ledger")) { + DEBUG("python.interp", + "Setting ledger.__path__ = " << (pathname / "ledger")); + + python::object ledger_dict = module_ledger.attr("__dict__"); + python::list temp_list; + temp_list.append((pathname / "ledger").string()); + + ledger_dict["__path__"] = temp_list; + } else { + throw_(std::runtime_error, + _("Python failed to initialize (couldn't find ledger)")); + } +#if defined(DEBUG_ON) + path_initialized = true; +#endif + break; + } + } +#if defined(DEBUG_ON) + if (! path_initialized) + DEBUG("python.init", + "Ledger failed to find 'ledger/__init__.py' on the PYTHONPATH"); +#endif +} + +object python_interpreter_t::import_into_main(const string& str) +{ + if (! is_initialized) + initialize(); + + try { + object mod = python::import(str.c_str()); + if (! mod) + throw_(std::runtime_error, + _("Failed to import Python module %1") << str); + + // Import all top-level entries directly into the main namespace + main_nspace.update(mod.attr("__dict__")); + + return mod; + } + catch (const error_already_set&) { + PyErr_Print(); + } + return object(); +} + +object python_interpreter_t::import_option(const string& str) +{ + path file(str); + + python::object sys_module = python::import("sys"); + python::object sys_dict = sys_module.attr("__dict__"); + + python::list paths(sys_dict["path"]); + +#if BOOST_VERSION >= 103700 + paths.insert(0, file.parent_path().string()); + sys_dict["path"] = paths; + + string name = file.filename(); + if (contains(name, ".py")) + name = file.stem(); +#else // BOOST_VERSION >= 103700 + paths.insert(0, file.branch_path().string()); + sys_dict["path"] = paths; + + string name = file.leaf(); +#endif // BOOST_VERSION >= 103700 + + return python::import(python::str(name.c_str())); +} + +object python_interpreter_t::eval(std::istream& in, py_eval_mode_t mode) +{ + bool first = true; + string buffer; + buffer.reserve(4096); + + while (! in.eof()) { + char buf[256]; + in.getline(buf, 255); + if (buf[0] == '!') + break; + if (first) + first = false; + else + buffer += "\n"; + buffer += buf; + } + + if (! is_initialized) + initialize(); + + try { + int input_mode = -1; + switch (mode) { + case PY_EVAL_EXPR: input_mode = Py_eval_input; break; + case PY_EVAL_STMT: input_mode = Py_single_input; break; + case PY_EVAL_MULTI: input_mode = Py_file_input; break; + } + + return python_run(this, buffer, input_mode); + } + catch (const error_already_set&) { + PyErr_Print(); + throw_(std::runtime_error, _("Failed to evaluate Python code")); + } + return object(); +} + +object python_interpreter_t::eval(const string& str, py_eval_mode_t mode) +{ + if (! is_initialized) + initialize(); + + try { + int input_mode = -1; + switch (mode) { + case PY_EVAL_EXPR: input_mode = Py_eval_input; break; + case PY_EVAL_STMT: input_mode = Py_single_input; break; + case PY_EVAL_MULTI: input_mode = Py_file_input; break; + } + + return python_run(this, str, input_mode); + } + catch (const error_already_set&) { + PyErr_Print(); + throw_(std::runtime_error, _("Failed to evaluate Python code")); + } + return object(); +} + +value_t python_interpreter_t::python_command(call_scope_t& args) +{ + if (! is_initialized) + initialize(); + + char ** argv(new char *[args.size() + 1]); + + argv[0] = new char[std::strlen(argv0) + 1]; + std::strcpy(argv[0], argv0); + + for (std::size_t i = 0; i < args.size(); i++) { + string arg = args[i].as_string(); + argv[i + 1] = new char[arg.length() + 1]; + std::strcpy(argv[i + 1], arg.c_str()); + } + + int status = 1; + + try { + status = Py_Main(static_cast<int>(args.size()) + 1, argv); + } + catch (const error_already_set&) { + PyErr_Print(); + throw_(std::runtime_error, _("Failed to execute Python module")); + } + catch (...) { + for (std::size_t i = 0; i < args.size() + 1; i++) + delete[] argv[i]; + delete[] argv; + throw; + } + + for (std::size_t i = 0; i < args.size() + 1; i++) + delete[] argv[i]; + delete[] argv; + + if (status != 0) + throw status; + + return NULL_VALUE; +} + +value_t python_interpreter_t::server_command(call_scope_t& args) +{ + if (! is_initialized) + initialize(); + + python::object server_module; + + try { + server_module = python::import("ledger.server"); + if (! server_module) + throw_(std::runtime_error, + _("Could not import ledger.server; please check your PYTHONPATH")); + } + catch (const error_already_set&) { + PyErr_Print(); + throw_(std::runtime_error, + _("Could not import ledger.server; please check your PYTHONPATH")); + } + + if (python::object main_function = server_module.attr("main")) { + functor_t func(main_function, "main"); + try { + func(args); + } + catch (const error_already_set&) { + PyErr_Print(); + throw_(std::runtime_error, + _("Error while invoking ledger.server's main() function")); + } + return true; + } else { + throw_(std::runtime_error, + _("The ledger.server module is missing its main() function!")); + } + + return false; +} + +option_t<python_interpreter_t> * +python_interpreter_t::lookup_option(const char * p) +{ + switch (*p) { + case 'i': + OPT(import_); + break; + } + return NULL; +} + +expr_t::ptr_op_t python_interpreter_t::lookup(const symbol_t::kind_t kind, + const string& name) +{ + // Give our superclass first dibs on symbol definitions + if (expr_t::ptr_op_t op = session_t::lookup(kind, name)) + return op; + + switch (kind) { + case symbol_t::FUNCTION: + if (option_t<python_interpreter_t> * handler = lookup_option(name.c_str())) + return MAKE_OPT_FUNCTOR(python_interpreter_t, handler); + + if (is_initialized && main_nspace.has_key(name.c_str())) { + DEBUG("python.interp", "Python lookup: " << name); + + if (python::object obj = main_nspace.get(name.c_str())) + return WRAP_FUNCTOR(functor_t(obj, name)); + } + break; + + case symbol_t::OPTION: + if (option_t<python_interpreter_t> * handler = lookup_option(name.c_str())) + return MAKE_OPT_HANDLER(python_interpreter_t, handler); + break; + + case symbol_t::PRECOMMAND: { + const char * p = name.c_str(); + switch (*p) { + case 'p': + if (is_eq(p, "python")) + return MAKE_FUNCTOR(python_interpreter_t::python_command); + break; + + case 's': + if (is_eq(p, "server")) + return MAKE_FUNCTOR(python_interpreter_t::server_command); + break; + } + } + + default: + break; + } + + return NULL; +} + +namespace { + void append_value(list& lst, const value_t& value) + { + if (value.is_scope()) { + const scope_t * scope = value.as_scope(); + if (const post_t * post = dynamic_cast<const post_t *>(scope)) + lst.append(ptr(post)); + else if (const xact_t * xact = dynamic_cast<const xact_t *>(scope)) + lst.append(ptr(xact)); + else if (const account_t * account = + dynamic_cast<const account_t *>(scope)) + lst.append(ptr(account)); + else if (const period_xact_t * period_xact = + dynamic_cast<const period_xact_t *>(scope)) + lst.append(ptr(period_xact)); + else if (const auto_xact_t * auto_xact = + dynamic_cast<const auto_xact_t *>(scope)) + lst.append(ptr(auto_xact)); + else + throw_(std::logic_error, + _("Cannot downcast scoped object to specific type")); + } else { + lst.append(value); + } + } +} + +value_t python_interpreter_t::functor_t::operator()(call_scope_t& args) +{ + try { + std::signal(SIGINT, SIG_DFL); + + if (! PyCallable_Check(func.ptr())) { + extract<value_t> val(func); + std::signal(SIGINT, sigint_handler); + if (val.check()) + return val(); + return NULL_VALUE; + } + else if (args.size() > 0) { + list arglist; + // jww (2009-11-05): What about a single argument which is a sequence, + // rather than a sequence of arguments? + if (args.value().is_sequence()) + foreach (const value_t& value, args.value().as_sequence()) + append_value(arglist, value); + else + append_value(arglist, args.value()); + + if (PyObject * val = + PyObject_CallObject(func.ptr(), python::tuple(arglist).ptr())) { + extract<value_t> xval(val); + value_t result; + if (xval.check()) { + result = xval(); + Py_DECREF(val); + } else { + Py_DECREF(val); + throw_(calc_error, + _("Could not evaluate Python variable '%1'") << name); + } + std::signal(SIGINT, sigint_handler); + return result; + } + else if (PyErr_Occurred()) { + PyErr_Print(); + throw_(calc_error, _("Failed call to Python function '%1'") << name); + } else { + assert(false); + } + } + else { + std::signal(SIGINT, sigint_handler); + return call<value_t>(func.ptr()); + } + } + catch (const error_already_set&) { + std::signal(SIGINT, sigint_handler); + PyErr_Print(); + throw_(calc_error, _("Failed call to Python function '%1'") << name); + } + catch (...) { + std::signal(SIGINT, sigint_handler); + } + std::signal(SIGINT, sigint_handler); + + return NULL_VALUE; +} + +} // namespace ledger diff --git a/src/python/pyinterp.h b/src/pyinterp.h index 3d69d972..f2d7b760 100644 --- a/src/python/pyinterp.h +++ b/src/pyinterp.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2007, John Wiegley. All rights reserved. + * 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 @@ -32,27 +32,35 @@ #ifndef _PYINTERP_H #define _PYINTERP_H -#include "xpath.h" +#include "session.h" -#include <boost/python.hpp> -#include <Python.h> +#if defined(HAVE_BOOST_PYTHON) namespace ledger { -class python_interpreter_t : public xml::xpath_t::symbol_scope_t +class python_interpreter_t : public session_t { - boost::python::handle<> mmodule; - - public: - boost::python::dict nspace; - - python_interpreter_t(xml::xpath_t::scope_t& parent); +public: + python::dict main_nspace; + bool is_initialized; + python_interpreter_t() + : session_t(), main_nspace(), is_initialized(false) { + TRACE_CTOR(python_interpreter_t, ""); + } + virtual ~python_interpreter_t() { - Py_Finalize(); + TRACE_DTOR(python_interpreter_t); + + if (is_initialized) + Py_Finalize(); } - boost::python::object import(const string& name); + void initialize(); + void hack_system_paths(); + + python::object import_into_main(const string& name); + python::object import_option(const string& name); enum py_eval_mode_t { PY_EVAL_EXPR, @@ -60,39 +68,57 @@ class python_interpreter_t : public xml::xpath_t::symbol_scope_t PY_EVAL_MULTI }; - boost::python::object eval(std::istream& in, - py_eval_mode_t mode = PY_EVAL_EXPR); - boost::python::object eval(const string& str, - py_eval_mode_t mode = PY_EVAL_EXPR); - boost::python::object eval(const char * c_str, - py_eval_mode_t mode = PY_EVAL_EXPR) { + python::object eval(std::istream& in, + py_eval_mode_t mode = PY_EVAL_EXPR); + python::object eval(const string& str, + py_eval_mode_t mode = PY_EVAL_EXPR); + python::object eval(const char * c_str, + py_eval_mode_t mode = PY_EVAL_EXPR) { string str(c_str); return eval(str, mode); } + value_t python_command(call_scope_t& scope); + value_t server_command(call_scope_t& args); + class functor_t { + functor_t(); + protected: - boost::python::object func; + python::object func; + public: - functor_t(const string& name, boost::python::object _func) : func(_func) {} - virtual ~functor_t() {} - virtual value_t operator()(xml::xpath_t::call_scope_t& args); + string name; + + functor_t(python::object _func, const string& _name) + : func(_func), name(_name) { + TRACE_CTOR(functor_t, "python::object, const string&"); + } + functor_t(const functor_t& other) + : func(other.func), name(other.name) { + TRACE_CTOR(functor_t, "copy"); + } + virtual ~functor_t() throw() { + TRACE_DTOR(functor_t); + } + virtual value_t operator()(call_scope_t& args); }; - virtual xml::xpath_t::ptr_op_t lookup(const string& name) { - if (boost::python::object func = eval(name)) - return WRAP_FUNCTOR(functor_t(name, func)); - else - return xml::xpath_t::symbol_scope_t::lookup(name); - } + option_t<python_interpreter_t> * lookup_option(const char * p); - class lambda_t : public functor_t { - public: - lambda_t(boost::python::object code) : functor_t("<lambda>", code) {} - virtual value_t operator()(xml::xpath_t::call_scope_t& args); - }; + virtual expr_t::ptr_op_t lookup(const symbol_t::kind_t kind, + const string& name); + + OPTION_(python_interpreter_t, import_, DO_(scope) { + interactive_t args(scope, "ss"); + parent->import_option(args.get<string>(1)); + }); }; +extern shared_ptr<python_interpreter_t> python_session; + } // namespace ledger +#endif // HAVE_BOOST_PYTHON + #endif // _PYINTERP_H diff --git a/src/python/pyledger.cc b/src/pyledger.cc index ebbdc82e..cf7e1527 100644 --- a/src/python/pyledger.cc +++ b/src/pyledger.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2007, John Wiegley. All rights reserved. + * 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 @@ -29,11 +29,11 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include <pyledger.h> +#include <system.hh> -using namespace boost::python; +#include "pyinterp.h" -ledger::session_t python_session; +using namespace boost::python; namespace ledger { extern void initialize_for_python(); @@ -41,6 +41,12 @@ namespace ledger { BOOST_PYTHON_MODULE(ledger) { - ledger::set_session_context(&python_session); - ledger::initialize_for_python(); + using namespace ledger; + + if (! python_session.get()) + python_session.reset(new python_interpreter_t); + + set_session_context(python_session.get()); + + initialize_for_python(); } diff --git a/src/python/py_amount.cc b/src/python/py_amount.cc deleted file mode 100644 index 07be1512..00000000 --- a/src/python/py_amount.cc +++ /dev/null @@ -1,320 +0,0 @@ -/* - * Copyright (c) 2003-2007, 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 "pyinterp.h" -#include "pyutils.h" -#include "amount.h" - -#include <boost/python/exception_translator.hpp> -#include <boost/python/implicit.hpp> -#include <boost/python/args.hpp> - -namespace ledger { - -using namespace boost::python; - -amount_t py_round_0(const amount_t& amount) { - return amount.round(); -} -amount_t py_round_1(const amount_t& amount, amount_t::precision_t prec) { - return amount.round(prec); -} - -double py_to_double_0(amount_t& amount) { - return amount.to_double(); -} -double py_to_double_1(amount_t& amount, bool no_check) { - return amount.to_double(no_check); -} - -long py_to_long_0(amount_t& amount) { - return amount.to_long(); -} -long py_to_long_1(amount_t& amount, bool no_check) { - return amount.to_long(no_check); -} - -boost::optional<amount_t> py_value_0(const amount_t& amount) { - return amount.value(); -} -boost::optional<amount_t> py_value_1(const amount_t& amount, - const boost::optional<moment_t>& moment) { - return amount.value(moment); -} - -void py_parse_2(amount_t& amount, object in, unsigned char flags) { - if (PyFile_Check(in.ptr())) { - pyifstream instr(reinterpret_cast<PyFileObject *>(in.ptr())); - amount.parse(instr, flags); - } else { - PyErr_SetString(PyExc_IOError, - "Argument to amount.parse(file) is not a file object"); - } -} -void py_parse_1(amount_t& amount, object in) { - py_parse_2(amount, in, 0); -} - -void py_parse_str_1(amount_t& amount, const string& str) { - amount.parse(str); -} -void py_parse_str_2(amount_t& amount, const string& str, unsigned char flags) { - amount.parse(str, flags); -} - -void py_read_1(amount_t& amount, object in) { - if (PyFile_Check(in.ptr())) { - pyifstream instr(reinterpret_cast<PyFileObject *>(in.ptr())); - amount.read(instr); - } else { - PyErr_SetString(PyExc_IOError, - "Argument to amount.parse(file) is not a file object"); - } -} -void py_read_2(amount_t& amount, const std::string& str) { - const char * p = str.c_str(); - amount.read(p); -} - -#define EXC_TRANSLATOR(type) \ - void exc_translate_ ## type(const type& err) { \ - PyErr_SetString(PyExc_ArithmeticError, err.what()); \ - } - -EXC_TRANSLATOR(amount_error) - -void export_amount() -{ - scope().attr("AMOUNT_PARSE_NO_MIGRATE") = AMOUNT_PARSE_NO_MIGRATE; - scope().attr("AMOUNT_PARSE_NO_REDUCE") = AMOUNT_PARSE_NO_REDUCE; - - class_< amount_t > ("amount") -#if 0 - .def("initialize", &amount_t::initialize) - .staticmethod("initialize") - .def("shutdown", &amount_t::shutdown) - .staticmethod("shutdown") -#endif - - .add_static_property("current_pool", - make_getter(&amount_t::current_pool, - return_value_policy<reference_existing_object>())) - - .add_static_property("keep_base", &amount_t::keep_base) - - .add_static_property("keep_price", &amount_t::keep_price) - .add_static_property("keep_date", &amount_t::keep_date) - .add_static_property("keep_tag", &amount_t::keep_tag) - - .add_static_property("stream_fullstrings", &amount_t::stream_fullstrings) - - .def(init<double>()) - .def(init<long>()) - .def(init<std::string>()) - - .def("exact", &amount_t::exact, args("value"), - "Construct an amount object whose display precision is always equal to its\n\ -internal precision.") - .staticmethod("exact") - - .def(init<amount_t>()) - - .def("compare", &amount_t::compare) - - .def(self == self) - .def(self == long()) - .def(long() == self) - .def(self == double()) - .def(double() == self) - - .def(self != self) - .def(self != long()) - .def(long() != self) - .def(self != double()) - .def(double() != self) - - .def(! self) - - .def(self < self) - .def(self < long()) - .def(long() < self) - .def(self < double()) - .def(double() < self) - - .def(self <= self) - .def(self <= long()) - .def(long() <= self) - .def(self <= double()) - .def(double() <= self) - - .def(self > self) - .def(self > long()) - .def(long() > self) - .def(self > double()) - .def(double() > self) - - .def(self >= self) - .def(self >= long()) - .def(long() >= self) - .def(self >= double()) - .def(double() >= self) - - .def(self += self) - .def(self += long()) - .def(self += double()) - - .def(self + self) - .def(self + long()) - .def(long() + self) - .def(self + double()) - .def(double() + self) - - .def(self -= self) - .def(self -= long()) - .def(self -= double()) - - .def(self - self) - .def(self - long()) - .def(long() - self) - .def(self - double()) - .def(double() - self) - - .def(self *= self) - .def(self *= long()) - .def(self *= double()) - - .def(self * self) - .def(self * long()) - .def(long() * self) - .def(self * double()) - .def(double() * self) - - .def(self /= self) - .def(self /= long()) - .def(self /= double()) - - .def(self / self) - .def(self / long()) - .def(long() / self) - .def(self / double()) - .def(double() / self) - - .add_property("precision", &amount_t::precision) - - .def("negate", &amount_t::negate) - .def("in_place_negate", &amount_t::in_place_negate, - return_value_policy<reference_existing_object>()) - .def(- self) - - .def("abs", &amount_t::abs) - .def("__abs__", &amount_t::abs) - - .def("round", py_round_0) - .def("round", py_round_1) - .def("unround", &amount_t::unround) - - .def("reduce", &amount_t::reduce) - .def("in_place_reduce", &amount_t::in_place_reduce, - return_value_policy<reference_existing_object>()) - - .def("unreduce", &amount_t::unreduce) - .def("in_place_unreduce", &amount_t::in_place_unreduce, - return_value_policy<reference_existing_object>()) - - .def("value", py_value_0) - .def("value", py_value_1) - - .def("sign", &amount_t::sign) - .def("__nonzero__", &amount_t::is_nonzero) - .def("is_nonzero", &amount_t::is_nonzero) - .def("is_zero", &amount_t::is_zero) - .def("is_realzero", &amount_t::is_realzero) - .def("is_null", &amount_t::is_null) - - .def("to_double", py_to_double_0) - .def("to_double", py_to_double_1) - .def("__float__", py_to_double_0) - .def("to_long", py_to_long_0) - .def("to_long", py_to_long_1) - .def("__int__", py_to_long_0) - .def("to_string", &amount_t::to_string) - .def("__str__", &amount_t::to_string) - .def("to_fullstring", &amount_t::to_fullstring) - .def("__repr__", &amount_t::to_fullstring) - - .def("fits_in_double", &amount_t::fits_in_double) - .def("fits_in_long", &amount_t::fits_in_long) - - .add_property("quantity_string", &amount_t::quantity_string) - - .add_property("commodity", - make_function(&amount_t::commodity, - return_value_policy<reference_existing_object>()), - make_function(&amount_t::set_commodity, - with_custodian_and_ward<1, 2>())) - - .def("has_commodity", &amount_t::has_commodity) - .def("clear_commodity", &amount_t::clear_commodity) - .add_property("number", &amount_t::number) - - .def("annotate_commodity", &amount_t::annotate_commodity) - .def("commodity_annotated", &amount_t::commodity_annotated) - .add_property("annotation_details", &amount_t::annotation_details) - .def("strip_annotations", &amount_t::strip_annotations) - - .def("parse", py_parse_1) - .def("parse", py_parse_2) - .def("parse", py_parse_str_1) - .def("parse", py_parse_str_2) - - .def("parse_conversion", &amount_t::parse_conversion) - .staticmethod("parse_conversion") - - .def("read", py_read_1) - .def("read", py_read_2) - .def("write", &amount_t::write) - - .def("valid", &amount_t::valid) - ; - - register_optional_to_python<amount_t>(); - - implicitly_convertible<double, amount_t>(); - implicitly_convertible<long, amount_t>(); - implicitly_convertible<string, amount_t>(); - -#define EXC_TRANSLATE(type) \ - register_exception_translator<type>(&exc_translate_ ## type); - - EXC_TRANSLATE(amount_error); -} - -} // namespace ledger diff --git a/src/python/py_times.cc b/src/python/py_times.cc deleted file mode 100644 index 173f21fa..00000000 --- a/src/python/py_times.cc +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (c) 2003-2007, 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 "pyinterp.h" -#include "pyutils.h" - -#include <boost/cast.hpp> -#include <boost/python/module.hpp> -#include <boost/python/def.hpp> -#include <boost/python/to_python_converter.hpp> - -#include <Python.h> -#include <datetime.h> - -// jww (2007-05-04): Convert time duration objects to PyDelta - -namespace ledger { - -using namespace boost::python; - -typedef boost::gregorian::date date; - -struct date_to_python -{ - static PyObject* convert(const date& dte) - { - PyDateTime_IMPORT; - return PyDate_FromDate(dte.year(), dte.month(), dte.day()); - } -}; - -struct date_from_python -{ - static void* convertible(PyObject* obj_ptr) - { - PyDateTime_IMPORT; - if (PyDate_Check(obj_ptr)) return obj_ptr; - return 0; - } - - static void construct(PyObject* obj_ptr, converter::rvalue_from_python_stage1_data* data) - { - PyDateTime_IMPORT; - int y = PyDateTime_GET_YEAR(obj_ptr); - int m = PyDateTime_GET_MONTH(obj_ptr); - int d = PyDateTime_GET_DAY(obj_ptr); - date* dte = new date(y,m,d); - data->convertible = (void*)dte; - } -}; - -typedef register_python_conversion<date, date_to_python, date_from_python> - date_python_conversion; - - -struct datetime_to_python -{ - static PyObject* convert(const moment_t& moment) - { - PyDateTime_IMPORT; - date dte = moment.date(); - moment_t::time_duration_type tod = moment.time_of_day(); - return PyDateTime_FromDateAndTime(dte.year(), dte.month(), dte.day(), - tod.hours(), tod.minutes(), tod.seconds(), - tod.total_microseconds() % 1000000); - } -}; - -struct datetime_from_python -{ - static void* convertible(PyObject* obj_ptr) - { - PyDateTime_IMPORT; - if(PyDateTime_Check(obj_ptr)) return obj_ptr; - return 0; - } - - static void construct(PyObject* obj_ptr, converter::rvalue_from_python_stage1_data* data) - { - PyDateTime_IMPORT; - int y = PyDateTime_GET_YEAR(obj_ptr); - int m = PyDateTime_GET_MONTH(obj_ptr); - int d = PyDateTime_GET_DAY(obj_ptr); - int h = PyDateTime_DATE_GET_HOUR(obj_ptr); - int min = PyDateTime_DATE_GET_MINUTE(obj_ptr); - int s = PyDateTime_DATE_GET_SECOND(obj_ptr); - moment_t* moment = new moment_t(date(y,m,d), - moment_t::time_duration_type(h, min, s)); - data->convertible = (void*)moment; - } -}; - -typedef register_python_conversion<moment_t, datetime_to_python, datetime_from_python> - datetime_python_conversion; - -void export_times() -{ - date_python_conversion(); - datetime_python_conversion(); - - register_optional_to_python<moment_t>(); -} - -} // namespace ledger diff --git a/src/python/py_utils.cc b/src/python/py_utils.cc deleted file mode 100644 index 50ce9712..00000000 --- a/src/python/py_utils.cc +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright (c) 2003-2007, 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 "pyinterp.h" -#include "pyutils.h" - -#include <boost/python/module.hpp> -#include <boost/python/def.hpp> -#include <boost/python/to_python_converter.hpp> - -namespace ledger { - -using namespace boost::python; - -struct bool_to_python -{ - static PyObject * convert(const bool truth) - { - if (truth) - Py_RETURN_TRUE; - else - Py_RETURN_FALSE; - } -}; - -struct bool_from_python -{ - static void* convertible(PyObject* obj_ptr) - { - if (!PyBool_Check(obj_ptr)) return 0; - return obj_ptr; - } - - static void construct(PyObject* obj_ptr, - converter::rvalue_from_python_stage1_data* data) - { - void* storage = ((converter::rvalue_from_python_storage<bool>*) data)->storage.bytes; - if (obj_ptr == Py_True) - new (storage) bool(true); - else - new (storage) bool(false); - data->convertible = storage; - } -}; - -typedef register_python_conversion<bool, bool_to_python, bool_from_python> - bool_python_conversion; - - -struct string_to_python -{ - static PyObject* convert(const string& str) - { - return incref(object(*boost::polymorphic_downcast<const std::string *>(&str)).ptr()); - } -}; - -struct string_from_python -{ - static void* convertible(PyObject* obj_ptr) - { - if (!PyString_Check(obj_ptr)) return 0; - return obj_ptr; - } - - static void construct(PyObject* obj_ptr, converter::rvalue_from_python_stage1_data* data) - { - const char* value = PyString_AsString(obj_ptr); - if (value == 0) throw_error_already_set(); - void* storage = ((converter::rvalue_from_python_storage<string>*) data)->storage.bytes; - new (storage) string(value); - data->convertible = storage; - } -}; - -typedef register_python_conversion<string, string_to_python, string_from_python> - string_python_conversion; - - -struct istream_to_python -{ - static PyObject* convert(const std::istream& str) - { - return incref(boost::python::detail::none()); - } -}; - -struct istream_from_python -{ - static void* convertible(PyObject* obj_ptr) - { - if (!PyFile_Check(obj_ptr)) return 0; - return obj_ptr; - } - - static void construct(PyObject* obj_ptr, converter::rvalue_from_python_stage1_data* data) - { - void* storage = ((converter::rvalue_from_python_storage<pyifstream>*) data)->storage.bytes; - new (storage) pyifstream(reinterpret_cast<PyFileObject *>(obj_ptr)); - data->convertible = storage; - } -}; - -typedef register_python_conversion<std::istream, istream_to_python, istream_from_python> - istream_python_conversion; - - -struct ostream_to_python -{ - static PyObject* convert(const std::ostream& str) - { - return incref(boost::python::detail::none()); - } -}; - -struct ostream_from_python -{ - static void* convertible(PyObject* obj_ptr) - { - if (!PyFile_Check(obj_ptr)) return 0; - return obj_ptr; - } - - static void construct(PyObject* obj_ptr, converter::rvalue_from_python_stage1_data* data) - { - void* storage = ((converter::rvalue_from_python_storage<pyofstream>*) data)->storage.bytes; - new (storage) pyofstream(reinterpret_cast<PyFileObject *>(obj_ptr)); - data->convertible = storage; - } -}; - -typedef register_python_conversion<std::ostream, ostream_to_python, ostream_from_python> - ostream_python_conversion; - - -void export_utils() -{ - bool_python_conversion(); - string_python_conversion(); - istream_python_conversion(); - ostream_python_conversion(); -} - -} // namespace ledger diff --git a/src/python/pyinterp.cc b/src/python/pyinterp.cc deleted file mode 100644 index d521a0ee..00000000 --- a/src/python/pyinterp.cc +++ /dev/null @@ -1,238 +0,0 @@ -/* - * Copyright (c) 2003-2007, 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 "pyinterp.h" - -#include <boost/python/module_init.hpp> - -namespace ledger { - -using namespace boost::python; - -void export_utils(); -void export_times(); -void export_amount(); -void export_commodity(); -#if 0 -void export_balance(); -void export_value(); -void export_journal(); -void export_parser(); -void export_option(); -void export_walk(); -void export_report(); -void export_format(); -void export_valexpr(); -#endif - -void initialize_for_python() -{ - export_utils(); - export_times(); - export_amount(); - export_commodity(); -#if 0 - export_balance(); - export_value(); - export_journal(); - export_parser(); - export_option(); - export_walk(); - export_format(); - export_report(); - export_valexpr(); -#endif -} - -struct python_run -{ - object result; - - python_run(python_interpreter_t * intepreter, - const string& str, int input_mode) - : result(handle<>(borrowed(PyRun_String(str.c_str(), input_mode, - intepreter->nspace.ptr(), - intepreter->nspace.ptr())))) {} - operator object() { - return result; - } -}; - -python_interpreter_t::python_interpreter_t(xml::xpath_t::scope_t& parent) - : xml::xpath_t::symbol_scope_t(parent), - mmodule(borrowed(PyImport_AddModule("__main__"))), - nspace(handle<>(borrowed(PyModule_GetDict(mmodule.get())))) -{ - Py_Initialize(); - boost::python::detail::init_module("ledger", &initialize_for_python); -} - -object python_interpreter_t::import(const string& str) -{ - assert(Py_IsInitialized()); - - try { - PyObject * mod = PyImport_Import(PyString_FromString(str.c_str())); - if (! mod) - throw_(std::logic_error, "Failed to import Python module " << str); - - object newmod(handle<>(borrowed(mod))); - -#if 1 - // Import all top-level entries directly into the main namespace - dict m_nspace(handle<>(borrowed(PyModule_GetDict(mod)))); - nspace.update(m_nspace); -#else - nspace[string(PyModule_GetName(mod))] = newmod; -#endif - return newmod; - } - catch (const error_already_set&) { - PyErr_Print(); - throw_(std::logic_error, "Importing Python module " << str); - } - return object(); -} - -object python_interpreter_t::eval(std::istream& in, py_eval_mode_t mode) -{ - bool first = true; - string buffer; - buffer.reserve(4096); - - while (! in.eof()) { - char buf[256]; - in.getline(buf, 255); - if (buf[0] == '!') - break; - if (first) - first = false; - else - buffer += "\n"; - buffer += buf; - } - - try { - int input_mode; - switch (mode) { - case PY_EVAL_EXPR: input_mode = Py_eval_input; break; - case PY_EVAL_STMT: input_mode = Py_single_input; break; - case PY_EVAL_MULTI: input_mode = Py_file_input; break; - } - assert(Py_IsInitialized()); - return python_run(this, buffer, input_mode); - } - catch (const error_already_set&) { - PyErr_Print(); - throw_(std::logic_error, "Evaluating Python code"); - } - return object(); -} - -object python_interpreter_t::eval(const string& str, py_eval_mode_t mode) -{ - try { - int input_mode; - switch (mode) { - case PY_EVAL_EXPR: input_mode = Py_eval_input; break; - case PY_EVAL_STMT: input_mode = Py_single_input; break; - case PY_EVAL_MULTI: input_mode = Py_file_input; break; - } - assert(Py_IsInitialized()); - return python_run(this, str, input_mode); - } - catch (const error_already_set&) { - PyErr_Print(); - throw_(std::logic_error, "Evaluating Python code"); - } - return object(); -} - -value_t python_interpreter_t::functor_t::operator() - (xml::xpath_t::call_scope_t& args) -{ - try { - if (! PyCallable_Check(func.ptr())) { - return extract<value_t>(func.ptr()); - } else { - if (args.size() > 0) { - list arglist; - if (args.value().is_sequence()) - foreach (const value_t& value, args.value().as_sequence()) - arglist.append(value); - else - arglist.append(args.value()); - - if (PyObject * val = - PyObject_CallObject(func.ptr(), - boost::python::tuple(arglist).ptr())) { - value_t result = extract<value_t>(val)(); - Py_DECREF(val); - return result; - } - else if (PyObject * err = PyErr_Occurred()) { - PyErr_Print(); - throw_(xml::xpath_t::calc_error, - "While calling Python function '" /*<< name() <<*/ "': " << err); - } else { - assert(false); - } - } else { - return call<value_t>(func.ptr()); - } - } - } - catch (const error_already_set&) { - PyErr_Print(); - throw_(xml::xpath_t::calc_error, - "While calling Python function '" /*<< name() <<*/ "'"); - } - return NULL_VALUE; -} - -value_t python_interpreter_t::lambda_t::operator() - (xml::xpath_t::call_scope_t& args) -{ - try { - assert(args.size() == 1); - value_t item = args[0]; - assert(item.is_xml_node()); - return call<value_t>(func.ptr(), item.as_xml_node()); - } - catch (const error_already_set&) { - PyErr_Print(); - throw_(xml::xpath_t::calc_error, - "While evaluating Python lambda expression"); - } - return NULL_VALUE; -} - -} // namespace ledger diff --git a/src/python/pyutils.h b/src/python/pyutils.h deleted file mode 100644 index 41bbbfde..00000000 --- a/src/python/pyutils.h +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (c) 2003-2007, 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. - */ - -#ifndef _PY_UTILS_H -#define _PY_UTILS_H - -#include "pyfstream.h" - -template <typename T, typename TfromPy> -struct object_from_python -{ - object_from_python() { - boost::python::converter::registry::push_back - (&TfromPy::convertible, &TfromPy::construct, - boost::python::type_id<T>()); - } -}; - -template <typename T, typename TtoPy, typename TfromPy> -struct register_python_conversion -{ - register_python_conversion() { - boost::python::to_python_converter<T, TtoPy>(); - object_from_python<T, TfromPy>(); - } -}; - -template <typename T> -struct register_optional_to_python : public boost::noncopyable -{ - struct optional_to_python - { - static PyObject * convert(const boost::optional<T>& value) - { - return boost::python::incref - (value ? boost::python::to_python_value<T>()(*value) : - boost::python::detail::none()); - } - }; - - struct optional_from_python - { - static void * convertible(PyObject * source) - { - using namespace boost::python::converter; - - if (source == Py_None) - return source; - - const registration& converters(registered<T>::converters); - - if (implicit_rvalue_convertible_from_python(source, converters)) { - rvalue_from_python_stage1_data data = - rvalue_from_python_stage1(source, converters); - return rvalue_from_python_stage2(source, data, converters); - } - return NULL; - } - - static void construct(PyObject * source, - boost::python::converter::rvalue_from_python_stage1_data * data) - { - using namespace boost::python::converter; - - void * const storage = ((rvalue_from_python_storage<T> *) data)->storage.bytes; - - if (data->convertible == source) // == None - new (storage) boost::optional<T>(); // A Boost uninitialized value - else - new (storage) boost::optional<T>(*static_cast<T *>(data->convertible)); - - data->convertible = storage; - } - }; - - explicit register_optional_to_python() { - register_python_conversion<boost::optional<T>, - optional_to_python, optional_from_python>(); - } -}; - -//boost::python::register_ptr_to_python< boost::shared_ptr<Base> >(); - -#endif // _PY_UTILS_H diff --git a/src/python/tuples.hpp b/src/python/tuples.hpp deleted file mode 100644 index 523846a7..00000000 --- a/src/python/tuples.hpp +++ /dev/null @@ -1,281 +0,0 @@ -/* - * Copyright (c) 2003-2007, 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. - */ - -// Copyright 2004-2007 Roman Yakovenko. -// Distributed under the Boost Software License, Version 1.0. (See -// accompanying file LICENSE_1_0.txt or copy at -// http://www.boost.org/LICENSE_1_0.txt) - -#ifndef TUPLES_HPP_16_JAN_2007 -#define TUPLES_HPP_16_JAN_2007 - -#include "boost/python.hpp" -#include "boost/tuple/tuple.hpp" -#include "boost/python/object.hpp" //len function -#include <boost/mpl/int.hpp> -#include <boost/mpl/next.hpp> - -/** - * Converts boost::tuples::tuple<...> to\from Python tuple - * - * The conversion is done "on-the-fly", you should only register the conversion - * with your tuple classes. - * For example: - * - * typedef boost::tuples::tuple< int, double, std::string > triplet; - * boost::python::register_tuple< triplet >(); - * - * That's all. After this point conversion to\from next types will be handled - * by Boost.Python library: - * - * triplet - * triplet& ( return type only ) - * const triplet - * const triplet& - * - * Implementation description. - * The conversion uses Boost.Python custom r-value converters. r-value converters - * is very powerful and undocumented feature of the library. The only documentation - * we have is http://boost.org/libs/python/doc/v2/faq.html#custom_string . - * - * The conversion consists from two parts: "to" and "from". - * - * "To" conversion - * The "to" part is pretty easy and well documented ( http://docs.python.org/api/api.html ). - * You should use Python C API to create an instance of a class and than you - * initialize the relevant members of the instance. - * - * "From" conversion - * Lets start from analyzing one of the use case Boost.Python library have to - * deal with: - * - * void do_smth( const triplet& arg ){...} - * - * In order to allow calling this function from Python, the library should keep - * parameter "arg" alive until the function returns. In other words, the library - * should provide instances life-time management. The provided interface is not - * ideal and could be improved. You have to implement two functions: - * - * void* convertible( PyObject* obj ) - * Checks whether the "obj" could be converted to an instance of the desired - * class. If true, the function should return "obj", otherwise NULL - * - * void construct( PyObject* obj, converter::rvalue_from_python_stage1_data* data) - * Constructs the instance of the desired class. This function will be called - * if and only if "convertible" function returned true. The first argument - * is Python object, which was passed as parameter to "convertible" function. - * The second object is some kind of memory allocator for one object. Basically - * it keeps a memory chunk. You will use the memory for object allocation. - * - * For some unclear for me reason, the library implements "C style Inheritance" - * ( http://www.embedded.com/97/fe29712.htm ). So, in order to create new - * object in the storage you have to cast to the "right" class: - * - * typedef converter::rvalue_from_python_storage<your_type_t> storage_t; - * storage_t* the_storage = reinterpret_cast<storage_t*>( data ); - * void* memory_chunk = the_storage->storage.bytes; - * - * "memory_chunk" points to the memory, where the instance will be allocated. - * - * In order to create object at specific location, you should use placement new - * operator: - * - * your_type_t* instance = new (memory_chunk) your_type_t(); - * - * Now, you can continue to initialize the instance. - * - * instance->set_xyz = read xyz from obj - * - * If "your_type_t" constructor requires some arguments, "read" the Python - * object before you call the constructor: - * - * xyz_type xyz = read xyz from obj - * your_type_t* instance = new (memory_chunk) your_type_t(xyz); - * - * Hint: - * In most case you don't really need\have to work with C Python API. Let - * Boost.Python library to do some work for you! - * - **/ - -namespace boost{ namespace python{ - -namespace details{ - -//Small helper function, introduced to allow short syntax for index incrementing -template< int index> -typename mpl::next< mpl::int_< index > >::type increment_index(){ - typedef typename mpl::next< mpl::int_< index > >::type next_index_type; - return next_index_type(); -} - -} - -template< class TTuple > -struct to_py_tuple{ - - typedef mpl::int_< tuples::length< TTuple >::value > length_type; - - static PyObject* convert(const TTuple& c_tuple){ - list values; - //add all c_tuple items to "values" list - convert_impl( c_tuple, values, mpl::int_< 0 >(), length_type() ); - //create Python tuple from the list - return incref( python::tuple( values ).ptr() ); - } - -private: - - template< int index, int length > - static void - convert_impl( const TTuple &c_tuple, list& values, mpl::int_< index >, mpl::int_< length > ) { - values.append( c_tuple.template get< index >() ); - convert_impl( c_tuple, values, details::increment_index<index>(), length_type() ); - } - - template< int length > - static void - convert_impl( const TTuple&, list& values, mpl::int_< length >, mpl::int_< length >) - {} - -}; - - -template< class TTuple> -struct from_py_sequence{ - - typedef TTuple tuple_type; - - typedef mpl::int_< tuples::length< TTuple >::value > length_type; - - static void* - convertible(PyObject* py_obj){ - - if( !PySequence_Check( py_obj ) ){ - return 0; - } - - if( !PyObject_HasAttrString( py_obj, "__len__" ) ){ - return 0; - } - - python::object py_sequence( handle<>( borrowed( py_obj ) ) ); - - if( tuples::length< TTuple >::value != len( py_sequence ) ){ - return 0; - } - - if( convertible_impl( py_sequence, mpl::int_< 0 >(), length_type() ) ){ - return py_obj; - } - else{ - return 0; - } - } - - static void - construct( PyObject* py_obj, converter::rvalue_from_python_stage1_data* data){ - typedef converter::rvalue_from_python_storage<TTuple> storage_t; - storage_t* the_storage = reinterpret_cast<storage_t*>( data ); - void* memory_chunk = the_storage->storage.bytes; - TTuple* c_tuple = new (memory_chunk) TTuple(); - data->convertible = memory_chunk; - - python::object py_sequence( handle<>( borrowed( py_obj ) ) ); - construct_impl( py_sequence, *c_tuple, mpl::int_< 0 >(), length_type() ); - } - - static TTuple to_c_tuple( PyObject* py_obj ){ - if( !convertible( py_obj ) ){ - throw std::runtime_error( "Unable to construct boost::tuples::tuple from Python object!" ); - } - TTuple c_tuple; - python::object py_sequence( handle<>( borrowed( py_obj ) ) ); - construct_impl( py_sequence, c_tuple, mpl::int_< 0 >(), length_type() ); - return c_tuple; - } - -private: - - template< int index, int length > - static bool - convertible_impl( const python::object& py_sequence, mpl::int_< index >, mpl::int_< length > ){ - - typedef typename tuples::element< index, TTuple>::type element_type; - - object element = py_sequence[index]; - extract<element_type> type_checker( element ); - if( !type_checker.check() ){ - return false; - } - else{ - return convertible_impl( py_sequence, details::increment_index<index>(), length_type() ); - } - } - - template< int length > - static bool - convertible_impl( const python::object& py_sequence, mpl::int_< length >, mpl::int_< length > ){ - return true; - } - - template< int index, int length > - static void - construct_impl( const python::object& py_sequence, TTuple& c_tuple, mpl::int_< index >, mpl::int_< length > ){ - - typedef typename tuples::element< index, TTuple>::type element_type; - - object element = py_sequence[index]; - c_tuple.template get< index >() = extract<element_type>( element ); - - construct_impl( py_sequence, c_tuple, details::increment_index<index>(), length_type() ); - } - - template< int length > - static void - construct_impl( const python::object& py_sequence, TTuple& c_tuple, mpl::int_< length >, mpl::int_< length > ) - {} - -}; - -template< class TTuple> -void register_tuple(){ - - to_python_converter< TTuple, to_py_tuple<TTuple> >(); - - converter::registry::push_back( &from_py_sequence<TTuple>::convertible - , &from_py_sequence<TTuple>::construct - , type_id<TTuple>() ); -}; - -} } //boost::python - -#endif//TUPLES_HPP_16_JAN_2007 diff --git a/src/pyutils.h b/src/pyutils.h new file mode 100644 index 00000000..54d6fa28 --- /dev/null +++ b/src/pyutils.h @@ -0,0 +1,189 @@ +/* + * 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. + */ + +#ifndef _PY_UTILS_H +#define _PY_UTILS_H + +template <typename T, typename TfromPy> +struct object_from_python +{ + object_from_python() { + boost::python::converter::registry::insert + (&TfromPy::convertible, &TfromPy::construct, + boost::python::type_id<T>()); + } +}; + +template <typename T, typename TtoPy, typename TfromPy> +struct register_python_conversion +{ + register_python_conversion() { + boost::python::to_python_converter<T, TtoPy>(); + object_from_python<T, TfromPy>(); + } +}; + +template <typename T> +struct register_optional_to_python : public boost::noncopyable +{ + struct optional_to_python + { + static PyObject * convert(const boost::optional<T>& value) + { + return boost::python::incref + (value ? boost::python::to_python_value<T>()(*value) : + boost::python::detail::none()); + } + }; + + struct optional_from_python + { + static void * convertible(PyObject * source) + { + using namespace boost::python::converter; + + if (source == Py_None) + return source; + + const registration& converters(registered<T>::converters); + + if (implicit_rvalue_convertible_from_python(source, converters)) { + rvalue_from_python_stage1_data data = + rvalue_from_python_stage1(source, converters); + return rvalue_from_python_stage2(source, data, converters); + } + return NULL; + } + + static void construct(PyObject * source, + boost::python::converter::rvalue_from_python_stage1_data * data) + { + using namespace boost::python::converter; + + void * const storage = + reinterpret_cast<rvalue_from_python_storage<T> *>(data)->storage.bytes; + + if (data->convertible == source) // == None + new (storage) boost::optional<T>(); // A Boost uninitialized value + else + new (storage) boost::optional<T>(*reinterpret_cast<T *>(data->convertible)); + + data->convertible = storage; + } + }; + + explicit register_optional_to_python() { + register_python_conversion<boost::optional<T>, + optional_to_python, optional_from_python>(); + } +}; + +template <typename T1, typename T2> +struct PairToTupleConverter +{ + static PyObject * convert(const std::pair<T1, T2>& pair) { + return boost::python::incref + (boost::python::make_tuple(pair.first, pair.second).ptr()); + } +}; + +template <typename MapType> +struct map_value_type_converter +{ + map_value_type_converter() { + boost::python::to_python_converter + <typename MapType::value_type, + PairToTupleConverter<const typename MapType::key_type, + typename MapType::mapped_type> >(); + } +}; + +template <typename T> +PyObject * str_to_py_unicode(const T& str) +{ + using namespace boost::python; + PyObject * pstr = PyString_FromString(str.c_str()); + PyObject * uni = PyUnicode_FromEncodedObject(pstr, "UTF-8", NULL); + return object(handle<>(borrowed(uni))).ptr(); +} + +namespace boost { namespace python { + +// Use expr to create the PyObject corresponding to x +# define BOOST_PYTHON_RETURN_TO_PYTHON_BY_VALUE(T, expr, pytype)\ + template <> struct to_python_value<T&> \ + : detail::builtin_to_python \ + { \ + inline PyObject* operator()(T const& x) const \ + { \ + return (expr); \ + } \ + inline PyTypeObject const* get_pytype() const \ + { \ + return (pytype); \ + } \ + }; \ + template <> struct to_python_value<T const&> \ + : detail::builtin_to_python \ + { \ + inline PyObject* operator()(T const& x) const \ + { \ + return (expr); \ + } \ + inline PyTypeObject const* get_pytype() const \ + { \ + return (pytype); \ + } \ + }; + +# define BOOST_PYTHON_ARG_TO_PYTHON_BY_VALUE(T, expr) \ + namespace converter \ + { \ + template <> struct arg_to_python< T > \ + : handle<> \ + { \ + arg_to_python(T const& x) \ + : python::handle<>(expr) {} \ + }; \ + } + +// Specialize argument and return value converters for T using expr +# define BOOST_PYTHON_TO_PYTHON_BY_VALUE(T, expr, pytype) \ + BOOST_PYTHON_RETURN_TO_PYTHON_BY_VALUE(T,expr, pytype) \ + BOOST_PYTHON_ARG_TO_PYTHON_BY_VALUE(T,expr) + +BOOST_PYTHON_TO_PYTHON_BY_VALUE(ledger::string, ::PyUnicode_FromEncodedObject(::PyString_FromString(x.c_str()), "UTF-8", NULL), &PyUnicode_Type) + +} } // namespace boost::python + +//boost::python::register_ptr_to_python< boost::shared_ptr<Base> >(); + +#endif // _PY_UTILS_H diff --git a/src/query.cc b/src/query.cc new file mode 100644 index 00000000..cfa321b0 --- /dev/null +++ b/src/query.cc @@ -0,0 +1,455 @@ +/* + * 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(); + } + } + + 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) { + 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 '=': ++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 == "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_next_arg = 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.finish) { + 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.finish); + 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 arg2 = new expr_t::op_t(expr_t::op_t::VALUE); + assert(tok.value); + arg2->set_value(mask_t(*tok.value)); + + node->set_right(expr_t::op_t::new_node + (expr_t::op_t::O_SEQ, + expr_t::op_t::new_node + (expr_t::op_t::O_CONS, arg1, arg2))); + } 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(false); break; + } + + expr_t::ptr_op_t mask = new expr_t::op_t(expr_t::op_t::VALUE); + DEBUG("query.mask", "Mask from string: " << *tok.value); + mask->set_value(mask_t(*tok.value)); + DEBUG("query.mask", "Mask is: " << mask->as_value().as_mask().str()); + + 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 diff --git a/src/query.h b/src/query.h new file mode 100644 index 00000000..ebc14020 --- /dev/null +++ b/src/query.h @@ -0,0 +1,305 @@ +/* + * 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. + */ + +/** + * @addtogroup expr + */ + +/** + * @file predicate.h + * @author John Wiegley + * + * @ingroup expr + */ +#ifndef _QUERY_H +#define _QUERY_H + +#include "predicate.h" + +namespace ledger { + +class query_t : public predicate_t +{ +public: + class lexer_t + { + friend class query_t; + friend class parser_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; + bool consume_next_arg; + + public: + struct token_t + { + enum kind_t { + UNKNOWN, + + LPAREN, + RPAREN, + + TOK_NOT, + TOK_AND, + TOK_OR, + TOK_EQ, + + TOK_DATE, + TOK_CODE, + TOK_PAYEE, + TOK_NOTE, + TOK_ACCOUNT, + TOK_META, + TOK_EXPR, + + TERM, + + END_REACHED + + } kind; + + optional<string> value; + + explicit token_t(kind_t _kind = UNKNOWN, + const optional<string>& _value = none) + : kind(_kind), value(_value) { + TRACE_CTOR(query_t::lexer_t::token_t, ""); + } + token_t(const token_t& tok) + : kind(tok.kind), value(tok.value) { + TRACE_CTOR(query_t::lexer_t::token_t, "copy"); + } + ~token_t() throw() { + TRACE_DTOR(query_t::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_DATE: return "TOK_DATE"; + case TOK_CODE: return "TOK_CODE"; + case TOK_PAYEE: return "TOK_PAYEE"; + case TOK_NOTE: return "TOK_NOTE"; + case TOK_ACCOUNT: return "TOK_ACCOUNT"; + case TOK_META: return "TOK_META"; + case TOK_EXPR: return "TOK_EXPR"; + case TERM: return string("TERM(") + *value + ")"; + case END_REACHED: return "END_REACHED"; + } + assert(false); + return empty_string; + } + + 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_DATE: return "date"; + case TOK_CODE: return "code"; + case TOK_PAYEE: return "payee"; + case TOK_NOTE: return "note"; + case TOK_ACCOUNT: return "account"; + case TOK_META: return "meta"; + case TOK_EXPR: return "expr"; + + case END_REACHED: return "<EOF>"; + + case TERM: + assert(false); + return "<TERM>"; + + case UNKNOWN: + default: + assert(false); + return "<UNKNOWN>"; + } + } + + void unexpected(); + void expected(char wanted, char c = '\0'); + }; + + token_t token_cache; + + lexer_t(value_t::sequence_t::const_iterator _begin, + value_t::sequence_t::const_iterator _end) + : begin(_begin), end(_end), + consume_whitespace(false), + consume_next_arg(false) + { + TRACE_CTOR(query_t::lexer_t, ""); + assert(begin != end); + arg_i = (*begin).as_string().begin(); + arg_end = (*begin).as_string().end(); + } + lexer_t(const lexer_t& lexer) + : 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(query_t::lexer_t, "copy"); + } + ~lexer_t() throw() { + TRACE_DTOR(query_t::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; + } + }; + +protected: + class parser_t + { + friend class query_t; + + value_t args; + lexer_t lexer; + + expr_t::ptr_op_t parse_query_term(lexer_t::token_t::kind_t tok_context); + expr_t::ptr_op_t parse_unary_expr(lexer_t::token_t::kind_t tok_context); + expr_t::ptr_op_t parse_and_expr(lexer_t::token_t::kind_t tok_context); + expr_t::ptr_op_t parse_or_expr(lexer_t::token_t::kind_t tok_context); + expr_t::ptr_op_t parse_query_expr(lexer_t::token_t::kind_t tok_context); + + public: + parser_t(const value_t& _args) + : args(_args), lexer(args.begin(), args.end()) { + TRACE_CTOR(query_t::parser_t, ""); + } + parser_t(const parser_t& parser) + : args(parser.args), lexer(parser.lexer) { + TRACE_CTOR(query_t::parser_t, "copy"); + } + ~parser_t() throw() { + TRACE_DTOR(query_t::parser_t); + } + + expr_t::ptr_op_t parse() { + return parse_query_expr(lexer_t::token_t::TOK_ACCOUNT); + } + + bool tokens_remaining() { + lexer_t::token_t tok = lexer.peek_token(); + assert(tok.kind != lexer_t::token_t::UNKNOWN); + return tok.kind != lexer_t::token_t::END_REACHED; + } + }; + + optional<parser_t> parser; + +public: + query_t() { + TRACE_CTOR(query_t, ""); + } + query_t(const query_t& other) + : predicate_t(other) { + TRACE_CTOR(query_t, "copy"); + } + query_t(const string& arg, + const keep_details_t& _what_to_keep = keep_details_t()) + : predicate_t(_what_to_keep) { + TRACE_CTOR(query_t, "string, keep_details_t"); + if (! arg.empty()) { + value_t temp(string_value(arg)); + parse_args(temp.to_sequence()); + } + } + query_t(const value_t& args, + const keep_details_t& _what_to_keep = keep_details_t()) + : predicate_t(_what_to_keep) { + TRACE_CTOR(query_t, "value_t, keep_details_t"); + if (! args.empty()) + parse_args(args); + } + virtual ~query_t() { + TRACE_DTOR(query_t); + } + + void parse_args(const value_t& args) { + if (! parser) + parser = parser_t(args); + ptr = parser->parse(); // expr_t::ptr + } + + void parse_again() { + assert(parser); + ptr = parser->parse(); // expr_t::ptr + } + + bool tokens_remaining() { + return parser && parser->tokens_remaining(); + } + + virtual string text() { + return print_to_str(); + } +}; + +} // namespace ledger + +#endif // _QUERY_H diff --git a/src/quotes.cc b/src/quotes.cc new file mode 100644 index 00000000..f892e93f --- /dev/null +++ b/src/quotes.cc @@ -0,0 +1,109 @@ +/* + * 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 "amount.h" +#include "commodity.h" +#include "pool.h" +#include "quotes.h" + +namespace ledger { + +optional<price_point_t> +commodity_quote_from_script(commodity_t& commodity, + const optional<commodity_t&>& exchange_commodity) +{ + DEBUG("commodity.download", "downloading quote for symbol " << commodity.symbol()); +#if defined(DEBUG_ON) + if (exchange_commodity) + DEBUG("commodity.download", + " in terms of commodity " << exchange_commodity->symbol()); +#endif + + char buf[256]; + buf[0] = '\0'; + + string getquote_cmd("getquote \""); + getquote_cmd += commodity.symbol(); + getquote_cmd += "\" \""; + if (exchange_commodity) + getquote_cmd += exchange_commodity->symbol(); + getquote_cmd += "\""; + + DEBUG("commodity.download", "invoking command: " << getquote_cmd); + + bool success = true; + if (FILE * fp = popen(getquote_cmd.c_str(), "r")) { + if (std::feof(fp) || ! std::fgets(buf, 255, fp)) + success = false; + if (pclose(fp) != 0) + success = false; + } else { + success = false; + } + + if (success && buf[0]) { + if (char * p = std::strchr(buf, '\n')) *p = '\0'; + DEBUG("commodity.download", "downloaded quote: " << buf); + + if (optional<std::pair<commodity_t *, price_point_t> > point = + commodity_pool_t::current_pool->parse_price_directive(buf)) { + if (commodity_pool_t::current_pool->price_db) { +#if defined(__GNUG__) && __GNUG__ < 3 + ofstream database(*commodity_pool_t::current_pool->price_db, + ios::out | ios::app); +#else + ofstream database(*commodity_pool_t::current_pool->price_db, + std::ios_base::out | std::ios_base::app); +#endif + database << "P " + << format_datetime(point->second.when, FMT_WRITTEN) + << " " << commodity.symbol() + << " " << point->second.price + << std::endl; + } + return point->second; + } + } else { + DEBUG("commodity.download", + "Failed to download price for '" << commodity.symbol() << + "' (command: \"getquote " << commodity.symbol() << + " " << (exchange_commodity ? + exchange_commodity->symbol() : "''") << "\")"); + + // Don't try to download this commodity again. + commodity.add_flags(COMMODITY_NOMARKET); + } + return none; +} + +} // namespace ledger diff --git a/src/python/pyledger.h b/src/quotes.h index 3ab82558..d00c5bfd 100644 --- a/src/python/pyledger.h +++ b/src/quotes.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2007, John Wiegley. All rights reserved. + * 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 @@ -29,19 +29,25 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef _PYLEDGER_H -#define _PYLEDGER_H +/** + * @addtogroup extra + */ + +/** + * @file quotes.h + * @author John Wiegley + * + * @ingroup extra + */ +#ifndef _QUOTES_H +#define _QUOTES_H + +namespace ledger { -////////////////////////////////////////////////////////////////////// -// -// Ledger Accounting Tool (with Python support via Boost.Python) -// -// A command-line tool for general double-entry accounting. -// -// Copyright (c) 2003,2004 John Wiegley <johnw@newartisans.com> -// +optional<price_point_t> +commodity_quote_from_script(commodity_t& commodity, + const optional<commodity_t&>& exchange_commodity); -#include <ledger.h> -#include <pyinterp.h> +} // namespace ledger -#endif // _PYLEDGER_H +#endif // _QUOTES_H diff --git a/src/report.cc b/src/report.cc new file mode 100644 index 00000000..2d9d7cc6 --- /dev/null +++ b/src/report.cc @@ -0,0 +1,1307 @@ +/* + * 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 "report.h" +#include "session.h" +#include "pool.h" +#include "format.h" +#include "query.h" +#include "output.h" +#include "iterators.h" +#include "filters.h" +#include "precmd.h" +#include "stats.h" +#include "generate.h" +#include "draft.h" +#include "xml.h" +#include "emacs.h" + +namespace ledger { + +void report_t::normalize_options(const string& verb) +{ + // Patch up some of the reporting options based on what kind of + // command it was. + +#ifdef HAVE_ISATTY + if (! HANDLED(force_color)) { + if (! HANDLED(no_color) && isatty(STDOUT_FILENO)) + HANDLER(color).on_only(string("?normalize")); + if (HANDLED(color) && ! isatty(STDOUT_FILENO)) + HANDLER(color).off(); + } + if (! HANDLED(force_pager)) { + if (HANDLED(pager_) && ! isatty(STDOUT_FILENO)) + HANDLER(pager_).off(); + } +#endif + + item_t::use_effective_date = (HANDLED(effective) && + ! HANDLED(actual_dates)); + + commodity_pool_t::current_pool->keep_base = HANDLED(base); + commodity_pool_t::current_pool->get_quotes = session.HANDLED(download); + + if (session.HANDLED(price_exp_)) + commodity_pool_t::current_pool->quote_leeway = + session.HANDLER(price_exp_).value.as_long(); + + if (session.HANDLED(price_db_)) + commodity_pool_t::current_pool->price_db = session.HANDLER(price_db_).str(); + else + commodity_pool_t::current_pool->price_db = none; + + if (HANDLED(date_format_)) + set_date_format(HANDLER(date_format_).str().c_str()); + if (HANDLED(datetime_format_)) + set_datetime_format(HANDLER(datetime_format_).str().c_str()); + if (HANDLED(start_of_week_)) { + if (optional<date_time::weekdays> weekday = + string_to_day_of_week(HANDLER(start_of_week_).str())) + start_of_week = *weekday; + } + + if (verb == "print" || verb == "xact" || verb == "dump") { + HANDLER(related).on_only(string("?normalize")); + HANDLER(related_all).on_only(string("?normalize")); + } + else if (verb == "equity") { + HANDLER(equity).on_only(string("?normalize")); + } + + if (verb == "print") + HANDLER(limit_).on(string("?normalize"), "actual"); + + if (! HANDLED(empty)) + HANDLER(display_).on(string("?normalize"), "amount|(!post&total)"); + + if (verb[0] != 'b' && verb[0] != 'r') + HANDLER(base).on_only(string("?normalize")); + + // If a time period was specified with -p, check whether it also gave a + // begin and/or end to the report period (though these can be overridden + // using -b or -e). Then, if no _duration_ was specified (such as monthly), + // then ignore the period since the begin/end are the only interesting + // details. + if (HANDLED(period_)) { + if (! HANDLED(sort_all_)) + HANDLER(sort_xacts_).on_only(string("?normalize")); + + date_interval_t interval(HANDLER(period_).str()); + + optional<date_t> begin = interval.begin(session.current_year); + optional<date_t> end = interval.end(session.current_year); + + if (! HANDLED(begin_) && begin) { + string predicate = "date>=[" + to_iso_extended_string(*begin) + "]"; + HANDLER(limit_).on(string("?normalize"), predicate); + } + if (! HANDLED(end_) && end) { + string predicate = "date<[" + to_iso_extended_string(*end) + "]"; + HANDLER(limit_).on(string("?normalize"), predicate); + } + + if (! interval.duration) + HANDLER(period_).off(); + } + + // If -j or -J were specified, set the appropriate format string now so as + // to avoid option ordering issues were we to have done it during the + // initial parsing of the options. + if (HANDLED(amount_data)) { + HANDLER(format_) + .on_with(string("?normalize"), HANDLER(plot_amount_format_).value); + } + else if (HANDLED(total_data)) { + HANDLER(format_) + .on_with(string("?normalize"), HANDLER(plot_total_format_).value); + } + + // If the --exchange (-X) option was used, parse out any final price + // settings that may be there. + if (HANDLED(exchange_) && + HANDLER(exchange_).str().find('=') != string::npos) { + value_t(0L).exchange_commodities(HANDLER(exchange_).str(), true, + terminus); + } + + long cols = 0; + if (HANDLED(columns_)) + cols = HANDLER(columns_).value.to_long(); + else if (const char * columns = std::getenv("COLUMNS")) + cols = lexical_cast<long>(columns); + else + cols = 80L; + + if (cols > 0) { + DEBUG("auto.columns", "cols = " << cols); + + if (! HANDLER(date_width_).specified) + HANDLER(date_width_) + .on_with(none, static_cast<long>(format_date(CURRENT_DATE(), + FMT_PRINTED).length())); + + long date_width = HANDLER(date_width_).value.to_long(); + long payee_width = (HANDLER(payee_width_).specified ? + HANDLER(payee_width_).value.to_long() : + int(double(cols) * 0.263157)); + long account_width = (HANDLER(account_width_).specified ? + HANDLER(account_width_).value.to_long() : + int(double(cols) * 0.302631)); + long amount_width = (HANDLER(amount_width_).specified ? + HANDLER(amount_width_).value.to_long() : + int(double(cols) * 0.157894)); + long total_width = (HANDLER(total_width_).specified ? + HANDLER(total_width_).value.to_long() : + amount_width); + + DEBUG("auto.columns", "date_width = " << date_width); + DEBUG("auto.columns", "payee_width = " << payee_width); + DEBUG("auto.columns", "account_width = " << account_width); + DEBUG("auto.columns", "amount_width = " << amount_width); + DEBUG("auto.columns", "total_width = " << total_width); + + if (! HANDLER(date_width_).specified && + ! HANDLER(payee_width_).specified && + ! HANDLER(account_width_).specified && + ! HANDLER(amount_width_).specified && + ! HANDLER(total_width_).specified) { + long total = (4 /* the spaces between */ + date_width + payee_width + + account_width + amount_width + total_width); + if (total > cols) { + DEBUG("auto.columns", "adjusting account down"); + account_width -= total - cols; + DEBUG("auto.columns", "account_width now = " << account_width); + } + } + + if (! HANDLER(date_width_).specified) + HANDLER(date_width_).on_with(string("?normalize"), date_width); + if (! HANDLER(payee_width_).specified) + HANDLER(payee_width_).on_with(string("?normalize"), payee_width); + if (! HANDLER(account_width_).specified) + HANDLER(account_width_).on_with(string("?normalize"), account_width); + if (! HANDLER(amount_width_).specified) + HANDLER(amount_width_).on_with(string("?normalize"), amount_width); + if (! HANDLER(total_width_).specified) + HANDLER(total_width_).on_with(string("?normalize"), total_width); + } +} + +void report_t::parse_query_args(const value_t& args, const string& whence) +{ + query_t query(args, what_to_keep()); + if (query) { + HANDLER(limit_).on(whence, query.text()); + + DEBUG("report.predicate", + "Predicate = " << HANDLER(limit_).str()); + } + + if (query.tokens_remaining()) { + query.parse_again(); + if (query) { + HANDLER(display_).on(whence, query.text()); + + DEBUG("report.predicate", + "Display predicate = " << HANDLER(display_).str()); + } + } +} + +void report_t::posts_report(post_handler_ptr handler) +{ + journal_posts_iterator walker(*session.journal.get()); + pass_down_posts(chain_post_handlers(*this, handler), walker); + session.journal->clear_xdata(); +} + +void report_t::generate_report(post_handler_ptr handler) +{ + HANDLER(limit_).on(string("#generate"), "actual"); + + generate_posts_iterator walker + (session, HANDLED(seed_) ? + static_cast<unsigned int>(HANDLER(seed_).value.to_long()) : 0, + HANDLED(head_) ? + static_cast<unsigned int>(HANDLER(head_).value.to_long()) : 50); + + pass_down_posts(chain_post_handlers(*this, handler), walker); +} + +void report_t::xact_report(post_handler_ptr handler, xact_t& xact) +{ + xact_posts_iterator walker(xact); + pass_down_posts(chain_post_handlers(*this, handler), walker); + xact.clear_xdata(); +} + +void report_t::accounts_report(acct_handler_ptr handler) +{ + journal_posts_iterator walker(*session.journal.get()); + + // The lifetime of the chain object controls the lifetime of all temporary + // objects created within it during the call to pass_down_posts, which will + // be needed later by the pass_down_accounts. + post_handler_ptr chain = + chain_post_handlers(*this, post_handler_ptr(new ignore_posts), true); + pass_down_posts(chain, walker); + + HANDLER(amount_).expr.mark_uncompiled(); + HANDLER(total_).expr.mark_uncompiled(); + HANDLER(display_amount_).expr.mark_uncompiled(); + HANDLER(display_total_).expr.mark_uncompiled(); + HANDLER(revalued_total_).expr.mark_uncompiled(); + + scoped_ptr<accounts_iterator> iter; + if (! HANDLED(sort_)) { + iter.reset(new basic_accounts_iterator(*session.journal->master)); + } else { + expr_t sort_expr(HANDLER(sort_).str()); + sort_expr.set_context(this); + iter.reset(new sorted_accounts_iterator(*session.journal->master, + sort_expr, HANDLED(flat))); + } + + if (HANDLED(display_)) + pass_down_accounts(handler, *iter.get(), + predicate_t(HANDLER(display_).str(), what_to_keep()), + *this); + else + pass_down_accounts(handler, *iter.get()); + + session.journal->clear_xdata(); +} + +void report_t::commodities_report(post_handler_ptr handler) +{ + posts_commodities_iterator walker(*session.journal.get()); + pass_down_posts(chain_post_handlers(*this, handler), walker); + session.journal->clear_xdata(); +} + +value_t report_t::fn_amount_expr(call_scope_t& scope) +{ + return HANDLER(amount_).expr.calc(scope); +} + +value_t report_t::fn_total_expr(call_scope_t& scope) +{ + return HANDLER(total_).expr.calc(scope); +} + +value_t report_t::fn_display_amount(call_scope_t& scope) +{ + return HANDLER(display_amount_).expr.calc(scope); +} + +value_t report_t::fn_display_total(call_scope_t& scope) +{ + return HANDLER(display_total_).expr.calc(scope); +} + +value_t report_t::fn_market(call_scope_t& scope) +{ + interactive_t args(scope, "a&ts"); + + value_t result; + optional<datetime_t> moment = (args.has(1) ? + args.get<datetime_t>(1) : + optional<datetime_t>()); + if (args.has(2)) + result = args.value_at(0).exchange_commodities(args.get<string>(2), + /* add_prices= */ false, + moment); + else + result = args.value_at(0).value(true, moment); + + if (! result.is_null()) + return result; + + return args.value_at(0); +} + +value_t report_t::fn_get_at(call_scope_t& scope) +{ + interactive_t args(scope, "Sl"); + + DEBUG("report.get_at", "get_at[0] = " << args.value_at(0)); + DEBUG("report.get_at", "get_at[1] = " << args.value_at(1)); + + if (args.get<long>(1) == 0) { + if (! args.value_at(0).is_sequence()) + return args.value_at(0); + } else { + if (! args.value_at(0).is_sequence()) + throw_(std::runtime_error, + _("Attempting to get argument at index %1 from %2") + << args.get<long>(1) << args.value_at(0).label()); + } + return args.get<const value_t::sequence_t&>(0)[args.get<long>(1)]; +} + +value_t report_t::fn_is_seq(call_scope_t& scope) +{ + return scope.value().is_sequence(); +} + +value_t report_t::fn_strip(call_scope_t& args) +{ + return args.value().strip_annotations(what_to_keep()); +} + +value_t report_t::fn_trim(call_scope_t& args) +{ + string temp(args.value().to_string()); + scoped_array<char> buf(new char[temp.length() + 1]); + std::strcpy(buf.get(), temp.c_str()); + + const char * p = buf.get(); + while (*p && std::isspace(*p)) + p++; + + const char * e = buf.get() + temp.length(); + while (e > p && std::isspace(*e)) + e--; + + if (e == p) { + return string_value(empty_string); + } + else if (e < p) { + assert(false); + return string_value(empty_string); + } + else { + return string_value(string(p, e - p)); + } +} + +value_t report_t::fn_scrub(call_scope_t& args) +{ + value_t temp(args.value().strip_annotations(what_to_keep())); + if (HANDLED(base)) + return temp; + else + return temp.unreduced(); +} + +value_t report_t::fn_rounded(call_scope_t& args) +{ + return args.value().rounded(); +} + +value_t report_t::fn_unrounded(call_scope_t& args) +{ + return args.value().unrounded(); +} + +value_t report_t::fn_quantity(call_scope_t& scope) +{ + interactive_t args(scope, "a"); + return args.get<amount_t>(0).number(); +} + +value_t report_t::fn_floor(call_scope_t& scope) +{ + interactive_t args(scope, "v"); + return args.value_at(0).floored(); +} + +value_t report_t::fn_abs(call_scope_t& scope) +{ + interactive_t args(scope, "v"); + return args.value_at(0).abs(); +} + +value_t report_t::fn_truncated(call_scope_t& scope) +{ + interactive_t args(scope, "v&ll"); + return string_value(format_t::truncate + (args.get<string>(0), + args.has(1) && args.get<int>(1) > 0 ? args.get<int>(1) : 0, + args.has(2) ? args.get<int>(2) : 0)); +} + +value_t report_t::fn_justify(call_scope_t& scope) +{ + interactive_t args(scope, "vl&lbb"); + std::ostringstream out; + args.value_at(0) + .print(out, args.get<int>(1), + args.has(2) ? args.get<int>(2) : -1, + args.has(3) ? args.get<bool>(3) : false, + args.has(4) ? args.get<bool>(4) : false); + return string_value(out.str()); +} + +value_t report_t::fn_quoted(call_scope_t& scope) +{ + interactive_t args(scope, "s"); + std::ostringstream out; + + out << '"'; + foreach (const char ch, args.get<string>(0)) { + if (ch == '"') + out << "\\\""; + else + out << ch; + } + out << '"'; + + return string_value(out.str()); +} + +value_t report_t::fn_join(call_scope_t& scope) +{ + interactive_t args(scope, "s"); + std::ostringstream out; + + foreach (const char ch, args.get<string>(0)) { + if (ch != '\n') + out << ch; + else + out << "\\n"; + } + return string_value(out.str()); +} + +value_t report_t::fn_format_date(call_scope_t& scope) +{ + interactive_t args(scope, "d&s"); + if (args.has(1)) + return string_value(format_date(args.get<date_t>(0), FMT_CUSTOM, + args.get<string>(1).c_str())); + else + return string_value(format_date(args.get<date_t>(0), FMT_PRINTED)); +} + +value_t report_t::fn_ansify_if(call_scope_t& scope) +{ + interactive_t args(scope, "v&s"); + + if (args.has(1)) { + string color = args.get<string>(1); + std::ostringstream buf; + if (color == "black") buf << "\033[30m"; + else if (color == "red") buf << "\033[31m"; + else if (color == "green") buf << "\033[32m"; + else if (color == "yellow") buf << "\033[33m"; + else if (color == "blue") buf << "\033[34m"; + else if (color == "magenta") buf << "\033[35m"; + else if (color == "cyan") buf << "\033[36m"; + else if (color == "white") buf << "\033[37m"; + else if (color == "bold") buf << "\033[1m"; + else if (color == "underline") buf << "\033[4m"; + else if (color == "blink") buf << "\033[5m"; + buf << args.value_at(0); + buf << "\033[0m"; + return string_value(buf.str()); + } else { + return args.value_at(0); + } +} + +value_t report_t::fn_percent(call_scope_t& scope) +{ + interactive_t args(scope, "aa"); + return (amount_t("100.00%") * + (args.get<amount_t>(0) / args.get<amount_t>(1)).number()); +} + +value_t report_t::fn_price(call_scope_t& scope) +{ + interactive_t args(scope, "v"); + return args.value_at(0).price(); +} + +value_t report_t::fn_lot_date(call_scope_t& scope) +{ + interactive_t args(scope, "v"); + if (args.value_at(0).has_annotation()) { + const annotation_t& details(args.value_at(0).annotation()); + if (details.date) + return *details.date; + } + return NULL_VALUE; +} + +value_t report_t::fn_lot_price(call_scope_t& scope) +{ + interactive_t args(scope, "v"); + if (args.value_at(0).has_annotation()) { + const annotation_t& details(args.value_at(0).annotation()); + if (details.price) + return *details.price; + } + return NULL_VALUE; +} + +value_t report_t::fn_lot_tag(call_scope_t& scope) +{ + interactive_t args(scope, "v"); + if (args.value_at(0).has_annotation()) { + const annotation_t& details(args.value_at(0).annotation()); + if (details.tag) + return string_value(*details.tag); + } + return NULL_VALUE; +} + +value_t report_t::fn_to_boolean(call_scope_t& scope) +{ + interactive_t args(scope, "v"); + args.value_at(0).in_place_cast(value_t::BOOLEAN); + return args.value_at(0); +} + +value_t report_t::fn_to_int(call_scope_t& scope) +{ + interactive_t args(scope, "v"); + args.value_at(0).in_place_cast(value_t::INTEGER); + return args.value_at(0); +} + +value_t report_t::fn_to_datetime(call_scope_t& scope) +{ + interactive_t args(scope, "v"); + args.value_at(0).in_place_cast(value_t::DATETIME); + return args.value_at(0); +} + +value_t report_t::fn_to_date(call_scope_t& scope) +{ + interactive_t args(scope, "v"); + args.value_at(0).in_place_cast(value_t::DATE); + return args.value_at(0); +} + +value_t report_t::fn_to_amount(call_scope_t& scope) +{ + interactive_t args(scope, "v"); + args.value_at(0).in_place_cast(value_t::AMOUNT); + return args.value_at(0); +} + +value_t report_t::fn_to_balance(call_scope_t& scope) +{ + interactive_t args(scope, "v"); + args.value_at(0).in_place_cast(value_t::BALANCE); + return args.value_at(0); +} + +value_t report_t::fn_to_string(call_scope_t& scope) +{ + interactive_t args(scope, "v"); + args.value_at(0).in_place_cast(value_t::STRING); + return args.value_at(0); +} + +value_t report_t::fn_to_mask(call_scope_t& scope) +{ + interactive_t args(scope, "v"); + args.value_at(0).in_place_cast(value_t::MASK); + return args.value_at(0); +} + +value_t report_t::fn_to_sequence(call_scope_t& scope) +{ + interactive_t args(scope, "v"); + args.value_at(0).in_place_cast(value_t::SEQUENCE); + return args.value_at(0); +} + +namespace { + value_t fn_black(call_scope_t&) { + return string_value("black"); + } + value_t fn_blink(call_scope_t&) { + return string_value("blink"); + } + value_t fn_blue(call_scope_t&) { + return string_value("blue"); + } + value_t fn_bold(call_scope_t&) { + return string_value("bold"); + } + value_t fn_cyan(call_scope_t&) { + return string_value("cyan"); + } + value_t fn_green(call_scope_t&) { + return string_value("green"); + } + value_t fn_magenta(call_scope_t&) { + return string_value("magenta"); + } + value_t fn_red(call_scope_t&) { + return string_value("red"); + } + value_t fn_underline(call_scope_t&) { + return string_value("underline"); + } + value_t fn_white(call_scope_t&) { + return string_value("white"); + } + value_t fn_yellow(call_scope_t&) { + return string_value("yellow"); + } + value_t fn_false(call_scope_t&) { + return false; + } + value_t fn_null(call_scope_t&) { + return NULL_VALUE; + } +} + +value_t report_t::reload_command(call_scope_t&) +{ + session.close_journal_files(); + session.read_journal_files(); + return true; +} + +value_t report_t::echo_command(call_scope_t& scope) +{ + interactive_t args(scope, "s"); + std::ostream& out(output_stream); + out << args.get<string>(0) << std::endl; + return true; +} + +option_t<report_t> * report_t::lookup_option(const char * p) +{ + switch (*p) { + case '%': + OPT_CH(percent); + break; + case 'A': + OPT_CH(average); + break; + case 'B': + OPT_CH(basis); + break; + case 'C': + OPT_CH(cleared); + break; + case 'D': + OPT_CH(daily); + break; + case 'E': + OPT_CH(empty); + break; + case 'F': + OPT_CH(format_); + break; + case 'G': + OPT_CH(gain); + break; + case 'I': + OPT_CH(price); + break; + case 'J': + OPT_CH(total_data); + break; + case 'L': + OPT_CH(actual); + break; + case 'M': + OPT_CH(monthly); + break; + case 'O': + OPT_CH(quantity); + break; + case 'P': + OPT_CH(by_payee); + break; + case 'R': + OPT_CH(real); + break; + case 'S': + OPT_CH(sort_); + break; + case 'T': + OPT_CH(total_); + break; + case 'U': + OPT_CH(uncleared); + break; + case 'V': + OPT_CH(market); + break; + case 'W': + OPT_CH(weekly); + break; + case 'X': + OPT_CH(exchange_); + break; + case 'Y': + OPT_CH(yearly); + break; + case 'a': + OPT(abbrev_len_); + else OPT_(account_); + else OPT(actual); + else OPT(actual_dates); + else OPT(add_budget); + else OPT(amount_); + else OPT(amount_data); + else OPT(anon); + else OPT_ALT(color, ansi); + else OPT(average); + else OPT(account_width_); + else OPT(amount_width_); + break; + case 'b': + OPT(balance_format_); + else OPT(base); + else OPT_ALT(basis, cost); + else OPT_(begin_); + else OPT(budget); + else OPT(by_payee); + break; + case 'c': + OPT(csv_format_); + else OPT(cleared); + else OPT(collapse); + else OPT(collapse_if_zero); + else OPT(color); + else OPT(columns_); + else OPT_ALT(basis, cost); + else OPT_(current); + break; + case 'd': + OPT(daily); + else OPT(date_); + else OPT(date_format_); + else OPT(datetime_format_); + else OPT(depth_); + else OPT(deviation); + else OPT_(display_); + else OPT(display_amount_); + else OPT(display_total_); + else OPT_ALT(dow, days-of-week); + else OPT(date_width_); + break; + case 'e': + OPT(effective); + else OPT(empty); + else OPT_(end_); + else OPT(equity); + else OPT(exact); + else OPT(exchange_); + break; + case 'f': + OPT(flat); + else OPT_ALT(forecast_while_, forecast_); + else OPT(forecast_years_); + else OPT(format_); + else OPT(force_color); + else OPT(force_pager); + else OPT_ALT(head_, first_); + break; + case 'g': + OPT(gain); + break; + case 'h': + OPT(head_); + break; + case 'i': + OPT(invert); + break; + case 'j': + OPT_CH(amount_data); + break; + case 'l': + OPT_(limit_); + else OPT(lot_dates); + else OPT(lot_prices); + else OPT(lot_tags); + else OPT(lots); + else OPT(lots_actual); + else OPT_ALT(tail_, last_); + break; + case 'm': + OPT(market); + else OPT(monthly); + break; + case 'n': + OPT_CH(collapse); + else OPT(no_color); + else OPT(no_total); + else OPT(now_); + break; + case 'o': + OPT(only_); + else OPT_(output_); + break; + case 'p': + OPT(pager_); + else OPT(payee_); + else OPT(pending); + else OPT(percent); + else OPT_(period_); + else OPT_ALT(sort_xacts_, period_sort_); + else OPT(plot_amount_format_); + else OPT(plot_total_format_); + else OPT(price); + else OPT(prices_format_); + else OPT(pricedb_format_); + else OPT(print_format_); + else OPT(payee_width_); + else OPT(prepend_format_); + break; + case 'q': + OPT(quantity); + else OPT(quarterly); + break; + case 'r': + OPT(raw); + else OPT(real); + else OPT(register_format_); + else OPT_(related); + else OPT(related_all); + else OPT(revalued); + else OPT(revalued_only); + else OPT(revalued_total_); + break; + case 's': + OPT(sort_); + else OPT(sort_all_); + else OPT(sort_xacts_); + else OPT_(subtotal); + else OPT(start_of_week_); + else OPT(seed_); + break; + case 't': + OPT_CH(amount_); + else OPT(tail_); + else OPT(total_); + else OPT(total_data); + else OPT(truncate_); + else OPT(total_width_); + break; + case 'u': + OPT(unbudgeted); + else OPT(uncleared); + else OPT(unrealized); + else OPT(unrealized_gains_); + else OPT(unrealized_losses_); + else OPT(unround); + else OPT(unsorted); + break; + case 'w': + OPT(weekly); + else OPT_(wide); + break; + case 'y': + OPT_CH(date_format_); + else OPT(yearly); + break; + } + return NULL; +} + +void report_t::define(const symbol_t::kind_t kind, const string& name, + expr_t::ptr_op_t def) +{ + session.define(kind, name, def); +} + +expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind, + const string& name) +{ + if (expr_t::ptr_op_t def = session.lookup(kind, name)) + return def; + + const char * p = name.c_str(); + + switch (kind) { + case symbol_t::FUNCTION: + // Support 2.x's single-letter value expression names. + if (*(p + 1) == '\0') { + switch (*p) { + case 'd': + case 'm': + return MAKE_FUNCTOR(report_t::fn_now); + case 'P': + return MAKE_FUNCTOR(report_t::fn_market); + case 't': + return MAKE_FUNCTOR(report_t::fn_display_amount); + case 'T': + return MAKE_FUNCTOR(report_t::fn_display_total); + case 'U': + return MAKE_FUNCTOR(report_t::fn_abs); + case 'S': + return MAKE_FUNCTOR(report_t::fn_strip); + case 'i': + throw_(std::runtime_error, + _("The i value expression variable is no longer supported")); + case 'A': + throw_(std::runtime_error, + _("The A value expression variable is no longer supported")); + case 'v': + case 'V': + throw_(std::runtime_error, + _("The V and v value expression variables are no longer supported")); + case 'I': + case 'B': + throw_(std::runtime_error, + _("The I and B value expression variables are no longer supported")); + case 'g': + case 'G': + throw_(std::runtime_error, + _("The G and g value expression variables are no longer supported")); + default: + return NULL; + } + } + + switch (*p) { + case 'a': + if (is_eq(p, "amount_expr")) + return MAKE_FUNCTOR(report_t::fn_amount_expr); + else if (is_eq(p, "ansify_if")) + return MAKE_FUNCTOR(report_t::fn_ansify_if); + else if (is_eq(p, "abs")) + return MAKE_FUNCTOR(report_t::fn_abs); + break; + + case 'b': + if (is_eq(p, "black")) + return WRAP_FUNCTOR(fn_black); + else if (is_eq(p, "blink")) + return WRAP_FUNCTOR(fn_blink); + else if (is_eq(p, "blue")) + return WRAP_FUNCTOR(fn_blue); + else if (is_eq(p, "bold")) + return WRAP_FUNCTOR(fn_bold); + break; + + case 'c': + if (is_eq(p, "cyan")) + return WRAP_FUNCTOR(fn_cyan); + break; + + case 'd': + if (is_eq(p, "display_amount")) + return MAKE_FUNCTOR(report_t::fn_display_amount); + else if (is_eq(p, "display_total")) + return MAKE_FUNCTOR(report_t::fn_display_total); + else if (is_eq(p, "date")) + return MAKE_FUNCTOR(report_t::fn_now); + break; + + case 'f': + if (is_eq(p, "format_date")) + return MAKE_FUNCTOR(report_t::fn_format_date); + else if (is_eq(p, "floor")) + return MAKE_FUNCTOR(report_t::fn_floor); + break; + + case 'g': + if (is_eq(p, "get_at")) + return MAKE_FUNCTOR(report_t::fn_get_at); + else if (is_eq(p, "green")) + return WRAP_FUNCTOR(fn_green); + break; + + case 'i': + if (is_eq(p, "is_seq")) + return MAKE_FUNCTOR(report_t::fn_is_seq); + break; + + case 'j': + if (is_eq(p, "justify")) + return MAKE_FUNCTOR(report_t::fn_justify); + else if (is_eq(p, "join")) + return MAKE_FUNCTOR(report_t::fn_join); + break; + + case 'm': + if (is_eq(p, "market")) + return MAKE_FUNCTOR(report_t::fn_market); + else if (is_eq(p, "magenta")) + return WRAP_FUNCTOR(fn_magenta); + break; + + case 'n': + if (is_eq(p, "null")) + return WRAP_FUNCTOR(fn_null); + else if (is_eq(p, "now")) + return MAKE_FUNCTOR(report_t::fn_now); + break; + + case 'o': + if (is_eq(p, "options")) + return MAKE_FUNCTOR(report_t::fn_options); + break; + + case 'p': + if (is_eq(p, "post")) + return WRAP_FUNCTOR(fn_false); + else if (is_eq(p, "percent")) + return MAKE_FUNCTOR(report_t::fn_percent); + else if (is_eq(p, "price")) + return MAKE_FUNCTOR(report_t::fn_price); + break; + + case 'q': + if (is_eq(p, "quoted")) + return MAKE_FUNCTOR(report_t::fn_quoted); + else if (is_eq(p, "quantity")) + return MAKE_FUNCTOR(report_t::fn_quantity); + break; + + case 'r': + if (is_eq(p, "rounded")) + return MAKE_FUNCTOR(report_t::fn_rounded); + else if (is_eq(p, "red")) + return WRAP_FUNCTOR(fn_red); + break; + + case 's': + if (is_eq(p, "scrub")) + return MAKE_FUNCTOR(report_t::fn_scrub); + else if (is_eq(p, "strip")) + return MAKE_FUNCTOR(report_t::fn_strip); + break; + + case 't': + if (is_eq(p, "truncated")) + return MAKE_FUNCTOR(report_t::fn_truncated); + else if (is_eq(p, "total_expr")) + return MAKE_FUNCTOR(report_t::fn_total_expr); + else if (is_eq(p, "today")) + return MAKE_FUNCTOR(report_t::fn_today); + else if (is_eq(p, "t")) + return MAKE_FUNCTOR(report_t::fn_display_amount); + else if (is_eq(p, "trim")) + return MAKE_FUNCTOR(report_t::fn_trim); + else if (is_eq(p, "to_boolean")) + return MAKE_FUNCTOR(report_t::fn_to_boolean); + else if (is_eq(p, "to_int")) + return MAKE_FUNCTOR(report_t::fn_to_int); + else if (is_eq(p, "to_datetime")) + return MAKE_FUNCTOR(report_t::fn_to_datetime); + else if (is_eq(p, "to_date")) + return MAKE_FUNCTOR(report_t::fn_to_date); + else if (is_eq(p, "to_amount")) + return MAKE_FUNCTOR(report_t::fn_to_amount); + else if (is_eq(p, "to_balance")) + return MAKE_FUNCTOR(report_t::fn_to_balance); + else if (is_eq(p, "to_string")) + return MAKE_FUNCTOR(report_t::fn_to_string); + else if (is_eq(p, "to_mask")) + return MAKE_FUNCTOR(report_t::fn_to_mask); + else if (is_eq(p, "to_sequence")) + return MAKE_FUNCTOR(report_t::fn_to_sequence); + break; + + case 'T': + if (is_eq(p, "T")) + return MAKE_FUNCTOR(report_t::fn_display_total); + break; + + case 'u': + if (is_eq(p, "underline")) + return WRAP_FUNCTOR(fn_underline); + else if (is_eq(p, "unrounded")) + return MAKE_FUNCTOR(report_t::fn_unrounded); + break; + + case 'w': + if (is_eq(p, "white")) + return WRAP_FUNCTOR(fn_white); + break; + + case 'y': + if (is_eq(p, "yellow")) + return WRAP_FUNCTOR(fn_yellow); + break; + } + + // Check if they are trying to access an option's setting or value. + if (option_t<report_t> * handler = lookup_option(p)) + return MAKE_OPT_FUNCTOR(report_t, handler); + break; + + case symbol_t::OPTION: + if (option_t<report_t> * handler = lookup_option(p)) + return MAKE_OPT_HANDLER(report_t, handler); + break; + + case symbol_t::COMMAND: + switch (*p) { + case 'b': + if (*(p + 1) == '\0' || is_eq(p, "bal") || is_eq(p, "balance")) { + return expr_t::op_t::wrap_functor + (reporter<account_t, acct_handler_ptr, &report_t::accounts_report> + (new format_accounts(*this, report_format(HANDLER(balance_format_)), + maybe_format(HANDLER(prepend_format_))), + *this, "#balance")); + } + else if (is_eq(p, "budget")) { + HANDLER(amount_).set_expr(string("#budget"), "(amount, 0)"); + + budget_flags |= BUDGET_WRAP_VALUES; + if (! (budget_flags & ~BUDGET_WRAP_VALUES)) + budget_flags |= BUDGET_BUDGETED; + + return expr_t::op_t::wrap_functor + (reporter<account_t, acct_handler_ptr, &report_t::accounts_report> + (new format_accounts(*this, report_format(HANDLER(budget_format_)), + maybe_format(HANDLER(prepend_format_))), + *this, "#budget")); + } + break; + + case 'c': + if (is_eq(p, "csv")) { + return WRAP_FUNCTOR + (reporter<> + (new format_posts(*this, report_format(HANDLER(csv_format_)), + maybe_format(HANDLER(prepend_format_))), + *this, "#csv")); + } + else if (is_eq(p, "cleared")) { + HANDLER(amount_).set_expr(string("#cleared"), + "(amount, cleared ? amount : 0)"); + + return expr_t::op_t::wrap_functor + (reporter<account_t, acct_handler_ptr, &report_t::accounts_report> + (new format_accounts(*this, report_format(HANDLER(cleared_format_)), + maybe_format(HANDLER(prepend_format_))), + *this, "#cleared")); + } + break; + + case 'e': + if (is_eq(p, "equity")) + return WRAP_FUNCTOR + (reporter<> + (new format_posts(*this, report_format(HANDLER(print_format_))), + *this, "#equity")); + else if (is_eq(p, "entry")) + return WRAP_FUNCTOR(xact_command); + else if (is_eq(p, "emacs")) + return WRAP_FUNCTOR + (reporter<>(new format_emacs_posts(output_stream), *this, "#emacs")); + else if (is_eq(p, "echo")) + return MAKE_FUNCTOR(report_t::echo_command); + break; + + case 'p': + if (*(p + 1) == '\0' || is_eq(p, "print")) + return WRAP_FUNCTOR + (reporter<> + (new format_posts(*this, report_format(HANDLER(print_format_)), + HANDLED(raw)), *this, "#print")); + else if (is_eq(p, "prices")) + return expr_t::op_t::wrap_functor + (reporter<post_t, post_handler_ptr, &report_t::commodities_report> + (new format_posts(*this, report_format(HANDLER(prices_format_)), + maybe_format(HANDLER(prepend_format_))), + *this, "#prices")); + else if (is_eq(p, "pricedb")) + return expr_t::op_t::wrap_functor + (reporter<post_t, post_handler_ptr, &report_t::commodities_report> + (new format_posts(*this, report_format(HANDLER(pricedb_format_)), + maybe_format(HANDLER(prepend_format_))), + *this, "#pricedb")); + break; + + case 'r': + if (*(p + 1) == '\0' || is_eq(p, "reg") || is_eq(p, "register")) + return WRAP_FUNCTOR + (reporter<> + (new format_posts(*this, report_format(HANDLER(register_format_)), + false, maybe_format(HANDLER(prepend_format_))), + *this, "#register")); + else if (is_eq(p, "reload")) + return MAKE_FUNCTOR(report_t::reload_command); + break; + + case 's': + if (is_eq(p, "stats") || is_eq(p, "stat")) + return WRAP_FUNCTOR(report_statistics); + break; + + case 'x': + if (is_eq(p, "xact")) + return WRAP_FUNCTOR(xact_command); + else if (is_eq(p, "xml")) + return WRAP_FUNCTOR(reporter<>(new format_xml(*this), *this, "#xml")); + break; + } + break; + + case symbol_t::PRECOMMAND: + switch (*p) { + case 'a': + if (is_eq(p, "args")) + return WRAP_FUNCTOR(args_command); + break; + case 'e': + if (is_eq(p, "eval")) + return WRAP_FUNCTOR(eval_command); + break; + case 'f': + if (is_eq(p, "format")) + return WRAP_FUNCTOR(format_command); + break; + case 'g': + if (is_eq(p, "generate")) + return expr_t::op_t::wrap_functor + (reporter<post_t, post_handler_ptr, &report_t::generate_report> + (new format_posts(*this, report_format(HANDLER(print_format_)), + false), *this, "#generate")); + case 'p': + if (is_eq(p, "parse")) + return WRAP_FUNCTOR(parse_command); + else if (is_eq(p, "period")) + return WRAP_FUNCTOR(period_command); + break; + case 't': + if (is_eq(p, "template")) + return WRAP_FUNCTOR(template_command); + break; + } + break; + + default: + break; + } + + return NULL; +} + +} // namespace ledger diff --git a/src/report.h b/src/report.h new file mode 100644 index 00000000..94d39215 --- /dev/null +++ b/src/report.h @@ -0,0 +1,958 @@ +/* + * 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. + */ + +/** + * @addtogroup report + */ + +/** + * @file report.h + * @author John Wiegley + * + * @ingroup report + */ +#ifndef _REPORT_H +#define _REPORT_H + +#include "interactive.h" +#include "expr.h" +#include "query.h" +#include "chain.h" +#include "stream.h" +#include "option.h" +#include "commodity.h" +#include "annotate.h" +#include "session.h" +#include "format.h" + +namespace ledger { + +class session_t; +class xact_t; + +// These are the elements of any report: +// +// 1. Formatting string used for outputting the underlying ReportedType. +// +// 2. Handler object for the ReportedType. This is constructed using #1, or +// else #1 is ignored completely. This handler object is also constructed +// with the output stream that will be used during formatting. +// +// --- The details of #1 and #2 together represent the ItemHandler. +// +// 3. Mode of the report. Currently there are four modes: +// +// a. Posting or commodity iteration. In this mode, all the journal's +// xacts, the postings of a specific xact, or all the journal's +// commodities are walked. In the first two cases, it's the underlying +// postings which are passed to #2; in the second case, each +// commodity is passed to #2. +// +// b. Account iteration. This employs step 'a', but add a prologue and +// epilogue to it. In the prologue it "sums" all account totals and +// subtotals; in the epilogue it calls yet another handler whose job is +// reporting (the handler used in 'a' is only for calculation). +// +// There is one variation on 'b' in which a "totals" line is also +// displayed. +// +// c. Write journal. In this mode, a single function is called that output +// the journal object as a textual file. #2 is used to print out each +// posting in the journal. +// +// d. Dump binary file. This is just like 'c', except that it dumps out a +// binary file and #2 is completely ignored. +// +// 4. For 'a' and 'b' in #3, there is a different iteration function called, +// depending on whether we're iterating: +// +// a. The postings of an xact: walk_postings. +// b. The xacts of a journal: walk_xacts. +// c. The commodities of a journal: walk_commodities. +// +// 5. Finally, for the 'a' and 'b' reporting modes, there is a variant which +// says that the formatter should be "flushed" after the entities are +// iterated. This does not happen for the commodities iteration, however. + +class report_t : public scope_t +{ + report_t(); + +public: + session_t& session; + output_stream_t output_stream; + +#define BUDGET_NO_BUDGET 0x00 +#define BUDGET_BUDGETED 0x01 +#define BUDGET_UNBUDGETED 0x02 +#define BUDGET_WRAP_VALUES 0x04 + + datetime_t terminus; + uint_least8_t budget_flags; + + explicit report_t(session_t& _session) + : session(_session), terminus(CURRENT_TIME()), + budget_flags(BUDGET_NO_BUDGET) {} + + virtual ~report_t() { + output_stream.close(); + } + + void normalize_options(const string& verb); + void parse_query_args(const value_t& args, const string& whence); + + void posts_report(post_handler_ptr handler); + void generate_report(post_handler_ptr handler); + void xact_report(post_handler_ptr handler, xact_t& xact); + void accounts_report(acct_handler_ptr handler); + void commodities_report(post_handler_ptr handler); + + value_t fn_amount_expr(call_scope_t& scope); + value_t fn_total_expr(call_scope_t& scope); + value_t fn_display_amount(call_scope_t& scope); + value_t fn_display_total(call_scope_t& scope); + value_t fn_market(call_scope_t& scope); + value_t fn_get_at(call_scope_t& scope); + value_t fn_is_seq(call_scope_t& scope); + value_t fn_strip(call_scope_t& scope); + value_t fn_trim(call_scope_t& scope); + value_t fn_scrub(call_scope_t& scope); + value_t fn_quantity(call_scope_t& scope); + value_t fn_rounded(call_scope_t& scope); + value_t fn_unrounded(call_scope_t& scope); + value_t fn_truncated(call_scope_t& scope); + value_t fn_floor(call_scope_t& scope); + value_t fn_abs(call_scope_t& scope); + value_t fn_justify(call_scope_t& scope); + value_t fn_quoted(call_scope_t& scope); + value_t fn_join(call_scope_t& scope); + value_t fn_format_date(call_scope_t& scope); + value_t fn_ansify_if(call_scope_t& scope); + value_t fn_percent(call_scope_t& scope); + value_t fn_price(call_scope_t& scope); + value_t fn_lot_date(call_scope_t& scope); + value_t fn_lot_price(call_scope_t& scope); + value_t fn_lot_tag(call_scope_t& scope); + value_t fn_to_boolean(call_scope_t& scope); + value_t fn_to_int(call_scope_t& scope); + value_t fn_to_datetime(call_scope_t& scope); + value_t fn_to_date(call_scope_t& scope); + value_t fn_to_amount(call_scope_t& scope); + value_t fn_to_balance(call_scope_t& scope); + value_t fn_to_string(call_scope_t& scope); + value_t fn_to_mask(call_scope_t& scope); + value_t fn_to_sequence(call_scope_t& scope); + + value_t fn_now(call_scope_t&) { + return terminus; + } + value_t fn_today(call_scope_t&) { + return terminus.date(); + } + + value_t fn_options(call_scope_t&) { + return value_t(static_cast<scope_t *>(this)); + } + + string report_format(option_t<report_t>& option) { + if (HANDLED(format_)) + return HANDLER(format_).str(); + return option.str(); + } + + optional<string> maybe_format(option_t<report_t>& option) { + if (option) + return option.str(); + return none; + } + + value_t reload_command(call_scope_t&); + value_t echo_command(call_scope_t& scope); + + keep_details_t what_to_keep() { + bool lots = HANDLED(lots) || HANDLED(lots_actual); + return keep_details_t(lots || HANDLED(lot_prices), + lots || HANDLED(lot_dates), + lots || HANDLED(lot_tags), + HANDLED(lots_actual)); + } + + void report_options(std::ostream& out) + { + HANDLER(abbrev_len_).report(out); + HANDLER(account_).report(out); + HANDLER(actual).report(out); + HANDLER(actual_dates).report(out); + HANDLER(add_budget).report(out); + HANDLER(amount_).report(out); + HANDLER(amount_data).report(out); + HANDLER(anon).report(out); + HANDLER(average).report(out); + HANDLER(balance_format_).report(out); + HANDLER(base).report(out); + HANDLER(basis).report(out); + HANDLER(begin_).report(out); + HANDLER(budget).report(out); + HANDLER(budget_format_).report(out); + HANDLER(by_payee).report(out); + HANDLER(cleared).report(out); + HANDLER(cleared_format_).report(out); + HANDLER(color).report(out); + HANDLER(collapse).report(out); + HANDLER(collapse_if_zero).report(out); + HANDLER(columns_).report(out); + HANDLER(csv_format_).report(out); + HANDLER(current).report(out); + HANDLER(daily).report(out); + HANDLER(date_).report(out); + HANDLER(date_format_).report(out); + HANDLER(datetime_format_).report(out); + HANDLER(depth_).report(out); + HANDLER(deviation).report(out); + HANDLER(display_).report(out); + HANDLER(display_amount_).report(out); + HANDLER(display_total_).report(out); + HANDLER(dow).report(out); + HANDLER(effective).report(out); + HANDLER(empty).report(out); + HANDLER(end_).report(out); + HANDLER(equity).report(out); + HANDLER(exact).report(out); + HANDLER(exchange_).report(out); + HANDLER(flat).report(out); + HANDLER(force_color).report(out); + HANDLER(force_pager).report(out); + HANDLER(forecast_while_).report(out); + HANDLER(forecast_years_).report(out); + HANDLER(format_).report(out); + HANDLER(gain).report(out); + HANDLER(head_).report(out); + HANDLER(invert).report(out); + HANDLER(limit_).report(out); + HANDLER(lot_dates).report(out); + HANDLER(lot_prices).report(out); + HANDLER(lot_tags).report(out); + HANDLER(lots).report(out); + HANDLER(lots_actual).report(out); + HANDLER(market).report(out); + HANDLER(monthly).report(out); + HANDLER(no_total).report(out); + HANDLER(now_).report(out); + HANDLER(only_).report(out); + HANDLER(output_).report(out); + HANDLER(pager_).report(out); + HANDLER(payee_).report(out); + HANDLER(pending).report(out); + HANDLER(percent).report(out); + HANDLER(period_).report(out); + HANDLER(plot_amount_format_).report(out); + HANDLER(plot_total_format_).report(out); + HANDLER(prepend_format_).report(out); + HANDLER(price).report(out); + HANDLER(prices_format_).report(out); + HANDLER(pricedb_format_).report(out); + HANDLER(print_format_).report(out); + HANDLER(quantity).report(out); + HANDLER(quarterly).report(out); + HANDLER(raw).report(out); + HANDLER(real).report(out); + HANDLER(register_format_).report(out); + HANDLER(related).report(out); + HANDLER(related_all).report(out); + HANDLER(revalued).report(out); + HANDLER(revalued_only).report(out); + HANDLER(revalued_total_).report(out); + HANDLER(seed_).report(out); + HANDLER(sort_).report(out); + HANDLER(sort_all_).report(out); + HANDLER(sort_xacts_).report(out); + HANDLER(start_of_week_).report(out); + HANDLER(subtotal).report(out); + HANDLER(tail_).report(out); + HANDLER(total_).report(out); + HANDLER(total_data).report(out); + HANDLER(truncate_).report(out); + HANDLER(unbudgeted).report(out); + HANDLER(uncleared).report(out); + HANDLER(unrealized).report(out); + HANDLER(unrealized_gains_).report(out); + HANDLER(unrealized_losses_).report(out); + HANDLER(unround).report(out); + HANDLER(unsorted).report(out); + HANDLER(weekly).report(out); + HANDLER(wide).report(out); + HANDLER(yearly).report(out); + HANDLER(date_width_).report(out); + HANDLER(payee_width_).report(out); + HANDLER(account_width_).report(out); + HANDLER(amount_width_).report(out); + HANDLER(total_width_).report(out); + } + + option_t<report_t> * lookup_option(const char * p); + + virtual void define(const symbol_t::kind_t kind, const string& name, + expr_t::ptr_op_t def); + + virtual expr_t::ptr_op_t lookup(const symbol_t::kind_t kind, + const string& name); + + /** + * Option handlers + */ + + OPTION__(report_t, abbrev_len_, + CTOR(report_t, abbrev_len_) { on_with(none, 2L); }); + OPTION(report_t, account_); + + OPTION_(report_t, actual, DO() { // -L + parent->HANDLER(limit_).on(string("--actual"), "actual"); + }); + + OPTION(report_t, actual_dates); + + OPTION_(report_t, add_budget, DO() { + parent->budget_flags |= BUDGET_BUDGETED | BUDGET_UNBUDGETED; + }); + + OPTION__ + (report_t, amount_, // -t + expr_t expr; + CTOR(report_t, amount_) { + set_expr(none, "amount"); + } + void set_expr(const optional<string>& whence, const string& str) { + expr = str; + on(whence, str); + } + DO_(args) { + set_expr(args[0].to_string(), args[1].to_string()); + }); + + OPTION(report_t, amount_data); // -j + OPTION(report_t, anon); + + OPTION_(report_t, average, DO() { // -A + parent->HANDLER(display_total_) + .set_expr(string("--average"), "total_expr/count"); + }); + + OPTION__(report_t, balance_format_, CTOR(report_t, balance_format_) { + on(none, + "%(justify(scrub(display_total), 20, -1, true, color))" + " %(!options.flat ? depth_spacer : \"\")" + "%-(ansify_if(partial_account(options.flat), blue if color))\n%/" + "%$1\n%/" + "--------------------\n"); + }); + + OPTION(report_t, base); + + OPTION_(report_t, basis, DO() { // -B + parent->HANDLER(revalued).on_only(string("--basis")); + parent->HANDLER(amount_).set_expr(string("--basis"), "rounded(cost)"); + }); + + OPTION_(report_t, begin_, DO_(args) { // -b + date_interval_t interval(args[1].to_string()); + optional<date_t> begin = interval.begin(parent->session.current_year); + if (! begin) + throw_(std::invalid_argument, + _("Could not determine beginning of period '%1'") + << args[1].to_string()); + + string predicate = "date>=[" + to_iso_extended_string(*begin) + "]"; + parent->HANDLER(limit_).on(string("--begin"), predicate); + }); + + OPTION_(report_t, budget, DO() { + parent->budget_flags |= BUDGET_BUDGETED; + }); + + OPTION__(report_t, budget_format_, CTOR(report_t, budget_format_) { + on(none, + "%(justify(scrub(get_at(total_expr, 0)), 12, -1, true, color))" + " %(justify(scrub(- get_at(total_expr, 1)), 12, " + " 12 + 1 + 12, true, color))" + " %(justify(scrub(get_at(total_expr, 1) + " + " get_at(total_expr, 0)), 12, " + " 12 + 1 + 12 + 1 + 12, true, color))" + " %(ansify_if(" + " justify((get_at(total_expr, 1) ? " + " scrub((100% * get_at(total_expr, 0)) / " + " - get_at(total_expr, 1)) : 0), " + " 5, -1, true, false)," + " magenta if (color and get_at(total_expr, 1) and " + " (abs(quantity(get_at(total_expr, 0)) / " + " quantity(get_at(total_expr, 1))) >= 1))))" + " %(!options.flat ? depth_spacer : \"\")" + "%-(ansify_if(partial_account(options.flat), blue if color))\n" + "%/%$1 %$2 %$3 %$4\n%/" + "------------ ------------ ------------ -----\n"); + }); + + OPTION(report_t, by_payee); // -P + + OPTION_(report_t, cleared, DO() { // -C + parent->HANDLER(limit_).on(string("--cleared"), "cleared"); + }); + + OPTION__(report_t, cleared_format_, CTOR(report_t, cleared_format_) { + on(none, + "%(justify(scrub(get_at(total_expr, 0)), 16, -1, true, color))" + " %(justify(scrub(get_at(total_expr, 1)), 16, -1, true, color))" + " %(latest_cleared ? format_date(latest_cleared) : \" \")" + " %(!options.flat ? depth_spacer : \"\")" + "%-(ansify_if(partial_account(options.flat), blue if color))\n%/" + "%$1 %$2 %$3\n%/" + "---------------- ---------------- ---------\n"); + }); + + OPTION(report_t, color); + + OPTION_(report_t, collapse, DO() { // -n + // Make sure that balance reports are collapsed too, but only apply it + // to account xacts + parent->HANDLER(display_).on(string("--collapse"), "post|depth<=1"); + }); + + OPTION_(report_t, collapse_if_zero, DO() { + parent->HANDLER(collapse).on_only(string("--collapse-if-zero")); + }); + + OPTION(report_t, columns_); + + OPTION__(report_t, csv_format_, CTOR(report_t, csv_format_) { + on(none, + "%(quoted(date))," + "%(quoted(payee))," + "%(quoted(account))," + "%(quoted(scrub(display_amount)))," + "%(quoted(cleared ? \"*\" : (pending ? \"!\" : \"\")))," + "%(quoted(code))," + "%(quoted(join(note | xact.note)))\n"); + }); + + OPTION_(report_t, current, DO() { // -c + parent->HANDLER(limit_).on(string("--current"), "date<=today"); + }); + + OPTION_(report_t, daily, DO() { // -D + parent->HANDLER(period_).on(string("--daily"), "daily"); + }); + + OPTION(report_t, date_); + OPTION(report_t, date_format_); + OPTION(report_t, datetime_format_); + + OPTION_(report_t, depth_, DO_(scope) { + interactive_t args(scope, "sl"); + parent->HANDLER(display_).on(string("--depth"), + string("depth<=") + args.get<string>(1)); + }); + + OPTION_(report_t, deviation, DO() { + parent->HANDLER(display_total_) + .set_expr(string("--deviation"), "amount_expr-total_expr/count"); + }); + + OPTION__ + (report_t, display_, // -d + CTOR(report_t, display_) {} + virtual void on_with(const optional<string>& whence, const value_t& text) { + if (! handled) + option_t<report_t>::on_with(whence, text); + else + option_t<report_t>::on_with(whence, + string_value(string("(") + str() + ")&(" + + text.as_string() + ")")); + }); + + OPTION__ + (report_t, display_amount_, + expr_t expr; + CTOR(report_t, display_amount_) { + set_expr(none, "amount_expr"); + } + void set_expr(const optional<string>& whence, const string& str) { + expr = str; + on(whence, str); + } + DO_(args) { + set_expr(args[0].to_string(), args[1].to_string()); + }); + + OPTION__ + (report_t, display_total_, + expr_t expr; + CTOR(report_t, display_total_) { + set_expr(none, "total_expr"); + } + void set_expr(const optional<string>& whence, const string& str) { + expr = str; + on(whence, str); + } + DO_(args) { + set_expr(args[0].to_string(), args[1].to_string()); + }); + + OPTION(report_t, dow); + OPTION(report_t, effective); + OPTION(report_t, empty); // -E + + OPTION_(report_t, end_, DO_(args) { // -e + date_interval_t interval(args[1].to_string()); + // Use begin() here so that if the user says --end=2008, we end on + // 2008/01/01 instead of 2009/01/01 (which is what end() would return). + optional<date_t> end = interval.begin(parent->session.current_year); + if (! end) + throw_(std::invalid_argument, + _("Could not determine end of period '%1'") + << args[1].to_string()); + + string predicate = "date<[" + to_iso_extended_string(*end) + "]"; + parent->HANDLER(limit_).on(string("--end"), predicate); + + parent->terminus = datetime_t(*end); + }); + + OPTION(report_t, equity); + OPTION(report_t, exact); + + OPTION_(report_t, exchange_, DO_(args) { // -X + on_with(args[0].as_string(), args[1]); + call_scope_t no_args(*parent); + no_args.push_back(args[0]); + parent->HANDLER(market).parent = parent; + parent->HANDLER(market).handler(no_args); + }); + + OPTION(report_t, flat); + OPTION(report_t, force_color); + OPTION(report_t, force_pager); + OPTION(report_t, forecast_while_); + OPTION(report_t, forecast_years_); + OPTION(report_t, format_); // -F + + OPTION_(report_t, gain, DO() { // -G + parent->HANDLER(revalued).on_only(string("--gain")); + parent->HANDLER(amount_).set_expr(string("--gain"), "(amount, cost)"); + // Since we are displaying the amounts of revalued postings, they + // will end up being composite totals, and hence a pair of pairs. + parent->HANDLER(display_amount_) + .set_expr(string("--gain"), + "use_direct_amount ? amount :" + " (is_seq(get_at(amount_expr, 0)) ?" + " get_at(get_at(amount_expr, 0), 0) :" + " market(get_at(amount_expr, 0), date, exchange)" + " - get_at(amount_expr, 1))"); + parent->HANDLER(revalued_total_) + .set_expr(string("--gain"), + "(market(get_at(total_expr, 0), date, exchange), " + "get_at(total_expr, 1))"); + parent->HANDLER(display_total_) + .set_expr(string("--gain"), + "use_direct_amount ? total_expr :" + " market(get_at(total_expr, 0), date, exchange)" + " - get_at(total_expr, 1)"); + }); + + OPTION(report_t, head_); + + OPTION_(report_t, invert, DO() { + parent->HANDLER(amount_).set_expr(string("--invert"), "-amount"); + }); + + OPTION__ + (report_t, limit_, // -l + CTOR(report_t, limit_) {} + virtual void on_with(const optional<string>& whence, const value_t& text) { + if (! handled) + option_t<report_t>::on_with(whence, text); + else + option_t<report_t>::on_with(whence, + string_value(string("(") + str() + ")&(" + + text.as_string() + ")")); + }); + + OPTION(report_t, lot_dates); + OPTION(report_t, lot_prices); + OPTION(report_t, lot_tags); + OPTION(report_t, lots); + OPTION(report_t, lots_actual); + + OPTION_(report_t, market, DO() { // -V + parent->HANDLER(revalued).on_only(string("--market")); + parent->HANDLER(display_amount_) + .set_expr(string("--market"), "market(amount_expr, date, exchange)"); + parent->HANDLER(display_total_) + .set_expr(string("--market"), "market(total_expr, date, exchange)"); + }); + + OPTION_(report_t, monthly, DO() { // -M + parent->HANDLER(period_).on(string("--monthly"), "monthly"); + }); + + OPTION_(report_t, no_color, DO() { + parent->HANDLER(color).off(); + }); + + OPTION(report_t, no_total); + + OPTION_(report_t, now_, DO_(args) { + date_interval_t interval(args[1].to_string()); + optional<date_t> begin = interval.begin(parent->session.current_year); + if (! begin) + throw_(std::invalid_argument, + _("Could not determine beginning of period '%1'") + << args[1].to_string()); + ledger::epoch = parent->terminus = datetime_t(*begin); + parent->session.current_year = ledger::epoch->date().year(); + }); + + OPTION__ + (report_t, only_, + CTOR(report_t, only_) {} + virtual void on_with(const optional<string>& whence, const value_t& text) { + if (! handled) + option_t<report_t>::on_with(whence, text); + else + option_t<report_t>::on_with(whence, + string_value(string("(") + str() + ")&(" + + text.as_string() + ")")); + }); + + OPTION(report_t, output_); // -o + +#ifdef HAVE_ISATTY + OPTION__ + (report_t, pager_, + CTOR(report_t, pager_) { + if (! std::getenv("PAGER") && isatty(STDOUT_FILENO)) { + bool have_less = false; + if (exists(path("/opt/local/bin/less")) || + exists(path("/usr/local/bin/less")) || + exists(path("/usr/bin/less"))) + have_less = true; + + if (have_less) { + on(none, "less"); + setenv("LESS", "-FRSX", 0); // don't overwrite + } + } + } + virtual void on_with(const optional<string>& whence, const value_t& text) { + string cmd(text.to_string()); + if (cmd == "" || cmd == "false" || cmd == "off" || + cmd == "none" || cmd == "no" || cmd == "disable") + option_t<report_t>::off(); + else + option_t<report_t>::on_with(whence, text); + }); +#else // HAVE_ISATTY + OPTION__ + (report_t, pager_, + CTOR(report_t, pager_) { + } + virtual void on_with(const optional<string>& whence, const value_t& text) { + string cmd(text.to_string()); + if (cmd == "" || cmd == "false" || cmd == "off" || + cmd == "none" || cmd == "no" || cmd == "disable") + option_t<report_t>::off(); + else + option_t<report_t>::on_with(whence, text); + }); +#endif // HAVE_ISATTY + + OPTION(report_t, payee_); + + OPTION_(report_t, pending, DO() { // -C + parent->HANDLER(limit_).on(string("--pending"), "pending"); + }); + + OPTION_(report_t, percent, DO() { // -% + parent->HANDLER(total_) + .set_expr(string("--percent"), + "is_account&parent&parent.total&percent(total, parent.total)"); + }); + + OPTION__ + (report_t, period_, // -p + CTOR(report_t, period_) {} + virtual void on_with(const optional<string>& whence, const value_t& text) { + if (! handled) + option_t<report_t>::on_with(whence, text); + else + option_t<report_t>::on_with(whence, + string_value(text.as_string() + " " + str())); + }); + + OPTION__(report_t, plot_amount_format_, CTOR(report_t, plot_amount_format_) { + on(none, + "%(format_date(date, \"%Y-%m-%d\")) %(quantity(scrub(display_amount)))\n"); + }); + + OPTION__(report_t, plot_total_format_, CTOR(report_t, plot_total_format_) { + on(none, + "%(format_date(date, \"%Y-%m-%d\")) %(quantity(scrub(display_total)))\n"); + }); + + OPTION(report_t, prepend_format_); + + OPTION_(report_t, price, DO() { // -I + parent->HANDLER(display_amount_) + .set_expr(string("--price"), "price(amount_expr)"); + parent->HANDLER(display_total_) + .set_expr(string("--price"), "price(total_expr)"); + }); + + OPTION__(report_t, prices_format_, CTOR(report_t, prices_format_) { + on(none, + "%(date) %-8(account) %(justify(scrub(display_amount), 12, " + " 2 + 9 + 8 + 12, true, color))\n"); + }); + + OPTION__(report_t, pricedb_format_, CTOR(report_t, pricedb_format_) { + on(none, + "P %(datetime) %(account) %(scrub(display_amount))\n"); + }); + + OPTION__(report_t, print_format_, CTOR(report_t, print_format_) { + on(none, + "%(xact.date)" + "%(!effective & xact.effective_date ?" + " \"=\" + xact.effective_date : \"\")" + "%(xact.cleared ? \" *\" : (xact.pending ? \" !\" : \"\"))" + "%(code ? \" (\" + code + \")\" :" + " \"\") %(payee)%(xact.comment)\n" + " %(xact.uncleared ?" + " (cleared ? \"* \" : (pending ? \"! \" : \"\")) : \"\")" + "%(calculated ? account : justify(account, 34, -1, false))" + "%(calculated ? \"\" : \" \" + justify(scrub(amount), 12, -1, true))" + "%(has_cost & !cost_calculated ?" + " \" @ \" + justify(scrub(abs(cost / amount)), 0) : \"\")" + "%(comment)\n%/" + " %$7%$8%$9%$A%$B\n%/\n"); + }); + + OPTION_(report_t, quantity, DO() { // -O + parent->HANDLER(revalued).off(); + parent->HANDLER(amount_).set_expr(string("--quantity"), "amount"); + parent->HANDLER(total_).set_expr(string("--quantity"), "total"); + }); + + OPTION_(report_t, quarterly, DO() { + parent->HANDLER(period_).on(string("--quarterly"), "quarterly"); + }); + + OPTION(report_t, raw); + + OPTION_(report_t, real, DO() { // -R + parent->HANDLER(limit_).on(string("--real"), "real"); + }); + + OPTION__(report_t, register_format_, CTOR(report_t, register_format_) { + on(none, + "%(ansify_if(justify(format_date(date), date_width), green " + " if color & date > today))" + " %(ansify_if(justify(truncated(payee, payee_width), payee_width), " + " bold if color & !cleared & actual))" + " %(ansify_if(justify(truncated(account, account_width, abbrev_len), " + " account_width), blue if color))" + " %(justify(scrub(display_amount), amount_width, " + " 3 + date_width + payee_width + account_width + amount_width, " + " true, color))" + " %(justify(scrub(display_total), total_width, " + " 4 + date_width + payee_width + account_width + amount_width " + " + total_width, true, color))\n%/" + "%(justify(\" \", 2 + date_width + payee_width))%$3 %$4 %$5\n"); + }); + + OPTION(report_t, related); // -r + + OPTION_(report_t, related_all, DO() { + parent->HANDLER(related).on_only(string("--related-all")); + }); + + OPTION(report_t, revalued); + OPTION(report_t, revalued_only); + + OPTION__ + (report_t, revalued_total_, + expr_t expr; + CTOR(report_t, revalued_total_) {} + void set_expr(const optional<string>& whence, const string& str) { + expr = str; + on(whence, str); + } + DO_(args) { + set_expr(args[0].to_string(), args[1].to_string()); + }); + + OPTION(report_t, seed_); + + OPTION_(report_t, sort_, DO_(args) { // -S + on_with(args[0].as_string(), args[1]); + parent->HANDLER(sort_xacts_).off(); + parent->HANDLER(sort_all_).off(); + }); + + OPTION_(report_t, sort_all_, DO_(args) { + parent->HANDLER(sort_).on_with(string("--sort-all"), args[1]); + parent->HANDLER(sort_xacts_).off(); + }); + + OPTION_(report_t, sort_xacts_, DO_(args) { + parent->HANDLER(sort_).on_with(string("--sort-xacts"), args[1]); + parent->HANDLER(sort_all_).off(); + }); + + OPTION(report_t, start_of_week_); + OPTION(report_t, subtotal); // -s + OPTION(report_t, tail_); + + OPTION__ + (report_t, total_, // -T + expr_t expr; + CTOR(report_t, total_) { + set_expr(none, "total"); + } + void set_expr(const optional<string>& whence, const string& str) { + expr = str; + on(whence, str); + } + DO_(args) { + set_expr(args[0].to_string(), args[1].to_string()); + }); + + OPTION(report_t, total_data); // -J + + OPTION_(report_t, truncate_, DO_(args) { + string style(args[1].to_string()); + if (style == "leading") + format_t::default_style = format_t::TRUNCATE_LEADING; + else if (style == "middle") + format_t::default_style = format_t::TRUNCATE_MIDDLE; + else if (style == "trailing") + format_t::default_style = format_t::TRUNCATE_TRAILING; + else + throw_(std::invalid_argument, + _("Unrecognized truncation style: '%1'") << style); + format_t::default_style_changed = true; + }); + + OPTION_(report_t, unbudgeted, DO() { + parent->budget_flags |= BUDGET_UNBUDGETED; + }); + + OPTION_(report_t, uncleared, DO() { // -U + parent->HANDLER(limit_).on(string("--uncleared"), "uncleared|pending"); + }); + + OPTION(report_t, unrealized); + + OPTION(report_t, unrealized_gains_); + OPTION(report_t, unrealized_losses_); + + OPTION_(report_t, unround, DO() { + parent->HANDLER(display_amount_) + .set_expr(string("--unround"), "unrounded(amount_expr)"); + parent->HANDLER(display_total_) + .set_expr(string("--unround"), "unrounded(total_expr)"); + }); + + OPTION(report_t, unsorted); + + OPTION_(report_t, weekly, DO() { // -W + parent->HANDLER(period_).on(string("--weekly"), "weekly"); + }); + + OPTION_(report_t, wide, DO() { // -w + parent->HANDLER(columns_).on_with(string("--wide"), 132L); + }); + + OPTION_(report_t, yearly, DO() { // -Y + parent->HANDLER(period_).on(string("--yearly"), "yearly"); + }); + + OPTION__(report_t, date_width_, + bool specified; + CTOR(report_t, date_width_) { specified = false; } + DO_(args) { value = args[1].to_long(); specified = true; }); + OPTION__(report_t, payee_width_, + bool specified; + CTOR(report_t, payee_width_) { specified = false; } + DO_(args) { value = args[1].to_long(); specified = true; }); + OPTION__(report_t, account_width_, + bool specified; + CTOR(report_t, account_width_) { specified = false; } + DO_(args) { value = args[1].to_long(); specified = true; }); + OPTION__(report_t, amount_width_, + bool specified; + CTOR(report_t, amount_width_) { specified = false; } + DO_(args) { value = args[1].to_long(); specified = true; }); + OPTION__(report_t, total_width_, + bool specified; + CTOR(report_t, total_width_) { specified = false; } + DO_(args) { value = args[1].to_long(); specified = true; }); +}; + + +template <class Type = post_t, + class handler_ptr = post_handler_ptr, + void (report_t::*report_method)(handler_ptr) = + &report_t::posts_report> +class reporter +{ + shared_ptr<item_handler<Type> > handler; + + report_t& report; + string whence; + +public: + reporter(item_handler<Type> * _handler, + report_t& _report, const string& _whence) + : handler(_handler), report(_report), whence(_whence) {} + + value_t operator()(call_scope_t& args) + { + if (args.size() > 0) + report.parse_query_args(args.value(), whence); + + (report.*report_method)(handler_ptr(handler)); + + return true; + } +}; + +} // namespace ledger + +#endif // _REPORT_H diff --git a/src/scope.cc b/src/scope.cc new file mode 100644 index 00000000..64736ca3 --- /dev/null +++ b/src/scope.cc @@ -0,0 +1,74 @@ +/* + * 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 "scope.h" + +namespace ledger { + +scope_t * scope_t::default_scope = NULL; + +void symbol_scope_t::define(const symbol_t::kind_t kind, + const string& name, expr_t::ptr_op_t def) +{ + DEBUG("scope.symbols", "Defining '" << name << "' = " << def); + + if (! symbols) + symbols = symbol_map(); + + std::pair<symbol_map::iterator, bool> result + = symbols->insert(symbol_map::value_type(symbol_t(kind, name, def), def)); + if (! result.second) { + symbol_map::iterator i = symbols->find(symbol_t(kind, name)); + assert(i != symbols->end()); + symbols->erase(i); + + result = symbols->insert(symbol_map::value_type(symbol_t(kind, name, def), + def)); + if (! result.second) + throw_(compile_error, + _("Redefinition of '%1' in the same scope") << name); + } +} + +expr_t::ptr_op_t symbol_scope_t::lookup(const symbol_t::kind_t kind, + const string& name) +{ + if (symbols) { + symbol_map::const_iterator i = symbols->find(symbol_t(kind, name)); + if (i != symbols->end()) + return (*i).second; + } + return child_scope_t::lookup(kind, name); +} + +} // namespace ledger diff --git a/src/scope.h b/src/scope.h new file mode 100644 index 00000000..93f80230 --- /dev/null +++ b/src/scope.h @@ -0,0 +1,348 @@ +/* + * 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. + */ + +/** + * @addtogroup expr + */ + +/** + * @file scope.h + * @author John Wiegley + * + * @ingroup expr + */ +#ifndef _SCOPE_H +#define _SCOPE_H + +#include "op.h" + +namespace ledger { + +struct symbol_t +{ + enum kind_t { + UNKNOWN, + FUNCTION, + OPTION, + PRECOMMAND, + COMMAND, + DIRECTIVE, + FORMAT + }; + + kind_t kind; + string name; + expr_t::ptr_op_t definition; + + symbol_t() : kind(UNKNOWN), name(""), definition(NULL) { + TRACE_CTOR(symbol_t, ""); + } + symbol_t(kind_t _kind, string _name, expr_t::ptr_op_t _definition = NULL) + : kind(_kind), name(_name), definition(_definition) { + TRACE_CTOR(symbol_t, "symbol_t::kind_t, string"); + } + symbol_t(const symbol_t& sym) + : kind(sym.kind), name(sym.name), + definition(sym.definition) { + TRACE_CTOR(symbol_t, "copy"); + } + ~symbol_t() throw() { + TRACE_DTOR(symbol_t); + } + + bool operator<(const symbol_t& sym) const { + return kind < sym.kind || name < sym.name; + } + +#if defined(HAVE_BOOST_SERIALIZATION) +private: + /** Serialization. */ + + friend class boost::serialization::access; + + template<class Archive> + void serialize(Archive& ar, const unsigned int /* version */) { + ar & kind; + ar & name; + ar & definition; + } +#endif // HAVE_BOOST_SERIALIZATION +}; + +class scope_t +{ +public: + static scope_t * default_scope; + + explicit scope_t() { + TRACE_CTOR(scope_t, ""); + } + virtual ~scope_t() { + TRACE_DTOR(scope_t); + } + + virtual void define(const symbol_t::kind_t, const string&, + expr_t::ptr_op_t) {} + virtual expr_t::ptr_op_t lookup(const symbol_t::kind_t kind, + const string& name) = 0; + +#if defined(HAVE_BOOST_SERIALIZATION) +private: + /** Serialization. */ + + friend class boost::serialization::access; + + template<class Archive> + void serialize(Archive&, const unsigned int /* version */) {} +#endif // HAVE_BOOST_SERIALIZATION +}; + +class child_scope_t : public noncopyable, public scope_t +{ +public: + scope_t * parent; + + explicit child_scope_t() : parent(NULL) { + TRACE_CTOR(child_scope_t, ""); + } + explicit child_scope_t(scope_t& _parent) + : parent(&_parent) { + TRACE_CTOR(child_scope_t, "scope_t&"); + } + virtual ~child_scope_t() { + TRACE_DTOR(child_scope_t); + } + + virtual void define(const symbol_t::kind_t kind, + const string& name, expr_t::ptr_op_t def) { + if (parent) + parent->define(kind, name, def); + } + + virtual expr_t::ptr_op_t lookup(const symbol_t::kind_t kind, + const string& name) { + if (parent) + return parent->lookup(kind, name); + return NULL; + } + +#if defined(HAVE_BOOST_SERIALIZATION) +private: + /** Serialization. */ + + friend class boost::serialization::access; + + template<class Archive> + void serialize(Archive& ar, const unsigned int /* version */) { + ar & boost::serialization::base_object<scope_t>(*this); + ar & parent; + } +#endif // HAVE_BOOST_SERIALIZATION +}; + +class symbol_scope_t : public child_scope_t +{ + typedef std::map<symbol_t, expr_t::ptr_op_t> symbol_map; + + optional<symbol_map> symbols; + +public: + explicit symbol_scope_t() { + TRACE_CTOR(symbol_scope_t, ""); + } + explicit symbol_scope_t(scope_t& _parent) : child_scope_t(_parent) { + TRACE_CTOR(symbol_scope_t, "scope_t&"); + } + virtual ~symbol_scope_t() { + TRACE_DTOR(symbol_scope_t); + } + + virtual void define(const symbol_t::kind_t kind, const string& name, + expr_t::ptr_op_t def); + + virtual expr_t::ptr_op_t lookup(const symbol_t::kind_t kind, + const string& name); + +#if defined(HAVE_BOOST_SERIALIZATION) +private: + /** Serialization. */ + + friend class boost::serialization::access; + + template<class Archive> + void serialize(Archive& ar, const unsigned int /* version */) { + ar & boost::serialization::base_object<child_scope_t>(*this); + ar & symbols; + } +#endif // HAVE_BOOST_SERIALIZATION +}; + +class call_scope_t : public child_scope_t +{ + value_t args; + +public: + explicit call_scope_t(scope_t& _parent) : child_scope_t(_parent) { + TRACE_CTOR(call_scope_t, "scope_t&"); + } + virtual ~call_scope_t() { + TRACE_DTOR(call_scope_t); + } + + void set_args(const value_t& _args) { + args = _args; + } + value_t& value() { + return args; + } + + value_t& operator[](const std::size_t index) { + return args[index]; + } + const value_t& operator[](const std::size_t index) const { + return args[index]; + } + + void push_front(const value_t& val) { + args.push_front(val); + } + void push_back(const value_t& val) { + args.push_back(val); + } + void pop_back() { + args.pop_back(); + } + + value_t::sequence_t::const_iterator begin() const { + return args.begin(); + } + value_t::sequence_t::const_iterator end() const { + return args.end(); + } + + std::size_t size() const { + return args.size(); + } + bool empty() const { + return args.size() == 0; + } + +#if defined(HAVE_BOOST_SERIALIZATION) +private: + explicit call_scope_t() { + TRACE_CTOR(call_scope_t, ""); + } + + /** Serialization. */ + + friend class boost::serialization::access; + + template<class Archive> + void serialize(Archive& ar, const unsigned int /* version */) { + ar & boost::serialization::base_object<child_scope_t>(*this); + ar & args; + } +#endif // HAVE_BOOST_SERIALIZATION +}; + +class bind_scope_t : public child_scope_t +{ + bind_scope_t(); + +public: + scope_t& grandchild; + + explicit bind_scope_t(scope_t& _parent, + scope_t& _grandchild) + : child_scope_t(_parent), grandchild(_grandchild) { + TRACE_CTOR(bind_scope_t, "scope_t&, scope_t&"); + } + virtual ~bind_scope_t() { + TRACE_DTOR(bind_scope_t); + } + + virtual void define(const symbol_t::kind_t kind, const string& name, + expr_t::ptr_op_t def) { + parent->define(kind, name, def); + grandchild.define(kind, name, def); + } + + virtual expr_t::ptr_op_t lookup(const symbol_t::kind_t kind, + const string& name) { + if (expr_t::ptr_op_t def = grandchild.lookup(kind, name)) + return def; + return child_scope_t::lookup(kind, name); + } + +#if defined(HAVE_BOOST_SERIALIZATION) +private: + /** Serialization. */ + + friend class boost::serialization::access; + + template<class Archive> + void serialize(Archive& ar, const unsigned int /* version */) { + ar & boost::serialization::base_object<child_scope_t>(*this); + ar & grandchild; + } +#endif // HAVE_BOOST_SERIALIZATION +}; + +template <typename T> +T * search_scope(scope_t * ptr) +{ + if (T * sought = dynamic_cast<T *>(ptr)) + return sought; + + if (bind_scope_t * scope = dynamic_cast<bind_scope_t *>(ptr)) { + if (T * sought = search_scope<T>(&scope->grandchild)) + return sought; + return search_scope<T>(scope->parent); + } + else if (child_scope_t * scope = dynamic_cast<child_scope_t *>(ptr)) { + return search_scope<T>(scope->parent); + } + return NULL; +} + +template <typename T> +inline T& find_scope(child_scope_t& scope, bool skip_this = true) +{ + if (T * sought = search_scope<T>(skip_this ? scope.parent : &scope)) + return *sought; + + throw_(std::runtime_error, _("Could not find scope")); + return reinterpret_cast<T&>(scope); // never executed +} + +} // namespace ledger + +#endif // _SCOPE_H diff --git a/src/session.cc b/src/session.cc new file mode 100644 index 00000000..1882e554 --- /dev/null +++ b/src/session.cc @@ -0,0 +1,248 @@ +/* + * 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 "session.h" +#include "xact.h" +#include "account.h" +#include "journal.h" +#include "iterators.h" +#include "filters.h" +#include "archive.h" + +namespace ledger { + +void set_session_context(session_t * session) +{ + if (session) { + times_initialize(); + amount_t::initialize(); + + amount_t::parse_conversion("1.0m", "60s"); + amount_t::parse_conversion("1.0h", "60m"); + + value_t::initialize(); + } + else if (! session) { + value_t::shutdown(); + amount_t::shutdown(); + times_shutdown(); + } +} + +session_t::session_t() + : flush_on_next_data_file(false), + current_year(CURRENT_DATE().year()), + journal(new journal_t) +{ + TRACE_CTOR(session_t, ""); + + if (const char * home_var = std::getenv("HOME")) + HANDLER(price_db_).on(none, (path(home_var) / ".pricedb").string()); + else + HANDLER(price_db_).on(none, path("./.pricedb").string()); +} + +std::size_t session_t::read_data(const string& master_account) +{ + bool populated_data_files = false; + + if (HANDLER(file_).data_files.empty()) { + path file; + if (const char * home_var = std::getenv("HOME")) + file = path(home_var) / ".ledger"; + + if (! file.empty() && exists(file)) + HANDLER(file_).data_files.push_back(file); + else + throw_(parse_error, "No journal file was specified (please use -f)"); + + populated_data_files = true; + } + + std::size_t xact_count = 0; + + account_t * acct = journal->master; + if (! master_account.empty()) + acct = journal->find_account(master_account); + + optional<path> price_db_path; + if (HANDLED(price_db_)) + price_db_path = resolve_path(HANDLER(price_db_).str()); + +#if defined(HAVE_BOOST_SERIALIZATION) + optional<archive_t> cache; + if (HANDLED(cache_) && master_account.empty()) + cache = archive_t(HANDLED(cache_).str()); + + if (! (cache && + cache->should_load(HANDLER(file_).data_files) && + cache->load(*journal.get()))) { +#endif // HAVE_BOOST_SERIALIZATION + if (price_db_path) { + if (exists(*price_db_path)) { + if (journal->read(*price_db_path) > 0) + throw_(parse_error, _("Transactions not allowed in price history file")); + } + } + + foreach (const path& pathname, HANDLER(file_).data_files) { + if (pathname == "-") { + // To avoid problems with stdin and pipes, etc., we read the entire + // file in beforehand into a memory buffer, and then parcel it out + // from there. + std::ostringstream buffer; + + while (std::cin.good() && ! std::cin.eof()) { + char line[8192]; + std::cin.read(line, 8192); + std::streamsize count = std::cin.gcount(); + buffer.write(line, count); + } + buffer.flush(); + + std::istringstream buf_in(buffer.str()); + + xact_count += journal->read(buf_in, "/dev/stdin", acct); + journal->sources.push_back(journal_t::fileinfo_t()); + } else { + xact_count += journal->read(pathname, acct); + } + } + + assert(xact_count == journal->xacts.size()); + +#if defined(HAVE_BOOST_SERIALIZATION) + if (cache && cache->should_save(*journal.get())) + cache->save(*journal.get()); + } +#endif // HAVE_BOOST_SERIALIZATION + + if (populated_data_files) + HANDLER(file_).data_files.clear(); + + VERIFY(journal->valid()); + + return journal->xacts.size(); +} + +void session_t::read_journal_files() +{ + INFO_START(journal, "Read journal file"); + + string master_account; + if (HANDLED(master_account_)) + master_account = HANDLER(master_account_).str(); + + std::size_t count = read_data(master_account); + if (count == 0) + throw_(parse_error, + _("Failed to locate any transactions; did you specify a valid file with -f?")); + + INFO_FINISH(journal); + + INFO("Found " << count << " transactions"); +} + +void session_t::close_journal_files() +{ + journal.reset(); + amount_t::shutdown(); + + journal.reset(new journal_t); + amount_t::initialize(); +} + +option_t<session_t> * session_t::lookup_option(const char * p) +{ + switch (*p) { + case 'Q': + OPT_CH(download); // -Q + break; + case 'Z': + OPT_CH(price_exp_); + break; + case 'c': + OPT(cache_); + break; + case 'd': + OPT(download); // -Q + break; + case 'e': + OPT(european); + break; + case 'f': + OPT_(file_); // -f + break; + case 'i': + OPT(input_date_format_); + break; + case 'l': + OPT_ALT(price_exp_, leeway_); + break; + case 'm': + OPT(master_account_); + break; + case 'p': + OPT(price_db_); + else OPT(price_exp_); + break; + case 's': + OPT(strict); + break; + } + return NULL; +} + +expr_t::ptr_op_t session_t::lookup(const symbol_t::kind_t kind, + const string& name) +{ + switch (kind) { + case symbol_t::FUNCTION: + // Check if they are trying to access an option's setting or value. + if (option_t<session_t> * handler = lookup_option(name.c_str())) + return MAKE_OPT_FUNCTOR(session_t, handler); + break; + + case symbol_t::OPTION: + if (option_t<session_t> * handler = lookup_option(name.c_str())) + return MAKE_OPT_HANDLER(session_t, handler); + break; + + default: + break; + } + + return symbol_scope_t::lookup(kind, name); +} + +} // namespace ledger diff --git a/src/session.h b/src/session.h new file mode 100644 index 00000000..5c4612a0 --- /dev/null +++ b/src/session.h @@ -0,0 +1,151 @@ +/* + * 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. + */ + +/** + * @defgroup report Reporting + */ + +/** + * @file session.h + * @author John Wiegley + * + * @ingroup report + */ +#ifndef _SESSION_H +#define _SESSION_H + +#include "interactive.h" +#include "account.h" +#include "journal.h" +#include "option.h" +#include "commodity.h" + +namespace ledger { + +class commodity_pool_t; +class xact_t; + +class session_t : public symbol_scope_t +{ + friend void set_session_context(session_t * session); + +public: + bool flush_on_next_data_file; + date_t::year_type current_year; + std::auto_ptr<journal_t> journal; + + explicit session_t(); + virtual ~session_t() { + TRACE_DTOR(session_t); + } + + void set_flush_on_next_data_file(const bool truth) { + flush_on_next_data_file = truth; + } + + std::size_t read_data(const string& master_account = ""); + + void read_journal_files(); + void close_journal_files(); + + void report_options(std::ostream& out) + { + HANDLER(cache_).report(out); + HANDLER(download).report(out); + HANDLER(european).report(out); + HANDLER(file_).report(out); + HANDLER(input_date_format_).report(out); + HANDLER(master_account_).report(out); + HANDLER(price_db_).report(out); + HANDLER(price_exp_).report(out); + HANDLER(strict).report(out); + } + + option_t<session_t> * lookup_option(const char * p); + + virtual expr_t::ptr_op_t lookup(const symbol_t::kind_t kind, + const string& name); + + /** + * Option handlers + */ + + OPTION(session_t, cache_); + OPTION(session_t, download); // -Q + + OPTION_(session_t, european, DO() { + commodity_t::european_by_default = true; + }); + + OPTION__ + (session_t, price_exp_, // -Z + CTOR(session_t, price_exp_) { value = 24L * 3600L; } + DO_(args) { + value = args[1].to_long() * 60L; + }); + + OPTION__ + (session_t, file_, // -f + std::list<path> data_files; + CTOR(session_t, file_) {} + DO_(args) { + assert(args.size() == 2); + if (parent->flush_on_next_data_file) { + data_files.clear(); + parent->flush_on_next_data_file = false; + } + data_files.push_back(args[1].as_string()); + }); + + OPTION_(session_t, input_date_format_, DO_(args) { + // This changes static variables inside times.h, which affects the basic + // date parser. + set_input_date_format(args[1].as_string().c_str()); + }); + + OPTION(session_t, master_account_); + OPTION(session_t, price_db_); + OPTION(session_t, strict); +}; + +/** + * Set the current session context, transferring all static globals to point + * at the data structures related to this session. Although Ledger itself is + * not thread-safe, by locking, switching session context, then unlocking + * after an operation is done, multiple threads can sequentially make use of + * the library. Thus, a session_t maintains all of the information relating + * to a single usage of the Ledger library. + */ +void set_session_context(session_t * session); + +} // namespace ledger + +#endif // _SESSION_H diff --git a/src/stats.cc b/src/stats.cc new file mode 100644 index 00000000..b89a5949 --- /dev/null +++ b/src/stats.cc @@ -0,0 +1,121 @@ +/* + * 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 "draft.h" +#include "xact.h" +#include "post.h" +#include "account.h" +#include "report.h" +#include "session.h" + +namespace ledger { + +value_t report_statistics(call_scope_t& args) +{ + report_t& report(find_scope<report_t>(args)); + std::ostream& out(report.output_stream); + + const account_t::xdata_t::details_t& + statistics(report.session.journal->master->family_details(true)); + + if (! is_valid(statistics.earliest_post) && + ! is_valid(statistics.latest_post)) + return NULL_VALUE; + + assert(is_valid(statistics.earliest_post)); + assert(is_valid(statistics.latest_post)); + + { + straccstream accum; + out << ACCUM(accum << _("Time period: %1 to %2 (%3 days)") + << format_date(statistics.earliest_post) + << format_date(statistics.latest_post) + << (statistics.latest_post - + statistics.earliest_post).days()) + << std::endl << std::endl; + } + + out << _(" Files these postings came from:") << std::endl; + + foreach (const path& pathname, statistics.filenames) + if (! pathname.empty()) + out << " " << pathname.string() << std::endl; + out << std::endl; + + out << _(" Unique payees: "); + out.width(6); + out << statistics.payees_referenced.size() << std::endl; + + out << _(" Unique accounts: "); + out.width(6); + out << statistics.accounts_referenced.size() << std::endl; + + out << std::endl; + + out << _(" Number of postings: "); + out.width(6); + out << statistics.posts_count; + + out << " ("; + out.precision(2); + out << (double((statistics.latest_post - statistics.earliest_post).days()) / + double(statistics.posts_count)) << _(" per day)") << std::endl; + + out << _(" Uncleared postings: "); + out.width(6); + out << (statistics.posts_count - + statistics.posts_cleared_count) << std::endl; + + out << std::endl; + + out << _(" Days since last post: "); + out.width(6); + out << (CURRENT_DATE() - statistics.latest_post).days() + << std::endl; + + out << _(" Posts in last 7 days: "); + out.width(6); + out << statistics.posts_last_7_count << std::endl; + out << _(" Posts in last 30 days: "); + out.width(6); + out << statistics.posts_last_30_count << std::endl; + out << _(" Posts seen this month: "); + out.width(6); + out << statistics.posts_this_month_count << std::endl; + + out.flush(); + + return NULL_VALUE; +} + +} // namespace ledger diff --git a/src/utility/mask.h b/src/stats.h index daae014f..d2d10de6 100644 --- a/src/utility/mask.h +++ b/src/stats.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2007, John Wiegley. All rights reserved. + * 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 @@ -29,27 +29,27 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef _MASK_H -#define _MASK_H +/** + * @addtogroup stats + */ -#include "utils.h" +/** + * @file stats.h + * @author John Wiegley + * + * @ingroup report + */ +#ifndef _STATS_H +#define _STATS_H -namespace ledger { +#include "value.h" -class mask_t -{ - public: - bool exclude; - boost::regex expr; +namespace ledger { - explicit mask_t(const string& pattern); - mask_t(const mask_t& m) : exclude(m.exclude), expr(m.expr) {} +class call_scope_t; - bool match(const string& str) const { - return boost::regex_match(str, expr) && ! exclude; - } -}; +value_t report_statistics(call_scope_t& scope); } // namespace ledger -#endif // _MASK_H +#endif // _STATS_H diff --git a/src/stream.cc b/src/stream.cc new file mode 100644 index 00000000..e39b74e0 --- /dev/null +++ b/src/stream.cc @@ -0,0 +1,139 @@ +/* + * 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 "stream.h" + +namespace ledger { + +namespace { + /** + * @brief Forks a child process so that Ledger may handle running a + * pager + * + * In order for the pager option to work, Ledger has to run the pager + * itself, which requires Ledger to fork a new process in order to run + * the pager. This function does the necessary fork. After the fork, + * two processes exist. One of them is exec'd to create the pager; + * the other is still Ledger. + * + * This function returns only for the process that is still Ledger. + * + * @param out Pointer to a pointer to the output stream. This The + * pointer to the output stream is changed so that the stream is + * connected to the stdin of the pager. The caller is responsible for + * cleaning this up. + * + * @param pager_path Path to the pager command. + * + * @return The file descriptor of the pipe to the pager. The caller + * is responsible for cleaning this up. + * + * @exception std::logic_error Some problem was encountered, such as + * failure to create a pipe or failure to fork a child process. + */ + int do_fork(std::ostream ** os, const path& pager_path) + { + int pfd[2]; + + int status = pipe(pfd); + if (status == -1) + throw std::logic_error(_("Failed to create pipe")); + + status = fork(); + if (status < 0) { + throw std::logic_error(_("Failed to fork child process")); + } + else if (status == 0) { // child + // Duplicate pipe's reading end into stdin + status = dup2(pfd[0], STDIN_FILENO); + if (status == -1) + perror("dup2"); + + // Close unuseful file descriptors: the pipe's writing and reading + // ends (the latter is not needed anymore, after the duplication). + close(pfd[1]); + close(pfd[0]); + + // Find command name: its the substring starting right of the + // rightmost '/' character in the pager pathname. See manpage for + // strrchr. +#if BOOST_VERSION >= 103700 + path basename(pager_path.filename()); +#else + path basename(pager_path.leaf()); +#endif + execlp(pager_path.string().c_str(), basename.string().c_str(), NULL); + + // We should never, ever reach here + perror((std::string("execlp: ") + pager_path.string()).c_str()); + exit(1); + } + else { // parent + close(pfd[0]); + typedef iostreams::stream<iostreams::file_descriptor_sink> fdstream; + *os = new fdstream(pfd[1]); + } + return pfd[1]; + } +} + +void output_stream_t::initialize(const optional<path>& output_file, + const optional<path>& pager_path) +{ + if (output_file && *output_file != "-") + os = new ofstream(*output_file); + else if (pager_path) + pipe_to_pager_fd = do_fork(&os, *pager_path); + else + os = &std::cout; +} + +void output_stream_t::close() +{ + if (os != &std::cout) { + checked_delete(os); + os = &std::cout; + } + + if (pipe_to_pager_fd != -1) { + ::close(pipe_to_pager_fd); + pipe_to_pager_fd = -1; + + int status; + wait(&status); + if (! WIFEXITED(status) || WEXITSTATUS(status) != 0) + throw std::logic_error(_("Error in the pager")); + } +} + +} // namespace ledger diff --git a/src/stream.h b/src/stream.h new file mode 100644 index 00000000..e87da67a --- /dev/null +++ b/src/stream.h @@ -0,0 +1,142 @@ +/* + * 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. + */ + +/** + * @addtogroup util + */ + +/** + * @file stream.h + * @author John Wiegley, Omari Norman + * + * @ingroup util + * + * @brief A utility class for abstracting an output stream. + * + * Because Ledger might send output to a file, the console, or a pager + * child process, different cleanup is needed for each scenario. This + * file abstracts those various needs. + */ +#ifndef _STREAM_H +#define _STREAM_H + +#include "utils.h" + +namespace ledger { + +/** + * @brief An output stream + * + * A stream to output in Ledger may be going to one of three places: + * to stdout, to a file, or to a pager. Construct an output_stream_t and + * the stream will automatically be cleaned up upon destruction. + * + * This class suffers from "else-if-heimer's disease," see Marshall + * Cline's "C++ FAQ Lite". Arguably this should be three different + * classes, but that introduces additional unneeded complications. + */ +class output_stream_t +{ + output_stream_t& operator=(const output_stream_t&); + +private: + int pipe_to_pager_fd; + +public: + /** + * A pointer to the ostream. Don't delete this; the output_stream_t + * class takes care of this. + */ + std::ostream * os; + + /** + * Construct a new output_stream_t. + */ + output_stream_t() : pipe_to_pager_fd(-1), os(&std::cout) { + TRACE_CTOR(output_stream_t, ""); + } + + /** + * When copy-constructed, make the copy just be a new output stream. This + * allows large classes to rely on their default copy-constructor without + * worrying about pointer copying within output_stream_t. + */ + output_stream_t(const output_stream_t&) + : pipe_to_pager_fd(-1), os(&std::cout) { + TRACE_CTOR(output_stream_t, "copy"); + } + + /** + * Destroys an output_stream_t. This deletes the dynamically + * allocated ostream, if necessary. It also closes output file + * descriptor, if necessary. + */ + ~output_stream_t() { + TRACE_DTOR(output_stream_t); + close(); + } + + /** + * Initialize the output stream object. + * + * @param output_file File to which to send output. If both this + * and pager are set, output_file takes priority. + * + * @param pager_path Path to a pager. To not use a pager, leave this + * empty. + */ + void initialize(const optional<path>& output_file = none, + const optional<path>& pager_path = none); + + /** + * Convertor to a standard ostream. This is used so that we can + * stream directly to an object of type output_stream_t. + */ + operator std::ostream&() { + return *os; + } + + /** + * Flushing function. A simple proxy for ostream's flush. + */ + void flush() { + os->flush(); + } + + /** + * Close the output stream, waiting on the pager process if necessary. + */ + void close(); +}; + +} // namespace ledger + +#endif // _STREAM_H diff --git a/src/utility/system.hh b/src/system.hh.in index 92fc5874..341ce129 100644 --- a/src/utility/system.hh +++ b/src/system.hh.in @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2007, John Wiegley. All rights reserved. + * 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 @@ -29,13 +29,11 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef _SYSTEM_HH -#define _SYSTEM_HH - /** + * @addtogroup util + * * @file system.hh * @author John Wiegley - * @date Mon Apr 23 03:43:05 2007 * * @brief All system headers needed by Ledger. * @@ -43,8 +41,10 @@ * None of these header files (with the exception of acconf.h, when * configure is re-run) are expected to change. */ +#ifndef _SYSTEM_HH +#define _SYSTEM_HH -#include "acconf.h" +#include "config.h" #if defined(__GNUG__) && __GNUG__ < 3 #define _XOPEN_SOURCE @@ -52,6 +52,8 @@ #include <algorithm> #include <exception> +#include <typeinfo> +#include <locale> #include <stdexcept> #include <iostream> #include <streambuf> @@ -63,11 +65,13 @@ #include <map> #include <memory> #include <new> +#include <set> #include <stack> #include <string> #include <vector> #if defined(__GNUG__) && __GNUG__ < 3 + namespace std { inline ostream & right (ostream & i) { i.setf(i.right, i.adjustfield); @@ -78,6 +82,15 @@ namespace std { return i; } } + +typedef std::streamoff istream_pos_type; +typedef std::streamoff ostream_pos_type; + +#else // ! (defined(__GNUG__) && __GNUG__ < 3) + +typedef std::istream::pos_type istream_pos_type; +typedef std::ostream::pos_type ostream_pos_type; + #endif #include <cassert> @@ -86,7 +99,7 @@ namespace std { #include <cstdio> #include <cstdlib> #include <cstring> -#include <ctime> +#include <csignal> #if defined __FreeBSD__ && __FreeBSD__ <= 4 // FreeBSD has a broken isspace macro, so don't use it @@ -94,46 +107,43 @@ namespace std { #endif #include <sys/stat.h> - -#ifdef WIN32 +#if defined(WIN32) #include <io.h> #else #include <unistd.h> #endif - #if defined(HAVE_GETPWUID) || defined(HAVE_GETPWNAM) #include <pwd.h> #endif -#if defined(HAVE_NL_LANGINFO) -#include <langinfo.h> +#if defined(HAVE_UNIX_PIPES) +#include <sys/types.h> +#include <sys/wait.h> #endif - -#include <gmp.h> - -#define HAVE_GDTOA 1 -#ifdef HAVE_GDTOA -#include "gdtoa.h" +#if defined(HAVE_GETTEXT) +#include "gettext.h" +#define _(str) gettext(str) +#else +#define _(str) str #endif -extern "C" { -#if defined(HAVE_EXPAT) -#include <expat.h> // expat XML parser -#elif defined(HAVE_XMLPARSE) -#include <xmlparse.h> // expat XML parser -#endif -} +#include <gmp.h> +#include <mpfr.h> +#include "sha1.h" +#include "utf8.h" -#if defined(HAVE_LIBOFX) -#include <libofx.h> +#if defined(HAVE_LIBEDIT) +#include <editline/readline.h> #endif #include <boost/algorithm/string/classification.hpp> #include <boost/algorithm/string/predicate.hpp> -#include <boost/any.hpp> +#include <boost/bind.hpp> #include <boost/cast.hpp> #include <boost/current_function.hpp> #include <boost/date_time/posix_time/posix_time.hpp> +#include <boost/date_time/posix_time/posix_time_io.hpp> +#include <boost/date_time/gregorian/gregorian_io.hpp> #include <boost/filesystem/convenience.hpp> #include <boost/filesystem/exception.hpp> #include <boost/filesystem/fstream.hpp> @@ -142,20 +152,105 @@ extern "C" { #include <boost/foreach.hpp> #include <boost/function.hpp> #include <boost/intrusive_ptr.hpp> -#include <boost/lambda/bind.hpp> -#include <boost/lambda/lambda.hpp> +#include <boost/iostreams/stream.hpp> +#include <boost/iostreams/write.hpp> +#include <boost/iostreams/device/file_descriptor.hpp> +#include <boost/iterator/transform_iterator.hpp> #include <boost/lexical_cast.hpp> -#include <boost/multi_index/hashed_index.hpp> -#include <boost/multi_index/key_extractors.hpp> -#include <boost/multi_index/ordered_index.hpp> -#include <boost/multi_index/random_access_index.hpp> -#include <boost/multi_index/sequenced_index.hpp> -#include <boost/multi_index_container.hpp> #include <boost/operators.hpp> #include <boost/optional.hpp> #include <boost/ptr_container/ptr_list.hpp> -#include <boost/ptr_container/ptr_vector.hpp> +#include <boost/random/mersenne_twister.hpp> +#include <boost/random/uniform_int.hpp> +#include <boost/random/uniform_real.hpp> +#include <boost/random/variate_generator.hpp> +#if defined(HAVE_BOOST_REGEX_UNICODE) +#include <boost/regex/icu.hpp> +#else #include <boost/regex.hpp> +#endif // HAVE_BOOST_REGEX_UNICODE #include <boost/variant.hpp> +#include <boost/version.hpp> + +#if defined(HAVE_BOOST_SERIALIZATION) + +#include <boost/archive/binary_iarchive.hpp> +#include <boost/archive/binary_oarchive.hpp> + +#include <boost/serialization/base_object.hpp> +#include <boost/serialization/binary_object.hpp> +#include <boost/serialization/optional.hpp> +#include <boost/serialization/shared_ptr.hpp> +#include <boost/serialization/variant.hpp> +#include <boost/serialization/utility.hpp> +#include <boost/serialization/export.hpp> +#include <boost/serialization/level.hpp> +#include <boost/serialization/string.hpp> +#include <boost/serialization/vector.hpp> +#include <boost/serialization/deque.hpp> +#include <boost/serialization/list.hpp> +#include <boost/serialization/map.hpp> + +#include <boost/date_time/posix_time/time_serialize.hpp> +#include <boost/date_time/gregorian/greg_serialize.hpp> + +namespace boost { +namespace serialization { + +template <class Archive> +void serialize(Archive& ar, boost::filesystem::path& p, const unsigned int) +{ + std::string s; + if (Archive::is_saving::value) + s = p.string(); + + ar & s; + + if (Archive::is_loading::value) + p = s; +} + +template <class Archive, class T> +void serialize(Archive& ar, boost::intrusive_ptr<T>& ptr, const unsigned int) +{ + if (Archive::is_saving::value) { + T * p = ptr.get(); + ar & p; + } + else if (Archive::is_loading::value) { + T * p; + ar & p; + ptr.reset(p); + } +} + +template <class Archive, class T> +void serialize(Archive&, boost::function<T>&, const unsigned int) +{ +} + +template <class Archive> +void serialize(Archive& ar, istream_pos_type& pos, const unsigned int) +{ + ar & make_binary_object(&pos, sizeof(istream_pos_type)); +} + +} // namespace serialization +} // namespace boost + +#endif // HAVE_BOOST_SERIALIZATION + +#if defined(HAVE_BOOST_PYTHON) + +#include <boost/python.hpp> + +#include <boost/python/detail/wrap_python.hpp> +#include <datetime.h> +#include <unicodeobject.h> + +#include <boost/python/module_init.hpp> +#include <boost/python/suite/indexing/vector_indexing_suite.hpp> + +#endif // HAVE_BOOST_PYTHON #endif // _SYSTEM_HH diff --git a/src/temps.cc b/src/temps.cc new file mode 100644 index 00000000..fd099e9a --- /dev/null +++ b/src/temps.cc @@ -0,0 +1,137 @@ +/* + * 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 "xact.h" +#include "post.h" +#include "account.h" +#include "temps.h" + +namespace ledger { + +temporaries_t::~temporaries_t() +{ + if (post_temps) { + foreach (post_t& post, *post_temps) { + if (! post.xact->has_flags(ITEM_TEMP)) + post.xact->remove_post(&post); + + if (post.account && ! post.account->has_flags(ACCOUNT_TEMP)) + post.account->remove_post(&post); + } + } + + if (acct_temps) { + foreach (account_t& acct, *acct_temps) { + if (acct.parent && ! acct.parent->has_flags(ACCOUNT_TEMP)) + acct.parent->remove_account(&acct); + } + } +} + +xact_t& temporaries_t::copy_xact(xact_t& origin) +{ + if (! xact_temps) + xact_temps = std::list<xact_t>(); + + xact_temps->push_back(origin); + xact_t& temp(xact_temps->back()); + + temp.add_flags(ITEM_TEMP); + return temp; +} + +xact_t& temporaries_t::create_xact() +{ + if (! xact_temps) + xact_temps = std::list<xact_t>(); + + xact_temps->push_back(xact_t()); + xact_t& temp(xact_temps->back()); + + temp.add_flags(ITEM_TEMP); + return temp; +} + +post_t& temporaries_t::copy_post(post_t& origin, xact_t& xact, + account_t * account) +{ + if (! post_temps) + post_temps = std::list<post_t>(); + + post_temps->push_back(origin); + post_t& temp(post_temps->back()); + + if (account) + temp.account = account; + temp.add_flags(ITEM_TEMP); + + temp.account->add_post(&temp); + xact.add_post(&temp); + + return temp; +} + +post_t& temporaries_t::create_post(xact_t& xact, account_t * account) +{ + if (! post_temps) + post_temps = std::list<post_t>(); + + post_temps->push_back(post_t(account)); + post_t& temp(post_temps->back()); + + temp.account = account; + temp.add_flags(ITEM_TEMP); + + temp.account->add_post(&temp); + xact.add_post(&temp); + + return temp; +} + +account_t& temporaries_t::create_account(const string& name, + account_t * parent) +{ + if (! acct_temps) + acct_temps = std::list<account_t>(); + + acct_temps->push_back(account_t(parent, name)); + account_t& temp(acct_temps->back()); + + if (parent) + parent->add_account(&temp); + + temp.add_flags(ACCOUNT_TEMP); + return temp; +} + +} // namespace ledger diff --git a/src/temps.h b/src/temps.h new file mode 100644 index 00000000..4243079c --- /dev/null +++ b/src/temps.h @@ -0,0 +1,76 @@ +/* + * 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. + */ + +/** + * @addtogroup report + */ + +/** + * @file temps.h + * @author John Wiegley + * + * @ingroup report + */ +#ifndef _TEMPS_H +#define _TEMPS_H + +namespace ledger { + +class temporaries_t +{ + optional<std::list<xact_t> > xact_temps; + optional<std::list<post_t> > post_temps; + optional<std::list<account_t> > acct_temps; + +public: + ~temporaries_t(); + + xact_t& copy_xact(xact_t& origin); + xact_t& create_xact(); + xact_t& last_xact() { + return xact_temps->back(); + } + post_t& copy_post(post_t& origin, xact_t& xact, + account_t * account = NULL); + post_t& create_post(xact_t& xact, account_t * account); + post_t& last_post() { + return post_temps->back(); + } + account_t& create_account(const string& name = "", + account_t * parent = NULL); + account_t& last_account() { + return acct_temps->back(); + } +}; + +} // namespace ledger + +#endif // _TEMPS_H diff --git a/src/textual.cc b/src/textual.cc new file mode 100644 index 00000000..cf670cae --- /dev/null +++ b/src/textual.cc @@ -0,0 +1,1396 @@ +/* + * 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 "journal.h" +#include "xact.h" +#include "post.h" +#include "account.h" +#include "option.h" +#include "query.h" +#include "pstream.h" +#include "pool.h" +#include "session.h" + +#define TIMELOG_SUPPORT 1 +#if defined(TIMELOG_SUPPORT) +#include "timelog.h" +#endif + +namespace ledger { + +namespace { + class instance_t : public noncopyable, public scope_t + { + static const std::size_t MAX_LINE = 1024; + + public: + typedef std::pair<commodity_t *, amount_t> fixed_rate_t; + typedef variant<account_t *, string, fixed_rate_t> state_t; + + std::list<state_t>& state_stack; + +#if defined(TIMELOG_SUPPORT) + time_log_t& timelog; +#endif + instance_t * parent; + std::istream& in; + scope_t& scope; + journal_t& journal; + account_t * master; + const path * original_file; + accounts_map account_aliases; + bool strict; + path pathname; + char linebuf[MAX_LINE + 1]; + std::size_t linenum; + istream_pos_type line_beg_pos; + istream_pos_type curr_pos; + std::size_t count; + std::size_t errors; + + optional<date_t::year_type> current_year; + + instance_t(std::list<state_t>& _state_stack, +#if defined(TIMELOG_SUPPORT) + 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); + + ~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); + } + bool front_is_fixed_rate() { + return state_stack.front().type() == typeid(fixed_rate_t); + } + + 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) + void clock_in_directive(char * line, bool capitalized); + void clock_out_directive(char * line, bool capitalized); +#endif + + void default_commodity_directive(char * line); + void default_account_directive(char * line); + void price_conversion_directive(char * line); + void price_xact_directive(char * line); + void nomarket_directive(char * line); + void year_directive(char * line); + void option_directive(char * line); + void automated_xact_directive(char * line); + void period_xact_directive(char * line); + void xact_directive(char * line, std::streamsize len); + void include_directive(char * line); + void master_account_directive(char * line); + void end_directive(char * line); + void alias_directive(char * line); + void fixed_directive(char * line); + void tag_directive(char * line); + void define_directive(char * line); + bool general_directive(char * line); + + post_t * parse_post(char * line, + std::streamsize len, + account_t * account, + xact_t * xact, + bool honor_strict = true, + bool defer_expr = false); + + bool parse_posts(account_t * account, + xact_base_t& xact, + const bool defer_expr = false); + + xact_t * parse_xact(char * line, + std::streamsize len, + account_t * account); + + virtual expr_t::ptr_op_t lookup(const symbol_t::kind_t kind, + const string& name); + }; + + void parse_amount_expr(scope_t& scope, + std::istream& in, + amount_t& amount, + optional<expr_t> * amount_expr, + post_t * post, + const parse_flags_t& flags = PARSE_DEFAULT, + const bool defer_expr = false) + { + expr_t expr(in, flags.plus_flags(PARSE_PARTIAL)); + + DEBUG("textual.parse", "Parsed an amount expression"); + +#if defined(DEBUG_ENABLED) + DEBUG_IF("textual.parse") { + if (_debug_stream) { + ledger::dump_value_expr(*_debug_stream, expr); + *_debug_stream << std::endl; + } + } +#endif + + if (expr) { + bind_scope_t bound_scope(scope, *post); + if (defer_expr) { + assert(amount_expr); + *amount_expr = expr; + (*amount_expr)->compile(bound_scope); + } else { + value_t result(expr.calc(bound_scope)); + if (result.is_long()) { + amount = result.to_amount(); + } else { + if (! result.is_amount()) + throw_(amount_error, + _("Amount expressions must result in a simple amount")); + amount = result.as_amount(); + } + DEBUG("textual.parse", "The posting amount is " << amount); + } + } + } +} + +instance_t::instance_t(std::list<state_t>& _state_stack, +#if defined(TIMELOG_SUPPORT) + 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) + : state_stack(_state_stack), +#if defined(TIMELOG_SUPPORT) + timelog(_timelog), +#endif + parent(_parent), in(_in), scope(_scope), + journal(_journal), master(_master), + original_file(_original_file), strict(_strict) +{ + TRACE_CTOR(instance_t, "..."); + + if (! master) + master = journal.master; + state_stack.push_front(master); + + if (_original_file) + pathname = *_original_file; + else + pathname = "/dev/stdin"; +} + +instance_t::~instance_t() +{ + TRACE_DTOR(instance_t); + + assert(! state_stack.empty()); + state_stack.pop_front(); +} + +void instance_t::parse() +{ + INFO("Parsing file '" << pathname.string() << "'"); + + TRACE_START(instance_parse, 1, + "Done parsing file '" << pathname.string() << "'"); + + if (! in.good() || in.eof()) + return; + + linenum = 0; + errors = 0; + count = 0; + curr_pos = in.tellg(); + + while (in.good() && ! in.eof()) { + try { + read_next_directive(); + } + catch (const std::exception& err) { + string current_context = error_context(); + + if (parent) { + std::list<instance_t *> instances; + + for (instance_t * instance = parent; + instance; + instance = instance->parent) + instances.push_front(instance); + + foreach (instance_t * instance, instances) + add_error_context(_("In file included from %1") + << file_context(instance->pathname, + instance->linenum)); + } + add_error_context(_("While parsing file %1") + << file_context(pathname, linenum)); + + if (caught_signal != NONE_CAUGHT) + throw; + + string context = error_context(); + if (! context.empty()) + std::cerr << context << std::endl; + + if (! current_context.empty()) + std::cerr << current_context << std::endl; + + std::cerr << _("Error: ") << err.what() << std::endl; + errors++; + } + } + + TRACE_STOP(instance_parse, 1); +} + +std::streamsize instance_t::read_line(char *& line) +{ + assert(in.good()); + assert(! in.eof()); // no one should call us in that case + + line_beg_pos = curr_pos; + + check_for_signal(); + + in.getline(linebuf, MAX_LINE); + std::streamsize len = in.gcount(); + + if (len > 0) { + if (linenum == 0 && utf8::is_bom(linebuf)) + line = &linebuf[3]; + else + line = linebuf; + + if (line[len - 1] == '\r') // strip Windows CRLF down to LF + line[--len] = '\0'; + + linenum++; + + curr_pos = line_beg_pos; + curr_pos += len; + + return len - 1; // LF is being silently dropped + } + return 0; +} + +void instance_t::read_next_directive() +{ + char * line; + std::streamsize len = read_line(line); + + if (len == 0 || line == NULL) + return; + + switch (line[0]) { + case '\0': + assert(false); // shouldn't ever reach here + break; + + case ' ': + case '\t': { + break; + } + + case ';': // comments + case '#': + case '*': + case '|': + break; + + case '-': // option setting + option_directive(line); + break; + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + xact_directive(line, len); + break; + case '=': // automated xact + automated_xact_directive(line); + break; + case '~': // period xact + period_xact_directive(line); + break; + + case '@': + case '!': + line++; + // fall through... + default: // some other directive + if (! general_directive(line)) { + switch (line[0]) { +#if defined(TIMELOG_SUPPORT) + case 'i': + clock_in_directive(line, false); + break; + case 'I': + clock_in_directive(line, true); + break; + + case 'o': + clock_out_directive(line, false); + break; + case 'O': + clock_out_directive(line, true); + break; + + case 'h': + case 'b': + break; +#endif // TIMELOG_SUPPORT + + case 'A': // a default account for unbalanced posts + default_account_directive(line); + break; + case 'C': // a set of conversions + price_conversion_directive(line); + break; + case 'D': // a default commodity for "xact" + default_commodity_directive(line); + break; + case 'N': // don't download prices + nomarket_directive(line); + break; + case 'P': // a pricing xact + price_xact_directive(line); + break; + case 'Y': // set the current year + year_directive(line); + break; + } + } + break; + } +} + +#if defined(TIMELOG_SUPPORT) + +void instance_t::clock_in_directive(char * line, + bool /*capitalized*/) +{ + string datetime(line, 2, 19); + + char * p = skip_ws(line + 22); + char * n = next_element(p, true); + char * end = n ? next_element(n, true) : NULL; + + if (end && *end == ';') + end = skip_ws(end + 1); + else + end = NULL; + + position_t position; + position.pathname = pathname; + position.beg_pos = line_beg_pos; + position.beg_line = linenum; + position.end_pos = curr_pos; + position.end_line = linenum; + + time_xact_t event(position, parse_datetime(datetime, current_year), + p ? top_account()->find_account(p) : NULL, + n ? n : "", + end ? end : ""); + + timelog.clock_in(event); +} + +void instance_t::clock_out_directive(char * line, + bool /*capitalized*/) +{ + string datetime(line, 2, 19); + + char * p = skip_ws(line + 22); + char * n = next_element(p, true); + char * end = n ? next_element(n, true) : NULL; + + if (end && *end == ';') + end = skip_ws(end + 1); + else + end = NULL; + + position_t position; + position.pathname = pathname; + position.beg_pos = line_beg_pos; + position.beg_line = linenum; + position.end_pos = curr_pos; + position.end_line = linenum; + + time_xact_t event(position, parse_datetime(datetime, current_year), + p ? top_account()->find_account(p) : NULL, + n ? n : "", + end ? end : ""); + + timelog.clock_out(event); + count++; +} + +#endif // TIMELOG_SUPPORT + +void instance_t::default_commodity_directive(char * line) +{ + amount_t amt(skip_ws(line + 1)); + VERIFY(amt.valid()); + commodity_pool_t::current_pool->default_commodity = &amt.commodity(); + amt.commodity().add_flags(COMMODITY_KNOWN); +} + +void instance_t::default_account_directive(char * line) +{ + journal.bucket = top_account()->find_account(skip_ws(line + 1)); + journal.bucket->add_flags(ACCOUNT_KNOWN); +} + +void instance_t::price_conversion_directive(char * line) +{ + if (char * p = std::strchr(line + 1, '=')) { + *p++ = '\0'; + amount_t::parse_conversion(line + 1, p); + } +} + +void instance_t::price_xact_directive(char * line) +{ + optional<std::pair<commodity_t *, price_point_t> > point = + commodity_pool_t::current_pool->parse_price_directive(skip_ws(line + 1)); + if (! point) + throw parse_error(_("Pricing entry failed to parse")); +} + +void instance_t::nomarket_directive(char * line) +{ + char * p = skip_ws(line + 1); + string symbol; + commodity_t::parse_symbol(p, symbol); + + if (commodity_t * commodity = + commodity_pool_t::current_pool->find_or_create(symbol)) + commodity->add_flags(COMMODITY_NOMARKET | COMMODITY_KNOWN); +} + +void instance_t::year_directive(char * line) +{ + current_year = lexical_cast<unsigned short>(skip_ws(line + 1)); +} + +void instance_t::option_directive(char * line) +{ + char * p = next_element(line); + if (! p) { + p = std::strchr(line, '='); + if (p) + *p++ = '\0'; + } + + if (! process_option(pathname.string(), line + 2, scope, p, line)) + throw_(option_error, _("Illegal option --%1") << line + 2); +} + +void instance_t::automated_xact_directive(char * line) +{ + istream_pos_type pos = line_beg_pos; + std::size_t lnum = linenum; + + bool reveal_context = true; + + try { + + std::auto_ptr<auto_xact_t> ae + (new auto_xact_t(query_t(string(skip_ws(line + 1)), + keep_details_t(true, true, true)))); + + reveal_context = false; + + if (parse_posts(top_account(), *ae.get(), true)) { + reveal_context = true; + + journal.auto_xacts.push_back(ae.get()); + + ae->journal = &journal; + ae->pos = position_t(); + ae->pos->pathname = pathname; + ae->pos->beg_pos = pos; + ae->pos->beg_line = lnum; + ae->pos->end_pos = curr_pos; + ae->pos->end_line = linenum; + + ae.release(); + } + + } + catch (const std::exception& err) { + if (reveal_context) { + add_error_context(_("While parsing periodic transaction:")); + add_error_context(source_context(pathname, pos, curr_pos, "> ")); + } + throw; + } +} + +void instance_t::period_xact_directive(char * line) +{ + istream_pos_type pos = line_beg_pos; + std::size_t lnum = linenum; + + bool reveal_context = true; + + try { + + std::auto_ptr<period_xact_t> pe(new period_xact_t(skip_ws(line + 1))); + + reveal_context = false; + + if (parse_posts(top_account(), *pe.get())) { + reveal_context = true; + pe->journal = &journal; + + if (pe->finalize()) { + journal.extend_xact(pe.get()); + journal.period_xacts.push_back(pe.get()); + + pe->pos = position_t(); + pe->pos->pathname = pathname; + pe->pos->beg_pos = pos; + pe->pos->beg_line = lnum; + pe->pos->end_pos = curr_pos; + pe->pos->end_line = linenum; + + pe.release(); + } else { + pe->journal = NULL; + throw parse_error(_("Period transaction failed to balance")); + } + } + + } + catch (const std::exception& err) { + if (reveal_context) { + add_error_context(_("While parsing periodic transaction:")); + add_error_context(source_context(pathname, pos, curr_pos, "> ")); + } + throw; + } +} + +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, top_account())) { + std::auto_ptr<xact_t> manager(xact); + + if (journal.add_xact(xact)) { + manager.release(); // it's owned by the journal now + count++; + } + // It's perfectly valid for the journal to reject the xact, which it will + // do if the xact has no substantive effect (for example, a checking + // xact, all of whose postings have null amounts). + } else { + throw parse_error(_("Failed to parse transaction")); + } + + TRACE_STOP(xacts, 1); +} + +void instance_t::include_directive(char * line) +{ + path filename; + + if (line[0] != '/' && line[0] != '\\' && line[0] != '~') { + string::size_type pos = pathname.string().rfind('/'); + if (pos == string::npos) + pos = pathname.string().rfind('\\'); + if (pos != string::npos) + filename = path(string(pathname.string(), 0, pos + 1)) / line; + } else { + filename = line; + } + + filename = resolve_path(filename); + + DEBUG("textual.include", "Line " << linenum << ": " << + "Including path '" << filename << "'"); + + if (! exists(filename)) + throw_(std::runtime_error, + _("File to include was not found: '%1'") << filename); + + ifstream stream(filename); + + instance_t instance(state_stack, +#if defined(TIMELOG_SUPPORT) + timelog, +#endif + stream, scope, journal, master, + &filename, strict, this); + instance.parse(); + + errors += instance.errors; + count += instance.count; +} + +void instance_t::master_account_directive(char * line) +{ + 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 * kind) +{ + string name(kind); + + if ((name.empty() || name == "account") && ! front_is_account()) + throw_(std::runtime_error, + _("'end account' directive does not match open directive")); + else if (name == "tag" && ! front_is_string()) + throw_(std::runtime_error, + _("'end tag' directive does not match open directive")); + else if (name == "fixed" && ! front_is_fixed_rate()) + throw_(std::runtime_error, + _("'end fixed' directive does not match open directive")); + + if (state_stack.size() <= 1) + throw_(std::runtime_error, + _("'end' found, but no enclosing tag or account directive")); + else + state_stack.pop_front(); +} + +void instance_t::alias_directive(char * line) +{ + char * b = skip_ws(line); + if (char * e = std::strchr(b, '=')) { + char * z = e - 1; + while (std::isspace(*z)) + *z-- = '\0'; + *e++ = '\0'; + e = skip_ws(e); + + // Once we have an alias name (b) and the target account + // 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 = top_account()->find_account(e); + std::pair<accounts_map::iterator, bool> result + = account_aliases.insert(accounts_map::value_type(b, acct)); + assert(result.second); + } +} + +void instance_t::fixed_directive(char * line) +{ + if (optional<std::pair<commodity_t *, price_point_t> > price_point = + commodity_pool_t::current_pool->parse_price_directive(trim_ws(line), + true)) { + state_stack.push_front(fixed_rate_t(price_point->first, + price_point->second.price)); + } else { + throw_(std::runtime_error, _("Error in fixed directive")); + } +} + +void instance_t::tag_directive(char * line) +{ + string tag(trim_ws(line)); + + if (tag.find(':') == string::npos) + tag = string(":") + tag + ":"; + + state_stack.push_front(tag); +} + +void instance_t::define_directive(char * line) +{ + expr_t def(skip_ws(line)); + def.compile(scope); // causes definitions to be established +} + +bool instance_t::general_directive(char * line) +{ + char buf[8192]; + + std::strcpy(buf, line); + + char * p = buf; + char * arg = next_element(buf); + + if (*p == '@' || *p == '!') + p++; + + switch (*p) { + case 'a': + if (std::strcmp(p, "account") == 0) { + master_account_directive(arg); + return true; + } + else if (std::strcmp(p, "alias") == 0) { + alias_directive(arg); + return true; + } + break; + + case 'b': + if (std::strcmp(p, "bucket") == 0) { + default_account_directive(arg); + return true; + } + break; + + case 'd': + if (std::strcmp(p, "def") == 0 || std::strcmp(p, "define") == 0) { + define_directive(arg); + return true; + } + break; + + case 'e': + if (std::strcmp(p, "end") == 0) { + end_directive(arg); + return true; + } + break; + + case 'f': + if (std::strcmp(p, "fixed") == 0) { + fixed_directive(arg); + return true; + } + break; + + case 'i': + if (std::strcmp(p, "include") == 0) { + include_directive(arg); + return true; + } + break; + + case 't': + if (std::strcmp(p, "tag") == 0) { + tag_directive(arg); + return true; + } + break; + + case 'y': + if (std::strcmp(p, "year") == 0) { + year_directive(arg); + return true; + } + break; + } + + if (expr_t::ptr_op_t op = lookup(symbol_t::DIRECTIVE, p)) { + call_scope_t args(*this); + args.push_back(string_value(p)); + op->as_function()(args); + return true; + } + + return false; +} + +post_t * instance_t::parse_post(char * line, + std::streamsize len, + account_t * account, + xact_t * xact, + bool honor_strict, + bool defer_expr) +{ + TRACE_START(post_details, 1, "Time spent parsing postings:"); + + std::auto_ptr<post_t> post(new post_t); + + post->xact = xact; // this could be NULL + post->pos = position_t(); + post->pos->pathname = pathname; + post->pos->beg_pos = line_beg_pos; + post->pos->beg_line = linenum; + + char buf[MAX_LINE + 1]; + std::strcpy(buf, line); + std::size_t beg = 0; + + try { + + // Parse the state flag + + assert(line); + assert(*line); + + char * p = skip_ws(line); + + switch (*p) { + case '*': + post->set_state(item_t::CLEARED); + p = skip_ws(p + 1); + DEBUG("textual.parse", "line " << linenum << ": " + << "Parsed the CLEARED flag"); + break; + + case '!': + post->set_state(item_t::PENDING); + p = skip_ws(p + 1); + DEBUG("textual.parse", "line " << linenum << ": " + << "Parsed the PENDING flag"); + break; + } + + if (xact && + ((xact->_state == item_t::CLEARED && post->_state != item_t::CLEARED) || + (xact->_state == item_t::PENDING && post->_state == item_t::UNCLEARED))) + post->set_state(xact->_state); + + // Parse the account name + + if (! *p) + throw parse_error(_("Posting has no account")); + + char * next = next_element(p, true); + char * e = p + std::strlen(p); + + while (e > p && std::isspace(*(e - 1))) + e--; + + if ((*p == '[' && *(e - 1) == ']') || (*p == '(' && *(e - 1) == ')')) { + post->add_flags(POST_VIRTUAL); + DEBUG("textual.parse", "line " << linenum << ": " + << "Parsed a virtual account name"); + + if (*p == '[') { + post->add_flags(POST_MUST_BALANCE); + DEBUG("textual.parse", "line " << linenum << ": " + << "Posting must balance"); + } + p++; e--; + } + + string name(p, e - p); + DEBUG("textual.parse", "line " << linenum << ": " + << "Parsed account name " << name); + + if (account_aliases.size() > 0) { + accounts_map::const_iterator i = account_aliases.find(name); + if (i != account_aliases.end()) + post->account = (*i).second; + } + if (! post->account) + post->account = account->find_account(name); + + if (honor_strict && strict && ! post->account->has_flags(ACCOUNT_KNOWN)) { + if (post->_state == item_t::UNCLEARED) + warning_(_("\"%1\", line %2: Unknown account '%3'") + << pathname << linenum << post->account->fullname()); + post->account->add_flags(ACCOUNT_KNOWN); + } + + // Parse the optional amount + + bool saw_amount = false; + + if (next && *next && (*next != ';' && *next != '=')) { + saw_amount = true; + + beg = next - line; + ptristream stream(next, len - beg); + + if (*next != '(') // indicates a value expression + post->amount.parse(stream, PARSE_NO_REDUCE); + else + parse_amount_expr(scope, stream, post->amount, &post->amount_expr, + post.get(), PARSE_NO_REDUCE | PARSE_SINGLE | + PARSE_NO_ASSIGN, defer_expr); + + if (! post->amount.is_null() && post->amount.has_commodity()) { + if (honor_strict && strict && + ! post->amount.commodity().has_flags(COMMODITY_KNOWN)) { + if (post->_state == item_t::UNCLEARED) + warning_(_("\"%1\", line %2: Unknown commodity '%3'") + << pathname << linenum << post->amount.commodity()); + post->amount.commodity().add_flags(COMMODITY_KNOWN); + } + + if (! post->amount.has_annotation()) { + foreach (state_t& state, state_stack) { + if (state.type() == typeid(fixed_rate_t)) { + fixed_rate_t& rate(boost::get<fixed_rate_t>(state)); + if (*rate.first == post->amount.commodity()) { + annotation_t details(rate.second); + details.add_flags(ANNOTATION_PRICE_FIXATED); + post->amount.annotate(details); + break; + } + } + } + } + } + + DEBUG("textual.parse", "line " << linenum << ": " + << "post amount = " << post->amount); + + if (stream.eof()) { + next = NULL; + } else { + next = skip_ws(next + static_cast<std::ptrdiff_t>(stream.tellg())); + + // Parse the optional cost (@ PER-UNIT-COST, @@ TOTAL-COST) + + if (*next == '@') { + DEBUG("textual.parse", "line " << linenum << ": " + << "Found a price indicator"); + + bool per_unit = true; + + if (*++next == '@') { + per_unit = false; + DEBUG("textual.parse", "line " << linenum << ": " + << "And it's for a total price"); + } + + beg = ++next - line; + + p = skip_ws(next); + if (*p) { + post->cost = amount_t(); + + beg = p - line; + ptristream cstream(p, len - beg); + + if (*p != '(') // indicates a value expression + post->cost->parse(cstream, PARSE_NO_MIGRATE); + else + parse_amount_expr(scope, cstream, *post->cost, NULL, post.get(), + PARSE_NO_MIGRATE | PARSE_SINGLE | PARSE_NO_ASSIGN, + defer_expr); + + if (post->cost->sign() < 0) + throw parse_error(_("A posting's cost may not be negative")); + + post->cost->in_place_unround(); + + if (per_unit) { + // For the sole case where the cost might be uncommoditized, + // guarantee that the commodity of the cost after multiplication + // is the same as it was before. + commodity_t& cost_commodity(post->cost->commodity()); + *post->cost *= post->amount; + post->cost->set_commodity(cost_commodity); + } + + DEBUG("textual.parse", "line " << linenum << ": " + << "Total cost is " << *post->cost); + DEBUG("textual.parse", "line " << linenum << ": " + << "Annotated amount is " << post->amount); + + if (cstream.eof()) + next = NULL; + else + next = skip_ws(p + static_cast<std::ptrdiff_t>(cstream.tellg())); + } else { + throw parse_error(_("Expected a cost amount")); + } + } + } + } + + // Parse the optional balance assignment + + if (xact && next && *next == '=') { + DEBUG("textual.parse", "line " << linenum << ": " + << "Found a balance assignment indicator"); + + beg = ++next - line; + + p = skip_ws(next); + if (*p) { + post->assigned_amount = amount_t(); + + beg = p - line; + ptristream stream(p, len - beg); + + if (*p != '(') // indicates a value expression + post->assigned_amount->parse(stream, PARSE_NO_MIGRATE); + else + parse_amount_expr(scope, stream, *post->assigned_amount, NULL, + post.get(), PARSE_SINGLE | PARSE_NO_MIGRATE, + defer_expr); + + if (post->assigned_amount->is_null()) { + if (post->amount.is_null()) + throw parse_error(_("Balance assignment must evaluate to a constant")); + else + throw parse_error(_("Balance assertion must evaluate to a constant")); + } + + DEBUG("textual.parse", "line " << linenum << ": " + << "POST assign: parsed amt = " << *post->assigned_amount); + + amount_t& amt(*post->assigned_amount); + value_t account_total + (post->account->amount(false).strip_annotations(keep_details_t())); + + DEBUG("post.assign", + "line " << linenum << ": " "account balance = " << account_total); + DEBUG("post.assign", + "line " << linenum << ": " "post amount = " << amt); + + amount_t diff = amt; + + switch (account_total.type()) { + case value_t::AMOUNT: + diff -= account_total.as_amount(); + break; + + case value_t::BALANCE: + if (optional<amount_t> comm_bal = + account_total.as_balance().commodity_amount(amt.commodity())) + diff -= *comm_bal; + break; + + default: + break; + } + + DEBUG("post.assign", + "line " << linenum << ": " << "diff = " << diff); + DEBUG("textual.parse", + "line " << linenum << ": " << "POST assign: diff = " << diff); + + if (! diff.is_zero()) { + if (! post->amount.is_null()) { + diff -= post->amount; + if (! diff.is_zero()) + throw_(parse_error, _("Balance assertion off by %1") << diff); + } else { + post->amount = diff; + DEBUG("textual.parse", "line " << linenum << ": " + << "Overwrite null posting"); + } + } + + if (stream.eof()) + next = NULL; + else + next = skip_ws(p + static_cast<std::ptrdiff_t>(stream.tellg())); + } else { + throw parse_error(_("Expected an balance assignment/assertion amount")); + } + } + + // Parse the optional note + + if (next && *next == ';') { + post->append_note(++next, current_year); + next = line + len; + DEBUG("textual.parse", "line " << linenum << ": " + << "Parsed a posting note"); + } + + // There should be nothing more to read + + if (next && *next) + throw_(parse_error, + _("Unexpected char '%1' (Note: inline math requires parentheses)") + << *next); + + post->pos->end_pos = curr_pos; + post->pos->end_line = linenum; + + 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); + + return post.release(); + + } + catch (const std::exception& err) { + add_error_context(_("While parsing posting:")); + add_error_context(line_context(buf, beg, len)); + throw; + } +} + +bool instance_t::parse_posts(account_t * account, + xact_base_t& xact, + const bool defer_expr) +{ + TRACE_START(xact_posts, 1, "Time spent parsing postings:"); + + bool added = false; + + while (peek_whitespace_line()) { + char * line; + std::streamsize len = read_line(line); + assert(len > 0); + + if (post_t * post = + parse_post(line, len, account, NULL, /* honor_strict= */ false, + defer_expr)) { + xact.add_post(post); + added = true; + } + } + + TRACE_STOP(xact_posts, 1); + + return added; +} + +xact_t * instance_t::parse_xact(char * line, + std::streamsize len, + account_t * account) +{ + TRACE_START(xact_text, 1, "Time spent parsing transaction text:"); + + std::auto_ptr<xact_t> xact(new xact_t); + + xact->pos = position_t(); + xact->pos->pathname = pathname; + xact->pos->beg_pos = line_beg_pos; + xact->pos->beg_line = linenum; + + bool reveal_context = true; + + try { + + // Parse the date + + char * next = next_element(line); + + if (char * p = std::strchr(line, '=')) { + *p++ = '\0'; + xact->_date_eff = parse_date(p, current_year); + } + xact->_date = parse_date(line, current_year); + + // Parse the optional cleared flag: * + + if (next) { + switch (*next) { + case '*': + xact->_state = item_t::CLEARED; + next = skip_ws(++next); + break; + case '!': + xact->_state = item_t::PENDING; + next = skip_ws(++next); + break; + } + } + + // Parse the optional code: (TEXT) + + if (next && *next == '(') { + if (char * p = std::strchr(next++, ')')) { + *p++ = '\0'; + xact->code = next; + next = skip_ws(p); + } + } + + // Parse the description text + + if (next && *next) { + char * p = next_element(next, true); + xact->payee = next; + next = p; + } else { + xact->payee = _("<Unspecified payee>"); + } + + // Parse the xact note + + if (next && *next == ';') + xact->append_note(++next, current_year); + + TRACE_STOP(xact_text, 1); + + // Parse all of the posts associated with this xact + + TRACE_START(xact_details, 1, "Time spent parsing transaction details:"); + + post_t * last_post = NULL; + + while (peek_whitespace_line()) { + len = read_line(line); + + char * p = skip_ws(line); + if (! *p) + break; + + if (*p == ';') { + item_t * item; + if (last_post) + item = last_post; + else + item = xact.get(); + + // This is a trailing note, and possibly a metadata info tag + item->append_note(p + 1, current_year); + item->pos->end_pos = curr_pos; + item->pos->end_line++; + } else { + reveal_context = false; + + if (post_t * post = + parse_post(p, len - (p - line), account, xact.get())) { + xact->add_post(post); + last_post = post; + } + } + } + + if (xact->_state == item_t::UNCLEARED) { + item_t::state_t result = item_t::CLEARED; + + foreach (post_t * post, xact->posts) { + if (post->_state == item_t::UNCLEARED) { + result = item_t::UNCLEARED; + break; + } + else if (post->_state == item_t::PENDING) { + result = item_t::PENDING; + } + } + } + + xact->pos->end_pos = curr_pos; + xact->pos->end_line = linenum; + + 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); + + return xact.release(); + + } + catch (const std::exception& err) { + if (reveal_context) { + add_error_context(_("While parsing transaction:")); + add_error_context(source_context(xact->pos->pathname, + xact->pos->beg_pos, curr_pos, "> ")); + } + throw; + } +} + +expr_t::ptr_op_t instance_t::lookup(const symbol_t::kind_t kind, + const string& name) +{ + return scope.lookup(kind, name); +} + +std::size_t journal_t::parse(std::istream& in, + scope_t& scope, + account_t * master, + const path * original_file, + bool strict) +{ + TRACE_START(parsing_total, 1, "Total time spent parsing text:"); + + std::list<instance_t::state_t> state_stack; +#if defined(TIMELOG_SUPPORT) + time_log_t timelog(*this); +#endif + + instance_t parsing_instance(state_stack, +#if defined(TIMELOG_SUPPORT) + timelog, +#endif + in, scope, *this, master, + original_file, strict); + parsing_instance.parse(); + + TRACE_STOP(parsing_total, 1); + + // These tracers were started in textual.cc + TRACE_FINISH(xact_text, 1); + TRACE_FINISH(xact_details, 1); + TRACE_FINISH(xact_posts, 1); + TRACE_FINISH(xacts, 1); + TRACE_FINISH(instance_parse, 1); // report per-instance timers + TRACE_FINISH(parsing_total, 1); + + if (parsing_instance.errors > 0) + throw static_cast<int>(parsing_instance.errors); + + return parsing_instance.count; +} + +} // namespace ledger diff --git a/src/timelog.cc b/src/timelog.cc new file mode 100644 index 00000000..25bf8e94 --- /dev/null +++ b/src/timelog.cc @@ -0,0 +1,158 @@ +/* + * 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 "timelog.h" +#include "xact.h" +#include "post.h" +#include "account.h" +#include "journal.h" + +namespace ledger { + +namespace { + void clock_out_from_timelog(std::list<time_xact_t>& time_xacts, + time_xact_t out_event, + journal_t& journal) + { + time_xact_t event; + + if (time_xacts.size() == 1) { + event = time_xacts.back(); + time_xacts.clear(); + } + else if (time_xacts.empty()) { + throw parse_error(_("Timelog check-out event without a check-in")); + } + else if (! out_event.account) { + throw parse_error + (_("When multiple check-ins are active, checking out requires an account")); + } + else { + bool found = false; + + for (std::list<time_xact_t>::iterator i = time_xacts.begin(); + i != time_xacts.end(); + i++) + if (out_event.account == (*i).account) { + event = *i; + found = true; + time_xacts.erase(i); + break; + } + + if (! found) + throw parse_error + (_("Timelog check-out event does not match any current check-ins")); + } + + if (out_event.checkin < event.checkin) + throw parse_error + (_("Timelog check-out date less than corresponding check-in")); + + if (! out_event.desc.empty() && event.desc.empty()) { + event.desc = out_event.desc; + out_event.desc = empty_string; + } + + if (! out_event.note.empty() && event.note.empty()) + event.note = out_event.note; + + std::auto_ptr<xact_t> curr(new xact_t); + curr->_date = out_event.checkin.date(); + curr->code = out_event.desc; // if it wasn't used above + curr->payee = event.desc; + curr->pos = event.position; + + if (! event.note.empty()) + curr->append_note(event.note.c_str()); + + char buf[32]; + std::sprintf(buf, "%lds", long((out_event.checkin - event.checkin) + .total_seconds())); + amount_t amt; + amt.parse(buf); + VERIFY(amt.valid()); + + post_t * post = new post_t(event.account, amt, POST_VIRTUAL); + post->set_state(item_t::CLEARED); + post->pos = event.position; + curr->add_post(post); + event.account->add_post(post); + + if (! journal.add_xact(curr.get())) + throw parse_error(_("Failed to record 'out' timelog transaction")); + else + curr.release(); + } +} // unnamed namespace + +time_log_t::~time_log_t() +{ + TRACE_DTOR(time_log_t); + + if (! time_xacts.empty()) { + std::list<account_t *> accounts; + + foreach (time_xact_t& time_xact, time_xacts) + accounts.push_back(time_xact.account); + + foreach (account_t * account, accounts) + clock_out_from_timelog(time_xacts, + time_xact_t(none, CURRENT_TIME(), account), + journal); + + assert(time_xacts.empty()); + } +} + +void time_log_t::clock_in(time_xact_t event) +{ + if (! time_xacts.empty()) { + foreach (time_xact_t& time_xact, time_xacts) { + if (event.account == time_xact.account) + throw parse_error(_("Cannot double check-in to the same account")); + } + } + + time_xacts.push_back(event); +} + +void time_log_t::clock_out(time_xact_t event) +{ + if (time_xacts.empty()) + throw std::logic_error(_("Timelog check-out event without a check-in")); + + clock_out_from_timelog(time_xacts, event, journal); +} + +} // namespace ledger diff --git a/src/utility/flags.h b/src/timelog.h index 5ae8b60f..83256dfa 100644 --- a/src/utility/flags.h +++ b/src/timelog.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2007, John Wiegley. All rights reserved. + * 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 @@ -29,75 +29,75 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef _FLAGS_H -#define _FLAGS_H +/** + * @addtogroup data + */ -template <typename T = boost::uint_least8_t> -class supports_flags -{ -public: - typedef T flags_t; +/** + * @file timelog.h + * @author John Wiegley + * + * @ingroup data + */ +#ifndef _TIMELOG_H +#define _TIMELOG_H -protected: - flags_t flags_; +#include "utils.h" +#include "times.h" +#include "item.h" -public: - supports_flags() : flags_(0) {} - supports_flags(const flags_t arg) : flags_(arg) {} +namespace ledger { - flags_t flags() const { - return flags_; - } - bool has_flags(const flags_t arg) const { - return flags_ & arg; - } +class account_t; +class journal_t; + +class time_xact_t +{ +public: + datetime_t checkin; + account_t * account; + string desc; + string note; + position_t position; - void set_flags(const flags_t arg) { - flags_ = arg; + time_xact_t() : account(NULL) { + TRACE_CTOR(time_xact_t, ""); } - void clear_flags() { - flags_ = 0; + time_xact_t(const optional<position_t>& _position, + const datetime_t& _checkin, + account_t * _account = NULL, + const string& _desc = "", + const string& _note = "") + : checkin(_checkin), account(_account), desc(_desc), note(_note), + position(_position ? *_position : position_t()) { + TRACE_CTOR(time_xact_t, + "position_t, datetime_t, account_t *, string, string"); } - void add_flags(const flags_t arg) { - flags_ |= arg; + time_xact_t(const time_xact_t& xact) + : checkin(xact.checkin), account(xact.account), + desc(xact.desc), note(xact.note), position(xact.position) { + TRACE_CTOR(time_xact_t, "copy"); } - void drop_flags(const flags_t arg) { - flags_ &= ~arg; + ~time_xact_t() throw() { + TRACE_DTOR(time_xact_t); } }; -template <typename T = boost::uint_least8_t> -class delegates_flags : public boost::noncopyable +class time_log_t { -public: - typedef T flags_t; - -protected: - supports_flags<T>& flags_; + std::list<time_xact_t> time_xacts; + journal_t& journal; public: - delegates_flags() : flags_() {} - delegates_flags(supports_flags<T>& arg) : flags_(arg) {} - - flags_t flags() const { - return flags_.flags(); - } - bool has_flags(const flags_t arg) const { - return flags_.has_flags(arg); + time_log_t(journal_t& _journal) : journal(_journal) { + TRACE_CTOR(time_log_t, "journal_t&"); } + ~time_log_t(); - void set_flags(const flags_t arg) { - flags_.set_flags(arg); - } - void clear_flags() { - flags_.clear_flags(); - } - void add_flags(const flags_t arg) { - flags_.add_flags(arg); - } - void drop_flags(const flags_t arg) { - flags_.drop_flags(arg); - } + void clock_in(time_xact_t event); + void clock_out(time_xact_t event); }; -#endif // _FLAGS_H +} // namespace ledger + +#endif // _TIMELOG_H diff --git a/src/times.cc b/src/times.cc new file mode 100644 index 00000000..d4317509 --- /dev/null +++ b/src/times.cc @@ -0,0 +1,1587 @@ +/* + * 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 "times.h" + +namespace ledger { + +optional<datetime_t> epoch; + +date_time::weekdays start_of_week = gregorian::Sunday; + +//#define USE_BOOST_FACETS 1 + +namespace { + template <typename T, typename InputFacetType, typename OutputFacetType> + class temporal_io_t : public noncopyable + { + const char * fmt_str; +#if defined(USE_BOOST_FACETS) + std::istringstream input_stream; + std::ostringstream output_stream; + InputFacetType * input_facet; + OutputFacetType * output_facet; + std::string temp_string; +#endif // USE_BOOST_FACETS + + public: + date_traits_t traits; + bool input; + + temporal_io_t(const char * _fmt_str, bool _input) + : fmt_str(_fmt_str), + traits(icontains(fmt_str, "%y"), + icontains(fmt_str, "%m") || icontains(fmt_str, "%b"), + icontains(fmt_str, "%d")), + input(_input) { +#if defined(USE_BOOST_FACETS) + if (input) { + input_facet = new InputFacetType(fmt_str); + input_stream.imbue(std::locale(std::locale::classic(), input_facet)); + } else { + output_facet = new OutputFacetType(fmt_str); + output_stream.imbue(std::locale(std::locale::classic(), output_facet)); + } +#endif // USE_BOOST_FACETS + } + + void set_format(const char * fmt) { + fmt_str = fmt; + traits = date_traits_t(icontains(fmt_str, "%y"), + icontains(fmt_str, "%m") || + icontains(fmt_str, "%b"), + icontains(fmt_str, "%d")); +#if defined(USE_BOOST_FACETS) + if (input) + input_facet->format(fmt_str); + else + output_facet->format(fmt_str); +#endif // USE_BOOST_FACETS + } + + T parse(const char * str) { + } + + std::string format(const T& when) { +#if defined(USE_BOOST_FACETS) + output_stream.str(temp_string); + output_stream.seekp(std::ios_base::beg); + output_stream.clear(); + output_stream << when; + return output_stream.str(); +#else // USE_BOOST_FACETS + std::tm data(to_tm(when)); + char buf[128]; + std::strftime(buf, 127, fmt_str, &data); + return buf; +#endif // USE_BOOST_FACETS + } + }; + + template <> + datetime_t temporal_io_t<datetime_t, posix_time::time_input_facet, + posix_time::time_facet> + ::parse(const char * str) + { +#if defined(USE_BOOST_FACETS) + input_stream.seekg(std::ios_base::beg); + input_stream.clear(); + input_stream.str(str); + + datetime_t when; + input_stream >> when; +#if defined(DEBUG_ON) + if (when.is_not_a_date_time()) + DEBUG("times.parse", "Failed to parse date/time '" << str + << "' using pattern '" << fmt_str << "'"); +#endif + + if (! when.is_not_a_date_time() && + input_stream.good() && ! input_stream.eof() && + input_stream.peek() != EOF) { + DEBUG("times.parse", "This string has leftovers: '" << str << "'"); + return datetime_t(); + } + return when; +#else // USE_BOOST_FACETS + std::tm data; + std::memset(&data, 0, sizeof(std::tm)); + if (strptime(str, fmt_str, &data)) + return posix_time::ptime_from_tm(data); + else + return datetime_t(); +#endif // USE_BOOST_FACETS + } + + template <> + date_t temporal_io_t<date_t, gregorian::date_input_facet, + gregorian::date_facet> + ::parse(const char * str) + { +#if defined(USE_BOOST_FACETS) + input_stream.seekg(std::ios_base::beg); + input_stream.clear(); + input_stream.str(str); + + date_t when; + input_stream >> when; +#if defined(DEBUG_ON) + if (when.is_not_a_date()) + DEBUG("times.parse", "Failed to parse date '" << str + << "' using pattern '" << fmt_str << "'"); +#endif + + if (! when.is_not_a_date() && + input_stream.good() && ! input_stream.eof() && + input_stream.peek() != EOF) { + DEBUG("times.parse", "This string has leftovers: '" << str << "'"); + return date_t(); + } + return when; +#else // USE_BOOST_FACETS + std::tm data; + std::memset(&data, 0, sizeof(std::tm)); + data.tm_mday = 1; // some formats have no day + if (strptime(str, fmt_str, &data)) + return gregorian::date_from_tm(data); + else + return date_t(); +#endif // USE_BOOST_FACETS + } + + typedef temporal_io_t<datetime_t, posix_time::time_input_facet, + posix_time::time_facet> datetime_io_t; + typedef temporal_io_t<date_t, gregorian::date_input_facet, + gregorian::date_facet> date_io_t; + + shared_ptr<datetime_io_t> input_datetime_io; + shared_ptr<date_io_t> input_date_io; + shared_ptr<datetime_io_t> written_datetime_io; + shared_ptr<date_io_t> written_date_io; + shared_ptr<datetime_io_t> printed_datetime_io; + shared_ptr<date_io_t> printed_date_io; + + std::vector<shared_ptr<date_io_t> > readers; + + date_t parse_date_mask_routine(const char * date_str, date_io_t& io, + optional_year year, + date_traits_t * traits = NULL) + { + date_t when; + + if (std::strchr(date_str, '/')) { + when = io.parse(date_str); + } else { + char buf[128]; + VERIFY(std::strlen(date_str) < 127); + std::strcpy(buf, date_str); + + for (char * p = buf; *p; p++) + if (*p == '.' || *p == '-') + *p = '/'; + + when = io.parse(buf); + } + + if (! when.is_not_a_date()) { + DEBUG("times.parse", "Parsed date string: " << date_str); + DEBUG("times.parse", "Parsed result is: " << when); + + if (traits) + *traits = io.traits; + + if (! io.traits.has_year) { + when = date_t(year ? *year : CURRENT_DATE().year(), + when.month(), when.day()); + + if (when.month() > CURRENT_DATE().month()) + when -= gregorian::years(1); + } + } + return when; + } + + date_t parse_date_mask(const char * date_str, optional_year year, + date_traits_t * traits = NULL) + { + if (input_date_io.get()) { + date_t when = parse_date_mask_routine(date_str, *input_date_io.get(), + year, traits); + if (! when.is_not_a_date()) + return when; + } + + foreach (shared_ptr<date_io_t>& reader, readers) { + date_t when = parse_date_mask_routine(date_str, *reader.get(), + year, traits); + if (! when.is_not_a_date()) + return when; + } + + throw_(date_error, _("Invalid date: %1") << date_str); + return date_t(); + } +} + +optional<date_time::weekdays> string_to_day_of_week(const std::string& str) +{ + if (str == _("sun") || str == _("sunday") || str == "0") + return gregorian::Sunday; + else if (str == _("mon") || str == _("monday") || str == "1") + return gregorian::Monday; + else if (str == _("tue") || str == _("tuesday") || str == "2") + return gregorian::Tuesday; + else if (str == _("wed") || str == _("wednesday") || str == "3") + return gregorian::Wednesday; + else if (str == _("thu") || str == _("thursday") || str == "4") + return gregorian::Thursday; + else if (str == _("fri") || str == _("friday") || str == "5") + return gregorian::Friday; + else if (str == _("sat") || str == _("saturday") || str == "6") + return gregorian::Saturday; + else + return none; +} + +optional<date_time::months_of_year> +string_to_month_of_year(const std::string& str) +{ + if (str == _("jan") || str == _("january") || str == "0") + return gregorian::Jan; + else if (str == _("feb") || str == _("february") || str == "1") + return gregorian::Feb; + else if (str == _("mar") || str == _("march") || str == "2") + return gregorian::Mar; + else if (str == _("apr") || str == _("april") || str == "3") + return gregorian::Apr; + else if (str == _("may") || str == _("may") || str == "4") + return gregorian::May; + else if (str == _("jun") || str == _("june") || str == "5") + return gregorian::Jun; + else if (str == _("jul") || str == _("july") || str == "6") + return gregorian::Jul; + else if (str == _("aug") || str == _("august") || str == "7") + return gregorian::Aug; + else if (str == _("sep") || str == _("september") || str == "8") + return gregorian::Sep; + else if (str == _("oct") || str == _("october") || str == "9") + return gregorian::Oct; + else if (str == _("nov") || str == _("november") || str == "10") + return gregorian::Nov; + else if (str == _("dec") || str == _("december") || str == "11") + return gregorian::Dec; + else + return none; +} + +datetime_t parse_datetime(const char * str, optional_year) +{ + datetime_t when = input_datetime_io->parse(str); + if (when.is_not_a_date_time()) + throw_(date_error, _("Invalid date/time: %1") << str); + return when; +} + +date_t parse_date(const char * str, optional_year current_year) +{ + return parse_date_mask(str, current_year); +} + +date_t date_specifier_t::begin(const optional_year& current_year) const +{ + assert(year || current_year); + + year_type the_year = year ? *year : static_cast<year_type>(*current_year); + month_type the_month = month ? *month : date_t::month_type(1); + day_type the_day = day ? *day : date_t::day_type(1); + + if (day) + assert(! wday); + else if (wday) + assert(! day); + + // jww (2009-11-16): Handle wday. If a month is set, find the most recent + // wday in that month; if the year is set, then in that year. + + return gregorian::date(static_cast<date_t::year_type>(the_year), + static_cast<date_t::month_type>(the_month), + static_cast<date_t::day_type>(the_day)); +} + +date_t date_specifier_t::end(const optional_year& current_year) const +{ + if (day || wday) + return begin(current_year) + gregorian::days(1); + else if (month) + return begin(current_year) + gregorian::months(1); + else if (year) + return begin(current_year) + gregorian::years(1); + else { + assert(false); + return date_t(); + } +} + +std::ostream& operator<<(std::ostream& out, + const date_duration_t& duration) +{ + if (duration.quantum == date_duration_t::DAYS) + out << duration.length << " day(s)"; + else if (duration.quantum == date_duration_t::WEEKS) + out << duration.length << " week(s)"; + else if (duration.quantum == date_duration_t::MONTHS) + out << duration.length << " month(s)"; + else if (duration.quantum == date_duration_t::QUARTERS) + out << duration.length << " quarter(s)"; + else { + assert(duration.quantum == date_duration_t::YEARS); + out << duration.length << " year(s)"; + } + return out; +} + +class date_parser_t +{ + friend void show_period_tokens(std::ostream& out, const string& arg); + + class lexer_t + { + friend class date_parser_t; + + string::const_iterator begin; + string::const_iterator end; + + public: + struct token_t + { + enum kind_t { + UNKNOWN, + + TOK_DATE, + TOK_INT, + TOK_SLASH, + TOK_DASH, + TOK_DOT, + + TOK_A_YEAR, + TOK_A_MONTH, + TOK_A_WDAY, + + TOK_SINCE, + TOK_UNTIL, + TOK_IN, + TOK_THIS, + TOK_NEXT, + TOK_LAST, + TOK_EVERY, + + TOK_TODAY, + TOK_TOMORROW, + TOK_YESTERDAY, + + TOK_YEAR, + TOK_QUARTER, + TOK_MONTH, + TOK_WEEK, + TOK_DAY, + + TOK_YEARLY, + TOK_QUARTERLY, + TOK_BIMONTHLY, + TOK_MONTHLY, + TOK_BIWEEKLY, + TOK_WEEKLY, + TOK_DAILY, + + TOK_YEARS, + TOK_QUARTERS, + TOK_MONTHS, + TOK_WEEKS, + TOK_DAYS, + + END_REACHED + + } kind; + + typedef variant<unsigned short, + string, + date_specifier_t::year_type, + date_time::months_of_year, + date_time::weekdays, + date_specifier_t> content_t; + + optional<content_t> value; + + explicit token_t(kind_t _kind = UNKNOWN, + const optional<content_t>& _value = + content_t(empty_string)) + : kind(_kind), value(_value) { + TRACE_CTOR(date_parser_t::lexer_t::token_t, ""); + } + token_t(const token_t& tok) + : kind(tok.kind), value(tok.value) { + TRACE_CTOR(date_parser_t::lexer_t::token_t, "copy"); + } + ~token_t() throw() { + TRACE_DTOR(date_parser_t::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 { + std::ostringstream out; + + switch (kind) { + case UNKNOWN: + out << boost::get<string>(*value); + break; + case TOK_DATE: + return boost::get<date_specifier_t>(*value).to_string(); + case TOK_INT: + out << boost::get<unsigned short>(*value); + break; + case TOK_SLASH: return "/"; + case TOK_DASH: return "-"; + case TOK_DOT: return "."; + case TOK_A_YEAR: + out << boost::get<date_specifier_t::year_type>(*value); + break; + case TOK_A_MONTH: + out << date_specifier_t::month_type + (boost::get<date_time::months_of_year>(*value)); + break; + case TOK_A_WDAY: + out << date_specifier_t::day_of_week_type + (boost::get<date_time::weekdays>(*value)); + break; + case TOK_SINCE: return "since"; + case TOK_UNTIL: return "until"; + case TOK_IN: return "in"; + case TOK_THIS: return "this"; + case TOK_NEXT: return "next"; + case TOK_LAST: return "last"; + case TOK_EVERY: return "every"; + case TOK_TODAY: return "today"; + case TOK_TOMORROW: return "tomorrow"; + case TOK_YESTERDAY: return "yesterday"; + case TOK_YEAR: return "year"; + case TOK_QUARTER: return "quarter"; + case TOK_MONTH: return "month"; + case TOK_WEEK: return "week"; + case TOK_DAY: return "day"; + case TOK_YEARLY: return "yearly"; + case TOK_QUARTERLY: return "quarterly"; + case TOK_BIMONTHLY: return "bimonthly"; + case TOK_MONTHLY: return "monthly"; + case TOK_BIWEEKLY: return "biweekly"; + case TOK_WEEKLY: return "weekly"; + case TOK_DAILY: return "daily"; + case TOK_YEARS: return "years"; + case TOK_QUARTERS: return "quarters"; + case TOK_MONTHS: return "months"; + case TOK_WEEKS: return "weeks"; + case TOK_DAYS: return "days"; + case END_REACHED: return "<EOF>"; + default: + assert(false); + return empty_string; + } + + return out.str(); + } + + void dump(std::ostream& out) const { + switch (kind) { + case UNKNOWN: out << "UNKNOWN"; break; + case TOK_DATE: out << "TOK_DATE"; break; + case TOK_INT: out << "TOK_INT"; break; + case TOK_SLASH: out << "TOK_SLASH"; break; + case TOK_DASH: out << "TOK_DASH"; break; + case TOK_DOT: out << "TOK_DOT"; break; + case TOK_A_YEAR: out << "TOK_A_YEAR"; break; + case TOK_A_MONTH: out << "TOK_A_MONTH"; break; + case TOK_A_WDAY: out << "TOK_A_WDAY"; break; + case TOK_SINCE: out << "TOK_SINCE"; break; + case TOK_UNTIL: out << "TOK_UNTIL"; break; + case TOK_IN: out << "TOK_IN"; break; + case TOK_THIS: out << "TOK_THIS"; break; + case TOK_NEXT: out << "TOK_NEXT"; break; + case TOK_LAST: out << "TOK_LAST"; break; + case TOK_EVERY: out << "TOK_EVERY"; break; + case TOK_TODAY: out << "TOK_TODAY"; break; + case TOK_TOMORROW: out << "TOK_TOMORROW"; break; + case TOK_YESTERDAY: out << "TOK_YESTERDAY"; break; + case TOK_YEAR: out << "TOK_YEAR"; break; + case TOK_QUARTER: out << "TOK_QUARTER"; break; + case TOK_MONTH: out << "TOK_MONTH"; break; + case TOK_WEEK: out << "TOK_WEEK"; break; + case TOK_DAY: out << "TOK_DAY"; break; + case TOK_YEARLY: out << "TOK_YEARLY"; break; + case TOK_QUARTERLY: out << "TOK_QUARTERLY"; break; + case TOK_BIMONTHLY: out << "TOK_BIMONTHLY"; break; + case TOK_MONTHLY: out << "TOK_MONTHLY"; break; + case TOK_BIWEEKLY: out << "TOK_BIWEEKLY"; break; + case TOK_WEEKLY: out << "TOK_WEEKLY"; break; + case TOK_DAILY: out << "TOK_DAILY"; break; + case TOK_YEARS: out << "TOK_YEARS"; break; + case TOK_QUARTERS: out << "TOK_QUARTERS"; break; + case TOK_MONTHS: out << "TOK_MONTHS"; break; + case TOK_WEEKS: out << "TOK_WEEKS"; break; + case TOK_DAYS: out << "TOK_DAYS"; break; + case END_REACHED: out << "END_REACHED"; break; + default: + assert(false); + break; + } + } + + void unexpected(); + static void expected(char wanted, char c = '\0'); + }; + + token_t token_cache; + + lexer_t(string::const_iterator _begin, + string::const_iterator _end) + : begin(_begin), end(_end) + { + TRACE_CTOR(date_parser_t::lexer_t, ""); + } + lexer_t(const lexer_t& lexer) + : begin(lexer.begin), end(lexer.end), + token_cache(lexer.token_cache) + { + TRACE_CTOR(date_parser_t::lexer_t, "copy"); + } + ~lexer_t() throw() { + TRACE_DTOR(date_parser_t::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; + } + }; + + string arg; + lexer_t lexer; + +public: + date_parser_t(const string& _arg) + : arg(_arg), lexer(arg.begin(), arg.end()) { + TRACE_CTOR(date_parser_t, ""); + } + date_parser_t(const date_parser_t& parser) + : arg(parser.arg), lexer(parser.lexer) { + TRACE_CTOR(date_parser_t, "copy"); + } + ~date_parser_t() throw() { + TRACE_DTOR(date_parser_t); + } + + date_interval_t parse(); + +private: + void determine_when(lexer_t::token_t& tok, date_specifier_t& specifier); +}; + +void date_parser_t::determine_when(date_parser_t::lexer_t::token_t& tok, + date_specifier_t& specifier) +{ + switch (tok.kind) { + case lexer_t::token_t::TOK_DATE: + specifier = boost::get<date_specifier_t>(*tok.value); + break; + + case lexer_t::token_t::TOK_INT: + specifier.day = + date_specifier_t::day_type(boost::get<unsigned short>(*tok.value)); + break; + case lexer_t::token_t::TOK_A_YEAR: + specifier.year = boost::get<date_specifier_t::year_type>(*tok.value); + break; + case lexer_t::token_t::TOK_A_MONTH: + specifier.month = + date_specifier_t::month_type + (boost::get<date_time::months_of_year>(*tok.value)); + break; + case lexer_t::token_t::TOK_A_WDAY: + specifier.wday = + date_specifier_t::day_of_week_type + (boost::get<date_time::weekdays>(*tok.value)); + break; + + default: + tok.unexpected(); + break; + } +} + +date_interval_t date_parser_t::parse() +{ + optional<date_specifier_t> since_specifier; + optional<date_specifier_t> until_specifier; + optional<date_specifier_t> inclusion_specifier; + + date_interval_t period; + date_t today = CURRENT_DATE(); + bool end_inclusive = false; + + for (lexer_t::token_t tok = lexer.next_token(); + tok.kind != lexer_t::token_t::END_REACHED; + tok = lexer.next_token()) { + switch (tok.kind) { +#if 0 + case lexer_t::token_t::TOK_INT: + // jww (2009-11-18): NYI + assert(! "Need to allow for expressions like \"4 months ago\""); + tok.unexpected(); + break; +#endif + + case lexer_t::token_t::TOK_DATE: + if (! inclusion_specifier) + inclusion_specifier = date_specifier_t(); + determine_when(tok, *inclusion_specifier); + break; + + case lexer_t::token_t::TOK_INT: + if (! inclusion_specifier) + inclusion_specifier = date_specifier_t(); + determine_when(tok, *inclusion_specifier); + break; + + case lexer_t::token_t::TOK_A_YEAR: + if (! inclusion_specifier) + inclusion_specifier = date_specifier_t(); + determine_when(tok, *inclusion_specifier); + break; + + case lexer_t::token_t::TOK_A_MONTH: + if (! inclusion_specifier) + inclusion_specifier = date_specifier_t(); + determine_when(tok, *inclusion_specifier); + break; + + case lexer_t::token_t::TOK_A_WDAY: + if (! inclusion_specifier) + inclusion_specifier = date_specifier_t(); + determine_when(tok, *inclusion_specifier); + break; + + case lexer_t::token_t::TOK_DASH: + if (inclusion_specifier) { + since_specifier = inclusion_specifier; + until_specifier = date_specifier_t(); + inclusion_specifier = none; + + tok = lexer.next_token(); + determine_when(tok, *until_specifier); + + // The dash operator is special: it has an _inclusive_ end. + end_inclusive = true; + } else { + tok.unexpected(); + } + break; + + case lexer_t::token_t::TOK_SINCE: + if (since_specifier) { + tok.unexpected(); + } else { + since_specifier = date_specifier_t(); + tok = lexer.next_token(); + determine_when(tok, *since_specifier); + } + break; + + case lexer_t::token_t::TOK_UNTIL: + if (until_specifier) { + tok.unexpected(); + } else { + until_specifier = date_specifier_t(); + tok = lexer.next_token(); + determine_when(tok, *until_specifier); + } + break; + + case lexer_t::token_t::TOK_IN: + if (inclusion_specifier) { + tok.unexpected(); + } else { + inclusion_specifier = date_specifier_t(); + tok = lexer.next_token(); + determine_when(tok, *inclusion_specifier); + } + break; + + case lexer_t::token_t::TOK_THIS: + case lexer_t::token_t::TOK_NEXT: + case lexer_t::token_t::TOK_LAST: { + int8_t adjust = 0; + if (tok.kind == lexer_t::token_t::TOK_NEXT) + adjust = 1; + else if (tok.kind == lexer_t::token_t::TOK_LAST) + adjust = -1; + + tok = lexer.next_token(); + switch (tok.kind) { + case lexer_t::token_t::TOK_INT: + // jww (2009-11-18): Allow things like "last 5 weeks" + assert(! "Need to allow for expressions like \"last 5 weeks\""); + tok.unexpected(); + break; + + case lexer_t::token_t::TOK_A_MONTH: { + inclusion_specifier = date_specifier_t(); + determine_when(tok, *inclusion_specifier); + + date_t temp(today.year(), *inclusion_specifier->month, 1); + temp += gregorian::years(adjust); + inclusion_specifier = + date_specifier_t(static_cast<date_specifier_t::year_type>(temp.year()), + temp.month()); + break; + } + + case lexer_t::token_t::TOK_A_WDAY: { + inclusion_specifier = date_specifier_t(); + determine_when(tok, *inclusion_specifier); + + date_t temp = + date_duration_t::find_nearest(today, date_duration_t::WEEKS); + while (temp.day_of_week() != inclusion_specifier->wday) + temp += gregorian::days(1); + temp += gregorian::days(7 * adjust); + inclusion_specifier = date_specifier_t(temp); + break; + } + + case lexer_t::token_t::TOK_YEAR: { + date_t temp(today); + temp += gregorian::years(adjust); + inclusion_specifier = + date_specifier_t(static_cast<date_specifier_t::year_type>(temp.year())); + break; + } + + case lexer_t::token_t::TOK_QUARTER: { + date_t temp = + date_duration_t::find_nearest(today, date_duration_t::QUARTERS); + temp += gregorian::months(3 * adjust); + inclusion_specifier = + date_specifier_t(static_cast<date_specifier_t::year_type>(temp.year()), + temp.month()); +#if 0 + period.duration = date_duration_t(date_duration_t::QUARTERS, 1); +#endif + break; + } + + case lexer_t::token_t::TOK_MONTH: { + date_t temp(today); + temp += gregorian::months(adjust); + inclusion_specifier = + date_specifier_t(static_cast<date_specifier_t::year_type>(temp.year()), + temp.month()); + break; + } + + case lexer_t::token_t::TOK_WEEK: { + date_t temp = + date_duration_t::find_nearest(today, date_duration_t::WEEKS); + temp += gregorian::days(7 * adjust); + inclusion_specifier = date_specifier_t(today); +#if 0 + period.duration = date_duration_t(date_duration_t::WEEKS, 1); +#endif + break; + } + + case lexer_t::token_t::TOK_DAY: { + date_t temp(today); + temp += gregorian::days(adjust); + inclusion_specifier = date_specifier_t(temp); + break; + } + + default: + tok.unexpected(); + break; + } + break; + } + + case lexer_t::token_t::TOK_TODAY: + inclusion_specifier = date_specifier_t(today); + break; + case lexer_t::token_t::TOK_TOMORROW: + inclusion_specifier = date_specifier_t(today + gregorian::days(1)); + break; + case lexer_t::token_t::TOK_YESTERDAY: + inclusion_specifier = date_specifier_t(today - gregorian::days(1)); + break; + + case lexer_t::token_t::TOK_EVERY: + tok = lexer.next_token(); + if (tok == lexer_t::token_t::TOK_INT) { + int quantity = boost::get<unsigned short>(*tok.value); + tok = lexer.next_token(); + switch (tok.kind) { + case lexer_t::token_t::TOK_YEARS: + period.duration = date_duration_t(date_duration_t::YEARS, quantity); + break; + case lexer_t::token_t::TOK_QUARTERS: + period.duration = date_duration_t(date_duration_t::QUARTERS, quantity); + break; + case lexer_t::token_t::TOK_MONTHS: + period.duration = date_duration_t(date_duration_t::MONTHS, quantity); + break; + case lexer_t::token_t::TOK_WEEKS: + period.duration = date_duration_t(date_duration_t::WEEKS, quantity); + break; + case lexer_t::token_t::TOK_DAYS: + period.duration = date_duration_t(date_duration_t::DAYS, quantity); + break; + default: + tok.unexpected(); + break; + } + } else { + switch (tok.kind) { + case lexer_t::token_t::TOK_YEAR: + period.duration = date_duration_t(date_duration_t::YEARS, 1); + break; + case lexer_t::token_t::TOK_QUARTER: + period.duration = date_duration_t(date_duration_t::QUARTERS, 1); + break; + case lexer_t::token_t::TOK_MONTH: + period.duration = date_duration_t(date_duration_t::MONTHS, 1); + break; + case lexer_t::token_t::TOK_WEEK: + period.duration = date_duration_t(date_duration_t::WEEKS, 1); + break; + case lexer_t::token_t::TOK_DAY: + period.duration = date_duration_t(date_duration_t::DAYS, 1); + break; + default: + tok.unexpected(); + break; + } + } + break; + + case lexer_t::token_t::TOK_YEARLY: + period.duration = date_duration_t(date_duration_t::YEARS, 1); + break; + case lexer_t::token_t::TOK_QUARTERLY: + period.duration = date_duration_t(date_duration_t::QUARTERS, 1); + break; + case lexer_t::token_t::TOK_BIMONTHLY: + period.duration = date_duration_t(date_duration_t::MONTHS, 2); + break; + case lexer_t::token_t::TOK_MONTHLY: + period.duration = date_duration_t(date_duration_t::MONTHS, 1); + break; + case lexer_t::token_t::TOK_BIWEEKLY: + period.duration = date_duration_t(date_duration_t::WEEKS, 2); + break; + case lexer_t::token_t::TOK_WEEKLY: + period.duration = date_duration_t(date_duration_t::WEEKS, 1); + break; + case lexer_t::token_t::TOK_DAILY: + period.duration = date_duration_t(date_duration_t::DAYS, 1); + break; + + default: + tok.unexpected(); + break; + } + } + +#if 0 + if (! period.duration && inclusion_specifier) + period.duration = inclusion_specifier->implied_duration(); +#endif + + if (since_specifier || until_specifier) { + date_range_t range(since_specifier, until_specifier); + range.end_inclusive = end_inclusive; + + period.range = date_specifier_or_range_t(range); + } + else if (inclusion_specifier) { + period.range = date_specifier_or_range_t(*inclusion_specifier); + } + else { + /* otherwise, it's something like "monthly", with no date reference */ + } + + return period; +} + +void date_interval_t::parse(const string& str) +{ + date_parser_t parser(str); + *this = parser.parse(); +} + +void date_interval_t::resolve_end() +{ + if (start && ! end_of_duration) { + end_of_duration = duration->add(*start); + DEBUG("times.interval", + "stabilize: end_of_duration = " << *end_of_duration); + } + + if (finish && *end_of_duration > *finish) { + end_of_duration = finish; + DEBUG("times.interval", + "stabilize: end_of_duration reset to end: " << *end_of_duration); + } + + if (start && ! next) { + next = end_of_duration; + DEBUG("times.interval", "stabilize: next set to: " << *next); + } +} + +date_t date_duration_t::find_nearest(const date_t& date, skip_quantum_t skip) +{ + date_t result; + + switch (skip) { + case date_duration_t::YEARS: + result = date_t(date.year(), gregorian::Jan, 1); + break; + case date_duration_t::QUARTERS: + result = date_t(date.year(), date.month(), 1); + while (result.month() != gregorian::Jan && + result.month() != gregorian::Apr && + result.month() != gregorian::Jul && + result.month() != gregorian::Oct) + result -= gregorian::months(1); + break; + case date_duration_t::MONTHS: + result = date_t(date.year(), date.month(), 1); + break; + case date_duration_t::WEEKS: + result = date; + while (result.day_of_week() != start_of_week) + result -= gregorian::days(1); + break; + case date_duration_t::DAYS: + result = date; + break; + default: + assert(false); + break; + } + return result; +} + +void date_interval_t::stabilize(const optional<date_t>& date) +{ +#if defined(DEBUG_ON) + if (date) + DEBUG("times.interval", "stabilize: with date = " << *date); +#endif + + if (date && ! aligned) { + DEBUG("times.interval", "stabilize: date passed, but not aligned"); + if (duration) { + DEBUG("times.interval", + "stabilize: aligning with a duration: " << *duration); + + // The interval object has not been seeded with a start date yet, so + // find the nearest period before on on date which fits, if possible. + // + // Find an efficient starting point for the upcoming while loop. We + // want a date early enough that the range will be correct, but late + // enough that we don't spend hundreds of thousands of loops skipping + // through time. + optional<date_t> initial_start = start ? start : begin(date->year()); + optional<date_t> initial_finish = finish ? finish : end(date->year()); + +#if defined(DEBUG_ON) + if (initial_start) + DEBUG("times.interval", + "stabilize: initial_start = " << *initial_start); + if (initial_finish) + DEBUG("times.interval", + "stabilize: initial_finish = " << *initial_finish); +#endif + + date_t when = start ? *start : *date; + + if (duration->quantum == date_duration_t::MONTHS || + duration->quantum == date_duration_t::QUARTERS || + duration->quantum == date_duration_t::YEARS) { + DEBUG("times.interval", + "stabilize: monthly, quarterly or yearly duration"); + start = date_duration_t::find_nearest(when, duration->quantum); + } else { + DEBUG("times.interval", "stabilize: daily or weekly duration"); + start = date_duration_t::find_nearest(when - gregorian::days(400), + duration->quantum); + } + + DEBUG("times.interval", + "stabilize: beginning start date = " << *start); + + while (*start < *date) { + date_interval_t next_interval(*this); + ++next_interval; + + if (next_interval.start && *next_interval.start < *date) { + *this = next_interval; + } else { + end_of_duration = none; + next = none; + break; + } + } + + DEBUG("times.interval", "stabilize: proposed start date = " << *start); + + if (initial_start && (! start || *start < *initial_start)) { + // Using the discovered start, find the end of the period + resolve_end(); + + start = initial_start; + DEBUG("times.interval", "stabilize: start reset to initial start"); + } + if (initial_finish && (! finish || *finish > *initial_finish)) { + finish = initial_finish; + DEBUG("times.interval", "stabilize: finish reset to initial finish"); + } + +#if defined(DEBUG_ON) + if (start) + DEBUG("times.interval", "stabilize: final start = " << *start); + if (finish) + DEBUG("times.interval", "stabilize: final finish = " << *finish); +#endif + } + else if (range) { + if (date) { + start = range->begin(date->year()); + finish = range->end(date->year()); + } else { + start = range->begin(); + finish = range->end(); + } + } + aligned = true; + } + + // If there is no duration, then if we've reached here the date falls + // between start and finish. + if (! duration) { + DEBUG("times.interval", "stabilize: there was no duration given"); + + if (! start && ! finish) + throw_(date_error, + _("Invalid date interval: neither start, nor finish, nor duration")); + } else { + resolve_end(); + } +} + +bool date_interval_t::find_period(const date_t& date) +{ + stabilize(date); + + if (finish && date > *finish) { + DEBUG("times.interval", + "false: date [" << date << "] > finish [" << *finish << "]"); + return false; + } + + if (! start) { + throw_(std::runtime_error, _("Date interval is improperly initialized")); + } + else if (date < *start) { + DEBUG("times.interval", + "false: date [" << date << "] < start [" << *start << "]"); + return false; + } + + if (end_of_duration) { + if (date < *end_of_duration) { + DEBUG("times.interval", + "true: date [" << date << "] < end_of_duration [" + << *end_of_duration << "]"); + return true; + } + } else { + DEBUG("times.interval", "false: there is no end_of_duration"); + return false; + } + + // If we've reached here, it means the date does not fall into the current + // interval, so we must seek another interval that does match -- unless we + // pass by date in so doing, which means we shouldn't alter the current + // period of the interval at all. + + date_t scan = *start; + date_t end_of_scan = *end_of_duration; + + DEBUG("times.interval", "date = " << date); + DEBUG("times.interval", "scan = " << scan); + DEBUG("times.interval", "end_of_scan = " << end_of_scan); + + while (date >= scan && (! finish || scan < *finish)) { + if (date < end_of_scan) { + start = scan; + end_of_duration = end_of_scan; + next = none; + + DEBUG("times.interval", "true: start = " << *start); + DEBUG("times.interval", "true: end_of_duration = " << *end_of_duration); + + return true; + } + + scan = duration->add(scan); + end_of_scan = duration->add(scan); + } + + return false; +} + +date_interval_t& date_interval_t::operator++() +{ + if (! start) + throw_(date_error, _("Cannot increment an unstarted date interval")); + + stabilize(); + + if (! duration) + throw_(date_error, + _("Cannot increment a date interval without a duration")); + + assert(next); + + if (finish && *next >= *finish) { + start = none; + } else { + start = *next; + end_of_duration = duration->add(*start); + } + next = none; + + resolve_end(); + + return *this; +} + +void date_interval_t::dump(std::ostream& out, optional_year current_year) +{ + out << _("--- Before stabilization ---") << std::endl; + + if (range) + out << _(" range: ") << range->to_string() << std::endl; + if (start) + out << _(" start: ") << format_date(*start) << std::endl; + if (finish) + out << _(" finish: ") << format_date(*finish) << std::endl; + + if (duration) + out << _("duration: ") << duration->to_string() << std::endl; + + stabilize(begin(current_year)); + + out << std::endl + << _("--- After stabilization ---") << std::endl; + + if (range) + out << _(" range: ") << range->to_string() << std::endl; + if (start) + out << _(" start: ") << format_date(*start) << std::endl; + if (finish) + out << _(" finish: ") << format_date(*finish) << std::endl; + + if (duration) + out << _("duration: ") << duration->to_string() << std::endl; + + out << std::endl + << _("--- Sample dates in range (max. 20) ---") << std::endl; + + date_t last_date; + + for (int i = 0; i < 20 && *this; ++i, ++*this) { + out << std::right; + out.width(2); + + if (! last_date.is_not_a_date() && last_date == *start) + break; + + out << (i + 1) << ": " << format_date(*start); + if (duration) + out << " -- " << format_date(*inclusive_end()); + out << std::endl; + + if (! duration) + break; + + last_date = *start; + } +} + +date_parser_t::lexer_t::token_t date_parser_t::lexer_t::next_token() +{ + if (token_cache.kind != token_t::UNKNOWN) { + token_t tok = token_cache; + token_cache = token_t(); + return tok; + } + + while (begin != end && std::isspace(*begin)) + begin++; + + if (begin == end) + return token_t(token_t::END_REACHED); + + switch (*begin) { + case '/': ++begin; return token_t(token_t::TOK_SLASH); + case '-': ++begin; return token_t(token_t::TOK_DASH); + case '.': ++begin; return token_t(token_t::TOK_DOT); + default: break; + } + + string::const_iterator start = begin; + + // If the first character is a digit, try parsing the whole argument as a + // date using the typical date formats. This allows not only dates like + // "2009/08/01", but also dates that fit the user's --input-date-format, + // assuming their format fits in one argument and begins with a digit. + if (std::isdigit(*begin)) { + try { + string::const_iterator i = begin; + for (i = begin; i != end && ! std::isspace(*i); i++) {} + assert(i != begin); + + string possible_date(start, i); + date_traits_t traits; + + date_t when = parse_date_mask(possible_date.c_str(), none, &traits); + if (! when.is_not_a_date()) { + begin = i; + return token_t(token_t::TOK_DATE, + token_t::content_t(date_specifier_t(when, traits))); + } + } + catch (...) {} + } + + start = begin; + + string term; + bool alnum = std::isalnum(*begin); + for (; (begin != end && ! std::isspace(*begin) && + ((alnum && static_cast<bool>(std::isalnum(*begin))) || + (! alnum && ! static_cast<bool>(std::isalnum(*begin))))); begin++) + term.push_back(*begin); + + if (! term.empty()) { + if (std::isdigit(term[0])) { + if (term.length() == 4) + return token_t(token_t::TOK_A_YEAR, + token_t::content_t + (lexical_cast<date_specifier_t::year_type>(term))); + else + return token_t(token_t::TOK_INT, + token_t::content_t(lexical_cast<unsigned short>(term))); + } + else if (std::isalpha(term[0])) { + to_lower(term); + + if (optional<date_time::months_of_year> month = + string_to_month_of_year(term)) { + return token_t(token_t::TOK_A_MONTH, token_t::content_t(*month)); + } + else if (optional<date_time::weekdays> wday = + string_to_day_of_week(term)) { + return token_t(token_t::TOK_A_WDAY, token_t::content_t(*wday)); + } + else if (term == _("from") || term == _("since")) + return token_t(token_t::TOK_SINCE); + else if (term == _("to") || term == _("until")) + return token_t(token_t::TOK_UNTIL); + else if (term == _("in")) + return token_t(token_t::TOK_IN); + else if (term == _("this")) + return token_t(token_t::TOK_THIS); + else if (term == _("next")) + return token_t(token_t::TOK_NEXT); + else if (term == _("last")) + return token_t(token_t::TOK_LAST); + else if (term == _("every")) + return token_t(token_t::TOK_EVERY); + else if (term == _("today")) + return token_t(token_t::TOK_TODAY); + else if (term == _("tomorrow")) + return token_t(token_t::TOK_TOMORROW); + else if (term == _("yesterday")) + return token_t(token_t::TOK_YESTERDAY); + else if (term == _("year")) + return token_t(token_t::TOK_YEAR); + else if (term == _("quarter")) + return token_t(token_t::TOK_QUARTER); + else if (term == _("month")) + return token_t(token_t::TOK_MONTH); + else if (term == _("week")) + return token_t(token_t::TOK_WEEK); + else if (term == _("day")) + return token_t(token_t::TOK_DAY); + else if (term == _("yearly")) + return token_t(token_t::TOK_YEARLY); + else if (term == _("quarterly")) + return token_t(token_t::TOK_QUARTERLY); + else if (term == _("bimonthly")) + return token_t(token_t::TOK_BIMONTHLY); + else if (term == _("monthly")) + return token_t(token_t::TOK_MONTHLY); + else if (term == _("biweekly")) + return token_t(token_t::TOK_BIWEEKLY); + else if (term == _("weekly")) + return token_t(token_t::TOK_WEEKLY); + else if (term == _("daily")) + return token_t(token_t::TOK_DAILY); + else if (term == _("years")) + return token_t(token_t::TOK_YEARS); + else if (term == _("quarters")) + return token_t(token_t::TOK_QUARTERS); + else if (term == _("months")) + return token_t(token_t::TOK_MONTHS); + else if (term == _("weeks")) + return token_t(token_t::TOK_WEEKS); + else if (term == _("days")) + return token_t(token_t::TOK_DAYS); + } + else { + token_t::expected('\0', term[0]); + begin = ++start; + } + } else { + token_t::expected('\0', *begin); + } + + return token_t(token_t::UNKNOWN, token_t::content_t(term)); +} + +void date_parser_t::lexer_t::token_t::unexpected() +{ + switch (kind) { + case END_REACHED: + kind = UNKNOWN; + throw_(date_error, _("Unexpected end of expression")); + default: { + string desc = to_string(); + kind = UNKNOWN; + throw_(date_error, _("Unexpected date period token '%1'") << desc); + } + } +} + +void date_parser_t::lexer_t::token_t::expected(char wanted, char c) +{ + if (c == '\0' || c == -1) { + if (wanted == '\0' || wanted == -1) + throw_(date_error, _("Unexpected end")); + else + throw_(date_error, _("Missing '%1'") << wanted); + } else { + if (wanted == '\0' || wanted == -1) + throw_(date_error, _("Invalid char '%1'") << c); + else + throw_(date_error, _("Invalid char '%1' (wanted '%2')") << c << wanted); + } +} + +namespace { + typedef std::map<std::string, datetime_io_t *> datetime_io_map; + typedef std::map<std::string, date_io_t *> date_io_map; + + datetime_io_map temp_datetime_io; + date_io_map temp_date_io; +} + +std::string format_datetime(const datetime_t& when, + const format_type_t format_type, + const optional<const char *>& format) +{ + if (format_type == FMT_WRITTEN) { + return written_datetime_io->format(when); + } + else if (format_type == FMT_CUSTOM || format) { + datetime_io_map::iterator i = temp_datetime_io.find(*format); + if (i != temp_datetime_io.end()) { + return (*i).second->format(when); + } else { + datetime_io_t * formatter = new datetime_io_t(*format, false); + temp_datetime_io.insert(datetime_io_map::value_type(*format, formatter)); + return formatter->format(when); + } + } + else if (format_type == FMT_PRINTED) { + return printed_datetime_io->format(when); + } + else { + assert(false); + return empty_string; + } +} + +std::string format_date(const date_t& when, + const format_type_t format_type, + const optional<const char *>& format) +{ + if (format_type == FMT_WRITTEN) { + return written_date_io->format(when); + } + else if (format_type == FMT_CUSTOM || format) { + date_io_map::iterator i = temp_date_io.find(*format); + if (i != temp_date_io.end()) { + return (*i).second->format(when); + } else { + date_io_t * formatter = new date_io_t(*format, false); + temp_date_io.insert(date_io_map::value_type(*format, formatter)); + return formatter->format(when); + } + } + else if (format_type == FMT_PRINTED) { + return printed_date_io->format(when); + } + else { + assert(false); + return empty_string; + } +} + +namespace { + bool is_initialized = false; +} + +void set_datetime_format(const char * format) +{ + printed_datetime_io->set_format(format); +} + +void set_date_format(const char * format) +{ + printed_date_io->set_format(format); +} + +void set_input_date_format(const char * format) +{ + input_date_io.reset(new date_io_t(format, true)); +} + +void times_initialize() +{ + if (! is_initialized) { + input_datetime_io.reset(new datetime_io_t("%Y/%m/%d %H:%M:%S", true)); + + written_datetime_io.reset(new datetime_io_t("%Y/%m/%d %H:%M:%S", false)); + written_date_io.reset(new date_io_t("%Y/%m/%d", false)); + + printed_datetime_io.reset(new datetime_io_t("%y-%b-%d %H:%M:%S", false)); + printed_date_io.reset(new date_io_t("%y-%b-%d", false)); + + readers.push_back(shared_ptr<date_io_t>(new date_io_t("%m/%d", true))); + readers.push_back(shared_ptr<date_io_t>(new date_io_t("%Y/%m/%d", true))); + readers.push_back(shared_ptr<date_io_t>(new date_io_t("%Y/%m", true))); + readers.push_back(shared_ptr<date_io_t>(new date_io_t("%y/%m/%d", true))); + + is_initialized = true; + } +} + +void times_shutdown() +{ + if (is_initialized) { + input_datetime_io.reset(); + input_date_io.reset(); + written_datetime_io.reset(); + written_date_io.reset(); + printed_datetime_io.reset(); + printed_date_io.reset(); + + readers.clear(); + + foreach (datetime_io_map::value_type& pair, temp_datetime_io) + checked_delete(pair.second); + temp_datetime_io.clear(); + + foreach (date_io_map::value_type& pair, temp_date_io) + checked_delete(pair.second); + temp_date_io.clear(); + + is_initialized = false; + } +} + +void show_period_tokens(std::ostream& out, const string& arg) +{ + date_parser_t::lexer_t lexer(arg.begin(), arg.end()); + + out << _("--- Period expression tokens ---") << std::endl; + + date_parser_t::lexer_t::token_t token; + do { + token = lexer.next_token(); + token.dump(out); + out << ": " << token.to_string() << std::endl; + } + while (token.kind != date_parser_t::lexer_t::token_t::END_REACHED); +} + +} // namespace ledger diff --git a/src/times.h b/src/times.h new file mode 100644 index 00000000..826937bb --- /dev/null +++ b/src/times.h @@ -0,0 +1,632 @@ +/* + * 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. + */ + +/** + * @addtogroup util + */ + +/** + * @file times.h + * @author John Wiegley + * + * @ingroup util + * + * @brief datetime_t and date_t objects + */ +#ifndef _TIMES_H +#define _TIMES_H + +#include "utils.h" + +namespace ledger { + +DECLARE_EXCEPTION(datetime_error, std::runtime_error); +DECLARE_EXCEPTION(date_error, std::runtime_error); + +typedef boost::posix_time::ptime datetime_t; +typedef datetime_t::time_duration_type time_duration_t; + +inline bool is_valid(const datetime_t& moment) { + return ! moment.is_not_a_date_time(); +} + +typedef boost::gregorian::date date_t; +typedef boost::gregorian::date_iterator date_iterator_t; + +inline bool is_valid(const date_t& moment) { + return ! moment.is_not_a_date(); +} + +extern optional<datetime_t> epoch; + +#ifdef BOOST_DATE_TIME_HAS_HIGH_PRECISION_CLOCK +#define TRUE_CURRENT_TIME() (boost::posix_time::microsec_clock::universal_time()) +#define CURRENT_TIME() (epoch ? *epoch : TRUE_CURRENT_TIME()) +#else +#define TRUE_CURRENT_TIME() (boost::posix_time::second_clock::universal_time()) +#define CURRENT_TIME() (epoch ? *epoch : TRUE_CURRENT_TIME()) +#endif +#define CURRENT_DATE() \ + (epoch ? epoch->date() : boost::gregorian::day_clock::universal_day()) + +extern date_time::weekdays start_of_week; + +optional<date_time::weekdays> +string_to_day_of_week(const std::string& str); +optional<date_time::months_of_year> +string_to_month_of_year(const std::string& str); + +typedef optional<date_t::year_type> optional_year; + +datetime_t parse_datetime(const char * str, optional_year current_year = none); + +inline datetime_t parse_datetime(const std::string& str, + optional_year current_year = none) { + return parse_datetime(str.c_str(), current_year); +} + +date_t parse_date(const char * str, optional_year current_year = none); + +inline date_t parse_date(const std::string& str, + optional_year current_year = none) { + return parse_date(str.c_str(), current_year); +} + +enum format_type_t { + FMT_WRITTEN, FMT_PRINTED, FMT_CUSTOM +}; + +std::string format_datetime(const datetime_t& when, + const format_type_t format_type = FMT_PRINTED, + const optional<const char *>& format = none); +void set_datetime_format(const char * format); + +std::string format_date(const date_t& when, + const format_type_t format_type = FMT_PRINTED, + const optional<const char *>& format = none); +void set_date_format(const char * format); +void set_input_date_format(const char * format); + +inline void to_xml(std::ostream& out, const datetime_t& when, + bool wrap = true) +{ + if (wrap) { + push_xml x(out, "datetime"); + out << format_datetime(when, FMT_WRITTEN); + } else { + out << format_datetime(when, FMT_WRITTEN); + } +} + +inline void to_xml(std::ostream& out, const date_t& when, + bool wrap = true) +{ + if (wrap) { + push_xml x(out, "date"); + out << format_date(when, FMT_WRITTEN); + } else { + out << format_date(when, FMT_WRITTEN); + } +} + +struct date_traits_t +{ + bool has_year; + bool has_month; + bool has_day; + + date_traits_t(bool _has_year = false, + bool _has_month = false, + bool _has_day = false) + : has_year(_has_year), has_month(_has_month), has_day(_has_day) { + TRACE_CTOR(date_traits_t, "bool, bool, bool"); + } + date_traits_t(const date_traits_t& traits) + : has_year(traits.has_year), + has_month(traits.has_month), + has_day(traits.has_day) { + TRACE_CTOR(date_traits_t, "copy"); + } + ~date_traits_t() throw() { + TRACE_DTOR(date_traits_t); + } + + date_traits_t& operator=(const date_traits_t& traits) { + has_year = traits.has_year; + has_month = traits.has_month; + has_day = traits.has_day; + return *this; + } + + bool operator==(const date_traits_t& traits) const { + return (has_year == traits.has_year && + has_month == traits.has_month && + has_day == traits.has_day); + } + +#if defined(HAVE_BOOST_SERIALIZATION) +private: + /** Serialization. */ + + friend class boost::serialization::access; + + template<class Archive> + void serialize(Archive& ar, const unsigned int /* version */) { + ar & has_year; + ar & has_month; + ar & has_day; + } +#endif // HAVE_BOOST_SERIALIZATION +}; + +struct date_duration_t +{ + enum skip_quantum_t { + DAYS, WEEKS, MONTHS, QUARTERS, YEARS + } quantum; + int length; + + date_duration_t() : quantum(DAYS), length(0) { + TRACE_CTOR(date_duration_t, ""); + } + date_duration_t(skip_quantum_t _quantum, int _length) + : quantum(_quantum), length(_length) { + TRACE_CTOR(date_duration_t, "skip_quantum_t, int"); + } + date_duration_t(const date_duration_t& dur) + : quantum(dur.quantum), length(dur.length) { + TRACE_CTOR(date_duration_t, "copy"); + } + ~date_duration_t() throw() { + TRACE_DTOR(date_duration_t); + } + + date_t add(const date_t& date) const { + switch (quantum) { + case DAYS: + return date + gregorian::days(length); + case WEEKS: + return date + gregorian::weeks(length); + case MONTHS: + return date + gregorian::months(length); + case QUARTERS: + return date + gregorian::months(length * 3); + case YEARS: + return date + gregorian::years(length); + default: + assert(false); return date_t(); + } + } + + date_t subtract(const date_t& date) const { + switch (quantum) { + case DAYS: + return date - gregorian::days(length); + case WEEKS: + return date - gregorian::weeks(length); + case MONTHS: + return date - gregorian::months(length); + case QUARTERS: + return date - gregorian::months(length * 3); + case YEARS: + return date - gregorian::years(length); + default: + assert(false); return date_t(); + } + } + + string to_string() const { + std::ostringstream out; + + out << length << ' '; + + switch (quantum) { + case DAYS: out << "day"; break; + case WEEKS: out << "week"; break; + case MONTHS: out << "month"; break; + case QUARTERS: out << "quarter"; break; + case YEARS: out << "year"; break; + default: + assert(false); + break; + } + + if (length > 1) + out << 's'; + + return out.str(); + } + + static date_t find_nearest(const date_t& date, skip_quantum_t skip); + +#if defined(HAVE_BOOST_SERIALIZATION) +private: + /** Serialization. */ + + friend class boost::serialization::access; + + template<class Archive> + void serialize(Archive& ar, const unsigned int /* version */) { + ar & quantum; + ar & length; + } +#endif // HAVE_BOOST_SERIALIZATION +}; + +class date_specifier_t +{ + friend class date_parser_t; + +#if 0 + typedef date_t::year_type year_type; +#else + typedef unsigned short year_type; +#endif + typedef date_t::month_type month_type; + typedef date_t::day_type day_type; + typedef date_t::day_of_week_type day_of_week_type; + + optional<year_type> year; + optional<month_type> month; + optional<day_type> day; + optional<day_of_week_type> wday; + +public: + date_specifier_t(const optional<year_type>& _year = none, + const optional<month_type>& _month = none, + const optional<day_type>& _day = none, + const optional<day_of_week_type>& _wday = none) + : year(_year), month(_month), day(_day), wday(_wday) { + TRACE_CTOR(date_specifier_t, + "year_type, month_type, day_type, day_of_week_type"); + } + date_specifier_t(const date_t& date, + const optional<date_traits_t>& traits = none) { + TRACE_CTOR(date_specifier_t, "date_t, date_traits_t"); + if (! traits || traits->has_year) + year = date.year(); + if (! traits || traits->has_month) + month = date.month(); + if (! traits || traits->has_day) + day = date.day(); + } + date_specifier_t(const date_specifier_t& other) + : year(other.year), month(other.month), + day(other.day), wday(other.wday) { + TRACE_CTOR(date_specifier_t, "copy"); + } + ~date_specifier_t() throw() { + TRACE_DTOR(date_specifier_t); + } + + date_t begin(const optional_year& current_year = none) const; + date_t end(const optional_year& current_year = none) const; + + bool is_within(const date_t& date, + const optional_year& current_year = none) const { + return date >= begin(current_year) && date < end(current_year); + } + + optional<date_duration_t> implied_duration() const { + if (day || wday) + return date_duration_t(date_duration_t::DAYS, 1); + else if (month) + return date_duration_t(date_duration_t::MONTHS, 1); + else if (year) + return date_duration_t(date_duration_t::YEARS, 1); + else + return none; + } + + string to_string() const { + std::ostringstream out; + + if (year) + out << " year " << *year; + if (month) + out << " month " << *month; + if (day) + out << " day " << *day; + if (wday) + out << " wday " << *wday; + + return out.str(); + } + +#if defined(HAVE_BOOST_SERIALIZATION) +private: + /** Serialization. */ + + friend class boost::serialization::access; + + template<class Archive> + void serialize(Archive& ar, const unsigned int /* version */) { + ar & year; + ar & month; + ar & day; + ar & wday; + } +#endif // HAVE_BOOST_SERIALIZATION +}; + +class date_range_t +{ + friend class date_parser_t; + + optional<date_specifier_t> range_begin; + optional<date_specifier_t> range_end; + + bool end_inclusive; + +public: + date_range_t(const optional<date_specifier_t>& _range_begin = none, + const optional<date_specifier_t>& _range_end = none) + : range_begin(_range_begin), range_end(_range_end), + end_inclusive(false) { + TRACE_CTOR(date_range_t, "date_specifier_t, date_specifier_t"); + } + date_range_t(const date_range_t& other) + : range_begin(other.range_begin), range_end(other.range_end), + end_inclusive(other.end_inclusive) { + TRACE_CTOR(date_range_t, "date_range_t"); + } + ~date_range_t() throw() { + TRACE_DTOR(date_range_t); + } + + optional<date_t> begin(const optional_year& current_year = none) const { + if (range_begin) + return range_begin->begin(current_year); + else + return none; + } + optional<date_t> end(const optional_year& current_year = none) const { + if (range_end) { + if (end_inclusive) + return range_end->end(current_year); + else + return range_end->begin(current_year); + } else { + return none; + } + } + + bool is_within(const date_t& date, + const optional_year& current_year = none) const { + optional<date_t> b = begin(current_year); + optional<date_t> e = end(current_year); + bool after_begin = b ? date >= *b : true; + bool before_end = e ? date < *e : true; + return after_begin && before_end; + } + + string to_string() const { + std::ostringstream out; + + if (range_begin) + out << "from" << range_begin->to_string(); + if (range_end) + out << " to" << range_end->to_string(); + + return out.str(); + } + +#if defined(HAVE_BOOST_SERIALIZATION) +private: + /** Serialization. */ + + friend class boost::serialization::access; + + template<class Archive> + void serialize(Archive& ar, const unsigned int /* version */) { + ar & range_begin; + ar & range_end; + ar & end_inclusive; + } +#endif // HAVE_BOOST_SERIALIZATION +}; + +class date_specifier_or_range_t +{ + typedef variant<int, date_specifier_t, date_range_t> value_type; + + value_type specifier_or_range; + +public: + date_specifier_or_range_t() { + TRACE_CTOR(date_specifier_or_range_t, ""); + } + date_specifier_or_range_t(const date_specifier_or_range_t& other) + : specifier_or_range(other.specifier_or_range) { + TRACE_CTOR(date_specifier_or_range_t, "copy"); + } + date_specifier_or_range_t(const date_specifier_t& specifier) + : specifier_or_range(specifier) { + TRACE_CTOR(date_specifier_or_range_t, "date_specifier_t"); + } + date_specifier_or_range_t(const date_range_t& range) + : specifier_or_range(range) { + TRACE_CTOR(date_specifier_or_range_t, "date_range_t"); + } + ~date_specifier_or_range_t() throw() { + TRACE_DTOR(date_specifier_or_range_t); + } + + optional<date_t> begin(const optional_year& current_year = none) const { + if (specifier_or_range.type() == typeid(date_specifier_t)) + return boost::get<date_specifier_t>(specifier_or_range).begin(current_year); + else if (specifier_or_range.type() == typeid(date_range_t)) + return boost::get<date_range_t>(specifier_or_range).begin(current_year); + else + return none; + } + optional<date_t> end(const optional_year& current_year = none) const { + if (specifier_or_range.type() == typeid(date_specifier_t)) + return boost::get<date_specifier_t>(specifier_or_range).end(current_year); + else if (specifier_or_range.type() == typeid(date_range_t)) + return boost::get<date_range_t>(specifier_or_range).end(current_year); + else + return none; + } + + + string to_string() const { + std::ostringstream out; + + if (specifier_or_range.type() == typeid(date_specifier_t)) + out << "in" << boost::get<date_specifier_t>(specifier_or_range).to_string(); + else if (specifier_or_range.type() == typeid(date_range_t)) + out << boost::get<date_range_t>(specifier_or_range).to_string(); + + return out.str(); + } + +#if defined(HAVE_BOOST_SERIALIZATION) +private: + /** Serialization. */ + + friend class boost::serialization::access; + + template<class Archive> + void serialize(Archive& ar, const unsigned int /* version */) { + ar & specifier_or_range; + } +#endif // HAVE_BOOST_SERIALIZATION +}; + +class date_interval_t : public equality_comparable<date_interval_t> +{ +public: + static date_t add_duration(const date_t& date, + const date_duration_t& duration); + static date_t subtract_duration(const date_t& date, + const date_duration_t& duration); + + optional<date_specifier_or_range_t> range; + + optional<date_t> start; // the real start, after adjustment + optional<date_t> finish; // the real end, likewise + bool aligned; + optional<date_t> next; + optional<date_duration_t> duration; + optional<date_t> end_of_duration; + + explicit date_interval_t() : aligned(false) { + TRACE_CTOR(date_interval_t, ""); + } + date_interval_t(const string& str) : aligned(false) { + TRACE_CTOR(date_interval_t, "const string&"); + parse(str); + } + date_interval_t(const date_interval_t& other) + : range(other.range), + start(other.start), + finish(other.finish), + aligned(other.aligned), + next(other.next), + duration(other.duration), + end_of_duration(other.end_of_duration) { + TRACE_CTOR(date_interval_t, "copy"); + } + ~date_interval_t() throw() { + TRACE_DTOR(date_interval_t); + } + + bool operator==(const date_interval_t& other) const { + return (start == other.start && + (! start || *start == *other.start)); + } + + operator bool() const { + return is_valid(); + } + + optional<date_t> begin(const optional_year& current_year = none) const { + return start ? start : (range ? range->begin(current_year) : none); + } + optional<date_t> end(const optional_year& current_year = none) const { + return finish ? finish : (range ? range->end(current_year) : none); + } + + void parse(const string& str); + + void resolve_end(); + void stabilize(const optional<date_t>& date = none); + + bool is_valid() const { + return start; + } + + /** Find the current or next period containing date. Returns true if the + date_interval_t object has been altered to reflect the interval + containing date, or false if no such period can be found. */ + bool find_period(const date_t& date); + + optional<date_t> inclusive_end() const { + if (end_of_duration) + return *end_of_duration - gregorian::days(1); + else + return none; + } + + date_interval_t& operator++(); + + void dump(std::ostream& out, optional_year current_year = none); + +#if defined(HAVE_BOOST_SERIALIZATION) +private: + /** Serialization. */ + + friend class boost::serialization::access; + + template<class Archive> + void serialize(Archive& ar, const unsigned int /* version */) { + ar & range; + ar & start; + ar & finish; + ar & aligned; + ar & next; + ar & duration; + ar & end_of_duration; + } +#endif // HAVE_BOOST_SERIALIZATION +}; + +void times_initialize(); +void times_shutdown(); + +void show_period_tokens(std::ostream& out, const string& arg); + +std::ostream& operator<<(std::ostream& out, const date_duration_t& duration); + +} // namespace ledger + +#endif // _TIMES_H diff --git a/src/token.cc b/src/token.cc new file mode 100644 index 00000000..81c54a82 --- /dev/null +++ b/src/token.cc @@ -0,0 +1,485 @@ +/* + * 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 "token.h" +#include "parser.h" + +namespace ledger { + +int expr_t::token_t::parse_reserved_word(std::istream& in) +{ + char c = static_cast<char>(in.peek()); + + if (c == 'a' || c == 'd' || c == 'e' || c == 'f' || + c == 'i' || c == 'o' || c == 'n' || c == 't') { + length = 0; + + char buf[6]; + READ_INTO_(in, buf, 5, c, length, std::isalpha(c)); + + switch (buf[0]) { + case 'a': + if (std::strcmp(buf, "and") == 0) { + symbol[0] = '&'; + symbol[1] = '\0'; + kind = KW_AND; + return 1; + } + break; + + case 'd': + if (std::strcmp(buf, "div") == 0) { + symbol[0] = '/'; + symbol[1] = '/'; + symbol[2] = '\0'; + kind = KW_DIV; + return 1; + } + break; + + case 'e': + if (std::strcmp(buf, "else") == 0) { + symbol[0] = 'L'; + symbol[1] = 'S'; + symbol[2] = '\0'; + kind = KW_ELSE; + return 1; + } + break; + + case 'f': + if (std::strcmp(buf, "false") == 0) { + kind = VALUE; + value = false; + return 1; + } + break; + + case 'i': + if (std::strcmp(buf, "if") == 0) { + symbol[0] = 'i'; + symbol[1] = 'f'; + symbol[2] = '\0'; + kind = KW_IF; + return 1; + } + break; + + case 'o': + if (std::strcmp(buf, "or") == 0) { + symbol[0] = '|'; + symbol[1] = '\0'; + kind = KW_OR; + return 1; + } + break; + + case 'n': + if (std::strcmp(buf, "not") == 0) { + symbol[0] = '!'; + symbol[1] = '\0'; + kind = EXCLAM; + return 1; + } + break; + + case 't': + if (std::strcmp(buf, "true") == 0) { + kind = VALUE; + value = true; + return 1; + } + break; + } + + return 0; + } + return -1; +} + +void expr_t::token_t::parse_ident(std::istream& in) +{ + kind = IDENT; + length = 0; + + char c, buf[256]; + READ_INTO_(in, buf, 255, c, length, std::isalnum(c) || c == '_'); + + value.set_string(buf); +} + +void expr_t::token_t::next(std::istream& in, const parse_flags_t& pflags) +{ + if (in.eof()) { + kind = TOK_EOF; + return; + } + if (! in.good()) + throw_(parse_error, _("Input stream no longer valid")); + + char c = peek_next_nonws(in); + + if (in.eof()) { + kind = TOK_EOF; + return; + } + if (! in.good()) + throw_(parse_error, _("Input stream no longer valid")); + + symbol[0] = c; + symbol[1] = '\0'; + + length = 1; + + switch (c) { + case '&': + in.get(c); + c = static_cast<char>(in.peek()); + if (c == '&') { + in.get(c); + kind = KW_AND; + length = 2; + break; + } + kind = KW_AND; + break; + case '|': + in.get(c); + c = static_cast<char>(in.peek()); + if (c == '|') { + in.get(c); + kind = KW_OR; + length = 2; + break; + } + kind = KW_OR; + break; + + case '(': + in.get(c); + kind = LPAREN; + break; + case ')': + in.get(c); + kind = RPAREN; + break; + + case '[': { + in.get(c); + + char buf[256]; + READ_INTO_(in, buf, 255, c, length, c != ']'); + if (c != ']') + expected(']', c); + + in.get(c); + length++; + + date_interval_t timespan(buf); + optional<date_t> begin = timespan.begin(); + if (! begin) + throw_(parse_error, + _("Date specifier does not refer to a starting date")); + kind = VALUE; + value = *begin; + break; + } + + case '\'': + case '"': { + char delim; + in.get(delim); + char buf[4096]; + READ_INTO_(in, buf, 4095, c, length, c != delim); + if (c != delim) + expected(delim, c); + in.get(c); + length++; + kind = VALUE; + value.set_string(buf); + break; + } + + case '{': { + in.get(c); + amount_t temp; + temp.parse(in, PARSE_NO_MIGRATE); + in.get(c); + if (c != '}') + expected('}', c); + length++; + kind = VALUE; + value = temp; + break; + } + + case '!': + in.get(c); + c = static_cast<char>(in.peek()); + if (c == '=') { + in.get(c); + symbol[1] = c; + symbol[2] = '\0'; + kind = NEQUAL; + length = 2; + break; + } + else if (c == '~') { + in.get(c); + symbol[1] = c; + symbol[2] = '\0'; + kind = NMATCH; + length = 2; + break; + } + kind = EXCLAM; + break; + + case '-': + in.get(c); + kind = MINUS; + break; + case '+': + in.get(c); + kind = PLUS; + break; + + case '*': + in.get(c); + kind = STAR; + break; + + case '?': + in.get(c); + kind = QUERY; + break; + case ':': + in.get(c); + c = static_cast<char>(in.peek()); + if (c == '=') { + in.get(c); + symbol[1] = c; + symbol[2] = '\0'; + kind = DEFINE; + length = 2; + break; + } + kind = COLON; + break; + + case '/': { + in.get(c); + if (pflags.has_flags(PARSE_OP_CONTEXT)) { // operator context + kind = SLASH; + } else { // terminal context + // Read in the regexp + char buf[256]; + READ_INTO_(in, buf, 255, c, length, c != '/'); + if (c != '/') + expected('/', c); + in.get(c); + length++; + + kind = VALUE; + value.set_mask(buf); + } + break; + } + + case '=': + in.get(c); + c = static_cast<char>(in.peek()); + if (c == '~') { + in.get(c); + symbol[1] = c; + symbol[2] = '\0'; + kind = MATCH; + length = 2; + break; + } + else if (c == '=') { + in.get(c); + symbol[1] = c; + symbol[2] = '\0'; + kind = EQUAL; + length = 2; + break; + } + kind = EQUAL; + break; + + case '<': + in.get(c); + if (static_cast<char>(in.peek()) == '=') { + in.get(c); + symbol[1] = c; + symbol[2] = '\0'; + kind = LESSEQ; + length = 2; + break; + } + kind = LESS; + break; + + case '>': + in.get(c); + if (static_cast<char>(in.peek()) == '=') { + in.get(c); + symbol[1] = c; + symbol[2] = '\0'; + kind = GREATEREQ; + length = 2; + break; + } + kind = GREATER; + break; + + case '.': + in.get(c); + kind = DOT; + break; + + case ',': + in.get(c); + kind = COMMA; + break; + + case ';': + in.get(c); + kind = SEMI; + break; + + default: { + istream_pos_type pos = in.tellg(); + + // First, check to see if it's a reserved word, such as: and or not + int result = parse_reserved_word(in); + if (std::isalpha(c) && result == 1) + break; + + // If not, rewind back to the beginning of the word to scan it + // again. If the result was -1, it means no identifier was scanned + // so we don't have to rewind. + if (result == 0) { + in.clear(); + in.seekg(pos, std::ios::beg); + if (in.fail()) + throw_(parse_error, _("Failed to reset input stream")); + } + + // When in relaxed parsing mode, we want to migrate commodity flags + // so that any precision specified by the user updates the current + // maximum displayed precision. + parse_flags_t parse_flags; + + if (pflags.has_flags(PARSE_NO_MIGRATE)) + parse_flags.add_flags(PARSE_NO_MIGRATE); + if (pflags.has_flags(PARSE_NO_REDUCE)) + parse_flags.add_flags(PARSE_NO_REDUCE); + + try { + amount_t temp; + if (! temp.parse(in, parse_flags.plus_flags(PARSE_SOFT_FAIL))) { + // If the amount had no commodity, it must be an unambiguous + // variable reference + + in.clear(); + in.seekg(pos, std::ios::beg); + if (in.fail()) + throw_(parse_error, _("Failed to reset input stream")); + + c = static_cast<char>(in.peek()); + if (std::isdigit(c) || c == '.') + expected('\0', c); + + parse_ident(in); + } else { + kind = VALUE; + value = temp; + length = static_cast<std::size_t>(in.tellg() - pos); + } + } + catch (const std::exception& err) { + kind = ERROR; + length = static_cast<std::size_t>(in.tellg() - pos); + throw; + } + break; + } + } +} + +void expr_t::token_t::rewind(std::istream& in) +{ + in.seekg(- length, std::ios::cur); + if (in.fail()) + throw_(parse_error, _("Failed to rewind input stream")); +} + + +void expr_t::token_t::unexpected() +{ + kind_t prev_kind = kind; + + kind = ERROR; + + switch (prev_kind) { + case TOK_EOF: + throw_(parse_error, _("Unexpected end of expression")); + case IDENT: + throw_(parse_error, _("Unexpected symbol '%1'") << value); + case VALUE: + throw_(parse_error, _("Unexpected value '%1'") << value); + default: + throw_(parse_error, _("Unexpected token '%1'") << symbol); + } +} + +void expr_t::token_t::expected(char wanted, char c) +{ + kind = ERROR; + + 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); + } +} + +} // namespace ledger diff --git a/src/token.h b/src/token.h new file mode 100644 index 00000000..670f16e3 --- /dev/null +++ b/src/token.h @@ -0,0 +1,135 @@ +/* + * 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. + */ + +/** + * @defgroup expr Value expressions + */ + +/** + * @file token.h + * @author John Wiegley + * + * @ingroup expr + */ +#ifndef _TOKEN_H +#define _TOKEN_H + +#include "expr.h" + +namespace ledger { + +struct expr_t::token_t : public noncopyable +{ + enum kind_t { + ERROR, // an error occurred while tokenizing + VALUE, // any kind of literal value + IDENT, // [A-Za-z_][-A-Za-z0-9_:]* + MASK, // /regexp/ + + LPAREN, // ( + RPAREN, // ) + + EQUAL, // == + NEQUAL, // != + LESS, // < + LESSEQ, // <= + GREATER, // > + GREATEREQ, // >= + + DEFINE, // := + ASSIGN, // = + MATCH, // =~ + NMATCH, // !~ + MINUS, // - + PLUS, // + + STAR, // * + SLASH, // / + KW_DIV, // div + + EXCLAM, // !, not + KW_AND, // &, &&, and + KW_OR, // |, ||, or + KW_MOD, // % + + KW_IF, // if + KW_ELSE, // else + + QUERY, // ? + COLON, // : + + DOT, // . + COMMA, // , + SEMI, // ; + + TOK_EOF, + UNKNOWN + + } kind; + + char symbol[3]; + value_t value; + std::size_t length; + + explicit token_t() : kind(UNKNOWN), length(0) { + TRACE_CTOR(expr_t::token_t, ""); + } + ~token_t() throw() { + TRACE_DTOR(expr_t::token_t); + } + + token_t& operator=(const token_t& other) { + if (&other == this) + return *this; + assert(false); // only one token object is used at a time + return *this; + } + + void clear() { + kind = UNKNOWN; + length = 0; + value = NULL_VALUE; + + symbol[0] = '\0'; + symbol[1] = '\0'; + symbol[2] = '\0'; + } + + int parse_reserved_word(std::istream& in); + void parse_ident(std::istream& in); + void next(std::istream& in, const parse_flags_t& flags); + void rewind(std::istream& in); + void unexpected(); + void expected(char wanted, char c = '\0'); +}; + +} // namespace ledger + +#endif // _TOKEN_H diff --git a/src/traversal/abbrev.cc b/src/traversal/abbrev.cc deleted file mode 100644 index 089b8342..00000000 --- a/src/traversal/abbrev.cc +++ /dev/null @@ -1,94 +0,0 @@ -#include "abbrev.h" - -namespace ledger { - -string abbreviate(const string& str, - unsigned int width, - elision_style_t elision_style, - const bool is_account, - int abbrev_length) -{ - const unsigned int len = str.length(); - if (len <= width) - return str; - - assert(width < 4095); - - static char buf[4096]; - - switch (elision_style) { - case TRUNCATE_LEADING: - // This method truncates at the beginning. - std::strncpy(buf, str.c_str() + (len - width), width); - buf[0] = '.'; - buf[1] = '.'; - break; - - case TRUNCATE_MIDDLE: - // This method truncates in the middle. - std::strncpy(buf, str.c_str(), width / 2); - std::strncpy(buf + width / 2, - str.c_str() + (len - (width / 2 + width % 2)), - width / 2 + width % 2); - buf[width / 2 - 1] = '.'; - buf[width / 2] = '.'; - break; - - case ABBREVIATE: - if (is_account) { - std::list<string> parts; - string::size_type beg = 0; - for (string::size_type pos = str.find(':'); - pos != string::npos; - beg = pos + 1, pos = str.find(':', beg)) - parts.push_back(string(str, beg, pos - beg)); - parts.push_back(string(str, beg)); - - string result; - unsigned int newlen = len; - for (std::list<string>::iterator i = parts.begin(); - i != parts.end(); - i++) { - // Don't contract the last element - std::list<string>::iterator x = i; - if (++x == parts.end()) { - result += *i; - break; - } - - if (newlen > width) { - result += string(*i, 0, abbrev_length); - result += ":"; - newlen -= (*i).length() - abbrev_length; - } else { - result += *i; - result += ":"; - } - } - - if (newlen > width) { - // Even abbreviated its too big to show the last account, so - // abbreviate all but the last and truncate at the beginning. - std::strncpy(buf, result.c_str() + (result.length() - width), width); - buf[0] = '.'; - buf[1] = '.'; - } else { - std::strcpy(buf, result.c_str()); - } - break; - } - // fall through... - - case TRUNCATE_TRAILING: - // This method truncates at the end (the default). - std::strncpy(buf, str.c_str(), width - 2); - buf[width - 2] = '.'; - buf[width - 1] = '.'; - break; - } - buf[width] = '\0'; - - return buf; -} - -} // namespace ledger diff --git a/src/traversal/abbrev.h b/src/traversal/abbrev.h deleted file mode 100644 index ad880e45..00000000 --- a/src/traversal/abbrev.h +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef _ABBREV_H -#define _ABBREV_H - -#include "utils.h" - -namespace ledger { - -enum elision_style_t { - TRUNCATE_TRAILING, - TRUNCATE_MIDDLE, - TRUNCATE_LEADING, - ABBREVIATE -}; - -string abbreviate(const string& str, - unsigned int width, - elision_style_t elision_style = TRUNCATE_TRAILING, - const bool is_account = false, - int abbrev_length = 2); - -} // namespace ledger - -#endif // _ABBREV_H diff --git a/src/traversal/transform.cc b/src/traversal/transform.cc deleted file mode 100644 index 3331c2f3..00000000 --- a/src/traversal/transform.cc +++ /dev/null @@ -1,357 +0,0 @@ -/* - * Copyright (c) 2003-2007, 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 "transform.h" - -namespace ledger { - -#if 0 -void populate_account(account_t& acct, xml::document_t& document) -{ - if (! acct.parent) - return; - - account_repitem_t * acct_item; - if (acct.data == NULL) { - acct.data = acct_item = - static_cast<account_repitem_t *>(repitem_t::wrap(&acct)); - if (acct.parent) { - if (acct.parent->data == NULL) - populate_account(*acct.parent, acct_item); - else - static_cast<account_repitem_t *>(acct.parent->data)-> - add_child(acct_item); - } - } else { - acct_item = static_cast<account_repitem_t *>(acct.data); - } - - if (item->kind == repitem_t::ACCOUNT) - acct_item->add_child(item); - else - acct_item->add_content(item); -} - -class populate_accounts : public repitem_t::select_callback_t { - virtual void operator()(xml::document_t& document) { - if (item->kind == repitem_t::TRANSACTION) { - item->extract(); - populate_account(*static_cast<xact_repitem_t *>(item)->account(), item); - } - } -}; - -class clear_account_data : public repitem_t::select_callback_t { - virtual void operator()(xml::document_t& document) { - if (item->kind == repitem_t::ACCOUNT) - static_cast<account_repitem_t *>(item)->account->data = NULL; - } -}; - -void accounts_transform::execute(xml::document_t& document) -{ - populate_accounts cb1; - items->select_all(cb1); - - for (repitem_t * j = items->children; j; j = j->next) { - assert(j->kind == repitem_t::JOURNAL); - - j->clear(); - - for (accounts_map::iterator i = j->journal->master->accounts.begin(); - i != j->journal->master->accounts.end(); - i++) { - assert((*i).second->data); - j->add_child(static_cast<account_repitem_t *>((*i).second->data)); - (*i).second->data = NULL; - } - } - - clear_account_data cb2; - items->select_all(cb2); -} - -void compact_transform::execute(xml::document_t& document) -{ - for (repitem_t * i = items; i; i = i->next) { - if (i->kind == repitem_t::ACCOUNT) { - while (! i->contents && - i->children && ! i->children->next) { - account_repitem_t * p = static_cast<account_repitem_t *>(i); - i = p->children; - p->children = NULL; - p->last_child = NULL; - - i->set_parent(p->parent); - p->set_parent(NULL); - i->prev = p->prev; - if (p->prev) - p->prev->next = i; - p->prev = NULL; - i->next = p->next; - if (p->next) - p->next->prev = i; - p->next = NULL; - - if (i->parent->children == p) - i->parent->children = i; - if (i->parent->last_child == p) - i->parent->last_child = i; - - account_repitem_t * acct = static_cast<account_repitem_t *>(i); - acct->parents_elided = p->parents_elided + 1; - - checked_delete(p); - } - } - - if (i->children) - execute(i->children); - } -} - -void clean_transform::execute(xml::document_t& document) -{ - repitem_t * i = items; - while (i) { - if (i->kind == repitem_t::ACCOUNT) { - value_t temp; - i->add_total(temp); - if (! temp) { - repitem_t * next = i->next; - checked_delete(i); - i = next; - continue; - } - } -#if 0 - else if (i->kind == repitem_t::ENTRY && ! i->contents) { - assert(! i->children); - repitem_t * next = i->next; - checked_delete(i); - i = next; - continue; - } -#endif - - if (i->children) - execute(i->children); - - i = i->next; - } -} - -void entries_transform::execute(xml::document_t& document) -{ -} - -void optimize_transform::execute(xml::document_t& document) -{ - for (repitem_t * i = items; i; i = i->next) { - if (i->kind == repitem_t::ENTRY) { - if (i->contents && - i->contents->next && - ! i->contents->next->next) { // exactly two transactions - xact_repitem_t * first = - static_cast<xact_repitem_t *>(i->contents); - xact_repitem_t * second = - static_cast<xact_repitem_t *>(i->contents->next); - if (first->xact->amount == - second->xact->amount) - ; - } - } - - if (i->children) - execute(i->children); - } -} - -void split_transform::execute(xml::document_t& document) -{ - for (repitem_t * i = items; i; i = i->next) { - if (i->contents && i->contents->next) { - repitem_t * j; - - switch (i->kind) { - case repitem_t::TRANSACTION: - assert(false); - j = new xact_repitem_t(static_cast<xact_repitem_t *>(i)->xact); - break; - case repitem_t::ENTRY: - j = new entry_repitem_t(static_cast<entry_repitem_t *>(i)->entry); - break; - case repitem_t::ACCOUNT: - j = new account_repitem_t(static_cast<account_repitem_t *>(i)->account); - break; - default: - j = new repitem_t(i->kind); - break; - } - - j->set_parent(i->parent); - j->prev = i; - j->next = i->next; - i->next = j; - - j->contents = i->contents->next; - j->contents->prev = NULL; - j->contents->set_parent(j); - i->contents->next = NULL; - - j->last_content = i->last_content; - if (j->contents == i->last_content) - i->last_content = i->contents; - } - - if (i->children) - execute(i->children); - } -} - -void merge_transform::execute(xml::document_t& document) -{ - for (repitem_t * i = items; i; i = i->next) { - if (i->next) { - assert(i->kind == i->next->kind); - bool merge = false; - switch (i->kind) { - case repitem_t::TRANSACTION: - assert(false); - break; - case repitem_t::ENTRY: - if (static_cast<entry_repitem_t *>(i)->entry == - static_cast<entry_repitem_t *>(i->next)->entry) - merge = true; - break; - case repitem_t::ACCOUNT: -#if 0 - if (static_cast<account_repitem_t *>(i)->account == - static_cast<account_repitem_t *>(i->next)->account) - merge = true; -#endif - break; - default: - break; - } - - if (merge) { - repitem_t * j = i->next; - - i->next = i->next->next; - if (i->next) - i->next->prev = i; - - for (repitem_t * k = j->contents; k; k = k->next) - k->set_parent(i); - - i->last_content->next = j->contents; - i->last_content = j->last_content; - - j->contents = NULL; - assert(! j->children); - checked_delete(j); - } - } - - if (i->children) - execute(i->children); - } -} - -namespace { -#define REPITEM_FLAGGED 0x1 - - class mark_selected : public repitem_t::select_callback_t { - virtual void operator()(xml::document_t& document) { - item->flags |= REPITEM_FLAGGED; - } - }; - - class mark_selected_and_ancestors : public repitem_t::select_callback_t { - virtual void operator()(xml::document_t& document) { - while (item->parent) { - item->flags |= REPITEM_FLAGGED; - item = item->parent; - } - } - }; - - class delete_unmarked : public repitem_t::select_callback_t { - virtual void operator()(xml::document_t& document) { - if (item->parent && ! (item->flags & REPITEM_FLAGGED)) - checked_delete(item); - } - }; - - class delete_marked : public repitem_t::select_callback_t { - virtual void operator()(xml::document_t& document) { - if (item->flags & REPITEM_FLAGGED) - checked_delete(item); - } - }; - - class clear_flags : public repitem_t::select_callback_t { - virtual void operator()(xml::document_t& document) { - item->flags = 0; - } - }; -} - -void select_transform::execute(xml::document_t& document) -{ - if (! path) { - items->clear(); - return; - } - mark_selected_and_ancestors cb1; - items->select(path, cb1); - - delete_unmarked cb2; - items->select_all(cb2); - clear_flags cb3; - items->select_all(cb3); -} - -void remove_transform::execute(xml::document_t& document) -{ - if (! path) - return; - mark_selected cb1; - items->select(path, cb1); - - delete_marked cb2; - items->select_all(cb2); - clear_flags cb3; - items->select_all(cb3); -} -#endif - -} // namespace ledger diff --git a/src/traversal/transform.h b/src/traversal/transform.h deleted file mode 100644 index 158b9b6a..00000000 --- a/src/traversal/transform.h +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright (c) 2003-2007, 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. - */ - -#ifndef _TRANSFORM_H -#define _TRANSFORM_H - -#include "xpath.h" - -namespace ledger { - -class transform_t { - public: - virtual ~transform_t() {} - virtual value_t operator()(xml::xpath_t::scope_t& args) = 0; -}; - -class check_transform : public transform_t { - // --check checks the validity of the item list. - public: - virtual value_t operator()(xml::xpath_t::call_scope_t& args); -}; - -class accounts_transform : public transform_t { - // --accounts transforms the report tree into an account-wise view. - public: - virtual value_t operator()(xml::xpath_t::call_scope_t& args); -}; - -class compact_transform : public transform_t { - // --compact compacts an account tree to remove accounts with only - // one child account. - public: - virtual value_t operator()(xml::xpath_t::call_scope_t& args); -}; - -class clean_transform : public transform_t { - // --clean clears out entries and accounts that have no contents. - public: - virtual value_t operator()(xml::xpath_t::call_scope_t& args); -}; - -class entries_transform : public transform_t { - // --entries transforms the report tree into an entries-wise view. - public: - virtual value_t operator()(xml::xpath_t::call_scope_t& args); -}; - -class optimize_transform : public transform_t { - // --optimize optimizes entries for display by the print command. - // What this means is that if an entry has two transactions of the - // commodity (one the negative of the other), the amount of the - // second transaction will be nulled out. - public: - virtual value_t operator()(xml::xpath_t::call_scope_t& args); -}; - -class split_transform : public transform_t { - // --split breaks entry with two or more transactions into what - // seems like two entries each with one transaction -- even though - // it is the same entry being reported in both cases. This is - // useful before sorting, for exampel, in order to sort by - // transaction instead of by entry. - public: - virtual value_t operator()(xml::xpath_t::call_scope_t& args); -}; - -class merge_transform : public transform_t { - // --merge is the opposite of --split: any adjacent transactions - // which share the same entry will be merged into a group of - // transactions under one reported entry. - public: - virtual value_t operator()(xml::xpath_t::call_scope_t& args); -}; - -class combine_transform : public transform_t { - // --combine EXPR combines all transactions matching EXPR so that - // they appear within the same virtual entry (whose date will span - // the earliest to the latest of those entries, and whose payee name - // will show the terminating date or a label that is characteristic - // of the set). - public: - virtual value_t operator()(xml::xpath_t::call_scope_t& args); -}; - -class group_transform : public transform_t { - // --group groups all transactions that affect the same account - // within an entry, so that they appear as a single transaction. - public: - virtual value_t operator()(xml::xpath_t::call_scope_t& args); -}; - -class collapse_transform : public transform_t { - // --collapse makes all transactions within an entry appear as a - // single transaction, even if they affect different accounts. The - // fictitous account "<total>" is used to represent the final sum, - // if multiple accounts are involved. - public: - virtual value_t operator()(xml::xpath_t::call_scope_t& args); -}; - -class subtotal_transform : public transform_t { - // --subtotal will combine the transactions from all entries into - // one giant entry. When used in conjunction with --group, the - // affect is very similar to a regular balance report. - public: - virtual value_t operator()(xml::xpath_t::call_scope_t& args); -}; - -#if 0 -class select_transform : public transform_t -{ - protected: - xml::xpath_t xpath; - - public: - select_transform(const string& selection_path) { - xpath.parse(selection_path); - } - virtual ~select_transform() {} - - virtual value_t operator()(xml::xpath_t::call_scope_t& args); -}; - -class remove_transform : public select_transform -{ - public: - remove_transform(const string& selection_path) - : select_transform(selection_path) {} - - virtual value_t operator()(xml::xpath_t::call_scope_t& args); -}; -#endif - -} // namespace ledger - -#endif // _TRANSFORM_H diff --git a/src/traversal/xpath.cc b/src/traversal/xpath.cc deleted file mode 100644 index 85f25209..00000000 --- a/src/traversal/xpath.cc +++ /dev/null @@ -1,1670 +0,0 @@ -/* - * Copyright (c) 2003-2007, 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 "xpath.h" -#include "parser.h" - -namespace ledger { -namespace xml { - -#ifndef THREADSAFE -xpath_t::token_t * xpath_t::lookahead = NULL; -#endif - -void xpath_t::initialize() -{ - lookahead = new xpath_t::token_t; -} - -void xpath_t::shutdown() -{ - checked_delete(lookahead); - lookahead = NULL; -} - -void xpath_t::token_t::parse_ident(std::istream& in) -{ - if (in.eof()) { - kind = TOK_EOF; - return; - } - assert(in.good()); - - char c = peek_next_nonws(in); - - if (in.eof()) { - kind = TOK_EOF; - return; - } - assert(in.good()); - - kind = IDENT; - length = 0; - - char buf[256]; - READ_INTO_(in, buf, 255, c, length, - std::isalnum(c) || c == '_' || c == '.' || c == '-'); - - switch (buf[0]) { - case 'a': - if (std::strcmp(buf, "and") == 0) - kind = KW_AND; - break; - case 'd': - if (std::strcmp(buf, "div") == 0) - kind = KW_DIV; - break; - case 'e': - if (std::strcmp(buf, "eq") == 0) - kind = EQUAL; - break; - case 'f': - if (std::strcmp(buf, "false") == 0) { - kind = VALUE; - value = false; - } - break; - case 'g': - if (std::strcmp(buf, "gt") == 0) - kind = GREATER; - else if (std::strcmp(buf, "ge") == 0) - kind = GREATEREQ; - break; - case 'i': - if (std::strcmp(buf, "is") == 0) - kind = EQUAL; - break; - case 'l': - if (std::strcmp(buf, "lt") == 0) - kind = LESS; - else if (std::strcmp(buf, "le") == 0) - kind = LESSEQ; - break; - case 'm': - if (std::strcmp(buf, "mod") == 0) - kind = KW_MOD; - break; - case 'n': - if (std::strcmp(buf, "ne") == 0) - kind = NEQUAL; - break; - case 'o': - if (std::strcmp(buf, "or") == 0) - kind = KW_OR; - break; - case 't': - if (std::strcmp(buf, "true") == 0) { - kind = VALUE; - value = true; - } - break; - case 'u': - if (std::strcmp(buf, "union") == 0) - kind = KW_UNION; - break; - } - - if (kind == IDENT) - value.set_string(buf); -} - -void xpath_t::token_t::next(std::istream& in, flags_t flags) -{ - if (in.eof()) { - kind = TOK_EOF; - return; - } - assert(in.good()); - - char c = peek_next_nonws(in); - - if (in.eof()) { - kind = TOK_EOF; - return; - } - assert(in.good()); - - symbol[0] = c; - symbol[1] = '\0'; - - length = 1; - - if (! (flags & XPATH_PARSE_RELAXED) && - (std::isalpha(c) || c == '_')) { - parse_ident(in); - return; - } - - switch (c) { - case '@': - in.get(c); - kind = AT_SYM; - break; - case '$': - in.get(c); - kind = DOLLAR; - break; - - case '(': - in.get(c); - kind = LPAREN; - break; - case ')': - in.get(c); - kind = RPAREN; - break; - - case '[': { - in.get(c); - if (flags & XPATH_PARSE_ALLOW_DATE) { - char buf[256]; - READ_INTO_(in, buf, 255, c, length, c != ']'); - if (c != ']') - unexpected(c, ']'); - in.get(c); - length++; - interval_t timespan(buf); - kind = VALUE; - value = timespan.next(); - } else { - kind = LBRACKET; - } - break; - } - - case ']': { - in.get(c); - kind = RBRACKET; - break; - } - - case '\'': - case '"': { - char delim; - in.get(delim); - char buf[4096]; - READ_INTO_(in, buf, 4095, c, length, c != delim); - if (c != delim) - unexpected(c, delim); - in.get(c); - length++; - kind = VALUE; - value.set_string(buf); - break; - } - - case '{': { - in.get(c); - amount_t temp; - temp.parse(in, AMOUNT_PARSE_NO_MIGRATE); - in.get(c); - if (c != '}') - unexpected(c, '}'); - length++; - kind = VALUE; - value = temp; - break; - } - - case '!': - in.get(c); - c = in.peek(); - if (c == '=') { - in.get(c); - symbol[1] = c; - symbol[2] = '\0'; - kind = NEQUAL; - length = 2; - break; - } - kind = EXCLAM; - break; - - case '-': - in.get(c); - kind = MINUS; - break; - case '+': - in.get(c); - kind = PLUS; - break; - - case '*': - in.get(c); - kind = STAR; - break; - - case '/': - in.get(c); - kind = SLASH; - break; - - case '=': - in.get(c); - kind = EQUAL; - break; - - case '<': - in.get(c); - if (in.peek() == '=') { - in.get(c); - symbol[1] = c; - symbol[2] = '\0'; - kind = LESSEQ; - length = 2; - break; - } - kind = LESS; - break; - - case '>': - in.get(c); - if (in.peek() == '=') { - in.get(c); - symbol[1] = c; - symbol[2] = '\0'; - kind = GREATEREQ; - length = 2; - break; - } - kind = GREATER; - break; - - case '|': - in.get(c); - kind = PIPE; - break; - case ',': - in.get(c); - kind = COMMA; - break; - - case '.': - in.get(c); - c = in.peek(); - if (c == '.') { - in.get(c); - length++; - kind = DOTDOT; - break; - } - else if (! std::isdigit(c)) { - kind = DOT; - break; - } - in.unget(); // put the first '.' back - // fall through... - - default: - if (! (flags & XPATH_PARSE_RELAXED)) { - kind = UNKNOWN; - } else { - amount_t temp; - unsigned long pos = 0; - - // When in relaxed parsing mode, we want to migrate commodity - // flags so that any precision specified by the user updates the - // current maximum displayed precision. - try { - pos = (long)in.tellg(); - - unsigned char parse_flags = 0; - if (flags & XPATH_PARSE_NO_MIGRATE) - parse_flags |= AMOUNT_PARSE_NO_MIGRATE; - if (flags & XPATH_PARSE_NO_REDUCE) - parse_flags |= AMOUNT_PARSE_NO_REDUCE; - - temp.parse(in, parse_flags); - - kind = VALUE; - value = temp; - } - catch (amount_error& err) { - // If the amount had no commodity, it must be an unambiguous - // variable reference - - // jww (2007-04-19): There must be a more efficient way to do this! - if (std::strcmp(err.what(), "No quantity specified for amount") == 0) { - in.clear(); - in.seekg(pos, std::ios::beg); - - c = in.peek(); - assert(! (std::isdigit(c) || c == '.')); - parse_ident(in); - } else { - throw; - } - } - } - break; - } -} - -void xpath_t::token_t::rewind(std::istream& in) -{ - for (unsigned int i = 0; i < length; i++) - in.unget(); -} - - -void xpath_t::token_t::unexpected() -{ - switch (kind) { - case TOK_EOF: - throw_(parse_error, "Unexpected end of expression"); - case IDENT: - throw_(parse_error, "Unexpected symbol '" << value << "'"); - case VALUE: - throw_(parse_error, "Unexpected value '" << value << "'"); - default: - throw_(parse_error, "Unexpected operator '" << symbol << "'"); - } -} - -void xpath_t::token_t::unexpected(char c, char wanted) -{ - if ((unsigned char) c == 0xff) { - if (wanted) - throw_(parse_error, "Missing '" << wanted << "'"); - else - throw_(parse_error, "Unexpected end"); - } else { - if (wanted) - throw_(parse_error, "Invalid char '" << c << - "' (wanted '" << wanted << "')"); - else - throw_(parse_error, "Invalid char '" << c << "'"); - } -} - - -void xpath_t::scope_t::define(const string& name, const value_t& val) { - define(name, op_t::wrap_value(val)); -} - -void xpath_t::symbol_scope_t::define(const string& name, ptr_op_t def) -{ - DEBUG("ledger.xpath.syms", "Defining '" << name << "' = " << def); - - std::pair<symbol_map::iterator, bool> result - = symbols.insert(symbol_map::value_type(name, def)); - if (! result.second) { - symbol_map::iterator i = symbols.find(name); - assert(i != symbols.end()); - symbols.erase(i); - - std::pair<symbol_map::iterator, bool> result2 - = symbols.insert(symbol_map::value_type(name, def)); - if (! result2.second) - throw_(compile_error, - "Redefinition of '" << name << "' in same scope"); - } -} - -namespace { - value_t xpath_fn_last(xpath_t::call_scope_t& scope) - { - xpath_t::context_scope_t& context(CONTEXT_SCOPE(scope)); - return context.size(); - } - - value_t xpath_fn_position(xpath_t::call_scope_t& scope) - { - xpath_t::context_scope_t& context(CONTEXT_SCOPE(scope)); - return context.index() + 1; - } - - value_t xpath_fn_text(xpath_t::call_scope_t& scope) - { - xpath_t::context_scope_t& context(CONTEXT_SCOPE(scope)); - return value_t(context.xml_node().to_value().to_string(), true); - } - - value_t xpath_fn_type(xpath_t::call_scope_t& scope) - { - if (scope.size() == 0) { - xpath_t::context_scope_t& context(CONTEXT_SCOPE(scope)); - return string_value(context.value().label()); - } - else if (scope.size() == 1) { - return string_value(scope[0].label()); - } - else { - assert(false); - return string_value("INVALID"); - } - } -} - -xpath_t::ptr_op_t -xpath_t::symbol_scope_t::lookup(const string& name) -{ - switch (name[0]) { - case 'l': - if (name == "last") - return WRAP_FUNCTOR(bind(xpath_fn_last, _1)); - break; - - case 'p': - if (name == "position") - return WRAP_FUNCTOR(bind(xpath_fn_position, _1)); - break; - - case 't': - if (name == "text") - return WRAP_FUNCTOR(bind(xpath_fn_text, _1)); - else if (name == "type") - return WRAP_FUNCTOR(bind(xpath_fn_type, _1)); - break; - } - - symbol_map::const_iterator i = symbols.find(name); - if (i != symbols.end()) - return (*i).second; - - return child_scope_t::lookup(name); -} - - -xpath_t::ptr_op_t -xpath_t::parse_value_term(std::istream& in, flags_t tflags) const -{ - ptr_op_t node; - - token_t& tok = next_token(in, tflags); - - switch (tok.kind) { - case token_t::VALUE: - node = new op_t(op_t::VALUE); - node->set_value(tok.value); - break; - - case token_t::IDENT: { -#if 0 -#ifdef USE_BOOST_PYTHON - if (tok.value->as_string() == "lambda") // special - try { - char c, buf[4096]; - - std::strcpy(buf, "lambda "); - READ_INTO(in, &buf[7], 4000, c, true); - - ptr_op_t eval = new op_t(op_t::O_EVAL); - ptr_op_t lambda = new op_t(op_t::FUNCTION); - lambda->functor = new python_functor_t(python_eval(buf)); - eval->set_left(lambda); - ptr_op_t sym = new op_t(op_t::SYMBOL); - sym->name = new string("__ptr"); - eval->set_right(sym); - - node = eval; - - goto done; - } - catch(const boost::python::error_already_set&) { - throw_(parse_error, "Error parsing lambda expression"); - } -#endif /* USE_BOOST_PYTHON */ -#endif - - string ident = tok.value.as_string(); - - // An identifier followed by ( represents a function call - tok = next_token(in, tflags); - if (tok.kind == token_t::LPAREN) { - node = new op_t(op_t::FUNC_NAME); - node->set_string(ident); - - ptr_op_t call_node(new op_t(op_t::O_CALL)); - call_node->set_left(node); - call_node->set_right(parse_value_expr(in, tflags | XPATH_PARSE_PARTIAL)); - - tok = next_token(in, tflags); - if (tok.kind != token_t::RPAREN) - tok.unexpected(0xff, ')'); - - node = call_node; - } else { - if (std::isdigit(ident[0])) { - node = new op_t(op_t::ARG_INDEX); - node->set_long(lexical_cast<unsigned int>(ident.c_str())); - } - else if (optional<node_t::nameid_t> id = - document_t::lookup_builtin_id(ident)) { - node = new op_t(op_t::NODE_ID); - node->set_name(*id); - } - else { - node = new op_t(op_t::NODE_NAME); - node->set_string(ident); - } - push_token(tok); - } - break; - } - - case token_t::AT_SYM: { - tok = next_token(in, tflags); - if (tok.kind != token_t::IDENT) - throw_(parse_error, "@ symbol must be followed by attribute name"); - - string ident = tok.value.as_string(); - if (optional<node_t::nameid_t> id = document_t::lookup_builtin_id(ident)) { - node = new op_t(op_t::ATTR_ID); - node->set_name(*id); - } - else { - node = new op_t(op_t::ATTR_NAME); - node->set_string(ident); - } - break; - } - - case token_t::DOLLAR: - tok = next_token(in, tflags); - if (tok.kind != token_t::IDENT) - throw parse_error("$ symbol must be followed by variable name"); - - node = new op_t(op_t::VAR_NAME); - node->set_string(tok.value.as_string()); - break; - - case token_t::DOT: - node = new op_t(op_t::NODE_ID); - node->set_name(document_t::CURRENT); - break; - case token_t::DOTDOT: - node = new op_t(op_t::NODE_ID); - node->set_name(document_t::PARENT); - break; - case token_t::SLASH: - node = new op_t(op_t::NODE_ID); - node->set_name(document_t::ROOT); - push_token(); - break; - case token_t::STAR: - node = new op_t(op_t::NODE_ID); - node->set_name(document_t::ALL); - break; - - case token_t::LPAREN: - node = new op_t(op_t::O_COMMA); - node->set_left(parse_value_expr(in, tflags | XPATH_PARSE_PARTIAL)); - if (! node->left()) - throw_(parse_error, tok.symbol << " operator not followed by argument"); - - tok = next_token(in, tflags); - if (tok.kind != token_t::RPAREN) - tok.unexpected(0xff, ')'); - break; - - default: - push_token(tok); - break; - } - -#if 0 -#ifdef USE_BOOST_PYTHON - done: -#endif -#endif - return node; -} - -xpath_t::ptr_op_t -xpath_t::parse_predicate_expr(std::istream& in, flags_t tflags) const -{ - ptr_op_t node(parse_value_term(in, tflags)); - - if (node) { - token_t& tok = next_token(in, tflags); - while (tok.kind == token_t::LBRACKET) { - ptr_op_t prev(node); - node = new op_t(op_t::O_PRED); - node->set_left(prev); - node->set_right(parse_value_expr(in, tflags | XPATH_PARSE_PARTIAL)); - if (! node->right()) - throw_(parse_error, "[ operator not followed by valid expression"); - - tok = next_token(in, tflags); - if (tok.kind != token_t::RBRACKET) - tok.unexpected(0xff, ']'); - - tok = next_token(in, tflags); - } - - push_token(tok); - } - - return node; -} - -xpath_t::ptr_op_t -xpath_t::parse_path_expr(std::istream& in, flags_t tflags) const -{ - ptr_op_t node(parse_predicate_expr(in, tflags)); - - if (node) { - token_t& tok = next_token(in, tflags); - if (tok.kind == token_t::SLASH) { - ptr_op_t prev(node); - - tok = next_token(in, tflags); - node = new op_t(tok.kind == token_t::SLASH ? - op_t::O_RFIND : op_t::O_FIND); - if (tok.kind != token_t::SLASH) - push_token(tok); - - node->set_left(prev); - node->set_right(parse_path_expr(in, tflags)); - if (! node->right()) - throw_(parse_error, "/ operator not followed by a valid term"); - } else { - push_token(tok); - } - } - - return node; -} - -xpath_t::ptr_op_t -xpath_t::parse_unary_expr(std::istream& in, flags_t tflags) const -{ - ptr_op_t node; - - token_t& tok = next_token(in, tflags); - - switch (tok.kind) { - case token_t::EXCLAM: { - ptr_op_t texpr(parse_path_expr(in, tflags)); - if (! texpr) - throw_(parse_error, - tok.symbol << " operator not followed by argument"); - - // A very quick optimization - if (texpr->kind == op_t::VALUE) { - texpr->as_value().in_place_negate(); - node = texpr; - } else { - node = new op_t(op_t::O_NOT); - node->set_left(texpr); - } - break; - } - - case token_t::MINUS: { - ptr_op_t texpr(parse_path_expr(in, tflags)); - if (! texpr) - throw_(parse_error, - tok.symbol << " operator not followed by argument"); - - // A very quick optimization - if (texpr->kind == op_t::VALUE) { - texpr->as_value().in_place_negate(); - node = texpr; - } else { - node = new op_t(op_t::O_NEG); - node->set_left(texpr); - } - break; - } - - default: - push_token(tok); - node = parse_path_expr(in, tflags); - break; - } - - return node; -} - -xpath_t::ptr_op_t -xpath_t::parse_union_expr(std::istream& in, flags_t tflags) const -{ - ptr_op_t node(parse_unary_expr(in, tflags)); - - if (node) { - token_t& tok = next_token(in, tflags); - if (tok.kind == token_t::PIPE || tok.kind == token_t::KW_UNION) { - ptr_op_t prev(node); - node = new op_t(op_t::O_UNION); - node->set_left(prev); - node->set_right(parse_union_expr(in, tflags)); - if (! node->right()) - throw_(parse_error, - tok.symbol << " operator not followed by argument"); - } else { - push_token(tok); - } - } - - return node; -} - -xpath_t::ptr_op_t -xpath_t::parse_mul_expr(std::istream& in, flags_t tflags) const -{ - ptr_op_t node(parse_union_expr(in, tflags)); - - if (node) { - token_t& tok = next_token(in, tflags); - if (tok.kind == token_t::STAR || tok.kind == token_t::KW_DIV) { - ptr_op_t prev(node); - node = new op_t(tok.kind == token_t::STAR ? - op_t::O_MUL : op_t::O_DIV); - node->set_left(prev); - node->set_right(parse_mul_expr(in, tflags)); - if (! node->right()) - throw_(parse_error, - tok.symbol << " operator not followed by argument"); - - tok = next_token(in, tflags); - } - push_token(tok); - } - - return node; -} - -xpath_t::ptr_op_t -xpath_t::parse_add_expr(std::istream& in, flags_t tflags) const -{ - ptr_op_t node(parse_mul_expr(in, tflags)); - - if (node) { - token_t& tok = next_token(in, tflags); - if (tok.kind == token_t::PLUS || - tok.kind == token_t::MINUS) { - ptr_op_t prev(node); - node = new op_t(tok.kind == token_t::PLUS ? - op_t::O_ADD : op_t::O_SUB); - node->set_left(prev); - node->set_right(parse_add_expr(in, tflags)); - if (! node->right()) - throw_(parse_error, - tok.symbol << " operator not followed by argument"); - - tok = next_token(in, tflags); - } - push_token(tok); - } - - return node; -} - -xpath_t::ptr_op_t -xpath_t::parse_logic_expr(std::istream& in, flags_t tflags) const -{ - ptr_op_t node(parse_add_expr(in, tflags)); - - if (node) { - op_t::kind_t kind = op_t::LAST; - flags_t _flags = tflags; - token_t& tok = next_token(in, tflags); - switch (tok.kind) { - case token_t::EQUAL: - kind = op_t::O_EQ; - break; - case token_t::NEQUAL: - kind = op_t::O_NEQ; - break; - case token_t::LESS: - kind = op_t::O_LT; - break; - case token_t::LESSEQ: - kind = op_t::O_LTE; - break; - case token_t::GREATER: - kind = op_t::O_GT; - break; - case token_t::GREATEREQ: - kind = op_t::O_GTE; - break; - default: - push_token(tok); - break; - } - - if (kind != op_t::LAST) { - ptr_op_t prev(node); - node = new op_t(kind); - node->set_left(prev); - node->set_right(parse_add_expr(in, _flags)); - - if (! node->right()) { - if (tok.kind == token_t::PLUS) - throw_(parse_error, - tok.symbol << " operator not followed by argument"); - else - throw_(parse_error, - tok.symbol << " operator not followed by argument"); - } - } - } - - return node; -} - -xpath_t::ptr_op_t -xpath_t::parse_and_expr(std::istream& in, flags_t tflags) const -{ - ptr_op_t node(parse_logic_expr(in, tflags)); - - if (node) { - token_t& tok = next_token(in, tflags); - if (tok.kind == token_t::KW_AND) { - ptr_op_t prev(node); - node = new op_t(op_t::O_AND); - node->set_left(prev); - node->set_right(parse_and_expr(in, tflags)); - if (! node->right()) - throw_(parse_error, - tok.symbol << " operator not followed by argument"); - } else { - push_token(tok); - } - } - return node; -} - -xpath_t::ptr_op_t -xpath_t::parse_or_expr(std::istream& in, flags_t tflags) const -{ - ptr_op_t node(parse_and_expr(in, tflags)); - - if (node) { - token_t& tok = next_token(in, tflags); - if (tok.kind == token_t::KW_OR) { - ptr_op_t prev(node); - node = new op_t(op_t::O_OR); - node->set_left(prev); - node->set_right(parse_or_expr(in, tflags)); - if (! node->right()) - throw_(parse_error, - tok.symbol << " operator not followed by argument"); - } else { - push_token(tok); - } - } - return node; -} - -xpath_t::ptr_op_t -xpath_t::parse_value_expr(std::istream& in, flags_t tflags) const -{ - ptr_op_t node(parse_or_expr(in, tflags)); - - if (node) { - token_t& tok = next_token(in, tflags); - if (tok.kind == token_t::COMMA) { - ptr_op_t prev(node); - node = new op_t(op_t::O_COMMA); - node->set_left(prev); - node->set_right(parse_value_expr(in, tflags)); - if (! node->right()) - throw_(parse_error, - tok.symbol << " operator not followed by argument"); - tok = next_token(in, tflags); - } - - if (tok.kind != token_t::TOK_EOF) { - if (tflags & XPATH_PARSE_PARTIAL) - push_token(tok); - else - tok.unexpected(); - } - } - else if (! (tflags & XPATH_PARSE_PARTIAL)) { - throw_(parse_error, "Failed to parse value expression"); - } - - return node; -} - -xpath_t::ptr_op_t -xpath_t::parse_expr(std::istream& in, flags_t tflags) const -{ - ptr_op_t node(parse_value_expr(in, tflags)); - - if (use_lookahead) { - use_lookahead = false; -#ifdef THREADSAFE - lookahead.rewind(in); -#else - lookahead->rewind(in); -#endif - } -#ifdef THREADSAFE - lookahead.clear(); -#else - lookahead->clear(); -#endif - - return node; -} - - -xpath_t::ptr_op_t xpath_t::op_t::compile(scope_t& scope) -{ - switch (kind) { - case VAR_NAME: - case FUNC_NAME: - if (ptr_op_t def = scope.lookup(as_string())) { -#if 1 - return def; -#else - // Aren't definitions compiled when they go in? Would - // recompiling here really add any benefit? - return def->compile(scope); -#endif - } - return this; - - default: - break; - } - - if (kind < TERMINALS) - return this; - - ptr_op_t lhs(left()->compile(scope)); - ptr_op_t rhs(right() ? right()->compile(scope) : ptr_op_t()); - - if (lhs == left() && (! rhs || rhs == right())) - return this; - - ptr_op_t intermediate(copy(lhs, rhs)); - - if (lhs->is_value() && (! rhs || rhs->is_value())) - return wrap_value(intermediate->calc(scope)); - - return intermediate; -} - - -value_t xpath_t::op_t::current_value(scope_t& scope) -{ - xpath_t::context_scope_t& context(CONTEXT_SCOPE(scope)); - return context.value(); -} - -node_t& xpath_t::op_t::current_xml_node(scope_t& scope) -{ - xpath_t::context_scope_t& context(CONTEXT_SCOPE(scope)); - return context.xml_node(); -} - -namespace { - value_t select_nodes(xpath_t::scope_t& scope, const value_t& nodes, - xpath_t::ptr_op_t selection_path, bool recurse); - - value_t select_recursively(xpath_t::scope_t& scope, node_t& xml_node, - xpath_t::ptr_op_t selection_path) - { - value_t result; - - if (xml_node.is_parent_node()) { - parent_node_t& parent_node(xml_node.as_parent_node()); - foreach (node_t * child, parent_node) - result.push_back(select_nodes(scope, child, selection_path, true)); - } - return result; - } - - value_t select_nodes(xpath_t::scope_t& scope, const value_t& nodes, - xpath_t::ptr_op_t selection_path, bool recurse) - { - if (nodes.is_null()) - return NULL_VALUE; - - value_t result; - - if (! nodes.is_sequence()) { - xpath_t::context_scope_t node_scope(scope, nodes, 0, 1); - result.push_back(selection_path->calc(node_scope)); - - if (recurse && nodes.is_xml_node()) - result.push_back(select_recursively(scope, *nodes.as_xml_node(), - selection_path)); - } else { - std::size_t index = 0; - std::size_t size = nodes.as_sequence().size(); - - foreach (const value_t& node, nodes.as_sequence()) { - xpath_t::context_scope_t node_scope(scope, node, index, size); - result.push_back(selection_path->calc(node_scope)); - - if (recurse && nodes.is_xml_node()) - result.push_back(select_recursively(scope, *node.as_xml_node(), - selection_path)); - - index++; - } - } - return result; - } -} - -value_t xpath_t::op_t::calc(scope_t& scope) -{ - bool find_all_nodes = false; - - switch (kind) { - case VALUE: - return as_value(); - - case VAR_NAME: - case FUNC_NAME: - if (ptr_op_t reference = compile(scope)) { - return reference->calc(scope); - } else { - throw_(calc_error, "No " << (kind == VAR_NAME ? "variable" : "function") - << " named '" << as_string() << "'"); - } - break; - - case FUNCTION: - // This should never be evaluated directly; it only appears as the - // left node of an O_CALL operator. - assert(false); - break; - - case O_CALL: { - call_scope_t call_args(scope); - - if (right()) - call_args.set_args(right()->calc(scope)); - - ptr_op_t func = left(); - string name; - - if (func->kind == FUNC_NAME) { - name = func->as_string(); - func = func->compile(scope); - } - - if (func->kind != FUNCTION) - throw_(calc_error, - name.empty() ? string("Attempt to call non-function") : - (string("Attempt to call unknown function '") + name + "'")); - - return func->as_function()(call_args); - } - - case ARG_INDEX: { - call_scope_t& args(CALL_SCOPE(scope)); - - if (as_long() >= 0 && as_long() < args.size()) - return args[as_long()]; - else - throw_(calc_error, "Reference to non-existing argument"); - break; - } - - case O_FIND: - case O_RFIND: - return select_nodes(scope, left()->calc(scope), right(), kind == O_RFIND); - - case O_PRED: { - value_t values = left()->calc(scope); - - if (! values.is_null()) { - op_predicate pred(right()); - - if (! values.is_sequence()) { - context_scope_t value_scope(scope, values, 0, 1); - if (pred(value_scope)) - return values; - return NULL_VALUE; - } else { - std::size_t index = 0; - std::size_t size = values.as_sequence().size(); - - value_t result; - - foreach (const value_t& value, values.as_sequence()) { - context_scope_t value_scope(scope, value, index, size); - if (pred(value_scope)) - result.push_back(value); - index++; - } - return result; - } - } - break; - } - - case NODE_ID: - switch (as_name()) { - case document_t::CURRENT: - return current_value(scope); - - case document_t::PARENT: - if (optional<parent_node_t&> parent = current_xml_node(scope).parent()) - return &*parent; - else - throw_(std::logic_error, "Attempt to access parent of root node"); - break; - - case document_t::ROOT: - return ¤t_xml_node(scope).document(); - - case document_t::ALL: - find_all_nodes = true; - break; - - default: - break; // pass down to the NODE_NAME case - } - // fall through... - - case NODE_NAME: { - node_t& current_node(current_xml_node(scope)); - - if (current_node.is_parent_node()) { - const bool have_name_id = kind == NODE_ID; - - parent_node_t& parent(current_node.as_parent_node()); - - value_t result; - foreach (node_t * child, parent) { - if (find_all_nodes || - ( have_name_id && as_name() == child->name_id()) || - (! have_name_id && as_string() == child->name())) - result.push_back(child); - } - return result; - } - break; - } - - case ATTR_ID: - case ATTR_NAME: - if (optional<value_t&> value = - kind == ATTR_ID ? current_xml_node(scope).get_attr(as_name()) : - current_xml_node(scope).get_attr(as_string())) - return *value; - - break; - - case O_NEQ: - return left()->calc(scope) != right()->calc(scope); - case O_EQ: - return left()->calc(scope) == right()->calc(scope); - case O_LT: - return left()->calc(scope) < right()->calc(scope); - case O_LTE: - return left()->calc(scope) <= right()->calc(scope); - case O_GT: - return left()->calc(scope) > right()->calc(scope); - case O_GTE: - return left()->calc(scope) >= right()->calc(scope); - - case O_ADD: - return left()->calc(scope) + right()->calc(scope); - case O_SUB: - return left()->calc(scope) - right()->calc(scope); - case O_MUL: - return left()->calc(scope) * right()->calc(scope); - case O_DIV: - return left()->calc(scope) / right()->calc(scope); - - case O_NEG: - assert(! right()); - return left()->calc(scope).negate(); - - case O_NOT: - assert(! right()); - return ! left()->calc(scope); - - case O_AND: - return left()->calc(scope) && right()->calc(scope); - case O_OR: - return left()->calc(scope) || right()->calc(scope); - - case O_COMMA: - case O_UNION: { - value_t result(left()->calc(scope)); - - ptr_op_t next = right(); - while (next) { - ptr_op_t value_op; - if (next->kind == O_COMMA || next->kind == O_UNION) { - value_op = next->left(); - next = next->right(); - } else { - value_op = next; - next = NULL; - } - - result.push_back(value_op->calc(scope)); - } - return result; - } - - case LAST: - default: - assert(false); - break; - } - - return NULL_VALUE; -} - - -bool xpath_t::op_t::print(std::ostream& out, print_context_t& context) const -{ - bool found = false; - - if (context.start_pos && this == context.op_to_find) { - *context.start_pos = (long)out.tellp() - 1; - found = true; - } - - string symbol; - - switch (kind) { - case VALUE: { - const value_t& value(as_value()); - switch (value.type()) { - case value_t::VOID: - out << "<VOID>"; - break; - case value_t::BOOLEAN: - if (value) - out << "1"; - else - out << "0"; - break; - case value_t::INTEGER: - out << value; - break; - case value_t::AMOUNT: - if (! context.relaxed) - out << '{'; - out << value; - if (! context.relaxed) - out << '}'; - break; - case value_t::BALANCE: - case value_t::BALANCE_PAIR: - assert(false); - break; - case value_t::DATETIME: - out << '[' << value << ']'; - break; - case value_t::STRING: - out << '"' << value << '"'; - break; - - case value_t::XML_NODE: - out << '<' << value << '>'; - break; - case value_t::POINTER: - out << '&' << value; - break; - case value_t::SEQUENCE: - out << '~' << value << '~'; - break; - } - break; - } - - case ATTR_ID: - out << '@'; - // fall through... - case NODE_ID: { - context_scope_t& node_scope(CONTEXT_SCOPE(context.scope)); - if (optional<const char *> name = - node_scope.xml_node().document().lookup_name(as_name())) - out << *name; - else - out << '#' << as_name(); - break; - } - - case NODE_NAME: - case FUNC_NAME: - out << as_string(); - break; - - case ATTR_NAME: - out << '@' << as_string(); - break; - - case VAR_NAME: - out << '$' << as_string(); - break; - - case FUNCTION: - out << "<FUNCTION>"; - break; - - case ARG_INDEX: - out << '@' << as_long(); - break; - - case O_NOT: - out << "!"; - if (left() && left()->print(out, context)) - found = true; - break; - case O_NEG: - out << "-"; - if (left() && left()->print(out, context)) - found = true; - break; - - case O_UNION: - if (left() && left()->print(out, context)) - found = true; - out << " | "; - if (right() && right()->print(out, context)) - found = true; - break; - - case O_ADD: - out << "("; - if (left() && left()->print(out, context)) - found = true; - out << " + "; - if (right() && right()->print(out, context)) - found = true; - out << ")"; - break; - case O_SUB: - out << "("; - if (left() && left()->print(out, context)) - found = true; - out << " - "; - if (right() && right()->print(out, context)) - found = true; - out << ")"; - break; - case O_MUL: - out << "("; - if (left() && left()->print(out, context)) - found = true; - out << " * "; - if (right() && right()->print(out, context)) - found = true; - out << ")"; - break; - case O_DIV: - out << "("; - if (left() && left()->print(out, context)) - found = true; - out << " / "; - if (right() && right()->print(out, context)) - found = true; - out << ")"; - break; - - case O_NEQ: - out << "("; - if (left() && left()->print(out, context)) - found = true; - out << " != "; - if (right() && right()->print(out, context)) - found = true; - out << ")"; - break; - case O_EQ: - out << "("; - if (left() && left()->print(out, context)) - found = true; - out << " == "; - if (right() && right()->print(out, context)) - found = true; - out << ")"; - break; - case O_LT: - out << "("; - if (left() && left()->print(out, context)) - found = true; - out << " < "; - if (right() && right()->print(out, context)) - found = true; - out << ")"; - break; - case O_LTE: - out << "("; - if (left() && left()->print(out, context)) - found = true; - out << " <= "; - if (right() && right()->print(out, context)) - found = true; - out << ")"; - break; - case O_GT: - out << "("; - if (left() && left()->print(out, context)) - found = true; - out << " > "; - if (right() && right()->print(out, context)) - found = true; - out << ")"; - break; - case O_GTE: - out << "("; - if (left() && left()->print(out, context)) - found = true; - out << " >= "; - if (right() && right()->print(out, context)) - found = true; - out << ")"; - break; - - case O_AND: - out << "("; - if (left() && left()->print(out, context)) - found = true; - out << " & "; - if (right() && right()->print(out, context)) - found = true; - out << ")"; - break; - case O_OR: - out << "("; - if (left() && left()->print(out, context)) - found = true; - out << " | "; - if (right() && right()->print(out, context)) - found = true; - out << ")"; - break; - - case O_COMMA: - if (left() && left()->print(out, context)) - found = true; - out << ", "; - if (right() && right()->print(out, context)) - found = true; - break; - - case O_CALL: - if (left() && left()->print(out, context)) - found = true; - out << "("; - if (right() && right()->print(out, context)) - found = true; - out << ")"; - break; - - case O_FIND: - if (left() && left()->print(out, context)) - found = true; - out << "/"; - if (right() && right()->print(out, context)) - found = true; - break; - case O_RFIND: - if (left() && left()->print(out, context)) - found = true; - out << "//"; - if (right() && right()->print(out, context)) - found = true; - break; - case O_PRED: - if (left() && left()->print(out, context)) - found = true; - out << "["; - if (right() && right()->print(out, context)) - found = true; - out << "]"; - break; - - case LAST: - default: - assert(false); - break; - } - - if (! symbol.empty()) { - if (amount_t::current_pool->find(symbol)) - out << '@'; - out << symbol; - } - - if (context.end_pos && this == context.op_to_find) - *context.end_pos = (long)out.tellp() - 1; - - return found; -} - -void xpath_t::op_t::dump(std::ostream& out, const int depth) const -{ - out.setf(std::ios::left); - out.width(10); - out << this << " "; - - for (int i = 0; i < depth; i++) - out << " "; - - switch (kind) { - case VALUE: - out << "VALUE - " << as_value(); - break; - - case NODE_NAME: - out << "NODE_NAME - " << as_string(); - break; - case NODE_ID: - out << "NODE_ID - " << as_name(); - break; - - case ATTR_NAME: - out << "ATTR_NAME - " << as_string(); - break; - case ATTR_ID: - out << "ATTR_ID - " << as_name(); - break; - - case FUNC_NAME: - out << "FUNC_NAME - " << as_string(); - break; - - case VAR_NAME: - out << "VAR_NAME - " << as_string(); - break; - - case ARG_INDEX: - out << "ARG_INDEX - " << as_long(); - break; - - case FUNCTION: - out << "FUNCTION"; - break; - - case O_CALL: out << "O_CALL"; break; - - case O_NOT: out << "O_NOT"; break; - case O_NEG: out << "O_NEG"; break; - - case O_UNION: out << "O_UNION"; break; - - case O_ADD: out << "O_ADD"; break; - case O_SUB: out << "O_SUB"; break; - case O_MUL: out << "O_MUL"; break; - case O_DIV: out << "O_DIV"; break; - - case O_NEQ: out << "O_NEQ"; break; - case O_EQ: out << "O_EQ"; break; - case O_LT: out << "O_LT"; break; - case O_LTE: out << "O_LTE"; break; - case O_GT: out << "O_GT"; break; - case O_GTE: out << "O_GTE"; break; - - case O_AND: out << "O_AND"; break; - case O_OR: out << "O_OR"; break; - - case O_COMMA: out << "O_COMMA"; break; - - case O_FIND: out << "O_FIND"; break; - case O_RFIND: out << "O_RFIND"; break; - case O_PRED: out << "O_PRED"; break; - - case LAST: - default: - assert(false); - break; - } - - out << " (" << refc << ')' << std::endl; - - if (kind > TERMINALS) { - if (left()) { - left()->dump(out, depth + 1); - if (right()) - right()->dump(out, depth + 1); - } else { - assert(! right()); - } - } -} - -} // namespace xml - - -value_t xml_command(xml::xpath_t::call_scope_t& args) -{ - assert(args.size() == 0); - - value_t ostream = args.resolve("ostream"); - std::ostream& outs(ostream.as_ref_lval<std::ostream>()); - - xml::xpath_t::context_scope_t& node_context(CONTEXT_SCOPE(args)); - node_context.xml_node().print(outs); - - return true; -} - -} // namespace ledger diff --git a/src/traversal/xpath.h b/src/traversal/xpath.h deleted file mode 100644 index 26a887a1..00000000 --- a/src/traversal/xpath.h +++ /dev/null @@ -1,873 +0,0 @@ -/* - * Copyright (c) 2003-2007, 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. - */ - -#ifndef _XPATH_H -#define _XPATH_H - -#include "document.h" - -namespace ledger { -namespace xml { - -class xpath_t -{ -public: - struct op_t; - typedef intrusive_ptr<op_t> ptr_op_t; - - static void initialize(); - static void shutdown(); - - DECLARE_EXCEPTION(parse_error); - DECLARE_EXCEPTION(compile_error); - DECLARE_EXCEPTION(calc_error); - -public: - class call_scope_t; - - typedef function<value_t (call_scope_t&)> function_t; - -#define MAKE_FUNCTOR(x) \ - xml::xpath_t::op_t::wrap_functor(bind(&x, this, _1)) -#define WRAP_FUNCTOR(x) \ - xml::xpath_t::op_t::wrap_functor(x) - -public: - class scope_t : public noncopyable - { - public: - enum type_t { - CHILD_SCOPE, - SYMBOL_SCOPE, - CALL_SCOPE, - CONTEXT_SCOPE - } type_; - - explicit scope_t(type_t _type) : type_(_type) { - TRACE_CTOR(xpath_t::scope_t, "type_t"); - } - virtual ~scope_t() { - TRACE_DTOR(xpath_t::scope_t); - } - - const type_t type() const { - return type_; - } - - virtual void define(const string& name, ptr_op_t def) = 0; - void define(const string& name, const value_t& val); - virtual ptr_op_t lookup(const string& name) = 0; - value_t resolve(const string& name) { - return lookup(name)->calc(*this); - } - - virtual optional<scope_t&> find_scope(const type_t _type, - bool skip_this = false) = 0; - virtual optional<scope_t&> find_first_scope(const type_t _type1, - const type_t _type2, - bool skip_this = false) = 0; - - template <typename T> - T& find_scope(bool skip_this = false) { - assert(false); - } - template <typename T> - optional<T&> maybe_find_scope(bool skip_this = false) { - assert(false); - } - }; - - class child_scope_t : public scope_t - { - scope_t * parent; - - public: - explicit child_scope_t(type_t _type = CHILD_SCOPE) - : scope_t(_type), parent(NULL) { - TRACE_CTOR(xpath_t::child_scope_t, "type_t"); - } - explicit child_scope_t(scope_t& _parent, type_t _type = CHILD_SCOPE) - : scope_t(_type), parent(&_parent) { - TRACE_CTOR(xpath_t::child_scope_t, "scope_t&, type_t"); - } - virtual ~child_scope_t() { - TRACE_DTOR(xpath_t::child_scope_t); - } - public: - virtual void define(const string& name, ptr_op_t def) { - if (parent) - parent->define(name, def); - } - virtual ptr_op_t lookup(const string& name) { - if (parent) - return parent->lookup(name); - return ptr_op_t(); - } - - virtual optional<scope_t&> find_scope(type_t _type, - bool skip_this = false) { - for (scope_t * ptr = (skip_this ? parent : this); ptr; ) { - if (ptr->type() == _type) - return *ptr; - - ptr = polymorphic_downcast<child_scope_t *>(ptr)->parent; - } - return none; - } - - virtual optional<scope_t&> find_first_scope(const type_t _type1, - const type_t _type2, - bool skip_this = false) { - for (scope_t * ptr = (skip_this ? parent : this); ptr; ) { - if (ptr->type() == _type1 || ptr->type() == _type2) - return *ptr; - - ptr = polymorphic_downcast<child_scope_t *>(ptr)->parent; - } - return none; - } - }; - - class symbol_scope_t : public child_scope_t - { - typedef std::map<const string, ptr_op_t> symbol_map; - symbol_map symbols; - - public: - explicit symbol_scope_t() - : child_scope_t(SYMBOL_SCOPE) { - TRACE_CTOR(xpath_t::symbol_scope_t, ""); - } - explicit symbol_scope_t(scope_t& _parent) - : child_scope_t(_parent, SYMBOL_SCOPE) { - TRACE_CTOR(xpath_t::symbol_scope_t, "scope_t&"); - } - virtual ~symbol_scope_t() { - TRACE_DTOR(xpath_t::symbol_scope_t); - } - - virtual void define(const string& name, ptr_op_t def); - void define(const string& name, const value_t& val) { - scope_t::define(name, val); - } - virtual ptr_op_t lookup(const string& name); - }; - - class call_scope_t : public child_scope_t - { - value_t args; - - public: - explicit call_scope_t(scope_t& _parent) - : child_scope_t(_parent, CALL_SCOPE) { - TRACE_CTOR(xpath_t::call_scope_t, "scope_t&"); - } - virtual ~call_scope_t() { - TRACE_DTOR(xpath_t::call_scope_t); - } - - void set_args(const value_t& _args) { - args = _args; - } - - value_t& value() { - return args; - } - - value_t& operator[](const int index) { - return args[index]; - } - const value_t& operator[](const int index) const { - return args[index]; - } - - void push_back(const value_t& val) { - args.push_back(val); - } - void pop_back() { - args.pop_back(); - } - - const std::size_t size() const { - return args.size(); - } - }; - - class context_scope_t : public child_scope_t - { - public: - value_t current_element; - std::size_t element_index; - std::size_t sequence_size; - - explicit context_scope_t(scope_t& _parent, - const value_t& _element = NULL_VALUE, - const std::size_t _element_index = 0, - const std::size_t _sequence_size = 0) - : child_scope_t(_parent, CONTEXT_SCOPE), current_element(_element), - element_index(_element_index), sequence_size(_sequence_size) - { - TRACE_CTOR(xpath_t::context_scope_t, "scope_t&, const value_t&, ..."); - } - virtual ~context_scope_t() { - TRACE_DTOR(xpath_t::context_scope_t); - } - - const std::size_t index() const { - return element_index; - } - const std::size_t size() const { - return sequence_size; - } - - value_t& value() { - return current_element; - } - node_t& xml_node() { - assert(current_element.is_xml_node()); - return *current_element.as_xml_node(); - } - }; - -#define XPATH_PARSE_NORMAL 0x00 -#define XPATH_PARSE_PARTIAL 0x01 -#define XPATH_PARSE_RELAXED 0x02 -#define XPATH_PARSE_NO_MIGRATE 0x04 -#define XPATH_PARSE_NO_REDUCE 0x08 -#define XPATH_PARSE_ALLOW_DATE 0x10 - - typedef uint_least8_t flags_t; - -private: - struct token_t - { - enum kind_t { - VALUE, // any kind of literal value - - IDENT, // [A-Za-z_][-A-Za-z0-9_:]* - DOLLAR, // $ - AT_SYM, // @ - - DOT, // . - DOTDOT, // .. - SLASH, // / - - LPAREN, // ( - RPAREN, // ) - LBRACKET, // [ - RBRACKET, // ] - - EQUAL, // = - NEQUAL, // != - LESS, // < - LESSEQ, // <= - GREATER, // > - GREATEREQ, // >= - - MINUS, // - - PLUS, // + - STAR, // * - KW_DIV, - - EXCLAM, // ! - KW_AND, - KW_OR, - KW_MOD, - - PIPE, // | - KW_UNION, - - COMMA, // , - - TOK_EOF, - UNKNOWN - } kind; - - char symbol[3]; - value_t value; - std::size_t length; - - explicit token_t() : kind(UNKNOWN), length(0) { - TRACE_CTOR(xpath_t::token_t, ""); - } - token_t(const token_t& other) { - assert(false); - TRACE_CTOR(xpath_t::token_t, "copy"); - *this = other; - } - ~token_t() { - TRACE_DTOR(xpath_t::token_t); - } - - token_t& operator=(const token_t& other) { - if (&other == this) - return *this; - assert(false); - return *this; - } - - void clear() { - kind = UNKNOWN; - length = 0; - value = NULL_VALUE; - - symbol[0] = '\0'; - symbol[1] = '\0'; - symbol[2] = '\0'; - } - - void parse_ident(std::istream& in); - void next(std::istream& in, flags_t flags); - void rewind(std::istream& in); - void unexpected(); - - static void unexpected(char c, char wanted = '\0'); - }; - -public: - class path_iterator_t - { - typedef node_t * pointer; - typedef node_t& reference; - - xpath_t& path_expr; - scope_t& scope; - - mutable value_t::sequence_t sequence; - mutable bool searched; - - public: - typedef value_t::sequence_t::iterator iterator; - typedef value_t::sequence_t::const_iterator const_iterator; - - path_iterator_t(xpath_t& _path_expr, scope_t& _scope) - : path_expr(_path_expr), scope(_scope), searched(false) {} - - iterator begin() { - if (! searched) { - sequence = path_expr.calc(scope).to_sequence(); - searched = true; - } - return sequence.begin(); - } - const_iterator begin() const { - return const_cast<path_iterator_t *>(this)->begin(); - } - - iterator end() { return sequence.end(); } - const_iterator end() const { return sequence.end(); } - }; - - struct op_t : public noncopyable - { - enum kind_t { - VALUE, - - FUNC_NAME, - VAR_NAME, - ARG_INDEX, - - NODE_ID, - NODE_NAME, - ATTR_ID, - ATTR_NAME, - - CONSTANTS, // constants end here - - FUNCTION, - - TERMINALS, // terminals end here - - O_CALL, - O_ARG, - - O_FIND, - O_RFIND, - O_PRED, - - O_NEQ, - O_EQ, - O_LT, - O_LTE, - O_GT, - O_GTE, - - O_ADD, - O_SUB, - O_MUL, - O_DIV, - O_NEG, - - O_NOT, - O_AND, - O_OR, - - O_UNION, - - O_COMMA, - - LAST // operators end here - }; - - kind_t kind; - mutable short refc; - ptr_op_t left_; - - variant<unsigned int, // used by ARG_INDEX and O_ARG - value_t, // used by constant VALUE - string, // used by constants SYMBOL, *_NAME - function_t, // used by terminal FUNCTION - node_t::nameid_t, // used by NODE_ID and ATTR_ID - ptr_op_t> // used by all binary operators - data; - - explicit op_t(const kind_t _kind) : kind(_kind), refc(0){ - TRACE_CTOR(xpath_t::op_t, "const kind_t"); - } - ~op_t() { - TRACE_DTOR(xpath_t::op_t); - - DEBUG("ledger.xpath.memory", "Destroying " << this); - assert(refc == 0); - } - - bool is_long() const { - return data.type() == typeid(unsigned int); - } - unsigned int& as_long() { - assert(kind == ARG_INDEX || kind == O_ARG); - return boost::get<unsigned int>(data); - } - const unsigned int& as_long() const { - return const_cast<op_t *>(this)->as_long(); - } - void set_long(unsigned int val) { - data = val; - } - - bool is_value() const { - return kind == VALUE; - } - value_t& as_value() { - assert(kind == VALUE); - return boost::get<value_t>(data); - } - const value_t& as_value() const { - return const_cast<op_t *>(this)->as_value(); - } - void set_value(const value_t& val) { - data = val; - } - - bool is_string() const { - return data.type() == typeid(string); - } - string& as_string() { - assert(kind == NODE_NAME || kind == ATTR_NAME || kind == FUNC_NAME); - return boost::get<string>(data); - } - const string& as_string() const { - return const_cast<op_t *>(this)->as_string(); - } - void set_string(const string& val) { - data = val; - } - - bool is_function() const { - return kind == FUNCTION; - } - function_t& as_function() { - assert(kind == FUNCTION); - return boost::get<function_t>(data); - } - const function_t& as_function() const { - return const_cast<op_t *>(this)->as_function(); - } - void set_function(const function_t& val) { - data = val; - } - - bool is_name() const { - return data.type() == typeid(node_t::nameid_t); - } - node_t::nameid_t& as_name() { - assert(kind == NODE_ID || kind == ATTR_ID); - return boost::get<node_t::nameid_t>(data); - } - const node_t::nameid_t& as_name() const { - return const_cast<op_t *>(this)->as_name(); - } - void set_name(const node_t::nameid_t& val) { - data = val; - } - - ptr_op_t& as_op() { - assert(kind > TERMINALS); - return boost::get<ptr_op_t>(data); - } - const ptr_op_t& as_op() const { - return const_cast<op_t *>(this)->as_op(); - } - - void acquire() const { - DEBUG("ledger.xpath.memory", - "Acquiring " << this << ", refc now " << refc + 1); - assert(refc >= 0); - refc++; - } - void release() const { - DEBUG("ledger.xpath.memory", - "Releasing " << this << ", refc now " << refc - 1); - assert(refc > 0); - if (--refc == 0) - checked_delete(this); - } - - ptr_op_t& left() { - return left_; - } - const ptr_op_t& left() const { - assert(kind > TERMINALS); - return left_; - } - void set_left(const ptr_op_t& expr) { - assert(kind > TERMINALS); - left_ = expr; - } - - ptr_op_t& right() { - assert(kind > TERMINALS); - return as_op(); - } - const ptr_op_t& right() const { - assert(kind > TERMINALS); - return as_op(); - } - void set_right(const ptr_op_t& expr) { - assert(kind > TERMINALS); - data = expr; - } - - static ptr_op_t new_node(kind_t _kind, ptr_op_t _left = NULL, - ptr_op_t _right = NULL); - ptr_op_t copy(ptr_op_t _left = NULL, ptr_op_t _right = NULL) const { - return new_node(kind, _left, _right); - } - - static ptr_op_t wrap_value(const value_t& val); - static ptr_op_t wrap_functor(const function_t& fobj); - - ptr_op_t compile(scope_t& scope); - value_t current_value(scope_t& scope); - node_t& current_xml_node(scope_t& scope); - value_t calc(scope_t& scope); - - struct print_context_t - { - scope_t& scope; - const bool relaxed; - const ptr_op_t& op_to_find; - unsigned long * start_pos; - unsigned long * end_pos; - - print_context_t(scope_t& _scope, - const bool _relaxed = false, - const ptr_op_t& _op_to_find = ptr_op_t(), - unsigned long * _start_pos = NULL, - unsigned long * _end_pos = NULL) - : scope(_scope), relaxed(_relaxed), op_to_find(_op_to_find), - start_pos(_start_pos), end_pos(_end_pos) {} - }; - - bool print(std::ostream& out, print_context_t& context) const; - void dump(std::ostream& out, const int depth) const; - - friend inline void intrusive_ptr_add_ref(xpath_t::op_t * op) { - op->acquire(); - } - friend inline void intrusive_ptr_release(xpath_t::op_t * op) { - op->release(); - } - }; - - class op_predicate { - ptr_op_t op; - public: - explicit op_predicate(ptr_op_t _op) : op(_op) {} - bool operator()(scope_t& scope) { - return op->calc(scope).to_boolean(); - } - }; - -public: - ptr_op_t ptr; - - xpath_t& operator=(ptr_op_t _expr) { - expr = ""; - ptr = _expr; - return *this; - } - -#ifdef THREADSAFE - mutable token_t lookahead; -#else - static token_t * lookahead; -#endif - mutable bool use_lookahead; - - token_t& next_token(std::istream& in, flags_t tflags) const { - if (use_lookahead) - use_lookahead = false; - else -#ifdef THREADSAFE - lookahead.next(in, tflags); -#else - lookahead->next(in, tflags); -#endif -#ifdef THREADSAFE - return lookahead; -#else - return *lookahead; -#endif - } - void push_token(const token_t& tok) const { -#ifdef THREADSAFE - assert(&tok == &lookahead); -#else - assert(&tok == lookahead); -#endif - use_lookahead = true; - } - void push_token() const { - use_lookahead = true; - } - - ptr_op_t parse_value_term(std::istream& in, flags_t flags) const; - ptr_op_t parse_predicate_expr(std::istream& in, flags_t flags) const; - ptr_op_t parse_path_expr(std::istream& in, flags_t flags) const; - ptr_op_t parse_unary_expr(std::istream& in, flags_t flags) const; - ptr_op_t parse_union_expr(std::istream& in, flags_t flags) const; - ptr_op_t parse_mul_expr(std::istream& in, flags_t flags) const; - ptr_op_t parse_add_expr(std::istream& in, flags_t flags) const; - ptr_op_t parse_logic_expr(std::istream& in, flags_t flags) const; - ptr_op_t parse_and_expr(std::istream& in, flags_t flags) const; - ptr_op_t parse_or_expr(std::istream& in, flags_t flags) const; - ptr_op_t parse_querycolon_expr(std::istream& in, flags_t flags) const; - ptr_op_t parse_value_expr(std::istream& in, flags_t flags) const; - - ptr_op_t parse_expr(std::istream& in, - flags_t flags = XPATH_PARSE_RELAXED) const; - - ptr_op_t parse_expr(const string& str, - flags_t tflags = XPATH_PARSE_RELAXED) const - { - std::istringstream stream(str); -#if 0 - try { -#endif - return parse_expr(stream, tflags); -#if 0 - } - catch (error * err) { - err->context.push_back - (new line_context(str, (long)stream.tellg() - 1, - "While parsing value expression:")); - throw err; - } -#endif - } - - ptr_op_t parse_expr(const char * p, - flags_t tflags = XPATH_PARSE_RELAXED) const { - return parse_expr(string(p), tflags); - } - - bool print(std::ostream& out, op_t::print_context_t& context) const { - if (ptr) - ptr->print(out, context); - return true; - } - -public: - string expr; - flags_t flags; // flags used to parse `expr' - - explicit xpath_t() : ptr(NULL), use_lookahead(false), flags(0) { - TRACE_CTOR(xpath_t, ""); - } - explicit xpath_t(ptr_op_t _ptr) : ptr(_ptr), use_lookahead(false) { - TRACE_CTOR(xpath_t, "ptr_op_t"); - } - - explicit xpath_t(const string& _expr, flags_t _flags = XPATH_PARSE_RELAXED) - : ptr(NULL), use_lookahead(false), flags(0) { - TRACE_CTOR(xpath_t, "const string&, flags_t"); - if (! _expr.empty()) - parse(_expr, _flags); - } - explicit xpath_t(std::istream& in, flags_t _flags = XPATH_PARSE_RELAXED) - : ptr(NULL), use_lookahead(false), flags(0) { - TRACE_CTOR(xpath_t, "std::istream&, flags_t"); - parse(in, _flags); - } - xpath_t(const xpath_t& other) - : ptr(other.ptr), use_lookahead(false), - expr(other.expr), flags(other.flags) { - TRACE_CTOR(xpath_t, "copy"); - } - ~xpath_t() { - TRACE_DTOR(xpath_t); - } - -#if 0 - xpath_t& operator=(const string& _expr) { - parse(_expr); - return *this; - } -#endif - xpath_t& operator=(const xpath_t& _expr); - -#if 0 - operator ptr_op_t() throw() { - return ptr; - } - operator bool() const throw() { - return ptr != NULL; - } - operator string() const throw() { - return expr; - } -#endif - - void parse(const string& _expr, flags_t _flags = XPATH_PARSE_RELAXED) { - expr = _expr; - flags = _flags; - ptr = parse_expr(_expr, _flags); - } - void parse(std::istream& in, flags_t _flags = XPATH_PARSE_RELAXED) { - expr = ""; - flags = _flags; - ptr = parse_expr(in, _flags); - } - - void compile(scope_t& scope) { - if (ptr.get()) - ptr = ptr->compile(scope); - } - - value_t calc(scope_t& scope) const { - if (ptr.get()) - return ptr->calc(scope); - return NULL_VALUE; - } - - static value_t eval(const string& _expr, scope_t& scope) { - return xpath_t(_expr).calc(scope); - } - - path_iterator_t find_all(scope_t& scope) { - return path_iterator_t(*this, scope); - } - - void print(std::ostream& out, scope_t& scope) const { - op_t::print_context_t context(scope); - print(out, context); - } - - void dump(std::ostream& out) const { - if (ptr) - ptr->dump(out, 0); - } -}; - -inline xpath_t::ptr_op_t -xpath_t::op_t::new_node(kind_t _kind, ptr_op_t _left, ptr_op_t _right) { - ptr_op_t node(new op_t(_kind)); - node->set_left(_left); - node->set_right(_right); - return node; -} - -inline xpath_t::ptr_op_t -xpath_t::op_t::wrap_value(const value_t& val) { - xpath_t::ptr_op_t temp(new xpath_t::op_t(xpath_t::op_t::VALUE)); - temp->set_value(val); - return temp; -} - -inline xpath_t::ptr_op_t -xpath_t::op_t::wrap_functor(const function_t& fobj) { - xpath_t::ptr_op_t temp(new xpath_t::op_t(xpath_t::op_t::FUNCTION)); - temp->set_function(fobj); - return temp; -} - -template<> -inline xpath_t::symbol_scope_t& -xpath_t::scope_t::find_scope<xpath_t::symbol_scope_t>(bool skip_this) { - optional<scope_t&> scope = find_scope(SYMBOL_SCOPE, skip_this); - assert(scope); - return downcast<symbol_scope_t>(*scope); -} - -template<> -inline xpath_t::call_scope_t& -xpath_t::scope_t::find_scope<xpath_t::call_scope_t>(bool skip_this) { - optional<scope_t&> scope = find_scope(CALL_SCOPE, skip_this); - assert(scope); - return downcast<call_scope_t>(*scope); -} - -template<> -inline xpath_t::context_scope_t& -xpath_t::scope_t::find_scope<xpath_t::context_scope_t>(bool skip_this) { - optional<scope_t&> scope = find_scope(CONTEXT_SCOPE, skip_this); - assert(scope); - return downcast<context_scope_t>(*scope); -} - -#define FIND_SCOPE(scope_type, scope_ref) \ - downcast<xml::xpath_t::scope_t>(scope_ref).find_scope<scope_type>() - -#define CALL_SCOPE(scope_ref) \ - FIND_SCOPE(xml::xpath_t::call_scope_t, scope_ref) -#define SYMBOL_SCOPE(scope_ref) \ - FIND_SCOPE(xml::xpath_t::symbol_scope_t, scope_ref) -#define CONTEXT_SCOPE(scope_ref) \ - FIND_SCOPE(xml::xpath_t::context_scope_t, scope_ref) - -} // namespace xml - -value_t xml_command(xml::xpath_t::call_scope_t& args); - -} // namespace ledger - -#endif // _XPATH_H diff --git a/src/unistring.h b/src/unistring.h new file mode 100644 index 00000000..7c433d9d --- /dev/null +++ b/src/unistring.h @@ -0,0 +1,128 @@ +/* + * 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. + */ + +/** + * @addtogroup utils + */ + +/** + * @file unistring.h + * @author John Wiegley + * + * @ingroup utils + */ +#ifndef _UNISTRING_H +#define _UNISTRING_H + +namespace ledger { + +/** + * @class unistring + * + * @brief Abstract working with UTF-32 encoded Unicode strings + * + * The input to the string is a UTF8 encoded ledger::string, which can + * then have its true length be taken, or characters extracted. + */ +class unistring +{ +public: + std::vector<boost::uint32_t> utf32chars; + + unistring() { + TRACE_CTOR(unistring, ""); + } + unistring(const std::string& input) + { + TRACE_CTOR(unistring, "std::string"); + + const char * p = input.c_str(); + std::size_t len = input.length(); + + VERIFY(utf8::is_valid(p, p + len)); + utf8::unchecked::utf8to32(p, p + len, std::back_inserter(utf32chars)); + } + ~unistring() { + TRACE_DTOR(unistring); + } + + std::size_t length() const { + return utf32chars.size(); + } + + std::string extract(const std::string::size_type begin = 0, + const std::string::size_type len = 0) const + { + std::string utf8result; + std::string::size_type this_len = length(); + + assert(begin <= this_len); + assert(begin + len <= this_len); + + if (this_len) + utf8::unchecked::utf32to8 + (utf32chars.begin() + begin, + utf32chars.begin() + begin + + (len ? (len > this_len ? this_len : len) : this_len), + std::back_inserter(utf8result)); + + return utf8result; + } +}; + +inline void justify(std::ostream& out, + const std::string& str, + int width, + bool right = false, + bool redden = false) +{ + if (! right) { + if (redden) out << "\033[31m"; + out << str; + if (redden) out << "\033[0m"; + } + + unistring temp(str); + + int spacing = width - int(temp.length()); + while (spacing-- > 0) + out << ' '; + + if (right) { + if (redden) out << "\033[31m"; + out << str; + if (redden) out << "\033[0m"; + } +} + +} // namespace ledger + +#endif // _UNISTRING_H diff --git a/src/utility/binary.cc b/src/utility/binary.cc deleted file mode 100644 index 52d5f196..00000000 --- a/src/utility/binary.cc +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright (c) 2003-2007, 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 "utils.h" - -namespace ledger { -namespace binary { - -void read_bool(std::istream& in, bool& num) -{ - read_guard(in, 0x2005); - unsigned char val; - in.read(reinterpret_cast<char *>(&val), sizeof(val)); - num = val == 1; - read_guard(in, 0x2006); -} - -void read_bool(const char *& data, bool& num) -{ - read_guard(data, 0x2005); - const unsigned char val = *reinterpret_cast<const unsigned char *>(data); - data += sizeof(unsigned char); - num = val == 1; - read_guard(data, 0x2006); -} - -void read_string(std::istream& in, string& str) -{ - read_guard(in, 0x3001); - - unsigned char len; - read_number_nocheck(in, len); - if (len == 0xff) { - unsigned short slen; - read_number_nocheck(in, slen); - char * buf = new char[slen + 1]; - in.read(buf, slen); - buf[slen] = '\0'; - str = buf; - checked_array_delete(buf); - } - else if (len) { - char buf[256]; - in.read(buf, len); - buf[len] = '\0'; - str = buf; - } else { - str = ""; - } - - read_guard(in, 0x3002); -} - -void read_string(const char *& data, string& str) -{ - read_guard(data, 0x3001); - - unsigned char len; - read_number_nocheck(data, len); - if (len == 0xff) { - unsigned short slen; - read_number_nocheck(data, slen); - str = string(data, slen); - data += slen; - } - else if (len) { - str = string(data, len); - data += len; - } - else { - str = ""; - } - - read_guard(data, 0x3002); -} - -void read_string(const char *& data, string * str) -{ - read_guard(data, 0x3001); - - unsigned char len; - read_number_nocheck(data, len); - if (len == 0xff) { - unsigned short slen; - read_number_nocheck(data, slen); - new(str) string(data, slen); - data += slen; - } - else if (len) { - new(str) string(data, len); - data += len; - } - else { - new(str) string(""); - } - - read_guard(data, 0x3002); -} - - -void write_bool(std::ostream& out, bool num) -{ - write_guard(out, 0x2005); - unsigned char val = num ? 1 : 0; - out.write(reinterpret_cast<char *>(&val), sizeof(val)); - write_guard(out, 0x2006); -} - -void write_string(std::ostream& out, const string& str) -{ - write_guard(out, 0x3001); - - unsigned long len = str.length(); - if (len > 255) { - assert(len < 65536); - write_number_nocheck<unsigned char>(out, 0xff); - write_number_nocheck<unsigned short>(out, len); - } else { - write_number_nocheck<unsigned char>(out, len); - } - - if (len) - out.write(str.c_str(), len); - - write_guard(out, 0x3002); -} - -} // namespace binary -} // namespace ledger diff --git a/src/utility/binary.h b/src/utility/binary.h deleted file mode 100644 index 864c6ea2..00000000 --- a/src/utility/binary.h +++ /dev/null @@ -1,269 +0,0 @@ -/* - * Copyright (c) 2003-2007, 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. - */ - -#ifndef BINARY_H -#define BINARY_H - -namespace ledger { -namespace binary { - -template <typename T> -inline void read_number_nocheck(std::istream& in, T& num) { - in.read(reinterpret_cast<char *>(&num), sizeof(num)); -} - -template <typename T> -inline void read_number_nocheck(const char *& data, T& num) { - num = *reinterpret_cast<const T *>(data); - data += sizeof(T); -} - -template <typename T> -inline T read_number_nocheck(std::istream& in) { - T num; - read_number_nocheck(in, num); - return num; -} - -template <typename T> -inline T read_number_nocheck(const char *& data) { - T num; - read_number_nocheck(data, num); - return num; -} - -#if DEBUG_LEVEL >= ALPHA -#define read_guard(in, id) \ - if (read_number_nocheck<unsigned short>(in) != id) \ - assert(false); -#else -#define read_guard(in, id) -#endif - -template <typename T> -inline void read_number(std::istream& in, T& num) { - read_guard(in, 0x2003); - in.read(reinterpret_cast<char *>(&num), sizeof(num)); - read_guard(in, 0x2004); -} - -template <typename T> -inline void read_number(const char *& data, T& num) { - read_guard(data, 0x2003); - num = *reinterpret_cast<const T *>(data); - data += sizeof(T); - read_guard(data, 0x2004); -} - -template <typename T> -inline T read_number(std::istream& in) { - T num; - read_number(in, num); - return num; -} - -template <typename T> -inline T read_number(const char *& data) { - T num; - read_number(data, num); - return num; -} - -void read_bool(std::istream& in, bool& num); -void read_bool(const char *& data, bool& num); - -inline bool read_bool(std::istream& in) { - bool num; - read_bool(in, num); - return num; -} - -inline bool read_bool(const char *& data) { - bool num; - read_bool(data, num); - return num; -} - -template <typename T> -void read_long(std::istream& in, T& num) -{ - read_guard(in, 0x2001); - - unsigned char len; - read_number_nocheck(in, len); - - num = 0; - unsigned char temp; - if (len > 3) { - read_number_nocheck(in, temp); - num |= static_cast<unsigned long>(temp) << 24; - } - if (len > 2) { - read_number_nocheck(in, temp); - num |= static_cast<unsigned long>(temp) << 16; - } - if (len > 1) { - read_number_nocheck(in, temp); - num |= static_cast<unsigned long>(temp) << 8; - } - - read_number_nocheck(in, temp); - num |= static_cast<unsigned long>(temp); - - read_guard(in, 0x2002); -} - -template <typename T> -void read_long(const char *& data, T& num) -{ - read_guard(data, 0x2001); - - unsigned char len; - read_number_nocheck(data, len); - - num = 0; - unsigned char temp; - if (len > 3) { - read_number_nocheck(data, temp); - num |= static_cast<unsigned long>(temp) << 24; - } - if (len > 2) { - read_number_nocheck(data, temp); - num |= static_cast<unsigned long>(temp) << 16; - } - if (len > 1) { - read_number_nocheck(data, temp); - num |= static_cast<unsigned long>(temp) << 8; - } - - read_number_nocheck(data, temp); - num |= static_cast<unsigned long>(temp); - - read_guard(data, 0x2002); -} - -template <typename T> -inline T read_long(std::istream& in) { - T num; - read_long(in, num); - return num; -} - -template <typename T> -inline T read_long(const char *& data) { - T num; - read_long(data, num); - return num; -} - -void read_string(std::istream& in, string& str); -void read_string(const char *& data, string& str); -void read_string(const char *& data, string * str); - -inline string read_string(std::istream& in) { - string temp; - read_string(in, temp); - return temp; -} - -inline string read_string(const char *& data) { - string temp; - read_string(data, temp); - return temp; -} - - -template <typename T> -inline void write_number_nocheck(std::ostream& out, T num) { - out.write(reinterpret_cast<char *>(&num), sizeof(num)); -} - -#if DEBUG_LEVEL >= ALPHA -#define write_guard(out, id) \ - write_number_nocheck<unsigned short>(out, id) -#else -#define write_guard(in, id) -#endif - -template <typename T> -inline void write_number(std::ostream& out, T num) { - write_guard(out, 0x2003); - out.write(reinterpret_cast<char *>(&num), sizeof(num)); - write_guard(out, 0x2004); -} - -void write_bool(std::ostream& out, bool num); - -template <typename T> -void write_long(std::ostream& out, T num) -{ - write_guard(out, 0x2001); - - unsigned char len = 4; - if (static_cast<unsigned long>(num) < 0x00000100UL) - len = 1; - else if (static_cast<unsigned long>(num) < 0x00010000UL) - len = 2; - else if (static_cast<unsigned long>(num) < 0x01000000UL) - len = 3; - write_number_nocheck<unsigned char>(out, len); - - unsigned char temp; - if (len > 3) { - temp = (static_cast<unsigned long>(num) & 0xFF000000UL) >> 24; - write_number_nocheck(out, temp); - } - if (len > 2) { - temp = (static_cast<unsigned long>(num) & 0x00FF0000UL) >> 16; - write_number_nocheck(out, temp); - } - if (len > 1) { - temp = (static_cast<unsigned long>(num) & 0x0000FF00UL) >> 8; - write_number_nocheck(out, temp); - } - - temp = (static_cast<unsigned long>(num) & 0x000000FFUL); - write_number_nocheck(out, temp); - - write_guard(out, 0x2002); -} - -void write_string(std::ostream& out, const string& str); - -template <typename T> -inline void write_object(std::ostream& out, const T& journal) { - assert(false); -} - -} // namespace binary -} // namespace ledger - -#endif // BINARY_H diff --git a/src/utility/context.h b/src/utility/context.h deleted file mode 100644 index 411e6821..00000000 --- a/src/utility/context.h +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright (c) 2003-2007, 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. - */ - -#ifndef _CONTEXT_H -#define _CONTEXT_H - -namespace ledger { - -class context -{ -public: - string description; // ex: 'While parsing file "%R" at line %L' - - context() throw() {} - explicit context(const string& _description) throw() - : description(_description) {} - - virtual ~context() {} -}; - -class file_context : public context -{ -public: - path pathname; // ex: ledger.dat - - uint_least32_t linenum_beg; // ex: 1010 - uint_least32_t linenum_end; // ex: 1010 - uint_least32_t position_beg; - uint_least32_t position_end; - - optional<uint_least32_t> colnum_beg; // ex: 8 - optional<uint_least32_t> colnum_end; // ex: 8 - - explicit file_context(const path& _pathname, - const uint_least32_t _linenum_beg, - const uint_least32_t _linenum_end, - const uint_least32_t _position_beg, - const uint_least32_t _position_end) throw() - : context(""), - pathname(_pathname), - linenum_beg(_linenum_beg), - linenum_end(_linenum_end), - position_beg(_position_beg), - position_end(_position_end) {} -}; - -class string_context : public context -{ -public: - string text; // ex: (The multi-line text of an entry) - - optional<uint_least32_t> linenum_beg_off; // ex: 2 - optional<uint_least32_t> linenum_end_off; // ex: 2 - optional<uint_least32_t> colnum_beg_off; // ex: 8 - optional<uint_least32_t> colnum_end_off; // ex: 8 -}; - -#if 0 - -class file_context : public error_context -{ - protected: - string file; - unsigned long line; - public: - file_context(const string& _file, unsigned long _line, - const string& _desc = "") throw() - : error_context(_desc), file(_file), line(_line) {} - virtual ~file_context() throw() {} - - virtual void describe(std::ostream& out) const throw() { - if (! desc.empty()) - out << desc << " "; - - out << "\"" << file << "\", line " << line << ": "; - } -}; - -class line_context : public error_context { - public: - string line; - long pos; - - line_context(const string& _line, long _pos, - const string& _desc = "") throw() - : error_context(_desc), line(_line), pos(_pos) {} - virtual ~line_context() throw() {} - - virtual void describe(std::ostream& out) const throw() { - if (! desc.empty()) - out << desc << std::endl; - - out << " " << line << std::endl << " "; - long idx = pos < 0 ? line.length() - 1 : pos; - for (int i = 0; i < idx; i++) - out << " "; - out << "^" << std::endl; - } -}; - -#endif - -extern ptr_list<context> context_stack; - -#define PUSH_CONTEXT() try { -#define POP_CONTEXT(ctxt) \ - } catch (...) { \ - context_stack.push_front(new ctxt); \ - throw; \ - } - -} // namespace ledger - -#endif // _CONTEXT_H diff --git a/src/utility/pushvar.h b/src/utility/pushvar.h deleted file mode 100644 index 1a9bdcbc..00000000 --- a/src/utility/pushvar.h +++ /dev/null @@ -1,214 +0,0 @@ -/* - * Copyright (c) 2003-2007, 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. - */ - -/** - * @file scopevar.h - * @author John Wiegley - * @date Sun May 6 20:10:52 2007 - * - * @brief Adds a facility to C++ for handling "scoped executions". - * - * There are sometimes cases where you would like to guarantee that - * something happens at the end of a scope, such as calling a function - * to close a resource for you. - * - * The common idiom for this has become to write a helper class whose - * destructor will call that function. Of course, it must then be - * passed information from the calling scope to hold onto as state - * information, since the code within the class itself has no access - * to its points of use. - * - * This type of solution is cumbersome enough that it's sometimes - * avoided. Take calling pthread_mutex_unlock(pthread_mutex_t *) for - * example. A typical snippet of safe C++ code might look like this: - * - * @code - * void foo(pthread_mutex_t * mutex) { - * if (pthread_mutex_lock(mutex) == 0) { - * try { - * // Do work that requires the mutex to be locked; then... - * pthread_mutex_unlock(mutex); - * } - * catch (std::logic_error& exc) { - * // This is an exception we actually handle, and then exit - * pthread_mutex_unlock(mutex); - * } - * catch (...) { - * // These are exceptions we do not handle, but still the - * // mutex must be unlocked - * pthread_mutex_unlock(mutex); - * throw; - * } - * } - * } - * @endcode - * - * The alternative to this, as mentioned above, is to create a helper - * class named pthread_scoped_lock, which might look like this: - * - * @code - * class pthread_scoped_lock : public boost::noncopyable { - * pthread_mutex_t * mutex; - * public: - * explicit pthread_scoped_lock(pthread_mutex_t * locked_mutex) - * : mutex(locked_mutex) {} - * ~pthread_scoped_lock() { - * pthread_mutex_unlock(mutex); - * } - * }; - * @endcode - * - * Although this helper class is just as much work as writing the code - * above, it only needs to be written once. Now the access code can - * look like this: - * - * @code - * void foo(pthread_mutex_t * mutex) { - * if (pthread_mutex_lock(mutex) == 0) { - * pthread_scoped_lock(mutex); - * try { - * // Do work that requires the mutex to be locked - * } - * catch (std::logic_error& exc) { - * // This is an exception we actually handle, and then exit - * } - * } - * } - * @endcode - * - * But what if it could be even easier? That is what this file is - * for, to provide a scopevar<> class which guarantees execution - * of arbtirary code after a scope has terminated, without having to - * resort to custom utility classes. It relies on boost::bind to - * declare pending function calls. Here it what the above would look - * like: - * - * @code - * void foo(pthread_mutex_t * mutex) { - * if (pthread_mutex_lock(mutex) == 0) { - * scopevar<void> unlock_mutex - * (boost::bind(pthread_mutex_unlock, mutex)); - * try { - * // Do work that requires the mutex to be locked - * } - * catch (std::logic_error& exc) { - * // This is an exception we actually handle, and then exit - * } - * } - * } - * @endcode - * - * The advantage here is that no helper class ever needs to created, - * and hence no bugs from such helper classes can creep into the code. - * The single call to boost::bind creates a closure binding that will - * be invoked once the containing scope has terminated. - * - * Another kind of scopevar is useful for setting the values of - * variables to a predetermined value upon completion of a scope. - * Consider this example: - * - * @code - * bool foo_was_run; - * - * void foo() { - * scopevar<bool&> set_success((_1 = true), foo_was_run); - * // do some code, and make sure foo_was_run is set to true - * // once the scope is exited -- however this happens. - * } - * @endcode - * - * In this case, the Boost.Lambda library is used to create an - * anonymous functor whose job is to set the global variable - * `foo_was_run' to a predetermined value. - * - * Lastly, there is another helper class, `scoped_variable' whose job - * is solely to return variables to the value they had at the moment - * the scoped_variable class was instantiated. For example, let's say - * you have a `bar' variable that you want to work on, but you want to - * guarantee that its value is restored upon exiting the scope. This - * can be useful in recursion, for "pushing" and "popping" variable - * values during execution, for example: - * - * @code - * std::string bar = "Hello"; - * void foo() { - * scoped_variable<std::string> restore_bar(bar); - * bar = "Goodbye"; - * // do work with the changed bar; it gets restored upon exit - * } - * @endcode - * - * As a shortcut, you can specify the new value for the pushed - * variable as a second constructor argument: - * - * @code - * std::string bar = "Hello"; - * void foo() { - * scoped_variable<std::string> restore_bar(bar, "Goodbye"); - * // do work with the changed bar; it gets restored upon exit - * } - * @endcode - * - * Finally, you can stop a scopevar or scoped_variable from - * invoking its completion code by calling the `clear' method on the - * object instance. Once `clear' is called, the scoped execution - * becomes inert and will do nothing when the enclosing scope is - * exited. - */ - -#ifndef _SCOPEVAR_H -#define _SCOPEVAR_H - -template <typename T> -class push_variable : public boost::noncopyable -{ - T& var; - T prev; - bool enabled; - -public: - explicit push_variable(T& _var) - : var(_var), prev(var), enabled(true) {} - explicit push_variable(T& _var, const T& value) - : var(_var), prev(var), enabled(true) { - var = value; - } - ~push_variable() { - if (enabled) - var = prev; - } - - void clear() { - enabled = false; - } -}; - -#endif // _SCOPEVAR_H diff --git a/src/utility/times.h b/src/utility/times.h deleted file mode 100644 index 949d8031..00000000 --- a/src/utility/times.h +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (c) 2003-2007, 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. - */ - -#ifndef _TIMES_H -#define _TIMES_H - -namespace ledger { - -#define SUPPORT_DATE_AND_TIME 1 -#ifdef SUPPORT_DATE_AND_TIME - -typedef boost::posix_time::ptime moment_t; -typedef moment_t::time_duration_type duration_t; - -inline bool is_valid_moment(const moment_t& moment) { - return ! moment.is_not_a_date_time(); -} - -#else // SUPPORT_DATE_AND_TIME - -typedef boost::gregorian::date moment_t; -typedef boost::gregorian::date_duration duration_t; - -inline bool is_valid_moment(const moment_t& moment) { - return ! moment.is_not_a_date(); -} - -#endif // SUPPORT_DATE_AND_TIME - -extern const moment_t& now; - -DECLARE_EXCEPTION(datetime_error); - -class interval_t -{ -public: - interval_t() {} - interval_t(const string&) {} - - operator bool() const { - return false; - } - - void start(const moment_t&) {} - moment_t next() const { return moment_t(); } - - void parse(std::istream&) {} -}; - -#if 0 -inline moment_t ptime_local_to_utc(const moment_t& when) { - struct std::tm tm_gmt = to_tm(when); - return boost::posix_time::from_time_t(std::mktime(&tm_gmt)); -} - -// jww (2007-04-18): I need to make a general parsing function -// instead, and then make these into private methods. -inline moment_t ptime_from_local_date_string(const string& date_string) { - return ptime_local_to_utc(moment_t(boost::gregorian::from_string(date_string), - time_duration())); -} - -inline moment_t ptime_from_local_time_string(const string& time_string) { - return ptime_local_to_utc(boost::posix_time::time_from_string(time_string)); -} -#endif - -moment_t parse_datetime(const char * str); - -inline moment_t parse_datetime(const string& str) { - return parse_datetime(str.c_str()); -} - -extern const ptime time_now; -extern const date date_now; -extern bool day_before_month; - -#if 0 -struct intorchar -{ - int ival; - string sval; - - intorchar() : ival(-1) {} - intorchar(int val) : ival(val) {} - intorchar(const string& val) : ival(-1), sval(val) {} - intorchar(const intorchar& o) : ival(o.ival), sval(o.sval) {} -}; - -ledger::moment_t parse_abs_datetime(std::istream& input); -#endif - -} // namespace ledger - -#endif // _TIMES_H diff --git a/src/utility/utils.cc b/src/utils.cc index 7ddeb677..f2460ba1 100644 --- a/src/utility/utils.cc +++ b/src/utils.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2007, John Wiegley. All rights reserved. + * 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 @@ -29,7 +29,9 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "utils.h" +#include <system.hh> + +#include "times.h" /********************************************************************** * @@ -40,12 +42,12 @@ namespace ledger { -DECLARE_EXCEPTION(assertion_failed); +DECLARE_EXCEPTION(assertion_failed, std::logic_error); void debug_assert(const string& reason, const string& func, const string& file, - unsigned long line) + std::size_t line) { std::ostringstream buf; buf << "Assertion failed in \"" << file << "\", line " << line @@ -69,32 +71,34 @@ namespace ledger { bool verify_enabled = false; typedef std::pair<std::string, std::size_t> allocation_pair; -typedef std::map<void *, allocation_pair> live_memory_map; -typedef std::multimap<void *, allocation_pair> live_objects_map; +typedef std::map<void *, allocation_pair> memory_map; +typedef std::multimap<void *, allocation_pair> objects_map; -typedef std::pair<unsigned int, std::size_t> count_size_pair; +typedef std::pair<std::size_t, std::size_t> count_size_pair; typedef std::map<std::string, count_size_pair> object_count_map; -static live_memory_map * live_memory = NULL; -static object_count_map * live_memory_count = NULL; -static object_count_map * total_memory_count = NULL; - -static bool memory_tracing_active = false; +namespace { + bool memory_tracing_active = false; -static live_objects_map * live_objects = NULL; -static object_count_map * live_object_count = NULL; -static object_count_map * total_object_count = NULL; -static object_count_map * total_ctor_count = NULL; + memory_map * live_memory = NULL; + memory_map * freed_memory = NULL; + object_count_map * live_memory_count = NULL; + object_count_map * total_memory_count = NULL; + objects_map * live_objects = NULL; + object_count_map * live_object_count = NULL; + object_count_map * total_object_count = NULL; + object_count_map * total_ctor_count = NULL; +} void initialize_memory_tracing() { memory_tracing_active = false; - live_memory = new live_memory_map; + live_memory = new memory_map; + freed_memory = new memory_map; live_memory_count = new object_count_map; total_memory_count = new object_count_map; - - live_objects = new live_objects_map; + live_objects = new objects_map; live_object_count = new object_count_map; total_object_count = new object_count_map; total_ctor_count = new object_count_map; @@ -109,21 +113,20 @@ void shutdown_memory_tracing() if (live_objects) { IF_DEBUG("memory.counts") report_memory(std::cerr, true); - else - IF_DEBUG("memory.counts.live") - report_memory(std::cerr); + else IF_DEBUG("memory.counts.live") + report_memory(std::cerr); else if (live_objects->size() > 0) report_memory(std::cerr); } - checked_delete(live_memory); live_memory = NULL; - checked_delete(live_memory_count); live_memory_count = NULL; + checked_delete(live_memory); live_memory = NULL; + checked_delete(freed_memory); freed_memory = NULL; + checked_delete(live_memory_count); live_memory_count = NULL; checked_delete(total_memory_count); total_memory_count = NULL; - - checked_delete(live_objects); live_objects = NULL; - checked_delete(live_object_count); live_object_count = NULL; + checked_delete(live_objects); live_objects = NULL; + checked_delete(live_object_count); live_object_count = NULL; checked_delete(total_object_count); total_object_count = NULL; - checked_delete(total_ctor_count); total_ctor_count = NULL; + checked_delete(total_ctor_count); total_ctor_count = NULL; } inline void add_to_count_map(object_count_map& the_map, @@ -144,22 +147,24 @@ std::size_t current_memory_size() { std::size_t memory_size = 0; - for (object_count_map::const_iterator i = live_memory_count->begin(); - i != live_memory_count->end(); - i++) - memory_size += (*i).second.second; + foreach (const object_count_map::value_type& pair, *live_memory_count) + memory_size += pair.second.second; return memory_size; } static void trace_new_func(void * ptr, const char * which, std::size_t size) { + if (! live_memory || ! memory_tracing_active) return; + memory_tracing_active = false; - if (! live_memory) return; + memory_map::iterator i = freed_memory->find(ptr); + if (i != freed_memory->end()) + freed_memory->erase(i); live_memory->insert - (live_memory_map::value_type(ptr, allocation_pair(which, size))); + (memory_map::value_type(ptr, allocation_pair(which, size))); add_to_count_map(*live_memory_count, which, size); add_to_count_map(*total_memory_count, which, size); @@ -170,9 +175,9 @@ static void trace_new_func(void * ptr, const char * which, std::size_t size) static void trace_delete_func(void * ptr, const char * which) { - memory_tracing_active = false; + if (! live_memory || ! memory_tracing_active) return; - if (! live_memory) return; + memory_tracing_active = false; // Ignore deletions of memory not tracked, since it's possible that // a user (like boost) allocated a block of memory before memory @@ -180,15 +185,32 @@ static void trace_delete_func(void * ptr, const char * which) // If it really is a double-delete, the malloc library on OS/X will // notify me. - live_memory_map::iterator i = live_memory->find(ptr); - if (i == live_memory->end()) + memory_map::iterator i = live_memory->find(ptr); + if (i == live_memory->end()) { + i = freed_memory->find(ptr); + if (i != freed_memory->end()) + VERIFY(! "Freeing a block of memory twice"); +#if 0 + // There can be memory allocated by Boost or the standard library, which + // was allocated before memory tracing got turned on, that the system + // might free for some coincidental reason. As such, we can't rely on + // this check being valid. I've seen cases where processes ran to + // completion with it on, and then others where valid processes failed. + else + VERIFY(! "Freeing an unknown block of memory"); +#endif + memory_tracing_active = true; return; + } std::size_t size = (*i).second.second; VERIFY((*i).second.first == which); live_memory->erase(i); + freed_memory->insert + (memory_map::value_type(ptr, allocation_pair(which, size))); + object_count_map::iterator j = live_memory_count->find(which); VERIFY(j != live_memory_count->end()); @@ -250,12 +272,10 @@ namespace ledger { inline void report_count_map(std::ostream& out, object_count_map& the_map) { - for (object_count_map::iterator i = the_map.begin(); - i != the_map.end(); - i++) - out << " " << std::right << std::setw(12) << (*i).second.first - << " " << std::right << std::setw(12) << (*i).second.second - << " " << std::left << (*i).first + foreach (object_count_map::value_type& pair, the_map) + out << " " << std::right << std::setw(12) << pair.second.first + << " " << std::right << std::setw(7) << pair.second.second + << " " << std::left << pair.first << std::endl; } @@ -263,10 +283,8 @@ std::size_t current_objects_size() { std::size_t objects_size = 0; - for (object_count_map::const_iterator i = live_object_count->begin(); - i != live_object_count->end(); - i++) - objects_size += (*i).second.second; + foreach (const object_count_map::value_type& pair, *live_object_count) + objects_size += pair.second.second; return objects_size; } @@ -274,9 +292,9 @@ std::size_t current_objects_size() void trace_ctor_func(void * ptr, const char * cls_name, const char * args, std::size_t cls_size) { - memory_tracing_active = false; + if (! live_objects || ! memory_tracing_active) return; - if (! live_objects) return; + memory_tracing_active = false; static char name[1024]; std::strcpy(name, cls_name); @@ -287,7 +305,7 @@ void trace_ctor_func(void * ptr, const char * cls_name, const char * args, DEBUG("memory.debug", "TRACE_CTOR " << ptr << " " << name); live_objects->insert - (live_objects_map::value_type(ptr, allocation_pair(cls_name, cls_size))); + (objects_map::value_type(ptr, allocation_pair(cls_name, cls_size))); add_to_count_map(*live_object_count, cls_name, cls_size); add_to_count_map(*total_object_count, cls_name, cls_size); @@ -299,17 +317,21 @@ void trace_ctor_func(void * ptr, const char * cls_name, const char * args, void trace_dtor_func(void * ptr, const char * cls_name, std::size_t cls_size) { - memory_tracing_active = false; + if (! live_objects || ! memory_tracing_active) return; - if (! live_objects) return; + memory_tracing_active = false; DEBUG("memory.debug", "TRACE_DTOR " << ptr << " " << cls_name); - live_objects_map::iterator i = live_objects->find(ptr); - VERIFY(i != live_objects->end()); + objects_map::iterator i = live_objects->find(ptr); + if (i == live_objects->end()) { + warning_(_("Attempting to delete %1 a non-living %2") << ptr << cls_name); + memory_tracing_active = true; + return; + } - int ptr_count = live_objects->count(ptr); - for (int x = 0; x < ptr_count; x++, i++) { + std::size_t ptr_count = live_objects->count(ptr); + for (std::size_t x = 0; x < ptr_count; x++, i++) { if ((*i).second.first == cls_name) { live_objects->erase(i); break; @@ -317,7 +339,11 @@ void trace_dtor_func(void * ptr, const char * cls_name, std::size_t cls_size) } object_count_map::iterator k = live_object_count->find(cls_name); - VERIFY(k != live_object_count->end()); + if (k == live_object_count->end()) { + warning_(_("Failed to find %1 in live object counts") << cls_name); + memory_tracing_active = true; + return; + } (*k).second.second -= cls_size; if (--(*k).second.first == 0) @@ -328,7 +354,7 @@ void trace_dtor_func(void * ptr, const char * cls_name, std::size_t cls_size) void report_memory(std::ostream& out, bool report_all) { - if (! live_memory) return; + if (! live_memory || ! memory_tracing_active) return; if (live_memory_count->size() > 0) { out << "NOTE: There may be memory held by Boost " @@ -340,12 +366,10 @@ void report_memory(std::ostream& out, bool report_all) if (live_memory->size() > 0) { out << "Live memory:" << std::endl; - for (live_memory_map::const_iterator i = live_memory->begin(); - i != live_memory->end(); - i++) - out << " " << std::right << std::setw(7) << (*i).first - << " " << std::right << std::setw(7) << (*i).second.second - << " " << std::left << (*i).second.first + foreach (const memory_map::value_type& pair, *live_memory) + out << " " << std::right << std::setw(12) << pair.first + << " " << std::right << std::setw(7) << pair.second.second + << " " << std::left << pair.second.first << std::endl; } @@ -362,12 +386,10 @@ void report_memory(std::ostream& out, bool report_all) if (live_objects->size() > 0) { out << "Live objects:" << std::endl; - for (live_objects_map::const_iterator i = live_objects->begin(); - i != live_objects->end(); - i++) - out << " " << std::right << std::setw(7) << (*i).first - << " " << std::right << std::setw(7) << (*i).second.second - << " " << std::left << (*i).second.first + foreach (const objects_map::value_type& pair, *live_objects) + out << " " << std::right << std::setw(12) << pair.first + << " " << std::right << std::setw(7) << pair.second.second + << " " << std::left << pair.second.first << std::endl; } @@ -384,18 +406,30 @@ void report_memory(std::ostream& out, bool report_all) } } +} // namespace ledger + +#endif // VERIFY_ON + +/********************************************************************** + * + * String wrapper + */ + +namespace ledger { + +#if defined(VERIFY_ON) || defined(HAVE_BOOST_PYTHON) string::string() : std::string() { TRACE_CTOR(string, ""); } string::string(const string& str) : std::string(str) { - TRACE_CTOR(string, "const string&"); + TRACE_CTOR(string, "copy"); } string::string(const std::string& str) : std::string(str) { TRACE_CTOR(string, "const std::string&"); } -string::string(const int len, char x) : std::string(len, x) { - TRACE_CTOR(string, "const int, char"); +string::string(size_type len, char x) : std::string(len, x) { + TRACE_CTOR(string, "size_type, char"); } string::string(const char * str) : std::string(str) { TRACE_CTOR(string, "const char *"); @@ -403,25 +437,80 @@ string::string(const char * str) : std::string(str) { string::string(const char * str, const char * end) : std::string(str, end) { TRACE_CTOR(string, "const char *, const char *"); } -string::string(const string& str, int x) : std::string(str, x) { - TRACE_CTOR(string, "const string&, int"); +string::string(const string& str, size_type x) : std::string(str, x) { + TRACE_CTOR(string, "const string&, size_type"); } -string::string(const string& str, int x, int y) : std::string(str, x, y) { - TRACE_CTOR(string, "const string&, int, int"); +string::string(const string& str, size_type x, size_type y) + : std::string(str, x, y) { + TRACE_CTOR(string, "const string&, size_type, size_type"); } -string::string(const char * str, int x) : std::string(str, x) { - TRACE_CTOR(string, "const char *, int"); +string::string(const char * str, size_type x) : std::string(str, x) { + TRACE_CTOR(string, "const char *, size_type"); } -string::string(const char * str, int x, int y) : std::string(str, x, y) { - TRACE_CTOR(string, "const char *, int, int"); +string::string(const char * str, size_type x, size_type y) + : std::string(str, x, y) { + TRACE_CTOR(string, "const char *, size_type, size_type"); } -string::~string() { +string::~string() throw() { TRACE_DTOR(string); } -} // namespace ledger +#endif // defined(VERIFY_ON) || defined(HAVE_BOOST_PYTHON) -#endif // VERIFY_ON +string empty_string(""); + +strings_list split_arguments(const char * line) +{ + strings_list args; + + char buf[4096]; + char * q = buf; + char in_quoted_string = '\0'; + + for (const char * p = line; *p; p++) { + if (! in_quoted_string && std::isspace(*p)) { + if (q != buf) { + *q = '\0'; + args.push_back(buf); + q = buf; + } + } + else if (in_quoted_string != '\'' && *p == '\\') { + p++; + if (! *p) + throw_(std::logic_error, _("Invalid use of backslash")); + *q++ = *p; + } + else if (in_quoted_string != '"' && *p == '\'') { + if (in_quoted_string == '\'') + in_quoted_string = '\0'; + else + in_quoted_string = '\''; + } + else if (in_quoted_string != '\'' && *p == '"') { + if (in_quoted_string == '"') + in_quoted_string = '\0'; + else + in_quoted_string = '"'; + } + else { + *q++ = *p; + } + } + + if (in_quoted_string) + throw_(std::logic_error, + _("Unterminated string, expected '%1'") << in_quoted_string); + + if (q != buf) { + *q = '\0'; + args.push_back(buf); + } + + return args; +} + +} // namespace ledger /********************************************************************** * @@ -437,13 +526,7 @@ std::ostream * _log_stream = &std::cerr; std::ostringstream _log_buffer; #if defined(TRACING_ON) -unsigned int _trace_level; -#endif - -#ifdef BOOST_DATE_TIME_HAS_HIGH_PRECISION_CLOCK -#define CURRENT_TIME() boost::posix_time::microsec_clock::universal_time() -#else -#define CURRENT_TIME() boost::posix_time::second_clock::universal_time() +uint8_t _trace_level; #endif static inline void stream_memory_size(std::ostream& out, std::size_t size) @@ -463,27 +546,31 @@ static ptime logger_start; bool logger_func(log_level_t level) { - unsigned long appender = 0; - if (! logger_has_run) { logger_has_run = true; - logger_start = CURRENT_TIME(); + logger_start = TRUE_CURRENT_TIME(); +#if defined(VERIFY_ON) IF_VERIFY() - *_log_stream << " TIME OBJSZ MEMSZ" << std::endl; - - appender = (logger_start - now).total_milliseconds(); + *_log_stream << " TIME OBJSZ MEMSZ" << std::endl; +#else + IF_VERIFY() + *_log_stream << " TIME" << std::endl; +#endif } *_log_stream << std::right << std::setw(5) - << (CURRENT_TIME() - logger_start).total_milliseconds(); + << (TRUE_CURRENT_TIME() - + logger_start).total_milliseconds() << "ms"; +#if defined(VERIFY_ON) IF_VERIFY() { *_log_stream << std::right << std::setw(6) << std::setprecision(3); stream_memory_size(*_log_stream, current_objects_size()); *_log_stream << std::right << std::setw(6) << std::setprecision(3); stream_memory_size(*_log_stream, current_memory_size()); } +#endif *_log_stream << " " << std::left << std::setw(7); @@ -505,13 +592,7 @@ bool logger_func(log_level_t level) break; } - *_log_stream << ' ' << _log_buffer.str(); - - if (appender) - *_log_stream << " (" << appender << "ms startup)"; - - *_log_stream << std::endl; - + *_log_stream << ' ' << _log_buffer.str() << std::endl; _log_buffer.str(""); return true; @@ -525,6 +606,16 @@ namespace ledger { optional<std::string> _log_category; +struct __maybe_enable_debugging { + __maybe_enable_debugging() { + const char * p = std::getenv("LEDGER_DEBUG"); + if (p != NULL) { + _log_level = LOG_DEBUG; + _log_category = p; + } + } +} __maybe_enable_debugging_obj; + } // namespace ledger #endif // DEBUG_ON @@ -532,14 +623,15 @@ optional<std::string> _log_category; /********************************************************************** * - * Timers (allows log entries to specify cumulative time spent) + * Timers (allows log xacts to specify cumulative time spent) */ #if defined(LOGGING_ON) && defined(TIMERS_ON) namespace ledger { -struct timer_t { +struct timer_t +{ log_level_t level; ptime begin; time_duration spent; @@ -547,7 +639,7 @@ struct timer_t { bool active; timer_t(log_level_t _level, std::string _description) - : level(_level), begin(CURRENT_TIME()), + : level(_level), begin(TRUE_CURRENT_TIME()), spent(time_duration(0, 0, 0, 0)), description(_description), active(true) {} }; @@ -559,6 +651,7 @@ static timer_map timers; void start_timer(const char * name, log_level_t lvl) { #if defined(VERIFY_ON) + bool tracing_active = memory_tracing_active; memory_tracing_active = false; #endif @@ -567,46 +660,52 @@ void start_timer(const char * name, log_level_t lvl) timers.insert(timer_map::value_type(name, timer_t(lvl, _log_buffer.str()))); } else { assert((*i).second.description == _log_buffer.str()); - (*i).second.begin = CURRENT_TIME(); + (*i).second.begin = TRUE_CURRENT_TIME(); (*i).second.active = true; } _log_buffer.str(""); #if defined(VERIFY_ON) - memory_tracing_active = true; + memory_tracing_active = tracing_active; #endif } void stop_timer(const char * name) { #if defined(VERIFY_ON) + bool tracing_active = memory_tracing_active; memory_tracing_active = false; #endif timer_map::iterator i = timers.find(name); assert(i != timers.end()); - (*i).second.spent += CURRENT_TIME() - (*i).second.begin; + (*i).second.spent += TRUE_CURRENT_TIME() - (*i).second.begin; (*i).second.active = false; #if defined(VERIFY_ON) - memory_tracing_active = true; + memory_tracing_active = tracing_active; #endif } void finish_timer(const char * name) { #if defined(VERIFY_ON) + bool tracing_active = memory_tracing_active; memory_tracing_active = false; #endif timer_map::iterator i = timers.find(name); - if (i == timers.end()) + if (i == timers.end()) { +#if defined(VERIFY_ON) + memory_tracing_active = tracing_active; +#endif return; + } time_duration spent = (*i).second.spent; if ((*i).second.active) { - spent = CURRENT_TIME() - (*i).second.begin; + spent = TRUE_CURRENT_TIME() - (*i).second.begin; (*i).second.active = false; } @@ -628,7 +727,7 @@ void finish_timer(const char * name) timers.erase(i); #if defined(VERIFY_ON) - memory_tracing_active = true; + memory_tracing_active = tracing_active; #endif } @@ -638,15 +737,20 @@ void finish_timer(const char * name) /********************************************************************** * - * Exception handling + * Signal handlers */ -namespace ledger { +caught_signal_t caught_signal = NONE_CAUGHT; -std::ostringstream _exc_buffer; -ptr_list<context> context_stack; +void sigint_handler(int) +{ + caught_signal = INTERRUPTED; +} -} // namespace ledger +void sigpipe_handler(int) +{ + caught_signal = PIPE_CLOSED; +} /********************************************************************** * @@ -655,20 +759,18 @@ ptr_list<context> context_stack; namespace ledger { +const string version = PACKAGE_VERSION; + path expand_path(const path& pathname) { if (pathname.empty()) return pathname; -#if 1 - return pathname; -#else - // jww (2007-04-30): I need to port this code to use - // boost::filesystem::path + std::string path_string = pathname.string(); const char * pfx = NULL; - string::size_type pos = pathname.find_first_of('/'); + string::size_type pos = path_string.find_first_of('/'); - if (pathname.length() == 1 || pos == 1) { + if (path_string.length() == 1 || pos == 1) { pfx = std::getenv("HOME"); #ifdef HAVE_GETPWUID if (! pfx) { @@ -681,8 +783,8 @@ path expand_path(const path& pathname) } #ifdef HAVE_GETPWNAM else { - string user(pathname, 1, pos == string::npos ? - string::npos : pos - 1); + string user(path_string, 1, pos == string::npos ? + string::npos : pos - 1); struct passwd * pw = getpwnam(user.c_str()); if (pw) pfx = pw->pw_dir; @@ -702,10 +804,9 @@ path expand_path(const path& pathname) if (result.length() == 0 || result[result.length() - 1] != '/') result += '/'; - result += pathname.substr(pos + 1); + result += path_string.substr(pos + 1); return result; -#endif } path resolve_path(const path& pathname) diff --git a/src/utility/utils.h b/src/utils.h index b78e716d..ab8fb495 100644 --- a/src/utility/utils.h +++ b/src/utils.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2007, John Wiegley. All rights reserved. + * 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 @@ -30,42 +30,24 @@ */ /** + * @defgroup util General utilities + */ + +/** * @file utils.h * @author John Wiegley - * @date Sun May 6 21:20:00 2007 - * - * @brief This file contains general utility facilities used by Ledger. * - * Ledger has need of the following utility code, which this file - * provides or includes in: + * @ingroup util * - * - system headers - * - asserts - * - verification (basically, "heavy asserts") - * - tracing code - * - debug logging code - * - timing code - * - current error context - * - exception framework - * - date/time type - * - supports_flags<> for objects that use flags - * - push_variable<> for restoring variable values + * @brief General utility facilities used by Ledger */ - #ifndef _UTILS_H #define _UTILS_H -#if defined(DEBUG_MODE) -#define BOOST_MULTI_INDEX_ENABLE_SAFE_MODE 1 -#define BOOST_MULTI_INDEX_ENABLE_INVARIANT_CHECKING 1 -#endif - -#include <system.hh> - -/********************************************************************** - * - * Default values +/** + * @name Default values */ +/*@{*/ #if defined(DEBUG_MODE) #define VERIFY_ON 1 @@ -76,25 +58,28 @@ #define NO_ASSERTS 1 #define NO_LOGGING 1 #else -#define VERIFY_ON 1 // compiled in, use --verify to enable #define TRACING_ON 1 // use --trace X to enable #define TIMERS_ON 1 #endif -/********************************************************************** - * - * Forward declarations +/*@}*/ + +/** + * @name Forward declarations */ +/*@{*/ namespace ledger { using namespace boost; -#if defined(VERIFY_ON) +#if defined(VERIFY_ON) || defined(HAVE_BOOST_PYTHON) class string; #else typedef std::string string; #endif + typedef std::list<string> strings_list; + typedef posix_time::ptime ptime; typedef ptime::time_duration_type time_duration; typedef gregorian::date date; @@ -107,10 +92,12 @@ namespace ledger { typedef boost::filesystem::filesystem_error filesystem_error; } -/********************************************************************** - * - * Assertions +/*@}*/ + +/** + * @name Assertions */ +/*@{*/ #ifdef assert #undef assert @@ -123,7 +110,7 @@ namespace ledger { namespace ledger { void debug_assert(const string& reason, const string& func, - const string& file, unsigned long line); + const string& file, std::size_t line); } #define assert(x) \ @@ -136,10 +123,12 @@ namespace ledger { #endif // ASSERTS_ON -/********************************************************************** - * - * Verification (basically, very slow asserts) +/*@}*/ + +/** + * @name Verification (i.e., heavy asserts) */ +/*@{*/ #if defined(VERIFY_ON) @@ -161,30 +150,74 @@ void trace_ctor_func(void * ptr, const char * cls_name, const char * args, void trace_dtor_func(void * ptr, const char * cls_name, std::size_t cls_size); #define TRACE_CTOR(cls, args) \ - (DO_VERIFY() ? trace_ctor_func(this, #cls, args, sizeof(cls)) : ((void)0)) + (DO_VERIFY() ? \ + ledger::trace_ctor_func(this, #cls, args, sizeof(cls)) : ((void)0)) #define TRACE_DTOR(cls) \ - (DO_VERIFY() ? trace_dtor_func(this, #cls, sizeof(cls)) : ((void)0)) + (DO_VERIFY() ? \ + ledger::trace_dtor_func(this, #cls, sizeof(cls)) : ((void)0)) void report_memory(std::ostream& out, bool report_all = false); +} // namespace ledger + +#else // ! VERIFY_ON + +#define VERIFY(x) +#define DO_VERIFY() true +#define TRACE_CTOR(cls, args) +#define TRACE_DTOR(cls) + +#endif // VERIFY_ON + +#define IF_VERIFY() if (DO_VERIFY()) + +/*@}*/ + /** - * This string type is a wrapper around std::string that allows us to - * trace constructor and destructor calls. + * @name String wrapper + * + * This string type is a wrapper around std::string that allows us to trace + * constructor and destructor calls. It also makes ledger's use of strings a + * unique type, that the Boost.Python code can use as the basis for + * transparent Unicode conversions. */ +/*@{*/ + +namespace ledger { + +#if defined(VERIFY_ON) || defined(HAVE_BOOST_PYTHON) + class string : public std::string { public: string(); string(const string& str); string(const std::string& str); - string(const int len, char x); + string(size_type len, char x); + template<class _InputIterator> + string(_InputIterator __beg, _InputIterator __end) + : std::string(__beg, __end) { + TRACE_CTOR(string, "InputIterator, InputIterator"); + } string(const char * str); string(const char * str, const char * end); - string(const string& str, int x); - string(const string& str, int x, int y); - string(const char * str, int x); - string(const char * str, int x, int y); - ~string(); + string(const string& str, size_type x); + string(const string& str, size_type x, size_type y); + string(const char * str, size_type x); + string(const char * str, size_type x, size_type y); + ~string() throw(); + +#if defined(HAVE_BOOST_SERIALIZATION) +private: + /** Serialization. */ + + friend class boost::serialization::access; + + template<class Archive> + void serialize(Archive& ar, const unsigned int /* version */) { + ar & boost::serialization::base_object<std::string>(*this); + } +#endif // HAVE_BOOST_SERIALIZATION }; inline string operator+(const string& __lhs, const string& __rhs) @@ -231,23 +264,20 @@ inline bool operator!=(const char* __lhs, const string& __rhs) inline bool operator!=(const string& __lhs, const char* __rhs) { return __lhs.compare(__rhs) != 0; } -} // namespace ledger +#endif // defined(VERIFY_ON) || defined(HAVE_BOOST_PYTHON) -#else // ! VERIFY_ON +extern string empty_string; -#define VERIFY(x) -#define DO_VERIFY() true -#define TRACE_CTOR(cls, args) -#define TRACE_DTOR(cls) +strings_list split_arguments(const char * line); -#endif // VERIFY_ON +} // namespace ledger -#define IF_VERIFY() if (DO_VERIFY()) +/*@}*/ -/********************************************************************** - * - * Logging +/** + * @name Tracing and logging */ +/*@{*/ #if ! defined(NO_LOGGING) #define LOGGING_ON 1 @@ -282,7 +312,7 @@ bool logger_func(log_level_t level); #if defined(TRACING_ON) -extern unsigned int _trace_level; +extern uint8_t _trace_level; #define SHOW_TRACE(lvl) \ (ledger::_log_level >= ledger::LOG_TRACE && lvl <= ledger::_trace_level) @@ -377,10 +407,13 @@ inline bool category_matches(const char * cat) { #define IF_FATAL() if (SHOW_FATAL()) #define IF_CRITICAL() if (SHOW_CRITICAL()) -/********************************************************************** - * - * Timers (allows log entries to specify cumulative time spent) +/*@}*/ + +/** + * @name Timers + * This allows log xacts to specify cumulative time spent. */ +/*@{*/ #if defined(LOGGING_ON) && defined(TIMERS_ON) @@ -455,71 +488,40 @@ void finish_timer(const char * name); #endif // TIMERS_ON -/********************************************************************** - * - * Exception handling - */ +/*@}*/ -#include "context.h" +/* + * These files define the other internal facilities. + */ -namespace ledger { +#include "error.h" -#define DECLARE_EXCEPTION(name) \ - class name : public std::logic_error { \ - public: \ - name(const string& why) throw() : std::logic_error(why) {} \ - } +enum caught_signal_t { + NONE_CAUGHT, + INTERRUPTED, + PIPE_CLOSED +}; -extern std::ostringstream _exc_buffer; +extern caught_signal_t caught_signal; -template <typename T> -inline void throw_func(const std::string& message) { - _exc_buffer.str(""); - throw T(message); -} +void sigint_handler(int sig); +void sigpipe_handler(int sig); -#define throw_(cls, msg) \ - ((_exc_buffer << msg), throw_func<cls>(_exc_buffer.str())) - -#if 0 -inline void throw_unexpected_error(char c, char wanted) { - if (c == -1) { - if (wanted) - throw new error(string("Missing '") + wanted + "'"); - else - throw new error("Unexpected end of input"); - } else { - if (wanted) - throw new error(string("Invalid char '") + c + - "' (wanted '" + wanted + "')"); - else - throw new error(string("Invalid char '") + c + "'"); +inline void check_for_signal() { + switch (caught_signal) { + case NONE_CAUGHT: + break; + case INTERRUPTED: + throw std::runtime_error(_("Interrupted by user (use Control-D to quit)")); + case PIPE_CLOSED: + throw std::runtime_error(_("Pipe terminated")); } } -#else -inline void throw_unexpected_error(char, char) { -} -#endif - -} // namespace ledger - -/********************************************************************** - * - * Date/time support classes - * General support for objects with "flags" - * Support for object serialization (binary read/write) - * Support for scoped execution and variable restoration - */ -#include "times.h" -#include "flags.h" -#include "binary.h" -#include "pushvar.h" - -/********************************************************************** - * - * General utility functions +/** + * @name General utility functions */ +/*@{*/ #define foreach BOOST_FOREACH @@ -541,6 +543,171 @@ inline const string& either_or(const string& first, return first.empty() ? second : first; } +inline char * skip_ws(char * ptr) { + while (*ptr == ' ' || *ptr == '\t' || *ptr == '\n') + ptr++; + return ptr; +} + +inline char * trim_ws(char * ptr) { + std::size_t len = std::strlen(ptr); + int i = int(len) - 1; + while (i >= 0 && (ptr[i] == ' ' || ptr[i] == '\t' || ptr[i] == '\n')) + ptr[i--] = '\0'; + return skip_ws(ptr); +} + +inline char * next_element(char * buf, bool variable = false) { + for (char * p = buf; *p; p++) { + if (! (*p == ' ' || *p == '\t')) + continue; + + if (! variable) { + *p = '\0'; + return skip_ws(p + 1); + } + else if (*p == '\t') { + *p = '\0'; + return skip_ws(p + 1); + } + else if (*(p + 1) == ' ') { + *p = '\0'; + return skip_ws(p + 2); + } + } + return NULL; +} + +inline char peek_next_nonws(std::istream& in) { + char c = static_cast<char>(in.peek()); + while (in.good() && ! in.eof() && std::isspace(c)) { + in.get(c); + c = static_cast<char>(in.peek()); + } + return c; +} + +#define READ_INTO(str, targ, size, var, cond) { \ + char * _p = targ; \ + var = static_cast<char>(str.peek()); \ + while (str.good() && ! str.eof() && var != '\n' && \ + (cond) && _p - targ < size) { \ + str.get(var); \ + if (str.eof()) \ + break; \ + if (var == '\\') { \ + str.get(var); \ + if (in.eof()) \ + break; \ + switch (var) { \ + case 'b': var = '\b'; break; \ + case 'f': var = '\f'; break; \ + case 'n': var = '\n'; break; \ + case 'r': var = '\r'; break; \ + case 't': var = '\t'; break; \ + case 'v': var = '\v'; break; \ + default: break; \ + } \ + } \ + *_p++ = var; \ + var = static_cast<char>(str.peek()); \ + } \ + *_p = '\0'; \ + } + +#define READ_INTO_(str, targ, size, var, idx, cond) { \ + char * _p = targ; \ + var = static_cast<char>(str.peek()); \ + while (str.good() && ! str.eof() && var != '\n' && \ + (cond) && _p - targ < size) { \ + str.get(var); \ + if (str.eof()) \ + break; \ + idx++; \ + if (var == '\\') { \ + str.get(var); \ + if (in.eof()) \ + break; \ + switch (var) { \ + case 'b': var = '\b'; break; \ + case 'f': var = '\f'; break; \ + case 'n': var = '\n'; break; \ + case 'r': var = '\r'; break; \ + case 't': var = '\t'; break; \ + case 'v': var = '\v'; break; \ + default: break; \ + } \ + idx++; \ + } \ + *_p++ = var; \ + var = static_cast<char>(str.peek()); \ + } \ + *_p = '\0'; \ + } + +inline string to_hex(uint_least32_t * message_digest, const int len = 1) +{ + std::ostringstream buf; + + for(int i = 0; i < 5 ; i++) { + buf.width(8); + buf.fill('0'); + buf << std::hex << message_digest[i]; + if (i + 1 >= len) + break; // only output the first LEN dwords + } + return buf.str(); +} + +class push_xml +{ + std::ostream& out; + string tag; + bool leave_open; + +public: + push_xml(std::ostream& _out, const string& _tag, bool has_attrs = false, + bool _leave_open = false) + : out(_out), tag(_tag), leave_open(_leave_open) { + out << '<' << tag; + if (! has_attrs) + out << '>'; + } + ~push_xml() { + if (! leave_open) + out << "</" << tag << '>'; + } + + void close_attrs() { + out << '>'; + } + + static string guard(const string& str) { + std::ostringstream buf; + foreach (const char& ch, str) { + switch (ch) { + case '<': + buf << "<"; + break; + case '>': + buf << ">"; + break; + case '&': + buf << "&"; + break; + default: + buf << ch; + break; + } + } + return buf.str(); + } +}; + +extern const string version; + } // namespace ledger +/*@}*/ + #endif // _UTILS_H diff --git a/src/value.cc b/src/value.cc new file mode 100644 index 00000000..cce4c4e8 --- /dev/null +++ b/src/value.cc @@ -0,0 +1,1864 @@ +/* + * 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 "value.h" +#include "commodity.h" +#include "annotate.h" +#include "pool.h" +#include "unistring.h" // for justify() + +namespace ledger { + +intrusive_ptr<value_t::storage_t> value_t::true_value; +intrusive_ptr<value_t::storage_t> value_t::false_value; + +value_t::storage_t& value_t::storage_t::operator=(const value_t::storage_t& rhs) +{ + type = rhs.type; + + switch (type) { + case BALANCE: + data = new balance_t(*boost::get<balance_t *>(rhs.data)); + break; + case SEQUENCE: + data = new sequence_t(*boost::get<sequence_t *>(rhs.data)); + break; + + default: + data = rhs.data; + break; + } + + return *this; +} + +void value_t::initialize() +{ + true_value = new storage_t; + true_value->type = BOOLEAN; + true_value->data = true; + + false_value = new storage_t; + false_value->type = BOOLEAN; + false_value->data = false; +} + +void value_t::shutdown() +{ + true_value = intrusive_ptr<storage_t>(); + false_value = intrusive_ptr<storage_t>(); +} + +value_t::operator bool() const +{ + switch (type()) { + case VOID: + return false; + case BOOLEAN: + return as_boolean(); + case DATETIME: + return is_valid(as_datetime()); + case DATE: + return is_valid(as_date()); + case INTEGER: + return as_long(); + case AMOUNT: + return as_amount(); + case BALANCE: + return as_balance(); + case STRING: + return ! as_string().empty(); + case MASK: { + std::ostringstream out; + out << *this; + throw_(value_error, + _("Cannot determine truth of %1 (did you mean 'account =~ %2'?)") + << label() << out.str()); + } + case SEQUENCE: + if (! as_sequence().empty()) { + foreach (const value_t& value, as_sequence()) { + if (value) + return true; + } + } + return false; + case SCOPE: + return as_scope() != NULL; + default: + break; + } + + throw_(value_error, _("Cannot determine truth of %1") << label()); + return false; +} + +void value_t::set_type(type_t new_type) +{ + if (new_type == VOID) { +#if BOOST_VERSION >= 103700 + storage.reset(); +#else + storage = intrusive_ptr<storage_t>(); +#endif + } else { + if (! storage || storage->refc > 1) + storage = new storage_t; + else + storage->destroy(); + storage->type = new_type; + } +} + +bool value_t::to_boolean() const +{ + if (is_boolean()) { + return as_boolean(); + } else { + value_t temp(*this); + temp.in_place_cast(BOOLEAN); + return temp.as_boolean(); + } +} + +datetime_t value_t::to_datetime() const +{ + if (is_datetime()) { + return as_datetime(); + } else { + value_t temp(*this); + temp.in_place_cast(DATETIME); + return temp.as_datetime(); + } +} + +date_t value_t::to_date() const +{ + if (is_date()) { + return as_date(); + } else { + value_t temp(*this); + temp.in_place_cast(DATE); + return temp.as_date(); + } +} + +int value_t::to_int() const +{ + if (is_long()) { + return static_cast<int>(as_long()); + } else { + value_t temp(*this); + temp.in_place_cast(INTEGER); + return static_cast<int>(temp.as_long()); + } +} + +long value_t::to_long() const +{ + if (is_long()) { + return as_long(); + } else { + value_t temp(*this); + temp.in_place_cast(INTEGER); + return temp.as_long(); + } +} + +amount_t value_t::to_amount() const +{ + if (is_amount()) { + return as_amount(); + } else { + value_t temp(*this); + temp.in_place_cast(AMOUNT); + return temp.as_amount(); + } +} + +balance_t value_t::to_balance() const +{ + if (is_balance()) { + return as_balance(); + } else { + value_t temp(*this); + temp.in_place_cast(BALANCE); + return temp.as_balance(); + } +} + +string value_t::to_string() const +{ + if (is_string()) { + return as_string(); + } else { + value_t temp(*this); + temp.in_place_cast(STRING); + return temp.as_string(); + } +} + +mask_t value_t::to_mask() const +{ + if (is_mask()) { + return as_mask(); + } else { + value_t temp(*this); + temp.in_place_cast(MASK); + return temp.as_mask(); + } +} + +value_t::sequence_t value_t::to_sequence() const +{ + if (is_sequence()) { + return as_sequence(); + } else { + value_t temp(*this); + temp.in_place_cast(SEQUENCE); + return temp.as_sequence(); + } +} + + +void value_t::in_place_simplify() +{ +#if defined(DEBUG_ON) + LOGGER("value.simplify"); +#endif + + if (is_realzero()) { + DEBUG_("Zeroing type " << static_cast<int>(type())); + set_long(0L); + return; + } + + if (is_balance() && as_balance().single_amount()) { + DEBUG_("Reducing balance to amount"); + DEBUG_("as a balance it looks like: " << *this); + in_place_cast(AMOUNT); + DEBUG_("as an amount it looks like: " << *this); + } + +#ifdef REDUCE_TO_INTEGER // this is off by default + if (is_amount() && ! as_amount().has_commodity() && + as_amount().fits_in_long()) { + DEBUG_("Reducing amount to integer"); + in_place_cast(INTEGER); + } +#endif +} + +value_t value_t::number() const +{ + switch (type()) { + case VOID: + return 0L; + case BOOLEAN: + return as_boolean() ? 1L : 0L; + case INTEGER: + return as_long(); + case AMOUNT: + return as_amount().number(); + case BALANCE: + return as_balance().number(); + case SEQUENCE: + if (! as_sequence().empty()) { + value_t temp; + foreach (const value_t& value, as_sequence()) + temp += value.number(); + return temp; + } + break; + default: + break; + } + + throw_(value_error, _("Cannot determine numeric value of %1") << label()); + return false; +} + +value_t& value_t::operator+=(const value_t& val) +{ + if (is_string()) { + if (val.is_string()) + as_string_lval() += val.as_string(); + else + as_string_lval() += val.to_string(); + return *this; + } + else if (is_sequence()) { + if (val.is_sequence()) { + if (size() == val.size()) { + sequence_t::iterator i = begin(); + sequence_t::const_iterator j = val.begin(); + + for (; i != end(); i++, j++) + *i += *j; + } else { + throw_(value_error, _("Cannot add sequences of different lengths")); + } + } else { + as_sequence_lval().push_back(val); + } + return *this; + } + + switch (type()) { + case DATETIME: + switch (val.type()) { + case INTEGER: + as_datetime_lval() += + time_duration_t(0, 0, static_cast<time_duration_t::sec_type>(val.as_long())); + return *this; + case AMOUNT: + as_datetime_lval() += + time_duration_t(0, 0, static_cast<time_duration_t::sec_type> + (val.as_amount().to_long())); + return *this; + default: + break; + } + break; + + case DATE: + switch (val.type()) { + case INTEGER: + as_date_lval() += gregorian::date_duration(val.as_long()); + return *this; + case AMOUNT: + as_date_lval() += gregorian::date_duration(val.as_amount().to_long()); + return *this; + default: + break; + } + break; + + case INTEGER: + switch (val.type()) { + case INTEGER: + as_long_lval() += val.as_long(); + return *this; + case AMOUNT: + if (val.as_amount().has_commodity()) { + in_place_cast(BALANCE); + return *this += val; + } + in_place_cast(AMOUNT); + as_amount_lval() += val.as_amount(); + return *this; + case BALANCE: + in_place_cast(BALANCE); + as_balance_lval() += val.as_balance(); + return *this; + default: + break; + } + break; + + case AMOUNT: + switch (val.type()) { + case INTEGER: + if (as_amount().has_commodity()) { + in_place_cast(BALANCE); + return *this += val; + } else { + as_amount_lval() += val.as_long(); + return *this; + } + break; + + case AMOUNT: + if (as_amount().commodity() != val.as_amount().commodity()) { + in_place_cast(BALANCE); + return *this += val; + } else { + as_amount_lval() += val.as_amount(); + return *this; + } + break; + + case BALANCE: + in_place_cast(BALANCE); + as_balance_lval() += val.as_balance(); + return *this; + + default: + break; + } + break; + + case BALANCE: + switch (val.type()) { + case INTEGER: + as_balance_lval() += val.to_amount(); + return *this; + case AMOUNT: + as_balance_lval() += val.as_amount(); + return *this; + case BALANCE: + as_balance_lval() += val.as_balance(); + return *this; + default: + break; + } + break; + + default: + break; + } + + throw_(value_error, _("Cannot add %1 to %2") << val.label() << label()); + return *this; +} + +value_t& value_t::operator-=(const value_t& val) +{ + if (is_sequence()) { + sequence_t& seq(as_sequence_lval()); + + if (val.is_sequence()) { + if (size() == val.size()) { + sequence_t::iterator i = begin(); + sequence_t::const_iterator j = val.begin(); + + for (; i != end(); i++, j++) + *i -= *j; + } else { + throw_(value_error, _("Cannot subtract sequences of different lengths")); + } + } else { + sequence_t::iterator i = std::find(seq.begin(), seq.end(), val); + if (i != seq.end()) + seq.erase(i); + } + return *this; + } + + switch (type()) { + case DATETIME: + switch (val.type()) { + case INTEGER: + as_datetime_lval() -= + time_duration_t(0, 0, static_cast<time_duration_t::sec_type>(val.as_long())); + return *this; + case AMOUNT: + as_datetime_lval() -= + time_duration_t(0, 0, static_cast<time_duration_t::sec_type> + (val.as_amount().to_long())); + return *this; + default: + break; + } + break; + + case DATE: + switch (val.type()) { + case INTEGER: + as_date_lval() -= gregorian::date_duration(val.as_long()); + return *this; + case AMOUNT: + as_date_lval() -= gregorian::date_duration(val.as_amount().to_long()); + return *this; + default: + break; + } + break; + + case INTEGER: + switch (val.type()) { + case INTEGER: + as_long_lval() -= val.as_long(); + return *this; + case AMOUNT: + in_place_cast(AMOUNT); + as_amount_lval() -= val.as_amount(); + in_place_simplify(); + return *this; + case BALANCE: + in_place_cast(BALANCE); + as_balance_lval() -= val.as_balance(); + in_place_simplify(); + return *this; + default: + break; + } + break; + + case AMOUNT: + switch (val.type()) { + case INTEGER: + if (as_amount().has_commodity()) { + in_place_cast(BALANCE); + *this -= val; + in_place_simplify(); + return *this; + } else { + as_amount_lval() -= val.as_long(); + in_place_simplify(); + return *this; + } + break; + + case AMOUNT: + if (as_amount().commodity() != val.as_amount().commodity()) { + in_place_cast(BALANCE); + *this -= val; + in_place_simplify(); + return *this; + } else { + as_amount_lval() -= val.as_amount(); + in_place_simplify(); + return *this; + } + break; + + case BALANCE: + in_place_cast(BALANCE); + as_balance_lval() -= val.as_balance(); + in_place_simplify(); + return *this; + + default: + break; + } + break; + + case BALANCE: + switch (val.type()) { + case INTEGER: + as_balance_lval() -= val.to_amount(); + in_place_simplify(); + return *this; + case AMOUNT: + as_balance_lval() -= val.as_amount(); + in_place_simplify(); + return *this; + case BALANCE: + as_balance_lval() -= val.as_balance(); + in_place_simplify(); + return *this; + default: + break; + } + break; + + default: + break; + } + + throw_(value_error, _("Cannot subtract %1 from %2") << val.label() << label()); + + return *this; +} + +value_t& value_t::operator*=(const value_t& val) +{ + if (is_string()) { + string temp; + long count = val.to_long(); + for (long i = 0; i < count; i++) + temp += as_string(); + set_string(temp); + return *this; + } + else if (is_sequence()) { + value_t temp; + long count = val.to_long(); + for (long i = 0; i < count; i++) + temp += as_sequence(); + return *this = temp; + } + + switch (type()) { + case INTEGER: + switch (val.type()) { + case INTEGER: + as_long_lval() *= val.as_long(); + return *this; + case AMOUNT: + set_amount(val.as_amount() * as_long()); + return *this; + default: + break; + } + break; + + case AMOUNT: + switch (val.type()) { + case INTEGER: + as_amount_lval() *= val.as_long(); + return *this; + case AMOUNT: + as_amount_lval() *= val.as_amount(); + return *this; + case BALANCE: + if (val.as_balance().single_amount()) { + as_amount_lval() *= val.simplified().as_amount(); + return *this; + } + break; + default: + break; + } + break; + + case BALANCE: + switch (val.type()) { + case INTEGER: + as_balance_lval() *= val.as_long(); + return *this; + case AMOUNT: + if (as_balance().single_amount()) { + in_place_simplify(); + as_amount_lval() *= val.as_amount(); + return *this; + } + else if (! val.as_amount().has_commodity()) { + as_balance_lval() *= val.as_amount(); + return *this; + } + break; + default: + break; + } + break; + + default: + break; + } + + DEBUG("value.multiply.error", "Left: " << *this); + DEBUG("value.multiply.error", "Right: " << val); + + throw_(value_error, _("Cannot multiply %1 with %2") << label() << val.label()); + + return *this; +} + +value_t& value_t::operator/=(const value_t& val) +{ + switch (type()) { + case INTEGER: + switch (val.type()) { + case INTEGER: + as_long_lval() /= val.as_long(); + return *this; + case AMOUNT: + set_amount(val.as_amount() / as_long()); + return *this; + default: + break; + } + break; + + case AMOUNT: + switch (val.type()) { + case INTEGER: + as_amount_lval() /= val.as_long(); + return *this; + + case AMOUNT: + as_amount_lval() /= val.as_amount(); + return *this; + case BALANCE: + if (val.as_balance().single_amount()) { + value_t simpler(val.simplified()); + switch (simpler.type()) { + case INTEGER: + as_amount_lval() /= simpler.as_long(); + break; + case AMOUNT: + as_amount_lval() /= simpler.as_amount(); + break; + default: + assert(false); + break; + } + return *this; + } + break; + default: + break; + } + break; + + case BALANCE: + switch (val.type()) { + case INTEGER: + as_balance_lval() /= val.as_long(); + return *this; + case AMOUNT: + if (as_balance().single_amount()) { + in_place_simplify(); + as_amount_lval() /= val.as_amount(); + return *this; + } + else if (! val.as_amount().has_commodity()) { + as_balance_lval() /= val.as_amount(); + return *this; + } + break; + default: + break; + } + break; + + default: + break; + } + + throw_(value_error, _("Cannot divide %1 by %2") << label() << val.label()); + + return *this; +} + + +bool value_t::is_equal_to(const value_t& val) const +{ + switch (type()) { + case VOID: + return val.type() == VOID; + + case BOOLEAN: + if (val.is_boolean()) + return as_boolean() == val.as_boolean(); + break; + + case DATETIME: + if (val.is_datetime()) + return as_datetime() == val.as_datetime(); + break; + + case DATE: + if (val.is_date()) + return as_date() == val.as_date(); + break; + + case INTEGER: + switch (val.type()) { + case INTEGER: + return as_long() == val.as_long(); + case AMOUNT: + return val.as_amount() == to_amount(); + case BALANCE: + return val.as_balance() == to_amount(); + default: + break; + } + break; + + case AMOUNT: + switch (val.type()) { + case INTEGER: + return as_amount() == val.as_long(); + case AMOUNT: + return as_amount() == val.as_amount(); + case BALANCE: + return val.as_balance() == as_amount(); + default: + break; + } + break; + + case BALANCE: + switch (val.type()) { + case INTEGER: + return as_balance() == val.to_amount(); + case AMOUNT: + return as_balance() == val.as_amount(); + case BALANCE: + return as_balance() == val.as_balance(); + default: + break; + } + break; + + case STRING: + if (val.is_string()) + return as_string() == val.as_string(); + break; + + case MASK: + if (val.is_mask()) + return as_mask() == val.as_mask(); + break; + + case SEQUENCE: + if (val.is_sequence()) + return as_sequence() == val.as_sequence(); + break; + + default: + break; + } + + throw_(value_error, _("Cannot compare %1 by %2") << label() << val.label()); + + return *this; +} + +bool value_t::is_less_than(const value_t& val) const +{ + switch (type()) { + case DATETIME: + if (val.is_datetime()) + return as_datetime() < val.as_datetime(); + break; + + case DATE: + if (val.is_date()) + return as_date() < val.as_date(); + break; + + case INTEGER: + switch (val.type()) { + case INTEGER: + return as_long() < val.as_long(); + case AMOUNT: + return val.as_amount() >= as_long(); + default: + break; + } + break; + + case AMOUNT: + switch (val.type()) { + case INTEGER: + return as_amount() < val.as_long(); + case AMOUNT: + try { + return as_amount() < val.as_amount(); + } + catch (const amount_error&) { + return compare_amount_commodities()(&as_amount(), &val.as_amount()); + } + default: + break; + } + break; + + case BALANCE: + switch (val.type()) { + case INTEGER: + case AMOUNT: { + if (val.is_nonzero()) + break; + + bool no_amounts = true; + foreach (const balance_t::amounts_map::value_type& pair, + as_balance().amounts) { + if (pair.second >= 0L) + return false; + no_amounts = false; + } + return ! no_amounts; + } + default: + break; + } + break; + + case STRING: + if (val.is_string()) + return as_string() < val.as_string(); + break; + + case SEQUENCE: + switch (val.type()) { + case INTEGER: + case AMOUNT: { + if (val.is_nonzero()) + break; + + bool no_amounts = true; + foreach (const value_t& value, as_sequence()) { + if (value >= 0L) + return false; + no_amounts = false; + } + return ! no_amounts; + } + default: + break; + } + break; + + default: + break; + } + + throw_(value_error, _("Cannot compare %1 to %2") << label() << val.label()); + + return *this; +} + +bool value_t::is_greater_than(const value_t& val) const +{ + switch (type()) { + case DATETIME: + if (val.is_datetime()) + return as_datetime() > val.as_datetime(); + break; + + case DATE: + if (val.is_date()) + return as_date() > val.as_date(); + break; + + case INTEGER: + switch (val.type()) { + case INTEGER: + return as_long() > val.as_long(); + case AMOUNT: + return val.as_amount() > as_long(); + default: + break; + } + break; + + case AMOUNT: + switch (val.type()) { + case INTEGER: + return as_amount() > val.as_long(); + case AMOUNT: + return as_amount() > val.as_amount(); + default: + break; + } + break; + + case BALANCE: + switch (val.type()) { + case INTEGER: + case AMOUNT: { + if (val.is_nonzero()) + break; + + bool no_amounts = true; + foreach (const balance_t::amounts_map::value_type& pair, + as_balance().amounts) { + if (pair.second <= 0L) + return false; + no_amounts = false; + } + return ! no_amounts; + } + default: + break; + } + break; + + case STRING: + if (val.is_string()) + return as_string() > val.as_string(); + break; + + case SEQUENCE: + switch (val.type()) { + case INTEGER: + case AMOUNT: { + if (val.is_nonzero()) + break; + + bool no_amounts = true; + foreach (const value_t& value, as_sequence()) { + if (value <= 0L) + return false; + no_amounts = false; + } + return ! no_amounts; + } + default: + break; + } + break; + + default: + break; + } + + throw_(value_error, _("Cannot compare %1 to %2") << label() << val.label()); + + return *this; +} + +void value_t::in_place_cast(type_t cast_type) +{ + if (type() == cast_type) + return; + + _dup(); + + if (cast_type == BOOLEAN) { + set_boolean(bool(*this)); + return; + } + else if (cast_type == SEQUENCE) { + sequence_t temp; + if (! is_null()) + temp.push_back(*this); + set_sequence(temp); + return; + } + + switch (type()) { + case BOOLEAN: + switch (cast_type) { + case AMOUNT: + set_amount(as_boolean() ? 1L : 0L); + return; + case STRING: + set_string(as_boolean() ? "true" : "false"); + return; + default: + break; + } + break; + + case DATE: + switch (cast_type) { + case DATETIME: + set_datetime(datetime_t(as_date(), time_duration(0, 0, 0, 0))); + return; + case STRING: + set_string(format_date(as_date(), FMT_WRITTEN)); + return; + default: + break; + } + break; + case DATETIME: + switch (cast_type) { + case DATE: + set_date(as_datetime().date()); + return; + case STRING: + set_string(format_datetime(as_datetime(), FMT_WRITTEN)); + return; + default: + break; + } + break; + + case INTEGER: + switch (cast_type) { + case AMOUNT: + set_amount(as_long()); + return; + case BALANCE: + set_balance(to_amount()); + return; + case STRING: + set_string(lexical_cast<string>(as_long())); + return; + default: + break; + } + break; + + case AMOUNT: { + const amount_t& amt(as_amount()); + switch (cast_type) { + case INTEGER: + if (amt.is_null()) + set_long(0L); + else + set_long(as_amount().to_long()); + return; + case BALANCE: + if (amt.is_null()) + set_balance(balance_t()); + else + set_balance(as_amount()); + return; + case STRING: + if (amt.is_null()) + set_string(""); + else + set_string(as_amount().to_string()); + return; + default: + break; + } + break; + } + + case BALANCE: { + const balance_t& bal(as_balance()); + switch (cast_type) { + case AMOUNT: { + if (bal.amounts.size() == 1) { + // Because we are changing the current balance value to an amount + // value, and because set_amount takes a reference (and that memory is + // about to be repurposed), we must pass in a copy. + set_amount(amount_t((*bal.amounts.begin()).second)); + return; + } + else if (bal.amounts.size() == 0) { + set_amount(0L); + return; + } + else { + throw_(value_error, _("Cannot convert %1 with multiple commodities to %2") + << label() << label(cast_type)); + } + break; + } + case STRING: + if (bal.is_empty()) + set_string(""); + else + set_string(as_balance().to_string()); + return; + default: + break; + } + break; + } + + case STRING: + switch (cast_type) { + case INTEGER: { + if (all(as_string(), is_digit())) { + set_long(lexical_cast<long>(as_string())); + return; + } + break; + } + case AMOUNT: + set_amount(amount_t(as_string())); + return; + case DATE: + set_date(parse_date(as_string())); + return; + case DATETIME: + set_datetime(parse_datetime(as_string())); + return; + case MASK: + set_mask(as_string()); + return; + default: + break; + } + break; + + default: + break; + } + + throw_(value_error, + _("Cannot convert %1 to %2") << label() << label(cast_type)); +} + +void value_t::in_place_negate() +{ + switch (type()) { + case BOOLEAN: + set_boolean(! as_boolean()); + return; + case INTEGER: + case DATETIME: + set_long(- as_long()); + return; + case DATE: + set_long(- as_long()); + return; + case AMOUNT: + as_amount_lval().in_place_negate(); + return; + case BALANCE: + as_balance_lval().in_place_negate(); + return; + case SEQUENCE: { + value_t temp; + foreach (const value_t& value, as_sequence()) + temp.push_back(- value); + *this = temp; + return; + } + default: + break; + } + + throw_(value_error, _("Cannot negate %1") << label()); +} + +void value_t::in_place_not() +{ + switch (type()) { + case BOOLEAN: + set_boolean(! as_boolean()); + return; + case INTEGER: + case DATETIME: + set_boolean(! as_long()); + return; + case DATE: + set_boolean(! as_long()); + return; + case AMOUNT: + set_boolean(! as_amount()); + return; + case BALANCE: + set_boolean(! as_balance()); + return; + case STRING: + set_boolean(as_string().empty()); + return; + case SEQUENCE: { + value_t temp; + foreach (const value_t& value, as_sequence()) + temp.push_back(! value); + *this = temp; + return; + } + default: + break; + } + + throw_(value_error, _("Cannot 'not' %1") << label()); +} + +bool value_t::is_realzero() const +{ + switch (type()) { + case BOOLEAN: + return ! as_boolean(); + case INTEGER: + return as_long() == 0; + case DATETIME: + return ! is_valid(as_datetime()); + case DATE: + return ! is_valid(as_date()); + case AMOUNT: + return as_amount().is_realzero(); + case BALANCE: + return as_balance().is_realzero(); + case STRING: + return as_string().empty(); + case SEQUENCE: + return as_sequence().empty(); + + case SCOPE: + return as_scope() == NULL; + + default: + throw_(value_error, _("Cannot determine if %1 is really zero") << label()); + } + return false; +} + +bool value_t::is_zero() const +{ + switch (type()) { + case BOOLEAN: + return ! as_boolean(); + case INTEGER: + return as_long() == 0; + case DATETIME: + return ! is_valid(as_datetime()); + case DATE: + return ! is_valid(as_date()); + case AMOUNT: + return as_amount().is_zero(); + case BALANCE: + return as_balance().is_zero(); + case STRING: + return as_string().empty(); + case SEQUENCE: + return as_sequence().empty(); + + case SCOPE: + return as_scope() == NULL; + + default: + throw_(value_error, _("Cannot determine if %1 is zero") << label()); + } + return false; +} + +value_t value_t::value(const bool primary_only, + const optional<datetime_t>& moment, + const optional<commodity_t&>& in_terms_of) const +{ + switch (type()) { + case INTEGER: + return NULL_VALUE; + + case AMOUNT: + if (optional<amount_t> val = + as_amount().value(primary_only, moment, in_terms_of)) + return *val; + return NULL_VALUE; + + case BALANCE: + if (optional<balance_t> bal = + as_balance().value(primary_only, moment, in_terms_of)) + return *bal; + return NULL_VALUE; + + default: + break; + } + + throw_(value_error, _("Cannot find the value of %1") << label()); + return NULL_VALUE; +} + +value_t value_t::price() const +{ + switch (type()) { + case AMOUNT: + return as_amount().price(); + case BALANCE: + return as_balance().price(); + default: + return *this; + } +} + +value_t value_t::exchange_commodities(const std::string& commodities, + const bool add_prices, + const optional<datetime_t>& moment) +{ + scoped_array<char> buf(new char[commodities.length() + 1]); + + std::strcpy(buf.get(), commodities.c_str()); + + for (char * p = std::strtok(buf.get(), ","); + p; + p = std::strtok(NULL, ",")) { + if (commodity_t * commodity = + commodity_pool_t::current_pool->parse_price_expression(p, add_prices, + moment)) { + value_t result = value(false, moment, *commodity); + if (! result.is_null()) + return result; + } + } + return *this; +} + +void value_t::in_place_reduce() +{ + switch (type()) { + case AMOUNT: + as_amount_lval().in_place_reduce(); + return; + case BALANCE: + as_balance_lval().in_place_reduce(); + return; + default: + return; + } + + //throw_(value_error, "Cannot reduce " << label()); +} + +void value_t::in_place_unreduce() +{ + switch (type()) { + case AMOUNT: + as_amount_lval().in_place_unreduce(); + return; + case BALANCE: + as_balance_lval().in_place_unreduce(); + return; + default: + return; + } + + //throw_(value_error, "Cannot reduce " << label()); +} + +value_t value_t::abs() const +{ + switch (type()) { + case INTEGER: { + long val = as_long(); + if (val < 0) + return - val; + return val; + } + case AMOUNT: + return as_amount().abs(); + case BALANCE: + return as_balance().abs(); + default: + break; + } + + throw_(value_error, _("Cannot abs %1") << label()); + return NULL_VALUE; +} + +void value_t::in_place_round() +{ + switch (type()) { + case INTEGER: + return; + case AMOUNT: + as_amount_lval().in_place_round(); + return; + case BALANCE: + as_balance_lval().in_place_round(); + return; + case SEQUENCE: { + value_t temp; + foreach (const value_t& value, as_sequence()) + temp.push_back(value.rounded()); + *this = temp; + return; + } + default: + break; + } + + throw_(value_error, _("Cannot set rounding for %1") << label()); +} + +void value_t::in_place_truncate() +{ + switch (type()) { + case INTEGER: + return; + case AMOUNT: + as_amount_lval().in_place_truncate(); + return; + case BALANCE: + as_balance_lval().in_place_truncate(); + return; + case SEQUENCE: { + value_t temp; + foreach (const value_t& value, as_sequence()) + temp.push_back(value.truncated()); + *this = temp; + return; + } + default: + break; + } + + throw_(value_error, _("Cannot truncate %1") << label()); +} + +void value_t::in_place_floor() +{ + switch (type()) { + case INTEGER: + return; + case AMOUNT: + as_amount_lval().in_place_floor(); + return; + case BALANCE: + as_balance_lval().in_place_floor(); + return; + case SEQUENCE: { + value_t temp; + foreach (const value_t& value, as_sequence()) + temp.push_back(value.floored()); + *this = temp; + return; + } + default: + break; + } + + throw_(value_error, _("Cannot floor %1") << label()); +} + +void value_t::in_place_unround() +{ + switch (type()) { + case INTEGER: + return; + case AMOUNT: + as_amount_lval().in_place_unround(); + return; + case BALANCE: + as_balance_lval().in_place_unround(); + return; + case SEQUENCE: { + value_t temp; + foreach (const value_t& value, as_sequence()) + temp.push_back(value.unrounded()); + *this = temp; + return; + } + default: + break; + } + + throw_(value_error, _("Cannot unround %1") << label()); +} + +void value_t::annotate(const annotation_t& details) +{ + if (is_amount()) + as_amount_lval().annotate(details); + else + throw_(value_error, _("Cannot annotate %1") << label()); +} + +bool value_t::has_annotation() const +{ + if (is_amount()) + return as_amount().has_annotation(); + else + throw_(value_error, + _("Cannot determine whether %1 is annotated") << label()); + return false; +} + +annotation_t& value_t::annotation() +{ + if (is_amount()) + return as_amount_lval().annotation(); + else { + throw_(value_error, _("Cannot request annotation of %1") << label()); + return as_amount_lval().annotation(); // quiet g++ warning + } +} + +value_t value_t::strip_annotations(const keep_details_t& what_to_keep) const +{ + if (what_to_keep.keep_all()) + return *this; + + switch (type()) { + case VOID: + case BOOLEAN: + case INTEGER: + case DATETIME: + case DATE: + case STRING: + case MASK: + case SCOPE: + return *this; + + case SEQUENCE: { + sequence_t temp; + foreach (const value_t& value, as_sequence()) + temp.push_back(value.strip_annotations(what_to_keep)); + return temp; + } + + case AMOUNT: + return as_amount().strip_annotations(what_to_keep); + case BALANCE: + return as_balance().strip_annotations(what_to_keep); + + default: + assert(false); + break; + } + assert(false); + return NULL_VALUE; +} + +void value_t::print(std::ostream& out, + const int first_width, + const int latter_width, + const bool right_justify, + const bool colorize) const +{ + if (first_width > 0 && + (! is_amount() || as_amount().is_zero()) && + ! is_balance() && ! is_string()) { + out.width(first_width); + + if (right_justify) + out << std::right; + else + out << std::left; + } + + switch (type()) { + case VOID: + out << ""; + break; + + case BOOLEAN: + out << (as_boolean() ? "1" : "0"); + break; + + case DATETIME: + out << format_datetime(as_datetime(), FMT_WRITTEN); + break; + + case DATE: + out << format_date(as_date(), FMT_WRITTEN); + break; + + case INTEGER: + if (colorize && as_long() < 0) + justify(out, to_string(), first_width, right_justify, true); + else + out << as_long(); + break; + + case AMOUNT: { + if (as_amount().is_zero()) { + out << 0; + } else { + std::ostringstream buf; + buf << as_amount(); + justify(out, buf.str(), first_width, right_justify, + colorize && as_amount().sign() < 0); + } + break; + } + + case BALANCE: + as_balance().print(out, first_width, latter_width, right_justify, + colorize); + break; + + case STRING: + if (first_width > 0) + justify(out, as_string(), first_width, right_justify); + else + out << as_string(); + break; + + case MASK: + out << '/' << as_mask() << '/'; + break; + + case SEQUENCE: { + out << '('; + bool first = true; + foreach (const value_t& value, as_sequence()) { + if (first) + first = false; + else + out << ", "; + + value.print(out, first_width, latter_width, right_justify, + colorize); + } + out << ')'; + break; + } + + case SCOPE: + out << "<SCOPE>"; + break; + + default: + throw_(value_error, _("Cannot print %1") << label()); + } +} + +void value_t::dump(std::ostream& out, const bool relaxed) const +{ + switch (type()) { + case VOID: + out << "null"; + break; + + case BOOLEAN: + if (as_boolean()) + out << "true"; + else + out << "false"; + break; + + case DATETIME: + out << '[' << format_datetime(as_datetime(), FMT_WRITTEN) << ']'; + break; + case DATE: + out << '[' << format_date(as_date(), FMT_WRITTEN) << ']'; + break; + + case INTEGER: + out << as_long(); + break; + + case AMOUNT: + if (! relaxed) + out << '{'; + out << as_amount(); + if (! relaxed) + out << '}'; + break; + + case BALANCE: + out << as_balance(); + break; + + case STRING: + out << '"'; + foreach (const char& ch, as_string()) { + switch (ch) { + case '"': + out << "\\\""; + break; + case '\\': + out << "\\\\"; + break; + default: + out << ch; + break; + } + } + out << '"'; + break; + + case MASK: + out << '/' << as_mask() << '/'; + break; + + case SCOPE: + out << as_scope(); + break; + + case SEQUENCE: { + out << '('; + bool first = true; + foreach (const value_t& value, as_sequence()) { + if (first) + first = false; + else + out << ", "; + + value.dump(out, relaxed); + } + out << ')'; + break; + } + + default: + assert(false); + break; + } +} + +bool value_t::valid() const +{ + switch (type()) { + case AMOUNT: + return as_amount().valid(); + case BALANCE: + return as_balance().valid(); + default: + break; + } + return true; +} + +bool sort_value_is_less_than(const std::list<sort_value_t>& left_values, + const std::list<sort_value_t>& right_values) +{ + std::list<sort_value_t>::const_iterator left_iter = left_values.begin(); + std::list<sort_value_t>::const_iterator right_iter = right_values.begin(); + + while (left_iter != left_values.end() && right_iter != right_values.end()) { + // Don't even try to sort balance values + if (! (*left_iter).value.is_balance() && + ! (*right_iter).value.is_balance()) { + DEBUG("value.sort", + " Comparing " << (*left_iter).value << " < " << (*right_iter).value); + if ((*left_iter).value < (*right_iter).value) { + DEBUG("value.sort", " is less"); + return ! (*left_iter).inverted; + } + else if ((*left_iter).value > (*right_iter).value) { + DEBUG("value.sort", " is greater"); + return (*left_iter).inverted; + } + } + left_iter++; right_iter++; + } + + assert(left_iter == left_values.end()); + assert(right_iter == right_values.end()); + + return false; +} + +void to_xml(std::ostream& out, const value_t& value) +{ + switch (value.type()) { + case value_t::VOID: + out << "<void />"; + break; + case value_t::BOOLEAN: { + push_xml y(out, "boolean"); + out << (value.as_boolean() ? "true" : "false"); + break; + } + case value_t::INTEGER: { + push_xml y(out, "integer"); + out << value.as_long(); + break; + } + + case value_t::AMOUNT: + to_xml(out, value.as_amount()); + break; + case value_t::BALANCE: + to_xml(out, value.as_balance()); + break; + + case value_t::DATETIME: + to_xml(out, value.as_datetime()); + break; + case value_t::DATE: + to_xml(out, value.as_date()); + break; + case value_t::STRING: { + push_xml y(out, "string"); + out << y.guard(value.as_string()); + break; + } + case value_t::MASK: + to_xml(out, value.as_mask()); + break; + + case value_t::SEQUENCE: { + push_xml y(out, "sequence"); + foreach (const value_t& member, value.as_sequence()) + to_xml(out, member); + break; + } + + case value_t::SCOPE: + default: + assert(false); + break; + } +} + +} // namespace ledger diff --git a/src/numerics/value.h b/src/value.h index 580b775d..ffbb89e8 100644 --- a/src/numerics/value.h +++ b/src/value.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2007, John Wiegley. All rights reserved. + * 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 @@ -30,11 +30,16 @@ */ /** + * @addtogroup math + */ + +/** * @file value.h * @author John Wiegley - * @date Thu Jun 14 21:54:00 2007 * - * @brief Abstract dynamic type representing various numeric types. + * @ingroup math + * + * @brief Abstract dynamic type representing various numeric types * * A value_t object can be one of many types, and changes its type * dynamically based on how it is used. For example, if you assign @@ -44,13 +49,14 @@ #ifndef _VALUE_H #define _VALUE_H -#include "balpair.h" // pulls in balance.h and amount.h +#include "balance.h" // includes amount.h +#include "mask.h" namespace ledger { -namespace xml { - class node_t; -} +DECLARE_EXCEPTION(value_error, std::runtime_error); + +class scope_t; /** * @class value_t @@ -68,24 +74,20 @@ namespace xml { */ class value_t : public ordered_field_operators<value_t, - equality_comparable<value_t, balance_pair_t, equality_comparable<value_t, balance_t, - additive<value_t, balance_pair_t, additive<value_t, balance_t, - multiplicative<value_t, balance_pair_t, multiplicative<value_t, balance_t, ordered_field_operators<value_t, amount_t, ordered_field_operators<value_t, double, ordered_field_operators<value_t, unsigned long, - ordered_field_operators<value_t, long> > > > > > > > > > > + ordered_field_operators<value_t, long> > > > > > > > { public: /** * The sequence_t member type abstracts the type used to represent a * resizable "array" of value_t objects. */ - typedef std::vector<value_t> sequence_t; - + typedef std::deque<value_t> sequence_t; typedef sequence_t::iterator iterator; typedef sequence_t::const_iterator const_iterator; typedef sequence_t::difference_type difference_type; @@ -99,14 +101,14 @@ public: VOID, // a null value (i.e., uninitialized) BOOLEAN, // a boolean DATETIME, // a date and time (Boost posix_time) + DATE, // a date (Boost gregorian::date) INTEGER, // a signed integer value AMOUNT, // a ledger::amount_t BALANCE, // a ledger::balance_t - BALANCE_PAIR, // a ledger::balance_pair_t STRING, // a string object + MASK, // a regular expression mask SEQUENCE, // a vector of value_t objects - XML_NODE, // a pointer to an ledger::xml::node_t - POINTER // an opaque pointer of any type + SCOPE // a pointer to a scope }; private: @@ -124,7 +126,18 @@ private: * The `type' member holds the value_t::type_t value representing * the type of the object stored. */ - char data[sizeof(amount_t)]; + variant<bool, // BOOLEAN + datetime_t, // DATETIME + date_t, // DATE + long, // INTEGER + amount_t, // AMOUNT + balance_t *, // BALANCE + string, // STRING + mask_t, // MASK + sequence_t *, // SEQUENCE + scope_t * // SCOPE + > data; + type_t type; /** @@ -154,13 +167,10 @@ private: */ ~storage_t() { TRACE_DTOR(value_t::storage_t); - DEBUG("value.storage.refcount", "Destroying " << this); - assert(refc == 0); + VERIFY(refc == 0); destroy(); } - void destroy(); - private: /** * Assignment and copy operators. These are called when making a @@ -168,14 +178,10 @@ private: */ explicit storage_t(const storage_t& rhs) : type(rhs.type), refc(0) { - TRACE_CTOR(value_t::storage_t, ""); - std::memcpy(data, rhs.data, sizeof(data)); - } - storage_t& operator=(const storage_t& rhs) { - type = rhs.type; - std::memcpy(data, rhs.data, sizeof(data)); - return *this; + TRACE_CTOR(value_t::storage_t, "copy"); + *this = rhs; } + storage_t& operator=(const storage_t& rhs); /** * Reference counting methods. The intrusive_ptr_* methods are @@ -185,13 +191,13 @@ private: void acquire() const { DEBUG("value.storage.refcount", "Acquiring " << this << ", refc now " << refc + 1); - assert(refc >= 0); + VERIFY(refc >= 0); refc++; } void release() const { DEBUG("value.storage.refcount", "Releasing " << this << ", refc now " << refc - 1); - assert(refc > 0); + VERIFY(refc > 0); if (--refc == 0) checked_delete(this); } @@ -202,47 +208,65 @@ private: friend inline void intrusive_ptr_release(value_t::storage_t * storage) { storage->release(); } + + void destroy() { + DEBUG("value.storage.refcount", "Destroying " << this); + switch (type) { + case VOID: + return; + case BALANCE: + checked_delete(boost::get<balance_t *>(data)); + break; + case SEQUENCE: + checked_delete(boost::get<sequence_t *>(data)); + break; + default: + break; + } + data = false; + type = VOID; + } + +#if defined(HAVE_BOOST_SERIALIZATION) + private: + /** Serialization. */ + + friend class boost::serialization::access; + + template<class Archive> + void serialize(Archive& ar, const unsigned int /* version */) { + ar & data; + ar & type; + ar & refc; + } +#endif // HAVE_BOOST_SERIALIZATION }; /** - * The actual data for each value_t is kept in the `storage' member. + * The actual data for each value_t is kept in reference counted storage. * Data is modified using a copy-on-write policy. */ intrusive_ptr<storage_t> storage; /** - * _dup() makes a private copy of the current value so that it can + * Make a private copy of the current value (if necessary) so it can * subsequently be modified. - * - * _clear() removes our pointer to the current value and initializes - * a new value for things to be stored in. - * - * _reset() makes the current object appear as if it had been - * default initialized. */ - void _dup(); - void _clear() { - if (! storage || storage->refc > 1) - storage = new storage_t; - else - storage->destroy(); - } - void _reset() { - if (storage) - storage = intrusive_ptr<storage_t>(); + void _dup() { + VERIFY(storage); + if (storage->refc > 1) + storage = new storage_t(*storage.get()); } /** - * Because boolean "true" and "false" are so common, a pair of - * static references are kept to prevent the creation of throwaway - * storage_t objects just to represent these two common values. + * Because boolean "true" and "false" are so common, a pair of static + * references are kept to prevent the creation of throwaway storage_t + * objects just to represent these two common values. */ static intrusive_ptr<storage_t> true_value; static intrusive_ptr<storage_t> false_value; public: - // jww (2007-05-03): Make these private, and make ledger::initialize - // a member function of session_t. static void initialize(); static void shutdown(); @@ -263,26 +287,46 @@ public: value_t() { TRACE_CTOR(value_t, ""); } + value_t(const bool val) { TRACE_CTOR(value_t, "const bool"); set_boolean(val); } + + value_t(const datetime_t& val) { + TRACE_CTOR(value_t, "const datetime_t&"); + set_datetime(val); + } + value_t(const date_t& val) { + TRACE_CTOR(value_t, "const date_t&"); + set_date(val); + } + value_t(const long val) { TRACE_CTOR(value_t, "const long"); set_long(val); } - value_t(const moment_t val) { - TRACE_CTOR(value_t, "const moment_t"); - set_datetime(val); + value_t(const unsigned long val) { + TRACE_CTOR(value_t, "const unsigned long"); + set_amount(val); } value_t(const double val) { TRACE_CTOR(value_t, "const double"); set_amount(val); } - value_t(const unsigned long val) { - TRACE_CTOR(value_t, "const unsigned long"); + value_t(const amount_t& val) { + TRACE_CTOR(value_t, "const amount_t&"); set_amount(val); } + value_t(const balance_t& val) { + TRACE_CTOR(value_t, "const balance_t&"); + set_balance(val); + } + value_t(const mask_t& val) { + TRACE_CTOR(value_t, "const mask_t&"); + set_mask(val); + } + explicit value_t(const string& val, bool literal = false) { TRACE_CTOR(value_t, "const string&, bool"); if (literal) @@ -297,30 +341,15 @@ public: else set_amount(amount_t(val)); } - value_t(const amount_t& val) { - TRACE_CTOR(value_t, "const amount_t&"); - set_amount(val); - } - value_t(const balance_t& val) { - TRACE_CTOR(value_t, "const balance_t&"); - set_balance(val); - } - value_t(const balance_pair_t& val) { - TRACE_CTOR(value_t, "const balance_pair_t&"); - set_balance_pair(val); - } + value_t(const sequence_t& val) { TRACE_CTOR(value_t, "const sequence_t&"); set_sequence(val); } - value_t(xml::node_t * item) { - TRACE_CTOR(value_t, "xml::node_t *"); - set_xml_node(item); - } - template <typename T> - explicit value_t(T * item) { - TRACE_CTOR(value_t, "T *"); - set_pointer(item); + + explicit value_t(scope_t * item) { + TRACE_CTOR(value_t, "scope_t *"); + set_scope(item); } /** @@ -350,16 +379,21 @@ public: /** * Comparison operators. Values can be compared to other values */ - bool operator==(const value_t& val) const; - bool operator<(const value_t& val) const; + bool is_equal_to(const value_t& val) const; + bool is_less_than(const value_t& val) const; + bool is_greater_than(const value_t& val) const; template <typename T> bool operator==(const T& amt) const { - return *this == value_t(amt); + return is_equal_to(amt); } template <typename T> bool operator<(const T& amt) const { - return *this < value_t(amt); + return is_less_than(amt); + } + template <typename T> + bool operator>(const T& amt) const { + return is_greater_than(amt); } /** @@ -375,299 +409,317 @@ public: value_t& operator*=(const value_t& val); value_t& operator/=(const value_t& val); - value_t& add(const amount_t& amount, - const optional<amount_t>& cost = none); - /** * Unary arithmetic operators. */ - value_t negate() const { + value_t negated() const { value_t temp = *this; temp.in_place_negate(); return temp; } - void in_place_negate(); + void in_place_negate(); // exists for efficiency's sake + void in_place_not(); // exists for efficiency's sake value_t operator-() const { - return negate(); + return negated(); } value_t abs() const; - value_t round() const; - value_t unround() const; - value_t reduce() const { + value_t rounded() const { + value_t temp(*this); + temp.in_place_round(); + return temp; + } + void in_place_round(); + + value_t truncated() const { + value_t temp(*this); + temp.in_place_truncate(); + return temp; + } + void in_place_truncate(); + + value_t floored() const { + value_t temp(*this); + temp.in_place_floor(); + return temp; + } + void in_place_floor(); + + value_t unrounded() const { + value_t temp(*this); + temp.in_place_unround(); + return temp; + } + void in_place_unround(); + + value_t reduced() const { value_t temp(*this); temp.in_place_reduce(); return temp; } - void in_place_reduce(); + void in_place_reduce(); // exists for efficiency's sake + + value_t unreduced() const { + value_t temp(*this); + temp.in_place_unreduce(); + return temp; + } + void in_place_unreduce(); // exists for efficiency's sake - value_t value(const optional<moment_t>& moment = none) const; + // Return the "market value" of a given value at a specific time. + value_t value(const bool primary_only = false, + const optional<datetime_t>& moment = none, + const optional<commodity_t&>& in_terms_of = none) const; + + value_t price() const; + + value_t exchange_commodities(const std::string& commodities, + const bool add_prices = false, + const optional<datetime_t>& moment = none); /** * Truth tests. */ operator bool() const; + bool is_nonzero() const { + return ! is_zero(); + } + bool is_realzero() const; + bool is_zero() const; bool is_null() const { if (! storage) { + VERIFY(is_type(VOID)); return true; } else { - assert(! is_type(VOID)); + VERIFY(! is_type(VOID)); return false; } } type_t type() const { - type_t result = storage ? storage->type : VOID; - assert(result >= VOID && result <= POINTER); - return result; + return storage ? storage->type : VOID; } - bool is_type(type_t _type) const { return type() == _type; } + private: - void set_type(type_t new_type) { - assert(new_type >= VOID && new_type <= POINTER); - if (new_type == VOID) { - _reset(); - assert(is_null()); - } else { - _clear(); - storage->type = new_type; - assert(is_type(new_type)); - } - } + void set_type(type_t new_type); public: /** - * Data manipulation methods. A value object may be truth tested - * for the existence of every type it can contain: + * Data manipulation methods. A value object may be truth tested for the + * existence of every type it can contain: * * is_boolean() * is_long() * is_datetime() + * is_date() * is_amount() * is_balance() - * is_balance_pair() * is_string() + * is_mask() * is_sequence() - * is_xml_node() * is_pointer() * - * There are corresponding as_*() methods that represent a value as - * a reference to its underlying type. For example, as_integer() - * returns a reference to a "const long". + * There are corresponding as_*() methods that represent a value as a + * reference to its underlying type. For example, as_long() returns a + * reference to a "const long". * - * There are also as_*_lval() methods, which represent the - * underlying data as a reference to a non-const type. The - * difference here is that an _lval() call causes the underlying - * data to be fully copied before the resulting reference is - * returned. + * There are also as_*_lval() methods, which represent the underlying data + * as a reference to a non-const type. The difference here is that an + * _lval() call causes the underlying data to be fully copied before the + * resulting reference is returned. * * Lastly, there are corresponding set_*(data) methods for directly - * assigning data of a particular type, rather than using the - * regular assignment operator (whose implementation simply calls - * the various set_ methods). + * assigning data of a particular type, rather than using the regular + * assignment operator (whose implementation simply calls the various set_ + * methods). */ bool is_boolean() const { return is_type(BOOLEAN); } bool& as_boolean_lval() { - assert(is_boolean()); + VERIFY(is_boolean()); _dup(); - return *(bool *) storage->data; + return boost::get<bool>(storage->data); } const bool& as_boolean() const { - assert(is_boolean()); - return *(bool *) storage->data; + VERIFY(is_boolean()); + return boost::get<bool>(storage->data); } void set_boolean(const bool val) { set_type(BOOLEAN); storage = val ? true_value : false_value; } - bool is_long() const { - return is_type(INTEGER); + bool is_datetime() const { + return is_type(DATETIME); } - long& as_long_lval() { - assert(is_long()); + datetime_t& as_datetime_lval() { + VERIFY(is_datetime()); _dup(); - return *(long *) storage->data; + return boost::get<datetime_t>(storage->data); } - const long& as_long() const { - assert(is_long()); - return *(long *) storage->data; + const datetime_t& as_datetime() const { + VERIFY(is_datetime()); + return boost::get<datetime_t>(storage->data); } - void set_long(const long val) { - set_type(INTEGER); - *(long *) storage->data = val; + void set_datetime(const datetime_t& val) { + set_type(DATETIME); + storage->data = val; } - bool is_datetime() const { - return is_type(DATETIME); + bool is_date() const { + return is_type(DATE); } - moment_t& as_datetime_lval() { - assert(is_datetime()); + date_t& as_date_lval() { + VERIFY(is_date()); _dup(); - return *(moment_t *) storage->data; + return boost::get<date_t>(storage->data); } - const moment_t& as_datetime() const { - assert(is_datetime()); - return *(moment_t *) storage->data; + const date_t& as_date() const { + VERIFY(is_date()); + return boost::get<date_t>(storage->data); } - void set_datetime(const moment_t& val) { - set_type(DATETIME); - new((moment_t *) storage->data) moment_t(val); + void set_date(const date_t& val) { + set_type(DATE); + storage->data = val; + } + + bool is_long() const { + return is_type(INTEGER); + } + long& as_long_lval() { + VERIFY(is_long()); + _dup(); + return boost::get<long>(storage->data); + } + const long& as_long() const { + VERIFY(is_long()); + return boost::get<long>(storage->data); + } + void set_long(const long val) { + set_type(INTEGER); + storage->data = val; } bool is_amount() const { return is_type(AMOUNT); } amount_t& as_amount_lval() { - assert(is_amount()); + VERIFY(is_amount()); _dup(); - return *(amount_t *) storage->data; + return boost::get<amount_t>(storage->data); } const amount_t& as_amount() const { - assert(is_amount()); - return *(amount_t *) storage->data; + VERIFY(is_amount()); + return boost::get<amount_t>(storage->data); } void set_amount(const amount_t& val) { + VERIFY(val.valid()); set_type(AMOUNT); - new((amount_t *) storage->data) amount_t(val); + storage->data = val; } bool is_balance() const { return is_type(BALANCE); } balance_t& as_balance_lval() { - assert(is_balance()); + VERIFY(is_balance()); _dup(); - return **(balance_t **) storage->data; + return *boost::get<balance_t *>(storage->data); } const balance_t& as_balance() const { - assert(is_balance()); - return **(balance_t **) storage->data; + VERIFY(is_balance()); + return *boost::get<balance_t *>(storage->data); } void set_balance(const balance_t& val) { + VERIFY(val.valid()); set_type(BALANCE); - *(balance_t **) storage->data = new balance_t(val); - } - - bool is_balance_pair() const { - return is_type(BALANCE_PAIR); - } - balance_pair_t& as_balance_pair_lval() { - assert(is_balance_pair()); - _dup(); - return **(balance_pair_t **) storage->data; - } - const balance_pair_t& as_balance_pair() const { - assert(is_balance_pair()); - return **(balance_pair_t **) storage->data; - } - void set_balance_pair(const balance_pair_t& val) { - set_type(BALANCE_PAIR); - *(balance_pair_t **) storage->data = new balance_pair_t(val); + storage->data = new balance_t(val); } bool is_string() const { return is_type(STRING); } string& as_string_lval() { - assert(is_string()); + VERIFY(is_string()); _dup(); - return *(string *) storage->data; + return boost::get<string>(storage->data); } const string& as_string() const { - assert(is_string()); - return *(string *) storage->data; + VERIFY(is_string()); + return boost::get<string>(storage->data); } void set_string(const string& val = "") { set_type(STRING); - new((string *) storage->data) string(val); + storage->data = val; + VERIFY(boost::get<string>(storage->data) == val); } - - bool is_sequence() const { - return is_type(SEQUENCE); - } - sequence_t& as_sequence_lval() { - assert(is_sequence()); - _dup(); - return **(sequence_t **) storage->data; - } - const sequence_t& as_sequence() const { - assert(is_sequence()); - return **(sequence_t **) storage->data; - } - void set_sequence(const sequence_t& val) { - set_type(SEQUENCE); - *(sequence_t **) storage->data = new sequence_t(val); + void set_string(const char * val = "") { + set_type(STRING); + storage->data = string(val); + VERIFY(boost::get<string>(storage->data) == val); } - bool is_xml_node() const { - return is_type(XML_NODE); + bool is_mask() const { + return is_type(MASK); } - xml::node_t *& as_xml_node_lval() { - assert(is_xml_node()); + mask_t& as_mask_lval() { + VERIFY(is_mask()); _dup(); - return *(xml::node_t **) storage->data; - } - xml::node_t * as_xml_node() const { - assert(is_xml_node()); - return *(xml::node_t **) storage->data; + VERIFY(boost::get<mask_t>(storage->data).valid()); + return boost::get<mask_t>(storage->data); } - void set_xml_node(xml::node_t * val) { - set_type(XML_NODE); - *(xml::node_t **) storage->data = val; + const mask_t& as_mask() const { + VERIFY(is_mask()); + VERIFY(boost::get<mask_t>(storage->data).valid()); + return boost::get<mask_t>(storage->data); } - - bool is_pointer() const { - return is_type(POINTER); + void set_mask(const string& val) { + set_type(MASK); + storage->data = mask_t(val); } - boost::any& as_any_pointer_lval() { - assert(is_pointer()); - _dup(); - return *(boost::any *) storage->data; + void set_mask(const mask_t& val) { + set_type(MASK); + storage->data = val; } - template <typename T> - T *& as_pointer_lval() { - assert(is_pointer()); - _dup(); - return any_cast<T *>(*(boost::any *) storage->data); + + bool is_sequence() const { + return is_type(SEQUENCE); } - template <typename T> - T& as_ref_lval() { - assert(is_pointer()); + sequence_t& as_sequence_lval() { + VERIFY(is_sequence()); _dup(); - return *any_cast<T *>(*(boost::any *) storage->data); + return *boost::get<sequence_t *>(storage->data); } - boost::any as_any_pointer() const { - assert(is_pointer()); - return *(boost::any *) storage->data; + const sequence_t& as_sequence() const { + VERIFY(is_sequence()); + return *boost::get<sequence_t *>(storage->data); } - template <typename T> - T * as_pointer() const { - assert(is_pointer()); - return any_cast<T *>(*(boost::any *) storage->data); + void set_sequence(const sequence_t& val) { + set_type(SEQUENCE); + storage->data = new sequence_t(val); } - template <typename T> - T& as_ref() const { - assert(is_pointer()); - return *any_cast<T *>(*(boost::any *) storage->data); + + /** + * Dealing with scope pointers. + */ + bool is_scope() const { + return is_type(SCOPE); } - void set_any_pointer(const boost::any& val) { - set_type(POINTER); - new((boost::any *) storage->data) boost::any(val); + scope_t * as_scope() const { + VERIFY(is_scope()); + return boost::get<scope_t *>(storage->data); } - template <typename T> - void set_pointer(T * val) { - set_type(POINTER); - new((boost::any *) storage->data) boost::any(val); + void set_scope(scope_t * val) { + set_type(SCOPE); + storage->data = val; } /** @@ -675,14 +727,16 @@ public: * its underlying type, where possible. If not possible, an * exception is thrown. */ - bool to_boolean() const; - long to_long() const; - moment_t to_datetime() const; - amount_t to_amount() const; - balance_t to_balance() const; - balance_pair_t to_balance_pair() const; - string to_string() const; - sequence_t to_sequence() const; + bool to_boolean() const; + int to_int() const; + long to_long() const; + datetime_t to_datetime() const; + date_t to_date() const; + amount_t to_amount() const; + balance_t to_balance() const; + string to_string() const; + mask_t to_mask() const; + sequence_t to_sequence() const; /** * Dynamic typing conversion methods. @@ -700,36 +754,40 @@ public: * in_place_cast * in_place_simplify */ - value_t cast(type_t cast_type) const { + value_t casted(type_t cast_type) const { value_t temp(*this); temp.in_place_cast(cast_type); return temp; } void in_place_cast(type_t cast_type); - value_t simplify() const { + value_t simplified() const { value_t temp = *this; temp.in_place_simplify(); return temp; } - void in_place_simplify(); + void in_place_simplify(); + + value_t number() const; /** * Annotated commodity methods. */ - value_t annotated_price() const; - value_t annotated_date() const; - value_t annotated_tag() const; + void annotate(const annotation_t& details); + bool has_annotation() const; + + annotation_t& annotation(); + const annotation_t& annotation() const { + return const_cast<value_t&>(*this).annotation(); + } - value_t strip_annotations(const bool keep_price = amount_t::keep_price, - const bool keep_date = amount_t::keep_date, - const bool keep_tag = amount_t::keep_tag) const; + value_t strip_annotations(const keep_details_t& what_to_keep) const; /** - * Collection-style access methods + * Collection-style access methods for SEQUENCE values. */ - value_t& operator[](const int index) { - assert(! is_null()); + value_t& operator[](const std::size_t index) { + VERIFY(! is_null()); if (is_sequence()) return as_sequence_lval()[index]; else if (index == 0) @@ -739,8 +797,8 @@ public: static value_t null; return null; } - const value_t& operator[](const int index) const { - assert(! is_null()); + const value_t& operator[](const std::size_t index) const { + VERIFY(! is_null()); if (is_sequence()) return as_sequence()[index]; else if (index == 0) @@ -751,43 +809,66 @@ public: return null; } + void push_front(const value_t& val) { + if (is_null()) + *this = sequence_t(); + if (! is_sequence()) + in_place_cast(SEQUENCE); + as_sequence_lval().push_front(val); + } + void push_back(const value_t& val) { - if (! val.is_null()) { - if (is_null()) { - *this = val; - } else { - if (! is_sequence()) - in_place_cast(SEQUENCE); - - value_t::sequence_t& seq(as_sequence_lval()); - if (! val.is_sequence()) { - if (! val.is_null()) - seq.push_back(val); - } else { - const value_t::sequence_t& val_seq(val.as_sequence()); - std::copy(val_seq.begin(), val_seq.end(), back_inserter(seq)); - } - } - } + if (is_null()) + *this = sequence_t(); + if (! is_sequence()) + in_place_cast(SEQUENCE); + as_sequence_lval().push_back(val); } void pop_back() { - assert(! is_null()); + VERIFY(! is_null()); if (! is_sequence()) { - _reset(); +#if BOOST_VERSION >= 103700 + storage.reset(); +#else + storage = intrusive_ptr<storage_t>(); +#endif } else { as_sequence_lval().pop_back(); - std::size_t new_size = as_sequence().size(); - if (new_size == 0) - _reset(); - else if (new_size == 1) - *this = as_sequence().front(); + const sequence_t& seq(as_sequence()); + std::size_t new_size = seq.size(); + if (new_size == 0) { +#if BOOST_VERSION >= 103700 + storage.reset(); +#else + storage = intrusive_ptr<storage_t>(); +#endif + } + else if (new_size == 1) { + *this = seq.front(); + } } } - const std::size_t size() const { + sequence_t::iterator begin() { + return as_sequence_lval().begin(); + } + + sequence_t::iterator end() { + return as_sequence_lval().end(); + } + + sequence_t::const_iterator begin() const { + return as_sequence().begin(); + } + + sequence_t::const_iterator end() const { + return as_sequence().end(); + } + + std::size_t size() const { if (is_null()) return 0; else if (is_sequence()) @@ -796,66 +877,121 @@ public: return 1; } + bool empty() const { + return size() == 0; + } + /** * Informational methods. */ string label(optional<type_t> the_type = none) const { switch (the_type ? *the_type : type()) { case VOID: - return "an uninitialized value"; + return _("an uninitialized value"); case BOOLEAN: - return "a boolean"; - case INTEGER: - return "an integer"; + return _("a boolean"); case DATETIME: - return "a date/time"; + return _("a date/time"); + case DATE: + return _("a date"); + case INTEGER: + return _("an integer"); case AMOUNT: - return "an amount"; + return _("an amount"); case BALANCE: - return "a balance"; - case BALANCE_PAIR: - return "a balance pair"; + return _("a balance"); case STRING: - return "a string"; + return _("a string"); + case MASK: + return _("a regexp"); case SEQUENCE: - return "a sequence"; - case XML_NODE: - return "an xml node"; - case POINTER: - return "a pointer"; + return _("a sequence"); + case SCOPE: + return _("a scope"); default: assert(false); break; } assert(false); - return "<invalid>"; + return _("<invalid>"); } - value_t cost() const; - /** * Printing methods. */ - void print(std::ostream& out, const int first_width, - const int latter_width = -1) const; + void print(std::ostream& out, + const int first_width = -1, + const int latter_width = -1, + const bool right_justify = false, + const bool colorize = false) const; + void dump(std::ostream& out, const bool relaxed = true) const; /** * Debugging methods. */ + bool valid() const; + +#if defined(HAVE_BOOST_SERIALIZATION) +private: + /** Serialization. */ + + friend class boost::serialization::access; + + template<class Archive> + void serialize(Archive& ar, const unsigned int /* version */) { + ar & true_value; + ar & false_value; + ar & storage; + } +#endif // HAVE_BOOST_SERIALIZATION }; #define NULL_VALUE (value_t()) -inline value_t string_value(const string& str) { +inline value_t string_value(const string& str = "") { return value_t(str, true); } +#define VALUE_OR_ZERO(val) ((val).is_null() ? value_t(0L) : (val)) +#define SIMPLIFIED_VALUE_OR_ZERO(val) \ + ((val).is_null() ? value_t(0L) : (val).simplified()) + +inline value_t mask_value(const string& str) { + return value_t(mask_t(str)); +} + inline std::ostream& operator<<(std::ostream& out, const value_t& val) { - val.print(out, 12); + val.print(out); return out; } -DECLARE_EXCEPTION(value_error); +inline string value_context(const value_t& val) { + std::ostringstream buf; + val.print(buf, 20, 20, true); + return buf.str(); +} + +template <typename T> +inline value_t& add_or_set_value(value_t& lhs, const T& rhs) { + if (lhs.is_null()) + lhs = rhs; + else + lhs += rhs; + return lhs; +} + +struct sort_value_t +{ + bool inverted; + value_t value; + + sort_value_t() : inverted(false) {} +}; + +bool sort_value_is_less_than(const std::list<sort_value_t>& left_values, + const std::list<sort_value_t>& right_values); + +void to_xml(std::ostream& out, const value_t& value); } // namespace ledger diff --git a/src/xact.cc b/src/xact.cc new file mode 100644 index 00000000..623c5772 --- /dev/null +++ b/src/xact.cc @@ -0,0 +1,781 @@ +/* + * 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 "xact.h" +#include "post.h" +#include "account.h" +#include "journal.h" +#include "pool.h" + +namespace ledger { + +xact_base_t::xact_base_t(const xact_base_t& xact_base) + : item_t(xact_base), journal(xact_base.journal) +{ + TRACE_CTOR(xact_base_t, "copy"); +} + +xact_base_t::~xact_base_t() +{ + TRACE_DTOR(xact_base_t); + + if (! has_flags(ITEM_TEMP)) { + foreach (post_t * post, posts) { + // If the posting is a temporary, it will be destructed when the + // temporary is. + assert(! post->has_flags(ITEM_TEMP)); + checked_delete(post); + } + } +} + +void xact_base_t::add_post(post_t * post) +{ + // You can add temporary postings to transactions, but not real postings to + // temporary transactions. + if (! post->has_flags(ITEM_TEMP)) + assert(! has_flags(ITEM_TEMP)); + + posts.push_back(post); +} + +bool xact_base_t::remove_post(post_t * post) +{ + posts.remove(post); + post->xact = NULL; + return true; +} + +bool xact_base_t::has_xdata() +{ + foreach (post_t * post, posts) + if (post->has_xdata()) + return true; + + return false; +} + +void xact_base_t::clear_xdata() +{ + foreach (post_t * post, posts) + if (! post->has_flags(ITEM_TEMP)) + post->clear_xdata(); +} + +value_t xact_base_t::magnitude() const +{ + value_t halfbal = 0L; + foreach (const post_t * post, posts) { + if (post->amount.sign() > 0) { + if (post->cost) + halfbal += *post->cost; + else + halfbal += post->amount; + } + } + return halfbal; +} + +bool xact_base_t::finalize() +{ + // Scan through and compute the total balance for the xact. This is used + // for auto-calculating the value of xacts with no cost, and the per-unit + // price of unpriced commodities. + + value_t balance; + post_t * null_post = NULL; + + foreach (post_t * post, posts) { + if (! post->must_balance()) + continue; + + amount_t& p(post->cost ? *post->cost : post->amount); + if (! p.is_null()) { + DEBUG("xact.finalize", "post must balance = " << p.reduced()); + if (! post->cost && post->amount.has_annotation() && + post->amount.annotation().price) { + // If the amount has no cost, but is annotated with a per-unit + // price, use the price times the amount as the cost + post->cost = (*post->amount.annotation().price * + post->amount).unrounded(); + DEBUG("xact.finalize", + "annotation price = " << *post->amount.annotation().price); + DEBUG("xact.finalize", "amount = " << post->amount); + DEBUG("xact.finalize", "priced cost = " << *post->cost); + post->add_flags(POST_COST_CALCULATED); + add_or_set_value(balance, post->cost->rounded().reduced()); + } else { + // If the amount was a cost, it very likely has the "keep_precision" + // flag set, meaning commodity display precision is ignored when + // displaying the amount. We never want this set for the balance, + // so we must clear the flag in a temporary to avoid it propagating + // into the balance. + add_or_set_value(balance, p.keep_precision() ? + p.rounded().reduced() : p.reduced()); + } + } + else if (null_post) { + throw_(std::logic_error, + _("Only one posting with null amount allowed per transaction")); + } + else { + null_post = post; + } + } + VERIFY(balance.valid()); + +#if defined(DEBUG_ON) + DEBUG("xact.finalize", "initial balance = " << balance); + DEBUG("xact.finalize", "balance is " << balance.label()); + if (balance.is_balance()) + DEBUG("xact.finalize", "balance commodity count = " + << balance.as_balance().amounts.size()); +#endif + + // If there is only one post, balance against the default account if one has + // been set. + + if (journal && journal->bucket && posts.size() == 1 && ! balance.is_null()) { + null_post = new post_t(journal->bucket, ITEM_GENERATED); + null_post->_state = (*posts.begin())->_state; + add_post(null_post); + } + + if (null_post != NULL) { + // If one post has no value at all, its value will become the inverse of + // the rest. If multiple commodities are involved, multiple posts are + // generated to balance them all. + + DEBUG("xact.finalize", "there was a null posting"); + + if (balance.is_balance()) { + bool first = true; + const balance_t& bal(balance.as_balance()); + foreach (const balance_t::amounts_map::value_type& pair, bal.amounts) { + if (first) { + null_post->amount = pair.second.negated(); + null_post->add_flags(POST_CALCULATED); + first = false; + } else { + post_t * p = new post_t(null_post->account, pair.second.negated(), + ITEM_GENERATED | POST_CALCULATED); + p->set_state(null_post->state()); + add_post(p); + } + } + } + else if (balance.is_amount()) { + null_post->amount = balance.as_amount().negated(); + null_post->add_flags(POST_CALCULATED); + } + else if (balance.is_long()) { + null_post->amount = amount_t(- balance.as_long()); + null_post->add_flags(POST_CALCULATED); + } + else if (! balance.is_null() && ! balance.is_realzero()) { + throw_(balance_error, _("Transaction does not balance")); + } + balance = NULL_VALUE; + } + else if (balance.is_balance() && + balance.as_balance().amounts.size() == 2) { + // When an xact involves two different commodities (regardless of how + // many posts there are) determine the conversion ratio by dividing the + // total value of one commodity by the total value of the other. This + // establishes the per-unit cost for this post for both commodities. + + DEBUG("xact.finalize", "there were exactly two commodities"); + + bool saw_cost = false; + post_t * top_post = NULL; + + foreach (post_t * post, posts) { + if (! post->amount.is_null()) { + if (post->amount.has_annotation()) + top_post = post; + else if (! top_post) + top_post = post; + } + + if (post->cost && ! post->has_flags(POST_COST_CALCULATED)) { + saw_cost = true; + break; + } + } + + if (! saw_cost && top_post) { + const balance_t& bal(balance.as_balance()); + + DEBUG("xact.finalize", "there were no costs, and a valid top_post"); + + balance_t::amounts_map::const_iterator a = bal.amounts.begin(); + + const amount_t * x = &(*a++).second; + const amount_t * y = &(*a++).second; + + if (x->commodity() != top_post->amount.commodity()) { + const amount_t * t = x; + x = y; + y = t; + } + + if (*x && *y) { + DEBUG("xact.finalize", "primary amount = " << *x); + DEBUG("xact.finalize", "secondary amount = " << *y); + + commodity_t& comm(x->commodity()); + amount_t per_unit_cost; + amount_t total_cost; + + foreach (post_t * post, posts) { + if (post != top_post && post->must_balance() && + ! post->amount.is_null() && + post->amount.has_annotation() && + post->amount.annotation().price) { + amount_t temp = *post->amount.annotation().price * post->amount; + if (total_cost.is_null()) { + total_cost = temp; + y = &total_cost; + } else { + total_cost += temp; + } + DEBUG("xact.finalize", "total_cost = " << total_cost); + } + } + per_unit_cost = (*y / *x).abs().unrounded(); + + DEBUG("xact.finalize", "per_unit_cost = " << per_unit_cost); + + foreach (post_t * post, posts) { + const amount_t& amt(post->amount); + + if (post->must_balance() && amt.commodity() == comm) { + balance -= amt; + post->cost = per_unit_cost * amt; + post->add_flags(POST_COST_CALCULATED); + balance += *post->cost; + + DEBUG("xact.finalize", "set post->cost to = " << *post->cost); + } + } + } + } + } + + // Now that the post list has its final form, calculate the balance once + // more in terms of total cost, accounting for any possible gain/loss + // amounts. + + DEBUG("xact.finalize", "resolved balance = " << balance); + + posts_list copy(posts); + + foreach (post_t * post, copy) { + if (! post->cost) + continue; + + if (post->amount.commodity() == post->cost->commodity()) + throw_(balance_error, + _("A posting's cost must be of a different commodity than its amount")); + + cost_breakdown_t breakdown = + commodity_pool_t::current_pool->exchange + (post->amount, *post->cost, false, + datetime_t(date(), time_duration(0, 0, 0, 0))); + + if (post->amount.has_annotation() && + breakdown.basis_cost.commodity() == + breakdown.final_cost.commodity()) { + if (amount_t gain_loss = (breakdown.basis_cost - + breakdown.final_cost).rounded()) { + DEBUG("xact.finalize", "gain_loss = " << gain_loss); + + add_or_set_value(balance, gain_loss.reduced()); + + account_t * account; + if (gain_loss.sign() > 0) + account = journal->find_account(_("Equity:Capital Gains")); + else + account = journal->find_account(_("Equity:Capital Losses")); + + post_t * p = new post_t(account, gain_loss, ITEM_GENERATED); + p->set_state(post->state()); + add_post(p); + DEBUG("xact.finalize", "added gain_loss, balance = " << balance); + } + } else { + post->amount = breakdown.amount; + DEBUG("xact.finalize", "added breakdown, balance = " << balance); + } + } + + DEBUG("xact.finalize", "final balance = " << balance); + + if (! balance.is_null() && ! balance.is_zero()) { + add_error_context(item_context(*this, _("While balancing transaction"))); + add_error_context(_("Unbalanced remainder is:")); + add_error_context(value_context(balance)); + add_error_context(_("Amount to balance against:")); + add_error_context(value_context(magnitude())); + throw_(balance_error, _("Transaction does not balance")); + } + + // Add a pointer to each posting to their related accounts + + if (dynamic_cast<xact_t *>(this)) { + bool all_null = true; + bool some_null = false; + + foreach (post_t * post, posts) { + if (! post->amount.is_null()) { + all_null = false; + post->amount.in_place_reduce(); + } else { + some_null = true; + } + + post->account->add_post(post); + + post->xdata().add_flags(POST_EXT_VISITED); + post->account->xdata().add_flags(ACCOUNT_EXT_VISITED); + } + + if (all_null) + return false; // ignore this xact completely + else if (some_null) + throw_(balance_error, + _("There cannot be null amounts after balancing a transaction")); + } + + VERIFY(valid()); + + return true; +} + +bool xact_base_t::verify() +{ + // Scan through and compute the total balance for the xact. + + value_t balance; + + foreach (post_t * post, posts) { + if (! post->must_balance()) + continue; + + amount_t& p(post->cost ? *post->cost : post->amount); + assert(! p.is_null()); + + // If the amount was a cost, it very likely has the "keep_precision" flag + // set, meaning commodity display precision is ignored when displaying the + // amount. We never want this set for the balance, so we must clear the + // flag in a temporary to avoid it propagating into the balance. + add_or_set_value(balance, p.keep_precision() ? + p.rounded().reduced() : p.reduced()); + } + VERIFY(balance.valid()); + + // Now that the post list has its final form, calculate the balance once + // more in terms of total cost, accounting for any possible gain/loss + // amounts. + + foreach (post_t * post, posts) { + if (! post->cost) + continue; + + if (post->amount.commodity() == post->cost->commodity()) + throw_(amount_error, + _("A posting's cost must be of a different commodity than its amount")); + } + + if (! balance.is_null() && ! balance.is_zero()) { + add_error_context(item_context(*this, _("While balancing transaction"))); + add_error_context(_("Unbalanced remainder is:")); + add_error_context(value_context(balance)); + add_error_context(_("Amount to balance against:")); + add_error_context(value_context(magnitude())); + throw_(balance_error, _("Transaction does not balance")); + } + + VERIFY(valid()); + + return true; +} + +xact_t::xact_t(const xact_t& e) + : xact_base_t(e), code(e.code), payee(e.payee) +{ + TRACE_CTOR(xact_t, "copy"); +} + +void xact_t::add_post(post_t * post) +{ + post->xact = this; + xact_base_t::add_post(post); +} + +string xact_t::idstring() const +{ + std::ostringstream buf; + buf << format_date(*_date, FMT_WRITTEN); + buf << payee; + magnitude().number().print(buf); + return buf.str(); +} + +string xact_t::id() const +{ + SHA1 sha; + sha.Reset(); + sha << idstring().c_str(); + uint_least32_t message_digest[5]; + sha.Result(message_digest); + return to_hex(message_digest, 5); +} + +namespace { + value_t get_magnitude(xact_t& xact) { + return xact.magnitude(); + } + value_t get_idstring(xact_t& xact) { + return string_value(xact.idstring()); + } + value_t get_id(xact_t& xact) { + return string_value(xact.id()); + } + + value_t get_code(xact_t& xact) { + if (xact.code) + return string_value(*xact.code); + else + return string_value(empty_string); + } + + value_t get_payee(xact_t& xact) { + return string_value(xact.payee); + } + + template <value_t (*Func)(xact_t&)> + value_t get_wrapper(call_scope_t& scope) { + return (*Func)(find_scope<xact_t>(scope)); + } +} + +expr_t::ptr_op_t xact_t::lookup(const symbol_t::kind_t kind, + const string& name) +{ + if (kind != symbol_t::FUNCTION) + return item_t::lookup(kind, name); + + switch (name[0]) { + case 'c': + if (name == "code") + return WRAP_FUNCTOR(get_wrapper<&get_code>); + break; + + case 'i': + if (name == "id") + return WRAP_FUNCTOR(get_wrapper<&get_id>); + else if (name == "idstring") + return WRAP_FUNCTOR(get_wrapper<&get_idstring>); + break; + + case 'm': + if (name == "magnitude") + return WRAP_FUNCTOR(get_wrapper<&get_magnitude>); + break; + + case 'p': + if (name[1] == '\0' || name == "payee") + return WRAP_FUNCTOR(get_wrapper<&get_payee>); + break; + } + + return item_t::lookup(kind, name); +} + +bool xact_t::valid() const +{ + if (! _date) { + DEBUG("ledger.validate", "xact_t: ! _date"); + return false; + } + + foreach (post_t * post, posts) + if (post->xact != this || ! post->valid()) { + DEBUG("ledger.validate", "xact_t: post not valid"); + return false; + } + + return true; +} + +namespace { + + bool post_pred(expr_t::ptr_op_t op, post_t& post) + { + switch (op->kind) { + case expr_t::op_t::VALUE: + return op->as_value().to_boolean(); + break; + + case expr_t::op_t::O_MATCH: + if (op->left()->kind == expr_t::op_t::IDENT && + op->left()->as_ident() == "account" && + op->right()->kind == expr_t::op_t::VALUE && + op->right()->as_value().is_mask()) + return op->right()->as_value().as_mask() + .match(post.reported_account()->fullname()); + else + break; + + case expr_t::op_t::O_NOT: + return ! post_pred(op->left(), post); + + case expr_t::op_t::O_AND: + return post_pred(op->left(), post) && post_pred(op->right(), post); + + case expr_t::op_t::O_OR: + return post_pred(op->left(), post) || post_pred(op->right(), post); + + case expr_t::op_t::O_QUERY: + if (post_pred(op->left(), post)) + return post_pred(op->right()->left(), post); + else + return post_pred(op->right()->right(), post); + + default: + break; + } + + throw_(calc_error, _("Unhandled operator")); + return false; + } + +} // unnamed namespace + +void auto_xact_t::extend_xact(xact_base_t& xact) +{ + posts_list initial_posts(xact.posts.begin(), xact.posts.end()); + + try { + + bool needs_further_verification = false; + + foreach (post_t * initial_post, initial_posts) { + if (initial_post->has_flags(ITEM_GENERATED)) + continue; + + bool matches_predicate = false; + if (try_quick_match) { + try { + bool found_memoized_result = false; + if (! memoized_results.empty()) { + std::map<string, bool>::iterator i = + memoized_results.find(initial_post->account->fullname()); + if (i != memoized_results.end()) { + found_memoized_result = true; + matches_predicate = (*i).second; + } + } + + // Since the majority of people who use automated transactions simply + // match against account names, try using a *much* faster version of + // the predicate evaluator. + if (! found_memoized_result) { + matches_predicate = post_pred(predicate.get_op(), *initial_post); + memoized_results.insert + (std::pair<string, bool>(initial_post->account->fullname(), + matches_predicate)); + } + } + catch (...) { + DEBUG("xact.extend.fail", + "The quick matcher failed, going back to regular eval"); + try_quick_match = false; + matches_predicate = predicate(*initial_post); + } + } else { + matches_predicate = predicate(*initial_post); + } + if (matches_predicate) { + foreach (post_t * post, posts) { + amount_t post_amount; + if (post->amount.is_null()) { + if (! post->amount_expr) + throw_(amount_error, + _("Automated transaction's posting has no amount")); + + bind_scope_t bound_scope(*scope_t::default_scope, *initial_post); + value_t result(post->amount_expr->calc(bound_scope)); + if (result.is_long()) { + post_amount = result.to_amount(); + } else { + if (! result.is_amount()) + throw_(amount_error, + _("Amount expressions must result in a simple amount")); + post_amount = result.as_amount(); + } + } else { + post_amount = post->amount; + } + + amount_t amt; + if (! post_amount.commodity()) + amt = initial_post->amount * post_amount; + else + amt = post_amount; + + IF_DEBUG("xact.extend") { + DEBUG("xact.extend", + "Initial post on line " << initial_post->pos->beg_line << ": " + << "amount " << initial_post->amount << " (precision " + << initial_post->amount.precision() << ")"); + +#if defined(DEBUG_ON) + if (initial_post->amount.keep_precision()) + DEBUG("xact.extend", " precision is kept"); +#endif + + DEBUG("xact.extend", + "Posting on line " << post->pos->beg_line << ": " + << "amount " << post_amount << ", amt " << amt + << " (precision " << post_amount.precision() + << " != " << amt.precision() << ")"); + +#if defined(DEBUG_ON) + if (post_amount.keep_precision()) + DEBUG("xact.extend", " precision is kept"); + if (amt.keep_precision()) + DEBUG("xact.extend", " amt precision is kept"); +#endif + } + + account_t * account = post->account; + string fullname = account->fullname(); + assert(! fullname.empty()); + if (fullname == "$account" || fullname == "@account") + account = initial_post->account; + + // Copy over details so that the resulting post is a mirror of + // the automated xact's one. + post_t * new_post = new post_t(account, amt); + new_post->copy_details(*post); + new_post->add_flags(ITEM_GENERATED); + + xact.add_post(new_post); + new_post->account->add_post(new_post); + + if (new_post->must_balance()) + needs_further_verification = true; + } + } + } + + if (needs_further_verification) + xact.verify(); + + } + catch (const std::exception& err) { + add_error_context(item_context(*this, _("While applying automated transaction"))); + add_error_context(item_context(xact, _("While extending transaction"))); + throw; + } +} + +void extend_xact_base(journal_t * journal, + xact_base_t& base) +{ + foreach (auto_xact_t * xact, journal->auto_xacts) + xact->extend_xact(base); +} + +void to_xml(std::ostream& out, const xact_t& xact) +{ + push_xml x(out, "transaction", true, true); + + if (xact.state() == item_t::CLEARED) + out << " state=\"cleared\""; + else if (xact.state() == item_t::PENDING) + out << " state=\"pending\""; + + if (xact.has_flags(ITEM_GENERATED)) + out << " generated=\"true\""; + + x.close_attrs(); + + if (xact._date) { + push_xml y(out, "date"); + to_xml(out, *xact._date, false); + } + if (xact._date_eff) { + push_xml y(out, "effective-date"); + to_xml(out, *xact._date_eff, false); + } + + if (xact.code) { + push_xml y(out, "code"); + out << y.guard(*xact.code); + } + + { + push_xml y(out, "payee"); + out << y.guard(xact.payee); + } + + if (xact.note) { + push_xml y(out, "note"); + out << y.guard(*xact.note); + } + + if (xact.metadata) { + push_xml y(out, "metadata"); + foreach (const item_t::string_map::value_type& pair, *xact.metadata) { + if (pair.second) { + push_xml z(out, "variable"); + { + push_xml w(out, "key"); + out << y.guard(pair.first); + } + { + push_xml w(out, "value"); + out << y.guard(*pair.second); + } + } else { + push_xml z(out, "tag"); + out << y.guard(pair.first); + } + } + } +} + +} // namespace ledger diff --git a/src/xact.h b/src/xact.h new file mode 100644 index 00000000..c819b2a0 --- /dev/null +++ b/src/xact.h @@ -0,0 +1,232 @@ +/* + * 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. + */ + +/** + * @addtogroup data + */ + +/** + * @file xact.h + * @author John Wiegley + * + * @ingroup data + */ +#ifndef _XACT_H +#define _XACT_H + +#include "item.h" +#include "predicate.h" + +namespace ledger { + +class post_t; +class journal_t; + +typedef std::list<post_t *> posts_list; + +class xact_base_t : public item_t +{ +public: + journal_t * journal; + posts_list posts; + + xact_base_t() : item_t(), journal(NULL) { + TRACE_CTOR(xact_base_t, ""); + } + xact_base_t(const xact_base_t& e); + + virtual ~xact_base_t(); + + virtual void add_post(post_t * post); + virtual bool remove_post(post_t * post); + + posts_list::iterator posts_begin() { + return posts.begin(); + } + posts_list::iterator posts_end() { + return posts.end(); + } + + value_t magnitude() const; + + bool finalize(); + bool verify(); + + bool has_xdata(); + void clear_xdata(); + + virtual bool valid() const { + return true; + } + +#if defined(HAVE_BOOST_SERIALIZATION) +private: + /** Serialization. */ + + friend class boost::serialization::access; + + template<class Archive> + void serialize(Archive& ar, const unsigned int /* version */) { + ar & boost::serialization::base_object<item_t>(*this); + ar & journal; + ar & posts; + } +#endif // HAVE_BOOST_SERIALIZATION +}; + +class xact_t : public xact_base_t +{ +public: + optional<string> code; + string payee; + + xact_t() { + TRACE_CTOR(xact_t, ""); + } + xact_t(const xact_t& e); + + virtual ~xact_t() { + TRACE_DTOR(xact_t); + } + + virtual void add_post(post_t * post); + + string idstring() const; + string id() const; + + virtual expr_t::ptr_op_t lookup(const symbol_t::kind_t kind, + const string& name); + + virtual bool valid() const; + +#if defined(HAVE_BOOST_SERIALIZATION) +private: + /** Serialization. */ + + friend class boost::serialization::access; + + template<class Archive> + void serialize(Archive& ar, const unsigned int /* version */) { + ar & boost::serialization::base_object<xact_base_t>(*this); + ar & code; + ar & payee; + } +#endif // HAVE_BOOST_SERIALIZATION +}; + +class auto_xact_t : public xact_base_t +{ +public: + predicate_t predicate; + bool try_quick_match; + + std::map<string, bool> memoized_results; + + auto_xact_t() : try_quick_match(true) { + TRACE_CTOR(auto_xact_t, ""); + } + auto_xact_t(const auto_xact_t& other) + : xact_base_t(), predicate(other.predicate), + try_quick_match(other.try_quick_match) { + TRACE_CTOR(auto_xact_t, "copy"); + } + auto_xact_t(const predicate_t& _predicate) + : predicate(_predicate), try_quick_match(true) + { + TRACE_CTOR(auto_xact_t, "const predicate_t&"); + } + + virtual ~auto_xact_t() { + TRACE_DTOR(auto_xact_t); + } + + virtual void extend_xact(xact_base_t& xact); + +#if defined(HAVE_BOOST_SERIALIZATION) +private: + /** Serialization. */ + + friend class boost::serialization::access; + + template<class Archive> + void serialize(Archive& ar, const unsigned int /* version */) { + ar & boost::serialization::base_object<xact_base_t>(*this); + ar & predicate; + } +#endif // HAVE_BOOST_SERIALIZATION +}; + +class period_xact_t : public xact_base_t +{ + public: + date_interval_t period; + string period_string; + + period_xact_t() { + TRACE_CTOR(period_xact_t, ""); + } + period_xact_t(const period_xact_t& e) + : xact_base_t(e), period(e.period), period_string(e.period_string) { + TRACE_CTOR(period_xact_t, "copy"); + } + period_xact_t(const string& _period) + : period(_period), period_string(_period) { + TRACE_CTOR(period_xact_t, "const string&"); + } + + virtual ~period_xact_t() { + TRACE_DTOR(period_xact_t); + } + +#if defined(HAVE_BOOST_SERIALIZATION) +private: + /** Serialization. */ + + friend class boost::serialization::access; + + template<class Archive> + void serialize(Archive& ar, const unsigned int /* version */) { + ar & boost::serialization::base_object<xact_base_t>(*this); + ar & period; + ar & period_string; + } +#endif // HAVE_BOOST_SERIALIZATION +}; + +typedef std::list<xact_t *> xacts_list; +typedef std::list<auto_xact_t *> auto_xacts_list; +typedef std::list<period_xact_t *> period_xacts_list; + +void to_xml(std::ostream& out, const xact_t& xact); + +} // namespace ledger + +#endif // _XACT_H diff --git a/src/xml.cc b/src/xml.cc new file mode 100644 index 00000000..9cff980a --- /dev/null +++ b/src/xml.cc @@ -0,0 +1,124 @@ +/* + * 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 "xml.h" +#include "xact.h" +#include "post.h" +#include "account.h" +#include "session.h" +#include "report.h" + +namespace ledger { + +namespace { + void xml_account(std::ostream& out, const account_t * acct) { + if ((acct->has_xdata() && + acct->xdata().has_flags(ACCOUNT_EXT_VISITED)) || + acct->children_with_flags(ACCOUNT_EXT_VISITED)) { + out << "<account id=\""; + out.width(sizeof(unsigned long) * 2); + out.fill('0'); + out << std::hex << reinterpret_cast<unsigned long>(acct); + out << "\">\n"; + + out << "<name>" << acct->name << "</name>\n"; + value_t total = acct->amount(); + if (! total.is_null()) { + out << "<amount>\n"; + to_xml(out, total); + out << "</amount>\n"; + } + total = acct->total(); + if (! total.is_null()) { + out << "<total>\n"; + to_xml(out, total); + out << "</total>\n"; + } + out << "</account>\n"; + } + + foreach (const accounts_map::value_type& pair, acct->accounts) + xml_account(out, pair.second); + } + + void xml_transaction(std::ostream& out, const xact_t * xact) { + to_xml(out, *xact); + + foreach (const post_t * post, xact->posts) + if (post->has_xdata() && + post->xdata().has_flags(POST_EXT_VISITED)) + to_xml(out, *post); + + out << "</transaction>\n"; + } +} + +void format_xml::flush() +{ + std::ostream& out(report.output_stream); + + out << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; + out << "<ledger version=\"" << VERSION << "\">\n"; + + out << "<commodities>\n"; + foreach (const commodities_pair& pair, commodities) { + to_xml(out, *pair.second, true); + out << '\n'; + } + out << "</commodities>\n"; + + out << "<accounts>\n"; + xml_account(out, report.session.journal->master); + out << "</accounts>\n"; + + out << "<transactions>\n"; + foreach (const xact_t * xact, transactions) + xml_transaction(out, xact); + out << "</transactions>\n"; + + out << "</ledger>\n"; + out.flush(); +} + +void format_xml::operator()(post_t& post) +{ + assert(post.xdata().has_flags(POST_EXT_VISITED)); + + commodities.insert(commodities_pair(post.amount.commodity().symbol(), + &post.amount.commodity())); + + if (transactions_set.find(post.xact) == transactions_set.end()) + transactions.push_back(post.xact); +} + +} // namespace ledger diff --git a/src/xml.h b/src/xml.h new file mode 100644 index 00000000..30a7b1b1 --- /dev/null +++ b/src/xml.h @@ -0,0 +1,90 @@ +/* + * 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. + */ + +/** + * @addtogroup report + */ + +/** + * @file xml.h + * @author John Wiegley + * + * @ingroup report + * + * @brief Brief + * + * Long. + */ +#ifndef _XML_H +#define _XML_H + +#include "chain.h" + +namespace ledger { + +class xact_t; +class account_t; +class commodity_t; +class post_t; +class report_t; + +/** + * @brief Brief + * + * Long. + */ +class format_xml : public item_handler<post_t> +{ +protected: + report_t& report; + + typedef std::map<string, commodity_t *> commodities_map; + typedef std::pair<string, commodity_t *> commodities_pair; + + commodities_map commodities; + std::set<xact_t *> transactions_set; + std::deque<xact_t *> transactions; + +public: + format_xml(report_t& _report) : report(_report) { + TRACE_CTOR(format_xml, "report&"); + } + virtual ~format_xml() { + TRACE_DTOR(format_xml); + } + + virtual void flush(); + virtual void operator()(post_t& post); +}; + +} // namespace ledger + +#endif // _XML_H |