summaryrefslogtreecommitdiff
path: root/src/op.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/op.cc')
-rw-r--r--src/op.cc732
1 files changed, 732 insertions, 0 deletions
diff --git a/src/op.cc b/src/op.cc
new file mode 100644
index 00000000..f38fc86b
--- /dev/null
+++ b/src/op.cc
@@ -0,0 +1,732 @@
+/*
+ * Copyright (c) 2003-2009, 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 <system.hh>
+
+#include "op.h"
+#include "scope.h"
+#include "commodity.h"
+#include "pool.h"
+
+namespace ledger {
+
+expr_t::ptr_op_t expr_t::op_t::compile(scope_t& scope, const int depth)
+{
+ if (is_ident()) {
+ DEBUG("expr.compile", "lookup: " << as_ident());
+
+ if (ptr_op_t def = scope.lookup(symbol_t::FUNCTION, as_ident())) {
+ // Identifier references are first looked up at the point of
+ // definition, and then at the point of every use if they could
+ // not be found there.
+ if (SHOW_DEBUG("expr.compile")) {
+ DEBUG("expr.compile", "Found definition:");
+ def->dump(*_log_stream, 0);
+ }
+ return copy(def);
+ }
+ else if (left()) {
+ return copy();
+ }
+ return this;
+ }
+
+ if (kind < TERMINALS)
+ return this;
+
+ if (kind == O_DEFINE) {
+ switch (left()->kind) {
+ case IDENT:
+ scope.define(symbol_t::FUNCTION, left()->as_ident(), right());
+ break;
+ case O_CALL:
+ if (left()->left()->is_ident())
+ scope.define(symbol_t::FUNCTION, left()->left()->as_ident(), this);
+ else
+ throw_(compile_error, _("Invalid function definition"));
+ break;
+ default:
+ throw_(compile_error, _("Invalid function definition"));
+ }
+ return wrap_value(value_t());
+ }
+
+ ptr_op_t lhs(left()->compile(scope, depth));
+ ptr_op_t rhs(kind > UNARY_OPERATORS && has_right() ?
+ (kind == O_LOOKUP ? right() :
+ right()->compile(scope, depth)) : NULL);
+
+ if (lhs == left() && (! rhs || rhs == right()))
+ return this;
+
+ ptr_op_t intermediate(copy(lhs, rhs));
+
+ // Reduce constants immediately if possible
+ if ((! lhs || lhs->is_value()) && (! rhs || rhs->is_value()))
+ return wrap_value(intermediate->calc(scope, NULL, depth));
+
+ return intermediate;
+}
+
+value_t expr_t::op_t::calc(scope_t& scope, ptr_op_t * locus, const int depth)
+{
+#if defined(DEBUG_ON)
+ bool skip_debug = false;
+#endif
+ try {
+
+ value_t result;
+
+ switch (kind) {
+ case VALUE:
+ result = as_value();
+ break;
+
+ case IDENT: {
+ if (! left())
+ throw_(calc_error, _("Unknown identifier '%1'") << as_ident());
+
+ // Evaluating an identifier is the same as calling its definition
+ // directly, so we create an empty call_scope_t to reflect the scope for
+ // this implicit call.
+ call_scope_t call_args(scope);
+ result = left()->compile(call_args, depth + 1)
+ ->calc(call_args, locus, depth + 1);
+ break;
+ }
+
+ 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);
+ result = as_function()(call_args);
+#if defined(DEBUG_ON)
+ skip_debug = true;
+#endif
+ break;
+ }
+
+ case O_DEFINE: {
+ call_scope_t& call_args(downcast<call_scope_t>(scope));
+ std::size_t args_count = call_args.size();
+ std::size_t args_index = 0;
+
+ assert(left()->kind == O_CALL);
+
+ for (ptr_op_t sym = left()->right();
+ sym;
+ sym = sym->has_right() ? sym->right() : NULL) {
+ ptr_op_t varname = sym;
+ if (sym->kind == O_CONS)
+ varname = sym->left();
+
+ if (! varname->is_ident())
+ throw_(calc_error, _("Invalid function definition"));
+ else if (args_index == args_count)
+ scope.define(symbol_t::FUNCTION, varname->as_ident(),
+ wrap_value(false));
+ else
+ scope.define(symbol_t::FUNCTION, varname->as_ident(),
+ wrap_value(call_args[args_index++]));
+ }
+
+ if (args_index < args_count)
+ throw_(calc_error,
+ _("Too many arguments in function call (saw %1)") << args_count);
+
+ result = right()->calc(scope, locus, depth + 1);
+ break;
+ }
+
+ case O_LOOKUP:
+ if (value_t obj = left()->calc(scope, locus, depth + 1)) {
+ if (obj.is_scope()) {
+ if (obj.as_scope() == NULL) {
+ throw_(calc_error, _("Left operand of . operator is NULL"));
+ } else {
+ scope_t& objscope(*obj.as_scope());
+ if (ptr_op_t member =
+ objscope.lookup(symbol_t::FUNCTION, right()->as_ident())) {
+ result = member->calc(objscope, NULL, depth + 1);
+ break;
+ }
+ }
+ }
+ }
+ if (right()->kind != IDENT)
+ throw_(calc_error,
+ _("Right operand of . operator must be an identifier"));
+ else
+ throw_(calc_error,
+ _("Failed to lookup member '%1'") << right()->as_ident());
+ break;
+
+ case O_CALL: {
+ call_scope_t call_args(scope);
+
+ if (has_right())
+ call_args.set_args(right()->calc(scope, locus, depth + 1));
+
+ ptr_op_t func = left();
+ const string& name(func->as_ident());
+
+ func = func->left();
+ if (! func)
+ throw_(calc_error, _("Calling unknown function '%1'") << name);
+
+ if (func->is_function())
+ result = func->as_function()(call_args);
+ else
+ result = func->calc(call_args, locus, depth + 1);
+ break;
+ }
+
+ case O_MATCH:
+ result = (right()->calc(scope, locus, depth + 1).as_mask()
+ .match(left()->calc(scope, locus, depth + 1).to_string()));
+ break;
+
+ case O_EQ:
+ result = (left()->calc(scope, locus, depth + 1) ==
+ right()->calc(scope, locus, depth + 1));
+ break;
+ case O_LT:
+ result = (left()->calc(scope, locus, depth + 1) <
+ right()->calc(scope, locus, depth + 1));
+ break;
+ case O_LTE:
+ result = (left()->calc(scope, locus, depth + 1) <=
+ right()->calc(scope, locus, depth + 1));
+ break;
+ case O_GT:
+ result = (left()->calc(scope, locus, depth + 1) >
+ right()->calc(scope, locus, depth + 1));
+ break;
+ case O_GTE:
+ result = (left()->calc(scope, locus, depth + 1) >=
+ right()->calc(scope, locus, depth + 1));
+ break;
+
+ case O_ADD:
+ result = (left()->calc(scope, locus, depth + 1) +
+ right()->calc(scope, locus, depth + 1));
+ break;
+ case O_SUB:
+ result = (left()->calc(scope, locus, depth + 1) -
+ right()->calc(scope, locus, depth + 1));
+ break;
+ case O_MUL:
+ result = (left()->calc(scope, locus, depth + 1) *
+ right()->calc(scope, locus, depth + 1));
+ break;
+ case O_DIV:
+ result = (left()->calc(scope, locus, depth + 1) /
+ right()->calc(scope, locus, depth + 1));
+ break;
+
+ case O_NEG:
+ result = left()->calc(scope, locus, depth + 1).negated();
+ break;
+
+ case O_NOT:
+ result = ! left()->calc(scope, locus, depth + 1);
+ break;
+
+ case O_AND:
+ if (left()->calc(scope, locus, depth + 1))
+ result = right()->calc(scope, locus, depth + 1);
+ else
+ result = false;
+ break;
+
+ case O_OR:
+ if (value_t temp = left()->calc(scope, locus, depth + 1))
+ result = temp;
+ else
+ result = right()->calc(scope, locus, depth + 1);
+ break;
+
+ case O_QUERY:
+ assert(right());
+ assert(right()->kind == O_COLON);
+
+ if (value_t temp = left()->calc(scope, locus, depth + 1))
+ result = right()->left()->calc(scope, locus, depth + 1);
+ else
+ result = right()->right()->calc(scope, locus, depth + 1);
+ break;
+
+ case O_COLON:
+ assert(! "We should never calculate an O_COLON operator");
+ break;
+
+ case O_CONS:
+ result = left()->calc(scope, locus, depth + 1);
+ DEBUG("op.cons", "car = " << result);
+
+ if (has_right()) {
+ value_t temp;
+ temp.push_back(result);
+
+ ptr_op_t next = right();
+ while (next) {
+ ptr_op_t value_op;
+ if (next->kind == O_CONS) {
+ value_op = next->left();
+ next = next->right();
+ } else {
+ value_op = next;
+ next = NULL;
+ }
+ temp.push_back(value_op->calc(scope, locus, depth + 1));
+ DEBUG("op.cons", "temp now = " << temp);
+ }
+ result = temp;
+ }
+ break;
+
+ case O_SEQ: {
+ symbol_scope_t seq_scope(scope);
+
+ // An O_SEQ is very similar to an O_CONS except that only the last result
+ // value in the series is kept. O_CONS builds up a list.
+ //
+ // Another feature of O_SEQ is that it pushes a new symbol scope onto the
+ // stack.
+ result = left()->calc(seq_scope, locus, depth + 1);
+
+ if (has_right()) {
+ ptr_op_t next = right();
+ while (next) {
+ ptr_op_t value_op;
+ if (next->kind == O_SEQ) {
+ value_op = next->left();
+ next = next->right();
+ } else {
+ value_op = next;
+ next = NULL;
+ }
+ result = value_op->calc(seq_scope, locus, depth + 1);
+ }
+ }
+ break;
+ }
+
+ case LAST:
+ default:
+ assert(false);
+ break;
+ }
+
+#if defined(DEBUG_ON)
+ if (! skip_debug && SHOW_DEBUG("expr.calc")) {
+ for (int i = 0; i < depth; i++)
+ ledger::_log_buffer << '.';
+ ledger::_log_buffer << op_context(this) << " => ";
+ result.dump(ledger::_log_buffer, true);
+ DEBUG("expr.calc", "");
+ }
+#endif
+
+ return result;
+
+ }
+ catch (const std::exception& err) {
+ if (locus && ! *locus)
+ *locus = this;
+ throw;
+ }
+}
+
+namespace {
+ bool print_cons(std::ostream& out, const expr_t::const_ptr_op_t op,
+ const expr_t::op_t::context_t& context)
+ {
+ bool found = false;
+
+ assert(op->left());
+ if (op->left()->print(out, context))
+ found = true;
+
+ if (op->has_right()) {
+ out << ", ";
+ if (op->right()->kind == expr_t::op_t::O_CONS)
+ found = print_cons(out, op->right(), context);
+ else if (op->right()->print(out, context))
+ found = true;
+ }
+ return found;
+ }
+
+ bool print_seq(std::ostream& out, const expr_t::const_ptr_op_t op,
+ const expr_t::op_t::context_t& context)
+ {
+ bool found = false;
+
+ assert(op->left());
+ if (op->left()->print(out, context))
+ found = true;
+
+ if (op->has_right()) {
+ out << "; ";
+
+ if (op->right()->kind == expr_t::op_t::O_CONS)
+ found = print_cons(out, op->right(), context);
+ else if (op->right()->print(out, context))
+ found = true;
+ }
+
+ return found;
+ }
+}
+
+bool expr_t::op_t::print(std::ostream& out, const context_t& context) const
+{
+ bool found = false;
+
+ if (context.start_pos && this == context.op_to_find) {
+ *context.start_pos = out.tellp();
+ *context.start_pos -= 1;
+ found = true;
+ }
+
+ string symbol;
+
+ switch (kind) {
+ case VALUE:
+ as_value().dump(out, context.relaxed);
+ break;
+
+ case IDENT:
+ out << as_ident();
+ break;
+
+ case FUNCTION:
+ out << "<FUNCTION>";
+ break;
+
+ case O_NOT:
+ out << "!(";
+ if (left() && left()->print(out, context))
+ found = true;
+ out << ")";
+ break;
+ case O_NEG:
+ out << "-(";
+ if (left() && left()->print(out, context))
+ found = true;
+ out << ")";
+ break;
+
+ case O_ADD:
+ out << "(";
+ if (left() && left()->print(out, context))
+ found = true;
+ out << " + ";
+ if (has_right() && right()->print(out, context))
+ found = true;
+ out << ")";
+ break;
+ case O_SUB:
+ out << "(";
+ if (left() && left()->print(out, context))
+ found = true;
+ out << " - ";
+ if (has_right() && right()->print(out, context))
+ found = true;
+ out << ")";
+ break;
+ case O_MUL:
+ out << "(";
+ if (left() && left()->print(out, context))
+ found = true;
+ out << " * ";
+ if (has_right() && right()->print(out, context))
+ found = true;
+ out << ")";
+ break;
+ case O_DIV:
+ out << "(";
+ if (left() && left()->print(out, context))
+ found = true;
+ out << " / ";
+ if (has_right() && right()->print(out, context))
+ found = true;
+ out << ")";
+ break;
+
+ case O_EQ:
+ out << "(";
+ if (left() && left()->print(out, context))
+ found = true;
+ out << " == ";
+ if (has_right() && right()->print(out, context))
+ found = true;
+ out << ")";
+ break;
+ case O_LT:
+ out << "(";
+ if (left() && left()->print(out, context))
+ found = true;
+ out << " < ";
+ if (has_right() && right()->print(out, context))
+ found = true;
+ out << ")";
+ break;
+ case O_LTE:
+ out << "(";
+ if (left() && left()->print(out, context))
+ found = true;
+ out << " <= ";
+ if (has_right() && right()->print(out, context))
+ found = true;
+ out << ")";
+ break;
+ case O_GT:
+ out << "(";
+ if (left() && left()->print(out, context))
+ found = true;
+ out << " > ";
+ if (has_right() && right()->print(out, context))
+ found = true;
+ out << ")";
+ break;
+ case O_GTE:
+ out << "(";
+ if (left() && left()->print(out, context))
+ found = true;
+ out << " >= ";
+ if (has_right() && right()->print(out, context))
+ found = true;
+ out << ")";
+ break;
+
+ case O_AND:
+ out << "(";
+ if (left() && left()->print(out, context))
+ found = true;
+ out << " & ";
+ if (has_right() && right()->print(out, context))
+ found = true;
+ out << ")";
+ break;
+ case O_OR:
+ out << "(";
+ if (left() && left()->print(out, context))
+ found = true;
+ out << " | ";
+ if (has_right() && right()->print(out, context))
+ found = true;
+ out << ")";
+ break;
+
+ case O_QUERY:
+ if (left() && left()->print(out, context))
+ found = true;
+ out << " ? ";
+ if (has_right() && right()->print(out, context))
+ found = true;
+ break;
+
+ case O_COLON:
+ if (left() && left()->print(out, context))
+ found = true;
+ out << " : ";
+ if (has_right() && right()->print(out, context))
+ found = true;
+ break;
+
+ case O_CONS:
+ found = print_cons(out, this, context);
+ break;
+
+ case O_SEQ:
+ out << "(";
+ found = print_seq(out, this, context);
+ out << ")";
+ break;
+
+ case O_DEFINE:
+ if (left() && left()->print(out, context))
+ found = true;
+ out << " := ";
+ if (has_right() && right()->print(out, context))
+ found = true;
+ break;
+
+ case O_LOOKUP:
+ if (left() && left()->print(out, context))
+ found = true;
+ out << ".";
+ if (has_right() && right()->print(out, context))
+ found = true;
+ break;
+
+ case O_CALL:
+ if (left() && left()->print(out, context))
+ found = true;
+ if (has_right()) {
+ if (right()->kind == O_SEQ) {
+ if (right()->print(out, context))
+ found = true;
+ } else {
+ out << "(";
+ if (has_right() && right()->print(out, context))
+ found = true;
+ out << ")";
+ }
+ } else {
+ out << "()";
+ }
+ break;
+
+ case O_MATCH:
+ if (left() && left()->print(out, context))
+ found = true;
+ out << " =~ ";
+ if (has_right() && right()->print(out, context))
+ found = true;
+ break;
+
+ case LAST:
+ default:
+ assert(false);
+ break;
+ }
+
+ if (! symbol.empty()) {
+ if (commodity_pool_t::current_pool->find(symbol))
+ out << '@';
+ out << symbol;
+ }
+
+ if (context.end_pos && this == context.op_to_find) {
+ *context.end_pos = out.tellp();
+ *context.end_pos -= 1;
+ }
+
+ return found;
+}
+
+void expr_t::op_t::dump(std::ostream& out, const int depth) const
+{
+ out.setf(std::ios::left);
+ out.width((sizeof(void *) * 2) + 2);
+ out << this;
+
+ for (int i = 0; i < depth; i++)
+ out << " ";
+
+ switch (kind) {
+ case VALUE:
+ out << "VALUE: ";
+ as_value().dump(out);
+ break;
+
+ case IDENT:
+ out << "IDENT: " << as_ident();
+ break;
+
+ case FUNCTION:
+ out << "FUNCTION";
+ break;
+
+ case O_DEFINE: out << "O_DEFINE"; break;
+ case O_LOOKUP: out << "O_LOOKUP"; 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_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_QUERY: out << "O_QUERY"; break;
+ case O_COLON: out << "O_COLON"; break;
+
+ case O_CONS: out << "O_CONS"; break;
+ case O_SEQ: out << "O_SEQ"; break;
+
+ case LAST:
+ default:
+ assert(false);
+ break;
+ }
+
+ out << " (" << refc << ')' << std::endl;
+
+ // An identifier is a special non-terminal, in that its left() can
+ // hold the compiled definition of the identifier.
+ if (kind > TERMINALS || is_ident()) {
+ if (left()) {
+ left()->dump(out, depth + 1);
+ if (kind > UNARY_OPERATORS && has_right())
+ right()->dump(out, depth + 1);
+ }
+ else if (kind > UNARY_OPERATORS) {
+ assert(! has_right());
+ }
+ }
+}
+
+string op_context(const expr_t::ptr_op_t op,
+ const expr_t::ptr_op_t locus)
+{
+ ostream_pos_type start_pos, end_pos;
+ expr_t::op_t::context_t context(op, locus, &start_pos, &end_pos);
+ std::ostringstream buf;
+ buf << " ";
+ if (op->print(buf, context)) {
+ buf << "\n";
+ for (int i = 0; i <= end_pos; i++) {
+ if (i > start_pos)
+ buf << "^";
+ else
+ buf << " ";
+ }
+ }
+ return buf.str();
+}
+
+} // namespace ledger