summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile.am8
-rw-r--r--NEWS114
-rw-r--r--amount.cc188
-rw-r--r--amount.h71
-rw-r--r--balance.cc16
-rw-r--r--balance.h3
-rw-r--r--binary.cc178
-rw-r--r--config.cc190
-rw-r--r--config.h22
-rw-r--r--configure.in9
-rw-r--r--debug.h5
-rw-r--r--derive.cc124
-rw-r--r--format.cc34
-rw-r--r--format.h2
-rw-r--r--gnucash.cc24
-rw-r--r--journal.cc12
-rw-r--r--journal.h32
-rw-r--r--main.cc34
-rw-r--r--option.cc189
-rw-r--r--option.h33
-rw-r--r--qif.cc18
-rw-r--r--quotes.cc10
-rw-r--r--textual.cc81
-rw-r--r--valexpr.cc1164
-rw-r--r--valexpr.h272
-rw-r--r--value.cc32
-rw-r--r--value.h3
-rw-r--r--walk.cc30
-rw-r--r--walk.h15
-rw-r--r--xml.cc57
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
diff --git a/NEWS b/NEWS
index 2c247f70..8f1b5c5b 100644
--- a/NEWS
+++ b/NEWS
@@ -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
diff --git a/amount.cc b/amount.cc
index 9de94b9b..6efb715c 100644
--- a/amount.cc
+++ b/amount.cc
@@ -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;
}
diff --git a/amount.h b/amount.h
index 98df367b..6262caa9 100644
--- a/amount.h
+++ b/amount.h
@@ -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:
diff --git a/balance.cc b/balance.cc
index 6305eb95..01332fae 100644
--- a/balance.cc
+++ b/balance.cc
@@ -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;
diff --git a/balance.h b/balance.h
index 8a6e4a35..929888d2 100644
--- a/balance.h
+++ b/balance.h
@@ -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());
}
};
diff --git a/binary.cc b/binary.cc
index 24613da9..7b283d97 100644
--- a/binary.cc
+++ b/binary.cc
@@ -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);
}
diff --git a/config.cc b/config.cc
index 3344c708..069114fb 100644
--- a/config.cc
+++ b/config.cc
@@ -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
diff --git a/config.h b/config.h
index 73a4419c..99295189 100644
--- a/config.h
+++ b/config.h
@@ -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)
diff --git a/debug.h b/debug.h
index 97a5a667..2efbbe78 100644
--- a/debug.h
+++ b/debug.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)
diff --git a/derive.cc b/derive.cc
index 1c8964b5..5e103dc9 100644
--- a/derive.cc
+++ b/derive.cc
@@ -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();
diff --git a/format.cc b/format.cc
index 98bedff5..d062a710 100644
--- a/format.cc
+++ b/format.cc
@@ -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.
diff --git a/format.h b/format.h
index eff23b2c..3e644bd6 100644
--- a/format.h
+++ b/format.h
@@ -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;
diff --git a/gnucash.cc b/gnucash.cc
index 4b537959..03ea4fc3 100644
--- a/gnucash.cc
+++ b/gnucash.cc
@@ -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 = &in;
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");
diff --git a/journal.cc b/journal.cc
index 232bb97a..ceedb2c4 100644
--- a/journal.cc
+++ b/journal.cc
@@ -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));
}
}
diff --git a/journal.h b/journal.h
index e0007634..6eb28b90 100644
--- a/journal.h
+++ b/journal.h
@@ -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();
diff --git a/main.cc b/main.cc
index b727b23b..5a8c7174 100644
--- a/main.cc
+++ b/main.cc
@@ -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);
diff --git a/option.cc b/option.cc
index c9000a20..043d51dc 100644
--- a/option.cc
+++ b/option.cc
@@ -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) {
diff --git a/option.h b/option.h
index 6de6ac91..1acc20e9 100644
--- a/option.h
+++ b/option.h
@@ -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
diff --git a/qif.cc b/qif.cc
index 9bce9f04..64153b27 100644
--- a/qif.cc
+++ b/qif.cc
@@ -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;
}
diff --git a/quotes.cc b/quotes.cc
index d3f84439..48b0db01 100644
--- a/quotes.cc
+++ b/quotes.cc
@@ -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()) {
diff --git a/textual.cc b/textual.cc
index 4fd9d837..d5dbe3d7 100644
--- a/textual.cc
+++ b/textual.cc
@@ -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
diff --git a/valexpr.cc b/valexpr.cc
index 00fce46f..2b8f106e 100644
--- a/valexpr.cc
+++ b/valexpr.cc
@@ -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
diff --git a/valexpr.h b/valexpr.h
index c9db4601..0bb9aa32 100644
--- a/valexpr.h
+++ b/valexpr.h
@@ -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;
}
};
diff --git a/value.cc b/value.cc
index 2af9ecf6..1680ebd7 100644
--- a/value.cc
+++ b/value.cc
@@ -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) {
diff --git a/value.h b/value.h
index d2f893b5..c7aad0e7 100644
--- a/value.h
+++ b/value.h
@@ -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:
diff --git a/walk.cc b/walk.cc
index 606ce126..075b8bd8 100644
--- a/walk.cc
+++ b/walk.cc
@@ -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;
diff --git a/walk.h b/walk.h
index 887963bf..0f8c6956 100644
--- a/walk.h
+++ b/walk.h
@@ -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();
diff --git a/xml.cc b/xml.cc
index b02954c6..24a664db 100644
--- a/xml.cc
+++ b/xml.cc
@@ -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>";