This means transactions can only have day-level granularity -- which has always been the case from an data file point of view. The advantage to this restriction is that reports will now be immune from daylight savings related bugs, where a transaction falls to the wrong side of a --monthly report, for example.
1155 lines
27 KiB
C++
1155 lines
27 KiB
C++
/*
|
|
* 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<amount_t> 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 F_CODE_MASK:
|
|
if (details.entry && details.entry->code)
|
|
result = as_mask().match(*details.entry->code);
|
|
else
|
|
result = false;
|
|
break;
|
|
|
|
case F_PAYEE_MASK:
|
|
if (details.entry)
|
|
result = as_mask().match(details.entry->payee);
|
|
else
|
|
result = false;
|
|
break;
|
|
|
|
case F_NOTE_MASK:
|
|
if (details.xact && details.xact->note)
|
|
result = as_mask().match(*details.xact->note);
|
|
else
|
|
result = false;
|
|
break;
|
|
|
|
case F_ACCOUNT_MASK:
|
|
if (details.account)
|
|
result = as_mask().match(details.account->fullname());
|
|
else
|
|
result = false;
|
|
break;
|
|
|
|
case F_SHORT_ACCOUNT_MASK:
|
|
if (details.account)
|
|
result = as_mask().match(details.account->name);
|
|
else
|
|
result = false;
|
|
break;
|
|
|
|
case F_COMMODITY_MASK:
|
|
if (details.xact)
|
|
result = as_mask().match(details.xact->amount.commodity().base_symbol());
|
|
else
|
|
result = false;
|
|
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
|
|
// 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(right() ? 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 (ptr_op_t reference = compile(scope)) {
|
|
if (reference != this)
|
|
return reference->calc(scope);
|
|
}
|
|
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 (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;
|
|
}
|
|
|
|
if (func->kind != FUNCTION)
|
|
throw_(calc_error, "Calling non-function");
|
|
|
|
return func->as_function()(call_args);
|
|
}
|
|
|
|
case INDEX: {
|
|
call_scope_t args(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<unsigned long>(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 << "<FUNCTION>";
|
|
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 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<unsigned long>(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_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(std::ostream& in)
|
|
{
|
|
}
|
|
|
|
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
|