#include "valexpr.h" #include "walk.h" #include "error.h" #include "datetime.h" #include "debug.h" #include "util.h" namespace ledger { std::auto_ptr amount_expr; std::auto_ptr total_expr; std::time_t terminus; details_t::details_t(const transaction_t& _xact) : entry(_xact.entry), xact(&_xact), account(xact_account(_xact)) { DEBUG_PRINT("ledger.memory.ctors", "ctor details_t"); } bool compute_amount(value_expr_t * expr, amount_t& amt, transaction_t& xact) { value_t result; expr->compute(result, details_t(xact)); switch (result.type) { case value_t::BOOLEAN: amt = *((bool *) result.data); break; case value_t::INTEGER: amt = *((long *) result.data); break; case value_t::AMOUNT: amt = *((amount_t *) result.data); break; case value_t::BALANCE: case value_t::BALANCE_PAIR: return false; } return true; } 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 F_NOW: result = long(terminus); 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 PENDING: if (details.xact) result = details.xact->state == transaction_t::PENDING; 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_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)"); } } result = result.value(moment); } break; } case F_VALUE: { assert(left); left->compute(result, details); 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)"); } } result = result.value(moment); 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 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::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; // 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_FUNC)); node->constant_s = buf; in.get(c); if (peek_next_nonws(in) == ')') { 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 != ')') 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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::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; 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::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 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