ledger/valexpr.cc
John Wiegley d86a91d45b The new XPath parser has been integrated, although I have removed the
XML-related bits -- I just wanted the better infrastructure that had been
created during the rewrite.  It doesn't work, but it compiles and links now.
This means that all of the previous 3.0 code has been moved over, although
there are still snippets of code in pending/old that need to be restored.
2008-07-20 23:12:04 -04:00

1076 lines
26 KiB
C++

/*
* Copyright (c) 2003-2007, 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 "valexpr.h"
#include "parsexp.h"
#include "walk.h"
#include "utils.h"
namespace ledger {
value_expr amount_expr;
value_expr total_expr;
namespace expr {
std::auto_ptr<symbol_scope_t> 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(ptr_op_t expr, amount_t& amt,
const transaction_t * xact, ptr_op_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<valexpr_context *>(err->context.back()))
err->context.push_back(new valexpr_context(expr));
error_context * last = err->context.back();
if (valexpr_context * ctxt = dynamic_cast<valexpr_context *>(last)) {
ctxt->expr = expr;
ctxt->desc = "While computing amount expression:";
}
throw err;
}
return true;
}
void scope_t::define(const string& name, const value_t& val) {
define(name, op_t::wrap_value(val));
}
void symbol_scope_t::define(const string& name, ptr_op_t def)
{
DEBUG("ledger.xpath.syms", "Defining '" << name << "' = " << def);
std::pair<symbol_map::iterator, bool> result
= symbols.insert(symbol_map::value_type(name, def));
if (! result.second) {
symbol_map::iterator i = symbols.find(name);
assert(i != symbols.end());
symbols.erase(i);
std::pair<symbol_map::iterator, bool> result2
= symbols.insert(symbol_map::value_type(name, def));
if (! result2.second)
throw_(compile_error,
"Redefinition of '" << name << "' in same scope");
}
}
namespace {
int count_leaves(ptr_op_t expr)
{
int count = 0;
if (expr->kind != op_t::O_COMMA) {
count = 1;
} else {
count += count_leaves(expr->left());
count += count_leaves(expr->right());
}
return count;
}
ptr_op_t reduce_leaves(ptr_op_t expr, const details_t& details,
ptr_op_t context)
{
if (! expr)
return NULL;
value_expr temp;
if (expr->kind != op_t::O_COMMA) {
if (expr->kind < op_t::TERMINALS) {
temp.reset(expr);
} else {
temp.reset(new op_t(op_t::VALUE));
temp->set_value(value_t());
expr->compute(temp->as_value(), details, context);
}
} else {
temp.reset(new op_t(op_t::O_COMMA));
temp->set_left(reduce_leaves(expr->left(), details, context));
temp->set_right(reduce_leaves(expr->right(), details, context));
}
return temp.release();
}
ptr_op_t find_leaf(ptr_op_t context, int goal, long& found)
{
if (! context)
return NULL;
if (context->kind != op_t::O_COMMA) {
if (goal == found++)
return context;
} else {
ptr_op_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;
}
}
ptr_op_t symbol_scope_t::lookup(const string& name)
{
switch (name[0]) {
#if 0
case 'l':
if (name == "last")
return WRAP_FUNCTOR(bind(xpath_fn_last, _1));
break;
case 'p':
if (name == "position")
return WRAP_FUNCTOR(bind(xpath_fn_position, _1));
break;
case 't':
if (name == "text")
return WRAP_FUNCTOR(bind(xpath_fn_text, _1));
else if (name == "type")
return WRAP_FUNCTOR(bind(xpath_fn_type, _1));
#endif
break;
}
symbol_map::const_iterator i = symbols.find(name);
if (i != symbols.end())
return (*i).second;
return child_scope_t::lookup(name);
}
void op_t::compute(value_t& result, const details_t& details,
ptr_op_t context) const
{
try {
switch (kind) {
case ARG_INDEX:
throw new compute_error("Cannot directly compute an arg_index");
case VALUE:
result = as_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<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 (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->has_flags(TRANSACTION_VIRTUAL));
else
result = true;
break;
case ACTUAL:
if (details.xact)
result = ! (details.xact->has_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: {
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_lval();
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_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: {
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))
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: {
long arg_index = 0;
ptr_op_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: {
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))
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: {
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))
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: {
long arg_index = 0;
ptr_op_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;
}
#if 0
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;
#endif
case O_ARG: {
long arg_index = 0;
assert(left()->kind == ARG_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())
throw new compute_error("Comma operator missing left operand",
new valexpr_context(const_cast<op_t *>(this)));
if (! right())
throw new compute_error("Comma operator missing right operand",
new valexpr_context(const_cast<op_t *>(this)));
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: {
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))
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<valexpr_context *>(err->context.back()))
err->context.push_back(new valexpr_context(const_cast<op_t *>(this)));
throw err;
}
}
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 << " ";
#if 0
unsigned long start = (long)out.tellp() - 1;
unsigned long begin;
unsigned long end;
bool found = 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;
}
#endif
}
#if 0
xpath_t::ptr_op_t xpath_t::op_t::compile(scope_t& scope)
{
switch (kind) {
case VAR_NAME:
case FUNC_NAME:
if (ptr_op_t def = scope.lookup(as_string())) {
#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 xpath_t::op_t::calc(scope_t& scope)
{
bool find_all_nodes = false;
switch (kind) {
case VALUE:
return as_value();
case VAR_NAME:
case FUNC_NAME:
if (ptr_op_t reference = compile(scope)) {
return reference->calc(scope);
} else {
throw_(calc_error, "No " << (kind == VAR_NAME ? "variable" : "function")
<< " named '" << as_string() << "'");
}
break;
case FUNCTION:
// This should never be evaluated directly; it only appears as the
// left node of an O_CALL operator.
assert(false);
break;
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 == FUNC_NAME) {
name = func->as_string();
func = func->compile(scope);
}
if (func->kind != FUNCTION)
throw_(calc_error,
name.empty() ? string("Attempt to call non-function") :
(string("Attempt to call unknown function '") + name + "'"));
return func->as_function()(call_args);
}
case ARG_INDEX: {
call_scope_t& args(CALL_SCOPE(scope));
if (as_long() >= 0 && as_long() < args.size())
return args[as_long()];
else
throw_(calc_error, "Reference to non-existing argument");
break;
}
case O_FIND:
case O_RFIND:
return select_nodes(scope, left()->calc(scope), right(), kind == O_RFIND);
case O_PRED: {
value_t values = left()->calc(scope);
if (! values.is_null()) {
op_predicate pred(right());
if (! values.is_sequence()) {
context_scope_t value_scope(scope, values, 0, 1);
if (pred(value_scope))
return values;
return NULL_VALUE;
} else {
std::size_t index = 0;
std::size_t size = values.as_sequence().size();
value_t result;
foreach (const value_t& value, values.as_sequence()) {
context_scope_t value_scope(scope, value, index, size);
if (pred(value_scope))
result.push_back(value);
index++;
}
return result;
}
}
break;
}
case NODE_ID:
switch (as_name()) {
case document_t::CURRENT:
return current_value(scope);
case document_t::PARENT:
if (optional<parent_node_t&> parent = current_xml_node(scope).parent())
return &*parent;
else
throw_(std::logic_error, "Attempt to access parent of root node");
break;
case document_t::ROOT:
return &current_xml_node(scope).document();
case document_t::ALL:
find_all_nodes = true;
break;
default:
break; // pass down to the NODE_NAME case
}
// fall through...
case NODE_NAME: {
node_t& current_node(current_xml_node(scope));
if (current_node.is_parent_node()) {
const bool have_name_id = kind == NODE_ID;
parent_node_t& parent(current_node.as_parent_node());
value_t result;
foreach (node_t * child, parent) {
if (find_all_nodes ||
( have_name_id && as_name() == child->name_id()) ||
(! have_name_id && as_string() == child->name()))
result.push_back(child);
}
return result;
}
break;
}
case ATTR_ID:
case ATTR_NAME:
if (optional<value_t&> value =
kind == ATTR_ID ? current_xml_node(scope).get_attr(as_name()) :
current_xml_node(scope).get_attr(as_string()))
return *value;
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) && right()->calc(scope);
case O_OR:
return left()->calc(scope) || right()->calc(scope);
case O_COMMA:
case O_UNION: {
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;
}
#endif
} // namespace expr
value_expr::value_expr(const string& _expr_str) : expr_str(_expr_str)
{
TRACE_CTOR(value_expr, "const string&");
if (! _expr_str.empty())
ptr = expr::parser_t(expr_str).expr.ptr;
}
} // namespace ledger