seen by the value expression logic. This has the effect of changing valexprs that test against the current time, such as for calculating the market value of commodities.
1107 lines
26 KiB
C++
1107 lines
26 KiB
C++
#include "valexpr.h"
|
|
#include "walk.h"
|
|
#include "error.h"
|
|
#include "datetime.h"
|
|
#include "debug.h"
|
|
#include "util.h"
|
|
#ifdef USE_BOOST_PYTHON
|
|
#include "py_eval.h"
|
|
#endif
|
|
|
|
namespace ledger {
|
|
|
|
std::auto_ptr<value_expr_t> amount_expr;
|
|
std::auto_ptr<value_expr_t> total_expr;
|
|
|
|
std::time_t terminus = now;
|
|
|
|
void value_expr_t::compute(value_t& result, const details_t& details) const
|
|
{
|
|
switch (kind) {
|
|
case CONSTANT_I:
|
|
result = constant_i;
|
|
break;
|
|
case CONSTANT_T:
|
|
result = long(constant_t);
|
|
break;
|
|
|
|
case CONSTANT_A:
|
|
result = constant_a;
|
|
break;
|
|
|
|
case AMOUNT:
|
|
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;
|
|
}
|
|
else if (details.account && account_has_xdata(*details.account)) {
|
|
result = account_xdata(*details.account).value;
|
|
}
|
|
else {
|
|
result = 0L;
|
|
}
|
|
break;
|
|
|
|
case COST:
|
|
if (details.xact) {
|
|
bool set = false;
|
|
if (transaction_has_xdata(*details.xact)) {
|
|
transaction_xdata_t& xdata(transaction_xdata_(*details.xact));
|
|
if (xdata.dflags & TRANSACTION_COMPOSITE) {
|
|
if (xdata.composite_amount.type == value_t::BALANCE_PAIR &&
|
|
((balance_pair_t *) xdata.composite_amount.data)->cost)
|
|
result = *((balance_pair_t *) xdata.composite_amount.data)->cost;
|
|
else
|
|
result = xdata.composite_amount;
|
|
set = true;
|
|
}
|
|
}
|
|
|
|
if (! set) {
|
|
if (details.xact->cost)
|
|
result = *details.xact->cost;
|
|
else
|
|
result = details.xact->amount;
|
|
}
|
|
}
|
|
else if (details.account && account_has_xdata(*details.account)) {
|
|
result = account_xdata(*details.account).value.cost();
|
|
}
|
|
else {
|
|
result = 0L;
|
|
}
|
|
break;
|
|
|
|
case 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;
|
|
break;
|
|
case COST_TOTAL:
|
|
if (details.xact && transaction_has_xdata(*details.xact))
|
|
result = transaction_xdata_(*details.xact).total.cost();
|
|
else if (details.account && account_has_xdata(*details.account))
|
|
result = account_xdata(*details.account).total.cost();
|
|
else
|
|
result = 0L;
|
|
break;
|
|
|
|
case VALUE_EXPR:
|
|
if (amount_expr.get())
|
|
amount_expr->compute(result, details);
|
|
else
|
|
result = 0L;
|
|
break;
|
|
case TOTAL_EXPR:
|
|
if (total_expr.get())
|
|
total_expr->compute(result, details);
|
|
else
|
|
result = 0L;
|
|
break;
|
|
|
|
case DATE:
|
|
if (details.xact && transaction_has_xdata(*details.xact) &&
|
|
transaction_xdata_(*details.xact).date)
|
|
result = long(transaction_xdata_(*details.xact).date);
|
|
else if (details.xact)
|
|
result = long(details.xact->date());
|
|
else if (details.entry)
|
|
result = long(details.entry->date());
|
|
else
|
|
result = long(terminus);
|
|
break;
|
|
case CLEARED:
|
|
if (details.xact)
|
|
result = details.xact->state == transaction_t::CLEARED;
|
|
else
|
|
result = false;
|
|
break;
|
|
|
|
case REAL:
|
|
if (details.xact)
|
|
result = ! (details.xact->flags & TRANSACTION_VIRTUAL);
|
|
else
|
|
result = true;
|
|
break;
|
|
|
|
case ACTUAL:
|
|
if (details.xact)
|
|
result = ! (details.xact->flags & TRANSACTION_AUTO);
|
|
else
|
|
result = true;
|
|
break;
|
|
|
|
case INDEX:
|
|
if (details.xact && transaction_has_xdata(*details.xact))
|
|
result = long(transaction_xdata_(*details.xact).index + 1);
|
|
else if (details.account && account_has_xdata(*details.account))
|
|
result = long(account_xdata(*details.account).count);
|
|
else
|
|
result = 0L;
|
|
break;
|
|
|
|
case COUNT:
|
|
if (details.xact && transaction_has_xdata(*details.xact))
|
|
result = long(transaction_xdata_(*details.xact).index + 1);
|
|
else if (details.account && account_has_xdata(*details.account))
|
|
result = long(account_xdata(*details.account).total_count);
|
|
else
|
|
result = 0L;
|
|
break;
|
|
|
|
case DEPTH:
|
|
if (details.account)
|
|
result = long(details.account->depth);
|
|
else
|
|
result = 0L;
|
|
break;
|
|
|
|
case F_ARITH_MEAN:
|
|
if (details.xact && transaction_has_xdata(*details.xact)) {
|
|
assert(left);
|
|
left->compute(result, details);
|
|
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);
|
|
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));
|
|
break;
|
|
|
|
case F_NEG:
|
|
assert(left);
|
|
left->compute(result, details);
|
|
result.negate();
|
|
break;
|
|
|
|
case F_ABS:
|
|
assert(left);
|
|
left->compute(result, details);
|
|
result.abs();
|
|
break;
|
|
|
|
case F_STRIP: {
|
|
assert(left);
|
|
left->compute(result, details);
|
|
|
|
balance_t * bal = NULL;
|
|
switch (result.type) {
|
|
case value_t::BALANCE_PAIR:
|
|
bal = &((balance_pair_t *) result.data)->quantity;
|
|
// fall through...
|
|
|
|
case value_t::BALANCE:
|
|
if (! bal)
|
|
bal = (balance_t *) result.data;
|
|
|
|
if (bal->amounts.size() < 2) {
|
|
result.cast(value_t::AMOUNT);
|
|
} else {
|
|
value_t temp;
|
|
for (amounts_map::const_iterator i = bal->amounts.begin();
|
|
i != bal->amounts.end();
|
|
i++) {
|
|
amount_t x = (*i).second;
|
|
x.clear_commodity();
|
|
temp += x;
|
|
}
|
|
result = temp;
|
|
assert(temp.type == value_t::AMOUNT);
|
|
}
|
|
// fall through...
|
|
|
|
case value_t::AMOUNT:
|
|
((amount_t *) result.data)->clear_commodity();
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case F_CODE_MASK:
|
|
assert(mask);
|
|
if (details.entry)
|
|
result = mask->match(details.entry->code);
|
|
else
|
|
result = false;
|
|
break;
|
|
|
|
case F_PAYEE_MASK:
|
|
assert(mask);
|
|
if (details.entry)
|
|
result = mask->match(details.entry->payee);
|
|
else
|
|
result = false;
|
|
break;
|
|
|
|
case F_NOTE_MASK:
|
|
assert(mask);
|
|
if (details.xact)
|
|
result = mask->match(details.xact->note);
|
|
else
|
|
result = false;
|
|
break;
|
|
|
|
case F_ACCOUNT_MASK:
|
|
assert(mask);
|
|
if (details.account)
|
|
result = mask->match(details.account->fullname());
|
|
else
|
|
result = false;
|
|
break;
|
|
|
|
case F_SHORT_ACCOUNT_MASK:
|
|
assert(mask);
|
|
if (details.account)
|
|
result = mask->match(details.account->name);
|
|
else
|
|
result = false;
|
|
break;
|
|
|
|
case F_COMMODITY_MASK:
|
|
assert(mask);
|
|
if (details.xact)
|
|
result = mask->match(details.xact->amount.commodity().symbol);
|
|
else
|
|
result = false;
|
|
break;
|
|
|
|
case F_VALUE: {
|
|
assert(left);
|
|
left->compute(result, details);
|
|
|
|
std::time_t moment = terminus;
|
|
if (right) {
|
|
switch (right->kind) {
|
|
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)");
|
|
}
|
|
}
|
|
|
|
result = result.value(moment);
|
|
break;
|
|
}
|
|
|
|
case F_INTERP_FUNC: {
|
|
#ifdef USE_BOOST_PYTHON
|
|
if (! python_call(constant_s, right, details, result))
|
|
result = 0L;
|
|
#else
|
|
result = 0L;
|
|
#endif
|
|
break;
|
|
}
|
|
|
|
case O_NOT:
|
|
left->compute(result, details);
|
|
result.negate();
|
|
break;
|
|
|
|
case O_QUES: {
|
|
assert(left);
|
|
assert(right);
|
|
assert(right->kind == O_COL);
|
|
left->compute(result, details);
|
|
if (result)
|
|
right->left->compute(result, details);
|
|
else
|
|
right->right->compute(result, details);
|
|
break;
|
|
}
|
|
|
|
case O_AND:
|
|
assert(left);
|
|
assert(right);
|
|
left->compute(result, details);
|
|
if (result)
|
|
right->compute(result, details);
|
|
break;
|
|
|
|
case O_OR:
|
|
assert(left);
|
|
assert(right);
|
|
left->compute(result, details);
|
|
if (! result)
|
|
right->compute(result, details);
|
|
break;
|
|
|
|
case O_EQ:
|
|
case O_LT:
|
|
case O_LTE:
|
|
case O_GT:
|
|
case O_GTE: {
|
|
assert(left);
|
|
assert(right);
|
|
value_t temp;
|
|
left->compute(temp, details);
|
|
right->compute(result, details);
|
|
switch (kind) {
|
|
case O_EQ: result = temp == result; break;
|
|
case O_LT: result = temp < result; break;
|
|
case O_LTE: result = temp <= result; break;
|
|
case O_GT: result = temp > result; break;
|
|
case O_GTE: result = temp >= result; break;
|
|
default: assert(0); break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case O_ADD:
|
|
case O_SUB:
|
|
case O_MUL:
|
|
case O_DIV: {
|
|
assert(left);
|
|
assert(right);
|
|
value_t temp;
|
|
right->compute(temp, details);
|
|
left->compute(result, details);
|
|
switch (kind) {
|
|
case O_ADD: result += temp; break;
|
|
case O_SUB: result -= temp; break;
|
|
case O_MUL: result *= temp; break;
|
|
case O_DIV: result /= temp; break;
|
|
default: assert(0); break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case LAST:
|
|
default:
|
|
assert(0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static inline void unexpected(char c, char wanted = '\0') {
|
|
if ((unsigned char) c == 0xff) {
|
|
if (wanted)
|
|
throw value_expr_error(std::string("Missing '") + wanted + "'");
|
|
else
|
|
throw value_expr_error("Unexpected end");
|
|
} else {
|
|
if (wanted)
|
|
throw value_expr_error(std::string("Invalid char '") + c +
|
|
"' (wanted '" + wanted + "')");
|
|
else
|
|
throw value_expr_error(std::string("Invalid char '") + c + "'");
|
|
}
|
|
}
|
|
|
|
value_expr_t * parse_value_term(std::istream& in);
|
|
|
|
inline value_expr_t * parse_value_term(const char * p) {
|
|
std::istringstream stream(p);
|
|
return parse_value_term(stream);
|
|
}
|
|
|
|
value_expr_t * parse_value_term(std::istream& in)
|
|
{
|
|
std::auto_ptr<value_expr_t> node;
|
|
|
|
char buf[256];
|
|
char c = peek_next_nonws(in);
|
|
if (std::isdigit(c)) {
|
|
READ_INTO(in, buf, 255, c, std::isdigit(c));
|
|
|
|
node.reset(new value_expr_t(value_expr_t::CONSTANT_I));
|
|
node->constant_i = std::atol(buf);
|
|
return node.release();
|
|
}
|
|
else if (c == '{') {
|
|
in.get(c);
|
|
READ_INTO(in, buf, 255, c, c != '}');
|
|
if (c == '}')
|
|
in.get(c);
|
|
else
|
|
unexpected(c, '}');
|
|
|
|
node.reset(new value_expr_t(value_expr_t::CONSTANT_A));
|
|
node->constant_a.parse(buf);
|
|
return node.release();
|
|
}
|
|
|
|
in.get(c);
|
|
switch (c) {
|
|
// Basic terms
|
|
case 'm':
|
|
node.reset(new value_expr_t(value_expr_t::CONSTANT_T));
|
|
node->constant_t = terminus;
|
|
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 '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;
|
|
|
|
// 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;
|
|
|
|
// 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;
|
|
|
|
// Functions
|
|
case '^':
|
|
node.reset(new value_expr_t(value_expr_t::F_PARENT));
|
|
node->left = parse_value_term(in);
|
|
break;
|
|
|
|
case '-':
|
|
node.reset(new value_expr_t(value_expr_t::F_NEG));
|
|
node->left = parse_value_term(in);
|
|
break;
|
|
|
|
case 'U':
|
|
node.reset(new value_expr_t(value_expr_t::F_ABS));
|
|
node->left = parse_value_term(in);
|
|
break;
|
|
|
|
case 'S':
|
|
node.reset(new value_expr_t(value_expr_t::F_STRIP));
|
|
node->left = parse_value_term(in);
|
|
break;
|
|
|
|
case 'A':
|
|
node.reset(new value_expr_t(value_expr_t::F_ARITH_MEAN));
|
|
node->left = parse_value_term(in);
|
|
break;
|
|
|
|
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);
|
|
}
|
|
in.get(c);
|
|
if (c != ')')
|
|
unexpected(c, ')');
|
|
} else {
|
|
node->left = parse_value_term(in);
|
|
}
|
|
break;
|
|
|
|
// Other
|
|
case 'c':
|
|
case 'C':
|
|
case 'p':
|
|
case 'w':
|
|
case 'W':
|
|
case 'e':
|
|
case '/': {
|
|
bool code_mask = c == 'c';
|
|
bool commodity_mask = c == 'C';
|
|
bool payee_mask = c == 'p';
|
|
bool note_mask = c == 'e';
|
|
bool short_account_mask = c == 'w';
|
|
|
|
if (c == '/') {
|
|
c = peek_next_nonws(in);
|
|
if (c == '/') {
|
|
in.get(c);
|
|
c = in.peek();
|
|
if (c == '/') {
|
|
in.get(c);
|
|
c = in.peek();
|
|
short_account_mask = true;
|
|
} else {
|
|
payee_mask = true;
|
|
}
|
|
}
|
|
} else {
|
|
in.get(c);
|
|
}
|
|
|
|
// Read in the regexp
|
|
READ_INTO(in, buf, 255, c, c != '/');
|
|
if (c != '/')
|
|
unexpected(c, '/');
|
|
|
|
value_expr_t::kind_t kind;
|
|
|
|
if (short_account_mask)
|
|
kind = value_expr_t::F_SHORT_ACCOUNT_MASK;
|
|
else if (code_mask)
|
|
kind = value_expr_t::F_CODE_MASK;
|
|
else if (commodity_mask)
|
|
kind = value_expr_t::F_COMMODITY_MASK;
|
|
else if (payee_mask)
|
|
kind = value_expr_t::F_PAYEE_MASK;
|
|
else if (note_mask)
|
|
kind = value_expr_t::F_NOTE_MASK;
|
|
else
|
|
kind = value_expr_t::F_ACCOUNT_MASK;
|
|
|
|
in.get(c);
|
|
node.reset(new value_expr_t(kind));
|
|
node->mask = new mask_t(buf);
|
|
break;
|
|
}
|
|
|
|
case '@': {
|
|
READ_INTO(in, buf, 255, c, c != '(');
|
|
if (c != '(')
|
|
unexpected(c, '(');
|
|
|
|
node.reset(new value_expr_t(value_expr_t::F_INTERP_FUNC));
|
|
node->constant_s = buf;
|
|
|
|
in.get(c);
|
|
if (peek_next_nonws(in) == ')') {
|
|
in.get(c);
|
|
} else {
|
|
node->right = new value_expr_t(value_expr_t::O_ARG);
|
|
value_expr_t * cur = node->right;
|
|
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 != ')')
|
|
unexpected(c, ')');
|
|
}
|
|
break;
|
|
}
|
|
|
|
case '(':
|
|
node.reset(parse_value_expr(in, true));
|
|
in.get(c);
|
|
if (c != ')')
|
|
unexpected(c, ')');
|
|
break;
|
|
|
|
case '[': {
|
|
READ_INTO(in, buf, 255, c, c != ']');
|
|
if (c != ']')
|
|
unexpected(c, ']');
|
|
in.get(c);
|
|
|
|
node.reset(new value_expr_t(value_expr_t::CONSTANT_T));
|
|
|
|
interval_t timespan(buf);
|
|
node->constant_t = timespan.first();
|
|
break;
|
|
}
|
|
|
|
default:
|
|
in.unget();
|
|
break;
|
|
}
|
|
|
|
return node.release();
|
|
}
|
|
|
|
value_expr_t * parse_mul_expr(std::istream& in)
|
|
{
|
|
std::auto_ptr<value_expr_t> node(parse_value_term(in));
|
|
|
|
if (node.get() && ! in.eof()) {
|
|
char c = peek_next_nonws(in);
|
|
while (c == '*' || c == '/') {
|
|
in.get(c);
|
|
switch (c) {
|
|
case '*': {
|
|
std::auto_ptr<value_expr_t> prev(node.release());
|
|
node.reset(new value_expr_t(value_expr_t::O_MUL));
|
|
node->left = prev.release();
|
|
node->right = parse_value_term(in);
|
|
break;
|
|
}
|
|
|
|
case '/': {
|
|
std::auto_ptr<value_expr_t> prev(node.release());
|
|
node.reset(new value_expr_t(value_expr_t::O_DIV));
|
|
node->left = prev.release();
|
|
node->right = parse_value_term(in);
|
|
break;
|
|
}
|
|
}
|
|
c = peek_next_nonws(in);
|
|
}
|
|
}
|
|
|
|
return node.release();
|
|
}
|
|
|
|
value_expr_t * parse_add_expr(std::istream& in)
|
|
{
|
|
std::auto_ptr<value_expr_t> node(parse_mul_expr(in));
|
|
|
|
if (node.get() && ! in.eof()) {
|
|
char c = peek_next_nonws(in);
|
|
while (c == '+' || c == '-') {
|
|
in.get(c);
|
|
switch (c) {
|
|
case '+': {
|
|
std::auto_ptr<value_expr_t> prev(node.release());
|
|
node.reset(new value_expr_t(value_expr_t::O_ADD));
|
|
node->left = prev.release();
|
|
node->right = parse_mul_expr(in);
|
|
break;
|
|
}
|
|
|
|
case '-': {
|
|
std::auto_ptr<value_expr_t> prev(node.release());
|
|
node.reset(new value_expr_t(value_expr_t::O_SUB));
|
|
node->left = prev.release();
|
|
node->right = parse_mul_expr(in);
|
|
break;
|
|
}
|
|
}
|
|
c = peek_next_nonws(in);
|
|
}
|
|
}
|
|
|
|
return node.release();
|
|
}
|
|
|
|
value_expr_t * parse_logic_expr(std::istream& in)
|
|
{
|
|
std::auto_ptr<value_expr_t> 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);
|
|
return node.release();
|
|
}
|
|
|
|
node.reset(parse_add_expr(in));
|
|
|
|
if (node.get() && ! in.eof()) {
|
|
char c = peek_next_nonws(in);
|
|
if (c == '=' || c == '<' || c == '>') {
|
|
in.get(c);
|
|
switch (c) {
|
|
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);
|
|
break;
|
|
}
|
|
|
|
case '<': {
|
|
std::auto_ptr<value_expr_t> 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);
|
|
break;
|
|
}
|
|
|
|
case '>': {
|
|
std::auto_ptr<value_expr_t> 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);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
if (! in.eof())
|
|
unexpected(c);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return node.release();
|
|
}
|
|
|
|
value_expr_t * parse_value_expr(std::istream& in, const bool partial)
|
|
{
|
|
std::auto_ptr<value_expr_t> node(parse_logic_expr(in));
|
|
|
|
if (node.get() && ! in.eof()) {
|
|
char c = peek_next_nonws(in);
|
|
while (c == '&' || c == '|' || c == '?') {
|
|
in.get(c);
|
|
switch (c) {
|
|
case '&': {
|
|
std::auto_ptr<value_expr_t> prev(node.release());
|
|
node.reset(new value_expr_t(value_expr_t::O_AND));
|
|
node->left = prev.release();
|
|
node->right = parse_logic_expr(in);
|
|
break;
|
|
}
|
|
|
|
case '|': {
|
|
std::auto_ptr<value_expr_t> prev(node.release());
|
|
node.reset(new value_expr_t(value_expr_t::O_OR));
|
|
node->left = prev.release();
|
|
node->right = parse_logic_expr(in);
|
|
break;
|
|
}
|
|
|
|
case '?': {
|
|
std::auto_ptr<value_expr_t> 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);
|
|
c = peek_next_nonws(in);
|
|
if (c != ':')
|
|
unexpected(c, ':');
|
|
in.get(c);
|
|
choices->right = parse_logic_expr(in);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
if (! in.eof())
|
|
unexpected(c);
|
|
break;
|
|
}
|
|
c = peek_next_nonws(in);
|
|
}
|
|
}
|
|
|
|
char c;
|
|
if (! node.get()) {
|
|
in.get(c);
|
|
if (in.eof())
|
|
throw value_expr_error(std::string("Failed to parse value expression"));
|
|
else
|
|
unexpected(c);
|
|
} else if (! partial) {
|
|
in.get(c);
|
|
if (! in.eof())
|
|
unexpected(c);
|
|
else
|
|
in.unget();
|
|
}
|
|
|
|
return node.release();
|
|
}
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
|
|
void dump_value_expr(std::ostream& out, const value_expr_t * node)
|
|
{
|
|
switch (node->kind) {
|
|
case value_expr_t::CONSTANT_I:
|
|
out << "UINT[" << node->constant_i << ']';
|
|
break;
|
|
case value_expr_t::CONSTANT_T:
|
|
out << "DATE/TIME[" << 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::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;
|
|
|
|
case value_expr_t::F_ACCOUNT_MASK:
|
|
assert(node->mask);
|
|
out << "M_ACCT(" << node->mask->pattern << ')';
|
|
break;
|
|
|
|
case value_expr_t::F_SHORT_ACCOUNT_MASK:
|
|
assert(node->mask);
|
|
out << "M_SACCT(" << node->mask->pattern << ')';
|
|
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::F_INTERP_FUNC:
|
|
out << "F_INTERP[" << node->constant_s << "](";
|
|
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;
|
|
|
|
case value_expr_t::LAST:
|
|
default:
|
|
assert(0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
#endif // DEBUG_ENABLED
|
|
|
|
} // namespace ledger
|
|
|
|
#ifdef USE_BOOST_PYTHON
|
|
|
|
#include <boost/python.hpp>
|
|
|
|
using namespace boost::python;
|
|
using namespace ledger;
|
|
|
|
value_t py_compute_1(value_expr_t& value_expr, const details_t& item)
|
|
{
|
|
value_t result;
|
|
value_expr.compute(result, item);
|
|
return result;
|
|
}
|
|
|
|
template <typename T>
|
|
value_t py_compute(value_expr_t& value_expr, const T& item)
|
|
{
|
|
value_t result;
|
|
value_expr.compute(result, details_t(item));
|
|
return result;
|
|
}
|
|
|
|
value_expr_t * py_parse_value_expr_1(const std::string& str)
|
|
{
|
|
return parse_value_expr(str);
|
|
}
|
|
|
|
value_expr_t * py_parse_value_expr_2(const std::string& str, const bool partial)
|
|
{
|
|
return parse_value_expr(str, partial);
|
|
}
|
|
|
|
#define EXC_TRANSLATOR(type) \
|
|
void exc_translate_ ## type(const type& err) { \
|
|
PyErr_SetString(PyExc_RuntimeError, err.what()); \
|
|
}
|
|
|
|
EXC_TRANSLATOR(value_expr_error)
|
|
EXC_TRANSLATOR(compute_error)
|
|
EXC_TRANSLATOR(mask_error)
|
|
|
|
void export_valexpr()
|
|
{
|
|
class_< details_t > ("Details", init<const entry_t&>())
|
|
.def(init<const transaction_t&>())
|
|
.def(init<const account_t&>())
|
|
.add_property("entry",
|
|
make_getter(&details_t::entry,
|
|
return_value_policy<reference_existing_object>()))
|
|
.add_property("xact",
|
|
make_getter(&details_t::xact,
|
|
return_value_policy<reference_existing_object>()))
|
|
.add_property("account",
|
|
make_getter(&details_t::account,
|
|
return_value_policy<reference_existing_object>()))
|
|
;
|
|
|
|
class_< value_expr_t > ("ValueExpr", init<value_expr_t::kind_t>())
|
|
.def("compute", py_compute_1)
|
|
.def("compute", py_compute<account_t>)
|
|
.def("compute", py_compute<entry_t>)
|
|
.def("compute", py_compute<transaction_t>)
|
|
;
|
|
|
|
def("parse_value_expr", py_parse_value_expr_1,
|
|
return_value_policy<manage_new_object>());
|
|
def("parse_value_expr", py_parse_value_expr_2,
|
|
return_value_policy<manage_new_object>());
|
|
|
|
class_< item_predicate<transaction_t> >
|
|
("TransactionPredicate", init<std::string>())
|
|
.def("__call__", &item_predicate<transaction_t>::operator())
|
|
;
|
|
|
|
class_< item_predicate<account_t> >
|
|
("AccountPredicate", init<std::string>())
|
|
.def("__call__", &item_predicate<account_t>::operator())
|
|
;
|
|
|
|
#define EXC_TRANSLATE(type) \
|
|
register_exception_translator<type>(&exc_translate_ ## type);
|
|
|
|
EXC_TRANSLATE(value_expr_error);
|
|
EXC_TRANSLATE(compute_error);
|
|
EXC_TRANSLATE(mask_error);
|
|
}
|
|
|
|
#endif // USE_BOOST_PYTHON
|
|
|
|
#ifdef TEST
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
ledger::dump_value_expr(std::cout, ledger::parse_value_expr(argv[1]));
|
|
std::cout << std::endl;
|
|
}
|
|
|
|
#endif // TEST
|