summaryrefslogtreecommitdiffstats
path: root/src/libixion/formula_interpreter.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/libixion/formula_interpreter.cpp')
-rw-r--r--src/libixion/formula_interpreter.cpp1753
1 files changed, 1753 insertions, 0 deletions
diff --git a/src/libixion/formula_interpreter.cpp b/src/libixion/formula_interpreter.cpp
new file mode 100644
index 0000000..65c95e6
--- /dev/null
+++ b/src/libixion/formula_interpreter.cpp
@@ -0,0 +1,1753 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include "formula_interpreter.hpp"
+#include "formula_functions.hpp"
+#include "debug.hpp"
+
+#include <ixion/cell.hpp>
+#include <ixion/global.hpp>
+#include <ixion/matrix.hpp>
+#include <ixion/formula.hpp>
+#include <ixion/interface/session_handler.hpp>
+#include <ixion/interface/table_handler.hpp>
+#include <ixion/config.hpp>
+#include <ixion/cell_access.hpp>
+
+#include <cassert>
+#include <string>
+#include <iostream>
+#include <sstream>
+#include <cmath>
+#include <optional>
+
+namespace ixion {
+
+namespace {
+
+class invalid_expression : public general_error
+{
+public:
+ invalid_expression(const std::string& msg) : general_error(msg) {}
+};
+
+const formula_token paren_open = formula_token{fop_open};
+const formula_token paren_close = formula_token{fop_close};
+
+}
+
+formula_interpreter::formula_interpreter(const formula_cell* cell, model_context& cxt) :
+ m_parent_cell(cell),
+ m_context(cxt),
+ m_error(formula_error_t::no_error)
+{
+}
+
+formula_interpreter::~formula_interpreter()
+{
+}
+
+void formula_interpreter::set_origin(const abs_address_t& pos)
+{
+ m_pos = pos;
+}
+
+bool formula_interpreter::interpret()
+{
+ mp_handler = m_context.create_session_handler();
+ if (mp_handler)
+ mp_handler->begin_cell_interpret(m_pos);
+
+ try
+ {
+ init_tokens();
+
+ if (m_tokens.empty())
+ {
+ IXION_DEBUG("interpreter has no tokens to interpret");
+ return false;
+ }
+
+ m_cur_token_itr = m_tokens.begin();
+ m_error = formula_error_t::no_error;
+ m_result.reset();
+
+ expression();
+
+ if (m_cur_token_itr != m_tokens.end())
+ {
+ if (mp_handler)
+ mp_handler->set_invalid_expression("formula token interpretation ended prematurely.");
+ return false;
+ }
+
+ pop_result();
+
+ IXION_TRACE("interpretation successfully finished");
+
+ if (mp_handler)
+ mp_handler->end_cell_interpret();
+
+ return true;
+ }
+ catch (const invalid_expression& e)
+ {
+ IXION_DEBUG("invalid expression: " << e.what());
+
+ if (mp_handler)
+ mp_handler->set_invalid_expression(e.what());
+
+ m_error = formula_error_t::invalid_expression;
+ }
+ catch (const formula_error& e)
+ {
+ IXION_DEBUG("formula error: " << e.what());
+
+ if (mp_handler)
+ mp_handler->set_formula_error(e.what());
+
+ m_error = e.get_error();
+ }
+
+ if (mp_handler)
+ mp_handler->end_cell_interpret();
+
+ return false;
+}
+
+formula_result formula_interpreter::transfer_result()
+{
+ return std::move(m_result);
+}
+
+formula_error_t formula_interpreter::get_error() const
+{
+ return m_error;
+}
+
+void formula_interpreter::init_tokens()
+{
+ clear_stacks();
+
+ name_set used_names;
+ m_tokens.clear();
+
+ const formula_tokens_store_ptr_t& ts = m_parent_cell->get_tokens();
+ if (!ts)
+ return;
+
+ for (const formula_token& t : ts->get())
+ {
+ if (t.opcode == fop_named_expression)
+ {
+ // Named expression. Expand it.
+ const auto& name = std::get<std::string>(t.value);
+ const named_expression_t* expr = m_context.get_named_expression(
+ m_pos.sheet, name);
+
+ used_names.insert(name);
+ expand_named_expression(expr, used_names);
+ }
+ else
+ // Normal token.
+ m_tokens.push_back(&t);
+ }
+
+ m_end_token_pos = m_tokens.end();
+}
+
+namespace {
+
+void get_result_from_cell(const model_context& cxt, const abs_address_t& addr, formula_result& res)
+{
+ switch (cxt.get_celltype(addr))
+ {
+ case celltype_t::formula:
+ {
+ res = cxt.get_formula_result(addr);
+ break;
+ }
+ case celltype_t::boolean:
+ res.set_boolean(cxt.get_boolean_value(addr));
+ break;
+ case celltype_t::numeric:
+ res.set_value(cxt.get_numeric_value(addr));
+ break;
+ case celltype_t::string:
+ {
+ std::string_view s = cxt.get_string_value(addr);
+ res.set_string_value(std::string{s});
+ break;
+ }
+ case celltype_t::unknown:
+ default:
+ ;
+ }
+}
+
+}
+
+void formula_interpreter::pop_result()
+{
+ // there should only be one stack value left for the result value.
+ assert(get_stack().size() == 1);
+ stack_value& res = get_stack().back();
+ switch (res.get_type())
+ {
+ case stack_value_t::range_ref:
+ {
+ const abs_range_t& range = res.get_range();
+ get_result_from_cell(m_context, range.first, m_result);
+ break;
+ }
+ case stack_value_t::single_ref:
+ {
+ get_result_from_cell(m_context, res.get_address(), m_result);
+ break;
+ }
+ case stack_value_t::string:
+ {
+ m_result.set_string_value(res.get_string());
+ break;
+ }
+ case stack_value_t::boolean:
+ {
+ m_result.set_boolean(res.get_boolean());
+ break;
+ }
+ case stack_value_t::value:
+ IXION_TRACE("value=" << res.get_value());
+ m_result.set_value(res.get_value());
+ break;
+ case stack_value_t::matrix:
+ m_result.set_matrix(res.pop_matrix());
+ break;
+ case stack_value_t::error:
+ m_result.set_error(res.get_error());
+ break;
+ }
+
+ if (mp_handler)
+ mp_handler->set_result(m_result);
+}
+
+void formula_interpreter::expand_named_expression(const named_expression_t* expr, name_set& used_names)
+{
+ if (!expr)
+ throw formula_error(formula_error_t::name_not_found);
+
+ m_tokens.push_back(&paren_open);
+ for (const auto& t : expr->tokens)
+ {
+ if (t.opcode == fop_named_expression)
+ {
+ const auto& expr_name = std::get<std::string>(t.value);
+ if (used_names.count(expr_name) > 0)
+ {
+ // Circular reference detected.
+ throw invalid_expression("circular referencing of named expressions");
+ }
+ const named_expression_t* this_expr = m_context.get_named_expression(m_pos.sheet, expr_name);
+ used_names.insert(expr_name);
+ expand_named_expression(this_expr, used_names);
+ }
+ else
+ m_tokens.push_back(&t);
+ }
+ m_tokens.push_back(&paren_close);
+}
+
+void formula_interpreter::ensure_token_exists() const
+{
+ if (!has_token())
+ throw invalid_expression("formula expression ended prematurely");
+}
+
+bool formula_interpreter::has_token() const
+{
+ return m_cur_token_itr != m_end_token_pos;
+}
+
+void formula_interpreter::next()
+{
+ ++m_cur_token_itr;
+}
+
+const formula_token& formula_interpreter::token() const
+{
+ assert(m_cur_token_itr != m_end_token_pos);
+ return *(*m_cur_token_itr);
+}
+
+const formula_token& formula_interpreter::token_or_throw() const
+{
+ ensure_token_exists();
+ return *(*m_cur_token_itr);
+}
+
+const formula_token& formula_interpreter::next_token()
+{
+ next();
+ if (!has_token())
+ throw invalid_expression("expecting a token but no more tokens found.");
+
+ return token();
+}
+
+const std::string& formula_interpreter::string_or_throw() const
+{
+ assert(token().opcode == fop_string);
+
+ const string_id_t sid = std::get<string_id_t>(token().value);
+ const std::string* p = m_context.get_string(sid);
+ if (!p)
+ throw general_error("no string found for the specified string ID.");
+
+ if (mp_handler)
+ mp_handler->push_string(sid);
+
+ return *p;
+}
+
+namespace {
+
+bool valid_expression_op(fopcode_t oc)
+{
+ switch (oc)
+ {
+ case fop_plus:
+ case fop_minus:
+ case fop_equal:
+ case fop_not_equal:
+ case fop_less:
+ case fop_less_equal:
+ case fop_greater:
+ case fop_greater_equal:
+ return true;
+ default:
+ ;
+ }
+ return false;
+}
+
+/**
+ * Pop the value off of the stack but only as one of the following type:
+ *
+ * <ul>
+ * <li>value</li>
+ * <li>string</li>
+ * <li>matrix</li>
+ * </ul>
+ */
+std::optional<stack_value> pop_stack_value(const model_context& cxt, formula_value_stack& stack)
+{
+ switch (stack.get_type())
+ {
+ case stack_value_t::boolean:
+ return stack_value{stack.pop_boolean() ? 1.0 : 0.0};
+ case stack_value_t::value:
+ return stack_value{stack.pop_value()};
+ case stack_value_t::string:
+ return stack_value{stack.pop_string()};
+ case stack_value_t::matrix:
+ return stack_value{stack.pop_matrix()};
+ case stack_value_t::single_ref:
+ {
+ const abs_address_t& addr = stack.pop_single_ref();
+ auto ca = cxt.get_cell_access(addr);
+
+ switch (ca.get_type())
+ {
+ case celltype_t::empty:
+ {
+ // empty cell has a value of 0.
+ return stack_value{0.0};
+ }
+ case celltype_t::boolean:
+ // TODO : Decide whether we need to treat this as a
+ // distinct boolean value. For now, let's treat this as a
+ // numeric value equivalent.
+ case celltype_t::numeric:
+ {
+ double val = ca.get_numeric_value();
+ return stack_value{val};
+ }
+ case celltype_t::string:
+ {
+ std::size_t strid = ca.get_string_identifier();
+ const std::string* ps = cxt.get_string(strid);
+ if (!ps)
+ {
+ IXION_DEBUG("fail to get a string value for the id of " << strid);
+ return {};
+ }
+
+ return stack_value{*ps};
+ }
+ case celltype_t::formula:
+ {
+ formula_result res = ca.get_formula_result();
+
+ switch (res.get_type())
+ {
+ case formula_result::result_type::boolean:
+ return stack_value{res.get_boolean() ? 1.0 : 0.0};
+ case formula_result::result_type::value:
+ return stack_value{res.get_value()};
+ case formula_result::result_type::string:
+ return stack_value{res.get_string()};
+ case formula_result::result_type::error:
+ default:
+ return {};
+ }
+ }
+ default:
+ return {};
+ }
+ break;
+ }
+ case stack_value_t::range_ref:
+ default:;
+ }
+
+ return {};
+}
+
+void compare_values(formula_value_stack& vs, fopcode_t oc, double val1, double val2)
+{
+ switch (oc)
+ {
+ case fop_plus:
+ vs.push_value(val1 + val2);
+ break;
+ case fop_minus:
+ vs.push_value(val1 - val2);
+ break;
+ case fop_equal:
+ vs.push_value(val1 == val2);
+ break;
+ case fop_not_equal:
+ vs.push_value(val1 != val2);
+ break;
+ case fop_less:
+ vs.push_value(val1 < val2);
+ break;
+ case fop_less_equal:
+ vs.push_value(val1 <= val2);
+ break;
+ case fop_greater:
+ vs.push_value(val1 > val2);
+ break;
+ case fop_greater_equal:
+ vs.push_value(val1 >= val2);
+ break;
+ default:
+ throw invalid_expression("unknown expression operator.");
+ }
+}
+
+void compare_strings(formula_value_stack& vs, fopcode_t oc, const std::string& str1, const std::string& str2)
+{
+ switch (oc)
+ {
+ case fop_plus:
+ case fop_minus:
+ throw formula_error(formula_error_t::invalid_expression);
+ case fop_equal:
+ vs.push_value(str1 == str2);
+ break;
+ case fop_not_equal:
+ vs.push_value(str1 != str2);
+ break;
+ case fop_less:
+ vs.push_value(str1 < str2);
+ break;
+ case fop_less_equal:
+ vs.push_value(str1 <= str2);
+ break;
+ case fop_greater:
+ vs.push_value(str1 > str2);
+ break;
+ case fop_greater_equal:
+ vs.push_value(str1 >= str2);
+ break;
+ default:
+ throw invalid_expression("unknown expression operator.");
+ }
+}
+
+void compare_value_to_string(
+ formula_value_stack& vs, fopcode_t oc, double /*val1*/, const std::string& /*str2*/)
+{
+ // Value 1 is numeric while value 2 is string. String is
+ // always greater than numeric value.
+ switch (oc)
+ {
+ case fop_plus:
+ case fop_minus:
+ throw formula_error(formula_error_t::invalid_expression);
+ case fop_equal:
+ vs.push_value(false);
+ break;
+ case fop_not_equal:
+ vs.push_value(true);
+ break;
+ case fop_less:
+ case fop_less_equal:
+ // val1 < str2
+ vs.push_value(true);
+ break;
+ case fop_greater:
+ case fop_greater_equal:
+ // val1 > str2
+ vs.push_value(false);
+ break;
+ default:
+ throw invalid_expression("unknown expression operator.");
+ }
+}
+
+void compare_string_to_value(
+ formula_value_stack& vs, fopcode_t oc, const std::string& /*str1*/, double /*val2*/)
+{
+ switch (oc)
+ {
+ case fop_plus:
+ case fop_minus:
+ throw formula_error(formula_error_t::invalid_expression);
+ case fop_equal:
+ vs.push_value(false);
+ break;
+ case fop_not_equal:
+ vs.push_value(true);
+ break;
+ case fop_less:
+ case fop_less_equal:
+ // str1 < val2
+ vs.push_value(false);
+ break;
+ case fop_greater:
+ case fop_greater_equal:
+ // str1 > val2
+ vs.push_value(true);
+ break;
+ default:
+ throw invalid_expression("unknown expression operator.");
+ }
+}
+
+template<typename Op>
+matrix operate_all_elements(const matrix& mtx, double val)
+{
+ matrix res = mtx;
+
+ for (std::size_t col = 0; col < mtx.col_size(); ++col)
+ {
+ for (std::size_t row = 0; row < mtx.row_size(); ++row)
+ {
+ auto elem = mtx.get(row, col);
+
+ switch (elem.type)
+ {
+ case matrix::element_type::numeric:
+ {
+ auto v = Op{}(std::get<double>(elem.value), val);
+ if (v)
+ res.set(row, col, *v);
+ else
+ res.set(row, col, v.error());
+ break;
+ }
+ case matrix::element_type::string:
+ break;
+ case matrix::element_type::boolean:
+ {
+ auto v = Op{}(std::get<bool>(elem.value), val);
+ if (v)
+ res.set(row, col, *v);
+ else
+ res.set(row, col, v.error());
+ break;
+ }
+ case matrix::element_type::error:
+ res.set(row, col, std::get<formula_error_t>(elem.value));
+ break;
+ case matrix::element_type::empty:
+ break;
+ }
+ }
+ }
+
+ return res;
+}
+
+matrix operate_all_elements(const matrix& mtx, std::string_view val)
+{
+ matrix res = mtx;
+
+ for (std::size_t col = 0; col < mtx.col_size(); ++col)
+ {
+ for (std::size_t row = 0; row < mtx.row_size(); ++row)
+ {
+ auto elem = mtx.get(row, col);
+
+ switch (elem.type)
+ {
+ case matrix::element_type::numeric:
+ {
+ std::ostringstream os;
+ os << std::get<double>(elem.value) << val;
+ res.set(row, col, os.str());
+ break;
+ }
+ case matrix::element_type::string:
+ {
+ std::ostringstream os;
+ os << std::get<std::string_view>(elem.value) << val;
+ res.set(row, col, os.str());
+ break;
+ }
+ case matrix::element_type::boolean:
+ {
+ std::ostringstream os;
+ os << std::boolalpha << std::get<bool>(elem.value) << val;
+ res.set(row, col, os.str());
+ break;
+ }
+ case matrix::element_type::error:
+ res.set(row, col, std::get<formula_error_t>(elem.value));
+ break;
+ case matrix::element_type::empty:
+ break;
+ }
+ }
+ }
+
+ return res;
+}
+
+matrix operate_all_elements(std::string_view val, const matrix& mtx)
+{
+ matrix res = mtx;
+
+ for (std::size_t col = 0; col < mtx.col_size(); ++col)
+ {
+ for (std::size_t row = 0; row < mtx.row_size(); ++row)
+ {
+ auto elem = mtx.get(row, col);
+
+ switch (elem.type)
+ {
+ case matrix::element_type::numeric:
+ {
+ std::ostringstream os;
+ os << val << std::get<double>(elem.value);
+ res.set(row, col, os.str());
+ break;
+ }
+ case matrix::element_type::string:
+ {
+ std::ostringstream os;
+ os << val << std::get<std::string_view>(elem.value);
+ res.set(row, col, os.str());
+ break;
+ }
+ case matrix::element_type::boolean:
+ {
+ std::ostringstream os;
+ os << val << std::boolalpha << std::get<bool>(elem.value);
+ res.set(row, col, os.str());
+ break;
+ }
+ case matrix::element_type::error:
+ res.set(row, col, std::get<formula_error_t>(elem.value));
+ break;
+ case matrix::element_type::empty:
+ break;
+ }
+ }
+ }
+
+ return res;
+}
+
+template<typename Op>
+matrix operate_all_elements(double val, const matrix& mtx)
+{
+ matrix res = mtx;
+
+ for (std::size_t col = 0; col < mtx.col_size(); ++col)
+ {
+ for (std::size_t row = 0; row < mtx.row_size(); ++row)
+ {
+ auto elem = mtx.get(row, col);
+
+ switch (elem.type)
+ {
+ case matrix::element_type::numeric:
+ {
+ auto v = Op{}(val, std::get<double>(elem.value));
+ if (v)
+ res.set(row, col, *v);
+ else
+ res.set(row, col, v.error());
+ break;
+ }
+ case matrix::element_type::string:
+ break;
+ case matrix::element_type::boolean:
+ {
+ auto v = Op{}(val, std::get<bool>(elem.value));
+ if (v)
+ res.set(row, col, *v);
+ else
+ res.set(row, col, v.error());
+ break;
+ }
+ case matrix::element_type::error:
+ res.set(row, col, std::get<formula_error_t>(elem.value));
+ break;
+ case matrix::element_type::empty:
+ break;
+ }
+ }
+ }
+
+ return res;
+}
+
+struct add_op
+{
+ formula_op_result<double> operator()(double v1, double v2) const
+ {
+ return v1 + v2;
+ }
+};
+
+struct sub_op
+{
+ formula_op_result<double> operator()(double v1, double v2) const
+ {
+ return v1 - v2;
+ }
+};
+
+struct equal_op
+{
+ formula_op_result<double> operator()(double v1, double v2) const
+ {
+ return v1 == v2;
+ }
+};
+
+struct not_equal_op
+{
+ formula_op_result<bool> operator()(double v1, double v2) const
+ {
+ return v1 != v2;
+ }
+};
+
+struct less_op
+{
+ formula_op_result<bool> operator()(double v1, double v2) const
+ {
+ return v1 < v2;
+ }
+};
+
+struct less_equal_op
+{
+ formula_op_result<bool> operator()(double v1, double v2) const
+ {
+ return v1 <= v2;
+ }
+};
+
+struct greater_op
+{
+ formula_op_result<bool> operator()(double v1, double v2) const
+ {
+ return v1 > v2;
+ }
+};
+
+struct greater_equal_op
+{
+ formula_op_result<bool> operator()(double v1, double v2) const
+ {
+ return v1 >= v2;
+ }
+};
+
+struct multiply_op
+{
+ formula_op_result<double> operator()(double v1, double v2) const
+ {
+ return v1 * v2;
+ }
+};
+
+struct divide_op
+{
+ formula_op_result<double> operator()(double v1, double v2) const
+ {
+ if (v2 == 0.0)
+ return formula_error_t::division_by_zero;
+
+ return v1 / v2;
+ }
+};
+
+struct exponent_op
+{
+ formula_op_result<double> operator()(double v1, double v2) const
+ {
+ return std::pow(v1, v2);
+ }
+};
+
+void compare_matrix_to_value(formula_value_stack& vs, fopcode_t oc, const matrix& mtx, double val)
+{
+ switch (oc)
+ {
+ case fop_minus:
+ val = -val;
+ // fallthrough
+ case fop_plus:
+ {
+ matrix res = operate_all_elements<add_op>(mtx, val);
+ vs.push_matrix(res);
+ break;
+ }
+ case fop_equal:
+ {
+ matrix res = operate_all_elements<equal_op>(mtx, val);
+ vs.push_matrix(res);
+ break;
+ }
+ case fop_not_equal:
+ {
+ matrix res = operate_all_elements<not_equal_op>(mtx, val);
+ vs.push_matrix(res);
+ break;
+ }
+ case fop_less:
+ {
+ matrix res = operate_all_elements<less_op>(mtx, val);
+ vs.push_matrix(res);
+ break;
+ }
+ case fop_less_equal:
+ {
+ matrix res = operate_all_elements<less_equal_op>(mtx, val);
+ vs.push_matrix(res);
+ break;
+ }
+ case fop_greater:
+ {
+ matrix res = operate_all_elements<greater_op>(mtx, val);
+ vs.push_matrix(res);
+ break;
+ }
+ case fop_greater_equal:
+ {
+ matrix res = operate_all_elements<greater_equal_op>(mtx, val);
+ vs.push_matrix(res);
+ break;
+ }
+ default:
+ throw invalid_expression("unknown expression operator.");
+ }
+}
+
+void compare_value_to_matrix(formula_value_stack& vs, fopcode_t oc, double val, const matrix& mtx)
+{
+ switch (oc)
+ {
+ case fop_minus:
+ {
+ matrix res = operate_all_elements<sub_op>(val, mtx);
+ vs.push_matrix(res);
+ break;
+ }
+ case fop_plus:
+ {
+ matrix res = operate_all_elements<add_op>(val, mtx);
+ vs.push_matrix(res);
+ break;
+ }
+ case fop_equal:
+ {
+ matrix res = operate_all_elements<equal_op>(val, mtx);
+ vs.push_matrix(res);
+ break;
+ }
+ case fop_not_equal:
+ {
+ matrix res = operate_all_elements<not_equal_op>(val, mtx);
+ vs.push_matrix(res);
+ break;
+ }
+ case fop_less:
+ {
+ matrix res = operate_all_elements<less_op>(val, mtx);
+ vs.push_matrix(res);
+ break;
+ }
+ case fop_less_equal:
+ {
+ matrix res = operate_all_elements<less_equal_op>(val, mtx);
+ vs.push_matrix(res);
+ break;
+ }
+ case fop_greater:
+ {
+ matrix res = operate_all_elements<greater_op>(val, mtx);
+ vs.push_matrix(res);
+ break;
+ }
+ case fop_greater_equal:
+ {
+ matrix res = operate_all_elements<greater_equal_op>(val, mtx);
+ vs.push_matrix(res);
+ break;
+ }
+ default:
+ throw invalid_expression("unknown expression operator.");
+ }
+}
+
+std::optional<double> elem_to_numeric(const matrix::element& e)
+{
+ switch (e.type)
+ {
+ case matrix::element_type::numeric:
+ return std::get<double>(e.value);
+ case matrix::element_type::boolean:
+ return std::get<bool>(e.value) ? 1.0 : 0.0;
+ case matrix::element_type::empty:
+ return 0.0;
+ default:;
+ }
+
+ return {};
+};
+
+template<typename Op>
+resolved_stack_value op_matrix_or_numeric(const resolved_stack_value& lhs, const resolved_stack_value& rhs)
+{
+ switch (lhs.type())
+ {
+ case resolved_stack_value::value_type::matrix:
+ {
+ switch (rhs.type())
+ {
+ case resolved_stack_value::value_type::matrix: // matrix * matrix
+ {
+ const matrix& m1 = lhs.get_matrix();
+ const matrix& m2 = rhs.get_matrix();
+
+ if (m1.row_size() != m2.row_size() || m1.col_size() != m2.col_size())
+ throw invalid_expression("matrix size mis-match");
+
+ matrix res = m1; // copy
+
+ for (std::size_t col = 0; col < res.col_size(); ++col)
+ {
+ for (std::size_t row = 0; row < res.row_size(); ++row)
+ {
+ auto elem1 = res.get(row, col);
+ auto elem2 = m2.get(row, col);
+
+ std::optional<double> v1 = elem_to_numeric(elem1);
+ std::optional<double> v2 = elem_to_numeric(elem2);
+
+ if (v1 && v2)
+ {
+ auto v = Op{}(*v1, *v2);
+ if (v)
+ res.set(row, col, *v);
+ else
+ res.set(row, col, v.error());
+ }
+ else
+ res.set(row, col, formula_error_t::invalid_value_type);
+ }
+ }
+ return res;
+ }
+ case resolved_stack_value::value_type::numeric: // matrix * value
+ return operate_all_elements<Op>(lhs.get_matrix(), rhs.get_numeric());
+ case resolved_stack_value::value_type::string:
+ throw invalid_expression("unexpected string value");
+ }
+ break;
+ }
+ case resolved_stack_value::value_type::numeric:
+ {
+ switch (rhs.type())
+ {
+ case resolved_stack_value::value_type::matrix: // value * matrix
+ return operate_all_elements<Op>(lhs.get_numeric(), rhs.get_matrix());
+ case resolved_stack_value::value_type::numeric: // value * value
+ {
+ auto v = Op{}(lhs.get_numeric(), rhs.get_numeric());
+ if (!v)
+ throw formula_error(v.error());
+
+ return *v;
+ }
+ case resolved_stack_value::value_type::string:
+ throw invalid_expression("unexpected string value");
+ }
+ break;
+ }
+ case resolved_stack_value::value_type::string:
+ throw invalid_expression("unexpected string value");
+ }
+
+ std::ostringstream os;
+ os << "unhandled variant type: lhs=" << int(lhs.type()) << "; rhs=" << int(rhs.type());
+ throw invalid_expression(os.str());
+}
+
+std::ostream& operator<<(std::ostream& os, const matrix::element& e)
+{
+ switch (e.type)
+ {
+ case matrix::element_type::numeric:
+ os << std::get<double>(e.value);
+ break;
+ case matrix::element_type::string:
+ os << std::get<std::string_view>(e.value);
+ break;
+ case matrix::element_type::boolean:
+ os << std::boolalpha << std::get<bool>(e.value);
+ break;
+ case matrix::element_type::error:
+ case matrix::element_type::empty:
+ break;
+ }
+ return os;
+}
+
+resolved_stack_value concat_matrix_or_string(const resolved_stack_value& lhs, const resolved_stack_value& rhs)
+{
+ switch (lhs.type())
+ {
+ case resolved_stack_value::value_type::matrix:
+ {
+ switch (rhs.type())
+ {
+ case resolved_stack_value::value_type::matrix: // matrix & matrix
+ {
+ const matrix& m1 = lhs.get_matrix();
+ const matrix& m2 = rhs.get_matrix();
+
+ if (m1.row_size() != m2.row_size() || m1.col_size() != m2.col_size())
+ throw invalid_expression("matrix size mis-match");
+
+ matrix res = m1; // copy
+
+ for (std::size_t col = 0; col < res.col_size(); ++col)
+ {
+ for (std::size_t row = 0; row < res.row_size(); ++row)
+ {
+ auto elem1 = res.get(row, col);
+ auto elem2 = m2.get(row, col);
+
+ std::ostringstream os;
+ os << elem1 << elem2;
+
+ res.set(row, col, os.str());
+ }
+ }
+ return res;
+ }
+ case resolved_stack_value::value_type::string: // matrix & string
+ return operate_all_elements(lhs.get_matrix(), rhs.get_string());
+ case resolved_stack_value::value_type::numeric: // matrix & string
+ throw invalid_expression("unexpected numeric value");
+ }
+ break;
+ }
+ case resolved_stack_value::value_type::string:
+ {
+ switch (rhs.type())
+ {
+ case resolved_stack_value::value_type::matrix: // string & matrix
+ return operate_all_elements(lhs.get_string(), rhs.get_matrix());
+ case resolved_stack_value::value_type::string: // string & string
+ return lhs.get_string() + rhs.get_string();
+ case resolved_stack_value::value_type::numeric:
+ throw invalid_expression("unexpected numeric value");
+ }
+ break;
+ }
+ case resolved_stack_value::value_type::numeric:
+ throw invalid_expression("unexpected numeric value");
+ }
+
+ std::ostringstream os;
+ os << "unhandled variant type: lhs=" << int(lhs.type()) << "; rhs=" << int(rhs.type());
+ throw invalid_expression(os.str());
+}
+
+} // anonymous namespace
+
+void formula_interpreter::expression()
+{
+ // <term> + <term> + <term> + ... + <term>
+ // valid operators are: +, -, =, <, >, <=, >=, <>.
+
+ term();
+ while (has_token())
+ {
+ fopcode_t oc = token().opcode;
+ if (!valid_expression_op(oc))
+ return;
+
+ auto sv1 = pop_stack_value(m_context, get_stack());
+ if (!sv1)
+ {
+ IXION_DEBUG("failed to pop value from the stack");
+ throw formula_error(formula_error_t::general_error);
+ }
+
+ if (mp_handler)
+ mp_handler->push_token(oc);
+
+ next();
+ term();
+
+ auto sv2 = pop_stack_value(m_context, get_stack());
+ if (!sv2)
+ {
+ IXION_DEBUG("failed to pop value from the stack");
+ throw formula_error(formula_error_t::general_error);
+ }
+
+ switch (sv1->get_type())
+ {
+ case stack_value_t::value:
+ {
+ switch (sv2->get_type())
+ {
+ case stack_value_t::value:
+ {
+ // Both are numeric values.
+ compare_values(get_stack(), oc, sv1->get_value(), sv2->get_value());
+ break;
+ }
+ case stack_value_t::string:
+ {
+ compare_value_to_string(get_stack(), oc, sv1->get_value(), sv2->get_string());
+ break;
+ }
+ case stack_value_t::matrix:
+ {
+ compare_value_to_matrix(get_stack(), oc, sv1->get_value(), sv2->get_matrix());
+ break;
+ }
+ default:
+ {
+ IXION_DEBUG("unsupported value type for value 2: " << sv2->get_type());
+ throw formula_error(formula_error_t::general_error);
+ }
+ }
+ break;
+ }
+ case stack_value_t::string:
+ {
+ switch (sv2->get_type())
+ {
+ case stack_value_t::value:
+ {
+ // Value 1 is string while value 2 is numeric.
+ compare_string_to_value(get_stack(), oc, sv1->get_string(), sv2->get_value());
+ break;
+ }
+ case stack_value_t::string:
+ {
+ // Both are strings.
+ compare_strings(get_stack(), oc, sv1->get_string(), sv2->get_string());
+ break;
+ }
+ default:
+ {
+ IXION_DEBUG("unsupported value type for value 2: " << sv2->get_type());
+ throw formula_error(formula_error_t::general_error);
+ }
+ }
+ break;
+ }
+ case stack_value_t::matrix:
+ {
+ switch (sv2->get_type())
+ {
+ case stack_value_t::value:
+ {
+ compare_matrix_to_value(get_stack(), oc, sv1->get_matrix(), sv2->get_value());
+ break;
+ }
+ default:
+ {
+ IXION_DEBUG("unsupported value type for value 2: " << sv2->get_type());
+ throw formula_error(formula_error_t::general_error);
+ }
+ }
+ break;
+ }
+ default:
+ {
+ IXION_DEBUG("unsupported value type for value 1: " << sv1->get_type());
+ throw formula_error(formula_error_t::general_error);
+ }
+ }
+ }
+}
+
+void formula_interpreter::term()
+{
+ // <factor> || <factor> (*|^|&|/) <term>
+
+ factor();
+ if (!has_token())
+ return;
+
+ fopcode_t oc = token().opcode;
+
+ auto pop_matrix_or_numeric_values = [this]()
+ {
+ auto v1 = get_stack().pop_matrix_or_numeric();
+ next(); // skip the op token
+ term();
+ auto v2 = get_stack().pop_matrix_or_numeric();
+ return std::make_pair(std::move(v1), std::move(v2));
+ };
+
+ auto push_to_stack = [this](const resolved_stack_value& v)
+ {
+ switch (v.type())
+ {
+ case resolved_stack_value::value_type::matrix:
+ get_stack().push_matrix(v.get_matrix());
+ break;
+ case resolved_stack_value::value_type::numeric:
+ get_stack().push_value(v.get_numeric());
+ break;
+ case resolved_stack_value::value_type::string:
+ get_stack().push_string(v.get_string());
+ break;
+ default:
+ throw invalid_expression("result must be either matrix or double");
+ }
+ };
+
+ switch (oc)
+ {
+ case fop_multiply:
+ {
+ if (mp_handler)
+ mp_handler->push_token(oc);
+
+ const auto& [lhs, rhs] = pop_matrix_or_numeric_values();
+ auto res = op_matrix_or_numeric<multiply_op>(lhs, rhs);
+ push_to_stack(res);
+ return;
+ }
+ case fop_exponent:
+ {
+ if (mp_handler)
+ mp_handler->push_token(oc);
+
+ const auto& [base, exp] = pop_matrix_or_numeric_values();
+ auto res = op_matrix_or_numeric<exponent_op>(base, exp);
+ push_to_stack(res);
+ return;
+ }
+ case fop_concat:
+ {
+ if (mp_handler)
+ mp_handler->push_token(oc);
+
+ auto lhs = get_stack().pop_matrix_or_string();
+ next();
+ term();
+ auto rhs = get_stack().pop_matrix_or_string();
+ auto res = concat_matrix_or_string(lhs, rhs);
+ push_to_stack(res);
+ return;
+ }
+ case fop_divide:
+ {
+ if (mp_handler)
+ mp_handler->push_token(oc);
+
+ const auto& [lhs, rhs] = pop_matrix_or_numeric_values();
+ auto res = op_matrix_or_numeric<divide_op>(lhs, rhs);
+ push_to_stack(res);
+ return;
+ }
+ default:
+ ;
+ }
+}
+
+void formula_interpreter::factor()
+{
+ // <constant> || <variable> || '(' <expression> ')' || <function>
+
+ bool negative_sign = sign(); // NB: may be precedeed by a '+' or '-' sign.
+ fopcode_t oc = token().opcode;
+
+ switch (oc)
+ {
+ case fop_open:
+ paren();
+ break;
+ case fop_named_expression:
+ {
+ // All named expressions are supposed to be expanded prior to interpretation.
+ IXION_DEBUG("named expression encountered in factor.");
+ throw formula_error(formula_error_t::general_error);
+ }
+ case fop_value:
+ {
+ constant();
+ break;
+ }
+ case fop_single_ref:
+ single_ref();
+ break;
+ case fop_range_ref:
+ range_ref();
+ break;
+ case fop_table_ref:
+ table_ref();
+ break;
+ case fop_function:
+ function();
+ break;
+ case fop_string:
+ literal();
+ break;
+ case fop_array_open:
+ array();
+ break;
+ default:
+ {
+ std::ostringstream os;
+ os << "factor: unexpected token type: <" << get_opcode_name(oc) << ">";
+ throw invalid_expression(os.str());
+ }
+ }
+
+ if (negative_sign)
+ {
+ double v = get_stack().pop_value();
+ get_stack().push_value(v * -1.0);
+ }
+}
+
+bool formula_interpreter::sign()
+{
+ ensure_token_exists();
+
+ fopcode_t oc = token().opcode;
+ bool sign_set = false;
+
+ switch (oc)
+ {
+ case fop_minus:
+ sign_set = true;
+ // fall through
+ case fop_plus:
+ {
+ if (mp_handler)
+ mp_handler->push_token(oc);
+
+ next();
+
+ if (!has_token())
+ throw invalid_expression("sign: a sign cannot be the last token");
+ }
+ default:
+ ;
+ }
+
+ return sign_set;
+}
+
+void formula_interpreter::paren()
+{
+ if (mp_handler)
+ mp_handler->push_token(fop_open);
+
+ next();
+ expression();
+ if (token_or_throw().opcode != fop_close)
+ throw invalid_expression("paren: expected close paren");
+
+ if (mp_handler)
+ mp_handler->push_token(fop_close);
+
+ next();
+}
+
+void formula_interpreter::single_ref()
+{
+ const address_t& addr = std::get<address_t>(token().value);
+ IXION_TRACE("ref=" << addr.get_name() << "; origin=" << m_pos.get_name());
+
+ if (mp_handler)
+ mp_handler->push_single_ref(addr, m_pos);
+
+ abs_address_t abs_addr = addr.to_abs(m_pos);
+ IXION_TRACE("ref=" << abs_addr.get_name() << " (converted to absolute)");
+
+ if (abs_addr == m_pos)
+ {
+ // self-referencing is not permitted.
+ throw formula_error(formula_error_t::ref_result_not_available);
+ }
+
+ get_stack().push_single_ref(abs_addr);
+ next();
+}
+
+void formula_interpreter::range_ref()
+{
+ const range_t& range = std::get<range_t>(token().value);
+ IXION_TRACE("ref-start=" << range.first.get_name() << "; ref-end=" << range.last.get_name() << "; origin=" << m_pos.get_name());
+
+ if (mp_handler)
+ mp_handler->push_range_ref(range, m_pos);
+
+ abs_range_t abs_range = range.to_abs(m_pos);
+ abs_range.reorder();
+
+ IXION_TRACE("ref-start=" << abs_range.first.get_name() << "; ref-end=" << abs_range.last.get_name() << " (converted to absolute)");
+
+ // Check the reference range to make sure it doesn't include the parent cell.
+ if (abs_range.contains(m_pos))
+ {
+ // Referenced range contains the address of this cell. Not good.
+ throw formula_error(formula_error_t::ref_result_not_available);
+ }
+
+ get_stack().push_range_ref(abs_range);
+ next();
+}
+
+void formula_interpreter::table_ref()
+{
+ const iface::table_handler* table_hdl = m_context.get_table_handler();
+ if (!table_hdl)
+ {
+ IXION_DEBUG("failed to get a table_handler instance.");
+ throw formula_error(formula_error_t::ref_result_not_available);
+ }
+
+ const table_t& table = std::get<table_t>(token().value);
+
+ if (mp_handler)
+ mp_handler->push_table_ref(table);
+
+ abs_range_t range(abs_range_t::invalid);
+ if (table.name != empty_string_id)
+ {
+ range = table_hdl->get_range(table.name, table.column_first, table.column_last, table.areas);
+ }
+ else
+ {
+ // Table name is not given. Use the current cell position to infer
+ // which table to use.
+ range = table_hdl->get_range(m_pos, table.column_first, table.column_last, table.areas);
+ }
+
+ get_stack().push_range_ref(range);
+ next();
+}
+
+void formula_interpreter::constant()
+{
+ double val = std::get<double>(token().value);
+ next();
+ get_stack().push_value(val);
+ if (mp_handler)
+ mp_handler->push_value(val);
+}
+
+void formula_interpreter::literal()
+{
+ const std::string& s = string_or_throw();
+
+ next();
+ get_stack().push_string(s);
+}
+
+void formula_interpreter::array()
+{
+ // '{' <constant> or <literal> ',' or ';' <constant> or <literal> ',' or ';' .... '}'
+ assert(token().opcode == fop_array_open);
+
+ if (mp_handler)
+ mp_handler->push_token(fop_array_open);
+
+ next(); // skip '{'
+
+ std::vector<double> values;
+ std::vector<std::tuple<std::size_t, std::size_t, std::string>> strings;
+ std::size_t row = 0;
+ std::size_t col = 0;
+ std::optional<std::size_t> prev_col;
+
+ fopcode_t prev_op = fop_array_open;
+
+ for (; has_token(); next())
+ {
+ bool has_sign = false;
+
+ switch (prev_op)
+ {
+ case fop_array_open:
+ case fop_sep:
+ case fop_array_row_sep:
+ has_sign = sign();
+ break;
+ default:;
+ }
+
+ switch (token().opcode)
+ {
+ case fop_string:
+ {
+ switch (prev_op)
+ {
+ case fop_array_open:
+ case fop_sep:
+ case fop_array_row_sep:
+ break;
+ default:
+ throw invalid_expression("array: invalid placement of value");
+ }
+
+ strings.emplace_back(row, col, string_or_throw());
+ values.push_back(0); // placeholder value, will be replaced
+
+ ++col;
+ break;
+ }
+ case fop_value:
+ {
+ switch (prev_op)
+ {
+ case fop_minus:
+ case fop_plus:
+ case fop_array_open:
+ case fop_sep:
+ case fop_array_row_sep:
+ break;
+ default:
+ throw invalid_expression("array: invalid placement of value");
+ }
+
+ double v = std::get<double>(token().value);
+
+ if (mp_handler)
+ mp_handler->push_value(v);
+
+ if (has_sign)
+ v = -v;
+
+ values.push_back(v);
+
+ ++col;
+ break;
+ }
+ case fop_sep:
+ {
+ switch (prev_op)
+ {
+ case fop_value:
+ case fop_string:
+ break;
+ default:
+ throw invalid_expression("array: unexpected separator");
+ }
+
+ if (mp_handler)
+ mp_handler->push_token(fop_sep);
+ break;
+ }
+ case fop_array_row_sep:
+ {
+ switch (prev_op)
+ {
+ case fop_value:
+ case fop_string:
+ break;
+ default:
+ throw invalid_expression("array: unexpected row separator");
+ }
+
+ if (mp_handler)
+ mp_handler->push_token(fop_array_row_sep);
+
+ ++row;
+
+ if (prev_col && *prev_col != col)
+ throw invalid_expression("array: inconsistent column width");
+
+ prev_col = col;
+ col = 0;
+ break;
+ }
+ case fop_array_close:
+ {
+ switch (prev_op)
+ {
+ case fop_array_open:
+ case fop_value:
+ case fop_string:
+ break;
+ default:
+ throw invalid_expression("array: invalid placement of array close operator");
+ }
+
+ if (prev_col && *prev_col != col)
+ throw invalid_expression("array: inconsistent column width");
+
+ ++row;
+
+ // Stored values are in row-major order, but the matrix expects a column-major array.
+ numeric_matrix num_mtx_transposed(std::move(values), col, row);
+ numeric_matrix num_mtx(row, col);
+
+ for (std::size_t r = 0; r < row; ++r)
+ for (std::size_t c = 0; c < col; ++c)
+ num_mtx(r, c) = num_mtx_transposed(c, r);
+
+ if (strings.empty())
+ // pure numeric matrix
+ get_stack().push_matrix(std::move(num_mtx));
+ else
+ {
+ // multi-type matrix
+ matrix mtx(num_mtx);
+ for (const auto& [r, c, str] : strings)
+ mtx.set(r, c, str);
+
+ get_stack().push_matrix(std::move(mtx));
+ }
+
+ if (mp_handler)
+ mp_handler->push_token(fop_array_close);
+
+ next(); // skip '}'
+ return;
+ }
+ default:
+ {
+ std::ostringstream os;
+ os << "array: unexpected token type: <" << get_opcode_name(token().opcode) << ">";
+ throw invalid_expression(os.str());
+ }
+ }
+
+ prev_op = token().opcode;
+ }
+
+ throw invalid_expression("array: ended prematurely");
+}
+
+void formula_interpreter::function()
+{
+ // <func name> '(' <expression> ',' <expression> ',' ... ',' <expression> ')'
+ ensure_token_exists();
+ assert(token().opcode == fop_function);
+ formula_function_t func_oc = formula_functions::get_function_opcode(token());
+ if (mp_handler)
+ mp_handler->push_function(func_oc);
+
+ push_stack();
+
+ IXION_TRACE("function='" << get_formula_function_name(func_oc) << "'");
+ assert(get_stack().empty());
+
+ if (next_token().opcode != fop_open)
+ throw invalid_expression("expecting a '(' after a function name.");
+
+ if (mp_handler)
+ mp_handler->push_token(fop_open);
+
+ fopcode_t oc = next_token().opcode;
+ bool expect_sep = false;
+ while (oc != fop_close)
+ {
+ if (expect_sep)
+ {
+ if (oc != fop_sep)
+ throw invalid_expression("argument separator is expected, but not found.");
+ next();
+ expect_sep = false;
+
+ if (mp_handler)
+ mp_handler->push_token(oc);
+ }
+ else
+ {
+ expression();
+ expect_sep = true;
+ }
+ oc = token_or_throw().opcode;
+ }
+
+ if (mp_handler)
+ mp_handler->push_token(oc);
+
+ next();
+
+ // Function call pops all stack values pushed onto the stack this far, and
+ // pushes the result onto the stack.
+ formula_functions(m_context, m_pos).interpret(func_oc, get_stack());
+ assert(get_stack().size() == 1);
+
+ pop_stack();
+}
+
+void formula_interpreter::clear_stacks()
+{
+ m_stacks.clear();
+ m_stacks.emplace_back(m_context);
+}
+
+void formula_interpreter::push_stack()
+{
+ m_stacks.emplace_back(m_context);
+}
+
+void formula_interpreter::pop_stack()
+{
+ assert(m_stacks.size() >= 2);
+ assert(m_stacks.back().size() == 1);
+ auto tmp = m_stacks.back().release_back();
+ m_stacks.pop_back();
+ m_stacks.back().push_back(std::move(tmp));
+}
+
+formula_value_stack& formula_interpreter::get_stack()
+{
+ assert(!m_stacks.empty());
+ return m_stacks.back();
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */