summaryrefslogtreecommitdiffstats
path: root/src/model_parser.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/model_parser.cpp')
-rw-r--r--src/model_parser.cpp1203
1 files changed, 1203 insertions, 0 deletions
diff --git a/src/model_parser.cpp b/src/model_parser.cpp
new file mode 100644
index 0000000..fcabbcf
--- /dev/null
+++ b/src/model_parser.cpp
@@ -0,0 +1,1203 @@
+/* -*- 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 "model_parser.hpp"
+#include "app_common.hpp"
+
+#include "ixion/formula.hpp"
+#include "ixion/formula_name_resolver.hpp"
+#include "ixion/formula_result.hpp"
+#include "ixion/macros.hpp"
+#include "ixion/address_iterator.hpp"
+#include "ixion/dirty_cell_tracker.hpp"
+#include "ixion/cell_access.hpp"
+#include "ixion/config.hpp"
+#include "ixion/cell.hpp"
+
+#include <sstream>
+#include <iostream>
+#include <vector>
+#include <functional>
+#include <cstring>
+#include <cassert>
+#include <memory>
+
+#include <mdds/sorted_string_map.hpp>
+
+using namespace std;
+
+#define DEBUG_MODEL_PARSER 0
+
+namespace ixion {
+
+namespace {
+
+long to_long(std::string_view value)
+{
+ char* pe = nullptr;
+ long ret = std::strtol(value.data(), &pe, 10);
+
+ if (value.data() == pe)
+ {
+ std::ostringstream os;
+ os << "'" << value << "' is not a valid integer.";
+ throw model_parser::parse_error(os.str());
+ }
+
+ return ret;
+}
+
+bool is_separator(char c)
+{
+ switch (c)
+ {
+ case '=':
+ case ':':
+ case '@':
+ return true;
+ default:
+ ;
+ }
+
+ return false;
+}
+
+std::string_view parse_command_to_buffer(const char*& p, const char* p_end)
+{
+ ++p; // skip '%'.
+ std::size_t n = 1;
+
+ if (*p == '%')
+ {
+ // This line is a comment. Skip the rest of the line.
+ std::string_view ret{p, n}; // return it as command named '%'
+ while (p != p_end && *p != '\n') ++p;
+ return ret;
+ }
+
+ auto* p_head = p;
+ for (++p; p != p_end && *p != '\n'; ++p)
+ ++n;
+
+ return std::string_view{p_head, n};
+}
+
+class string_printer
+{
+ const model_context& m_cxt;
+ char m_sep;
+ bool m_first;
+
+public:
+ string_printer(const model_context& cxt, char sep) :
+ m_cxt(cxt), m_sep(sep), m_first(true) {}
+
+ void operator() (string_id_t sid)
+ {
+ if (m_first)
+ m_first = false;
+ else
+ cout << m_sep;
+
+ const std::string* p = m_cxt.get_string(sid);
+ if (p)
+ cout << *p;
+ }
+};
+
+void print_section_title(const char* title)
+{
+ std::cout << detail::get_formula_result_output_separator() << std::endl << title << std::endl;
+}
+
+namespace commands {
+
+enum class type
+{
+ unknown,
+ comment,
+ calc,
+ recalc,
+ check,
+ exit,
+ push,
+ mode_init,
+ mode_edit,
+ mode_result,
+ mode_result_cache,
+ mode_table,
+ mode_session,
+ mode_named_expression,
+ print_dependency,
+};
+
+typedef mdds::sorted_string_map<type> map_type;
+
+// Keys must be sorted.
+const std::vector<map_type::entry> entries =
+{
+ { IXION_ASCII("%"), type::comment },
+ { IXION_ASCII("calc"), type::calc },
+ { IXION_ASCII("check"), type::check },
+ { IXION_ASCII("exit"), type::exit },
+ { IXION_ASCII("mode edit"), type::mode_edit },
+ { IXION_ASCII("mode init"), type::mode_init },
+ { IXION_ASCII("mode named-expression"), type::mode_named_expression },
+ { IXION_ASCII("mode result"), type::mode_result },
+ { IXION_ASCII("mode result-cache"), type::mode_result_cache },
+ { IXION_ASCII("mode session"), type::mode_session },
+ { IXION_ASCII("mode table"), type::mode_table },
+ { IXION_ASCII("print dependency"), type::print_dependency },
+ { IXION_ASCII("push"), type::push },
+ { IXION_ASCII("recalc"), type::recalc },
+};
+
+const map_type& get()
+{
+ static map_type mt(entries.data(), entries.size(), type::unknown);
+ return mt;
+}
+
+} // namespace commands
+
+} // anonymous namespace
+
+model_parser::parse_error::parse_error(const string& msg) : general_error()
+{
+ ostringstream os;
+ os << "parse error: " << msg;
+ set_message(os.str());
+}
+
+// ============================================================================
+
+model_parser::check_error::check_error(const string& msg) :
+ general_error(msg) {}
+
+// ============================================================================
+
+model_parser::model_parser(const string& filepath, size_t thread_count) :
+ m_context({1048576, 1024}),
+ m_table_handler(),
+ m_session_handler_factory(m_context),
+ mp_table_entry(nullptr),
+ mp_name_resolver(formula_name_resolver::get(formula_name_resolver_t::excel_a1, &m_context)),
+ m_filepath(filepath),
+ m_strm(detail::load_file_content(m_filepath)),
+ m_thread_count(thread_count),
+ mp_head(nullptr),
+ mp_end(nullptr),
+ mp_char(nullptr),
+ m_current_sheet(0),
+ m_parse_mode(parse_mode_unknown),
+ m_print_separator(false),
+ m_print_sheet_name(false)
+{
+ m_context.set_session_handler_factory(&m_session_handler_factory);
+ m_context.set_table_handler(&m_table_handler);
+
+ mp_head = m_strm.data();
+ mp_end = mp_head + m_strm.size();
+}
+
+model_parser::~model_parser() {}
+
+void model_parser::parse()
+{
+ mp_char = mp_head;
+ m_parse_mode = parse_mode_unknown;
+
+ for (; mp_char != mp_end; ++mp_char)
+ {
+ // In each iteration, the p always points to the 1st character of a
+ // line.
+ if (*mp_char== '%')
+ {
+ parse_command();
+ if (m_parse_mode == parse_mode_exit)
+ return;
+ continue;
+ }
+
+ if (m_print_separator)
+ {
+ m_print_separator = false;
+ cout << detail::get_formula_result_output_separator() << endl;
+ }
+
+ switch (m_parse_mode)
+ {
+ case parse_mode_init:
+ parse_init();
+ break;
+ case parse_mode_edit:
+ parse_edit();
+ break;
+ case parse_mode_result:
+ parse_result();
+ break;
+ case parse_mode_result_cache:
+ parse_result_cache();
+ break;
+ case parse_mode_table:
+ parse_table();
+ break;
+ case parse_mode_session:
+ parse_session();
+ break;
+ case parse_mode_named_expression:
+ parse_named_expression();
+ break;
+ default:
+ throw parse_error("unknown parse mode");
+ }
+ }
+}
+
+void model_parser::init_model()
+{
+ if (m_context.empty())
+ m_context.append_sheet("sheet");
+}
+
+void model_parser::parse_command()
+{
+ // This line contains a command.
+ std::string_view buf_cmd = parse_command_to_buffer(mp_char, mp_end);
+ commands::type cmd = commands::get().find(buf_cmd.data(), buf_cmd.size());
+
+ switch (cmd)
+ {
+ case commands::type::comment:
+ // This is a comment line. Just ignore it.
+ break;
+ case commands::type::calc:
+ {
+ print_section_title("calculating");
+
+ // Perform full calculation on all currently stored formula cells.
+
+ for (const abs_range_t& pos : m_dirty_formula_cells)
+ register_formula_cell(m_context, pos.first);
+
+ abs_range_set_t empty;
+ std::vector<abs_range_t> sorted_cells =
+ query_and_sort_dirty_cells(m_context, empty, &m_dirty_formula_cells);
+ calculate_sorted_cells(m_context, sorted_cells, m_thread_count);
+ break;
+ }
+ case commands::type::recalc:
+ {
+ print_section_title("recalculating");
+
+ // Perform partial recalculation only on those formula cells that
+ // need recalculation.
+
+ std::vector<abs_range_t> sorted_cells =
+ query_and_sort_dirty_cells(m_context, m_modified_cells, &m_dirty_formula_cells);
+
+ calculate_sorted_cells(m_context, sorted_cells, m_thread_count);
+ break;
+ }
+ case commands::type::check:
+ {
+ // Check cell results.
+ check();
+ break;
+ }
+ case commands::type::exit:
+ {
+ // Exit the loop.
+ m_parse_mode = parse_mode_exit;
+ return;
+ }
+ case commands::type::push:
+ {
+ switch (m_parse_mode)
+ {
+ case parse_mode_table:
+ push_table();
+ break;
+ case parse_mode_named_expression:
+ push_named_expression();
+ break;
+ default:
+ throw parse_error("push command was used for wrong mode!");
+ }
+ break;
+ }
+ case commands::type::mode_init:
+ {
+ print_section_title("initializing");
+
+ m_parse_mode = parse_mode_init;
+ m_print_separator = true;
+ break;
+ }
+ case commands::type::mode_result:
+ {
+ // Clear any previous result values.
+ m_formula_results.clear();
+ m_parse_mode = parse_mode_result;
+ break;
+ }
+ case commands::type::mode_result_cache:
+ {
+ print_section_title("caching formula results");
+
+ m_parse_mode = parse_mode_result_cache;
+ m_print_separator = true;
+ break;
+ }
+ case commands::type::mode_edit:
+ {
+ print_section_title("editing");
+
+ m_parse_mode = parse_mode_edit;
+ m_dirty_formula_cells.clear();
+ m_modified_cells.clear();
+ m_print_separator = true;
+ break;
+ }
+ case commands::type::mode_table:
+ {
+ m_parse_mode = parse_mode_table;
+ mp_table_entry.reset(new table_handler::entry);
+ break;
+ }
+ case commands::type::mode_session:
+ {
+ print_section_title("session");
+
+ m_print_separator = true;
+ m_parse_mode = parse_mode_session;
+ break;
+ }
+ case commands::type::mode_named_expression:
+ {
+ m_print_separator = true;
+ m_parse_mode = parse_mode_named_expression;
+ mp_named_expression = std::make_unique<named_expression_type>();
+ break;
+ }
+ case commands::type::print_dependency:
+ {
+ print_section_title("print dependency");
+ print_dependency();
+ break;
+ }
+ case commands::type::unknown:
+ {
+ ostringstream os;
+ os << "unknown command: " << buf_cmd << endl;
+ throw parse_error(os.str());
+ }
+ default:
+ ;
+ }
+}
+
+void model_parser::parse_session()
+{
+ std::string_view cmd, value;
+ std::string_view* buf = &cmd;
+
+ for (; mp_char != mp_end && *mp_char != '\n'; ++mp_char)
+ {
+ if (*mp_char == ':')
+ {
+ if (buf == &value)
+ throw parse_error("2nd ':' character is illegal.");
+
+ buf = &value;
+ continue;
+ }
+
+ if (buf->empty())
+ *buf = std::string_view{mp_char, 1u};
+ else
+ *buf = std::string_view{buf->data(), buf->size() + 1u};
+ }
+
+ if (cmd == "row-limit")
+ {
+ rc_size_t ss = m_context.get_sheet_size();
+ ss.row = to_long(value);
+ m_context.set_sheet_size(ss);
+ }
+ else if (cmd == "column-limit")
+ {
+ rc_size_t ss = m_context.get_sheet_size();
+ ss.column = to_long(value);
+ m_context.set_sheet_size(ss);
+ }
+ else if (cmd == "insert-sheet")
+ {
+ m_context.append_sheet(std::string{value});
+ cout << "sheet: (name: " << value << ")" << endl;
+ }
+ else if (cmd == "current-sheet")
+ {
+ m_current_sheet = m_context.get_sheet_index(value);
+
+ if (m_current_sheet == invalid_sheet)
+ {
+ ostringstream os;
+ os << "No sheet named '" << value << "' found.";
+ throw parse_error(os.str());
+ }
+
+ cout << "current sheet: " << value << endl;
+ }
+ else if (cmd == "display-sheet-name")
+ {
+ cout << "display sheet name: " << value << endl;
+ m_print_sheet_name = to_bool(value);
+ m_session_handler_factory.show_sheet_name(m_print_sheet_name);
+ }
+}
+
+void model_parser::parse_init()
+{
+ init_model();
+
+ cell_def_type cell_def = parse_cell_definition();
+ if (cell_def.name.empty() && cell_def.value.empty())
+ return;
+
+ if (cell_def.matrix_value)
+ {
+ assert(cell_def.type == ct_formula);
+ const abs_address_t& pos = cell_def.pos.first;
+
+ formula_tokens_t tokens =
+ parse_formula_string(m_context, pos, *mp_name_resolver, cell_def.value);
+
+ m_context.set_grouped_formula_cells(cell_def.pos, std::move(tokens));
+ m_dirty_formula_cells.insert(cell_def.pos);
+
+ std::cout << "{" << get_display_range_string(cell_def.pos) << "}: (m) " << cell_def.value << std::endl;
+ return;
+ }
+
+ abs_address_iterator iter(cell_def.pos, rc_direction_t::vertical);
+
+ for (const abs_address_t& pos : iter)
+ {
+ m_modified_cells.insert(pos);
+
+ switch (cell_def.type)
+ {
+ case ct_formula:
+ {
+ formula_tokens_t tokens =
+ parse_formula_string(m_context, pos, *mp_name_resolver, cell_def.value);
+
+ auto ts = formula_tokens_store::create();
+ ts->get() = std::move(tokens);
+ m_context.set_formula_cell(pos, ts);
+ m_dirty_formula_cells.insert(pos);
+
+ std::cout << get_display_cell_string(pos) << ": (f) " << cell_def.value << std::endl;
+ break;
+ }
+ case ct_string:
+ {
+ m_context.set_string_cell(pos, cell_def.value);
+
+ std::cout << get_display_cell_string(pos) << ": (s) " << cell_def.value << std::endl;
+ break;
+ }
+ case ct_value:
+ {
+ double v = to_double(cell_def.value);
+ m_context.set_numeric_cell(pos, v);
+
+ std::cout << get_display_cell_string(pos) << ": (n) " << v << std::endl;
+ break;
+ }
+ case ct_boolean:
+ {
+ bool b = to_bool(cell_def.value);
+ m_context.set_boolean_cell(pos, b);
+
+ std::cout << get_display_cell_string(pos) << ": (b) " << (b ? "true" : "false") << std::endl;
+ break;
+ }
+ default:
+ throw model_parser::parse_error("unknown content type");
+ }
+ }
+}
+
+void model_parser::parse_edit()
+{
+ cell_def_type cell_def = parse_cell_definition();
+ if (cell_def.name.empty() && cell_def.value.empty())
+ return;
+
+ if (cell_def.matrix_value)
+ {
+ assert(cell_def.type == ct_formula);
+ const abs_address_t& pos = cell_def.pos.first;
+
+ m_modified_cells.insert(pos);
+ unregister_formula_cell(m_context, pos);
+
+ formula_tokens_t tokens =
+ parse_formula_string(m_context, pos, *mp_name_resolver, cell_def.value);
+
+ m_context.set_grouped_formula_cells(cell_def.pos, std::move(tokens));
+ m_dirty_formula_cells.insert(cell_def.pos);
+ register_formula_cell(m_context, pos);
+ return;
+ }
+
+ abs_address_iterator iter(cell_def.pos, rc_direction_t::vertical);
+
+ for (const abs_address_t& pos : iter)
+ {
+ m_modified_cells.insert(pos);
+ unregister_formula_cell(m_context, pos);
+
+ if (cell_def.value.empty())
+ {
+ // A valid name is given but with empty definition. Just remove the
+ // existing cell.
+ m_context.empty_cell(pos);
+ continue;
+ }
+
+ switch (cell_def.type)
+ {
+ case ct_formula:
+ {
+ formula_tokens_t tokens =
+ parse_formula_string(m_context, pos, *mp_name_resolver, cell_def.value);
+
+ auto ts = formula_tokens_store::create();
+ ts->get() = std::move(tokens);
+ m_context.set_formula_cell(pos, ts);
+ m_dirty_formula_cells.insert(pos);
+ register_formula_cell(m_context, pos);
+ std::cout << get_display_cell_string(pos) << ": (f) " << cell_def.value << std::endl;
+ break;
+ }
+ case ct_string:
+ {
+ m_context.set_string_cell(pos, cell_def.value);
+ std::cout << get_display_cell_string(pos) << ": (s) " << cell_def.value << std::endl;
+ break;
+ }
+ case ct_value:
+ {
+ double v = to_double(cell_def.value);
+ m_context.set_numeric_cell(pos, v);
+ std::cout << get_display_cell_string(pos) << ": (n) " << v << std::endl;
+ break;
+ }
+ default:
+ throw model_parser::parse_error("unknown content type");
+ }
+ }
+}
+
+void model_parser::parse_result()
+{
+ parsed_assignment_type res = parse_assignment();
+
+ auto name_s = std::string{res.first};
+
+ formula_result fres;
+ fres.parse(res.second);
+ model_parser::results_type::iterator itr = m_formula_results.find(name_s);
+ if (itr == m_formula_results.end())
+ {
+ // This cell doesn't exist yet.
+ pair<model_parser::results_type::iterator, bool> r =
+ m_formula_results.insert(model_parser::results_type::value_type(name_s, fres));
+ if (!r.second)
+ throw model_parser::parse_error("failed to insert a new result.");
+ }
+ else
+ itr->second = fres;
+}
+
+void model_parser::parse_result_cache()
+{
+ parsed_assignment_type res = parse_assignment();
+
+ auto name_s = std::string{res.first};
+
+ formula_result fres;
+ fres.parse(res.second);
+
+ formula_name_t fnt = mp_name_resolver->resolve(name_s, abs_address_t(m_current_sheet,0,0));
+
+ switch (fnt.type)
+ {
+ case formula_name_t::cell_reference:
+ {
+ abs_address_t pos = std::get<address_t>(fnt.value).to_abs(abs_address_t());
+ formula_cell* fc = m_context.get_formula_cell(pos);
+ if (!fc)
+ {
+ std::ostringstream os;
+ os << name_s << " is not a formula cell";
+ throw model_parser::parse_error(name_s);
+ }
+
+ fc->set_result_cache(fres);
+
+ cout << get_display_cell_string(pos) << ": " << fres.str(m_context) << endl;
+ break;
+ }
+ case formula_name_t::range_reference:
+ throw model_parser::parse_error("TODO: we do not support setting result cache to range just yet.");
+ default:
+ {
+ std::ostringstream os;
+ os << "invalid cell name: " << name_s;
+ throw model_parser::parse_error(os.str());
+ }
+ }
+}
+
+void model_parser::parse_table()
+{
+ assert(mp_table_entry);
+
+ // In table mode, each line must be attribute=value.
+ parsed_assignment_type res = parse_assignment();
+ const auto [name, value] = res;
+// const std::string_view name = res.first;
+// const std::string_view value = res.second;
+
+ table_handler::entry& entry = *mp_table_entry;
+
+ if (name == "name")
+ entry.name = m_context.add_string(value);
+ else if (name == "range")
+ {
+ if (!mp_name_resolver)
+ return;
+
+ abs_address_t pos(m_current_sheet,0,0);
+ formula_name_t ret = mp_name_resolver->resolve(value, pos);
+ if (ret.type != formula_name_t::range_reference)
+ throw parse_error("range of a table is expected to be given as a range reference.");
+
+ entry.range = std::get<range_t>(ret.value).to_abs(pos);
+ }
+ else if (name == "columns")
+ parse_table_columns(value);
+ else if (name == "totals-row-count")
+ entry.totals_row_count = to_double(value);
+}
+
+void model_parser::push_table()
+{
+ cout << detail::get_formula_result_output_separator() << endl;
+
+ if (!mp_table_entry)
+ return;
+
+ table_handler::entry& entry = *mp_table_entry;
+
+ const string* ps = m_context.get_string(entry.name);
+ if (ps)
+ cout << "name: " << *ps << endl;
+
+ if (mp_name_resolver)
+ cout << "range: " << mp_name_resolver->get_name(entry.range, abs_address_t(m_current_sheet,0,0), false) << endl;
+
+ cout << "columns: ";
+ std::for_each(entry.columns.begin(), entry.columns.end(), string_printer(m_context, ','));
+ cout << endl;
+
+ cout << "totals row count: " << mp_table_entry->totals_row_count << endl;
+ m_table_handler.insert(mp_table_entry);
+ assert(!mp_table_entry);
+}
+
+void model_parser::parse_named_expression()
+{
+ assert(mp_named_expression);
+
+ parsed_assignment_type res = parse_assignment();
+ if (res.first == "name")
+ mp_named_expression->name = std::string{res.second};
+ else if (res.first == "expression")
+ mp_named_expression->expression = std::string{res.second};
+ else if (res.first == "origin")
+ {
+ const std::string_view s = res.second;
+
+ formula_name_t name =
+ mp_name_resolver->resolve(s, abs_address_t(m_current_sheet,0,0));
+
+ if (name.type != formula_name_t::name_type::cell_reference)
+ {
+ std::ostringstream os;
+ os << "'" << s << "' is not a valid named expression origin.";
+ throw parse_error(os.str());
+ }
+
+ mp_named_expression->origin = std::get<address_t>(name.value).to_abs(abs_address_t(m_current_sheet,0,0));
+ }
+ else if (res.first == "scope")
+ {
+ // Resolve it as a sheet name and store the sheet index if found.
+ mp_named_expression->scope = m_context.get_sheet_index(res.second);
+ if (mp_named_expression->scope == invalid_sheet)
+ {
+ std::ostringstream os;
+ os << "no sheet named '" << res.second << "' exists in the model.";
+ throw parse_error(os.str());
+ }
+ }
+ else
+ {
+ std::ostringstream os;
+ os << "unknown property of named expression '" << res.first << "'";
+ throw parse_error(os.str());
+ }
+}
+
+void model_parser::push_named_expression()
+{
+ assert(mp_named_expression);
+
+ formula_tokens_t tokens = parse_formula_string(
+ m_context, mp_named_expression->origin, *mp_name_resolver,
+ mp_named_expression->expression);
+
+ std::string exp_s = print_formula_tokens(
+ m_context, mp_named_expression->origin, *mp_name_resolver, tokens);
+
+ cout << "name: " << mp_named_expression->name << endl;
+ cout << "expression: " << exp_s << endl;
+ cout << "origin: " << mp_named_expression->origin << endl;
+
+ cout << "scope: ";
+
+ if (mp_named_expression->scope == global_scope)
+ cout << "(global)";
+ else
+ {
+ std::string sheet_name =
+ m_context.get_sheet_name(mp_named_expression->scope);
+
+ if (sheet_name.empty())
+ {
+ ostringstream os;
+ os << "no sheet exists with a sheet index of " << mp_named_expression->scope;
+ throw std::runtime_error(os.str());
+ }
+
+ cout << sheet_name;
+ }
+
+ cout << endl;
+
+ if (mp_named_expression->scope == global_scope)
+ {
+ m_context.set_named_expression(mp_named_expression->name, std::move(tokens));
+ }
+ else
+ {
+ m_context.set_named_expression(
+ mp_named_expression->scope, mp_named_expression->name, std::move(tokens));
+ }
+
+ mp_named_expression.reset();
+}
+
+void model_parser::print_dependency()
+{
+ std::cout << detail::get_formula_result_output_separator() << std::endl;
+ std::cout << m_context.get_cell_tracker().to_string() << std::endl;
+}
+
+void model_parser::parse_table_columns(std::string_view str)
+{
+ assert(mp_table_entry);
+ table_handler::entry& entry = *mp_table_entry;
+
+ const char* p = str.data();
+ const char* pend = p + str.size();
+ std::string_view buf;
+ for (; p != pend; ++p)
+ {
+ if (*p == ',')
+ {
+ // Flush the current column name buffer.
+ string_id_t col_name = empty_string_id;
+ if (!buf.empty())
+ col_name = m_context.add_string(buf);
+
+ entry.columns.push_back(col_name);
+ buf = std::string_view{};
+ }
+ else
+ {
+ if (buf.empty())
+ buf = std::string_view{p, 1u};
+ else
+ buf = std::string_view{buf.data(), buf.size() + 1u};
+ }
+ }
+
+ string_id_t col_name = empty_string_id;
+ if (!buf.empty())
+ col_name = m_context.add_string(buf);
+
+ entry.columns.push_back(col_name);
+}
+
+model_parser::parsed_assignment_type model_parser::parse_assignment()
+{
+ // Parse to get name and value strings.
+ parsed_assignment_type res;
+ std::string_view buf;
+
+ for (; mp_char != mp_end && *mp_char != '\n'; ++mp_char)
+ {
+ if (*mp_char == '=')
+ {
+ if (buf.empty())
+ throw model_parser::parse_error("left hand side is empty");
+
+ res.first = buf;
+ buf = std::string_view{};
+ }
+ else
+ {
+ if (buf.empty())
+ buf = std::string_view{mp_char, 1u};
+ else
+ buf = std::string_view{buf.data(), buf.size() + 1u};
+ }
+ }
+
+ if (!buf.empty())
+ {
+ if (res.first.empty())
+ throw model_parser::parse_error("'=' is missing");
+
+ res.second = buf;
+ }
+
+ return res;
+}
+
+model_parser::cell_def_type model_parser::parse_cell_definition()
+{
+ enum class section_type
+ {
+ name,
+ braced_name,
+ after_braced_name,
+ braced_value,
+ value
+ };
+
+ section_type section = section_type::name;
+
+ cell_def_type ret;
+ ret.type = model_parser::ct_unknown;
+
+ char skip_next = 0;
+
+ std::string_view buf;
+
+ const char* line_head = mp_char;
+
+ for (; mp_char != mp_end && *mp_char != '\n'; ++mp_char)
+ {
+ if (skip_next)
+ {
+ if (*mp_char != skip_next)
+ {
+ std::ostringstream os;
+ os << "'" << skip_next << "' was expected, but '" << *mp_char << "' was found.";
+ throw model_parser::parse_error(os.str());
+ }
+
+ skip_next = 0;
+ continue;
+ }
+
+ switch (section)
+ {
+ case section_type::name:
+ {
+ if (mp_char == line_head && *mp_char == '{')
+ {
+ section = section_type::braced_name;
+ continue;
+ }
+
+ if (is_separator(*mp_char))
+ {
+ // Separator encountered. Set the name and clear the buffer.
+ if (buf.empty())
+ throw model_parser::parse_error("left hand side is empty");
+
+ ret.name = buf;
+ buf = std::string_view{};
+
+ switch (*mp_char)
+ {
+ case '=':
+ ret.type = model_parser::ct_formula;
+ break;
+ case ':':
+ ret.type = model_parser::ct_value;
+ break;
+ case '@':
+ ret.type = model_parser::ct_string;
+ break;
+ default:
+ ;
+ }
+
+ section = section_type::value;
+ continue;
+ }
+
+ break;
+ }
+ case section_type::braced_name:
+ {
+ if (*mp_char == '}')
+ {
+ ret.name = buf;
+ buf = std::string_view{};
+ section = section_type::after_braced_name;
+ continue;
+ }
+
+ break;
+ }
+ case section_type::after_braced_name:
+ {
+ switch (*mp_char)
+ {
+ case '{':
+ section = section_type::braced_value;
+ ret.type = model_parser::ct_formula;
+ skip_next = '=';
+ break;
+ case '=':
+ ret.type = model_parser::ct_formula;
+ section = section_type::value;
+ break;
+ case ':':
+ ret.type = model_parser::ct_value;
+ section = section_type::value;
+ break;
+ case '@':
+ ret.type = model_parser::ct_string;
+ section = section_type::value;
+ break;
+ default:
+ {
+ std::ostringstream os;
+ os << "Unexpected character after braced name: '" << *mp_char << "'";
+ throw model_parser::parse_error(os.str());
+ }
+ }
+
+ continue; // skip this character.
+ }
+ case section_type::braced_value:
+ case section_type::value:
+ default:
+ ;
+ }
+
+ if (buf.empty())
+ buf = std::string_view{mp_char, 1u};
+ else
+ buf = std::string_view{buf.data(), buf.size() + 1u};
+ }
+
+ ret.value = buf;
+
+ if (ret.type == model_parser::ct_value && !ret.value.empty())
+ {
+ // Check if this is a potential boolean value.
+ if (ret.value[0] == 't' || ret.value[0] == 'f')
+ ret.type = model_parser::ct_boolean;
+ }
+
+ if (section == section_type::braced_value)
+ {
+ // Make sure that the braced value ends with '}'.
+ char last = ret.value.back();
+ if (last != '}')
+ {
+ std::ostringstream os;
+ os << "'}' was expected at the end of a braced value, but '" << last << "' was found.";
+ model_parser::parse_error(os.str());
+ }
+ ret.value = std::string_view{ret.value.data(), ret.value.size() - 1u};
+ ret.matrix_value = true;
+ }
+
+ if (ret.name.empty())
+ {
+ if (ret.value.empty())
+ // This is an empty line. Bail out.
+ return ret;
+
+ // Buffer is not empty but name is not given. We must be missing a separator.
+ std::ostringstream os;
+ os << "separator may be missing (name='" << ret.name << "'; value='" << ret.value << "')";
+ throw model_parser::parse_error(os.str());
+ }
+
+ formula_name_t fnt = mp_name_resolver->resolve(ret.name, abs_address_t(m_current_sheet, 0, 0));
+
+ switch (fnt.type)
+ {
+ case formula_name_t::cell_reference:
+ {
+ ret.pos.first = std::get<address_t>(fnt.value).to_abs(abs_address_t(0,0,0));
+ ret.pos.last = ret.pos.first;
+ break;
+ }
+ case formula_name_t::range_reference:
+ {
+ ret.pos = std::get<range_t>(fnt.value).to_abs(abs_address_t(0,0,0));
+ break;
+ }
+ default:
+ {
+ std::ostringstream os;
+ os << "invalid cell name: " << ret.name;
+ throw model_parser::parse_error(os.str());
+ }
+ }
+
+ return ret;
+}
+
+void model_parser::check()
+{
+ cout << detail::get_formula_result_output_separator() << endl
+ << "checking results" << endl
+ << detail::get_formula_result_output_separator() << endl;
+
+ results_type::const_iterator itr = m_formula_results.begin(), itr_end = m_formula_results.end();
+ for (; itr != itr_end; ++itr)
+ {
+ const string& name = itr->first;
+ if (name.empty())
+ throw check_error("empty cell name");
+
+ const formula_result& res = itr->second;
+ cout << name << ": " << res.str(m_context) << endl;
+
+ formula_name_t name_type = mp_name_resolver->resolve(name, abs_address_t());
+ if (name_type.type != formula_name_t::cell_reference)
+ {
+ ostringstream os;
+ os << "unrecognized cell address: " << name;
+ throw std::runtime_error(os.str());
+ }
+
+ abs_address_t addr = std::get<address_t>(name_type.value).to_abs(abs_address_t());
+ cell_access ca = m_context.get_cell_access(addr);
+
+ switch (ca.get_type())
+ {
+ case celltype_t::formula:
+ {
+ formula_result res_cell = ca.get_formula_result();
+
+ if (res_cell != res)
+ {
+ ostringstream os;
+ os << "unexpected result: (expected: " << res.str(m_context) << "; actual: " << res_cell.str(m_context) << ")";
+ throw check_error(os.str());
+ }
+ break;
+ }
+ case celltype_t::numeric:
+ {
+ double actual_val = ca.get_numeric_value();
+ if (actual_val != res.get_value())
+ {
+ ostringstream os;
+ os << "unexpected numeric result: (expected: " << res.get_value() << "; actual: " << actual_val << ")";
+ throw check_error(os.str());
+ }
+ break;
+ }
+ case celltype_t::boolean:
+ {
+ bool actual = ca.get_boolean_value();
+ bool expected = res.get_boolean();
+ if (actual != expected)
+ {
+ ostringstream os;
+ os << std::boolalpha;
+ os << "unexpected boolean result: (expected: " << expected << "; actual: " << actual << ")";
+ throw check_error(os.str());
+ }
+ break;
+ }
+ case celltype_t::string:
+ {
+ std::string_view actual = ca.get_string_value();
+ const std::string& s_expected = res.get_string();
+
+ if (actual != s_expected)
+ {
+ std::ostringstream os;
+ os << "unexpected string result: (expected: '" << s_expected << "'; actual: '" << actual << "')";
+ throw check_error(os.str());
+ }
+
+ break;
+ }
+ case celltype_t::empty:
+ {
+ std::ostringstream os;
+ os << "cell " << name << " is empty.";
+ throw check_error(os.str());
+ }
+ case celltype_t::unknown:
+ {
+ std::ostringstream os;
+ os << "cell type is unknown for cell " << name;
+ throw check_error(os.str());
+ }
+ }
+ }
+}
+
+std::string model_parser::get_display_cell_string(const abs_address_t& pos) const
+{
+ address_t pos_display(pos);
+ pos_display.set_absolute(false);
+ return mp_name_resolver->get_name(pos_display, abs_address_t(), m_print_sheet_name);
+}
+
+std::string model_parser::get_display_range_string(const abs_range_t& pos) const
+{
+ range_t pos_display(pos);
+ pos_display.first.set_absolute(false);
+ pos_display.last.set_absolute(false);
+ return mp_name_resolver->get_name(pos_display, abs_address_t(), m_print_sheet_name);
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */