summaryrefslogtreecommitdiffstats
path: root/doc/cpp/quickstart
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:47:37 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:47:37 +0000
commit00e2eb4fd0266c5be01e3a527a66aaad5ab4b634 (patch)
treea6a58bd544eb0b76b9d3acc678ea88791acca045 /doc/cpp/quickstart
parentInitial commit. (diff)
downloadlibixion-00e2eb4fd0266c5be01e3a527a66aaad5ab4b634.tar.xz
libixion-00e2eb4fd0266c5be01e3a527a66aaad5ab4b634.zip
Adding upstream version 0.19.0.upstream/0.19.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'doc/cpp/quickstart')
-rw-r--r--doc/cpp/quickstart/index.rst13
-rw-r--r--doc/cpp/quickstart/using_document.rst143
-rw-r--r--doc/cpp/quickstart/using_model_context.rst318
3 files changed, 474 insertions, 0 deletions
diff --git a/doc/cpp/quickstart/index.rst b/doc/cpp/quickstart/index.rst
new file mode 100644
index 0000000..5097e02
--- /dev/null
+++ b/doc/cpp/quickstart/index.rst
@@ -0,0 +1,13 @@
+
+.. highlight:: cpp
+
+Quickstart
+==========
+
+.. toctree::
+ :maxdepth: 1
+
+ using_model_context.rst
+ using_document.rst
+
+
diff --git a/doc/cpp/quickstart/using_document.rst b/doc/cpp/quickstart/using_document.rst
new file mode 100644
index 0000000..8c6b779
--- /dev/null
+++ b/doc/cpp/quickstart/using_document.rst
@@ -0,0 +1,143 @@
+
+.. highlight:: cpp
+
+.. _quickstart-document:
+
+Using document class
+====================
+
+In the :ref:`quickstart-model-context` section, we saw an example of how to
+set up a cell value store and run some simple calculations using the
+:cpp:class:`~ixion::model_context` class. While that approach certainly works
+fine, one large drawback is that you do need to manually handle formula tokenization,
+formula cell registration (and un-registration), as well as to trace which cells
+have their values changed and which formula cells have been created or modified.
+This is because the :cpp:class:`~ixion::model_context` class is designed to only
+handle cell value storage, and all other operations related to formula expressions
+and formula cell (re-)calculations have to be done outside of it.
+
+Luckily, Ixion also provides a higher level document class called
+:cpp:class:`~ixion::document` which internally uses :cpp:class:`~ixion::model_context`
+and handles all the formula cell related operations internally. This section
+provides an overview of how to use the :cpp:class:`~ixion::document` class to
+do more or less similar things we did in the :ref:`quickstart-model-context`
+section.
+
+First, we need to instantiate the :cpp:class:`~ixion::document` instance and
+insert a sheet named ``MySheet``.
+
+::
+
+ ixion::document doc;
+ doc.append_sheet("MySheet");
+
+Next, like we did in the previous section, we will insert numbers 1 through 10
+in cells A1 through A10::
+
+ for (ixion::abs_address_t pos(0, 0, 0); pos.row <= 9; ++pos.row)
+ {
+ double value = pos.row + 1.0; // Set the row position + 1 as the cell value.
+ doc.set_numeric_cell(pos, value);
+ }
+
+So far we don't see much of a difference from model_context. Let's now insert
+string values into cells B2 and B3::
+
+ // Insert string values.
+ std::string s = "This cell contains a string value.";
+ doc.set_string_cell("B2", s);
+ doc.set_string_cell("B3", "This too contains a string value.");
+
+Here we see the first difference. When using :cpp:class:`~ixion::document`,
+You can specify the cell position either by :cpp:struct:`~ixion::abs_address_t`
+as with :cpp:class:`~ixion::model_context`, or by a string whose value is the
+name of the cell address. The default address syntax for the string cell address
+is "Excel A1" syntax. You can pick a different syntax by passing a value of type
+:cpp:enum:`~ixion::formula_name_resolver_t` to the constructor.
+
+It's worth noting that, when specifying the cell position as a string value and
+the sheet name is omitted, the first sheet is implied. You can also specify
+the sheet name explicitly as in the following::
+
+ doc.set_string_cell("MySheet!B4", "Yet another string value.");
+
+For a document with only one sheet, it makes no difference whether to include
+the sheet name or leave it out, but if you have more than one sheet, you need to
+specify the sheet name when specifying a cell position on sheets other than the
+first one.
+
+Now, let's insert a a formula into A11 to sum up values in A1:A10, and calculate
+it afterward::
+
+ doc.set_formula_cell("A11", "SUM(A1:A10)");
+ doc.calculate(0);
+
+And fetch the calculated value in A11 and see what the result is::
+
+ double value = doc.get_numeric_value("A11");
+ cout << "value of A11: " << value << endl;
+
+You should see the following output:
+
+.. code-block:: text
+
+ value of A11: 55
+
+It looks about right. The :cpp:func:`~ixion::document::calculate` method takes one
+argument that is the number of threads to use for the calculation. We pass 0 here to
+run the calculation using only the main thread.
+
+Now, let's re-write the formula in cell A11 to take the average of A1:A10 instead,
+run the calculation again, and check the value of A11::
+
+ // Insert a new formula to A11.
+ doc.set_formula_cell("A11", "AVERAGE(A1:A10)");
+ doc.calculate(0);
+
+ value = doc.get_numeric_value("A11");
+ cout << "value of A11: " << value << endl;
+
+The output says:
+
+.. code-block:: text
+
+ value of A11: 5.5
+
+which looks right. Note that, unlike the previous example, there is no need to un-register
+and register cell A11 before and after the edit.
+
+Lastly, let's insert into cell A10 a new formula that contains no references to other cells.
+As this will trigger a re-calculation of cell A11, we will check the values of both A10
+and A11::
+
+ // Overwrite A10 with a formula cell with no references.
+ doc.set_formula_cell("A10", "(100+50)/2");
+ doc.calculate(0);
+
+ value = doc.get_numeric_value("A10");
+ cout << "value of A10: " << value << endl;
+
+ value = doc.get_numeric_value("A11");
+ cout << "value of A11: " << value << endl;
+
+The output will be:
+
+.. code-block:: text
+
+ value of A10: 75
+ value of A11: 12
+
+Notice once again that there is no need to do formula cell registration nor manual tracking
+of dirty formula cells.
+
+
+Conclusion
+----------
+
+In this section, we have performed the same thing we did in the :ref:`quickstart-model-context`
+section, but with much less code, and without the complexity of low-level formula expression
+tokenization, formula cell registration, or manual tracking of modified cells. If you are
+looking to leverage the functionality of Ixion but don't want to deal with lower-level formula
+API, using the :cpp:class:`~ixion::document` class may be just the ticket.
+
+The complete source code of this example is avaiable `here <https://gitlab.com/ixion/ixion/-/blob/master/doc_example/document_simple.cpp>`_.
diff --git a/doc/cpp/quickstart/using_model_context.rst b/doc/cpp/quickstart/using_model_context.rst
new file mode 100644
index 0000000..f37bd5f
--- /dev/null
+++ b/doc/cpp/quickstart/using_model_context.rst
@@ -0,0 +1,318 @@
+
+.. highlight:: cpp
+
+.. _quickstart-model-context:
+
+Using model_context class
+=========================
+
+Create a model context instance
+-------------------------------
+
+When using ixion, the very first step is to create a :cpp:class:`~ixion::model_context`
+instance::
+
+ ixion::model_context cxt;
+
+The :cpp:class:`~ixion::model_context` class represents a document model data
+store that stores cell values spreading over one or more sheets. At the time of construction,
+the model contains no sheets. So the obvious next step is to insert a sheet::
+
+ // First and foremost, insert a sheet.
+ cxt.append_sheet("MySheet");
+
+The :cpp:func:`~ixion::model_context::append_sheet` method will append a new sheet to
+the model. You need to give a name when appending a sheet, and the name must be unique
+for each sheet.
+
+.. note::
+
+ Each sheet has a fixed size which cannot be changed once the :cpp:class:`~ixion::model_context`
+ object is instantiated. The default sheet size is 1048576 rows by 16384 columns. You can
+ specify a custom sheet size by passing a desired sheet size value to the
+ :cpp:class:`~ixion::model_context` constructor at the time of instantiation.
+
+
+Populate model context with values
+----------------------------------
+
+Now that you have your first sheet inserted, let's put in some numeric values. In this example,
+we'll insert into A1:A10 their respective row positions. To insert a numeric value, you use
+:cpp:func:`~ixion::model_context::set_numeric_cell` which takes the position of the cell as its
+first argument and the value to set as its second argument. You need to use :cpp:class:`~ixion::abs_address_t`
+to specify a cell position.
+
+::
+
+ // Now, populate it with some numeric values in A1:A10.
+ for (ixion::abs_address_t pos(0, 0, 0); pos.row <= 9; ++pos.row)
+ {
+ double value = pos.row + 1.0; // Set the row position + 1 as the cell value.
+ cxt.set_numeric_cell(pos, value);
+ }
+
+Note that, since row and column positions are internally 0-based, we add one to emulate how the row
+positions are presented in typical spreadsheet program.
+
+Inserting a string value can be done via :cpp:func:`~ixion::model_context::set_string_cell` in one
+of two ways. The first way is to store the value to a type that decays to ``std::string_view``, such
+as ``std::string``, char array, or string literal, and pass it to the method directly::
+
+ // Insert a string value into B2.
+ ixion::abs_address_t B2(0, 1, 1);
+ std::string s = "This cell contains a string value.";
+ cxt.set_string_cell(B2, s);
+
+ // Insert a literal string value into B3.
+ ixion::abs_address_t B3(0, 2, 1);
+ cxt.set_string_cell(B3, "This too contains a string value.");
+
+The second way is to add your string to the model_context's internal string pool first which will return its
+string ID, and pass that ID to the method::
+
+ // Insert a string value into B4 via string identifier.
+ ixion::string_id_t sid = cxt.add_string("Yet another string value.");
+ ixion::abs_address_t B4(0, 3, 1);
+ cxt.set_string_cell(B4, sid);
+
+The model_context class has two methods for inserting a string to the string pool:
+:cpp:func:`~ixion::model_context::add_string` and :cpp:func:`~ixion::model_context::append_string`. The
+:cpp:func:`~ixion::model_context::add_string` method checks for an existing entry with the same string value
+upon each insertion attempt, and it will not insert the new value if the value already exists in the pool.
+The :cpp:func:`~ixion::model_context::append_string` method, on the other hand, does not check the pool for
+an existing value and always inserts the value. The :cpp:func:`~ixion::model_context::append_string` method
+is appropriate if you know all your string entries ahead of time and wish to bulk-insert them. Otherwise
+using :cpp:func:`~ixion::model_context::add_string` is appropriate in most cases.
+
+
+Insert a formula cell into model context
+----------------------------------------
+
+Inserting a formula cell requires a few extra steps. First, you need to tokenize your formula string, and
+to do that, you need to create an instance of :cpp:class:`~ixion::formula_name_resolver`. The
+formula_name_resolver class is responsible for resolving "names" into references, functions, and named
+expressions names. Ixion provides multiple types of name resolvers, and you specify its type when passing
+an enum value of type :cpp:enum:`~ixion::formula_name_resolver_t` when calling its static
+:cpp:func:`ixion::formula_name_resolver::get` function. In this example, we'll be using the Excel A1
+syntax::
+
+ // Tokenize formula string first.
+ std::unique_ptr<ixion::formula_name_resolver> resolver =
+ ixion::formula_name_resolver::get(ixion::formula_name_resolver_t::excel_a1, &cxt);
+
+You can also optionally pass a memory address of your :cpp:class:`~ixion::model_context` instance which is
+required for resolving sheet names. You can pass a ``nullptr`` if you don't need to resolve sheet names.
+
+Next, let's create a formula string we want to tokenize. Here, we are inserting a formula expression
+**SUM(A1:A10)** into cell A11::
+
+ ixion::abs_address_t A11(0, 10, 0);
+ ixion::formula_tokens_t tokens = ixion::parse_formula_string(cxt, A11, *resolver, "SUM(A1:A10)");
+
+To tokenize a formula string, you call the :cpp:func:`ixion::parse_formula_string` function and pass
+
+* a model_context instance
+* the position of the cell to insert the formula into,
+* a formula_name_resolver instance, and
+* the formula string to tokenize.
+
+The function will then return a sequence of tokens representing the original formula string. Once you
+have the tokens, you can finally pass them to your model_context instance via
+:cpp:func:`~ixion::model_context::set_formula_cell`::
+
+ // Set the tokens into the model.
+ const ixion::formula_cell* cell = cxt.set_formula_cell(A11, std::move(tokens));
+
+There is a few things to note. First, you need to *move* your tokens to the method since instances of
+type :cpp:type:`ixion::formula_tokens_t` are non-copyable and only movable. Second, the method returns
+a pointer to the formula cell instance that just got inserted into the model. We are saving it here
+to use it in the next step below.
+
+When inserting a formula cell, you need to "register" it so that the model can record its reference
+dependencies via :cpp:func:`~ixion::register_formula_cell`::
+
+ // Register this formula cell for automatic dependency tracking.
+ ixion::register_formula_cell(cxt, A11, cell);
+
+Without registering formula cells, you won't be able to determine which formula cells to re-calculate
+for given modified cells. Here we are passing the pointer to the formula cell returned from the previous
+call. This is optional, and you can pass a ``nullptr`` instead. But by passing it you will avoid the
+overhead of searching for the cell instance from the model.
+
+
+Calculate formula cell
+----------------------
+
+Now that we have the formula cell in, let's run our first calculation. To calcualte formula cells, you
+need to first specify a range of modified cells in order to query for all formula cells affected by it
+either directly or indirectly, which we refer to as "dirty" formula cells. Since this is our initial
+calculation, we can simply specify the entire sheet to be "modified" which will effectively trigger all
+formula cells::
+
+ ixion::rc_size_t sheet_size = cxt.get_sheet_size();
+ ixion::abs_range_t entire_sheet(0, 0, 0, sheet_size.row, sheet_size.column); // sheet, row, column, row span, column span
+ ixion::abs_range_set_t modified_cells{entire_sheet};
+
+We will then pass it to :cpp:func:`~ixion::query_and_sort_dirty_cells` to get a sequence of formula cell
+addresses to calculate::
+
+ // Determine formula cells that need re-calculation given the modified cells.
+ // There should be only one formula cell in this example.
+ std::vector<ixion::abs_range_t> dirty_cells = ixion::query_and_sort_dirty_cells(cxt, modified_cells);
+ cout << "number of dirty cells: " << dirty_cells.size() << endl;
+
+Since so far we only have one formula cell, this should only return one range with the size of one row and one column. You
+will see the following output:
+
+.. code-block:: text
+
+ number of dirty cells: 1
+
+Let's inspect which cell it actually refers to::
+
+ cout << "dirty cell: " << dirty_cells[0] << endl;
+
+which will print:
+
+.. code-block:: text
+
+ dirty cell: (sheet:0; row:10; column:0)-(sheet:0; row:10; column:0)
+
+confirming that it certainly points to cell A11. Finally, pass this to :cpp:func:`~ixion::calculate_sorted_cells`::
+
+ // Now perform calculation.
+ ixion::calculate_sorted_cells(cxt, dirty_cells, 0);
+
+to calculate cell A11. After that, you can retrieve the result of the calculation by calling
+:cpp:func:`~ixion::model_context::get_numeric_value` for A11::
+
+ double value = cxt.get_numeric_value(A11);
+ cout << "value of A11: " << value << endl;
+
+You will see the following output:
+
+.. code-block:: text
+
+ value of A11: 55
+
+
+Modify formula cell
+-------------------
+
+Let's say you need to overwrite the formula in A11 to something else. The steps you need to take
+are very similar to the steps for inserting a brand-new formula cell, the only difference being
+that you need to "unregister" the old formula cell before overwriting it.
+
+Let's go through this step by step. First, create new tokens to insert::
+
+ // Insert a new formula to A11.
+ tokens = ixion::parse_formula_string(cxt, A11, *resolver, "AVERAGE(A1:A10)");
+
+This time we are inserting the formula **AVERAGE(A1:A10)** in A11 to overwrite the previous one
+**SUM(A1:A10)**. Before inserting these tokens, first unregister the current formula cell::
+
+ // Before overwriting, make sure to UN-register the old cell.
+ ixion::unregister_formula_cell(cxt, A11);
+
+This will remove the dependency information of the old formula from the model's internal tracker.
+Once that's done, the rest is the same as inserting a new formula::
+
+ // Set and register the new formula cell.
+ cell = cxt.set_formula_cell(A11, std::move(tokens));
+ ixion::register_formula_cell(cxt, A11, cell);
+
+Let's re-calculate the new formula cell. The re-calculation steps are also very similar to the initial
+calculation steps. The first step is to query for all dirty formula cells. This time, however, we don't
+query based on which formula cells are affected by modified cells, which we'll specify as none. Instead,
+we query based on which formula cells have been modified, which in this case is A11::
+
+ // This time, we know that none of the cell values have changed, but the
+ // formula A11 is updated & needs recalculation.
+ ixion::abs_range_set_t modified_formula_cells{A11};
+ dirty_cells = ixion::query_and_sort_dirty_cells(cxt, ixion::abs_range_set_t(), &modified_formula_cells);
+ cout << "number of dirty cells: " << dirty_cells.size() << endl;
+
+As is the first calculation, you should only get one dirty cell address from the :cpp:func:`~ixion::query_and_sort_dirty_cells`
+call. Running the above code should produce:
+
+.. code-block:: text
+
+ number of dirty cells: 1
+
+The rest should be familiar::
+
+ // Perform calculation again.
+ ixion::calculate_sorted_cells(cxt, dirty_cells, 0);
+
+ value = cxt.get_numeric_value(A11);
+ cout << "value of A11: " << value << endl;
+
+You should see the following output when finished:
+
+.. code-block:: text
+
+ value of A11: 5.5
+
+
+Formula cell with no references
+-------------------------------
+
+Next example shows a scenario where you want to overwrite a cell in A10, which
+currently stores a numeric value, with a formula cell that references no other
+cells. Let's add the new formula cell first::
+
+ // Overwrite A10 with a formula cell with no references.
+ ixion::abs_address_t A10(0, 9, 0);
+ tokens = ixion::parse_formula_string(cxt, A10, *resolver, "(100+50)/2");
+ cxt.set_formula_cell(A10, std::move(tokens));
+
+Here, we are not registering this cell since it contains no references hence it
+does not need to be tracked by dependency tracker. Also, since the previous
+cell in A10 is not a formula cell, there is no cell to unregister.
+
+.. warning::
+
+ Technically speaking, every formula cell that contains references to other
+ cells or contains at least one volatile function needs to be registered.
+ Since registering a formula cell that doesn't need to be registered is
+ entirely harmless (albeit a slight overhead), it's generally a good idea to
+ register every new formula cell regardless of its content.
+
+ Likewise, unregistering a formula cell that didn't need to be registered
+ (or wasn't registered) is entirely harmless. Even unregistering a cell
+ that didn't contain a formula cell is harmless, and essentially does
+ nothing. As such, it's probably a good idea to unregister a cell whenever
+ a new cell value is being placed.
+
+Let's obtain all formula cells in need to re-calculation::
+
+ modified_formula_cells = { A10 };
+ dirty_cells = ixion::query_and_sort_dirty_cells(cxt, ixion::abs_range_set_t(), &modified_formula_cells);
+ cout << "number of dirty cells: " << dirty_cells.size() << endl;
+
+Here, we are only passing one modified formula cell which is A10, and no other
+cells being modified. Since cell A11 references ``A1:A10`` and A10's value has
+changed, this should also trigger A11 for re-calculation. Running this code
+should produce the following output:
+
+.. code-block:: text
+
+ number of dirty cells: 2
+
+Let's calculate all affected formula cells and check the results of A10 and A11::
+
+ ixion::calculate_sorted_cells(cxt, dirty_cells, 0);
+ value = cxt.get_numeric_value(A10);
+ cout << "value of A10: " << value << endl;
+ value = cxt.get_numeric_value(A11);
+ cout << "value of A11: " << value << endl;
+
+Running this code should produce the following output:
+
+.. code-block:: text
+
+ value of A10: 75
+ value of A11: 12
+
+The complete source code of this example is avaiable `here <https://gitlab.com/ixion/ixion/-/blob/master/doc_example/model_context_simple.cpp>`_.
+