/* * Copyright (c) 2003-2008, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * - Neither the name of New Artisans LLC nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "op.h" #include "scope.h" #include "binary.h" namespace ledger { #if 0 void expr_t::op_t::compute(value_t& result, const details_t& details, ptr_op_t context) const { try { switch (kind) { case INDEX: throw compute_error("Cannot directly compute an argument index"); case VALUE: result = as_value(); break; case F_NOW: result = terminus; break; case AMOUNT: if (details.xact) { if (xact_has_xdata(*details.xact) && xact_xdata_(*details.xact).dflags & XACT_COMPOUND) result = xact_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 (xact_has_xdata(*details.xact)) { xact_xdata_t& xdata(xact_xdata_(*details.xact)); if (xdata.dflags & XACT_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 (xact_has_xdata(*details.xact)) { xact_xdata_t& xdata(xact_xdata_(*details.xact)); if (xdata.dflags & XACT_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 && xact_has_xdata(*details.xact)) result = xact_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 && xact_has_xdata(*details.xact)) result = xact_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 && xact_has_xdata(*details.xact)) result = xact_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 (value_expr::amount_expr.get()) value_expr::amount_expr->compute(result, details, context); else result = 0L; break; case TOTAL_EXPR: if (value_expr::total_expr.get()) value_expr::total_expr->compute(result, details, context); else result = 0L; break; case DATE: if (details.xact && xact_has_xdata(*details.xact) && is_valid(xact_xdata_(*details.xact).date)) result = xact_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 && xact_has_xdata(*details.xact) && is_valid(xact_xdata_(*details.xact).date)) result = xact_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 && xact_has_xdata(*details.xact) && is_valid(xact_xdata_(*details.xact).date)) result = xact_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 == xact_t::CLEARED; else result = false; break; case PENDING: if (details.xact) result = details.xact->state == xact_t::PENDING; else result = false; break; case REAL: if (details.xact) result = ! (details.xact->has_flags(XACT_VIRTUAL)); else result = true; break; case ACTUAL: if (details.xact) result = ! (details.xact->has_flags(XACT_AUTO)); else result = true; break; case INDEX: if (details.xact && xact_has_xdata(*details.xact)) result = long(xact_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 && xact_has_xdata(*details.xact)) result = long(xact_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: { long arg_index = 0; ptr_op_t expr = find_leaf(context, 0, arg_index); expr->compute(result, details, context); result = result.value(); break; } case F_DATE: { long arg_index = 0; ptr_op_t expr = find_leaf(context, 0, arg_index); expr->compute(result, details, context); result = result.as_datetime(); break; } case F_DATECMP: { long arg_index = 0; ptr_op_t expr = find_leaf(context, 0, arg_index); expr->compute(result, details, context); result = result.as_datetime(); 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 { add_error_context(expr_context(expr)); throw compute_error("Invalid date passed to datecmp(value,date)"); } break; } case F_YEAR: case F_MONTH: case F_DAY: { long arg_index = 0; ptr_op_t expr = find_leaf(context, 0, arg_index); expr->compute(result, details, context); if (! result.is_type(value_t::DATETIME)) { add_error_context(expr_context(expr)); throw compute_error("Invalid date passed to year|month|day(date)"); } const date_t& moment(result.as_date()); switch (kind) { case F_YEAR: result = (long)moment.year(); break; case F_MONTH: result = (long)moment.month(); break; case F_DAY: result = (long)moment.day(); break; default: break; } break; } case F_ARITH_MEAN: { long arg_index = 0; ptr_op_t expr = find_leaf(context, 0, arg_index); if (details.xact && xact_has_xdata(*details.xact)) { expr->compute(result, details, context); result /= amount_t(long(xact_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: { long arg_index = 0; ptr_op_t expr = find_leaf(context, 0, arg_index); expr->compute(result, details, context); result.abs(); break; } case F_ROUND: { long arg_index = 0; ptr_op_t expr = find_leaf(context, 0, arg_index); expr->compute(result, details, context); result.round(); break; } case F_COMMODITY: { long arg_index = 0; ptr_op_t expr = find_leaf(context, 0, arg_index); expr->compute(result, details, context); if (! result.is_type(value_t::AMOUNT)) { add_error_context(expr_context(expr)); throw compute_error("Argument to commodity() must be a commoditized amount"); } amount_t temp("1"); temp.set_commodity(result.as_amount().commodity()); result = temp; break; } case F_SET_COMMODITY: { long arg_index = 0; ptr_op_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)) { add_error_context(expr_context(expr)); throw compute_error("Second argument to set_commodity() must be a commoditized amount"); } amount_t one("1"); one.set_commodity(result.as_amount().commodity()); result = one; result *= temp; break; } case F_QUANTITY: { long arg_index = 0; ptr_op_t expr = find_leaf(context, 0, arg_index); expr->compute(result, details, context); const balance_t * bal = NULL; switch (result.type()) { case value_t::BALANCE_PAIR: bal = &(result.as_balance_pair().quantity()); // fall through... case value_t::BALANCE: if (! bal) bal = &result.as_balance(); if (bal->amounts.size() < 2) { result.cast(value_t::AMOUNT); } else { value_t temp; for (balance_t::amounts_map::value_type pair, bal->amounts) { amount_t x = pair.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 O_ARG: { long arg_index = 0; assert(left()->kind == INDEX); ptr_op_t expr = find_leaf(context, left()->as_long(), arg_index); if (expr) expr->compute(result, details, context); else result = 0L; break; } case O_COMMA: if (! left()) { add_error_context(expr_context(*this)); throw compute_error("Comma operator missing left operand"); } if (! right()) { add_error_context(expr_context(*this)); throw compute_error("Comma operator missing right operand"); } 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: { long arg_index = 0; ptr_op_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)) { add_error_context(expr_context(expr)); throw compute_error("Invalid date passed to P(value,date)"); } result = result.value(moment.as_datetime()); 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 (const std::exception& err) { add_error_context(expr_context(*this)); throw err; } } #endif expr_t::ptr_op_t expr_t::op_t::compile(scope_t& scope) { switch (kind) { case IDENT: if (ptr_op_t def = scope.lookup(as_ident())) { #if 1 return def; #else // jww (2008-08-02): Aren't definitions compiled when they go in? // Would recompiling here really add any benefit? return def->compile(scope); #endif } return this; default: break; } if (kind < TERMINALS) return this; ptr_op_t lhs(left()->compile(scope)); ptr_op_t rhs(kind > UNARY_OPERATORS ? right()->compile(scope) : ptr_op_t()); if (lhs == left() && (! rhs || rhs == right())) return this; ptr_op_t intermediate(copy(lhs, rhs)); if (lhs->is_value() && (! rhs || rhs->is_value())) return wrap_value(intermediate->calc(scope)); return intermediate; } value_t expr_t::op_t::calc(scope_t& scope) { switch (kind) { case VALUE: return as_value(); case IDENT: #if 0 if (ptr_op_t reference = compile(scope)) { if (reference != this) return reference->calc(scope); } #endif throw_(calc_error, "Unknown identifier '" << as_ident() << "'"); case FUNCTION: { // Evaluating a FUNCTION is the same as calling it directly; this happens // when certain functions-that-look-like-variables (such as "amount") are // resolved. call_scope_t call_args(scope); return as_function()(call_args); } case O_CALL: { call_scope_t call_args(scope); if (right()) call_args.set_args(right()->calc(scope)); ptr_op_t func = left(); string name; #if 0 // The expression must be compiled beforehand in order to resolve this // into a function. if (func->kind == IDENT) { name = func->as_ident(); ptr_op_t def = func->compile(scope); if (def == func) throw_(calc_error, "Calling unknown function '" << name << "'"); func = def; } #endif if (func->kind != FUNCTION) throw_(calc_error, "Calling non-function"); return func->as_function()(call_args); } case O_MATCH: assert(right()->is_mask()); return right()->as_mask().match(left()->calc(scope).to_string()); case INDEX: { const call_scope_t& args(downcast(scope)); if (as_index() < args.size()) return args[as_index()]; else throw_(calc_error, "Reference to non-existing argument " << as_index()); break; } case O_NEQ: return left()->calc(scope) != right()->calc(scope); case O_EQ: return left()->calc(scope) == right()->calc(scope); case O_LT: return left()->calc(scope) < right()->calc(scope); case O_LTE: return left()->calc(scope) <= right()->calc(scope); case O_GT: return left()->calc(scope) > right()->calc(scope); case O_GTE: return left()->calc(scope) >= right()->calc(scope); case O_ADD: return left()->calc(scope) + right()->calc(scope); case O_SUB: return left()->calc(scope) - right()->calc(scope); case O_MUL: return left()->calc(scope) * right()->calc(scope); case O_DIV: return left()->calc(scope) / right()->calc(scope); case O_NEG: assert(! right()); return left()->calc(scope).negate(); case O_NOT: assert(! right()); return ! left()->calc(scope); case O_AND: return ! left()->calc(scope) ? value_t(false) : right()->calc(scope); case O_OR: if (value_t temp = left()->calc(scope)) return temp; else return right()->calc(scope); case O_COMMA: { value_t result(left()->calc(scope)); ptr_op_t next = right(); while (next) { ptr_op_t value_op; if (next->kind == O_COMMA /* || next->kind == O_UNION */) { value_op = next->left(); next = next->right(); } else { value_op = next; next = NULL; } result.push_back(value_op->calc(scope)); } return result; } case LAST: default: assert(false); break; } return NULL_VALUE; } bool expr_t::op_t::print(std::ostream& out, print_context_t& context) const { bool found = false; if (context.start_pos && this == context.op_to_find) { *context.start_pos = static_cast(out.tellp()) - 1; found = true; } string symbol; switch (kind) { case VALUE: { as_value().print(out, context.relaxed); break; } case IDENT: out << as_ident(); break; case FUNCTION: out << ""; break; case INDEX: out << '@' << as_index(); break; case O_NOT: out << "!"; if (left() && left()->print(out, context)) found = true; break; case O_NEG: out << "-"; if (left() && left()->print(out, context)) found = true; break; case O_ADD: out << "("; if (left() && left()->print(out, context)) found = true; out << " + "; if (right() && right()->print(out, context)) found = true; out << ")"; break; case O_SUB: out << "("; if (left() && left()->print(out, context)) found = true; out << " - "; if (right() && right()->print(out, context)) found = true; out << ")"; break; case O_MUL: out << "("; if (left() && left()->print(out, context)) found = true; out << " * "; if (right() && right()->print(out, context)) found = true; out << ")"; break; case O_DIV: out << "("; if (left() && left()->print(out, context)) found = true; out << " / "; if (right() && right()->print(out, context)) found = true; out << ")"; break; case O_NEQ: out << "("; if (left() && left()->print(out, context)) found = true; out << " != "; if (right() && right()->print(out, context)) found = true; out << ")"; break; case O_EQ: out << "("; if (left() && left()->print(out, context)) found = true; out << " == "; if (right() && right()->print(out, context)) found = true; out << ")"; break; case O_LT: out << "("; if (left() && left()->print(out, context)) found = true; out << " < "; if (right() && right()->print(out, context)) found = true; out << ")"; break; case O_LTE: out << "("; if (left() && left()->print(out, context)) found = true; out << " <= "; if (right() && right()->print(out, context)) found = true; out << ")"; break; case O_GT: out << "("; if (left() && left()->print(out, context)) found = true; out << " > "; if (right() && right()->print(out, context)) found = true; out << ")"; break; case O_GTE: out << "("; if (left() && left()->print(out, context)) found = true; out << " >= "; if (right() && right()->print(out, context)) found = true; out << ")"; break; case O_AND: out << "("; if (left() && left()->print(out, context)) found = true; out << " & "; if (right() && right()->print(out, context)) found = true; out << ")"; break; case O_OR: out << "("; if (left() && left()->print(out, context)) found = true; out << " | "; if (right() && right()->print(out, context)) found = true; out << ")"; break; case O_COMMA: if (left() && left()->print(out, context)) found = true; out << ", "; if (right() && right()->print(out, context)) found = true; break; case O_CALL: if (left() && left()->print(out, context)) found = true; out << "("; if (right() && right()->print(out, context)) found = true; out << ")"; break; case O_MATCH: out << '/'; if (left() && left()->print(out, context)) found = true; out << "/ =~ "; if (right() && right()->print(out, context)) found = true; break; case LAST: default: assert(false); break; } if (! symbol.empty()) { if (amount_t::current_pool->find(symbol)) out << '@'; out << symbol; } if (context.end_pos && this == context.op_to_find) *context.end_pos = static_cast(out.tellp()) - 1; return found; } void expr_t::op_t::dump(std::ostream& out, const int depth) const { out.setf(std::ios::left); out.width(10); out << this; for (int i = 0; i < depth; i++) out << " "; switch (kind) { case VALUE: out << "VALUE - " << as_value(); break; case IDENT: out << "IDENT - " << as_ident(); break; case INDEX: out << "INDEX - " << as_index(); break; case FUNCTION: out << "FUNCTION"; break; case O_CALL: out << "O_CALL"; break; case O_MATCH: out << "O_MATCH"; break; case O_NOT: out << "O_NOT"; break; case O_NEG: out << "O_NEG"; break; case O_ADD: out << "O_ADD"; break; case O_SUB: out << "O_SUB"; break; case O_MUL: out << "O_MUL"; break; case O_DIV: out << "O_DIV"; break; case O_NEQ: out << "O_NEQ"; break; case O_EQ: out << "O_EQ"; break; case O_LT: out << "O_LT"; break; case O_LTE: out << "O_LTE"; break; case O_GT: out << "O_GT"; break; case O_GTE: out << "O_GTE"; break; case O_AND: out << "O_AND"; break; case O_OR: out << "O_OR"; break; case O_COMMA: out << "O_COMMA"; break; case LAST: default: assert(false); break; } out << " (" << refc << ')' << std::endl; if (kind > TERMINALS) { if (left()) { left()->dump(out, depth + 1); if (right()) right()->dump(out, depth + 1); } else { assert(! right()); } } } void expr_t::op_t::read(const char *& data) { #if 0 if (! read_bool(data)) return expr_t::ptr_op_t(); expr_t::op_t::kind_t kind; read_number(data, kind); expr_t::ptr_op_t expr = new expr_t::op_t(kind); if (kind > expr_t::op_t::TERMINALS) expr->set_left(read_value_expr(data)); switch (expr->kind) { case expr_t::op_t::INDEX: { long temp; read_long(data, temp); expr->set_index(temp); break; } case expr_t::op_t::VALUE: { value_t temp; read_value(data, temp); expr->set_value(temp); break; } case expr_t::op_t::MASK: if (read_bool(data)) read_mask(data, expr->as_mask_lval()); break; default: if (kind > expr_t::op_t::TERMINALS) expr->set_right(read_value_expr(data)); break; } return expr; #endif } void expr_t::op_t::write(std::ostream& out) const { #if 0 if (! expr) { write_bool(out, false); return; } write_bool(out, true); write_number(out, expr->kind); if (expr->kind > expr_t::op_t::TERMINALS) write_value_expr(out, expr->left()); switch (expr->kind) { case expr_t::op_t::INDEX: write_long(out, expr->as_index()); break; case expr_t::op_t::IDENT: write_long(out, expr->as_ident()); break; case expr_t::op_t::VALUE: write_value(out, expr->as_value()); break; case expr_t::op_t::MASK: if (expr->as_mask()) { write_bool(out, true); write_mask(out, expr->as_mask()); } else { write_bool(out, false); } break; default: if (expr->kind > expr_t::op_t::TERMINALS) write_value_expr(out, expr->right()); break; } #endif } #if 0 class op_predicate : public noncopyable { ptr_op_t op; op_predicate(); public: explicit op_predicate(ptr_op_t _op) : op(_op) { TRACE_CTOR(op_predicate, "ptr_op_t"); } ~op_predicate() throw() { TRACE_DTOR(op_predicate); } bool operator()(scope_t& scope) { return op->calc(scope).to_boolean(); } }; #endif } // namespace ledger