diff options
-rw-r--r-- | Makefile.am | 8 | ||||
-rw-r--r-- | NEWS | 114 | ||||
-rw-r--r-- | amount.cc | 188 | ||||
-rw-r--r-- | amount.h | 71 | ||||
-rw-r--r-- | balance.cc | 16 | ||||
-rw-r--r-- | balance.h | 3 | ||||
-rw-r--r-- | binary.cc | 178 | ||||
-rw-r--r-- | config.cc | 190 | ||||
-rw-r--r-- | config.h | 22 | ||||
-rw-r--r-- | configure.in | 9 | ||||
-rw-r--r-- | debug.h | 5 | ||||
-rw-r--r-- | derive.cc | 124 | ||||
-rw-r--r-- | format.cc | 34 | ||||
-rw-r--r-- | format.h | 2 | ||||
-rw-r--r-- | gnucash.cc | 24 | ||||
-rw-r--r-- | journal.cc | 12 | ||||
-rw-r--r-- | journal.h | 32 | ||||
-rw-r--r-- | main.cc | 34 | ||||
-rw-r--r-- | option.cc | 189 | ||||
-rw-r--r-- | option.h | 33 | ||||
-rw-r--r-- | qif.cc | 18 | ||||
-rw-r--r-- | quotes.cc | 10 | ||||
-rw-r--r-- | textual.cc | 81 | ||||
-rw-r--r-- | valexpr.cc | 1164 | ||||
-rw-r--r-- | valexpr.h | 272 | ||||
-rw-r--r-- | value.cc | 32 | ||||
-rw-r--r-- | value.h | 3 | ||||
-rw-r--r-- | walk.cc | 30 | ||||
-rw-r--r-- | walk.h | 15 | ||||
-rw-r--r-- | xml.cc | 57 |
30 files changed, 1927 insertions, 1043 deletions
diff --git a/Makefile.am b/Makefile.am index 68eb29b0..8c52b263 100644 --- a/Makefile.am +++ b/Makefile.am @@ -7,6 +7,7 @@ libledger_la_SOURCES = \ config.cc \ datetime.cc \ derive.cc \ + emacs.cc \ format.cc \ journal.cc \ mask.cc \ @@ -20,10 +21,6 @@ libledger_la_SOURCES = \ valexpr.cc \ value.cc \ walk.cc -if USE_EDITOR -libledger_la_CXXFLAGS += -DUSE_EDITOR=1 -libledger_la_SOURCES += emacs.cc -endif if HAVE_EXPAT libledger_la_CXXFLAGS += -DHAVE_EXPAT=1 libledger_la_SOURCES += gnucash.cc xml.cc @@ -77,9 +74,6 @@ bin_PROGRAMS = ledger ledger_CXXFLAGS = ledger_SOURCES = main.cc ledger_LDADD = $(LIBOBJS) libledger.la -if USE_EDITOR -ledger_CXXFLAGS += -DUSE_EDITOR=1 -endif if HAVE_EXPAT ledger_CXXFLAGS += -DHAVE_EXPAT=1 ledger_LDADD += -lexpat @@ -3,18 +3,87 @@ * 2.5 -- Added a new "csv" command, for outputting results in CSV format. +- There have a few changes to value expression syntax. The most + significant incompatibilities being: + + * Equality is now ==, not = + * The U, A, and S functions now requires parens around the argument. + Whereas before At was acceptable, now it must be specified as + A(t). + * The P function now always requires two arguments. The old + one-argument version P(x) is now the same as P(x,m). + + The following value expression features are new: + + * A C-like comma operator is supported, where all but the last term + are ignored. The is significant for the next feature: + * Function definitions are now supported. Scoping is governed + by parentheses. For example: + (x=100, x+10) ; yields 110 as the result + (f(x)=x*2,f(100)) ; yields 200 as the result + * Identifier names may be any length. Along with this support comes + alternate, longer names for all of the current one-letter value + expression variables: + + Old New + --- --- + m now + a amount + a amount + b cost + i price + d date + X cleared + Y pending + R real + L actual + n index + N count + l depth + O total + B cost_total + I price_total + v market + V market_total + g gain + G gain_total + U(x) abs(x) + S(x) quant(x), quantity(x) + comm(x), commodity(x) + setcomm(x,y), set_commodity(x,y) + A(x) mean(x), avg(x), average(x) + P(x,y) val(x,y), value(x,y) + min(x,y) + max(x,y) + +- There are new "parse" and "expr" commands, whose argument is a + single value expression. Ledger will simply print out the result of + evaluating it. "parse" happens before parsing your ledger file, + while "expr" happens afterward. Although "expr" is slower as a + result, any commodities you use will be formatted based on patterns + of usage seen in your ledger file. + + These commands can be used to test value expressions, or for doing + calculation of commoditized amounts from a script. + + A new "--debug" will also dump the resulting parse tree, useful for + submitting bug reports. + +- Added new min(x,y) and max(x,y) value expression functions. - Added a new value expression regexp command: - C// compare against transaction amount's commodity symbol + C// compare against a transaction amount's commodity symbol + +- Value expression function may now be defined within your ledger file + (or initialization file) using the following syntax: -- Added new @min(x,y) and @max(x,y) value expression functions. + @def foo(x)=x*1000 -- A new configure option "--disable-emacs" will disable generation of - transaction and entry location info, which is used by ledger.el and - the "write" command. If you use neither of these, then disabling - them will cut textual parsing time in half, and binary loading time - (and cache file size) by a third. + This line makes the function "foo" available to all subsequent value + expressions, to all command-line options taking a value expression, + and to the new "expr" command (see above). + +- Added a new "csv" command, for outputting results in CSV format. - Effective dates may now be specified for entries: @@ -74,28 +143,27 @@ transactions belong to the same entry. - Individual transactions may now be cleared separately. The old - syntax, which is still supported, clears all transactions in the + syntax, which is still supported, clears all transactions in an entry: 2004/05/27 * Book Store Expenses:Dining $20.00 Liabilities:MasterCard - The new (and optional) syntax allows clearing just the MasterCard - transaction: + The new syntax allows clearing of just the MasterCard transaction: 2004/05/27 Book Store Expenses:Dining $20.00 * Liabilities:MasterCard - NOTE: This changes the output format of the "emacs" and "xml" - reports. ledger.el will use the new syntax unless the Lisp variable + NOTE: This changes the output format of both the "emacs" and "xml" + reports. ledger.el uses the new syntax unless the Lisp variable `ledger-clear-whole-entries' is set to t. - Removed Python integration support. - Did much internal restructuring to allow the use of libledger.so in - non-command-line environments. + non-command-line environments (such as GUI tools). * 2.4 @@ -264,27 +332,27 @@ command-line driver. -d now specifies the display predicate. To give a date mask similar to 1.7, use the -p (period) option. - + -P now generates the "by payee" report. To specify a price database to use, use --price-db. - + -G now generates a net gain report. To print totals in a format consumable by gnuplot, use -J. - + -l now specifies the calculation predicate. To emulate the old usage of "-l \$100", use: -d "AT>100". - + -N is gone. Instead of "-N REGEX", use: -d "/REGEX/?T>0:T". - + -F now specifies the report format string. The old meaning of -F now has little use. - + -S now takes a value expression as the sorting criterion. To get the old meaning of "-S", use "-S d". - + -n now means "collapse entries in the register report". The get the old meaning of -n in the balance report, use "-T a". - + -p now specifies the reporting period. You can convert commodities in a report using value expressions. For example, to display hours at $10 per hour: @@ -329,7 +397,7 @@ command-line driver. Note: This command is identical (and internally converted) to: ledger -l "/expenses/|//john/" register - + - To include entries from another file into a specific account, use: !account ACCOUNT !include FILE @@ -66,14 +66,14 @@ static struct _init_amounts { commodity_t * commodity; commodity = commodity_t::find_commodity("s", true); - commodity->flags |= COMMODITY_STYLE_NOMARKET | COMMODITY_STYLE_BUILTIN; + commodity->flags() |= COMMODITY_STYLE_NOMARKET | COMMODITY_STYLE_BUILTIN; parse_conversion("1.0m", "60s"); parse_conversion("1.0h", "60m"); #if 0 commodity = commodity_t::find_commodity("b", true); - commodity->flags |= COMMODITY_STYLE_NOMARKET | COMMODITY_STYLE_BUILTIN; + commodity->flags() |= COMMODITY_STYLE_NOMARKET | COMMODITY_STYLE_BUILTIN; parse_conversion("1.00 Kb", "1024 b"); parse_conversion("1.00 Mb", "1024 Kb"); @@ -417,7 +417,7 @@ amount_t& amount_t::operator*=(const amount_t& amt) mpz_mul(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity)); quantity->prec += amt.quantity->prec; - unsigned int comm_prec = commodity().precision; + unsigned int 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; @@ -442,7 +442,7 @@ amount_t& amount_t::operator/=(const amount_t& amt) mpz_tdiv_q(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity)); quantity->prec += 6; - unsigned int comm_prec = commodity().precision; + unsigned int 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; @@ -504,12 +504,12 @@ amount_t::operator bool() const if (! quantity) return false; - if (quantity->prec <= commodity().precision) { + if (quantity->prec <= commodity().precision()) { return mpz_sgn(MPZ(quantity)) != 0; } else { mpz_set(temp, MPZ(quantity)); if (commodity_) - mpz_ui_pow_ui(divisor, 10, quantity->prec - commodity().precision); + mpz_ui_pow_ui(divisor, 10, quantity->prec - commodity().precision()); else mpz_ui_pow_ui(divisor, 10, quantity->prec); mpz_tdiv_q(temp, temp, divisor); @@ -559,9 +559,9 @@ amount_t amount_t::value(const std::time_t moment) const { if (quantity) { commodity_t& comm = commodity(); - if (! (comm.flags & COMMODITY_STYLE_NOMARKET)) + if (! (comm.flags() & COMMODITY_STYLE_NOMARKET)) if (amount_t amt = comm.value(moment)) - return (amt * *this).round(amt.commodity().precision); + return (amt * *this).round(amt.commodity().precision()); } return *this; } @@ -604,24 +604,23 @@ std::string amount_t::quantity_string() const unsigned short precision; if (comm == *commodity_t::null_commodity || - comm.flags & COMMODITY_STYLE_VARIABLE) { + comm.flags() & COMMODITY_STYLE_VARIABLE) { mpz_ui_pow_ui(divisor, 10, quantity->prec); mpz_tdiv_qr(quotient, remainder, MPZ(quantity), divisor); precision = quantity->prec; } - else if (comm.precision < quantity->prec) { - mpz_round(rquotient, MPZ(quantity), quantity->prec, - comm.precision); - mpz_ui_pow_ui(divisor, 10, comm.precision); + else if (comm.precision() < quantity->prec) { + mpz_round(rquotient, MPZ(quantity), quantity->prec, comm.precision()); + mpz_ui_pow_ui(divisor, 10, comm.precision()); mpz_tdiv_qr(quotient, remainder, rquotient, divisor); - precision = comm.precision; + precision = comm.precision(); } - else if (comm.precision > quantity->prec) { - mpz_ui_pow_ui(divisor, 10, comm.precision - quantity->prec); + else if (comm.precision() > quantity->prec) { + mpz_ui_pow_ui(divisor, 10, comm.precision() - quantity->prec); mpz_mul(rquotient, MPZ(quantity), divisor); - mpz_ui_pow_ui(divisor, 10, comm.precision); + mpz_ui_pow_ui(divisor, 10, comm.precision()); mpz_tdiv_qr(quotient, remainder, rquotient, divisor); - precision = comm.precision; + precision = comm.precision(); } else if (quantity->prec) { mpz_ui_pow_ui(divisor, 10, quantity->prec); @@ -682,11 +681,11 @@ std::ostream& operator<<(std::ostream& _out, const amount_t& amt) } amount_t base(amt); - if (amt.commodity().larger) { + if (amt.commodity().larger()) { amount_t last(amt); - while (last.commodity().larger) { - last /= *last.commodity().larger; - last.commodity_ = last.commodity().larger->commodity_; + while (last.commodity().larger()) { + last /= *last.commodity().larger(); + last.commodity_ = last.commodity().larger()->commodity_; if (ledger::abs(last) < 1) break; base = last; @@ -712,24 +711,24 @@ std::ostream& operator<<(std::ostream& _out, const amount_t& amt) unsigned short precision; if (comm == *commodity_t::null_commodity || - comm.flags & COMMODITY_STYLE_VARIABLE) { + comm.flags() & COMMODITY_STYLE_VARIABLE) { 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) { + 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); + comm.precision()); + mpz_ui_pow_ui(divisor, 10, comm.precision()); mpz_tdiv_qr(quotient, remainder, rquotient, divisor); - precision = comm.precision; + precision = comm.precision(); } - else if (comm.precision > base.quantity->prec) { - mpz_ui_pow_ui(divisor, 10, comm.precision - base.quantity->prec); + 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_ui_pow_ui(divisor, 10, comm.precision()); mpz_tdiv_qr(quotient, remainder, rquotient, divisor); - precision = comm.precision; + precision = comm.precision(); } else if (base.quantity->prec) { mpz_ui_pow_ui(divisor, 10, base.quantity->prec); @@ -755,12 +754,18 @@ std::ostream& operator<<(std::ostream& _out, const amount_t& amt) return _out; } - if (! (comm.flags & COMMODITY_STYLE_SUFFIXED)) { - if (comm.quote) - out << "\"" << comm.symbol << "\""; - else + if (! (comm.flags() & COMMODITY_STYLE_SUFFIXED)) { + if (comm.quote) { + std::string::size_type idx = comm.symbol.find(" {", 0); + if (idx != std::string::npos) + out << "\"" << comm.symbol.substr(0, idx) << "\"" + << comm.symbol.substr(idx); + else + out << "\"" << comm.symbol << "\""; + } else { out << comm.symbol; - if (comm.flags & COMMODITY_STYLE_SEPARATED) + } + if (comm.flags() & COMMODITY_STYLE_SEPARATED) out << " "; } @@ -770,7 +775,7 @@ std::ostream& operator<<(std::ostream& _out, const amount_t& amt) if (mpz_sgn(quotient) == 0) { out << '0'; } - else if (! (comm.flags & COMMODITY_STYLE_THOUSANDS)) { + else if (! (comm.flags() & COMMODITY_STYLE_THOUSANDS)) { char * p = mpz_get_str(NULL, 10, quotient); out << p; std::free(p); @@ -799,7 +804,7 @@ std::ostream& operator<<(std::ostream& _out, const amount_t& amt) i != strs.rend(); i++) { if (printed) { - out << (comm.flags & COMMODITY_STYLE_EUROPEAN ? '.' : ','); + out << (comm.flags() & COMMODITY_STYLE_EUROPEAN ? '.' : ','); out.width(3); out.fill('0'); } @@ -810,7 +815,7 @@ std::ostream& operator<<(std::ostream& _out, const amount_t& amt) } if (precision) { - out << ((comm.flags & COMMODITY_STYLE_EUROPEAN) ? ',' : '.'); + out << ((comm.flags() & COMMODITY_STYLE_EUROPEAN) ? ',' : '.'); out.width(precision); out.fill('0'); @@ -820,13 +825,19 @@ std::ostream& operator<<(std::ostream& _out, const amount_t& amt) std::free(p); } - if (comm.flags & COMMODITY_STYLE_SUFFIXED) { - if (comm.flags & COMMODITY_STYLE_SEPARATED) + if (comm.flags() & COMMODITY_STYLE_SUFFIXED) { + if (comm.flags() & COMMODITY_STYLE_SEPARATED) out << " "; - if (comm.quote) - out << "\"" << comm.symbol << "\""; - else + if (comm.quote) { + std::string::size_type idx = comm.symbol.find(" {", 0); + if (idx != std::string::npos) + out << "\"" << comm.symbol.substr(0, idx) << "\"" + << comm.symbol.substr(idx); + else + out << "\"" << comm.symbol << "\""; + } else { out << comm.symbol; + } } mpz_clear(quotient); @@ -878,6 +889,7 @@ void amount_t::parse(std::istream& in, unsigned short flags) std::string symbol; std::string quant; + std::string price; unsigned int comm_flags = COMMODITY_STYLE_DEFAULTS; bool negative = false; @@ -923,6 +935,34 @@ void amount_t::parse(std::istream& in, unsigned short flags) commodity_ = commodity_t::find_commodity(symbol, true); + // If a per-unit price is specified for this amount, record it by + // creating a specialized commodity at that price. This is a + // different from the whole transaction cost, which is associated + // with the transaction and not with the amount. For example, a + // sale of 10 AAPL shares for $100 (the cost) is a different thing + // from selling 10 AAPL {$10} (where $10 is the commodity price) for + // $50, which implies a capital loss of $50. + + if (peek_next_nonws(in) == '{') { + char c; + char buf[256]; + in.get(c); + READ_INTO(in, buf, 255, c, c != '}'); + if (c == '}') + in.get(c); + else + throw amount_error("Commodity price lacks closing brace"); + + symbol = symbol + " {" + buf + "}"; + commodity_t * priced_commodity = + commodity_t::find_commodity(symbol, true); + + priced_commodity->price = new amount_t(buf); + priced_commodity->base = commodity_; + + commodity_ = priced_commodity; + } + // Determine the precision of the amount, based on the usage of // comma or period. @@ -940,12 +980,12 @@ void amount_t::parse(std::istream& in, unsigned short flags) } else if (last_comma != std::string::npos && (! commodity_t::default_commodity || - commodity_t::default_commodity->flags & COMMODITY_STYLE_EUROPEAN)) { + commodity_t::default_commodity->flags() & COMMODITY_STYLE_EUROPEAN)) { comm_flags |= COMMODITY_STYLE_EUROPEAN; quantity->prec = quant.length() - last_comma - 1; } else if (last_period != std::string::npos && - ! (commodity().flags & COMMODITY_STYLE_EUROPEAN)) { + ! (commodity().flags() & COMMODITY_STYLE_EUROPEAN)) { quantity->prec = quant.length() - last_period - 1; } else { @@ -955,9 +995,9 @@ void amount_t::parse(std::istream& in, unsigned short flags) // Set the commodity's flags and precision accordingly if (newly_created || ! (flags & AMOUNT_PARSE_NO_MIGRATE)) { - commodity().flags |= comm_flags; - if (quantity->prec > commodity().precision) - commodity().precision = quantity->prec; + commodity().flags() |= comm_flags; + if (quantity->prec > commodity().precision()) + commodity().precision() = quantity->prec; } // Now we have the final number. Remove commas and periods, if @@ -991,9 +1031,9 @@ void amount_t::parse(std::istream& in, unsigned short flags) void amount_t::reduce() { - while (commodity_ && commodity().smaller) { - *this *= *commodity().smaller; - commodity_ = commodity().smaller->commodity_; + while (commodity_ && commodity().smaller()) { + *this *= *commodity().smaller(); + commodity_ = commodity().smaller()->commodity_; } } @@ -1014,12 +1054,12 @@ void parse_conversion(const std::string& larger_str, larger *= smaller; if (larger.commodity()) { - larger.commodity().smaller = new amount_t(smaller); - larger.commodity().flags = (smaller.commodity().flags | - COMMODITY_STYLE_NOMARKET); + larger.commodity().smaller() = new amount_t(smaller); + larger.commodity().flags() = (smaller.commodity().flags() | + COMMODITY_STYLE_NOMARKET); } if (smaller.commodity()) - smaller.commodity().larger = new amount_t(larger); + smaller.commodity().larger() = new amount_t(larger); } @@ -1161,6 +1201,8 @@ void commodity_t::set_symbol(const std::string& sym) quote = false; for (const char * p = symbol.c_str(); *p; p++) if (std::isspace(*p) || std::isdigit(*p) || *p == '-' || *p == '.') { + if (std::isspace(*p) && *(p + 1) == '{') + return; quote = true; return; } @@ -1168,15 +1210,15 @@ void commodity_t::set_symbol(const std::string& sym) void commodity_t::add_price(const std::time_t date, const amount_t& price) { - if (! history) - history = new history_t; + if (! history()) + history() = new history_t; - history_map::iterator i = history->prices.find(date); - if (i != history->prices.end()) { + history_map::iterator i = history()->prices.find(date); + if (i != history()->prices.end()) { (*i).second = price; } else { std::pair<history_map::iterator, bool> result - = history->prices.insert(history_pair(date, price)); + = history()->prices.insert(history_pair(date, price)); assert(result.second); } } @@ -1195,9 +1237,9 @@ commodity_t * commodity_t::find_commodity(const std::string& symbol, // Start out the new commodity with the default commodity's flags // and precision, if one has been defined. if (default_commodity) - commodity->flags = - (default_commodity->flags & ~(COMMODITY_STYLE_THOUSANDS | - COMMODITY_STYLE_NOMARKET)); + commodity->flags() = + (default_commodity->flags() & ~(COMMODITY_STYLE_THOUSANDS | + COMMODITY_STYLE_NOMARKET)); return commodity; } @@ -1210,23 +1252,23 @@ amount_t commodity_t::value(const std::time_t moment) std::time_t age = 0; amount_t price; - if (history) { - assert(history->prices.size() > 0); + if (history()) { + assert(history()->prices.size() > 0); if (moment == 0) { - history_map::reverse_iterator r = history->prices.rbegin(); + history_map::reverse_iterator r = history()->prices.rbegin(); age = (*r).first; price = (*r).second; } else { - history_map::iterator i = history->prices.lower_bound(moment); - if (i == history->prices.end()) { - history_map::reverse_iterator r = history->prices.rbegin(); + history_map::iterator i = history()->prices.lower_bound(moment); + if (i == history()->prices.end()) { + history_map::reverse_iterator r = history()->prices.rbegin(); age = (*r).first; price = (*r).second; } else { age = (*i).first; if (std::difftime(moment, age) != 0) { - if (i != history->prices.begin()) { + if (i != history()->prices.begin()) { --i; age = (*i).first; price = (*i).second; @@ -1242,8 +1284,8 @@ amount_t commodity_t::value(const std::time_t moment) if (updater) (*updater)(*this, moment, age, - (history && history->prices.size() > 0 ? - (*history->prices.rbegin()).first : 0), price); + (history() && history()->prices.size() > 0 ? + (*history()->prices.rbegin()).first : 0), price); return price; } @@ -73,6 +73,7 @@ class amount_t void clear_commodity() { commodity_ = NULL; } + amount_t base_amount() const; bool null() const { return ! quantity && ! commodity_; @@ -316,21 +317,47 @@ class commodity_t std::time_t last_lookup; }; + history_t * history_; + + history_t *& history() { + return base ? base->history() : history_; + } + const std::string symbol; bool quote; - std::string name; - std::string note; - unsigned short precision; - unsigned short flags; + std::string name_; + std::string note_; + unsigned short precision_; + unsigned short flags_; ident_t ident; - history_t * history; - amount_t * smaller; - amount_t * larger; + amount_t * smaller_; + amount_t * larger_; + commodity_t * base; // base commodity for AAPL {$10} is AAPL + amount_t * price; // its price is therefore $10.00 + + std::string& name() { + return base ? base->name() : name_; + } + std::string& note() { + return base ? base->note() : note_; + } + unsigned short& precision() { + return base ? base->precision() : precision_; + } + unsigned short& flags() { + return base ? base->flags() : flags_; + } + amount_t *& smaller() { + return base ? base->smaller() : smaller_; + } + amount_t *& larger() { + return base ? base->larger() : larger_; + } // If set, this global function pointer is called to determine // whether prices have been updated in the meanwhile. - static updater_t * updater; + static updater_t * updater; // This map remembers all commodities that have been defined. @@ -361,14 +388,15 @@ class commodity_t commodity_t(const std::string& _symbol = "", unsigned int _precision = 0, unsigned int _flags = COMMODITY_STYLE_DEFAULTS) - : precision(_precision), flags(_flags), history(NULL), - smaller(NULL), larger(NULL) { + : precision_(_precision), flags_(_flags), history_(NULL), + smaller_(NULL), larger_(NULL), base(NULL), price(NULL) { set_symbol(_symbol); } ~commodity_t() { - if (history) delete history; - if (smaller) delete smaller; - if (larger) delete larger; + if (history_) delete history_; + if (smaller_) delete smaller_; + if (larger_) delete larger_; + if (price) delete price; } operator bool() const { @@ -385,8 +413,8 @@ class commodity_t void add_price(const std::time_t date, const amount_t& price); bool remove_price(const std::time_t date) { - if (history) { - history_map::size_type n = history->prices.erase(date); + if (history_) { + history_map::size_type n = history_->prices.erase(date); return n > 0; } return false; @@ -398,7 +426,7 @@ class commodity_t if (symbol.empty() && this != null_commodity) return false; - if (precision > 16) + if (precision_ > 16) return false; return true; @@ -417,6 +445,17 @@ inline commodity_t& amount_t::commodity() const { return *commodity_; } +inline amount_t amount_t::base_amount() const { + if (commodity_ && commodity_->price) { + amount_t temp(*this); + assert(commodity_->base); + temp.set_commodity(*(commodity_->base)); + return temp; + } else { + return *this; + } +} + class amount_error : public std::exception { std::string reason; public: @@ -34,6 +34,22 @@ balance_t balance_t::value(const std::time_t moment) const return temp; } +balance_t balance_t::factor_price() const +{ + balance_t temp; + + for (amounts_map::const_iterator i = amounts.begin(); + i != amounts.end(); + i++) { + if ((*i).second.commodity().price) + temp += *((*i).second.commodity().price) * (*i).second; + else + temp += (*i).second; + } + + return temp; +} + struct compare_amount_commodities { bool operator()(const amount_t * left, const amount_t * right) const { return left->commodity().symbol < right->commodity().symbol; @@ -426,6 +426,7 @@ class balance_t amount_t amount(const commodity_t& commodity) const; balance_t value(const std::time_t moment) const; + balance_t factor_price() const; void write(std::ostream& out, const int first_width, @@ -443,7 +444,7 @@ class balance_t i != amounts.end(); i++) if ((*i).second.commodity()) - (*i).second = (*i).second.round((*i).second.commodity().precision); + (*i).second = (*i).second.round((*i).second.commodity().precision()); } }; @@ -11,18 +11,10 @@ namespace ledger { static unsigned long binary_magic_number = 0xFFEED765; -#ifdef USE_EDITOR #ifdef DEBUG_ENABLED -static unsigned long format_version = 0x00020589; +static unsigned long format_version = 0x0002050b; #else -static unsigned long format_version = 0x00020588; -#endif -#else -#ifdef DEBUG_ENABLED -static unsigned long format_version = 0x00020509; -#else -static unsigned long format_version = 0x00020508; -#endif +static unsigned long format_version = 0x0002050a; #endif static account_t ** accounts; @@ -272,8 +264,10 @@ inline void read_binary_value_expr(char *& data, value_expr_t *& expr) expr = new value_expr_t(kind); - read_binary_value_expr(data, expr->left); - read_binary_value_expr(data, expr->right); + if (kind > value_expr_t::TERMINALS) { + read_binary_value_expr(data, expr->left); + if (expr->left) expr->left->acquire(); + } switch (expr->kind) { case value_expr_t::CONSTANT_T: @@ -283,15 +277,30 @@ inline void read_binary_value_expr(char *& data, value_expr_t *& expr) read_binary_long(data, expr->constant_i); break; case value_expr_t::CONSTANT_A: - read_binary_amount(data, expr->constant_a); + expr->constant_a = new amount_t(); + read_binary_amount(data, *(expr->constant_a)); break; - case value_expr_t::F_FUNC: - read_binary_string(data, expr->constant_s); + case value_expr_t::CONSTANT_V: + assert(0); + break; + + case value_expr_t::F_CODE_MASK: + case value_expr_t::F_PAYEE_MASK: + case value_expr_t::F_NOTE_MASK: + case value_expr_t::F_ACCOUNT_MASK: + case value_expr_t::F_SHORT_ACCOUNT_MASK: + case value_expr_t::F_COMMODITY_MASK: + if (read_binary_number<unsigned char>(data) == 1) + read_binary_mask(data, expr->mask); break; - } - if (read_binary_number<unsigned char>(data) == 1) - read_binary_mask(data, expr->mask); + default: + if (kind > value_expr_t::TERMINALS) { + read_binary_value_expr(data, expr->right); + if (expr->right) expr->right->acquire(); + } + break; + } } @@ -301,18 +310,22 @@ inline void read_binary_transaction(char *& data, transaction_t * xact) read_binary_long(data, xact->_date_eff); xact->account = accounts[read_binary_long<account_t::ident_t>(data) - 1]; - if (read_binary_number<char>(data) == 1) + if (read_binary_number<char>(data) == 1) { read_binary_value_expr(data, xact->amount_expr); - else + if (xact->amount_expr) xact->amount_expr->acquire(); + } else { read_binary_amount(data, xact->amount); + } if (*data++ == 1) { xact->cost = new amount_t; - if (read_binary_number<char>(data) == 1) + if (read_binary_number<char>(data) == 1) { read_binary_value_expr(data, xact->cost_expr); - else + if (xact->cost_expr) xact->cost_expr->acquire(); + } else { read_binary_amount(data, *xact->cost); + } } else { xact->cost = NULL; } @@ -322,12 +335,10 @@ inline void read_binary_transaction(char *& data, transaction_t * xact) xact->flags |= TRANSACTION_BULK_ALLOC; read_binary_string(data, &xact->note); -#ifdef USE_EDITOR xact->beg_pos = read_binary_long<unsigned long>(data); read_binary_long(data, xact->beg_line); xact->end_pos = read_binary_long<unsigned long>(data); read_binary_long(data, xact->end_line); -#endif xact->data = NULL; @@ -340,13 +351,11 @@ inline void read_binary_transaction(char *& data, transaction_t * xact) inline void read_binary_entry_base(char *& data, entry_base_t * entry, transaction_t *& xact_pool, bool& finalize) { -#ifdef USE_EDITOR read_binary_long(data, entry->src_idx); entry->beg_pos = read_binary_long<unsigned long>(data); read_binary_long(data, entry->beg_line); entry->end_pos = read_binary_long<unsigned long>(data); read_binary_long(data, entry->end_line); -#endif bool ignore_calculated = read_binary_number<char>(data) == 1; @@ -376,8 +385,10 @@ inline void read_binary_auto_entry(char *& data, auto_entry_t * entry, { bool ignore; read_binary_entry_base(data, entry, xact_pool, ignore); - read_binary_string(data, &entry->predicate_string); - entry->predicate = new item_predicate<transaction_t>(entry->predicate_string); + value_expr_t * expr; + read_binary_value_expr(data, expr); + // the item_predicate constructor will acquire the reference + entry->predicate = new item_predicate<transaction_t>(expr); } inline void read_binary_period_entry(char *& data, period_entry_t * entry, @@ -396,10 +407,10 @@ inline commodity_t * read_binary_commodity(char *& data) read_binary_string(data, *(const_cast<std::string *>(&commodity->symbol))); read_binary_number(data, commodity->quote); - read_binary_string(data, commodity->name); - read_binary_string(data, commodity->note); - read_binary_number(data, commodity->precision); - read_binary_number(data, commodity->flags); + read_binary_string(data, commodity->name_); + read_binary_string(data, commodity->note_); + read_binary_number(data, commodity->precision_); + read_binary_number(data, commodity->flags_); read_binary_long(data, commodity->ident); return commodity; @@ -421,12 +432,12 @@ inline void read_binary_commodity_extra(char *& data, // Upon insertion, amt will be copied, which will cause the amount // to be duplicated (and thus not lost when the journal's // item_pool is deleted. - if (! commodity->history) - commodity->history = new commodity_t::history_t; - commodity->history->prices.insert(history_pair(when, amt)); + if (! commodity->history_) + commodity->history_ = new commodity_t::history_t; + commodity->history_->prices.insert(history_pair(when, amt)); } - if (commodity->history) - read_binary_long(data, commodity->history->last_lookup); + if (commodity->history_) + read_binary_long(data, commodity->history_->last_lookup); unsigned char flag; @@ -434,14 +445,23 @@ inline void read_binary_commodity_extra(char *& data, if (flag) { amount_t amt; read_binary_amount(data, amt); - commodity->smaller = new amount_t(amt); + commodity->smaller_ = new amount_t(amt); + } + + flag = read_binary_number<unsigned char>(data); + if (flag) { + amount_t amt; + read_binary_amount(data, amt); + commodity->larger_ = new amount_t(amt); } flag = read_binary_number<unsigned char>(data); if (flag) { amount_t amt; read_binary_amount(data, amt); - commodity->larger = new amount_t(amt); + commodity->price = new amount_t(amt); + commodity->base = + commodities[read_binary_long<commodity_t::ident_t>(data) - 1]; } } @@ -570,7 +590,7 @@ unsigned int read_binary_journal(std::istream& in, commodities = commodities_next = new commodity_t *[c_count]; for (commodity_t::ident_t i = 0; i < c_count; i++) { commodity_t * commodity = read_binary_commodity(data); - if (! (commodity->flags & COMMODITY_STYLE_BUILTIN)) { + if (! (commodity->flags_ & COMMODITY_STYLE_BUILTIN)) { if (commodity->symbol == "") { commodity_t::commodities.erase(commodity->symbol); delete commodity_t::null_commodity; @@ -737,7 +757,7 @@ void write_binary_mask(std::ostream& out, mask_t * mask) write_binary_string(out, mask->pattern); } -void write_binary_value_expr(std::ostream& out, value_expr_t * expr) +void write_binary_value_expr(std::ostream& out, const value_expr_t * expr) { if (expr == NULL) { write_binary_number<unsigned char>(out, 0); @@ -746,8 +766,9 @@ void write_binary_value_expr(std::ostream& out, value_expr_t * expr) write_binary_number<unsigned char>(out, 1); write_binary_number(out, expr->kind); - write_binary_value_expr(out, expr->left); - write_binary_value_expr(out, expr->right); + + if (expr->kind > value_expr_t::TERMINALS) + write_binary_value_expr(out, expr->left); switch (expr->kind) { case value_expr_t::CONSTANT_T: @@ -757,19 +778,32 @@ void write_binary_value_expr(std::ostream& out, value_expr_t * expr) write_binary_long(out, expr->constant_i); break; case value_expr_t::CONSTANT_A: - write_binary_amount(out, expr->constant_a); + write_binary_amount(out, *(expr->constant_a)); break; - case value_expr_t::F_FUNC: - write_binary_string(out, expr->constant_s); + case value_expr_t::CONSTANT_V: + assert(0); break; - } - if (expr->mask) { - write_binary_number<char>(out, 1); - write_binary_mask(out, expr->mask); - } else { - write_binary_number<char>(out, 0); + case value_expr_t::F_CODE_MASK: + case value_expr_t::F_PAYEE_MASK: + case value_expr_t::F_NOTE_MASK: + case value_expr_t::F_ACCOUNT_MASK: + case value_expr_t::F_SHORT_ACCOUNT_MASK: + case value_expr_t::F_COMMODITY_MASK: + if (expr->mask) { + write_binary_number<char>(out, 1); + write_binary_mask(out, expr->mask); + } else { + write_binary_number<char>(out, 0); + } + break; + + default: + if (expr->kind > value_expr_t::TERMINALS) + write_binary_value_expr(out, expr->right); + break; } + } void write_binary_transaction(std::ostream& out, transaction_t * xact, @@ -808,23 +842,19 @@ void write_binary_transaction(std::ostream& out, transaction_t * xact, write_binary_number(out, xact->flags); write_binary_string(out, xact->note); -#ifdef USE_EDITOR write_binary_long(out, xact->beg_pos); write_binary_long(out, xact->beg_line); write_binary_long(out, xact->end_pos); write_binary_long(out, xact->end_line); -#endif } void write_binary_entry_base(std::ostream& out, entry_base_t * entry) { -#ifdef USE_EDITOR write_binary_long(out, entry->src_idx); write_binary_long(out, entry->beg_pos); write_binary_long(out, entry->beg_line); write_binary_long(out, entry->end_pos); write_binary_long(out, entry->end_line); -#endif bool ignore_calculated = false; for (transactions_list::const_iterator i = entry->transactions.begin(); @@ -856,7 +886,7 @@ void write_binary_entry(std::ostream& out, entry_t * entry) void write_binary_auto_entry(std::ostream& out, auto_entry_t * entry) { write_binary_entry_base(out, entry); - write_binary_string(out, entry->predicate_string); + write_binary_value_expr(out, entry->predicate->predicate); } void write_binary_period_entry(std::ostream& out, period_entry_t * entry) @@ -869,39 +899,47 @@ void write_binary_commodity(std::ostream& out, commodity_t * commodity) { write_binary_string(out, commodity->symbol); write_binary_number(out, commodity->quote); - write_binary_string(out, commodity->name); - write_binary_string(out, commodity->note); - write_binary_number(out, commodity->precision); - write_binary_number(out, commodity->flags); + write_binary_string(out, commodity->name_); + write_binary_string(out, commodity->note_); + write_binary_number(out, commodity->precision_); + write_binary_number(out, commodity->flags_); commodity->ident = ++commodity_index; write_binary_long(out, commodity->ident); } void write_binary_commodity_extra(std::ostream& out, commodity_t * commodity) { - if (! commodity->history) { + if (! commodity->history_) { write_binary_long<unsigned long>(out, 0); } else { - write_binary_long<unsigned long>(out, commodity->history->prices.size()); - for (history_map::const_iterator i = commodity->history->prices.begin(); - i != commodity->history->prices.end(); + write_binary_long<unsigned long>(out, commodity->history_->prices.size()); + for (history_map::const_iterator i = commodity->history_->prices.begin(); + i != commodity->history_->prices.end(); i++) { write_binary_long(out, (*i).first); write_binary_amount(out, (*i).second); } - write_binary_long(out, commodity->history->last_lookup); + write_binary_long(out, commodity->history_->last_lookup); + } + + if (commodity->smaller_) { + write_binary_number<unsigned char>(out, 1); + write_binary_amount(out, *commodity->smaller_); + } else { + write_binary_number<unsigned char>(out, 0); } - if (commodity->smaller) { + if (commodity->larger_) { write_binary_number<unsigned char>(out, 1); - write_binary_amount(out, *commodity->smaller); + write_binary_amount(out, *commodity->larger_); } else { write_binary_number<unsigned char>(out, 0); } - if (commodity->larger) { + if (commodity->price) { write_binary_number<unsigned char>(out, 1); - write_binary_amount(out, *commodity->larger); + write_binary_amount(out, *commodity->price); + write_binary_long(out, commodity->base->ident); } else { write_binary_number<unsigned char>(out, 0); } @@ -17,15 +17,43 @@ namespace ledger { -std::list<option_t> config_options; +namespace { + config_t * config = NULL; + + void xact_amount(value_t& result, const details_t& details, value_expr_t *) + { + if (transaction_has_xdata(*details.xact) && + transaction_xdata_(*details.xact).dflags & TRANSACTION_COMPOSITE) + result = transaction_xdata_(*details.xact).composite_amount; + else + result = details.xact->amount; + } + + void xact_running_total(value_t& result, const details_t& details, + value_expr_t *) + { + result = transaction_xdata_(*details.xact).total; + } -static config_t * config = NULL; + void account_amount(value_t& result, const details_t& details, + value_expr_t *) + { + if (account_has_xdata(*details.account)) + result = account_xdata(*details.account).value; + } + + void account_total(value_t& result, const details_t& details, + value_expr_t *) + { + if (account_has_xdata(*details.account)) + result = account_xdata(*details.account).total; + } +} void config_t::reset() { - amount_expr = "a"; - total_expr = "O"; - total_expr_template = "#"; + amount_expr = "a"; + total_expr = "O"; pricing_leeway = 24 * 3600; budget_flags = BUDGET_NO_BUDGET; balance_format = "%20T %2_%-a\n"; @@ -34,8 +62,8 @@ void config_t::reset() wide_register_format = ("%D %-.35P %-.38A %22.108t %22.132T\n%/" "%48|%-.38A %22.108t %22.132T\n"); csv_register_format = "\"%D\",\"%P\",\"%A\",\"%t\",\"%T\"\n"; - plot_amount_format = "%D %(St)\n"; - plot_total_format = "%D %(ST)\n"; + plot_amount_format = "%D %(S(t))\n"; + plot_total_format = "%D %(S(T))\n"; print_format = "\n%d %Y%C%P\n %-34W %12o%n\n%/ %-34W %12o%n\n"; write_hdr_format = "%d %Y%C%P\n"; write_xact_format = " %-34W %12o%n\n"; @@ -62,6 +90,7 @@ void config_t::reset() show_revalued = false; show_revalued_only = false; download_quotes = false; + debug_mode = false; use_cache = false; cache_dirty = false; @@ -133,7 +162,7 @@ config_t::regexps_to_predicate(const std::string& command, if (! show_related && ! show_all_related) { if (! display_predicate.empty()) display_predicate += "&"; - else if (! show_empty) + if (! show_empty) display_predicate += "T&"; if (add_predicate == 2) @@ -143,6 +172,8 @@ config_t::regexps_to_predicate(const std::string& command, display_predicate += ")/"; } else if (! show_empty) { + if (! display_predicate.empty()) + display_predicate += "&"; display_predicate += "T"; } } @@ -178,6 +209,18 @@ void config_t::process_environment(char ** envp, const std::string& tag) config = NULL; } +static std::string expand_value_expr(const std::string& tmpl, + const std::string& expr) +{ + std::string xp = tmpl; + for (std::string::size_type i = xp.find('#'); + i != std::string::npos; + i = xp.find('#')) + xp = (std::string(xp, 0, i) + "(" + expr + ")" + + std::string(xp, i + 1)); + return xp; +} + void config_t::process_options(const std::string& command, strings_list::iterator arg, strings_list::iterator args_end) @@ -244,32 +287,8 @@ void config_t::process_options(const std::string& command, // Setup the values of %t and %T, used in format strings - try { - ledger::amount_expr.reset(parse_value_expr(amount_expr)); - } - catch (const value_expr_error& err) { - throw error(std::string("In amount expression '") + amount_expr + - "': " + err.what()); - } - - std::string expr = total_expr_template; - for (std::string::size_type i = expr.find('#'); - i != std::string::npos; - i = expr.find('#')) - expr = (std::string(expr, 0, i) + "(" + total_expr + ")" + - std::string(expr, i + 1)); - - DEBUG_PRINT("ledger.config.total_expr", - "Total expression template = " << total_expr_template); - DEBUG_PRINT("ledger.config.total_expr", - "Total expression is now = " << expr); - try { - ledger::total_expr.reset(parse_value_expr(expr)); - } - catch (const value_expr_error& err) { - throw error(std::string("In total expression '") + expr + "': " + - err.what()); - } + ledger::amount_expr.reset(new value_expr(amount_expr)); + ledger::total_expr.reset(new value_expr(total_expr)); // If downloading is to be supported, configure the updater @@ -683,6 +702,10 @@ OPT_BEGIN(account, "a:") { config->account = optarg; } OPT_END(account); +OPT_BEGIN(debug, "") { + config->debug_mode = true; +} OPT_END(debug); + ////////////////////////////////////////////////////////////////////// // // Report filtering @@ -755,6 +778,10 @@ OPT_BEGIN(actual, "L") { config->predicate += "L"; } OPT_END(actual); +OPT_BEGIN(lots, "") { + show_lots = true; +} OPT_END(lots); + ////////////////////////////////////////////////////////////////////// // // Output customization @@ -1013,6 +1040,12 @@ OPT_BEGIN(basis, "B") { config->total_expr = "B"; } OPT_END(basis); +OPT_BEGIN(price, "I") { + show_lots = true; // don't show them, but use in calculations + config->amount_expr = "i"; + config->total_expr = "I"; +} OPT_END(price); + OPT_BEGIN(market, "V") { config->show_revalued = true; @@ -1034,15 +1067,98 @@ OPT_BEGIN(gain, "G") { } OPT_END(gain); OPT_BEGIN(average, "A") { - config->total_expr_template = "A#"; + config->total_expr = expand_value_expr("A(#)", config->total_expr); } OPT_END(average); OPT_BEGIN(deviation, "D") { - config->total_expr_template = "t-A#"; + config->total_expr = expand_value_expr("t-A(#)", config->total_expr); } OPT_END(deviation); OPT_BEGIN(percentage, "%") { - config->total_expr_template = "^#&{100.0%}*(#/^#)"; + config->total_expr = expand_value_expr("^#&{100.0%}*(#/^#)", config->total_expr); } OPT_END(percentage); +////////////////////////////////////////////////////////////////////// + +option_t config_options[CONFIG_OPTIONS_SIZE] = { + { "account", 'a', true, opt_account, false }, + { "actual", 'L', false, opt_actual, false }, + { "add-budget", '\0', false, opt_add_budget, false }, + { "amount", 't', true, opt_amount, false }, + { "amount-data", 'j', false, opt_amount_data, false }, + { "average", 'A', false, opt_average, false }, + { "balance-format", '\0', true, opt_balance_format, false }, + { "basis", 'B', false, opt_basis, false }, + { "begin", 'b', true, opt_begin, false }, + { "budget", '\0', false, opt_budget, false }, + { "by-payee", 'P', false, opt_by_payee, false }, + { "cache", '\0', true, opt_cache, false }, + { "cleared", 'C', false, opt_cleared, false }, + { "collapse", 'n', false, opt_collapse, false }, + { "comm-as-payee", 'x', false, opt_comm_as_payee, false }, + { "csv-register-format", '\0', true, opt_csv_register_format, false }, + { "current", 'c', false, opt_current, false }, + { "date-format", 'y', true, opt_date_format, false }, + { "debug", '\0', false, opt_debug, false }, + { "deviation", 'D', false, opt_deviation, false }, + { "display", 'd', true, opt_display, false }, + { "dow", '\0', false, opt_dow, false }, + { "download", 'Q', false, opt_download, false }, + { "effective", '\0', false, opt_effective, false }, + { "empty", 'E', false, opt_empty, false }, + { "end", 'e', true, opt_end, false }, + { "equity-format", '\0', true, opt_equity_format, false }, + { "file", 'f', true, opt_file, false }, + { "forecast", '\0', true, opt_forecast, false }, + { "format", 'F', true, opt_format, false }, + { "full-help", 'H', false, opt_full_help, false }, + { "gain", 'G', false, opt_gain, false }, + { "head", '\0', true, opt_head, false }, + { "help", 'h', false, opt_help, false }, + { "help-calc", '\0', false, opt_help_calc, false }, + { "help-comm", '\0', false, opt_help_comm, false }, + { "help-disp", '\0', false, opt_help_disp, false }, + { "init-file", 'i', true, opt_init_file, false }, + { "input-date-format", '\0', true, opt_input_date_format, false }, + { "limit", 'l', true, opt_limit, false }, + { "lots", '\0', false, opt_lots, false }, + { "market", 'V', false, opt_market, false }, + { "monthly", 'M', false, opt_monthly, false }, + { "no-cache", '\0', false, opt_no_cache, false }, + { "output", 'o', true, opt_output, false }, + { "pager", '\0', true, opt_pager, false }, + { "percentage", '%', false, opt_percentage, false }, + { "performance", 'g', false, opt_performance, false }, + { "period", 'p', true, opt_period, false }, + { "period-sort", '\0', true, opt_period_sort, false }, + { "plot-amount-format", '\0', true, opt_plot_amount_format, false }, + { "plot-total-format", '\0', true, opt_plot_total_format, false }, + { "price", 'I', false, opt_price, false }, + { "price-db", '\0', true, opt_price_db, false }, + { "price-exp", 'Z', true, opt_price_exp, false }, + { "prices-format", '\0', true, opt_prices_format, false }, + { "print-format", '\0', true, opt_print_format, false }, + { "quantity", 'O', false, opt_quantity, false }, + { "real", 'R', false, opt_real, false }, + { "reconcile", '\0', true, opt_reconcile, false }, + { "reconcile-date", '\0', true, opt_reconcile_date, false }, + { "register-format", '\0', true, opt_register_format, false }, + { "related", 'r', false, opt_related, false }, + { "sort", 'S', true, opt_sort, false }, + { "subtotal", 's', false, opt_subtotal, false }, + { "tail", '\0', true, opt_tail, false }, + { "total", 'T', true, opt_total, false }, + { "total-data", 'J', false, opt_total_data, false }, + { "totals", '\0', false, opt_totals, false }, + { "unbudgeted", '\0', false, opt_unbudgeted, false }, + { "uncleared", 'U', false, opt_uncleared, false }, + { "version", 'v', false, opt_version, false }, + { "weekly", 'W', false, opt_weekly, false }, + { "wide", 'w', false, opt_wide, false }, + { "wide-register-format", '\0', true, opt_wide_register_format, false }, + { "write-hdr-format", '\0', true, opt_write_hdr_format, false }, + { "write-xact-format", '\0', true, opt_write_xact_format, false }, + { "yearly", 'Y', false, opt_yearly, false }, +}; + } // namespace ledger @@ -42,7 +42,6 @@ class config_t std::string sort_string; std::string amount_expr; std::string total_expr; - std::string total_expr_template; std::string forecast_limit; std::string reconcile_balance; std::string reconcile_date; @@ -66,6 +65,7 @@ class config_t bool download_quotes; bool use_cache; bool cache_dirty; + bool debug_mode; config_t() { reset(); @@ -73,7 +73,6 @@ class config_t config_t(const config_t&) { assert(0); } - void reset(); void regexps_to_predicate(const std::string& command, @@ -100,24 +99,15 @@ class config_t std::list<item_handler<transaction_t> *>& ptrs); }; -extern std::list<option_t> config_options; +#define CONFIG_OPTIONS_SIZE 78 +extern option_t config_options[CONFIG_OPTIONS_SIZE]; void option_help(std::ostream& out); -struct declared_option_handler : public option_handler { - declared_option_handler(const std::string& label, - const std::string& opt_chars) { - add_option_handler(config_options, label, opt_chars, *this); - } -}; - -#define OPT_BEGIN(tag, chars) \ - static struct opt_ ## tag ## _handler \ - : public declared_option_handler { \ - opt_ ## tag ## _handler() : declared_option_handler(#tag, chars) {} \ - virtual void operator()(const char * optarg) +#define OPT_BEGIN(tag, chars) \ + void opt_ ## tag(const char * optarg) -#define OPT_END(tag) } opt_ ## tag ## _handler_obj +#define OPT_END(tag) } // namespace ledger diff --git a/configure.in b/configure.in index ce50acc5..88acb260 100644 --- a/configure.in +++ b/configure.in @@ -200,15 +200,6 @@ AC_ARG_ENABLE(debug, esac],[debug=false]) AM_CONDITIONAL(DEBUG, test x$debug = xtrue) -AC_ARG_ENABLE(emacs, - [ --enable-emacs Turn on Emacs support], - [case "${enableval}" in - yes) emacs=true ;; - no) emacs=false ;; - *) AC_MSG_ERROR(bad value ${enableval} for --enable-emacs) ;; - esac],[emacs=true]) -AM_CONDITIONAL(USE_EDITOR, test x$emacs = xtrue) - # Checks for header files. AC_STDC_HEADERS AC_HAVE_HEADERS(sys/stat.h) @@ -55,6 +55,9 @@ bool _debug_active(const char * const cls); #define DEBUG(cls) (_debug_active(cls)) #define DEBUG_() DEBUG(_debug_cls) +#define DEBUG_IF(cls) if (_debug_active(cls)) +#define DEBUG_IF_() if (_debug_active(_debug_cls)) + #define DEBUG_PRINT(cls, x) \ if (_debug_stream && _debug_active(cls)) { \ *_debug_stream << x << std::endl; \ @@ -91,6 +94,8 @@ void operator delete[](void*, const std::nothrow_t&) throw(); #define DEBUG_CLASS(cls) #define DEBUG(cls) 0 #define DEBUG_() 0 +#define DEBUG_IF(cls) +#define DEBUG_IF_() #define DEBUG_PRINT(cls, x) #define DEBUG_PRINT_(x) #define DEBUG_PRINT_TIME(cls, x) @@ -23,7 +23,8 @@ entry_t * derive_new_entry(journal_t& journal, mask_t regexp(*i++); - for (entries_list::reverse_iterator j = journal.entries.rbegin(); + entries_list::reverse_iterator j; + for (j = journal.entries.rbegin(); j != journal.entries.rend(); j++) if (regexp.match((*j)->payee)) { @@ -33,23 +34,42 @@ entry_t * derive_new_entry(journal_t& journal, added->payee = matching ? matching->payee : regexp.pattern; - if (i == end) { - if (! matching) - throw error("Could not find a matching payee"); + if (! matching) { + account_t * acct; + if (i == end || ((*i)[0] == '-' || std::isdigit((*i)[0]))) { + acct = journal.find_account("Expenses"); + } + else if (i != end) { + acct = journal.find_account_re(*i); + if (! acct) + acct = journal.find_account(*i); + assert(acct); + i++; + } + if (i == end) + added->add_transaction(new transaction_t(acct)); + else + added->add_transaction(new transaction_t(acct, amount_t(*i++))); + + if (journal.basket) + acct = journal.basket; + else + acct = journal.find_account("Equity"); + + added->add_transaction(new transaction_t(acct)); + } + else if (i == end) { // If no argument were given but the payee, assume the user wants // to see the same transaction as last time. added->code = matching->code; - for (transactions_list::iterator j = matching->transactions.begin(); - j != matching->transactions.end(); - j++) - added->add_transaction(new transaction_t(**j)); + for (transactions_list::iterator k = matching->transactions.begin(); + k != matching->transactions.end(); + k++) + added->add_transaction(new transaction_t(**k)); } else if ((*i)[0] == '-' || std::isdigit((*i)[0])) { - if (! matching) - throw error("Could not determine the account to draw from"); - transaction_t * m_xact, * xact, * first; m_xact = matching->transactions.front(); @@ -68,67 +88,65 @@ entry_t * derive_new_entry(journal_t& journal, account_t * acct = journal.find_account_re(*i); if (! acct) acct = journal.find_account(*i); - if (acct) - added->transactions.back()->account = acct; + assert(acct); + added->transactions.back()->account = acct; } } else { while (i != end) { - std::string& re_pat(*i++); - account_t * acct = NULL; - commodity_t * cmdty = NULL; - - if (matching) { - mask_t acct_regex(re_pat); - - for (transactions_list::const_iterator x - = matching->transactions.begin(); - x != matching->transactions.end(); - x++) { - if (acct_regex.match((*x)->account->fullname())) { - acct = (*x)->account; - cmdty = &(*x)->amount.commodity(); - break; - } + std::string& re_pat(*i++); + account_t * acct = NULL; + amount_t * amt = NULL; + + mask_t acct_regex(re_pat); + + for (; j != journal.entries.rend(); j++) + if (regexp.match((*j)->payee)) { + entry_t * entry = *j; + for (transactions_list::const_iterator x = + entry->transactions.begin(); + x != entry->transactions.end(); + x++) + if (acct_regex.match((*x)->account->fullname())) { + acct = (*x)->account; + amt = &(*x)->amount; + matching = entry; + goto found; + } } - } + found: if (! acct) acct = journal.find_account_re(re_pat); if (! acct) acct = journal.find_account(re_pat); + transaction_t * xact; if (i == end) { - added->add_transaction(new transaction_t(acct)); - goto done; - } - - transaction_t * xact = new transaction_t(acct, amount_t(*i++)); - if (! xact->amount.commodity()) { - if (cmdty) - xact->amount.set_commodity(*cmdty); - else if (commodity_t::default_commodity) - xact->amount.set_commodity(*commodity_t::default_commodity); + if (amt) + xact = new transaction_t(acct, *amt); + else + xact = new transaction_t(acct); + } else { + xact = new transaction_t(acct, amount_t(*i++)); + if (! xact->amount.commodity()) { + if (amt) + xact->amount.set_commodity(amt->commodity()); + else if (commodity_t::default_commodity) + xact->amount.set_commodity(*commodity_t::default_commodity); + } } - added->add_transaction(xact); } - account_t * draw_acct; - if (matching) - draw_acct = matching->transactions.back()->account; - else if (journal.basket) - draw_acct = journal.basket; - else - throw error("Could not determine the account to draw from"); - - transaction_t * xact = new transaction_t(draw_acct); - added->add_transaction(xact); + assert(matching->transactions.back()->account); + if (account_t * draw_acct = matching->transactions.back()->account) + added->add_transaction(new transaction_t(draw_acct)); } done: - if (! added->finalize() || - ! run_hooks(journal.entry_finalize_hooks, *added)) + if (! run_hooks(journal.entry_finalize_hooks, *added) || + ! added->finalize()) throw error("Failed to finalize derived entry (check commodities)"); return added.release(); @@ -162,13 +162,9 @@ element_t * format_t::parse_elements(const std::string& fmt) throw format_error("Missing ')'"); current->type = element_t::VALUE_EXPR; - try { - current->val_expr = parse_value_expr(std::string(b, p)); - } - catch (value_expr_error& err) { - throw value_expr_error(std::string("In format expression '") + - std::string(b, p) + "': " + err.what()); - } + + assert(current->val_expr == NULL); + current->val_expr = new value_expr(std::string(b, p)); break; } @@ -293,22 +289,22 @@ void format_t::format(std::ostream& out_str, const details_t& details) const case element_t::AMOUNT: case element_t::TOTAL: case element_t::VALUE_EXPR: { - value_expr_t * expr = NULL; + value_calc * calc = NULL; switch (elem->type) { - case element_t::AMOUNT: expr = amount_expr.get(); break; - case element_t::TOTAL: expr = total_expr.get(); break; - case element_t::VALUE_EXPR: expr = elem->val_expr; break; + case element_t::AMOUNT: calc = amount_expr.get(); break; + case element_t::TOTAL: calc = total_expr.get(); break; + case element_t::VALUE_EXPR: calc = elem->val_expr; break; default: assert(0); break; } - if (! expr) + if (! calc) break; value_t value; balance_t * bal = NULL; - expr->compute(value, details); + calc->compute(value, details); switch (value.type) { case value_t::BOOLEAN: @@ -348,9 +344,9 @@ void format_t::format(std::ostream& out_str, const details_t& details) const amount_t unit_cost = *details.xact->cost / details.xact->amount; commodity_t& comm(unit_cost.commodity()); - bool has_flag = comm.flags & COMMODITY_STYLE_VARIABLE; + bool has_flag = comm.flags() & COMMODITY_STYLE_VARIABLE; if (! has_flag) - unit_cost.commodity().flags |= COMMODITY_STYLE_VARIABLE; + unit_cost.commodity().flags() |= COMMODITY_STYLE_VARIABLE; std::ostringstream stream; stream << details.xact->amount << " @ " << unit_cost; @@ -358,7 +354,7 @@ void format_t::format(std::ostream& out_str, const details_t& details) const use_disp = true; if (! has_flag) - unit_cost.commodity().flags &= ~COMMODITY_STYLE_VARIABLE; + unit_cost.commodity().flags() &= ~COMMODITY_STYLE_VARIABLE; } else if (details.entry) { unsigned int xacts_count = 0; @@ -388,7 +384,6 @@ void format_t::format(std::ostream& out_str, const details_t& details) const } break; -#ifdef USE_EDITOR case element_t::SOURCE: if (details.entry && details.entry->journal) { int idx = details.entry->src_idx; @@ -441,7 +436,6 @@ void format_t::format(std::ostream& out_str, const details_t& details) const if (details.xact) out << details.xact->end_line; break; -#endif case element_t::DATE_STRING: { std::time_t date = 0; @@ -617,7 +611,7 @@ void format_t::format(std::ostream& out_str, const details_t& details) const } } -format_transactions::format_transactions(std::ostream& _output_stream, +format_transactions::format_transactions(std::ostream& _output_stream, const std::string& format) : output_stream(_output_stream), last_entry(NULL), last_xact(NULL) { @@ -731,7 +725,7 @@ bool disp_subaccounts_p(const account_t& account, return display; } -bool display_account(const account_t& account, +bool display_account(const account_t& account, const item_predicate<account_t>& disp_pred) { // Never display an account that has already been displayed. @@ -51,7 +51,7 @@ struct element_t kind_t type; std::string chars; - value_expr_t * val_expr; + value_expr * val_expr; struct element_t * next; @@ -45,11 +45,9 @@ static std::istream * instreamp; static unsigned int offset; static XML_Parser parser; static std::string path; -#ifdef USE_EDITOR static unsigned int src_idx; static istream_pos_type beg_pos; static unsigned long beg_line; -#endif static transaction_t::state_t curr_state; @@ -148,13 +146,11 @@ static void endElement(void *userData, const char *name) have_error = "The above entry does not balance"; delete curr_entry; } else { -#ifdef USE_EDITOR curr_entry->src_idx = src_idx; curr_entry->beg_pos = beg_pos; curr_entry->beg_line = beg_line; curr_entry->end_pos = instreamp->tellg(); curr_entry->end_line = XML_GetCurrentLineNumber(parser) - offset; -#endif count++; } @@ -180,7 +176,7 @@ static void endElement(void *userData, const char *name) if (default_commodity) { curr_quant.set_commodity(*default_commodity); - value = curr_quant.round(default_commodity->precision); + value = curr_quant.round(default_commodity->precision()); if (curr_value.commodity() == *default_commodity) curr_value = value; @@ -193,12 +189,10 @@ static void endElement(void *userData, const char *name) if (value != curr_value) xact->cost = new amount_t(curr_value); -#ifdef USE_EDITOR xact->beg_pos = beg_pos; xact->beg_line = beg_line; xact->end_pos = instreamp->tellg(); xact->end_line = XML_GetCurrentLineNumber(parser) - offset; -#endif // Clear the relevant variables for the next run curr_state = transaction_t::UNCLEARED; @@ -264,23 +258,23 @@ static void dataHandler(void *userData, const char *s, int len) std::string symbol(s, len); commodity_t * comm = commodity_t::find_commodity(symbol, true); if (symbol != "$" && symbol != "USD") - comm->flags |= COMMODITY_STYLE_SEPARATED; + comm->flags() |= COMMODITY_STYLE_SEPARATED; account_comms.insert(account_comm_pair(curr_account, comm)); } else if (curr_entry) { std::string symbol(s, len); entry_comm = commodity_t::find_commodity(symbol, true); if (symbol != "$" && symbol != "USD") - entry_comm->flags |= COMMODITY_STYLE_SEPARATED; + entry_comm->flags() |= COMMODITY_STYLE_SEPARATED; } break; case COMM_NAME: - curr_comm->name = std::string(s, len); + curr_comm->name() = std::string(s, len); break; case COMM_PREC: - curr_comm->precision = len - 1; + curr_comm->precision() = len - 1; break; case ENTRY_NUM: @@ -313,8 +307,8 @@ static void dataHandler(void *userData, const char *s, int len) curr_value = convert_number(std::string(s, len), &precision); curr_value.set_commodity(*entry_comm); - if (precision > entry_comm->precision) - entry_comm->precision = precision; + if (precision > entry_comm->precision()) + entry_comm->precision() = precision; break; } @@ -382,9 +376,7 @@ unsigned int gnucash_parser_t::parse(std::istream& in, instreamp = ∈ path = original_file ? *original_file : "<gnucash>"; -#ifdef USE_EDITOR src_idx = journal->sources.size() - 1; -#endif // GnuCash uses the USD commodity without defining it, which really // means $. @@ -401,10 +393,8 @@ unsigned int gnucash_parser_t::parse(std::istream& in, XML_SetCharacterDataHandler(parser, dataHandler); while (in.good() && ! in.eof()) { -#ifdef USE_EDITOR beg_pos = in.tellg(); beg_line = (XML_GetCurrentLineNumber(parser) - offset) + 1; -#endif in.getline(buf, BUFSIZ - 1); std::strcat(buf, "\n"); @@ -13,6 +13,14 @@ const std::string version = PACKAGE_VERSION; bool transaction_t::use_effective_date = false; +transaction_t::~transaction_t() +{ + DEBUG_PRINT("ledger.memory.dtors", "dtor transaction_t"); + if (cost) delete cost; + if (amount_expr) amount_expr->release(); + if (cost_expr) cost_expr->release(); +} + std::time_t transaction_t::actual_date() const { if (_date == 0 && entry) @@ -96,6 +104,10 @@ bool entry_base_t::finalize() } else { balance += *p; } + + if ((*x)->cost && (*x)->amount.commodity().price) + balance += (*((*x)->amount.commodity().price) * (*x)->amount - + *((*x)->cost)); } } @@ -44,12 +44,10 @@ class transaction_t state_t state; unsigned short flags; std::string note; -#ifdef USE_EDITOR istream_pos_type beg_pos; unsigned long beg_line; istream_pos_type end_pos; unsigned long end_line; -#endif mutable void * data; static bool use_effective_date; @@ -58,13 +56,9 @@ class transaction_t : entry(NULL), _date(0), _date_eff(0), account(_account), amount_expr(NULL), cost(NULL), cost_expr(NULL), state(UNCLEARED), flags(TRANSACTION_NORMAL), -#ifdef USE_EDITOR - beg_pos(0), beg_line(0), end_pos(0), end_line(0), -#endif - data(NULL) { + beg_pos(0), beg_line(0), end_pos(0), end_line(0), data(NULL) { DEBUG_PRINT("ledger.memory.ctors", "ctor transaction_t"); } - transaction_t(account_t * _account, const amount_t& _amount, unsigned int _flags = TRANSACTION_NORMAL, @@ -72,30 +66,18 @@ class transaction_t : entry(NULL), _date(0), _date_eff(0), account(_account), amount(_amount), amount_expr(NULL), cost(NULL), cost_expr(NULL), state(UNCLEARED), flags(_flags), note(_note), -#ifdef USE_EDITOR - beg_pos(0), beg_line(0), end_pos(0), end_line(0), -#endif - data(NULL) { + beg_pos(0), beg_line(0), end_pos(0), end_line(0), data(NULL) { DEBUG_PRINT("ledger.memory.ctors", "ctor transaction_t"); } - transaction_t(const transaction_t& xact) : entry(xact.entry), _date(0), _date_eff(0), account(xact.account), amount(xact.amount), amount_expr(NULL), cost(xact.cost ? new amount_t(*xact.cost) : NULL), cost_expr(NULL), state(xact.state), flags(xact.flags), note(xact.note), -#ifdef USE_EDITOR - beg_pos(0), beg_line(0), end_pos(0), end_line(0), -#endif - data(NULL) { + beg_pos(0), beg_line(0), end_pos(0), end_line(0), data(NULL) { DEBUG_PRINT("ledger.memory.ctors", "ctor transaction_t"); } - - ~transaction_t() { - DEBUG_PRINT("ledger.memory.dtors", "dtor transaction_t"); - if (cost) - delete cost; - } + ~transaction_t(); std::time_t actual_date() const; std::time_t effective_date() const; @@ -124,26 +106,20 @@ class entry_base_t { public: journal_t * journal; -#ifdef USE_EDITOR unsigned long src_idx; istream_pos_type beg_pos; unsigned long beg_line; istream_pos_type end_pos; unsigned long end_line; -#endif transactions_list transactions; entry_base_t() : journal(NULL), -#ifdef USE_EDITOR beg_pos(0), beg_line(0), end_pos(0), end_line(0) -#endif { DEBUG_PRINT("ledger.memory.ctors", "ctor entry_base_t"); } entry_base_t(const entry_base_t& e) : journal(NULL), -#ifdef USE_EDITOR beg_pos(0), beg_line(0), end_pos(0), end_line(0) -#endif { DEBUG_PRINT("ledger.memory.ctors", "ctor entry_base_t"); for (transactions_list::const_iterator i = e.transactions.begin(); @@ -104,10 +104,10 @@ int parse_and_report(int argc, char * argv[], char * envp[]) command = "p"; else if (command == "output") command = "w"; -#ifdef USE_EDITOR else if (command == "emacs") command = "x"; -#endif + else if (command == "lisp") + command = "x"; else if (command == "xml") command = "X"; else if (command == "entry") @@ -122,6 +122,20 @@ int parse_and_report(int argc, char * argv[], char * envp[]) config.register_format = config.csv_register_format; command = "r"; } + else if (command == "parse") { + value_auto_ptr expr(ledger::parse_value_expr(*arg)); + if (config.debug_mode) { + ledger::dump_value_expr(std::cout, expr.get()); + std::cout << std::endl; + } + value_t result; + expr->compute(result, details_t()); + std::cout << result << std::endl; + return 0; + } + else if (command == "expr") { + // this gets done below... + } else throw error(std::string("Unrecognized command '") + command + "'"); @@ -211,6 +225,20 @@ int parse_and_report(int argc, char * argv[], char * envp[]) } #endif + // Are we handling the parse or expr commands? Do so now. + + if (command == "expr") { + value_auto_ptr expr(ledger::parse_value_expr(*arg)); + if (config.debug_mode) { + ledger::dump_value_expr(std::cout, expr.get()); + std::cout << std::endl; + } + value_t result; + expr->compute(result, details_t()); + std::cout << result << std::endl; + return 0; + } + // Compile the format strings const std::string * format; @@ -245,10 +273,8 @@ int parse_and_report(int argc, char * argv[], char * envp[]) formatter = new set_account_value; else if (command == "p" || command == "e") formatter = new format_entries(*out, *format); -#ifdef USE_EDITOR else if (command == "x") formatter = new format_emacs_transactions(*out); -#endif else if (command == "X") { #if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE) formatter = new format_xml_entries(*out, config.show_totals); @@ -1,4 +1,5 @@ #include "option.h" +#include "config.h" #include "debug.h" #include <iostream> @@ -6,149 +7,125 @@ #include "util.h" -void add_option_handler(std::list<option_t>& options, - const std::string& label, - const std::string& opt_chars, - option_handler& option) -{ - option_t opt; - - char buf[128]; - char * p = buf; - for (const char * q = label.c_str(); - *q && p - buf < 128; - q++) - if (*q == '_') - *p++ = '-'; - else - *p++ = *q; - *p = '\0'; - opt.long_opt = buf; - - if (! opt_chars.empty()) { - if (opt_chars[0] != ':') - opt.short_opt = opt_chars[0]; - - if (opt_chars[opt_chars.length() - 1] == ':') - opt.wants_arg = true; +namespace { + inline void process_option(option_t * opt, const char * arg = NULL) { + if (! opt->handled) { + opt->handler(arg); + opt->handled = true; + } } - opt.handler = &option; - - options.push_front(opt); -} - -namespace { - inline void process_option(const option_t& opt, - const char * arg = NULL) { - if (! opt.handler->handled) { - (*opt.handler)(arg); - opt.handler->handled = true; + option_t * search_options(option_t * array, const char * name) + { + int first = 0; + int last = CONFIG_OPTIONS_SIZE; + while (first <= last) { + int mid = (first + last) / 2; // compute mid point. + + int result; + if ((result = (int)name[0] - (int)array[mid].long_opt[0]) == 0) + result = std::strcmp(name, array[mid].long_opt); + + 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 &array[mid]; } + return NULL; + } + + option_t * search_options(option_t * array, const char letter) + { + for (int i = 0; i < CONFIG_OPTIONS_SIZE; i++) + if (letter == array[i].short_opt) + return &array[i]; + return NULL; } } -bool process_option(std::list<option_t>& options, - const std::string& opt, const char * arg) +bool process_option(option_t * options, const std::string& name, + const char * arg) { - for (std::list<option_t>::iterator i = options.begin(); - i != options.end(); - i++) - if ((*i).long_opt == opt) { - if (! (*i).handler->handled) { - (*(*i).handler)(arg); - (*i).handler->handled = true; - return true; - } - break; - } - + option_t * opt = search_options(options, name.c_str()); + if (opt != NULL && ! opt->handled) { + opt->handler(arg); + opt->handled = true; + return true; + } return false; } -void process_arguments(std::list<option_t>& options, - int argc, char ** argv, const bool anywhere, - std::list<std::string>& args) +void process_arguments(option_t * options, int argc, char ** argv, + const bool anywhere, std::list<std::string>& args) { int index = 0; - for (char ** i = argv; index < argc; i++, index++) { + for (char ** i = argv; *i; i++) { if ((*i)[0] != '-') { if (anywhere) { args.push_back(*i); continue; } else { - for (; index < argc; i++, index++) + for (; *i; i++) args.push_back(*i); break; } } - // --long-option + // --long-option or -s again: + option_t * opt = NULL; + char * value = NULL; + if ((*i)[1] == '-') { if ((*i)[2] == '\0') break; - for (std::list<option_t>::iterator j = options.begin(); - j != options.end(); - j++) - if ((*j).wants_arg) { - if (const char * p = std::strchr(*i + 2, '=')) { - if ((*j).long_opt == std::string(*i + 2, p - (*i + 2))) { - process_option(*j, p + 1); - goto next; - } - } - else if ((*j).long_opt == *i + 2) { - if (++index >= argc) - throw option_error(std::string("missing option argument for ") + - *i); - process_option(*j, argv[index]); - i++; - goto next; - } - } - else if ((*j).long_opt == *i + 2) { - process_option(*j); - goto next; - } - - throw option_error(std::string("illegal option ") + *i); + char * name = *i + 2; + if (char * p = std::strchr(name, '=')) { + *p++ = '\0'; + value = p; + } + + opt = search_options(options, name); + if (opt == NULL) + throw option_error(std::string("illegal option --") + name); + + if (opt->wants_arg && value == NULL) { + value = *++i; + if (value == NULL) + throw option_error(std::string("missing option argument for --") + + name); + } + process_option(opt, value); } else { - for (std::list<option_t>::iterator j = options.begin(); - j != options.end(); - j++) - if ((*i)[1] == (*j).short_opt) { - if ((*j).wants_arg) { - if (++index >= argc) - throw option_error(std::string("missing argument for option ") + - *i); - process_option(*j, argv[index]); - i++; - goto next; - } else { - process_option(*j); - if ((*i)[2]) { - std::strcpy(*i + 1, *i + 2); - goto again; - } - goto next; - } - } - - throw option_error(std::string("illegal option -- ") + (*i)[1]); + char c = (*i)[1]; + opt = search_options(options, c); + if (opt == NULL) + throw option_error(std::string("illegal option -") + c); + + if (opt->wants_arg) { + value = *++i; + if (value == NULL) + throw option_error(std::string("missing option argument for -") + c); + } } + assert(opt); + assert(value == NULL || opt->wants_arg); + process_option(opt, value); + next: ; } } -void process_environment(std::list<option_t>& options, - char ** envp, const std::string& tag) +void process_environment(option_t * options, char ** envp, + const std::string& tag) { const char * tag_p = tag.c_str(); - int tag_len = tag.length(); + unsigned int tag_len = tag.length(); for (char ** p = envp; *p; p++) if (! tag_p || std::strncmp(*p, tag_p, tag_len) == 0) { @@ -5,20 +5,14 @@ #include <string> #include <exception> -struct option_handler { - bool handled; - option_handler() : handled(false) {} - virtual ~option_handler() {} - virtual void operator()(const char * arg = NULL) = 0; -}; +typedef void (*handler_t)(const char * arg); struct option_t { - char short_opt; - std::string long_opt; - bool wants_arg; - option_handler * handler; - - option_t() : short_opt(0), wants_arg(false), handler(NULL) {} + const char * long_opt; + char short_opt; + bool wants_arg; + handler_t handler; + bool handled; }; class option_error : public std::exception { @@ -32,14 +26,11 @@ class option_error : public std::exception { } }; -void add_option_handler(std::list<option_t>& options, const std::string& label, - const std::string& opt_chars, option_handler& option); -bool process_option(std::list<option_t>& options, - const std::string& opt, const char * arg = NULL); -void process_arguments(std::list<option_t>& options, - int argc, char ** argv, const bool anywhere, - std::list<std::string>& args); -void process_environment(std::list<option_t>& options, - char ** envp, const std::string& tag); +bool process_option(option_t * options, const std::string& opt, + const char * arg = NULL); +void process_arguments(option_t * options, int argc, char ** argv, + const bool anywhere, std::list<std::string>& args); +void process_environment(option_t * options, char ** envp, + const std::string& tag); #endif // _OPTION_H @@ -63,7 +63,6 @@ unsigned int qif_parser_t::parse(std::istream& in, src_idx = journal->sources.size() - 1; linenum = 1; -#ifdef USE_EDITOR istream_pos_type beg_pos = 0; unsigned long beg_line = 0; @@ -72,9 +71,6 @@ unsigned int qif_parser_t::parse(std::istream& in, beg_pos = in.tellg(); \ beg_line = linenum; \ } -#else -#define SET_BEG_POS_AND_LINE() -#endif while (in.good() && ! in.eof()) { char c; @@ -119,16 +115,16 @@ unsigned int qif_parser_t::parse(std::istream& in, get_line(in); xact->amount.parse(line); - unsigned long flags = xact->amount.commodity().flags; - unsigned short prec = xact->amount.commodity().precision; + unsigned long flags = xact->amount.commodity().flags(); + unsigned short prec = xact->amount.commodity().precision(); if (! def_commodity) def_commodity = commodity_t::find_commodity("$", true); xact->amount.set_commodity(*def_commodity); - def_commodity->flags |= flags; - if (prec > def_commodity->precision) - def_commodity->precision = prec; + def_commodity->flags() |= flags; + if (prec > def_commodity->precision()) + def_commodity->precision() = prec; if (c == '$') { saw_splits = true; @@ -221,13 +217,11 @@ unsigned int qif_parser_t::parse(std::istream& in, } if (journal->add_entry(entry.get())) { -#ifdef USE_EDITOR entry->src_idx = src_idx; entry->beg_pos = beg_pos; entry->beg_line = beg_line; entry->end_pos = in.tellg(); entry->end_line = linenum; -#endif entry.release(); count++; } @@ -240,9 +234,7 @@ unsigned int qif_parser_t::parse(std::istream& in, saw_splits = false; saw_category = false; total = NULL; -#ifdef USE_EDITOR beg_line = 0; -#endif break; } @@ -22,12 +22,12 @@ void quotes_by_script::operator()(commodity_t& commodity, DEBUG_PRINT_TIME_(moment); DEBUG_PRINT_TIME_(date); DEBUG_PRINT_TIME_(last); - if (commodity.history) - DEBUG_PRINT_TIME_(commodity.history->last_lookup); + if (commodity.history()) + DEBUG_PRINT_TIME_(commodity.history()->last_lookup); DEBUG_PRINT_("pricing_leeway is " << pricing_leeway); - if ((commodity.history && - std::difftime(now, commodity.history->last_lookup) < pricing_leeway) || + if ((commodity.history() && + std::difftime(now, commodity.history()->last_lookup) < pricing_leeway) || std::difftime(now, last) < pricing_leeway || (price && std::difftime(moment, date) > 0 && std::difftime(moment, date) <= pricing_leeway)) @@ -61,7 +61,7 @@ void quotes_by_script::operator()(commodity_t& commodity, price.parse(buf); commodity.add_price(now, price); - commodity.history->last_lookup = now; + commodity.history()->last_lookup = now; cache_dirty = true; if (price && ! price_db.empty()) { @@ -290,13 +290,49 @@ transaction_t * parse_transaction(char * line, account_t * account) if (amount) { xact->amount_expr = parse_amount(amount, xact->amount, AMOUNT_PARSE_NO_REDUCE, *xact); + if (xact->amount_expr) + xact->amount_expr->acquire(); if (price) { xact->cost = new amount_t; xact->cost_expr = parse_amount(price, *xact->cost, AMOUNT_PARSE_NO_MIGRATE, *xact); + if (xact->cost_expr) + xact->cost_expr->acquire(); if (per_unit) { + if (xact->amount.commodity().base == NULL) { + std::string symbol = xact->amount.commodity().symbol; + symbol += " {"; + symbol += price; + symbol += "}"; + + commodity_t * priced_commodity = + commodity_t::find_commodity(symbol, true); + + priced_commodity->price = new amount_t(*xact->cost); + priced_commodity->base = &xact->amount.commodity(); + + xact->amount.set_commodity(*priced_commodity); + } + *xact->cost *= xact->amount; - *xact->cost = xact->cost->round(xact->cost->commodity().precision); + *xact->cost = xact->cost->round(xact->cost->commodity().precision()); + } + else if (xact->amount.commodity().base == NULL) { + amount_t cost(*xact->cost); + cost /= xact->amount; + cost = cost.round(cost.commodity().precision()); + + std::string symbol; + std::ostringstream symstr(symbol); + symstr << xact->amount.commodity().symbol << " {" << cost << "}"; + + commodity_t * priced_commodity = + commodity_t::find_commodity(symstr.str(), true); + + priced_commodity->price = new amount_t(cost); + priced_commodity->base = &xact->amount.commodity(); + + xact->amount.set_commodity(*priced_commodity); } } xact->amount.reduce(); @@ -318,10 +354,9 @@ bool parse_transactions(std::istream& in, in.getline(line, MAX_LINE); if (in.eof()) break; -#ifdef USE_EDITOR beg_pos += istream_pos_type(std::strlen(line) + 1); -#endif linenum++; + if (line[0] == ' ' || line[0] == '\t' || line[0] == '\r') { char * p = skip_ws(line); if (! *p || *p == '\r') @@ -403,20 +438,17 @@ entry_t * parse_entry(std::istream& in, char * line, account_t * master, TIMER_START(entry_xacts); -#ifdef USE_EDITOR istream_pos_type end_pos; unsigned long beg_line = linenum; -#endif + while (! in.eof() && (in.peek() == ' ' || in.peek() == '\t')) { line[0] = '\0'; in.getline(line, MAX_LINE); if (in.eof() && line[0] == '\0') break; -#ifdef USE_EDITOR end_pos = beg_pos + istream_pos_type(std::strlen(line) + 1); -#endif - linenum++; + if (line[0] == ' ' || line[0] == '\t' || line[0] == '\r') { char * p = skip_ws(line); if (! *p || *p == '\r') @@ -547,20 +579,17 @@ unsigned int textual_parser_t::parse(std::istream& in, src_idx = journal->sources.size() - 1; linenum = 1; -#ifdef USE_EDITOR istream_pos_type beg_pos = in.tellg(); istream_pos_type end_pos; unsigned long beg_line = linenum; -#endif + while (in.good() && ! in.eof()) { try { in.getline(line, MAX_LINE); if (in.eof()) break; - linenum++; -#ifdef USE_EDITOR end_pos = beg_pos + istream_pos_type(std::strlen(line) + 1); -#endif + linenum++; switch (line[0]) { case '\0': @@ -674,7 +703,7 @@ unsigned int textual_parser_t::parse(std::istream& in, parse_symbol(p, symbol); commodity_t * commodity = commodity_t::find_commodity(symbol, true); - commodity->flags |= COMMODITY_STYLE_NOMARKET; + commodity->flags() |= COMMODITY_STYLE_NOMARKET; break; } @@ -732,13 +761,11 @@ unsigned int textual_parser_t::parse(std::istream& in, if (pe->finalize()) { extend_entry_base(journal, *pe); journal->period_entries.push_back(pe); -#ifdef USE_EDITOR pe->src_idx = src_idx; pe->beg_pos = beg_pos; pe->beg_line = beg_line; pe->end_pos = end_pos; pe->end_line = linenum; -#endif } else { throw parse_error(path, linenum, "Period entry failed to balance"); } @@ -746,16 +773,15 @@ unsigned int textual_parser_t::parse(std::istream& in, break; } + case '@': case '!': { // directive char * p = next_element(line); std::string word(line + 1); if (word == "include") { push_var<std::string> save_path(path); push_var<unsigned int> save_src_idx(src_idx); -#ifdef USE_EDITOR push_var<istream_pos_type> save_beg_pos(beg_pos); push_var<istream_pos_type> save_end_pos(end_pos); -#endif push_var<unsigned int> save_linenum(linenum); path = p; @@ -799,26 +825,25 @@ unsigned int textual_parser_t::parse(std::istream& in, assert(result.second); } } + else if (word == "def") { + if (! global_scope.get()) + init_value_expr(); + value_auto_ptr expr(parse_boolean_expr(p, global_scope.get())); + } break; } default: { unsigned int first_line = linenum; -#ifdef USE_EDITOR istream_pos_type pos = end_pos; -#else - istream_pos_type pos; -#endif if (entry_t * entry = parse_entry(in, line, account_stack.front(), *this, pos)) { if (journal->add_entry(entry)) { -#ifdef USE_EDITOR entry->src_idx = src_idx; entry->beg_pos = beg_pos; entry->beg_line = beg_line; entry->end_pos = end_pos; entry->end_line = linenum; -#endif count++; } else { print_entry(std::cerr, *entry); @@ -833,9 +858,7 @@ unsigned int textual_parser_t::parse(std::istream& in, } else { throw parse_error(path, first_line, "Failed to parse entry"); } -#ifdef USE_EDITOR end_pos = pos; -#endif break; } } @@ -854,9 +877,7 @@ unsigned int textual_parser_t::parse(std::istream& in, << err.what() << std::endl;; errors++; } -#ifdef USE_EDITOR beg_pos = end_pos; -#endif } done: @@ -876,8 +897,6 @@ unsigned int textual_parser_t::parse(std::istream& in, return count; } -#ifdef USE_EDITOR - void write_textual_journal(journal_t& journal, std::string path, item_handler<transaction_t>& formatter, const std::string& write_hdr_format, @@ -964,6 +983,4 @@ void write_textual_journal(journal_t& journal, std::string path, } } -#endif // USE_EDITOR - } // namespace ledger @@ -7,9 +7,10 @@ namespace ledger { -std::auto_ptr<value_expr_t> amount_expr; -std::auto_ptr<value_expr_t> total_expr; +std::auto_ptr<value_calc> amount_expr; +std::auto_ptr<value_calc> total_expr; +std::auto_ptr<scope_t> global_scope; std::time_t terminus; details_t::details_t(const transaction_t& _xact) @@ -40,7 +41,103 @@ bool compute_amount(value_expr_t * expr, amount_t& amt, transaction_t& xact) return true; } -void value_expr_t::compute(value_t& result, const details_t& details) const +value_expr_t::~value_expr_t() +{ + DEBUG_PRINT("ledger.memory.dtors", "dtor value_expr_t"); + + DEBUG_PRINT("ledger.valexpr.memory", "Destroying " << this); + assert(refc == 0); + + if (left) + left->release(); + + switch (kind) { + case F_CODE_MASK: + case F_PAYEE_MASK: + case F_NOTE_MASK: + case F_ACCOUNT_MASK: + case F_SHORT_ACCOUNT_MASK: + case F_COMMODITY_MASK: + assert(mask); + delete mask; + break; + + case CONSTANT_A: + assert(constant_a); + delete constant_a; + break; + + case CONSTANT_I: + case CONSTANT_T: + case CONSTANT_V: + break; + + default: + if (kind > TERMINALS && right) + right->release(); + break; + } +} + +namespace { + int count_leaves(value_expr_t * expr) + { + int count = 0; + if (expr->kind != value_expr_t::O_COM) { + count = 1; + } else { + count += count_leaves(expr->left); + count += count_leaves(expr->right); + } + return count; + } + + value_expr_t * reduce_leaves(value_expr_t * expr, const details_t& details, + value_expr_t * context) + { + if (expr == NULL) + return NULL; + + value_auto_ptr temp; + + if (expr->kind != value_expr_t::O_COM) { + if (expr->kind < value_expr_t::TERMINALS) { + temp.reset(expr); + } else { + temp.reset(new value_expr_t(value_expr_t::CONSTANT_V)); + temp->constant_v = new value_t(); + expr->compute(*(temp->constant_v), details, context); + } + } else { + temp.reset(new value_expr_t(value_expr_t::O_COM)); + temp->set_left(reduce_leaves(expr->left, details, context)); + temp->set_right(reduce_leaves(expr->right, details, context)); + } + return temp.release(); + } + + value_expr_t * find_leaf(value_expr_t * context, int goal, int& found) + { + if (context == NULL) + return NULL; + + if (context->kind != value_expr_t::O_COM) { + if (goal == found++) + return context; + } else { + value_expr_t * expr = find_leaf(context->left, goal, found); + if (expr) + return expr; + expr = find_leaf(context->right, goal, found); + if (expr) + return expr; + } + return NULL; + } +} + +void value_expr_t::compute(value_t& result, const details_t& details, + value_expr_t * context) const { switch (kind) { case CONSTANT_I: @@ -49,18 +146,25 @@ void value_expr_t::compute(value_t& result, const details_t& details) const case CONSTANT_T: result = long(constant_t); break; - case CONSTANT_A: - result = constant_a; + result = *constant_a; + break; + case CONSTANT_V: + result = *constant_v; + break; + + case F_NOW: + result = long(terminus); break; case AMOUNT: + case PRICE: if (details.xact) { if (transaction_has_xdata(*details.xact) && transaction_xdata_(*details.xact).dflags & TRANSACTION_COMPOSITE) result = transaction_xdata_(*details.xact).composite_amount; else - result = details.xact->amount; + result = translate_amount(details.xact->amount); } else if (details.account && account_has_xdata(*details.account)) { result = account_xdata(*details.account).value; @@ -68,6 +172,8 @@ void value_expr_t::compute(value_t& result, const details_t& details) const else { result = 0L; } + if (kind == PRICE) + result = result.factor_price(); break; case COST: @@ -89,7 +195,7 @@ void value_expr_t::compute(value_t& result, const details_t& details) const if (details.xact->cost) result = *details.xact->cost; else - result = details.xact->amount; + result = translate_amount(details.xact->amount); } } else if (details.account && account_has_xdata(*details.account)) { @@ -101,12 +207,15 @@ void value_expr_t::compute(value_t& result, const details_t& details) const break; case TOTAL: + case PRICE_TOTAL: if (details.xact && transaction_has_xdata(*details.xact)) result = transaction_xdata_(*details.xact).total; else if (details.account && account_has_xdata(*details.account)) result = account_xdata(*details.account).total; else result = 0L; + if (kind == PRICE_TOTAL) + result = result.factor_price(); break; case COST_TOTAL: if (details.xact && transaction_has_xdata(*details.xact)) @@ -119,21 +228,17 @@ void value_expr_t::compute(value_t& result, const details_t& details) const case VALUE_EXPR: if (amount_expr.get()) - amount_expr->compute(result, details); + amount_expr->compute(result, details, context); else result = 0L; break; case TOTAL_EXPR: if (total_expr.get()) - total_expr->compute(result, details); + total_expr->compute(result, details, context); else result = 0L; break; - case F_NOW: - result = long(terminus); - break; - case DATE: if (details.xact && transaction_has_xdata(*details.xact) && transaction_xdata_(*details.xact).date) @@ -198,43 +303,72 @@ void value_expr_t::compute(value_t& result, const details_t& details) const result = 0L; break; - case F_ARITH_MEAN: + case F_ARITH_MEAN: { + int index = 0; + value_expr_t * expr = find_leaf(context, 0, index); if (details.xact && transaction_has_xdata(*details.xact)) { - assert(left); - left->compute(result, details); + expr->compute(result, details, context); result /= amount_t(long(transaction_xdata_(*details.xact).index + 1)); } else if (details.account && account_has_xdata(*details.account) && account_xdata(*details.account).total_count) { - assert(left); - left->compute(result, details); + expr->compute(result, details, context); result /= amount_t(long(account_xdata(*details.account).total_count)); } else { result = 0L; } break; + } case F_PARENT: if (details.account && details.account->parent) - left->compute(result, details_t(*details.account->parent)); + left->compute(result, details_t(*details.account->parent), context); break; - case F_NEG: - assert(left); - left->compute(result, details); - result.negate(); + case F_ABS: { + int index = 0; + value_expr_t * expr = find_leaf(context, 0, index); + expr->compute(result, details, context); + result.abs(); break; + } - case F_ABS: - assert(left); - left->compute(result, details); - result.abs(); + case F_COMMODITY: { + int index = 0; + value_expr_t * expr = find_leaf(context, 0, index); + expr->compute(result, details, context); + if (result.type != value_t::AMOUNT) + throw compute_error("Argument to commodity() must be a commoditized amount"); + amount_t temp("1"); + temp.set_commodity(((amount_t *) result.data)->commodity()); + result = temp; break; + } - case F_STRIP: { - assert(left); - left->compute(result, details); + case F_SET_COMMODITY: { + int index = 0; + value_expr_t * expr = find_leaf(context, 0, index); + value_t temp; + expr->compute(temp, details, context); + + index = 0; + expr = find_leaf(context, 1, index); + expr->compute(result, details, context); + if (result.type != value_t::AMOUNT) + throw compute_error("Second argument to set_commodity() must be a commoditized amount"); + amount_t one("1"); + one.set_commodity(((amount_t *) result.data)->commodity()); + result = one; + + result *= temp; + break; + } + + case F_QUANTITY: { + int index = 0; + value_expr_t * expr = find_leaf(context, 0, index); + expr->compute(result, details, context); balance_t * bal = NULL; switch (result.type) { @@ -320,82 +454,65 @@ void value_expr_t::compute(value_t& result, const details_t& details) const result = false; break; - case F_FUNC: { - if (constant_s == "min" || constant_s == "max") { - assert(left); - if (! right) { - left->compute(result, details); - break; - } - value_t temp; - left->compute(temp, details); - assert(right->kind == O_ARG); - right->left->compute(result, details); - - if (constant_s == "min") { - if (temp < result) - result = temp; - } else { - if (temp > result) - result = temp; - } - } - else if (constant_s == "price") { - assert(left); - left->compute(result, details); - - std::time_t moment = terminus; - if (right) { - assert(right->kind == O_ARG); - switch (right->left->kind) { - case F_NOW: - break; // already set to now - case DATE: - if (details.xact && transaction_has_xdata(*details.xact) && - transaction_xdata_(*details.xact).date) - moment = transaction_xdata_(*details.xact).date; - else if (details.xact) - moment = details.xact->date(); - else if (details.entry) - moment = details.entry->date(); - break; - case CONSTANT_T: - moment = right->left->constant_t; - break; - default: - throw compute_error("Invalid date passed to @price(value,date)"); - } - } + case O_ARG: { + int index = 0; + assert(left); + assert(left->kind == CONSTANT_I); + value_expr_t * expr = find_leaf(context, left->constant_i, index); + if (expr) + expr->compute(result, details, context); + else + result = 0L; + break; + } - result = result.value(moment); + case O_COM: + assert(left); + assert(right); + left->compute(result, details, context); + right->compute(result, details, context); + break; + + case O_DEF: + throw compute_error("Cannot compute function definition"); + + case O_REF: { + assert(left); + if (right) { + value_auto_ptr args(reduce_leaves(right, details, context)); + left->compute(result, details, args.get()); + } else { + left->compute(result, details, context); } break; } case F_VALUE: { - assert(left); - left->compute(result, details); + int index = 0; + value_expr_t * expr = find_leaf(context, 0, index); + expr->compute(result, details, context); + + index = 0; + expr = find_leaf(context, 1, index); std::time_t moment = terminus; - if (right) { - switch (right->kind) { - case F_NOW: - break; // already set to now - case DATE: - if (details.xact && transaction_has_xdata(*details.xact) && - transaction_xdata_(*details.xact).date) - moment = transaction_xdata_(*details.xact).date; - else if (details.xact) - moment = details.xact->date(); - else if (details.entry) - moment = details.entry->date(); - break; - case CONSTANT_T: - moment = right->constant_t; - break; - default: - throw compute_error("Invalid date passed to P(value,date)"); - } + switch (expr->kind) { + case F_NOW: + break; // already set to now + case DATE: + if (details.xact && transaction_has_xdata(*details.xact) && + transaction_xdata_(*details.xact).date) + moment = transaction_xdata_(*details.xact).date; + else if (details.xact) + moment = details.xact->date(); + else if (details.entry) + moment = details.entry->date(); + break; + case CONSTANT_T: + moment = expr->constant_t; + break; + default: + throw compute_error("Invalid date passed to P(value,date)"); } result = result.value(moment); @@ -403,7 +520,7 @@ void value_expr_t::compute(value_t& result, const details_t& details) const } case O_NOT: - left->compute(result, details); + left->compute(result, details, context); result.negate(); break; @@ -411,30 +528,31 @@ void value_expr_t::compute(value_t& result, const details_t& details) const assert(left); assert(right); assert(right->kind == O_COL); - left->compute(result, details); + left->compute(result, details, context); if (result) - right->left->compute(result, details); + right->left->compute(result, details, context); else - right->right->compute(result, details); + right->right->compute(result, details, context); break; } case O_AND: assert(left); assert(right); - left->compute(result, details); + left->compute(result, details, context); if (result) - right->compute(result, details); + right->compute(result, details, context); break; case O_OR: assert(left); assert(right); - left->compute(result, details); + left->compute(result, details, context); if (! result) - right->compute(result, details); + right->compute(result, details, context); break; + case O_NEQ: case O_EQ: case O_LT: case O_LTE: @@ -443,9 +561,10 @@ void value_expr_t::compute(value_t& result, const details_t& details) const assert(left); assert(right); value_t temp; - left->compute(temp, details); - right->compute(result, details); + left->compute(temp, details, context); + right->compute(result, details, context); switch (kind) { + case O_NEQ: result = temp != result; break; case O_EQ: result = temp == result; break; case O_LT: result = temp < result; break; case O_LTE: result = temp <= result; break; @@ -456,6 +575,12 @@ void value_expr_t::compute(value_t& result, const details_t& details) const break; } + case O_NEG: + assert(left); + left->compute(result, details, context); + result.negate(); + break; + case O_ADD: case O_SUB: case O_MUL: @@ -463,8 +588,8 @@ void value_expr_t::compute(value_t& result, const details_t& details) const assert(left); assert(right); value_t temp; - right->compute(temp, details); - left->compute(result, details); + right->compute(temp, details, context); + left->compute(result, details, context); switch (kind) { case O_ADD: result += temp; break; case O_SUB: result -= temp; break; @@ -475,6 +600,15 @@ void value_expr_t::compute(value_t& result, const details_t& details) const break; } + case O_PERC: { + assert(left); + result = "100.0%"; + value_t temp; + left->compute(temp, details, context); + result *= temp; + break; + } + case LAST: default: assert(0); @@ -497,107 +631,171 @@ static inline void unexpected(char c, char wanted = '\0') { } } -value_expr_t * parse_value_term(std::istream& in); +value_expr_t * parse_value_term(std::istream& in, scope_t * scope); -inline value_expr_t * parse_value_term(const char * p) { +inline value_expr_t * parse_value_term(const char * p, scope_t * scope) { std::istringstream stream(p); - return parse_value_term(stream); + return parse_value_term(stream, scope); } -value_expr_t * parse_value_term(std::istream& in) +value_expr_t * parse_value_term(std::istream& in, scope_t * scope) { - std::auto_ptr<value_expr_t> node; + value_auto_ptr node; char buf[256]; char c = peek_next_nonws(in); - if (std::isdigit(c)) { - READ_INTO(in, buf, 255, c, std::isdigit(c)); + if (std::isdigit(c) || c == '.') { + READ_INTO(in, buf, 255, c, std::isdigit(c) || c == '.'); - node.reset(new value_expr_t(value_expr_t::CONSTANT_I)); - node->constant_i = std::atol(buf); - return node.release(); + if (std::strchr(buf, '.')) { + node.reset(new value_expr_t(value_expr_t::CONSTANT_A)); + node->constant_a = new amount_t(buf); + } else { + node.reset(new value_expr_t(value_expr_t::CONSTANT_I)); + node->constant_i = std::atol(buf); + } + goto parsed; } - else if (c == '{') { - in.get(c); - READ_INTO(in, buf, 255, c, c != '}'); - if (c == '}') + else if (std::isalnum(c) || c == '_') { + bool have_args = false; + istream_pos_type beg; + + READ_INTO(in, buf, 255, c, std::isalnum(c) || c == '_'); + c = peek_next_nonws(in); + if (c == '(') { in.get(c); - else - unexpected(c, '}'); + beg = in.tellg(); - node.reset(new value_expr_t(value_expr_t::CONSTANT_A)); - node->constant_a.parse(buf); - return node.release(); - } + int paren_depth = 0; + while (! in.eof()) { + in.get(c); + if (c == '(' || c == '{' || c == '[') + paren_depth++; + else if (c == ')' || c == '}' || c == ']') { + if (paren_depth == 0) + break; + paren_depth--; + } + } + if (c != ')') + unexpected(c, ')'); - in.get(c); - switch (c) { - // Basic terms - case 'm': node.reset(new value_expr_t(value_expr_t::F_NOW)); break; - case 'a': node.reset(new value_expr_t(value_expr_t::AMOUNT)); break; - case 'b': node.reset(new value_expr_t(value_expr_t::COST)); break; - case 'd': node.reset(new value_expr_t(value_expr_t::DATE)); break; - case 'X': node.reset(new value_expr_t(value_expr_t::CLEARED)); break; - case 'Y': node.reset(new value_expr_t(value_expr_t::PENDING)); break; - case 'R': node.reset(new value_expr_t(value_expr_t::REAL)); break; - case 'L': node.reset(new value_expr_t(value_expr_t::ACTUAL)); break; - case 'n': node.reset(new value_expr_t(value_expr_t::INDEX)); break; - case 'N': node.reset(new value_expr_t(value_expr_t::COUNT)); break; - case 'l': node.reset(new value_expr_t(value_expr_t::DEPTH)); break; - case 'O': node.reset(new value_expr_t(value_expr_t::TOTAL)); break; - case 'B': node.reset(new value_expr_t(value_expr_t::COST_TOTAL)); break; + have_args = true; + c = peek_next_nonws(in); + } - // Relating to format_t - case 't': node.reset(new value_expr_t(value_expr_t::VALUE_EXPR)); break; - case 'T': node.reset(new value_expr_t(value_expr_t::TOTAL_EXPR)); break; + if (c == '=') { + in.get(c); + if (peek_next_nonws(in) == '=') { + in.unget(); + c = '\0'; + goto parsed; // parse this as == operator + } - // Compound terms - case 'v': node.reset(parse_value_expr("P(a,d)")); break; - case 'V': node.reset(parse_value_term("P(O,d)")); break; - case 'g': node.reset(parse_value_expr("v-b")); break; - case 'G': node.reset(parse_value_expr("V-B")); break; + std::auto_ptr<scope_t> params(new scope_t(scope)); - // Functions - case '^': - node.reset(new value_expr_t(value_expr_t::F_PARENT)); - node->left = parse_value_term(in); - break; + int index = 0; + if (have_args) { + bool done = false; - case '-': - node.reset(new value_expr_t(value_expr_t::F_NEG)); - node->left = parse_value_term(in); - break; + in.clear(); + in.seekg(beg, std::ios::beg); + while (! done && ! in.eof()) { + char ident[32]; + READ_INTO(in, ident, 31, c, std::isalnum(c) || c == '_'); - case 'U': - node.reset(new value_expr_t(value_expr_t::F_ABS)); - node->left = parse_value_term(in); - break; + c = peek_next_nonws(in); + in.get(c); + if (c != ',' && c != ')') + unexpected(c, ')'); + else if (c == ')') + done = true; + + // Define the parameter so that on lookup the parser will find + // an O_ARG value. + node.reset(new value_expr_t(value_expr_t::O_ARG)); + node->set_left(new value_expr_t(value_expr_t::CONSTANT_I)); + node->left->constant_i = index++; + params->define(ident, node.release()); + } + + if (peek_next_nonws(in) != '=') { + in.get(c); + unexpected(c, '='); + } + in.get(c); + } - case 'S': - node.reset(new value_expr_t(value_expr_t::F_STRIP)); - node->left = parse_value_term(in); - break; + // Define the value associated with the defined identifier + value_auto_ptr def(parse_boolean_expr(in, params.get())); + if (def.get() == NULL) + throw value_expr_error(std::string("Definition failed for '") + buf + "'"); + + node.reset(new value_expr_t(value_expr_t::O_DEF)); + node->set_left(new value_expr_t(value_expr_t::CONSTANT_I)); + node->left->constant_i = index; + node->set_right(def.release()); + + scope->define(buf, node.release()); + + // Returning a dummy value in place of the definition + node.reset(new value_expr_t(value_expr_t::CONSTANT_I)); + node->constant_i = 0; + } else { + assert(scope); + value_expr_t * def = scope->lookup(buf); + if (def == NULL) { + if (buf[1] == '\0' && + (buf[0] == 'c' || buf[0] == 'C' || buf[0] == 'p' || + buf[0] == 'w' || buf[0] == 'W' || buf[0] == 'e')) + goto find_term; + throw value_expr_error(std::string("Unknown identifier '") + buf + "'"); + } + else if (def->kind == value_expr_t::O_DEF) { + node.reset(new value_expr_t(value_expr_t::O_REF)); + node->set_left(def->right); + + int count = 0; + if (have_args) { + in.clear(); + in.seekg(beg, std::ios::beg); + value_auto_ptr args(parse_value_expr(in, scope, true)); + + if (peek_next_nonws(in) != ')') { + in.get(c); + unexpected(c, ')'); + } + in.get(c); - case 'A': - node.reset(new value_expr_t(value_expr_t::F_ARITH_MEAN)); - node->left = parse_value_term(in); - break; + if (args.get() != NULL) { + count = count_leaves(args.get()); + node->set_right(args.release()); + } + } - case 'P': - node.reset(new value_expr_t(value_expr_t::F_VALUE)); - if (peek_next_nonws(in) == '(') { - in.get(c); - node->left = parse_value_expr(in, true); - if (peek_next_nonws(in) == ',') { - in.get(c); - node->right = parse_value_expr(in, true); + if (count != def->left->constant_i) { + std::string msg; + std::ostringstream errmsg(msg); + errmsg << "Wrong number of arguments to '" << buf + << "': saw " << count + << ", wanted " << def->left->constant_i; + throw value_expr_error(errmsg.str()); + } + } + else { + node.reset(def); } - in.get(c); - if (c != ')') - unexpected(c, ')'); - } else { - node->left = parse_value_term(in); } + goto parsed; + } + + find_term: + in.get(c); + switch (c) { + // Functions + case '^': + node.reset(new value_expr_t(value_expr_t::F_PARENT)); + node->set_left(parse_value_term(in, scope)); break; // Other @@ -657,39 +855,39 @@ value_expr_t * parse_value_term(std::istream& in) break; } - case '@': { - READ_INTO(in, buf, 255, c, c != '('); - if (c != '(') - unexpected(c, '('); - - node.reset(new value_expr_t(value_expr_t::F_FUNC)); - node->constant_s = buf; - - in.get(c); - if (peek_next_nonws(in) == ')') { + case '{': { + int paren_depth = 0; + int i = 0; + while (i < 255 && ! in.eof()) { in.get(c); - } else { - value_expr_t * cur = node.get(); - cur->left = parse_value_expr(in, true); - in.get(c); - while (! in.eof() && c == ',') { - cur->right = new value_expr_t(value_expr_t::O_ARG); - cur = cur->right; - cur->left = parse_value_expr(in, true); - in.get(c); + if (c == '{') { + paren_depth++; } - if (c != ')') - unexpected(c, ')'); + else if (c == '}') { + if (paren_depth == 0) + break; + paren_depth--; + } + buf[i++] = c; } + buf[i] = '\0'; + + if (c != '}') + unexpected(c, '}'); + + node.reset(new value_expr_t(value_expr_t::CONSTANT_A)); + node->constant_a = new amount_t(buf); break; } - case '(': - node.reset(parse_value_expr(in, true)); + case '(': { + std::auto_ptr<scope_t> locals(new scope_t(scope)); + node.reset(parse_value_expr(in, locals.get(), true)); in.get(c); if (c != ')') unexpected(c, ')'); break; + } case '[': { READ_INTO(in, buf, 255, c, c != ']'); @@ -709,12 +907,23 @@ value_expr_t * parse_value_term(std::istream& in) break; } + parsed: return node.release(); } -value_expr_t * parse_mul_expr(std::istream& in) +value_expr_t * parse_mul_expr(std::istream& in, scope_t * scope) { - std::auto_ptr<value_expr_t> node(parse_value_term(in)); + value_auto_ptr node; + + if (peek_next_nonws(in) == '%') { + char c; + in.get(c); + node.reset(new value_expr_t(value_expr_t::O_PERC)); + node->set_left(parse_value_term(in, scope)); + return node.release(); + } + + node.reset(parse_value_term(in, scope)); if (node.get() && ! in.eof()) { char c = peek_next_nonws(in); @@ -722,18 +931,18 @@ value_expr_t * parse_mul_expr(std::istream& in) in.get(c); switch (c) { case '*': { - std::auto_ptr<value_expr_t> prev(node.release()); + value_auto_ptr prev(node.release()); node.reset(new value_expr_t(value_expr_t::O_MUL)); - node->left = prev.release(); - node->right = parse_value_term(in); + node->set_left(prev.release()); + node->set_right(parse_value_term(in, scope)); break; } case '/': { - std::auto_ptr<value_expr_t> prev(node.release()); + value_auto_ptr prev(node.release()); node.reset(new value_expr_t(value_expr_t::O_DIV)); - node->left = prev.release(); - node->right = parse_value_term(in); + node->set_left(prev.release()); + node->set_right(parse_value_term(in, scope)); break; } } @@ -744,9 +953,28 @@ value_expr_t * parse_mul_expr(std::istream& in) return node.release(); } -value_expr_t * parse_add_expr(std::istream& in) +value_expr_t * parse_add_expr(std::istream& in, scope_t * scope) { - std::auto_ptr<value_expr_t> node(parse_mul_expr(in)); + value_auto_ptr node; + + if (peek_next_nonws(in) == '-') { + char c; + in.get(c); + value_auto_ptr expr(parse_mul_expr(in, scope)); + if (expr->kind == value_expr_t::CONSTANT_I) { + expr->constant_i = - expr->constant_i; + return expr.release(); + } + else if (expr->kind == value_expr_t::CONSTANT_A) { + expr->constant_a->negate(); + return expr.release(); + } + node.reset(new value_expr_t(value_expr_t::O_NEG)); + node->set_left(expr.release()); + return node.release(); + } + + node.reset(parse_mul_expr(in, scope)); if (node.get() && ! in.eof()) { char c = peek_next_nonws(in); @@ -754,18 +982,18 @@ value_expr_t * parse_add_expr(std::istream& in) in.get(c); switch (c) { case '+': { - std::auto_ptr<value_expr_t> prev(node.release()); + value_auto_ptr prev(node.release()); node.reset(new value_expr_t(value_expr_t::O_ADD)); - node->left = prev.release(); - node->right = parse_mul_expr(in); + node->set_left(prev.release()); + node->set_right(parse_mul_expr(in, scope)); break; } case '-': { - std::auto_ptr<value_expr_t> prev(node.release()); + value_auto_ptr prev(node.release()); node.reset(new value_expr_t(value_expr_t::O_SUB)); - node->left = prev.release(); - node->right = parse_mul_expr(in); + node->set_left(prev.release()); + node->set_right(parse_mul_expr(in, scope)); break; } } @@ -776,54 +1004,61 @@ value_expr_t * parse_add_expr(std::istream& in) return node.release(); } -value_expr_t * parse_logic_expr(std::istream& in) +value_expr_t * parse_logic_expr(std::istream& in, scope_t * scope) { - std::auto_ptr<value_expr_t> node; + value_auto_ptr node; if (peek_next_nonws(in) == '!') { char c; in.get(c); node.reset(new value_expr_t(value_expr_t::O_NOT)); - node->left = parse_logic_expr(in); + node->set_left(parse_add_expr(in, scope)); return node.release(); } - node.reset(parse_add_expr(in)); + node.reset(parse_add_expr(in, scope)); if (node.get() && ! in.eof()) { char c = peek_next_nonws(in); - if (c == '=' || c == '<' || c == '>') { + if (c == '!' || c == '=' || c == '<' || c == '>') { in.get(c); switch (c) { + case '!': case '=': { - std::auto_ptr<value_expr_t> prev(node.release()); - node.reset(new value_expr_t(value_expr_t::O_EQ)); - node->left = prev.release(); - node->right = parse_add_expr(in); + bool negate = c == '!'; + if ((c = peek_next_nonws(in)) == '=') + in.get(c); + else + unexpected(c, '='); + value_auto_ptr prev(node.release()); + node.reset(new value_expr_t(negate ? value_expr_t::O_NEQ : + value_expr_t::O_EQ)); + node->set_left(prev.release()); + node->set_right(parse_add_expr(in, scope)); break; } case '<': { - std::auto_ptr<value_expr_t> prev(node.release()); + value_auto_ptr prev(node.release()); node.reset(new value_expr_t(value_expr_t::O_LT)); if (peek_next_nonws(in) == '=') { in.get(c); node->kind = value_expr_t::O_LTE; } - node->left = prev.release(); - node->right = parse_add_expr(in); + node->set_left(prev.release()); + node->set_right(parse_add_expr(in, scope)); break; } case '>': { - std::auto_ptr<value_expr_t> prev(node.release()); + value_auto_ptr prev(node.release()); node.reset(new value_expr_t(value_expr_t::O_GT)); if (peek_next_nonws(in) == '=') { in.get(c); node->kind = value_expr_t::O_GTE; } - node->left = prev.release(); - node->right = parse_add_expr(in); + node->set_left(prev.release()); + node->set_right(parse_add_expr(in, scope)); break; } @@ -838,9 +1073,9 @@ value_expr_t * parse_logic_expr(std::istream& in) return node.release(); } -value_expr_t * parse_value_expr(std::istream& in, const bool partial) +value_expr_t * parse_boolean_expr(std::istream& in, scope_t * scope) { - std::auto_ptr<value_expr_t> node(parse_logic_expr(in)); + value_auto_ptr node(parse_logic_expr(in, scope)); if (node.get() && ! in.eof()) { char c = peek_next_nonws(in); @@ -848,33 +1083,208 @@ value_expr_t * parse_value_expr(std::istream& in, const bool partial) in.get(c); switch (c) { case '&': { - std::auto_ptr<value_expr_t> prev(node.release()); + value_auto_ptr prev(node.release()); node.reset(new value_expr_t(value_expr_t::O_AND)); - node->left = prev.release(); - node->right = parse_logic_expr(in); + node->set_left(prev.release()); + node->set_right(parse_logic_expr(in, scope)); break; } case '|': { - std::auto_ptr<value_expr_t> prev(node.release()); + value_auto_ptr prev(node.release()); node.reset(new value_expr_t(value_expr_t::O_OR)); - node->left = prev.release(); - node->right = parse_logic_expr(in); + node->set_left(prev.release()); + node->set_right(parse_logic_expr(in, scope)); break; } case '?': { - std::auto_ptr<value_expr_t> prev(node.release()); + value_auto_ptr prev(node.release()); node.reset(new value_expr_t(value_expr_t::O_QUES)); - node->left = prev.release(); - value_expr_t * choices; - node->right = choices = new value_expr_t(value_expr_t::O_COL); - choices->left = parse_logic_expr(in); + node->set_left(prev.release()); + node->set_right(new value_expr_t(value_expr_t::O_COL)); + node->right->set_left(parse_logic_expr(in, scope)); c = peek_next_nonws(in); if (c != ':') unexpected(c, ':'); in.get(c); - choices->right = parse_logic_expr(in); + node->right->set_right(parse_logic_expr(in, scope)); + break; + } + + default: + if (! in.eof()) + unexpected(c); + break; + } + c = peek_next_nonws(in); + } + } + + return node.release(); +} + +void init_value_expr() +{ + global_scope.reset(new scope_t()); + scope_t * globals = global_scope.get(); + + value_expr_t * node; + + // Basic terms + node = new value_expr_t(value_expr_t::F_NOW); + globals->define("m", node); + globals->define("now", node); + + node = new value_expr_t(value_expr_t::AMOUNT); + globals->define("a", node); + globals->define("amount", node); + + node = new value_expr_t(value_expr_t::COST); + globals->define("b", node); + globals->define("cost", node); + + node = new value_expr_t(value_expr_t::PRICE); + globals->define("i", node); + globals->define("price", node); + + node = new value_expr_t(value_expr_t::DATE); + globals->define("d", node); + globals->define("date", node); + + node = new value_expr_t(value_expr_t::CLEARED); + globals->define("X", node); + globals->define("cleared", node); + + node = new value_expr_t(value_expr_t::PENDING); + globals->define("Y", node); + globals->define("pending", node); + + node = new value_expr_t(value_expr_t::REAL); + globals->define("R", node); + globals->define("real", node); + + node = new value_expr_t(value_expr_t::ACTUAL); + globals->define("L", node); + globals->define("actual", node); + + node = new value_expr_t(value_expr_t::INDEX); + globals->define("n", node); + globals->define("index", node); + + node = new value_expr_t(value_expr_t::COUNT); + globals->define("N", node); + globals->define("count", node); + + node = new value_expr_t(value_expr_t::DEPTH); + globals->define("l", node); + globals->define("depth", node); + + node = new value_expr_t(value_expr_t::TOTAL); + globals->define("O", node); + globals->define("total", node); + + node = new value_expr_t(value_expr_t::COST_TOTAL); + globals->define("B", node); + globals->define("cost_total", node); + + node = new value_expr_t(value_expr_t::PRICE_TOTAL); + globals->define("I", node); + globals->define("price_total", node); + + // Relating to format_t + globals->define("t", new value_expr_t(value_expr_t::VALUE_EXPR)); + globals->define("T", new value_expr_t(value_expr_t::TOTAL_EXPR)); + + // Functions + node = new value_expr_t(value_expr_t::O_DEF); + node->set_left(new value_expr_t(value_expr_t::CONSTANT_I)); + node->left->constant_i = 1; + node->set_right(new value_expr_t(value_expr_t::F_ABS)); + globals->define("U", node); + globals->define("abs", node); + + node = new value_expr_t(value_expr_t::O_DEF); + node->set_left(new value_expr_t(value_expr_t::CONSTANT_I)); + node->left->constant_i = 1; + node->set_right(new value_expr_t(value_expr_t::F_QUANTITY)); + globals->define("S", node); + globals->define("quant", node); + globals->define("quantity", node); + + node = new value_expr_t(value_expr_t::O_DEF); + node->set_left(new value_expr_t(value_expr_t::CONSTANT_I)); + node->left->constant_i = 1; + node->set_right(new value_expr_t(value_expr_t::F_COMMODITY)); + globals->define("comm", node); + globals->define("commodity", node); + + node = new value_expr_t(value_expr_t::O_DEF); + node->set_left(new value_expr_t(value_expr_t::CONSTANT_I)); + node->left->constant_i = 2; + node->set_right(new value_expr_t(value_expr_t::F_SET_COMMODITY)); + globals->define("setcomm", node); + globals->define("set_commodity", node); + + node = new value_expr_t(value_expr_t::O_DEF); + node->set_left(new value_expr_t(value_expr_t::CONSTANT_I)); + node->left->constant_i = 1; + node->set_right(new value_expr_t(value_expr_t::F_ARITH_MEAN)); + globals->define("A", node); + globals->define("avg", node); + globals->define("mean", node); + globals->define("average", node); + + node = new value_expr_t(value_expr_t::O_DEF); + node->set_left(new value_expr_t(value_expr_t::CONSTANT_I)); + node->left->constant_i = 2; + node->set_right(new value_expr_t(value_expr_t::F_VALUE)); + globals->define("P", node); + globals->define("val", node); + globals->define("value", node); + + // Macros + node = parse_value_expr("P(a,d)"); + globals->define("v", node); + globals->define("market", node); + + node = parse_value_expr("P(O,d)"); + globals->define("V", node); + globals->define("market_total", node); + + node = parse_value_expr("v-b"); + globals->define("g", node); + globals->define("gain", node); + + node = parse_value_expr("V-B"); + globals->define("G", node); + globals->define("gain_total", node); + + parse_boolean_expr("min(x,y)=x<y?x:y", globals); + parse_boolean_expr("max(x,y)=x>y?x:y", globals); +} + +value_expr_t * parse_value_expr(std::istream& in, scope_t * scope, + const bool partial) +{ + if (! global_scope.get()) + init_value_expr(); + + std::auto_ptr<scope_t> this_scope(new scope_t(scope ? scope : + global_scope.get())); + value_auto_ptr node; + node.reset(parse_boolean_expr(in, this_scope.get())); + + if (node.get() && ! in.eof()) { + char c = peek_next_nonws(in); + while (c == ',') { + in.get(c); + switch (c) { + case ',': { + value_auto_ptr prev(node.release()); + node.reset(new value_expr_t(value_expr_t::O_COM)); + node->set_left(prev.release()); + node->set_right(parse_logic_expr(in, this_scope.get())); break; } @@ -905,185 +1315,103 @@ value_expr_t * parse_value_expr(std::istream& in, const bool partial) return node.release(); } -#ifdef DEBUG_ENABLED - -void dump_value_expr(std::ostream& out, const value_expr_t * node) +void dump_value_expr(std::ostream& out, const value_expr_t * node, + const int depth) { + out.setf(std::ios::left); + out.width(10); + out << node << " "; + + for (int i = 0; i < depth; i++) + out << " "; + switch (node->kind) { case value_expr_t::CONSTANT_I: - out << "UINT[" << node->constant_i << ']'; + out << "CONSTANT_I - " << node->constant_i; break; case value_expr_t::CONSTANT_T: - out << "DATE/TIME[" << node->constant_t << ']'; + out << "CONSTANT_T - [" << node->constant_t << ']'; break; case value_expr_t::CONSTANT_A: - out << "CONST[" << node->constant_a << ']'; - break; - - case value_expr_t::AMOUNT: out << "AMOUNT"; break; - case value_expr_t::COST: out << "COST"; break; - case value_expr_t::DATE: out << "DATE"; break; - case value_expr_t::CLEARED: out << "CLEARED"; break; - case value_expr_t::PENDING: out << "PENDING"; break; - case value_expr_t::REAL: out << "REAL"; break; - case value_expr_t::ACTUAL: out << "ACTUAL"; break; - case value_expr_t::INDEX: out << "INDEX"; break; - case value_expr_t::COUNT: out << "COUNT"; break; - case value_expr_t::DEPTH: out << "DEPTH"; break; - case value_expr_t::TOTAL: out << "TOTAL"; break; - case value_expr_t::COST_TOTAL: out << "COST_TOTAL"; break; - - case value_expr_t::F_ARITH_MEAN: - out << "MEAN("; - dump_value_expr(out, node->left); - out << ')'; - break; - - case value_expr_t::F_NEG: - out << "ABS("; - dump_value_expr(out, node->left); - out << ')'; - break; - - case value_expr_t::F_ABS: - out << "ABS("; - dump_value_expr(out, node->left); - out << ')'; - break; - - case value_expr_t::F_STRIP: - out << "STRIP("; - dump_value_expr(out, node->left); - out << ')'; - break; - - case value_expr_t::F_CODE_MASK: - assert(node->mask); - out << "M_CODE(" << node->mask->pattern << ')'; - break; - - case value_expr_t::F_PAYEE_MASK: - assert(node->mask); - out << "M_PAYEE(" << node->mask->pattern << ')'; - break; - - case value_expr_t::F_NOTE_MASK: - assert(node->mask); - out << "M_NOTE(" << node->mask->pattern << ')'; - break; - + out << "CONSTANT_A - {" << *(node->constant_a) << '}'; + break; + case value_expr_t::CONSTANT_V: + out << "CONSTANT_V - {" << *(node->constant_v) << '}'; + break; + + case value_expr_t::AMOUNT: out << "AMOUNT"; break; + case value_expr_t::COST: out << "COST"; break; + case value_expr_t::PRICE: out << "PRICE"; break; + case value_expr_t::DATE: out << "DATE"; break; + case value_expr_t::CLEARED: out << "CLEARED"; break; + case value_expr_t::PENDING: out << "PENDING"; break; + case value_expr_t::REAL: out << "REAL"; break; + case value_expr_t::ACTUAL: out << "ACTUAL"; break; + case value_expr_t::INDEX: out << "INDEX"; break; + case value_expr_t::COUNT: out << "COUNT"; break; + case value_expr_t::DEPTH: out << "DEPTH"; break; + case value_expr_t::TOTAL: out << "TOTAL"; break; + case value_expr_t::COST_TOTAL: out << "COST_TOTAL"; break; + case value_expr_t::PRICE_TOTAL: out << "PRICE_TOTAL"; break; + + case value_expr_t::F_NOW: out << "F_NOW"; break; + case value_expr_t::F_ARITH_MEAN: out << "F_ARITH_MEAN"; break; + case value_expr_t::F_ABS: out << "F_ABS"; break; + case value_expr_t::F_QUANTITY: out << "F_QUANTITY"; break; + case value_expr_t::F_COMMODITY: out << "F_COMMODITY"; break; + case value_expr_t::F_SET_COMMODITY: out << "F_SET_COMMODITY"; break; + case value_expr_t::F_CODE_MASK: out << "F_CODE_MASK"; break; + case value_expr_t::F_PAYEE_MASK: out << "F_PAYEE_MASK"; break; + case value_expr_t::F_NOTE_MASK: out << "F_NOTE_MASK"; break; case value_expr_t::F_ACCOUNT_MASK: - assert(node->mask); - out << "M_ACCT(" << node->mask->pattern << ')'; - break; - + out << "F_ACCOUNT_MASK"; break; case value_expr_t::F_SHORT_ACCOUNT_MASK: - assert(node->mask); - out << "M_SACCT(" << node->mask->pattern << ')'; - break; - + out << "F_SHORT_ACCOUNT_MASK"; break; case value_expr_t::F_COMMODITY_MASK: - assert(node->mask); - out << "M_COMM(" << node->mask->pattern << ')'; - break; - - case value_expr_t::F_VALUE: - out << "VALUE("; - dump_value_expr(out, node->left); - if (node->right) { - out << ", "; - dump_value_expr(out, node->right); - } - out << ')'; - break; - - case value_expr_t::O_NOT: - out << '!'; - dump_value_expr(out, node->left); - break; - - case value_expr_t::O_ARG: - dump_value_expr(out, node->left); - if (node->right) { - out << ','; - dump_value_expr(out, node->right); - } - break; - - case value_expr_t::O_QUES: - dump_value_expr(out, node->left); - out << '?'; - dump_value_expr(out, node->right->left); - out << ':'; - dump_value_expr(out, node->right->right); - break; - - case value_expr_t::O_AND: - case value_expr_t::O_OR: - out << '('; - dump_value_expr(out, node->left); - switch (node->kind) { - case value_expr_t::O_AND: out << " & "; break; - case value_expr_t::O_OR: out << " | "; break; - default: assert(0); break; - } - dump_value_expr(out, node->right); - out << ')'; - break; - - case value_expr_t::O_EQ: - case value_expr_t::O_LT: - case value_expr_t::O_LTE: - case value_expr_t::O_GT: - case value_expr_t::O_GTE: - out << '('; - dump_value_expr(out, node->left); - switch (node->kind) { - case value_expr_t::O_EQ: out << '='; break; - case value_expr_t::O_LT: out << '<'; break; - case value_expr_t::O_LTE: out << "<="; break; - case value_expr_t::O_GT: out << '>'; break; - case value_expr_t::O_GTE: out << ">="; break; - default: assert(0); break; - } - dump_value_expr(out, node->right); - out << ')'; - break; - - case value_expr_t::O_ADD: - case value_expr_t::O_SUB: - case value_expr_t::O_MUL: - case value_expr_t::O_DIV: - out << '('; - dump_value_expr(out, node->left); - switch (node->kind) { - case value_expr_t::O_ADD: out << '+'; break; - case value_expr_t::O_SUB: out << '-'; break; - case value_expr_t::O_MUL: out << '*'; break; - case value_expr_t::O_DIV: out << '/'; break; - default: assert(0); break; - } - dump_value_expr(out, node->right); - out << ')'; - break; + out << "F_COMMODITY_MASK"; break; + case value_expr_t::F_VALUE: out << "F_VALUE"; break; + + case value_expr_t::O_NOT: out << "O_NOT"; break; + case value_expr_t::O_ARG: out << "O_ARG"; break; + case value_expr_t::O_DEF: out << "O_DEF"; break; + case value_expr_t::O_REF: out << "O_REF"; break; + case value_expr_t::O_COM: out << "O_COM"; break; + case value_expr_t::O_QUES: out << "O_QUES"; break; + case value_expr_t::O_COL: out << "O_COL"; break; + case value_expr_t::O_AND: out << "O_AND"; break; + case value_expr_t::O_OR: out << "O_OR"; break; + case value_expr_t::O_NEQ: out << "O_NEQ"; break; + case value_expr_t::O_EQ: out << "O_EQ"; break; + case value_expr_t::O_LT: out << "O_LT"; break; + case value_expr_t::O_LTE: out << "O_LTE"; break; + case value_expr_t::O_GT: out << "O_GT"; break; + case value_expr_t::O_GTE: out << "O_GTE"; break; + case value_expr_t::O_NEG: out << "O_NEG"; break; + case value_expr_t::O_ADD: out << "O_ADD"; break; + case value_expr_t::O_SUB: out << "O_SUB"; break; + case value_expr_t::O_MUL: out << "O_MUL"; break; + case value_expr_t::O_DIV: out << "O_DIV"; break; + case value_expr_t::O_PERC: out << "O_PERC"; break; case value_expr_t::LAST: default: assert(0); break; } -} -#endif // DEBUG_ENABLED + out << " (" << node->refc << ')' << std::endl; -} // namespace ledger - -#ifdef TEST - -int main(int argc, char *argv[]) -{ - ledger::dump_value_expr(std::cout, ledger::parse_value_expr(argv[1])); - std::cout << std::endl; + if (node->kind > value_expr_t::TERMINALS) { + if (node->left) { + dump_value_expr(out, node->left, depth + 1); + if (node->right) + dump_value_expr(out, node->right, depth + 1); + } else { + assert(node->right == NULL); + } + } else { + assert(node->left == NULL); + } } -#endif // TEST +} // namespace ledger @@ -16,6 +16,7 @@ struct details_t const transaction_t * xact; const account_t * account; + details_t() : entry(NULL), xact(NULL), account(NULL) {} details_t(const entry_t& _entry) : entry(&_entry), xact(NULL), account(NULL) { DEBUG_PRINT("ledger.memory.ctors", "ctor details_t"); @@ -32,6 +33,29 @@ struct details_t #endif }; +typedef void (*value_func_t)(value_t& result, const details_t& details, + value_expr_t * context); + +class value_calc +{ +public: + virtual ~value_calc() {} + virtual void compute(value_t& result, const details_t& details, + value_expr_t * context = NULL) = 0; +}; + +class value_func : public value_calc +{ + value_func_t func; +public: + value_func(value_func_t _func) : func(_func) {} + + virtual void compute(value_t& result, const details_t& details, + value_expr_t * context = NULL) { + func(result, details, context); + } +}; + struct value_expr_t { enum kind_t { @@ -39,10 +63,14 @@ struct value_expr_t CONSTANT_I, CONSTANT_T, CONSTANT_A, + CONSTANT_V, + + CONSTANTS, // Item details AMOUNT, COST, + PRICE, DATE, CLEARED, PENDING, @@ -55,6 +83,7 @@ struct value_expr_t COUNT, TOTAL, COST_TOTAL, + PRICE_TOTAL, // Relating to format_t VALUE_EXPR, @@ -62,13 +91,12 @@ struct value_expr_t // Functions F_NOW, - F_PARENT, F_ARITH_MEAN, + F_QUANTITY, + F_COMMODITY, + F_SET_COMMODITY, F_VALUE, - F_FUNC, - F_NEG, F_ABS, - F_STRIP, F_CODE_MASK, F_PAYEE_MASK, F_NOTE_MASK, @@ -76,11 +104,18 @@ struct value_expr_t F_SHORT_ACCOUNT_MASK, F_COMMODITY_MASK, + TERMINALS, + + F_PARENT, + // Binary operators + O_NEG, O_ADD, O_SUB, O_MUL, O_DIV, + O_PERC, + O_NEQ, O_EQ, O_LT, O_LTE, @@ -91,88 +126,244 @@ struct value_expr_t O_OR, O_QUES, O_COL, + O_COM, + O_DEF, + O_REF, O_ARG, LAST }; kind_t kind; + mutable short refc; value_expr_t * left; - value_expr_t * right; union { - std::time_t constant_t; - long constant_i; + std::time_t constant_t; + long constant_i; + amount_t * constant_a; + value_t * constant_v; + mask_t * mask; + value_expr_t * right; }; - std::string constant_s; - amount_t constant_a; - mask_t * mask; value_expr_t(const kind_t _kind) - : kind(_kind), left(NULL), right(NULL), mask(NULL) { + : kind(_kind), refc(0), left(NULL), right(NULL) { DEBUG_PRINT("ledger.memory.ctors", "ctor value_expr_t"); } + ~value_expr_t(); + + void release() const { + DEBUG_PRINT("ledger.valexpr.memory", + "Releasing " << this << ", refc now " << refc - 1); + assert(refc > 0); + if (--refc == 0) + delete this; + } + value_expr_t * acquire() { + DEBUG_PRINT("ledger.valexpr.memory", + "Acquiring " << this << ", refc now " << refc + 1); + assert(refc >= 0); + refc++; + return this; + } + const value_expr_t * acquire() const { + DEBUG_PRINT("ledger.valexpr.memory", + "Acquiring " << this << ", refc now " << refc + 1); + refc++; + return this; + } - ~value_expr_t() { - DEBUG_PRINT("ledger.memory.dtors", "dtor value_expr_t"); - if (mask) delete mask; - if (left) delete left; - if (right) delete right; + void set_left(value_expr_t * expr) { + assert(kind > TERMINALS); + if (left) + left->release(); + left = expr ? expr->acquire() : NULL; } - void compute(value_t& result, const details_t& details) const; + void set_right(value_expr_t * expr) { + assert(kind > TERMINALS); + if (right) + right->release(); + right = expr ? expr->acquire() : NULL; + } + + void compute(value_t& result, const details_t& details, + value_expr_t * context = NULL) const; +}; + +struct scope_t +{ + scope_t * parent; + + typedef std::map<const std::string, value_expr_t *> symbol_map; + typedef std::pair<const std::string, value_expr_t *> symbol_pair; + + symbol_map symbols; + + scope_t(scope_t * _parent = NULL) : parent(_parent) {} + ~scope_t() { + for (symbol_map::iterator i = symbols.begin(); + i != symbols.end(); + i++) + (*i).second->release(); + } + + void define(const std::string& name, value_expr_t * def) { + DEBUG_PRINT("ledger.valexpr.syms", + "Defining '" << name << "' = " << def); + std::pair<symbol_map::iterator, bool> result + = symbols.insert(symbol_pair(name, def->acquire())); + if (! result.second) + throw value_expr_error(std::string("Redefinition of '") + + name + "' in same scope"); + } + value_expr_t * lookup(const std::string& name) { + symbol_map::const_iterator i = symbols.find(name); + if (i != symbols.end()) + return (*i).second; + else if (parent) + return parent->lookup(name); + return NULL; + } }; -extern std::auto_ptr<value_expr_t> amount_expr; -extern std::auto_ptr<value_expr_t> total_expr; +extern std::auto_ptr<scope_t> global_scope; + extern std::time_t terminus; +extern bool initialized; + +void init_value_expr(); bool compute_amount(value_expr_t * expr, amount_t& amt, transaction_t& xact); -inline void compute_amount(value_t& result, const details_t& details) { - if (amount_expr.get()) - amount_expr->compute(result, details); +struct scope_t; +value_expr_t * parse_boolean_expr(std::istream& in, scope_t * scope); + +inline value_expr_t * parse_boolean_expr(const char * p, + scope_t * scope = NULL) { + std::istringstream stream(p); + return parse_boolean_expr(stream, scope); } -inline void compute_total(value_t& result, const details_t& details) { - if (total_expr.get()) - total_expr->compute(result, details); +inline value_expr_t * parse_boolean_expr(const std::string& str, + scope_t * scope = NULL) { + return parse_boolean_expr(str.c_str(), scope); } value_expr_t * parse_value_expr(std::istream& in, + scope_t * scope = NULL, const bool partial = false); inline value_expr_t * parse_value_expr(const char * p, + scope_t * scope = NULL, const bool partial = false) { std::istringstream stream(p); - return parse_value_expr(stream, partial); + return parse_value_expr(stream, scope, partial); } inline value_expr_t * parse_value_expr(const std::string& str, + scope_t * scope = NULL, const bool partial = false) { - return parse_value_expr(str.c_str()); + return parse_value_expr(str.c_str(), scope); } -#ifdef DEBUG_ENABLED -void dump_value_expr(std::ostream& out, const value_expr_t * node); -#endif +void dump_value_expr(std::ostream& out, const value_expr_t * node, + const int depth = 0); + +////////////////////////////////////////////////////////////////////// +// +// This class is used so that during the "in between" stages of value +// expression parsing -- while no one yet holds a reference to the +// value_expr_t object -- we can be assured of deletion should an +// exception happen to whip by. + +struct value_auto_ptr { + value_expr_t * ptr; + value_auto_ptr() : ptr(NULL) {} + explicit value_auto_ptr(value_expr_t * _ptr) : ptr(_ptr) {} + ~value_auto_ptr() { + if (ptr && ptr->refc == 0) + delete ptr; + } + value_expr_t& operator*() const throw() { + return *ptr; + } + value_expr_t * operator->() const throw() { + return ptr; + } + value_expr_t * get() const throw() { return ptr; } + value_expr_t * release() throw() { + value_expr_t * tmp = ptr; + ptr = 0; + return tmp; + } + void reset(value_expr_t * p = 0) throw() { + if (p != ptr) { + if (ptr && ptr->refc == 0) + delete ptr; + ptr = p; + } + } +}; + +////////////////////////////////////////////////////////////////////// + +class value_expr : public value_calc +{ + std::string expr; + value_expr_t * parsed; + +public: + value_expr(const std::string& _expr) : expr(_expr) { + try { + parsed = parse_value_expr(expr); + parsed->acquire(); + } + catch (const value_expr_error& err) { + throw error(std::string("In value expression '") + + expr + "': " + err.what()); + } + } + value_expr(value_expr_t * _parsed) : parsed(_parsed->acquire()) {} + + virtual ~value_expr() { + if (parsed != NULL) + parsed->release(); + } + + virtual void compute(value_t& result, const details_t& details, + value_expr_t * context = NULL) { + parsed->compute(result, details, context); + } +}; + +extern std::auto_ptr<value_calc> amount_expr; +extern std::auto_ptr<value_calc> total_expr; + +inline void compute_amount(value_t& result, const details_t& details) { + if (amount_expr.get() != NULL) + amount_expr->compute(result, details); +} + +inline void compute_total(value_t& result, const details_t& details) { + if (total_expr.get() != NULL) + total_expr->compute(result, details); +} ////////////////////////////////////////////////////////////////////// template <typename T> class item_predicate { + public: const value_expr_t * predicate; - bool allocated; - public: - item_predicate(const std::string& _predicate) - : predicate(NULL), allocated(false) { + item_predicate(const std::string& _predicate) : predicate(NULL) { DEBUG_PRINT("ledger.memory.ctors", "ctor item_predicate<T>"); if (! _predicate.empty()) { try { - predicate = parse_value_expr(_predicate); - allocated = true; + predicate = parse_value_expr(_predicate)->acquire(); } catch (value_expr_error& err) { throw value_expr_error(std::string("In predicate '") + @@ -181,14 +372,14 @@ class item_predicate } } item_predicate(const value_expr_t * _predicate = NULL) - : predicate(_predicate), allocated(false) { + : predicate(_predicate->acquire()) { DEBUG_PRINT("ledger.memory.ctors", "ctor item_predicate<T>"); } ~item_predicate() { DEBUG_PRINT("ledger.memory.dtors", "dtor item_predicate<T>"); - if (predicate && allocated) - delete predicate; + if (predicate) + predicate->release(); } bool operator()(const T& item) const { @@ -196,9 +387,8 @@ class item_predicate value_t result; predicate->compute(result, details_t(item)); return result; - } else { - return true; } + return true; } }; @@ -747,6 +747,38 @@ value_t value_t::cost() const return value_t(); } +value_t value_t::factor_price() const +{ + switch (type) { + case BOOLEAN: + case INTEGER: + return *this; + + case AMOUNT: { + commodity_t& comm = ((amount_t *) data)->commodity(); + if (comm.price != NULL) + return value_t(*comm.price * *((amount_t *) data)); + return *this; + } + + case BALANCE: + return ((balance_t *) data)->factor_price(); + + case BALANCE_PAIR: { + balance_pair_t temp(((balance_pair_t *) data)->quantity.factor_price()); + if (((balance_pair_t *) data)->cost) + temp.cost = new balance_t(((balance_pair_t *) data)->cost); + return temp; + } + + default: + assert(0); + break; + } + assert(0); + return value_t(); +} + value_t& value_t::add(const amount_t& amount, const amount_t * cost) { switch (type) { @@ -266,6 +266,7 @@ class value_t void abs(); void cast(type_t cast_type); value_t cost() const; + value_t factor_price() const; value_t& add(const amount_t& amount, const amount_t * cost = NULL); value_t value(const std::time_t moment) const { @@ -290,7 +291,7 @@ class value_t case AMOUNT: { amount_t& amount = *((amount_t *) data); if (amount.commodity()) - amount = amount.round(amount.commodity().precision); + amount = amount.round(amount.commodity().precision()); break; } case BALANCE: @@ -7,6 +7,8 @@ namespace ledger { +bool show_lots = false; + template <> bool compare_items<transaction_t>::operator()(const transaction_t * left, const transaction_t * right) @@ -39,12 +41,20 @@ transaction_xdata_t& transaction_xdata(const transaction_t& xact) void add_transaction_to(const transaction_t& xact, value_t& value) { if (transaction_has_xdata(xact) && - transaction_xdata_(xact).dflags & TRANSACTION_COMPOSITE) + transaction_xdata_(xact).dflags & TRANSACTION_COMPOSITE) { value += transaction_xdata_(xact).composite_amount; - else if (xact.cost || value) - value.add(xact.amount, xact.cost); - else - value = xact.amount; + } + else if (xact.cost || value) { + amount_t * cost = xact.cost; + if (cost && cost->commodity().price) + cost = new amount_t(cost->base_amount()); + value.add(translate_amount(xact.amount), cost); + if (cost != xact.cost) + delete cost; + } + else { + value = translate_amount(xact.amount); + } } void truncate_entries::flush() @@ -790,7 +800,7 @@ void walk_accounts(account_t& account, const std::string& sort_string) { if (! sort_string.empty()) { - std::auto_ptr<value_expr_t> sort_order; + value_auto_ptr sort_order; try { sort_order.reset(parse_value_expr(sort_string)); } @@ -814,15 +824,15 @@ void walk_commodities(commodities_map& commodities, for (commodities_map::iterator i = commodities.begin(); i != commodities.end(); i++) { - if ((*i).second->flags & COMMODITY_STYLE_NOMARKET) + if ((*i).second->flags() & COMMODITY_STYLE_NOMARKET) continue; entry_temps.push_back(entry_t()); acct_temps.push_back(account_t(NULL, (*i).second->symbol)); - if ((*i).second->history) - for (history_map::iterator j = (*i).second->history->prices.begin(); - j != (*i).second->history->prices.end(); + if ((*i).second->history()) + for (history_map::iterator j = (*i).second->history()->prices.begin(); + j != (*i).second->history()->prices.end(); j++) { entry_temps.back()._date = (*j).first; @@ -120,6 +120,11 @@ inline const account_t * xact_account(const transaction_t& xact) { return xact_account(const_cast<transaction_t&>(xact)); } +extern bool show_lots; + +#define translate_amount(amt) \ + ((! show_lots && amt.commodity().price) ? amt.base_amount() : amt) + ////////////////////////////////////////////////////////////////////// inline void walk_transactions(transactions_list::iterator begin, @@ -212,20 +217,19 @@ class sort_transactions : public item_handler<transaction_t> transactions_deque transactions; const value_expr_t * sort_order; - bool allocated; public: sort_transactions(item_handler<transaction_t> * handler, const value_expr_t * _sort_order) : item_handler<transaction_t>(handler), - sort_order(_sort_order), allocated(false) {} + sort_order(_sort_order) {} sort_transactions(item_handler<transaction_t> * handler, const std::string& _sort_order) - : item_handler<transaction_t>(handler), allocated(false) { + : item_handler<transaction_t>(handler) { try { sort_order = parse_value_expr(_sort_order); - allocated = true; + sort_order->acquire(); } catch (value_expr_error& err) { throw value_expr_error(std::string("In sort string '") + _sort_order + @@ -235,8 +239,7 @@ class sort_transactions : public item_handler<transaction_t> virtual ~sort_transactions() { assert(sort_order); - if (allocated) - delete sort_order; + sort_order->release(); } virtual void post_accumulated_xacts(); @@ -115,18 +115,32 @@ static void endElement(void *userData, const char *name) else if (std::strcmp(name, "tr:generated") == 0) { curr_entry->transactions.back()->flags |= TRANSACTION_AUTO; } - else if (std::strcmp(name, "commodity") == 0) { + else if (std::strcmp(name, "symbol") == 0) { assert(! curr_comm); curr_comm = commodity_t::find_commodity(data, true); - curr_comm->flags |= COMMODITY_STYLE_SUFFIXED; - if (! comm_flags.empty()) - for (std::string::size_type i = 0, l = comm_flags.length(); i < l; i++) + curr_comm->flags() |= COMMODITY_STYLE_SUFFIXED; + if (! comm_flags.empty()) { + for (std::string::size_type i = 0, l = comm_flags.length(); i < l; i++) { switch (comm_flags[i]) { - case 'P': curr_comm->flags &= ~COMMODITY_STYLE_SUFFIXED; break; - case 'S': curr_comm->flags |= COMMODITY_STYLE_SEPARATED; break; - case 'T': curr_comm->flags |= COMMODITY_STYLE_THOUSANDS; break; - case 'E': curr_comm->flags |= COMMODITY_STYLE_EUROPEAN; break; + case 'P': curr_comm->flags() &= ~COMMODITY_STYLE_SUFFIXED; break; + case 'S': curr_comm->flags() |= COMMODITY_STYLE_SEPARATED; break; + case 'T': curr_comm->flags() |= COMMODITY_STYLE_THOUSANDS; break; + case 'E': curr_comm->flags() |= COMMODITY_STYLE_EUROPEAN; break; } + } + } + } + else if (std::strcmp(name, "price") == 0) { + assert(curr_comm); + amount_t * price = new amount_t(data); + std::string symbol; + std::ostringstream symstr(symbol); + symstr << curr_comm->symbol << " {" << *price << "}"; + commodity_t * priced_comm = + commodity_t::find_commodity(symstr.str(), true); + priced_comm->price = price; + priced_comm->base = curr_comm; + curr_comm = priced_comm; } else if (std::strcmp(name, "quantity") == 0) { curr_entry->transactions.back()->amount.parse(data); @@ -134,8 +148,8 @@ static void endElement(void *userData, const char *name) std::string::size_type i = data.find('.'); if (i != std::string::npos) { int precision = data.length() - i - 1; - if (precision > curr_comm->precision) - curr_comm->precision = precision; + if (precision > curr_comm->precision()) + curr_comm->precision() = precision; } curr_entry->transactions.back()->amount.set_commodity(*curr_comm); curr_comm = NULL; @@ -240,11 +254,24 @@ void xml_write_amount(std::ostream& out, const amount_t& amount, commodity_t& c = amount.commodity(); for (int i = 0; i < depth + 2; i++) out << ' '; out << "<commodity flags=\""; - if (! (c.flags & COMMODITY_STYLE_SUFFIXED)) out << 'P'; - if (c.flags & COMMODITY_STYLE_SEPARATED) out << 'S'; - if (c.flags & COMMODITY_STYLE_THOUSANDS) out << 'T'; - if (c.flags & COMMODITY_STYLE_EUROPEAN) out << 'E'; - out << "\">" << c.symbol << "</commodity>\n"; + if (! (c.flags() & COMMODITY_STYLE_SUFFIXED)) out << 'P'; + if (c.flags() & COMMODITY_STYLE_SEPARATED) out << 'S'; + if (c.flags() & COMMODITY_STYLE_THOUSANDS) out << 'T'; + if (c.flags() & COMMODITY_STYLE_EUROPEAN) out << 'E'; + out << "\">\n"; + for (int i = 0; i < depth + 4; i++) out << ' '; + if (c.price) { + out << "<symbol>" << c.base->symbol << "</symbol>\n"; + for (int i = 0; i < depth + 4; i++) out << ' '; + out << "<price>\n"; + xml_write_amount(out, *c.price, depth + 6); + for (int i = 0; i < depth + 4; i++) out << ' '; + out << "</price>\n"; + } else { + out << "<symbol>" << c.symbol << "</symbol>\n"; + } + for (int i = 0; i < depth + 2; i++) out << ' '; + out << "</commodity>\n"; for (int i = 0; i < depth + 2; i++) out << ' '; out << "<quantity>"; |