#include "valexpr.h" #include "walk.h" #include "utils.h" namespace ledger { value_expr amount_expr; value_expr total_expr; std::auto_ptr global_scope; datetime_t terminus; details_t::details_t(const transaction_t& _xact) : entry(_xact.entry), xact(&_xact), account(xact_account(_xact)) { DEBUG("ledger.memory.ctors", "ctor details_t"); } bool compute_amount(value_expr_t * expr, amount_t& amt, const transaction_t * xact, value_expr_t * context) { value_t result; try { expr->compute(result, xact ? details_t(*xact) : details_t(), context); result.cast(value_t::AMOUNT); amt = result.as_amount_lval(); } catch (error * err) { if (err->context.empty() || ! dynamic_cast(err->context.back())) err->context.push_back(new valexpr_context(expr)); error_context * last = err->context.back(); if (valexpr_context * ctxt = dynamic_cast(last)) { ctxt->expr = expr->acquire(); ctxt->desc = "While computing amount expression:"; } throw err; } return true; } value_expr_t::~value_expr_t() { DEBUG("ledger.memory.dtors", "dtor value_expr_t " << this); DEBUG("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: assert(value); delete value; 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) return NULL; value_expr 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)); temp->value = new value_t; expr->compute(*(temp->value), 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) 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 { try { switch (kind) { case ARG_INDEX: throw new compute_error("Cannot directly compute an arg_index"); case CONSTANT: assert(value); result = *value; break; case F_NOW: result = terminus; break; case AMOUNT: if (details.xact) { if (transaction_has_xdata(*details.xact) && transaction_xdata_(*details.xact).dflags & TRANSACTION_COMPOUND) result = transaction_xdata_(*details.xact).value; 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 PRICE: if (details.xact) { bool set = false; if (transaction_has_xdata(*details.xact)) { transaction_xdata_t& xdata(transaction_xdata_(*details.xact)); if (xdata.dflags & TRANSACTION_COMPOUND) { result = xdata.value.value(); set = true; } } if (! set) { optional value = details.xact->amount.value(); if (value) result = *value; else result = 0L; } } else if (details.account && account_has_xdata(*details.account)) { result = account_xdata(*details.account).value.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_COMPOUND) { result = xdata.value.cost(); 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 PRICE_TOTAL: if (details.xact && transaction_has_xdata(*details.xact)) result = transaction_xdata_(*details.xact).total.value(); else if (details.account && account_has_xdata(*details.account)) result = account_xdata(*details.account).total.value(); 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, context); else result = 0L; break; case TOTAL_EXPR: if (total_expr.get()) total_expr->compute(result, details, context); else result = 0L; break; case DATE: if (details.xact && transaction_has_xdata(*details.xact) && is_valid(transaction_xdata_(*details.xact).date)) result = transaction_xdata_(*details.xact).date; else if (details.xact) result = details.xact->date(); else if (details.entry) result = details.entry->date(); else result = terminus; break; case ACT_DATE: if (details.xact && transaction_has_xdata(*details.xact) && is_valid(transaction_xdata_(*details.xact).date)) result = transaction_xdata_(*details.xact).date; else if (details.xact) result = details.xact->actual_date(); else if (details.entry) result = details.entry->actual_date(); else result = terminus; break; case EFF_DATE: if (details.xact && transaction_has_xdata(*details.xact) && is_valid(transaction_xdata_(*details.xact).date)) result = transaction_xdata_(*details.xact).date; else if (details.xact) result = details.xact->effective_date(); else if (details.entry) result = details.entry->effective_date(); else result = 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_PRICE: { int arg_index = 0; value_expr_t * expr = find_leaf(context, 0, arg_index); expr->compute(result, details, context); result = result.value(); break; } case F_DATE: { int arg_index = 0; value_expr_t * expr = find_leaf(context, 0, arg_index); expr->compute(result, details, context); result = result.as_datetime_lval(); break; } case F_DATECMP: { int arg_index = 0; value_expr_t * expr = find_leaf(context, 0, arg_index); expr->compute(result, details, context); result = result.as_datetime_lval(); if (! result) break; arg_index = 0; expr = find_leaf(context, 1, arg_index); value_t moment; expr->compute(moment, details, context); if (moment.is_type(value_t::DATETIME)) { result.cast(value_t::INTEGER); moment.cast(value_t::INTEGER); result -= moment; } else { throw new compute_error("Invalid date passed to datecmp(value,date)", new valexpr_context(expr)); } break; } case F_YEAR: case F_MONTH: case F_DAY: { int arg_index = 0; value_expr_t * expr = find_leaf(context, 0, arg_index); expr->compute(result, details, context); if (! result.is_type(value_t::DATETIME)) throw new compute_error("Invalid date passed to year|month|day(date)", new valexpr_context(expr)); datetime_t& moment(result.as_datetime_lval()); switch (kind) { case F_YEAR: result = (long)moment.date().year(); break; case F_MONTH: result = (long)moment.date().month(); break; case F_DAY: result = (long)moment.date().day(); break; default: break; } break; } case F_ARITH_MEAN: { int arg_index = 0; value_expr_t * expr = find_leaf(context, 0, arg_index); if (details.xact && transaction_has_xdata(*details.xact)) { 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) { 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), context); break; case F_ABS: { int arg_index = 0; value_expr_t * expr = find_leaf(context, 0, arg_index); expr->compute(result, details, context); result.abs(); break; } case F_ROUND: { int arg_index = 0; value_expr_t * expr = find_leaf(context, 0, arg_index); expr->compute(result, details, context); result.round(); break; } case F_COMMODITY: { int arg_index = 0; value_expr_t * expr = find_leaf(context, 0, arg_index); expr->compute(result, details, context); if (! result.is_type(value_t::AMOUNT)) throw new compute_error("Argument to commodity() must be a commoditized amount", new valexpr_context(expr)); amount_t temp("1"); temp.set_commodity(result.as_amount_lval().commodity()); result = temp; break; } case F_SET_COMMODITY: { int arg_index = 0; value_expr_t * expr = find_leaf(context, 0, arg_index); value_t temp; expr->compute(temp, details, context); arg_index = 0; expr = find_leaf(context, 1, arg_index); expr->compute(result, details, context); if (! result.is_type(value_t::AMOUNT)) throw new compute_error ("Second argument to set_commodity() must be a commoditized amount", new valexpr_context(expr)); amount_t one("1"); one.set_commodity(result.as_amount_lval().commodity()); result = one; result *= temp; break; } case F_QUANTITY: { int arg_index = 0; value_expr_t * expr = find_leaf(context, 0, arg_index); expr->compute(result, details, context); balance_t * bal = NULL; switch (result.type()) { case value_t::BALANCE_PAIR: bal = &(result.as_balance_pair_lval().quantity()); // fall through... case value_t::BALANCE: if (! bal) bal = &(result.as_balance_lval()); if (bal->amounts.size() < 2) { result.cast(value_t::AMOUNT); } else { value_t temp; for (balance_t::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.is_type(value_t::AMOUNT)); } // fall through... case value_t::AMOUNT: result.as_amount_lval().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().base_symbol()); else result = false; break; case O_ARG: { int arg_index = 0; assert(left); assert(left->kind == ARG_INDEX); value_expr_t * expr = find_leaf(context, left->arg_index, arg_index); if (expr) expr->compute(result, details, context); else result = 0L; break; } case O_COM: assert(left); assert(right); left->compute(result, details, context); right->compute(result, details, context); break; case O_DEF: result = 0L; break; case O_REF: { assert(left); if (right) { value_expr args(reduce_leaves(right, details, context)); left->compute(result, details, args.get()); } else { left->compute(result, details, context); } break; } case F_VALUE: { int arg_index = 0; value_expr_t * expr = find_leaf(context, 0, arg_index); expr->compute(result, details, context); arg_index = 0; expr = find_leaf(context, 1, arg_index); value_t moment; expr->compute(moment, details, context); if (! moment.is_type(value_t::DATETIME)) throw new compute_error("Invalid date passed to P(value,date)", new valexpr_context(expr)); result = result.value(moment.as_datetime_lval()); break; } case O_NOT: left->compute(result, details, context); if (result.strip_annotations()) result = false; else result = true; break; case O_QUES: { assert(left); assert(right); assert(right->kind == O_COL); left->compute(result, details, context); if (result.strip_annotations()) right->left->compute(result, details, context); else right->right->compute(result, details, context); break; } case O_AND: assert(left); assert(right); left->compute(result, details, context); result = result.strip_annotations(); if (result) right->compute(result, details, context); break; case O_OR: assert(left); assert(right); left->compute(result, details, context); if (! result.strip_annotations()) right->compute(result, details, context); break; case O_NEQ: 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, 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; case O_GT: result = temp > result; break; case O_GTE: result = temp >= result; break; default: assert(false); break; } break; } case O_NEG: assert(left); left->compute(result, details, context); result.negate(); break; case O_ADD: case O_SUB: case O_MUL: case O_DIV: { assert(left); assert(right); value_t temp; right->compute(temp, details, context); left->compute(result, details, context); 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(false); break; } break; } case O_PERC: { assert(left); result = "100.0%"; value_t temp; left->compute(temp, details, context); result *= temp; break; } case LAST: default: assert(false); break; } } catch (error * err) { if (err->context.empty() || ! dynamic_cast(err->context.back())) err->context.push_back(new valexpr_context(this)); throw err; } } static inline void unexpected(char c, char wanted = '\0') { if ((unsigned char) c == 0xff) { if (wanted) throw new value_expr_error(string("Missing '") + wanted + "'"); else throw new value_expr_error("Unexpected end"); } else { if (wanted) throw new value_expr_error(string("Invalid char '") + c + "' (wanted '" + wanted + "')"); else throw new value_expr_error(string("Invalid char '") + c + "'"); } } value_expr_t * parse_value_term(std::istream& in, scope_t * scope, const short flags); inline value_expr_t * parse_value_term(const char * p, scope_t * scope, const short flags) { std::istringstream stream(p); return parse_value_term(stream, scope, flags); } value_expr_t * parse_value_term(std::istream& in, scope_t * scope, const short flags) { value_expr node; char buf[256]; char c = peek_next_nonws(in); if (flags & PARSE_VALEXPR_RELAXED) { if (c == '@') { in.get(c); c = peek_next_nonws(in); } else if (! (c == '(' || c == '[' || c == '{' || c == '/')) { amount_t temp; char prev_c = c; unsigned long pos = 0; // When in relaxed parsing mode, we do want to migrate commodity // flags, so that any precision specified by the user updates // the current maximum precision displayed. try { pos = (long)in.tellg(); unsigned char parse_flags = 0; if (flags & PARSE_VALEXPR_NO_MIGRATE) parse_flags |= AMOUNT_PARSE_NO_MIGRATE; if (flags & PARSE_VALEXPR_NO_REDUCE) parse_flags |= AMOUNT_PARSE_NO_REDUCE; temp.parse(in, parse_flags); } catch (amount_error * err) { // If the amount had no commodity, it must be an unambiguous // variable reference if (std::strcmp(err->what(), "No quantity specified for amount") == 0) { in.clear(); in.seekg(pos, std::ios::beg); c = prev_c; goto parse_ident; } else { throw err; } } node.reset(new value_expr_t(value_expr_t::CONSTANT)); node->value = new value_t(temp); goto parsed; } } parse_ident: if (std::isdigit(c) || c == '.') { READ_INTO(in, buf, 255, c, std::isdigit(c) || c == '.'); amount_t temp; temp.parse(buf, AMOUNT_PARSE_NO_MIGRATE); node.reset(new value_expr_t(value_expr_t::CONSTANT)); node->value = new value_t(temp); goto parsed; } 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); beg = in.tellg(); 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, ')'); have_args = true; c = peek_next_nonws(in); } bool definition = false; if (c == '=') { in.get(c); if (peek_next_nonws(in) == '=') { in.unget(); c = '\0'; } else { definition = true; } } if (definition) { std::auto_ptr params(new scope_t(scope)); int arg_index = 0; if (have_args) { bool done = false; 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 == '_'); 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::ARG_INDEX)); node->left->arg_index = arg_index++; params->define(ident, node.release()); } if (peek_next_nonws(in) != '=') { in.get(c); unexpected(c, '='); } in.get(c); } // Define the value associated with the defined identifier value_expr def(parse_boolean_expr(in, params.get(), flags)); if (! def.get()) throw new value_expr_error(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::ARG_INDEX)); node->left->arg_index = arg_index; node->set_right(def.release()); scope->define(buf, node.get()); } else { assert(scope); value_expr_t * def = scope->lookup(buf); if (! def) { if (buf[1] == '\0' && (buf[0] == 'c' || buf[0] == 'C' || buf[0] == 'p' || buf[0] == 'w' || buf[0] == 'W' || buf[0] == 'e')) { in.unget(); goto find_term; } throw new value_expr_error(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); unsigned int count = 0; if (have_args) { in.clear(); in.seekg(beg, std::ios::beg); value_expr args (parse_value_expr(in, scope, flags | PARSE_VALEXPR_PARTIAL)); if (peek_next_nonws(in) != ')') { in.get(c); unexpected(c, ')'); } in.get(c); if (args.get()) { count = count_leaves(args.get()); node->set_right(args.release()); } } if (count != def->left->arg_index) { std::ostringstream errmsg; errmsg << "Wrong number of arguments to '" << buf << "': saw " << count << ", wanted " << def->left->arg_index; throw new value_expr_error(errmsg.str()); } } else { node.reset(def); } } 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, flags)); 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 '{': { amount_t temp; temp.parse(in, AMOUNT_PARSE_NO_MIGRATE); in.get(c); if (c != '}') unexpected(c, '}'); node.reset(new value_expr_t(value_expr_t::CONSTANT)); node->value = new value_t(temp); break; } case '(': { std::auto_ptr locals(new scope_t(scope)); node.reset(parse_value_expr(in, locals.get(), flags | PARSE_VALEXPR_PARTIAL)); 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)); interval_t timespan(buf); node->value = new value_t(timespan.first()); break; } default: in.unget(); break; } parsed: return node.release(); } value_expr_t * parse_mul_expr(std::istream& in, scope_t * scope, const short flags) { value_expr 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, flags)); return node.release(); } node.reset(parse_value_term(in, scope, flags)); if (node.get() && ! in.eof()) { char c = peek_next_nonws(in); while (c == '*' || c == '/') { in.get(c); switch (c) { case '*': { value_expr prev(node.release()); node.reset(new value_expr_t(value_expr_t::O_MUL)); node->set_left(prev.release()); node->set_right(parse_value_term(in, scope, flags)); break; } case '/': { value_expr prev(node.release()); node.reset(new value_expr_t(value_expr_t::O_DIV)); node->set_left(prev.release()); node->set_right(parse_value_term(in, scope, flags)); break; } } c = peek_next_nonws(in); } } return node.release(); } value_expr_t * parse_add_expr(std::istream& in, scope_t * scope, const short flags) { value_expr node; if (peek_next_nonws(in) == '-') { char c; in.get(c); value_expr expr(parse_mul_expr(in, scope, flags)); if (expr->kind == value_expr_t::CONSTANT) { expr->value->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, flags)); if (node.get() && ! in.eof()) { char c = peek_next_nonws(in); while (c == '+' || c == '-') { in.get(c); switch (c) { case '+': { value_expr prev(node.release()); node.reset(new value_expr_t(value_expr_t::O_ADD)); node->set_left(prev.release()); node->set_right(parse_mul_expr(in, scope, flags)); break; } case '-': { value_expr prev(node.release()); node.reset(new value_expr_t(value_expr_t::O_SUB)); node->set_left(prev.release()); node->set_right(parse_mul_expr(in, scope, flags)); break; } } c = peek_next_nonws(in); } } return node.release(); } value_expr_t * parse_logic_expr(std::istream& in, scope_t * scope, const short flags) { value_expr node; if (peek_next_nonws(in) == '!') { char c; in.get(c); node.reset(new value_expr_t(value_expr_t::O_NOT)); node->set_left(parse_add_expr(in, scope, flags)); return node.release(); } node.reset(parse_add_expr(in, scope, flags)); if (node.get() && ! in.eof()) { char c = peek_next_nonws(in); if (c == '!' || c == '=' || c == '<' || c == '>') { in.get(c); switch (c) { case '!': case '=': { bool negate = c == '!'; if ((c = peek_next_nonws(in)) == '=') in.get(c); else unexpected(c, '='); value_expr 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, flags)); break; } case '<': { value_expr 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->set_left(prev.release()); node->set_right(parse_add_expr(in, scope, flags)); break; } case '>': { value_expr 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->set_left(prev.release()); node->set_right(parse_add_expr(in, scope, flags)); break; } default: if (! in.eof()) unexpected(c); break; } } } return node.release(); } value_expr_t * parse_boolean_expr(std::istream& in, scope_t * scope, const short flags) { value_expr node(parse_logic_expr(in, scope, flags)); if (node.get() && ! in.eof()) { char c = peek_next_nonws(in); while (c == '&' || c == '|' || c == '?') { in.get(c); switch (c) { case '&': { value_expr prev(node.release()); node.reset(new value_expr_t(value_expr_t::O_AND)); node->set_left(prev.release()); node->set_right(parse_logic_expr(in, scope, flags)); break; } case '|': { value_expr prev(node.release()); node.reset(new value_expr_t(value_expr_t::O_OR)); node->set_left(prev.release()); node->set_right(parse_logic_expr(in, scope, flags)); break; } case '?': { value_expr prev(node.release()); node.reset(new value_expr_t(value_expr_t::O_QUES)); 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, flags)); c = peek_next_nonws(in); if (c != ':') unexpected(c, ':'); in.get(c); node->right->set_right(parse_logic_expr(in, scope, flags)); 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); globals->define("today", 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::PRICE); globals->define("i", node); globals->define("price", 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::DATE); globals->define("d", node); globals->define("date", node); node = new value_expr_t(value_expr_t::ACT_DATE); globals->define("act_date", node); globals->define("actual_date", node); node = new value_expr_t(value_expr_t::EFF_DATE); globals->define("eff_date", node); globals->define("effective_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::PRICE_TOTAL); globals->define("I", node); globals->define("total_price", node); node = new value_expr_t(value_expr_t::COST_TOTAL); globals->define("B", node); globals->define("total_cost", 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::ARG_INDEX)); node->left->arg_index = 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::ARG_INDEX)); node->left->arg_index = 1; node->set_right(new value_expr_t(value_expr_t::F_ROUND)); globals->define("round", node); node = new value_expr_t(value_expr_t::O_DEF); node->set_left(new value_expr_t(value_expr_t::ARG_INDEX)); node->left->arg_index = 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::ARG_INDEX)); node->left->arg_index = 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::ARG_INDEX)); node->left->arg_index = 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::ARG_INDEX)); node->left->arg_index = 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::ARG_INDEX)); node->left->arg_index = 2; node->set_right(new value_expr_t(value_expr_t::F_VALUE)); globals->define("P", node); parse_value_definition("@value=@P(@t,@m)", globals); parse_value_definition("@total_value=@P(@T,@m)", globals); parse_value_definition("@valueof(x)=@P(@x,@m)", globals); parse_value_definition("@datedvalueof(x,y)=@P(@x,@y)", globals); node = new value_expr_t(value_expr_t::O_DEF); node->set_left(new value_expr_t(value_expr_t::ARG_INDEX)); node->left->arg_index = 1; node->set_right(new value_expr_t(value_expr_t::F_PRICE)); globals->define("priceof", node); node = new value_expr_t(value_expr_t::O_DEF); node->set_left(new value_expr_t(value_expr_t::ARG_INDEX)); node->left->arg_index = 1; node->set_right(new value_expr_t(value_expr_t::F_DATE)); globals->define("dateof", node); node = new value_expr_t(value_expr_t::O_DEF); node->set_left(new value_expr_t(value_expr_t::ARG_INDEX)); node->left->arg_index = 2; node->set_right(new value_expr_t(value_expr_t::F_DATECMP)); globals->define("datecmp", node); node = new value_expr_t(value_expr_t::O_DEF); node->set_left(new value_expr_t(value_expr_t::ARG_INDEX)); node->left->arg_index = 1; node->set_right(new value_expr_t(value_expr_t::F_YEAR)); globals->define("yearof", node); node = new value_expr_t(value_expr_t::O_DEF); node->set_left(new value_expr_t(value_expr_t::ARG_INDEX)); node->left->arg_index = 1; node->set_right(new value_expr_t(value_expr_t::F_MONTH)); globals->define("monthof", node); node = new value_expr_t(value_expr_t::O_DEF); node->set_left(new value_expr_t(value_expr_t::ARG_INDEX)); node->left->arg_index = 1; node->set_right(new value_expr_t(value_expr_t::F_DAY)); globals->define("dayof", node); parse_value_definition("@year=@yearof(@d)", globals); parse_value_definition("@month=@monthof(@d)", globals); parse_value_definition("@day=@dayof(@d)", globals); // 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("total_market", 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("total_gain", node); parse_value_definition("@min(x,y)=@x<@y?@x:@y", globals); parse_value_definition("@max(x,y)=@x>@y?@x:@y", globals); } value_expr_t * parse_value_expr(std::istream& in, scope_t * scope, const short flags) { if (! global_scope.get()) init_value_expr(); std::auto_ptr this_scope(new scope_t(scope ? scope : global_scope.get())); value_expr node; node.reset(parse_boolean_expr(in, this_scope.get(), flags)); if (node.get() && ! in.eof()) { char c = peek_next_nonws(in); while (c == ',') { in.get(c); switch (c) { case ',': { value_expr 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(), flags)); 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 new value_expr_error(string("Failed to parse value expression")); else unexpected(c); } else if (! (flags & PARSE_VALEXPR_PARTIAL)) { in.get(c); if (! in.eof()) unexpected(c); else in.unget(); } return node.release(); } valexpr_context::valexpr_context(const ledger::value_expr_t * _expr, const string& desc) throw() : error_context(desc), expr(_expr), error_node(_expr) { error_node->acquire(); } valexpr_context::~valexpr_context() throw() { if (expr) expr->release(); if (error_node) error_node->release(); } void valexpr_context::describe(std::ostream& out) const throw() { if (! expr) { out << "valexpr_context expr not set!" << std::endl; return; } if (! desc.empty()) out << desc << std::endl; out << " "; unsigned long start = (long)out.tellp() - 1; unsigned long begin; unsigned long end; bool found = ledger::print_value_expr(out, expr, true, error_node, &begin, &end); out << std::endl; if (found) { out << " "; for (unsigned int i = 0; i < end - start; i++) { if (i >= begin - start) out << "^"; else out << " "; } out << std::endl; } } bool print_value_expr(std::ostream& out, const value_expr_t * node, const bool relaxed, const value_expr_t * node_to_find, unsigned long * start_pos, unsigned long * end_pos) { bool found = false; if (start_pos && node == node_to_find) { *start_pos = (long)out.tellp() - 1; found = true; } string symbol; switch (node->kind) { case value_expr_t::ARG_INDEX: out << node->arg_index; break; case value_expr_t::CONSTANT: switch (node->value->type()) { case value_t::BOOLEAN: assert(false); break; case value_t::DATETIME: out << '[' << *(node->value) << ']'; break; case value_t::INTEGER: case value_t::AMOUNT: if (! relaxed) out << '{'; out << *(node->value); if (! relaxed) out << '}'; break; //case value_t::BALANCE: //case value_t::BALANCE_PAIR: default: assert(false); break; } break; case value_expr_t::AMOUNT: symbol = "amount"; break; case value_expr_t::PRICE: symbol = "price"; break; case value_expr_t::COST: symbol = "cost"; break; case value_expr_t::DATE: symbol = "date"; break; case value_expr_t::ACT_DATE: symbol = "actual_date"; break; case value_expr_t::EFF_DATE: symbol = "effective_date"; break; case value_expr_t::CLEARED: symbol = "cleared"; break; case value_expr_t::PENDING: symbol = "pending"; break; case value_expr_t::REAL: symbol = "real"; break; case value_expr_t::ACTUAL: symbol = "actual"; break; case value_expr_t::INDEX: symbol = "index"; break; case value_expr_t::COUNT: symbol = "count"; break; case value_expr_t::DEPTH: symbol = "depth"; break; case value_expr_t::TOTAL: symbol = "total"; break; case value_expr_t::PRICE_TOTAL: symbol = "total_price"; break; case value_expr_t::COST_TOTAL: symbol = "total_cost"; break; case value_expr_t::F_NOW: symbol = "now"; break; case value_expr_t::VALUE_EXPR: if (print_value_expr(out, amount_expr.get(), relaxed, node_to_find, start_pos, end_pos)) found = true; break; case value_expr_t::TOTAL_EXPR: if (print_value_expr(out, total_expr.get(), relaxed, node_to_find, start_pos, end_pos)) found = true; break; case value_expr_t::F_ARITH_MEAN: symbol = "average"; break; case value_expr_t::F_ABS: symbol = "abs"; break; case value_expr_t::F_QUANTITY: symbol = "quantity"; break; case value_expr_t::F_COMMODITY: symbol = "commodity"; break; case value_expr_t::F_SET_COMMODITY: symbol = "set_commodity"; break; case value_expr_t::F_VALUE: symbol = "valueof"; break; case value_expr_t::F_PRICE: symbol = "priceof"; break; case value_expr_t::F_DATE: symbol = "dateof"; break; case value_expr_t::F_DATECMP: symbol = "datecmp"; break; case value_expr_t::F_YEAR: symbol = "yearof"; break; case value_expr_t::F_MONTH: symbol = "monthof"; break; case value_expr_t::F_DAY: symbol = "dayof"; break; case value_expr_t::F_CODE_MASK: out << "c/" << node->mask->expr.str() << "/"; break; case value_expr_t::F_PAYEE_MASK: out << "p/" << node->mask->expr.str() << "/"; break; case value_expr_t::F_NOTE_MASK: out << "e/" << node->mask->expr.str() << "/"; break; case value_expr_t::F_ACCOUNT_MASK: out << "W/" << node->mask->expr.str() << "/"; break; case value_expr_t::F_SHORT_ACCOUNT_MASK: out << "w/" << node->mask->expr.str() << "/"; break; case value_expr_t::F_COMMODITY_MASK: out << "C/" << node->mask->expr.str() << "/"; break; case value_expr_t::O_NOT: out << "!"; if (print_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) found = true; break; case value_expr_t::O_NEG: out << "-"; if (print_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) found = true; break; case value_expr_t::O_PERC: out << "%"; if (print_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) found = true; break; case value_expr_t::O_ARG: out << "@arg" << node->arg_index; break; case value_expr_t::O_DEF: out << "left, relaxed, node_to_find, start_pos, end_pos)) found = true; out << "\" value=\""; if (print_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos)) found = true; out << "\">"; break; case value_expr_t::O_REF: if (print_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) found = true; if (node->right) { out << "("; if (print_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos)) found = true; out << ")"; } break; case value_expr_t::O_COM: if (print_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) found = true; out << ", "; if (print_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos)) found = true; break; case value_expr_t::O_QUES: out << "("; if (print_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) found = true; out << " ? "; if (print_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos)) found = true; out << ")"; break; case value_expr_t::O_COL: if (print_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) found = true; out << " : "; if (print_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos)) found = true; break; case value_expr_t::O_AND: out << "("; if (print_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) found = true; out << " & "; if (print_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos)) found = true; out << ")"; break; case value_expr_t::O_OR: out << "("; if (print_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) found = true; out << " | "; if (print_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos)) found = true; out << ")"; break; case value_expr_t::O_NEQ: out << "("; if (print_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) found = true; out << " != "; if (print_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos)) found = true; out << ")"; break; case value_expr_t::O_EQ: out << "("; if (print_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) found = true; out << " == "; if (print_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos)) found = true; out << ")"; break; case value_expr_t::O_LT: out << "("; if (print_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) found = true; out << " < "; if (print_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos)) found = true; out << ")"; break; case value_expr_t::O_LTE: out << "("; if (print_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) found = true; out << " <= "; if (print_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos)) found = true; out << ")"; break; case value_expr_t::O_GT: out << "("; if (print_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) found = true; out << " > "; if (print_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos)) found = true; out << ")"; break; case value_expr_t::O_GTE: out << "("; if (print_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) found = true; out << " >= "; if (print_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos)) found = true; out << ")"; break; case value_expr_t::O_ADD: out << "("; if (print_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) found = true; out << " + "; if (print_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos)) found = true; out << ")"; break; case value_expr_t::O_SUB: out << "("; if (print_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) found = true; out << " - "; if (print_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos)) found = true; out << ")"; break; case value_expr_t::O_MUL: out << "("; if (print_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) found = true; out << " * "; if (print_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos)) found = true; out << ")"; break; case value_expr_t::O_DIV: out << "("; if (print_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) found = true; out << " / "; if (print_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos)) found = true; out << ")"; break; case value_expr_t::LAST: default: assert(false); break; } if (! symbol.empty()) { if (amount_t::current_pool->find(symbol)) out << '@'; out << symbol; } if (end_pos && node == node_to_find) *end_pos = (long)out.tellp() - 1; return found; } 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::ARG_INDEX: out << "ARG_INDEX - " << node->arg_index; break; case value_expr_t::CONSTANT: out << "CONSTANT - " << *(node->value); break; case value_expr_t::AMOUNT: out << "AMOUNT"; break; case value_expr_t::PRICE: out << "PRICE"; break; case value_expr_t::COST: out << "COST"; break; case value_expr_t::DATE: out << "DATE"; break; case value_expr_t::ACT_DATE: out << "ACT_DATE"; break; case value_expr_t::EFF_DATE: out << "EFF_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::PRICE_TOTAL: out << "PRICE_TOTAL"; break; case value_expr_t::COST_TOTAL: out << "COST_TOTAL"; break; case value_expr_t::VALUE_EXPR: out << "VALUE_EXPR"; break; case value_expr_t::TOTAL_EXPR: out << "TOTAL_EXPR"; 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: out << "F_ACCOUNT_MASK"; break; case value_expr_t::F_SHORT_ACCOUNT_MASK: out << "F_SHORT_ACCOUNT_MASK"; break; case value_expr_t::F_COMMODITY_MASK: out << "F_COMMODITY_MASK"; break; case value_expr_t::F_VALUE: out << "F_VALUE"; break; case value_expr_t::F_PRICE: out << "F_PRICE"; break; case value_expr_t::F_DATE: out << "F_DATE"; break; case value_expr_t::F_DATECMP: out << "F_DATECMP"; break; case value_expr_t::F_YEAR: out << "F_YEAR"; break; case value_expr_t::F_MONTH: out << "F_MONTH"; break; case value_expr_t::F_DAY: out << "F_DAY"; 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(false); break; } out << " (" << node->refc << ')' << 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); } } else { assert(! node->left); } } } // namespace ledger